Feature Selection Toolbox FST4
UTIA · Czech Academy of Sciences
Loading...
Searching...
No Matches
classifier_svm.hpp
Go to the documentation of this file.
1#ifndef FSTCLASSIFIERSVM_H
2#define FSTCLASSIFIERSVM_H
3 /*
27=========================================================================
28Copyright and license:
29 * FST4 software (with the exception of any bundled or externally linked
30 third-party components) is Copyright (C) 2000-present Institute of
31 Information Theory and Automation of the Czech Academy of Sciences (UTIA).
32 * FST4 is free software: you can redistribute it and/or modify it under
33 the terms of the GNU Affero General Public License as published by the
34 Free Software Foundation, version 3. The full text of the license is in
35 the file LICENSE in the distribution root and at
36 https://www.gnu.org/licenses/agpl-3.0.html.
37 * FST4 is distributed in the hope that it will be useful, but WITHOUT ANY
38 WARRANTY; without even the implied warranty of MERCHANTABILITY or
39 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
40 License for more details.
41 * Closed-source use without the AGPL's copyleft obligations requires a
42 commercial license from UTIA; in case of interest write to
43 utia@utia.cas.cz. LICENSING.md in the distribution root explains both
44 options, the contributor license agreement that every contribution
45 requires, and the contacts (technical questions and pull requests:
46 somol@utia.cas.cz, with "FST" in the subject line).
47========================================================================== */
48
49#include <memory>
50#include <cassert>
51#include <iostream>
52#include <sstream>
53#include <list>
54#include <cstring> // memcpy
55#include "error.hpp"
56#include "global.hpp"
57#include "fst_rand.hpp" // FST-owned PRNG: identical random sequences on every platform
58#include "classifier.hpp"
59#include "svm.h"
60
61/*============== Template parameter type naming conventions ==============
62--------- Numeric types: -------------------------------------------------
63DATATYPE - data sample values - usually real numbers (but may be integers
64 in text processing etc.)
65REALTYPE - must be real numbers - for representing intermediate results of
66 calculations like mean, covariance etc.
67IDXTYPE - index values for enumeration of data samples - (nonnegative) integers,
68 extent depends on numbers of samples in data
69DIMTYPE - index values for enumeration of features (dimensions), or classes (not
70 class sizes) - (nonnegative) integers, usually lower extent than IDXTYPE,
71 but be aware of expressions like _classes*_features*_features !
72 in linearized representations of feature matrices for all classes
73BINTYPE - feature selection marker type - represents ca. <10 different feature
74 states (selected, deselected, sel./desel. temporarily 1st nested loop, 2nd...)
75RETURNTYPE - criterion value: real value, but may be extended in future to support
76 multiple values
77--------- Class types: ---------------------------------------------------
78SUBSET - class of class type Subset
79CLASSIFIER - class implementing interface defined in abstract class Classifier
80EVALUATOR - class implementing interface defined in abstract class Sequential_Step
81DISTANCE - class implementing interface defined in abstract class Distance
82DATAACCESSOR - class implementing interface defined in abstract class Data_Accessor
83INTERVALCONTAINER - class of class type Data_Intervaller
84CONTAINER - STL container of class type Data_Interval
85========================================================================== */
86
87namespace FST {
88
89// NOTE: this is the proxy to LIBSVM external library
90// therefore some FST coding conventions can not
91// be strictly kept
92
105
106template<class RETURNTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
107class Classifier_LIBSVM : public Clonable_Impl<Classifier_LIBSVM<RETURNTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>,Classifier<RETURNTYPE,DIMTYPE,SUBSET,DATAACCESSOR>,CLONE_TO_STATELESS,CLONE_INHERIT,CLONE_WEAK> {
108public:
115 typedef std::shared_ptr<DATAACCESSOR> PDataAccessor;
116 typedef std::shared_ptr<SUBSET> const PSubset;
117 typedef typename DATAACCESSOR::PPattern PPattern;
118 Classifier_LIBSVM();
119 virtual ~Classifier_LIBSVM() {notify("Classifier_LIBSVM destructor."); cleanup();}
120
125 void initialize(const PDataAccessor da);
126protected:
130 void allocate();
134 void cleanup();
135public:
136 void set_parameter_C(double newC) {parameters.C = newC;}
137 void set_parameter_gamma(double newgamma) {parameters.gamma = newgamma;}
138 void set_parameter_coef0(double newcoef0) {parameters.coef0 = newcoef0;}
139 void set_kernel_type(int kernel_type) {parameters.kernel_type = kernel_type;}
140 double get_parameter_C() const {return parameters.C;}
141 double get_parameter_gamma() const {return parameters.gamma;}
142 double get_parameter_coef0() const {return parameters.coef0;}
143 int get_kernel_type() const {return parameters.kernel_type;}
144
147 virtual bool classify(DIMTYPE &cls, const PPattern &pattern) override;
151 virtual bool classify_scores(std::vector<RETURNTYPE> &scores, const PPattern &pattern) override;
155 virtual bool train(const PDataAccessor da, const PSubset sub) override;
158 virtual bool test(RETURNTYPE &result, const PDataAccessor da) override;
159
196 bool optimize_parameters(const PDataAccessor da, const PSubset sub, const int max_points=100, const int max_throws=100, const double lgC_min=-5, const double lgC_max=9, const double lggamma_min=-15, const double lggamma_max=3, const double lgcoef0_min=-2, const double lgcoef0_max=5, std::ostream& os=std::cout);
197
198
199 virtual std::ostream& print(std::ostream& os) const override {os << "Classifier_LIBSVM(kernel="<<get_kernel_type()<<")"; return os;}
200private:
201 friend parent_impl;
207 Classifier_LIBSVM(const Classifier_LIBSVM& csvm, int);
208protected:
209 // the following to be initialized using initialize() based on dataaccessor info
211 DIMTYPE _classes;
212 DIMTYPE _features;
213 struct svm_problem problem;
214 struct svm_parameter parameters;
215 struct svm_model *model;
216 struct svm_node *onepattern;
217
219
221 class ParameterSet {
222 public:
223 ParameterSet(): _crit_value(0.0), _Cpar(1.0), _gamma(1.0), _coef0(1.0) {}
224 ParameterSet(const RETURNTYPE crit_value, const double C, const double gamma, const double coef0) : _crit_value(crit_value), _Cpar(C), _gamma(gamma), _coef0(coef0) {}
225 ParameterSet(const ParameterSet& ps) : _crit_value(ps._crit_value), _Cpar(ps._Cpar), _gamma(ps._gamma), _coef0(ps._coef0) {}
226 void operator=(const ParameterSet& ps) {_crit_value=ps._crit_value; _Cpar=ps._Cpar; _gamma=ps._gamma; _coef0=ps._coef0;}
227 RETURNTYPE _crit_value;
228 double _Cpar;
229 double _gamma;
230 double _coef0;
231 };
232 typedef std::list<ParameterSet> PARAMSETLIST;
233
234private:
235 std::unique_ptr<DIMTYPE[]> _index;
236 DIMTYPE _subfeatures;
237 std::vector<double> _dec;
239 void fill_onepattern(const PPattern &pattern)
240 {
241 assert(_index);
242 assert(_subfeatures>0 && _subfeatures<=_features);
243 DIMTYPE nzfeat=0; // no. of nonzero features processed in current pattern
244 for(DIMTYPE f=0;f<_subfeatures;f++)
245 {
246 const DIMTYPE ftmp=_index[f]; // (full set) feature index
247 const double tmp=pattern[ftmp]; // feature value
248 if(tmp!=0.0) {
249 onepattern[nzfeat].index=ftmp+1;
250 onepattern[nzfeat].value=tmp;
251 nzfeat++;
252 }
253 }
254 onepattern[nzfeat].index=-1;
255 }
256};
257
258template<class RETURNTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
259Classifier_LIBSVM<RETURNTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::Classifier_LIBSVM(const Classifier_LIBSVM& csvm, int) : // copy-constructor
261 _classes(csvm._classes),
262 _features(csvm._features),
265{
266 notify("Classifier_LIBSVM (weak) copy-constructor.");
267 // does not copy LibSVM-defined structures, except for 'parameters'
268 // other-than 'parameters' LibSVM structures are only pre-allocated using allocate()
269 parameters.svm_type=csvm.parameters.svm_type;
270 parameters.kernel_type=csvm.parameters.kernel_type;
271 parameters.degree=csvm.parameters.degree;
272 parameters.gamma=csvm.parameters.gamma;
273 parameters.coef0=csvm.parameters.coef0;
274 parameters.cache_size=csvm.parameters.cache_size;
275 parameters.eps=csvm.parameters.eps;
276 parameters.C=csvm.parameters.C;
277 parameters.nr_weight=csvm.parameters.nr_weight;
278 parameters.nu=csvm.parameters.nu;
279 parameters.p=csvm.parameters.p;
280 parameters.shrinking=csvm.parameters.shrinking;
281 parameters.probability=csvm.parameters.probability;
282 // pre-null everything allocate()/cleanup() touch: on an allocation failure cleanup()
283 // would otherwise free() garbage pointers of this partially constructed object
284 problem.x=nullptr;
285 problem.y=nullptr;
286 onepattern=nullptr;
287 parameters.weight_label=nullptr;
288 parameters.weight=nullptr;
289 model=nullptr;
290 allocate();
291 if(csvm.parameters.weight_label) memcpy((void *)parameters.weight_label,(void *)csvm.parameters.weight_label,(long)(_classes) * sizeof(int));
292 if(csvm.parameters.weight) memcpy((void *)parameters.weight,(void *)csvm.parameters.weight,(long)(_classes) * sizeof(double));
293 if(csvm._index) {_index.reset(new DIMTYPE[_features]); memcpy((void *)_index.get(),(void *)(csvm._index).get(),sizeof(DIMTYPE)*_features);}
294}
295
296template<class RETURNTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
297Classifier_LIBSVM<RETURNTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::Classifier_LIBSVM()
298{
299 notify("Classifier_LIBSVM constructor.");
300 _all_patterns=0;
301 _classes=0;
302 _features=0;
303 problem.x=nullptr;
304 problem.y=nullptr;
305 onepattern=nullptr;
306 parameters.weight_label=nullptr;
307 parameters.weight=nullptr;
308 model=nullptr;
309 _subfeatures=0;
310
311 // default parameters
312 parameters.svm_type=C_SVC;
313 parameters.kernel_type=RBF; //LINEAR;
314 parameters.degree=2;
315 parameters.gamma=/*standard_gamma=*/1.0;
316 parameters.coef0=1.0;
317 parameters.cache_size=10;
318 parameters.eps=0.001;
319 parameters.C=/*standard_C=*/1.0;
320 parameters.nr_weight=0;
321 parameters.weight_label=nullptr; // just sharing a pointer
322 parameters.weight=nullptr; // just sharing a pointer
323 parameters.nu=1.0;
324 parameters.p=1.0;
325 parameters.shrinking=0;
326 parameters.probability=0;
327 svm_class_weighing=false;
328}
329
330template<class RETURNTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
331bool Classifier_LIBSVM<RETURNTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::optimize_parameters(const PDataAccessor da, const PSubset sub, const int max_points, const int max_throws, const double lgC_min, const double lgC_max, const double lggamma_min, const double lggamma_max, const double lgcoef0_min, const double lgcoef0_max, std::ostream& os)
332{
333 typename PARAMSETLIST::iterator iter; // cursor over _paramlist for the scan loops below
334 // NOTE: assumes Classifier_LIBSVM::initialize() had been called for this da
335 // assumes initial C, gamma, and coef0 have been set using set_parameter_*(), or that
336 // the default values are OK (the initial values are the first candidate evaluated)
337 assert(da);
338 assert(sub);
339 assert(sub->get_d()>0);
340 assert(max_points>0);
341 assert(max_throws>0);
342 assert(lgC_max>lgC_min);
343 assert(lggamma_max>lggamma_min);
344 assert(lgcoef0_max>lgcoef0_min);
345 const double lgCrange=lgC_max-lgC_min;
346 const double lggammarange=lggamma_max-lggamma_min;
347 const double lgcoef0range=lgcoef0_max-lgcoef0_min;
348 PARAMSETLIST _paramlist;
349 ParameterSet tmpset, bestset;
350 // evaluate the initial (current) parameter set
351 RETURNTYPE val, maxval, firstval;
352 RETURNTYPE result=0.0;
353 RETURNTYPE cnt=0.0;
354 for(bool b=da->getFirstSplit();b==true;b=da->getNextSplit()) {
355 if(!train(da,sub)) return false;
356 if(!test(val,da)) return false;
357 result+=val;
358 cnt+=1.0;
359 }
360 firstval=maxval=val=result/(RETURNTYPE)cnt;
361 {
362 std::ostringstream sos;
363 sos << "SVM before optimization: accuracy=" << val << ", C=" << get_parameter_C();
364 switch(get_kernel_type()) {
365 case LINEAR: sos << std::endl; break;
366 case RBF: sos << ", gamma=" << get_parameter_gamma() << std::endl; break;
367 case POLY:
368 case SIGMOID: sos << ", gamma=" << get_parameter_gamma() << ", coef0=" << get_parameter_coef0() << std::endl; break;
369 }
370 syncout::print(os,sos);
371 }
373 for(int i=0;i<max_points;i++)
374 { // find and evaluate next parameter set candidate
375 double maxdistmin=0.0;
376 for(int j=0;j<max_throws;j++)
377 { // find next parameter set candidate
378 // the bounds are natural logarithms: a candidate is exp(uniform(min,max))
379 tmpset._Cpar =exp(lgC_min+(((double)fst_rand()*(double)(lgC_max-lgC_min))/(double)FST_RAND_MAX));
380 tmpset._gamma=exp(lggamma_min+(((double)fst_rand()*(double)(lggamma_max-lggamma_min))/(double)FST_RAND_MAX));
381 tmpset._coef0=exp(lgcoef0_min+(((double)fst_rand()*(double)(lgcoef0_max-lgcoef0_min))/(double)FST_RAND_MAX));
382
383 double distmin=-1.0; // potential new candidate (weighted) distance to already evaluated candidates
384 for(iter=_paramlist.begin();iter!=_paramlist.end();iter++)
385 {
386 double dist=0.0;
387 assert(get_kernel_type()==LINEAR || get_kernel_type()==POLY || get_kernel_type()==RBF || get_kernel_type()==SIGMOID);
388 switch(get_kernel_type()) {
389 case LINEAR: dist=sqrt(((log(tmpset._Cpar)-log(iter->_Cpar))/lgCrange)*((log(tmpset._Cpar)-log(iter->_Cpar))/lgCrange)); break;
390 case RBF: dist=sqrt(((log(tmpset._Cpar)-log(iter->_Cpar))/lgCrange)*((log(tmpset._Cpar)-log(iter->_Cpar))/lgCrange)+((log(tmpset._gamma)-log(iter->_gamma))/lggammarange)*((log(tmpset._gamma)-log(iter->_gamma))/lggammarange)); break;
391 case POLY:
392 case SIGMOID: dist=sqrt(((log(tmpset._Cpar)-log(iter->_Cpar))/lgCrange)*((log(tmpset._Cpar)-log(iter->_Cpar))/lgCrange)+((log(tmpset._gamma)-log(iter->_gamma))/lggammarange)*((log(tmpset._gamma)-log(iter->_gamma))/lggammarange)+((log(tmpset._coef0)-log(iter->_coef0))/lgcoef0range)*((log(tmpset._coef0)-log(iter->_coef0))/lgcoef0range)); break;
393 }
394 // candidates more distant from all others are preferred
395 // distances are weighed to penalize closeness to worse-performing candidates
396 // NOTE: the weight is the candidate's accuracy, so an already-evaluated candidate
397 // of accuracy 0 nullifies every distance measured against it (with asserts
398 // disabled this makes the draw for the next point effectively arbitrary)
399 assert(iter->_crit_value>0.0);
400 if(distmin<0 || dist*iter->_crit_value<distmin) distmin=dist*iter->_crit_value;
401 }
402 assert(distmin>=0.0);
403 if(distmin>maxdistmin)
404 { // better (more distant from those already tested, esp. from the bad ones) parameter set candidate found
405 maxdistmin=distmin;
406 bestset=tmpset;
407 }
408 }
409 // evaluate the chosen candidate and add it to list
410 set_parameter_C(bestset._Cpar);
413 assert(svm_check_parameter(&problem,&parameters)==nullptr);
414 result=0.0;
415 cnt=0.0;
416 for(bool b=da->getFirstSplit();b==true;b=da->getNextSplit()) {
417 if(!train(da,sub)) return false;
418 if(!test(val,da)) return false;
419 result+=val;
420 cnt+=1.0;
421 }
422 val=result/(RETURNTYPE)cnt;
423 bestset._crit_value=val;
424 _paramlist.push_back(bestset);
425 {
426 std::ostringstream sos;
427 sos << i+1 << ". ";
428 if(val>maxval) {maxval=val; sos << "BEST accuracy=";} else sos << "test accuracy=";
429 sos << bestset._crit_value << ", C=" << get_parameter_C();
430 switch(get_kernel_type()) {
431 case LINEAR: sos << std::endl; break;
432 case RBF: sos << ", gamma=" << get_parameter_gamma() << std::endl; break;
433 case POLY:
434 case SIGMOID: sos << ", gamma=" << get_parameter_gamma() << ", coef0=" << get_parameter_coef0() << std::endl; break;
435 }
436 syncout::print(os,sos);
437 }
438 }
439 // identify the best parameter set among all tested ones
440 assert(!_paramlist.empty());
441 iter=_paramlist.begin();
442 bestset=(*iter);
443 while(iter!=_paramlist.end())
444 {
445 if(iter->_crit_value>bestset._crit_value) bestset=(*iter);
446 iter++;
447 }
448 set_parameter_C(bestset._Cpar);
451 {
452 std::ostringstream sos;
453 sos << "SVM after optimization: accuracy=" << bestset._crit_value << ", C=" << get_parameter_C();
454 switch(get_kernel_type()) {
455 case LINEAR: sos << std::endl; break;
456 case RBF: sos << ", gamma=" << get_parameter_gamma() << std::endl; break;
457 case POLY:
458 case SIGMOID: sos << ", gamma=" << get_parameter_gamma() << ", coef0=" << get_parameter_coef0() << std::endl; break;
459 }
460 sos << "Accuracy difference=" << bestset._crit_value-firstval << std::endl << std::endl;
461 syncout::print(os,sos);
462 }
463 return true;
464}
465
466template<class RETURNTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
468{
469 try {
470 // NOTE: cleanup() must NOT be called here - it would destroy the original data
471 // when allocate() runs from the weak copy-constructor
472 assert(_all_patterns>0);
473 assert(_classes>1);
474 assert(_features>0);
475
476 problem.y = (double *) malloc((std::size_t)(_all_patterns) * sizeof(double));
477 problem.x = (struct svm_node **)malloc((std::size_t)(_all_patterns) * sizeof(struct svm_node *));
478 parameters.weight_label = (int *) malloc((std::size_t)(_classes) * sizeof(int));
479 parameters.weight = (double *) malloc((std::size_t)(_classes) * sizeof(double));
480 onepattern = (struct svm_node *) malloc((std::size_t)(_features+1) * sizeof(struct svm_node));
481 // malloc does not throw: the failure must be tested explicitly, or the catch
482 // below is dead code and a null problem.x gets dereferenced further down
483 if(!problem.y || !problem.x || !parameters.weight_label || !parameters.weight || !onepattern)
484 throw fst_error("Classifier_LIBSVM allocation problem (malloc failed).");
485 for(IDXTYPE p=0;p<_all_patterns;p++) problem.x[p]=nullptr;
486 // pre-allocate all problem.x[] arrays to max size, to prevent re-allocation speed efficiency problems
487 // - WARNING! this leads to uneconomical use of memory
488 for(IDXTYPE p=0;p<_all_patterns;p++) {
489 problem.x[p] = (struct svm_node *) malloc((long)(_features+1) * sizeof(struct svm_node));
490 if(!problem.x[p]) throw fst_error("Classifier_LIBSVM allocation problem (malloc failed).");
491 }
492
493 // class penalty weighing disabled
494 parameters.nr_weight=0;
496 assert(svm_check_parameter(&problem,&parameters)==nullptr);
497 }
498 catch (...) {
499 cleanup();
500 throw fst_error("Classifier_LIBSVM allocation problem.");
501 }
502}
503
504template<class RETURNTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
506{
507 assert(da);
508 try {
509 cleanup();
510 _all_patterns = da->getClassSizeSum(); assert(_all_patterns>0);
511 _classes = da->getNoOfClasses(); assert(_classes>1);
512 _features = da->getNoOfFeatures(); assert(_features>0);
513 allocate();
514 }
515 catch (...) {
516 cleanup();
517 throw fst_error("Classifier_LIBSVM initialization problem.");
518 }
519}
520
521template<class RETURNTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
523{
524 // NOTE: called from destructor
525 if(problem.x!=nullptr) {
526 for(IDXTYPE p=0;p<_all_patterns;p++) if(problem.x[p]!=nullptr) {free(problem.x[p]); problem.x[p]=nullptr;}
527 free(problem.x); problem.x=nullptr; // the array only, not the actual pointers
528 }
529 if(problem.y!=nullptr) free(problem.y);
530 problem.y=nullptr;
531 if(onepattern!=nullptr) free(onepattern);
532 onepattern=nullptr;
533 if(parameters.weight_label!=nullptr) free(parameters.weight_label);
534 parameters.weight_label=nullptr;
535 if(parameters.weight!=nullptr) free(parameters.weight);
536 parameters.weight=nullptr;
537 if(model!=nullptr) {svm_free_and_destroy_model(&model); model=nullptr;}
538 // _index is sized after _features, so it must be dropped here too: a re-initialize()
539 // on a larger dataset would otherwise keep (and overrun) the smaller buffer
540 _index.reset();
541
543 _classes=0;
544 _features=0;
545}
546
547template<class RETURNTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
549{
550 assert(da);
551 assert(_classes==da->getNoOfClasses());
552 assert(_features==da->getNoOfFeatures());
553 assert(_all_patterns==da->getClassSizeSum());
554 assert(problem.x!=nullptr);
555 assert(problem.y!=nullptr);
556 assert(onepattern!=nullptr);
557 assert(parameters.weight_label!=nullptr);
558 assert(parameters.weight!=nullptr);
559 assert(svm_check_parameter(&problem,&parameters)==nullptr);
560 try {
561 // feature index buffering to accelerate data transfer
562 DIMTYPE f,ftmp;
563 if(!_index || _features<sub->get_n_raw()) _index.reset(new DIMTYPE[_features]);
564 _subfeatures=0;
565 for(DIMTYPE fsel : sub->selected_features()) {assert(_subfeatures<_features); _index[_subfeatures++]=fsel;}
566#ifdef DEBUG
567 {
568 std::ostringstream sos;
569 sos << "index: "; for(f=0;f<_subfeatures;f++) sos << _index[f] << " ";
570 syncout::print(std::cout,sos);
571 }
572#endif
573 assert(_subfeatures==sub->get_d_raw());
574
575 typename DATAACCESSOR::PPattern p,ptmp;
576 IDXTYPE s,i;
577 IDXTYPE count;
578 DIMTYPE nzfeat;
579 double tmp;
580
581
582 count=0;
583 for(DIMTYPE c_train=0;c_train<_classes;c_train++)
584 {
585 da->setClass(c_train);
586 for(const auto &blk : da->blocks(TRAIN)) for(i=0,p=blk.data,s=blk.size;i<s;i++)
587 {
588 problem.y[count]=c_train; // current pattern class info
589 nzfeat=0; // no. of nonzero features processed in current pattern
590 ptmp=&p[(std::size_t)i*_features]; // temp pointer to current pattern
591 for(f=0;f<_subfeatures;f++)
592 {
593 ftmp=_index[f]; // (full set) feature index
594 tmp=ptmp[ftmp]; // feature value
595 if(tmp!=0.0) {
596 problem.x[count][nzfeat].index=ftmp+1;
597 problem.x[count][nzfeat].value=tmp;
598 nzfeat++;
599 }
600 }
601 problem.x[count][nzfeat].index=-1;
602 count++;
603 }
604 }
605 assert(count>0);
606 problem.l=count;
607 if(model!=nullptr) svm_free_and_destroy_model(&model);
608 // train SVM model
609 model=svm_train(&problem, &parameters);
610 }
611 catch(...) {
612 cleanup();
613 throw fst_error("Classifier_LIBSVM::train() error.");
614 }
615 if(model==nullptr) return false;
616 return true;
617}
618
619template<class RETURNTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
620bool Classifier_LIBSVM<RETURNTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::classify(DIMTYPE &cls, const PPattern &pattern) // classifies pattern, returns the respective class index
621{
622 if(model==nullptr) throw fst_error("Classifier_LIBSVM::classify(): no trained model. Note that clones do not inherit the trained state (LibSVM models can not be cloned) - call train() first.");
623 assert(svm_check_parameter(&problem,&parameters)==nullptr);
624 try {
625 // assumes feature index buffering to accelerate data transfer
626 fill_onepattern(pattern);
627 // now classify the pattern
628 const RETURNTYPE tmpval=svm_predict(model, onepattern);
629 cls=(DIMTYPE)tmpval;
630 }
631 catch(...) {
632 cleanup();
633 throw fst_error("Classifier_LIBSVM::classify() error.");
634 }
635 return true;
636}
637
638template<class RETURNTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
640{
641 // per-class scores from the (deterministic) LibSVM decision values: two-class = the
642 // single decision value signed per label side; K>2 = the one-vs-one decision values
643 // aggregated per class (score[i] += dec(i,j), score[j] -= dec(i,j) over all pairs --
644 // continuous, deterministic, no ties beyond genuine score equality).
645 // NOTE: LibSVM's probability mode is deliberately NOT used here - it retrains with an
646 // internal cross-validation driven by libc rand() (nondeterministic across runs
647 // and platform-dependent; vendored LibSVM is the fst_rand-shim exception)
648 if(model==nullptr) throw fst_error("Classifier_LIBSVM::classify_scores(): no trained model. Note that clones do not inherit the trained state (LibSVM models can not be cloned) - call train() first.");
649 try {
650 fill_onepattern(pattern); // same transfer as classify()
651 const int k=svm_get_nr_class(model); // the MODEL's class count guards the accesses below, not the
652 // dataset's: a training fold that contained a single class
653 // yields k==1, and model->label[1] then does not exist
654 if(_classes==2) { // two-class: one decision value, split over the two label sides
655 if(k!=2) return false;
656 double dec=0.0;
657 svm_predict_values(model, onepattern, &dec); // sign points towards model->label[0]
658 scores.assign(2,(RETURNTYPE)0.0);
659 const int l0=model->label[0], l1=model->label[1];
660 if(l0<0 || l0>1 || l1<0 || l1>1) return false;
661 scores[l0]=(RETURNTYPE)dec;
662 scores[l1]=(RETURNTYPE)(-dec);
663 }
664 else { // K>2: aggregate the one-vs-one decision values per class (sign of pair (i,j) points towards model->label[i]);
665 // classes unseen in training never appear among model labels and keep score 0 (deterministic)
666 if(k<2) return false;
667 _dec.assign((size_t)k*(size_t)(k-1)/2,0.0); // member scratch: avoids a per-pattern allocation
668 svm_predict_values(model, onepattern, _dec.data());
669 scores.assign(_classes,(RETURNTYPE)0.0);
670 int pos=0;
671 for(int i=0;i<k;i++) for(int j=i+1;j<k;j++,pos++) {
672 const int li=model->label[i], lj=model->label[j];
673 if(li<0 || li>=(int)_classes || lj<0 || lj>=(int)_classes) return false;
674 scores[li]+=(RETURNTYPE)_dec[pos];
675 scores[lj]-=(RETURNTYPE)_dec[pos];
676 }
677 }
678 }
679 catch(...) {
680 cleanup();
681 throw fst_error("Classifier_LIBSVM::classify_scores() error.");
682 }
683 return true;
684}
685
686template<class RETURNTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
688{
689 assert(da);
690 assert(_classes==da->getNoOfClasses());
691 assert(_features==da->getNoOfFeatures());
692 assert(_all_patterns==da->getClassSizeSum());
693 if(model==nullptr) throw fst_error("Classifier_LIBSVM::test(): no trained model. Note that clones do not inherit the trained state (LibSVM models can not be cloned) - call train() first.");
694 assert(svm_check_parameter(&problem,&parameters)==nullptr);
695 try {
696 // assumes feature index buffering to accelerate data transfer
697 assert(_index);
698 assert(_subfeatures>0);
699
700 typename DATAACCESSOR::PPattern p;
701 IDXTYPE s,i;
702 IDXTYPE count, correct;
703 DIMTYPE clstmp;
704
705
706 count=0;
707 correct=0;
708 for(DIMTYPE c_test=0;c_test<_classes;c_test++)
709 {
710 da->setClass(c_test);
711 for(const auto &blk : da->blocks(TEST)) for(i=0,p=blk.data,s=blk.size;i<s;i++)
712 {
713 if(!classify(clstmp,&p[(std::size_t)i*_features])) return false;
714 if(clstmp==c_test) correct++;
715 count++;
716 }
717 }
718 assert(count>0);
719 result=(RETURNTYPE)correct/(RETURNTYPE)count;
720#ifdef DEBUG
721 {
722 std::ostringstream sos;
723 sos << " result=" << result << std::endl;
724 syncout::print(std::cout,sos);
725 }
726#endif
727 }
728 catch(...) {
729 cleanup();
730 throw fst_error("Classifier_LIBSVM::test() error.");
731 }
732 return true;
733}
734
735} // namespace
736#endif // FSTCLASSIFIERSVM_H ///:~
Nested class to hold parameter candidates in the course of optimize_parameters() run.
Definition classifier_svm.hpp:221
double _Cpar
candidate C value (named to avoid clashing with the enclosing class's parameters.C)
Definition classifier_svm.hpp:228
void operator=(const ParameterSet &ps)
copy-assignment
Definition classifier_svm.hpp:226
double _coef0
candidate coef0 value
Definition classifier_svm.hpp:230
double _gamma
candidate gamma value
Definition classifier_svm.hpp:229
RETURNTYPE _crit_value
mean criterion value (accuracy, averaged over the accessor's split loop) this candidate achieved,...
Definition classifier_svm.hpp:227
ParameterSet(const RETURNTYPE crit_value, const double C, const double gamma, const double coef0)
constructs a fully-specified candidate
Definition classifier_svm.hpp:224
ParameterSet(const ParameterSet &ps)
copy-constructor
Definition classifier_svm.hpp:225
void initialize(const PDataAccessor da)
Must be called (once, or again after a dataset-shape change) before train()/test()....
Definition classifier_svm.hpp:505
std::shared_ptr< DATAACCESSOR > PDataAccessor
shared pointer to the data accessor supplying train/test blocks
Definition classifier_svm.hpp:115
std::shared_ptr< SUBSET > const PSubset
shared pointer to the (fixed) feature subset being trained/evaluated
Definition classifier_svm.hpp:116
std::unique_ptr< DIMTYPE[]> _index
subset position -> raw feature index map, filled by train() from sub->selected_features(); regrown on...
Definition classifier_svm.hpp:235
void set_parameter_gamma(double newgamma)
sets the LibSVM kernel gamma (RBF/POLY/SIGMOID)
Definition classifier_svm.hpp:137
DIMTYPE _subfeatures
current subset size (number of valid entries in _index[]), set by train()
Definition classifier_svm.hpp:236
Clonable_Impl< Classifier_LIBSVM, Classifier< RETURNTYPE, DIMTYPE, SUBSET, DATAACCESSOR >, CLONE_TO_STATELESS, CLONE_INHERIT, CLONE_WEAK > parent_impl
Definition classifier_svm.hpp:114
DIMTYPE _classes
class count, set by initialize()
Definition classifier_svm.hpp:211
void allocate()
Definition classifier_svm.hpp:467
double get_parameter_coef0() const
current LibSVM kernel coef0 (POLY/SIGMOID)
Definition classifier_svm.hpp:142
bool svm_class_weighing
vestigial class-weighing switch: set false by the constructor and never read – allocate() uncondition...
Definition classifier_svm.hpp:218
void fill_onepattern(const PPattern &pattern)
transfers the pattern's nonzero selected features into onepattern (the shared classify()/classify_sco...
Definition classifier_svm.hpp:239
bool optimize_parameters(const PDataAccessor da, const PSubset sub, const int max_points=100, const int max_throws=100, const double lgC_min=-5, const double lgC_max=9, const double lggamma_min=-15, const double lggamma_max=3, const double lgcoef0_min=-2, const double lgcoef0_max=5, std::ostream &os=std::cout)
Searches for better SVM hyperparameters C, gamma and coef0 and adopts the best set found.
Definition classifier_svm.hpp:331
virtual bool classify(DIMTYPE &cls, const PPattern &pattern) override
Definition classifier_svm.hpp:620
void set_parameter_coef0(double newcoef0)
sets the LibSVM kernel coef0 (POLY/SIGMOID)
Definition classifier_svm.hpp:138
IDXTYPE _all_patterns
total pattern count across all classes, set by initialize()
Definition classifier_svm.hpp:210
struct svm_node * onepattern
single-pattern LibSVM transfer buffer, filled by fill_onepattern() for classify()/classify_scores()
Definition classifier_svm.hpp:216
DATAACCESSOR::PPattern PPattern
one data sample as exposed by DATAACCESSOR
Definition classifier_svm.hpp:117
int get_kernel_type() const
see svm.h for admissible values
Definition classifier_svm.hpp:143
void set_kernel_type(int kernel_type)
LINEAR/POLY/RBF/SIGMOID – see svm.h for admissible values.
Definition classifier_svm.hpp:139
virtual bool classify_scores(std::vector< RETURNTYPE > &scores, const PPattern &pattern) override
Definition classifier_svm.hpp:639
void set_parameter_C(double newC)
sets the LibSVM regularization parameter C
Definition classifier_svm.hpp:136
double get_parameter_gamma() const
current LibSVM kernel gamma (RBF/POLY/SIGMOID)
Definition classifier_svm.hpp:141
struct svm_problem problem
LibSVM training data (problem.x/y), rebuilt by train() from the designated TRAIN blocks.
Definition classifier_svm.hpp:213
struct svm_model * model
the trained LibSVM model, or nullptr before the first successful train()
Definition classifier_svm.hpp:215
std::list< ParameterSet > PARAMSETLIST
the growing list of parameter candidates evaluated so far, in evaluation order
Definition classifier_svm.hpp:232
virtual bool test(RETURNTYPE &result, const PDataAccessor da) override
Definition classifier_svm.hpp:687
virtual bool train(const PDataAccessor da, const PSubset sub) override
Definition classifier_svm.hpp:548
std::vector< double > _dec
Definition classifier_svm.hpp:237
DIMTYPE _features
raw (full, unnarrowed) feature count, set by initialize()
Definition classifier_svm.hpp:212
struct svm_parameter parameters
LibSVM training parameters (C, gamma, kernel_type, ...) – see set_parameter_*()/set_kernel_type().
Definition classifier_svm.hpp:214
void cleanup()
Definition classifier_svm.hpp:522
virtual std::ostream & print(std::ostream &os) const override
writes a human-readable identification of the object; base default writes nothing
Definition classifier_svm.hpp:199
double get_parameter_C() const
current LibSVM regularization parameter C
Definition classifier_svm.hpp:140
CRTP mixin implementing the Clonable protocol for a concrete class.
Definition clonable.hpp:184
FST4 specific error passing class.
Definition error.hpp:61
static void print(std::ostream &os, std::ostringstream &sos)
Writes sos's buffered content to os, serializing concurrent writers under FST_THREADED via a shared m...
Definition global.hpp:86
Defines interface for classifier implementations.
FST4 specific error passing class.
FST4-owned pseudo-random number generator (UCRT-compatible LCG), replacing platform-specific std::ran...
Global definitions.
The Feature Selection Toolbox 4 library: every library component – data access, criteria and classifi...
Definition data_accessor.hpp:80
@ CLONE_TO_STATELESS
forward to stateless_clone() (Classifier_LIBSVM contract)
Definition clonable.hpp:152
@ CLONE_WEAK
new Derived(*this,0) via the weak/referencing copy-constructor, then set_cloned()
Definition clonable.hpp:150
@ CLONE_INHERIT
do not add behavior; defer to the Base class body (typically a family-level throwing default)
Definition clonable.hpp:147
int fst_rand()
Returns the next pseudo-random draw in 0..FST_RAND_MAX; use instead of std::rand().
Definition fst_rand.hpp:93
const int FST_RAND_MAX
Maximum value fst_rand() can return; use instead of the libc macro RAND_MAX.
Definition fst_rand.hpp:101
void notify(const std::string msg)
Prints msg followed by a newline via syncout::print(); a no-op unless the preprocessor macro DEBUG is...
Definition global.hpp:112
@ TEST
the split's test part
Definition data_accessor.hpp:86
@ TRAIN
the split's training part
Definition data_accessor.hpp:85
Copyright © 2000–present ÚTIA, Institute of Information Theory and Automation of the Czech Academy of Sciences · Contacts · This site sets no cookies and uses no analytics.