Sfoglia il codice sorgente

Merge branch 'master' of dbv.inf-cv.uni-jena.de:nice/nice-gp-hik-core into incnoveltysemseg_mergebranch

Johannes Ruehle 11 anni fa
parent
commit
9ec56452eb

+ 55 - 7
FMKGPHyperparameterOptimization.cpp

@@ -261,7 +261,6 @@ void FMKGPHyperparameterOptimization::updateEigenVectors()
   if ( verbose )
   {
     std::cerr << "FMKGPHyperparameterOptimization::updateEigenVectors -- size of ikmsums: " << ikmsums.size() << std::endl;
-    std::cerr << "class of first object: " << ikmsums.begin()->first << std::endl;
   }
   
   if ( learnBalanced )
@@ -280,15 +279,12 @@ void FMKGPHyperparameterOptimization::updateEigenVectors()
   }
   else
   {
-    std::cerr << "not balanced, considere for VarApprox: " << nrOfEigenvaluesToConsiderForVarApprox << " eigenvalues" << std::endl;
-    std::cerr << "and for simple: " << nrOfEigenvaluesToConsider << std::endl;
-    if (nrOfEigenvaluesToConsiderForVarApprox > 1)
-      nrOfEigenvaluesToConsiderForVarApprox = 1;
     //compute the largest eigenvalue of K + noise
     eigenMax.resize(1);
     eigenMaxVectors.resize(1);    
     
-    eig->getEigenvalues ( * ( ikmsums.begin()->second ),  eigenMax[0], eigenMaxVectors[0], nrOfEigenvaluesToConsiderForVarApprox );
+    //TODO check why we are only interested in the largest EW!
+    eig->getEigenvalues ( * ( ikmsums.begin()->second ),  eigenMax[0], eigenMaxVectors[0], 1 /* we are only interested in the largest eigenvalue here*/ );
   }
 }
 
@@ -430,7 +426,8 @@ void FMKGPHyperparameterOptimization::performOptimization ( std::map<int, GPLike
       OPTIMIZATION::matrix_type hyperp ( 1, 1, value );
       gplikes.begin()->second->setParameterLowerBound ( value );
       gplikes.begin()->second->setParameterUpperBound ( value );
-      gplikes.begin()->second->evaluate ( hyperp );
+      //we do not need to compute the likelihood here - we are only interested in directly obtaining alpha vectors
+      gplikes.begin()->second->computeAlphaDirect( hyperp );
     }
   }
 
@@ -538,6 +535,7 @@ void FMKGPHyperparameterOptimization::computeMatricesAndLUTs ( const std::map<in
       fmk->hik_prepare_alpha_multiplications ( i->second, A, B );
       A.setIoUntilEndOfFile ( false );
       B.setIoUntilEndOfFile ( false );
+
       precomputedA[ i->first ] = A;
       precomputedB[ i->first ] = B;
 
@@ -1425,6 +1423,56 @@ int FMKGPHyperparameterOptimization::classify ( const NICE::SparseVector & xstar
   }
 }
 
