/** * @file VCSVMLight.cpp * @brief Simple Nearest Neighbour Implementation * @author Erik Rodner * @date 10/25/2007 */ #ifdef NICE_USELIB_SVMLIGHT #include #include "vislearning/classifier/vclassifier/VCSVMLight.h" // just because of some macro problems # define SVM_LINEAR 0 /* linear kernel type */ # define SVM_POLY 1 /* polynoial kernel type */ # define SVM_RBF 2 /* rbf kernel type */ # define SVM_SIGMOID 3 /* sigmoid kernel type */ # define SVM_CUSTOM 4 /* custom kernel type */ using namespace OBJREC; using namespace std; using namespace NICE; VCSVMLight::VCSVMLight ( const Config *_conf, const string & section ) : VecClassifier ( _conf ) { finalModel = NULL; std::string kernel_type_s = _conf->gS(section, "kernel", "rbf"); if ( (kernel_type_s == "none") || (kernel_type_s == "linear") ) { kernel_type = SVM_LINEAR; } else if ( (kernel_type_s == "poly") ) { kernel_type = SVM_POLY; } else if ( (kernel_type_s == "rbf") ) { kernel_type = SVM_RBF; } else if ( (kernel_type_s == "sigmoid") ) { kernel_type = SVM_SIGMOID; } else if ( (kernel_type_s == "custom") ) { kernel_type = SVM_CUSTOM; } else { fthrow ( Exception, "Kernel method " << kernel_type_s << " not supported." ); } std::string normalization_type_s = _conf->gS(section, "normalization_type", "euclidean" ); if ( normalization_type_s == "none" ) { normalization_type = SVM_NORMALIZATION_NONE; } else if ( normalization_type_s == "euclidean" ) { normalization_type = SVM_NORMALIZATION_EUCLIDEAN; } else if ( normalization_type_s == "maxmin" ) { normalization_type = SVM_NORMALIZATION_01; } else { fthrow ( Exception, "Normalization method " << normalization_type << " not supported." ); } fprintf (stderr, "VCSVMLight: kernel %d (%s) normalization %d (%s)\n", kernel_type, kernel_type_s.c_str(), normalization_type, normalization_type_s.c_str() ); poly_degree = _conf->gI(section, "poly_degree", 1); rbf_gamma = _conf->gD(section, "rbf_gamma", 1.0); sigmoidpoly_scale = _conf->gD(section, "sigmoidpoly_scale", 1.0); sigmoidpoly_bias = _conf->gD(section, "sigmoidpoly_bias", 1.0); svm_c = _conf->gD(section, "C", 0.0 ); use_crossvalidation = _conf->gB(section, "use_crossvalidation", true ); optimize_parameters = _conf->gB(section, "optimize_parameters", true ); rbf_gamma_min = _conf->gD(section, "rbf_gamma_min", 0.1 ); rbf_gamma_max = _conf->gD(section, "rbf_gamma_max", 2.0 ); rbf_gamma_step = _conf->gD(section, "rbf_gamma_step", 0.3 ); svm_c_min = _conf->gD(section, "svm_c_min", 0.0 ); svm_c_max = _conf->gD(section, "svm_c_max", 9.0 ); svm_c_step = _conf->gD(section, "svm_c_step", 1.0 ); bool calibrate_probabilities = _conf->gB(section, "calibrate_probabilities", true ); if ( calibrate_probabilities ) { logreg = new VCLogisticRegression ( _conf ); } else { logreg = NULL; } // used in SVMLight verbosity = _conf->gI(section, "verbose", 1 ); if ( optimize_parameters && use_crossvalidation ) { fprintf (stderr, "VCSVMLight: SVMLight verbosity activated as a workaround for a bug within SVMLight\n"); verbosity = 1; } maxClassNo = 1; } VCSVMLight::VCSVMLight ( const VCSVMLight & classifier ) : VecClassifier() { this->max = classifier.max; this->min = classifier.min; this->normalization_type = classifier.normalization_type; this->kernel_type = classifier.kernel_type; this->poly_degree = classifier.poly_degree; this->rbf_gamma = classifier.rbf_gamma; this->sigmoidpoly_scale = classifier.sigmoidpoly_scale; this->sigmoidpoly_bias = classifier.sigmoidpoly_bias; this->svm_c = classifier.svm_c; this->use_crossvalidation = classifier.use_crossvalidation; this->optimize_parameters = classifier.optimize_parameters; this->rbf_gamma_min = classifier.rbf_gamma_min; this->rbf_gamma_max = classifier.rbf_gamma_max; this->rbf_gamma_step = classifier.rbf_gamma_step; this->svm_c_min = classifier.svm_c_min; this->svm_c_max = classifier.svm_c_max; this->svm_c_step = classifier.svm_c_step; if ( classifier.logreg != NULL ) this->logreg = classifier.logreg->clone(); else this->logreg = NULL; if ( classifier.finalModel != NULL ) this->finalModel = copy_model ( classifier.finalModel ); else this->finalModel = NULL; } VCSVMLight::~VCSVMLight() { } void VCSVMLight::normalizeVector ( int normalization_type, NICE::Vector & x ) const { assert ( x.size() > 0 ); if ( normalization_type == SVM_NORMALIZATION_EUCLIDEAN ) { double length = x.normL2(); if ( fabs(length) > 1e-12 ) for ( uint i = 0 ; i < x.size() ; i++ ) x[i] /= length; } else if ( normalization_type == SVM_NORMALIZATION_01 ) { for ( uint i = 0 ; i < x.size() ; i++ ) if ( fabs(max[i]-min[i]) > 1e-20 ) x[i] = (x[i] - min[i]) / (max[i] - min[i]); } } double VCSVMLight::getSVMScore ( const NICE::Vector & x ) const { if ( finalModel != NULL ) { DOC *example; NICE::Vector x_normalized ( x ); normalizeVector ( normalization_type, x_normalized ); WORD *words = (WORD *)my_malloc(sizeof(WORD)*(x.size()+10)); long wc; for(wc=0; wc<(long)x.size(); wc++){ words[wc].weight = (FVAL)(x_normalized[wc]); words[wc].wnum = wc+1; } words[wc].wnum = 0; long queryid=0; long slackid=0; long costfactor=1; char comment [2] = ""; example = create_example(0,queryid,slackid,costfactor, create_svector(words,comment,1.0)); double score = classify_example ( finalModel, example ); free (words); free_example (example,1); return score; } else { fprintf (stderr, "VCSVMLight: no model found !!\n"); exit(-1); } return 0.0; } ClassificationResult VCSVMLight::classify ( const NICE::Vector & x ) const { double score = getSVMScore ( x ); if ( logreg != NULL ) { NICE::Vector y (1); y[0] = score; return logreg->classify ( y ); } else { FullVector scores ( maxClassNo+1 ); scores[0] = 0.0; scores[1] = score; // we are dealing only with binary classification if ( score < 0.0 ) return ClassificationResult (0, scores); else return ClassificationResult (1, scores); } } void VCSVMLight::estimateMaxMin ( const LabeledSetVector & train ) { max.resize(0); min.resize(0); LOOP_ALL(train) { EACH(classno,v); UNUSED_PARAMETER(classno); if ( (max.size()<=0) && (min.size()<=0) ) { max = Vector(v); min = Vector(v); } else { for ( uint i = 0 ; i < v.size() ; i++ ) { if ( v[i] > max[i] ) max[i] = v[i]; if ( v[i] < min[i] ) min[i] = v[i]; } } } } void VCSVMLight::readDocuments( DOC ***docs, double **label, long int *totwords, long int *totdoc, const LabeledSetVector & train ) { if ( normalization_type == SVM_NORMALIZATION_01 ) estimateMaxMin ( train ); char comment [2] = ""; WORD *words; long vectorcount = train.count(); long vectorsize = train.dimension(); long dnum=0,dpos=0,dneg=0,dunlab=0,queryid,slackid,max_docs=vectorcount+2; long max_words_doc=vectorsize; double costfactor; long int wc=0; (*docs) = (DOC **)my_malloc(sizeof(DOC *)*max_docs); /* feature vectors */ (*label) = (double *)my_malloc(sizeof(double)*max_docs); /* target values */ words = (WORD *)my_malloc(sizeof(WORD)*(max_words_doc+10)); (*totwords)=0; (*totwords) = vectorsize+1; queryid=0; slackid=0; costfactor=1.; dnum=0; LOOP_ALL(train) { EACH(classno,v); NICE::Vector v_normalized ( v ); normalizeVector ( normalization_type, v_normalized ); for(wc=0; wc MAXFEATNUM) { printf("\nMaximum feature number exceeds limit defined in MAXFEATNUM!\n"); exit(1); } (*docs)[dnum] = create_example(dnum,queryid,slackid,costfactor, create_svector(words,comment,1.0)); dnum++; } free(words); (*totdoc)=dnum; } void VCSVMLight::initMainParameters ( LEARN_PARM *learn_parm, KERNEL_PARM *kernel_parm ) { learn_parm->svm_c=svm_c; kernel_parm->kernel_type=kernel_type; kernel_parm->poly_degree=poly_degree; kernel_parm->rbf_gamma=rbf_gamma; kernel_parm->coef_lin=sigmoidpoly_scale; kernel_parm->coef_const=sigmoidpoly_bias; strcpy(kernel_parm->custom,"empty"); if(learn_parm->svm_c<0) { printf("\nThe C parameter must be greater than zero!\n\n"); exit(-1); } } void VCSVMLight::initParameters ( LEARN_PARM *learn_parm, KERNEL_PARM *kernel_parm ) { /* set default */ strcpy (learn_parm->predfile, ""); strcpy (learn_parm->alphafile, ""); learn_parm->biased_hyperplane=1; learn_parm->sharedslack=0; learn_parm->remove_inconsistent=0; learn_parm->skip_final_opt_check=0; learn_parm->svm_maxqpsize=10; learn_parm->svm_newvarsinqp=0; if( kernel_type == SVM_LINEAR ) learn_parm->svm_iter_to_shrink=2; else learn_parm->svm_iter_to_shrink=100; learn_parm->maxiter=100000; learn_parm->kernel_cache_size=40; learn_parm->eps=0.1; learn_parm->transduction_posratio=-1.0; learn_parm->svm_costratio=1.0; learn_parm->svm_costratio_unlab=1.0; learn_parm->svm_unlabbound=1E-5; learn_parm->epsilon_crit=0.001; learn_parm->epsilon_a=1E-15; learn_parm->rho=1.0; learn_parm->xa_depth=0; // cross validation learn_parm->compute_loo = (use_crossvalidation) ? 1 : 0; learn_parm->type=CLASSIFICATION; if((learn_parm->skip_final_opt_check) && (kernel_parm->kernel_type == SVM_LINEAR)) { printf("\nIt does not make sense to skip the final optimality check for linear kernels.\n\n"); learn_parm->skip_final_opt_check=0; } if((learn_parm->skip_final_opt_check) && (learn_parm->remove_inconsistent)) { printf("\nIt is necessary to do the final optimality check when removing inconsistent \nexamples.\n"); exit(-1); } if((learn_parm->svm_maxqpsize<2)) { printf("\nMaximum size of QP-subproblems not in valid range: %ld [2..]\n",learn_parm->svm_maxqpsize); exit(-1); } if((learn_parm->svm_maxqpsizesvm_newvarsinqp)) { printf("\nMaximum size of QP-subproblems [%ld] must be larger than the number of\n",learn_parm->svm_maxqpsize); printf("new variables [%ld] entering the working set in each iteration.\n",learn_parm->svm_newvarsinqp); exit(-1); } if(learn_parm->svm_iter_to_shrink<1) { printf("\nMaximum number of iterations for shrinking not in valid range: %ld [1,..]\n",learn_parm->svm_iter_to_shrink); exit(-1); } if(learn_parm->transduction_posratio>1) { printf("\nThe fraction of unlabeled examples to classify as positives must\n"); printf("be less than 1.0 !!!\n\n"); exit(-1); } if(learn_parm->svm_costratio<=0) { printf("\nThe COSTRATIO parameter must be greater than zero!\n\n"); exit(-1); } if(learn_parm->epsilon_crit<=0) { printf("\nThe epsilon parameter must be greater than zero!\n\n"); exit(-1); } if(learn_parm->rho<0) { printf("\nThe parameter rho for xi/alpha-estimates and leave-one-out pruning must\n"); printf("be greater than zero (typically 1.0 or 2.0, see T. Joachims, Estimating the\n"); printf("Generalization Performance of an SVM Efficiently, ICML, 2000.)!\n\n"); exit(-1); } if((learn_parm->xa_depth<0) || (learn_parm->xa_depth>100)) { printf("\nThe parameter depth for ext. xi/alpha-estimates must be in [0..100] (zero\n"); printf("for switching to the conventional xa/estimates described in T. Joachims,\n"); printf("Estimating the Generalization Performance of an SVM Efficiently, ICML, 2000.)\n"); exit(-1); } } MODEL *VCSVMLight::optimizeParameters ( DOC **docs, double *target, long int totwords, long int totdoc, MODEL *model, KERNEL_PARM *kernel_parm, LEARN_PARM *learn_parm ) { double best_error = numeric_limits::max(); double old_best_error = best_error; const int numberOfParameters = (kernel_type == SVM_RBF ? 2 : 1 ); KERNEL_PARM *currentKernelParm = (KERNEL_PARM *)my_malloc(sizeof(KERNEL_PARM)); LEARN_PARM *currentLearnParm = (LEARN_PARM *)my_malloc(sizeof(LEARN_PARM)); MODEL *currentModel = (MODEL *)my_malloc(sizeof(MODEL)); initParameters ( learn_parm, kernel_parm ); initMainParameters ( learn_parm, kernel_parm ); double *parametersToOptimize [ numberOfParameters ]; double minValue [ numberOfParameters ]; double maxValue [ numberOfParameters ]; double steps [ numberOfParameters ]; parametersToOptimize[0] = &(currentLearnParm->svm_c); minValue[0] = svm_c_min; maxValue[0] = svm_c_max; steps[0] = svm_c_step; if ( kernel_type == SVM_RBF ) { parametersToOptimize[1] = &(currentKernelParm->rbf_gamma); minValue[1] = rbf_gamma_min; maxValue[1] = rbf_gamma_max; steps[1] = rbf_gamma_step; } if ( use_crossvalidation ) { fprintf (stderr, "VCSVMLight: error estimate type: leave-one-out error\n"); } else { fprintf (stderr, "VCSVMLight: error estimate type: Xi/Alpha error\n"); } do { old_best_error = best_error; for ( int i = 0 ; i < numberOfParameters ; i++ ) { for ( double value = minValue[i] ; value < maxValue[i] ; value += steps[i] ) { memcpy ( currentKernelParm, kernel_parm, sizeof(KERNEL_PARM) ); memcpy ( currentLearnParm, learn_parm, sizeof(LEARN_PARM) ); double *parameter = parametersToOptimize[i]; *parameter = value; singleTraining ( docs, target, totwords, totdoc, currentModel, currentKernelParm, currentLearnParm ); fprintf (stderr, "VCSVMLight: optimize rbf_gamma=%f C=%f err=%f xa_err=%f (%f, %f)\n", currentKernelParm->rbf_gamma, currentLearnParm->svm_c, currentModel->loo_error, currentModel->xa_error, kernel_parm->rbf_gamma, learn_parm->svm_c ); double currentError = (use_crossvalidation) ? currentModel->loo_error : currentModel->xa_error; if ( currentError < 0 ) { fthrow ( Exception, "Error < 0; check your settings; SVMLight verbosity bug ?"); } if ( currentError < best_error ) { fprintf (stderr, "VCSVMLight: new optimum found !\n"); best_error = currentError; memcpy ( kernel_parm, currentKernelParm, sizeof(KERNEL_PARM) ); memcpy ( learn_parm, currentLearnParm, sizeof(LEARN_PARM) ); memcpy ( model, currentModel, sizeof(MODEL) ); } } } } while ( old_best_error > best_error ); free_model(currentModel,0); free (currentLearnParm); free (currentKernelParm); // rewrite rbf_gamma = kernel_parm->rbf_gamma; svm_c = learn_parm->svm_c; fprintf (stderr, "VCSVMLight: optimimum rbf_gamma=%f C=%f !\n", rbf_gamma, svm_c ); return model; } MODEL *VCSVMLight::singleTraining ( DOC **docs, double *target, long int totwords, long int totdoc, MODEL *model, KERNEL_PARM *kernel_parm, LEARN_PARM *learn_parm ) { double *alpha_in=NULL; KERNEL_CACHE *kernel_cache; if(kernel_parm->kernel_type == SVM_LINEAR) { /* don't need the cache */ kernel_cache=NULL; } else { /* Always get a new kernel cache. It is not possible to use the same cache for two different training runs */ kernel_cache = kernel_cache_init(totdoc,learn_parm->kernel_cache_size); } svm_learn_classification(docs,target,totdoc,totwords,learn_parm, kernel_parm,kernel_cache,model,alpha_in); if(kernel_cache) { /* Free the memory used for the cache. */ kernel_cache_cleanup(kernel_cache); } free(alpha_in); return model; } void VCSVMLight::svmLightTraining ( const LabeledSetVector & trainSet ) { double *target; long int totwords,totdoc,i; DOC **docs; /* training examples */ LEARN_PARM learn_parm; KERNEL_PARM kernel_parm; MODEL *model=(MODEL *)my_malloc(sizeof(MODEL)); readDocuments( &docs,&target,&totwords,&totdoc, trainSet); initParameters(&learn_parm, &kernel_parm); initMainParameters(&learn_parm, &kernel_parm); if ( optimize_parameters ) optimizeParameters ( docs, target, totwords, totdoc, model, &kernel_parm, &learn_parm ); else singleTraining ( docs, target, totwords, totdoc, model, &kernel_parm, &learn_parm ); finalModel = copy_model(model); free_model(model,0); for(i=0;iteach ( scoreSet ); scoreSet.clear(); } } void VCSVMLight::finishTeaching() { } /** clone this object */ VCSVMLight *VCSVMLight::clone(void) const { VCSVMLight *classifier = new VCSVMLight( *this ); return classifier; } void VCSVMLight::clear () { if ( finalModel != NULL ) { free(finalModel); finalModel = NULL; } } void VCSVMLight::read (const string& s, int format) { finalModel = read_model ( const_cast(s.c_str()) ); } void VCSVMLight::save (const string& s, int format) const { write_model ( const_cast(s.c_str()), finalModel ); } void VCSVMLight::store ( std::ostream & os, int format ) const { fprintf (stderr, "VCSVMLight: unable to write to stream! please use read()\n"); } void VCSVMLight::restore ( std::istream & is, int format ) { fprintf (stderr, "VCSVMLight: unable to read from stream! please use save()\n"); exit (-1); } #endif