+int FMKGPHyperparameterOptimization::classify ( const NICE::Vector & xstar, NICE::SparseVector & scores ) const
+{
+  // loop through all classes
+  if ( precomputedA.size() == 0 )
+  {
+    fthrow ( Exception, "The precomputation vector is zero...have you trained this classifier?" );
+  }
+
+  uint maxClassNo = 0;
+  for ( map<int, PrecomputedType>::const_iterator i = precomputedA.begin() ; i != precomputedA.end(); i++ )
+  {
+    uint classno = i->first;
+    maxClassNo = std::max ( maxClassNo, classno );
+    double beta;
+
+    if ( q != NULL ) {
+      map<int, double *>::const_iterator j = precomputedT.find ( classno );
+      double *T = j->second;
+      fmk->hik_kernel_sum_fast ( T, *q, xstar, beta );
+    } else {
+      const PrecomputedType & A = i->second;
+      map<int, PrecomputedType>::const_iterator j = precomputedB.find ( classno );
+      const PrecomputedType & B = j->second;
+
+      // fmk->hik_kernel_sum ( A, B, xstar, beta ); if A, B are of type Matrix
+      // Giving the transformation pf as an additional
+      // argument is necessary due to the following reason:
+      // FeatureMatrixT is sorted according to the original values, therefore,
+      // searching for upper and lower bounds ( findFirst... functions ) require original feature
+      // values as inputs. However, for calculation we need the transformed features values.
+
+      fmk->hik_kernel_sum ( A, B, xstar, beta, pf );
+    }
+
+    scores[ classno ] = beta;
+  }
+  scores.setDim ( maxClassNo + 1 );
+  
+  if ( precomputedA.size() > 1 ) {
+    // multi-class classification
+    return scores.maxElement();
+  } else {
+    // binary setting
+    // FIXME: not really flexible for every situation
+    scores[binaryLabelNegative] = -scores[binaryLabelPositive]; 
+    
+    return scores[ binaryLabelPositive ] <= 0.0 ? binaryLabelNegative : binaryLabelPositive;
+  }
+}
+
 void FMKGPHyperparameterOptimization::computePredictiveVarianceApproximateRough ( const NICE::SparseVector & x, NICE::Vector & predVariances ) const
 {
   double kSelf ( 0.0 );

+ 15 - 1
FMKGPHyperparameterOptimization.h

@@ -228,12 +228,26 @@ class FMKGPHyperparameterOptimization : NICE::Persistent
     /**
     * @brief classify an example 
     *
-    * @param x input example
+    * @param x input example (sparse vector)
     * @param scores scores for each class number
     *
     * @return class number achieving the best score
     */
     int classify ( const NICE::SparseVector & x, SparseVector & scores ) const;
+    
+    /**
+    * @brief classify an example that is given as non-sparse vector
+    * NOTE: whenever possible, you should sparse vectors to obtain significantly smaller computation times
+    * 
+    * @date 18-06-2013 (dd-mm-yyyy)
+    * @author Alexander Freytag
+    *
+    * @param x input example (non-sparse vector)
+    * @param scores scores for each class number
+    *
+    * @return class number achieving the best score
+    */
+    int classify ( const NICE::Vector & x, SparseVector & scores ) const;    
 
     /**
     * @brief compute predictive variance for a given test example using a rough approximation: k_{**} -  k_*^T (K+\sigma I)^{-1} k_* <= k_{**} - |k_*|^2 * 1 / \lambda_max(K + \sigma I), where we approximate |k_*|^2 by neglecting the mixed terms

+ 53 - 1
FastMinKernel.cpp

@@ -507,7 +507,6 @@ void FastMinKernel::hik_kernel_sum(const NICE::VVector & A, const NICE::VVector
 {
   // sparse version of hik_kernel_sum, no really significant changes,
   // we are just skipping zero elements
-  // for additional comments see the non-sparse version of hik_kernel_sum
   beta = 0.0;
   for (SparseVector::const_iterator i = xstar.begin(); i != xstar.end(); i++)
   {
@@ -559,6 +558,59 @@ void FastMinKernel::hik_kernel_sum(const NICE::VVector & A, const NICE::VVector
   }
 }
 
+void FastMinKernel::hik_kernel_sum(const NICE::VVector & A, const NICE::VVector & B, const NICE::Vector & xstar, double & beta, const ParameterizedFunction *pf) const
+{
+  beta = 0.0;
+  int dim ( 0 );
+  for (NICE::Vector::const_iterator i = xstar.begin(); i != xstar.end(); i++, dim++)
+  {
+  
+    double fval = *i;
+    
+    int nrZeroIndices = X_sorted.getNumberOfZeroElementsPerDimension(dim);
+    if ( nrZeroIndices == n ) {
+      // all features are zero and let us ignore it completely
+      continue;
+    }
+
+    int position;
+
+    //where is the example x^z_i located in
+    //the sorted array? -> perform binary search, runtime O(log(n))
+    // search using the original value
+    X_sorted.findFirstLargerInDimension(dim, fval, position);
+    position--;
+  
+    //NOTE again - pay attention! This is only valid if all entries are NOT negative! - if not, ask wether the current feature is greater than zero. If so, subtract the nrZeroIndices, if not do not
+    //sum_{l \in L_k} \alpha_l x^l_k
+    double firstPart(0.0);
+    //TODO in the "overnext" line there occurs the following error
+    // Invalid read of size 8
+    if (position >= 0) 
+      firstPart = (A[dim][position-nrZeroIndices]);
+    
+    // sum_{u \in U_k} alpha_u
+    
+    // sum_{u \in U_k} alpha_u
+    // => double secondPart( B(dim, n-1) - B(dim, position));
+    //TODO in the next line there occurs the following error
+    // Invalid read of size 8      
+    double secondPart( B[dim][n-1-nrZeroIndices]);
+    //TODO in the "overnext" line there occurs the following error
+    // Invalid read of size 8    
+    if (position >= 0) 
+      secondPart-= B[dim][position-nrZeroIndices];
+    
+    if ( pf != NULL )
+    {
+      fval = pf->f ( dim, fval );
+    }   
+    
+    // but apply using the transformed one
+    beta += firstPart + secondPart* fval;
+  }
+}
+
 void FastMinKernel::hik_kernel_sum_fast(const double *Tlookup, const Quantization & q, const NICE::Vector & xstar, double & beta) const
 {
   beta = 0.0;

+ 29 - 2
FastMinKernel.h

@@ -204,9 +204,25 @@ namespace NICE {
       */
       void hik_kernel_sum(const NICE::VVector & A, const NICE::VVector & B, const NICE::SparseVector & xstar, double & beta, const ParameterizedFunction *pf = NULL ) const;
       
+      /**
+      * @brief Computing k_{*}*alpha using the minimum kernel trick and exploiting sparsity of the feature vector given
+      * NOTE: Whenever possible, you should use sparse features to obtain significantly smaller computation times!
+      *
+      * @author Alexander Freytag
+      * @date 18-06-2013 (dd-mm-yyyy)
+      * @param A pre-computation matrix (VVector) (use the prepare method) 
+      * @param B pre-computation matrix (VVector)
+      * @param xstar new feature vector (non-sparse Vector)
+      * @param beta result of the scalar product
+      * @param pf optional feature transformation
+      */
+      void hik_kernel_sum(const NICE::VVector & A, const NICE::VVector & B, const NICE::Vector & xstar, double & beta, const ParameterizedFunction *pf = NULL ) const;      
+      
       /**
       * @brief compute beta = k_*^T * alpha by using a large lookup table created by hik_prepare_alpha_multiplications_fast
-      * @author Erik Rodner
+      * NOTE: Whenever possible, you should use sparse features to obtain significantly smaller computation times!
+      * @author Alexander Freytag
+      * @date 18-06-2013 (dd-mm-yyyy)
       *
       * @param Tlookup large lookup table calculated by hik_prepare_alpha_multiplications_fast
       * @param q Quantization object
@@ -214,11 +230,22 @@ namespace NICE {
       * @param beta result of the calculation
       */
       void hik_kernel_sum_fast(const double* Tlookup, const Quantization & q, const NICE::Vector & xstar, double & beta) const;
+      /**
+      * @brief compute beta = k_*^T * alpha by using a large lookup table created by hik_prepare_alpha_multiplications_fast
+      * NOTE: Whenever possible, you should use sparse features to obtain significantly smaller computation times!
+      * @author Alexander Frytag
+      *
+      * @param Tlookup large lookup table calculated by hik_prepare_alpha_multiplications_fast
+      * @param q Quantization object
+      * @param xstar feature vector (indirect k_*)
+      * @param beta result of the calculation
+      */      
+
       void hik_kernel_sum_fast(const double *Tlookup, const Quantization & q, const NICE::SparseVector & xstar, double & beta) const;
 
       /**
       * @brief compute lookup table for HIK calculation using quantized signals and prepare for K*alpha or k_*^T * alpha computations
-      * @author Erik Rodner
+      * @author Erik Rodner, Alexander Freytag
       *
       * @param alpha coefficient vector
       * @param A pre-calculation array computed by hik_prepare_alpha_multiplications

+ 1 - 1
FeatureMatrixT.h

@@ -81,7 +81,7 @@ template<class T> class FeatureMatrixT : NICE::Persistent
     /** 
     * @brief Recommended constructor
     * @author Alexander Freytag
-    * @date 07-12-2011 (dd-mm-yyyy)
+    * @date 07-12-2011 (dd-mm-yyyy) 
     */
     FeatureMatrixT(const std::vector<std::vector<T> > & _features, const int & _dim = -1);
     

+ 4 - 4
FeatureMatrixT.tcc

@@ -637,7 +637,7 @@ namespace NICE {
       {
         std::cerr << "set_features without features" << std::endl;
       }
-      
+            
       // resize our data structure      
       if (_dim >= 0) //did the user specified the number of dimensions?
         set_d(_dim);
@@ -664,8 +664,8 @@ namespace NICE {
             set_d(0);
           }          
         }
-      }    
-      
+      }  
+            
       // set number of examples n
       if (d>0)
       {
@@ -674,7 +674,7 @@ namespace NICE {
         else //we have examples x dimes (as usually done)   
           n = _features.size(); 
       }       
-      
+            
       // insert all values
       if (dimensionsOverExamples) //do we have dim x examples ?
       {

+ 39 - 0
GPHIKClassifier.cpp

@@ -116,6 +116,12 @@ void GPHIKClassifier::classify ( const SparseVector * example,  int & result, Sp
   this->classify( example, result, scores, tmpUncertainty );
 }
 
+void GPHIKClassifier::classify ( const NICE::Vector * example,  int & result, SparseVector & scores )
+{
+  double tmpUncertainty;
+  this->classify( example, result, scores, tmpUncertainty );
+}
+
 void GPHIKClassifier::classify ( const SparseVector * example,  int & result, SparseVector & scores, double & uncertainty )
 {
   scores.clear();
@@ -149,6 +155,38 @@ void GPHIKClassifier::classify ( const SparseVector * example,  int & result, Sp
   }    
 }
 
+void GPHIKClassifier::classify ( const NICE::Vector * example,  int & result, SparseVector & scores, double & uncertainty )
+{
+  scores.clear();
+  
+  int classno = gphyper->classify ( *example, scores );
+
+  if ( scores.size() == 0 ) {
+    fthrow(Exception, "Zero scores, something is likely to be wrong here: svec.size() = " << example->size() );
+  }
+  
+  result = scores.maxElement();
+   
+  if (uncertaintyPredictionForClassification)
+  {
+    if (varianceApproximation != NONE)
+    {
+      std::cerr << "ERROR: Uncertainty computation is currently not supported for NICE::Vector - use SparseVector instead" << std::endl;
+      uncertainty = std::numeric_limits<double>::max();
+    }  
+    else
+    {
+      //do nothing
+      uncertainty = std::numeric_limits<double>::max();
+    }
+  }
+  else
+  {
+    //do nothing
+    uncertainty = std::numeric_limits<double>::max();
+  }    
+}
+
 /** training process */
 void GPHIKClassifier::train ( const std::vector< NICE::SparseVector *> & examples, const NICE::Vector & labels )
 {
@@ -158,6 +196,7 @@ void GPHIKClassifier::train ( const std::vector< NICE::SparseVector *> & example
   Timer t;
   t.start();
   FastMinKernel *fmk = new FastMinKernel ( examples, noise, this->debug );
+  
   t.stop();
   if (verbose)
     std::cerr << "Time used for setting up the fmk object: " << t.getLast() << std::endl;  

+ 23 - 0
GPHIKClassifier.h

@@ -89,6 +89,29 @@ class GPHIKClassifier
      * @param uncertainty (double*) predictive variance of the classification result, if computed
      */    
     void classify ( const NICE::SparseVector * example,  int & result, NICE::SparseVector & scores, double & uncertainty );
+    
+    /** 
+     * @brief classify a given example with the previously learnt model
+     * NOTE: whenever possible, you should the sparse version to obtain significantly smaller computation times* 
+     * @date 18-06-2013 (dd-mm-yyyy)
+     * @author Alexander Freytag
+     * @param example (non-sparse Vector) to be classified given in a non-sparse representation
+     * @param result (int) class number of most likely class
+     * @param scores (SparseVector) classification scores for known classes
+     */        
+    void classify ( const NICE::Vector * example,  int & result, NICE::SparseVector & scores );
+    
+    /** 
+     * @brief classify a given example with the previously learnt model
+     * NOTE: whenever possible, you should the sparse version to obtain significantly smaller computation times
+     * @date 18-06-2013 (dd-mm-yyyy)
+     * @author Alexander Freytag
+     * @param example (non-sparse Vector) to be classified given in a non-sparse representation
+     * @param result (int) class number of most likely class
+     * @param scores (SparseVector) classification scores for known classes
+     * @param uncertainty (double*) predictive variance of the classification result, if computed
+     */    
+    void classify ( const NICE::Vector * example,  int & result, NICE::SparseVector & scores, double & uncertainty );    
 
     /**
      * @brief train this classifier using a given set of examples and a given set of binary label vectors 

+ 77 - 0
GPLikelihoodApprox.cpp

@@ -125,6 +125,83 @@ void GPLikelihoodApprox::calculateLikelihood ( double mypara, const FeatureMatri
   cerr << "OPTGT: " << mypara << " " << gt_nlikelihood << " " << gt_logdet << " " << gt_dataterm << endl;
 }
 
+void GPLikelihoodApprox::computeAlphaDirect(const OPTIMIZATION::matrix_type & x)
+{
+  Timer t;
+//   NICE::Vector diagonalElements;
+  
+//   ikm->getDiagonalElements ( diagonalElements );
+
+  // set simple jacobi pre-conditioning
+  ILSConjugateGradients *linsolver_cg = dynamic_cast<ILSConjugateGradients *> ( linsolver );
+
+//   //TODO why do we need this?  
+//   if ( linsolver_cg != NULL )
+//     linsolver_cg->setJacobiPreconditioner ( diagonalElements );
+  
+
+  // all alpha vectors will be stored!
+  std::map<int, NICE::Vector> alphas;
+
+  // This has to be done m times for the multi-class case
+  if (verbose)
+    std::cerr << "run ILS for every bin label. binaryLabels.size(): " << binaryLabels.size() << std::endl;
+  for ( std::map<int, NICE::Vector>::const_iterator j = binaryLabels.begin(); j != binaryLabels.end() ; j++)
+  {
+    // (b) y^T (K+sI)^{-1} y
+    int classCnt = j->first;
+    if (verbose)
+    {
+      std::cerr << "Solving linear equation system for class " << classCnt << " ..." << std::endl;
+      std::cerr << "Size of the kernel matrix " << ikm->rows() << std::endl;
+      std::cerr << "binary label: " << j->second << std::endl;
+    }
+
+    /** About finding a good initial solution
+     * K~ = K + sigma^2 I
+     *
+     * (0) we have already estimated alpha for a previous hyperparameter, then
+     *     we should use this as an initial estimate. According to my quick
+     *     tests this really helps!
+     * (1) K~ \approx lambda_max v v^T
+     * \lambda_max v v^T * alpha = y     | multiply with v^T from left
+     * => \lambda_max v^T alpha = v^T y
+     * => alpha = y / lambda_max could be a good initial start
+     * If we put everything in the first equation this gives us
+     * v = y ....which is somehow a weird assumption (cf Kernel PCA)
+     *  This reduces the number of iterations by 5 or 8
+     */
+    Vector alpha;
+    
+    if ( (usePreviousAlphas) && (lastAlphas != NULL) )
+    {
+      std::map<int, NICE::Vector>::iterator alphaIt = lastAlphas->begin();
+      alpha = (*lastAlphas)[classCnt];
+    }
+    else  
+    {
+      //TODO hand over the eigenmax
+      alpha = (binaryLabels[classCnt] ); //* (1.0 / eigenmax[0]) );
+    }
+    
+    NICE::Vector initialAlpha;
+    if ( verbose )
+     initialAlpha = alpha;
+
+    if ( verbose )
+      std::cerr << "Using the standard solver ..." << std::endl;
+
+    t.start();
+    linsolver->solveLin ( *ikm, binaryLabels[classCnt], alpha );
+    t.stop();
+   
+    alphas.insert( std::pair<int, NICE::Vector> ( classCnt, alpha) );
+  }  
+  
+  // save the parameter value and alpha vectors
+  ikm->getParameters ( min_parameter );
+  this->min_alphas = alphas;
+}
 
 double GPLikelihoodApprox::evaluate(const OPTIMIZATION::matrix_type & x)
 {

+ 10 - 0
GPLikelihoodApprox.h

@@ -103,6 +103,16 @@ class GPLikelihoodApprox : public OPTIMIZATION::CostFunction
     virtual ~GPLikelihoodApprox();
      
     // ------ main methods ------
+    
+    /**
+    * @brief Compute alpha vectors for given hyperparameters
+    *
+    * @param x vector with specified hyperparameters to evaluate their likelihood
+    *
+    * @return void
+    */    
+    void computeAlphaDirect(const OPTIMIZATION::matrix_type & x);
+    
     /**
     * @brief Evaluate the likelihood for given hyperparameters
     *

+ 185 - 0
progs/classifyDatasetGPHIK.cpp

@@ -0,0 +1,185 @@
+/** 
+* @file classifyDatasetGPHIK.cpp
+* @brief Perform classification on an arbitrary dataset with the GPHIK-Classifier
+* @author Alexander Freytag
+* @date 16-09-2013
+*/
+
+// STL-includes
+#include <iostream>
+#include <vector>
+
+// NICE-core includes
+#include <core/basics/Config.h>
+#include <core/basics/Exception.h>
+#include <core/vector/MatrixT.h>
+#include <core/vector/SparseVectorT.h>
+
+// gp-hik-core includes
+#include "gp-hik-core/GPHIKClassifier.h"
+
+
+void readSparseExamples ( const std::string & fn,  std::vector< NICE::SparseVector * > & examples, NICE::Vector & labels )
+{
+  // initially cleaning of variables
+  examples.clear();
+  labels.clear();
+  
+  std::vector<double> labels_std;
+  labels_std.clear();
+  
+  std::cerr << "Reading " << fn << std::endl;
+  std::ifstream ifs ( fn.c_str(), std::ios::in );
+  if ( ! ifs.good() )
+  {
+      std::cerr <<  "Unable to read " << fn << std::endl;
+      return;
+  }
+  
+  // read until no new line is in the file anymore
+  while ( !ifs.eof() )
+  {
+    int classno;
+    if ( !(ifs >> classno) )
+      break;
+    
+    labels_std.push_back( classno );
+    
+    NICE::SparseVector *v = new NICE::SparseVector; 
+    /* needed format in every line: 
+     * SVECTOR dimension size index value index value ... END
+     * with 
+     *      SVECTOR   -- starting flag
+     *      dimension -- overall feature dimension and
+     *      size      -- number of non-zero entries for the current feature vector 
+     *      index     -- integer value specifying a non-zero dimension
+     *      value     -- double value specifying the value for the corresp. non-zero dimension
+     *      END       -- ending flag
+     */
+    try
+    {
+      v->restore ( ifs, NICE::SparseVector::FORMAT_INDEX );    
+    }
+    catch ( NICE::Exception excep)
+    {
+      std::cerr << "Error while reading features. Error message: " << excep.what() << std::endl;
+      break;
+    }
+          
+    
+    examples.push_back ( v );
+  }
+  ifs.close();
+  
+  labels = NICE::Vector( labels_std );
+}
+
+void mapClassNumbersToIndices( const NICE::Vector & labels, std::map<int,int> & mapClassNoToIdx )
+{
+  mapClassNoToIdx.clear();
+  int classCnt ( 0 );
+  
+  for ( NICE::Vector::const_iterator it_labels = labels.begin(); it_labels != labels.end(); it_labels++ )
+  {
+    if ( mapClassNoToIdx.find( *it_labels ) == mapClassNoToIdx.end() )
+    {
+      mapClassNoToIdx.insert( std::pair<int,int>( (int) round(*it_labels), classCnt ) );
+      classCnt++;
+    }
+  }
+}
+
+
+int main (int argc, char* argv[])
+{  
+  std::set_terminate(__gnu_cxx::__verbose_terminate_handler);
+
+  NICE::Config conf ( argc, argv );
+ 
+  NICE::GPHIKClassifier classifier ( &conf, "GPHIKClassifier" );  
+  
+  // ========================================================================
+  //                            TRAINING STEP
+  // ========================================================================  
+   
+  // read training data
+  std::vector< NICE::SparseVector * > examplesTrain;
+  NICE::Vector labelsTrain;
+  
+  std::string s_fn_trainingSet = conf.gS("main", "trainset");
+  readSparseExamples ( s_fn_trainingSet, examplesTrain, labelsTrain );
+
+  //map the occuring classes to a minimal set of indices
+  std::map<int,int> map_classNoToClassIdx_train; // < classNo, Idx>
+  
+  mapClassNumbersToIndices( labelsTrain, map_classNoToClassIdx_train );
+  
+  //how many different classes do we have in the training set?
+  int i_noClassesTrain ( map_classNoToClassIdx_train.size() );
+
+  // train GPHIK classifier
+  classifier.train ( examplesTrain, labelsTrain );
+  
+  // ========================================================================
+  //                            TEST STEP
+  // ========================================================================
+  
+  // read test data
+  std::vector< NICE::SparseVector * > examplesTest;
+  NICE::Vector labelsTest;
+  
+  std::string s_fn_testSet = conf.gS("main", "testset");
+  readSparseExamples ( s_fn_testSet, examplesTest, labelsTest );
+  
+  //map the occuring classes to a minimal set of indices
+  std::map<int,int> map_classNoToClassIdx_test; // < classNo, Idx>
+  
+  mapClassNumbersToIndices( labelsTest, map_classNoToClassIdx_test );
+
+  //how many different classes do we have in the test set?  
+  int i_noClassesTest ( map_classNoToClassIdx_test.size() );
+  
+  // evaluate GPHIK classifier on unseen test data
+  int idx ( 0 );
+  NICE::SparseVector scores;  /* not needed in this evaluation, so we just declare it ones */
+  
+  NICE::Matrix confusion ( i_noClassesTest, i_noClassesTrain, 0.0 );
+  
+  for (std::vector< NICE::SparseVector *>::const_iterator itTestExamples = examplesTest.begin(); itTestExamples != examplesTest.end(); itTestExamples++, idx++)
+  {
+    int classno_groundtruth = labelsTest( idx );
+    int classno_predicted;
+
+    classifier.classify ( *itTestExamples, classno_predicted, scores /* not needed anyway in that evaluation*/ );
+    
+    
+    int idx_classno_groundtruth ( map_classNoToClassIdx_test[ classno_groundtruth ] );
+    int idx_classno_predicted ( map_classNoToClassIdx_train[ classno_predicted ] );
+        
+    confusion( idx_classno_groundtruth, idx_classno_predicted ) += 1;
+  }
+
+
+  confusion.normalizeRowsL1();
+  std::cerr << confusion << std::endl;
+
+  std::cerr << "average recognition rate: " << confusion.trace()/confusion.rows() << std::endl;
+  
+  
+  // ========================================================================
+  //                           clean up memory
+  // ========================================================================
+  
+  // release memore of feature vectors from training set
+  for (std::vector< NICE::SparseVector *>::const_iterator itTrainExamples = examplesTrain.begin(); itTrainExamples != examplesTrain.end(); itTrainExamples++ )
+  {
+    delete *itTrainExamples;
+  }
+  
+  // release memore of feature vectors from test set
+  for (std::vector< NICE::SparseVector *>::const_iterator itTestExamples = examplesTest.begin(); itTestExamples != examplesTest.end(); itTestExamples++ )
+  {
+    delete *itTestExamples;
+  }
+  return 0;
+}