123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- /**
- * @file KCNullSpace.cpp
- * @brief Classification and novelty detection with kernel null space methods (Kernel Null Foley Sammon Transform - KNFST)
- * @author Paul Bodesheim
- * @date 26/11/2012
- */
- #include <iostream>
- #include <sstream>
- #include "core/vector/Algorithms.h"
- #include "KCNullSpace.h"
- #include <limits>
- #undef DEBUG
- using namespace NICE;
- using namespace std;
- using namespace OBJREC;
- KCNullSpace::KCNullSpace( const Config *conf, Kernel *kernelFunction, const string & section )
- : KernelClassifier ( conf, kernelFunction )
- {
- this->maxClassNo = 0;
- this->verbose = conf->gB( section, "verbose", false );
- }
- KCNullSpace::KCNullSpace( const KCNullSpace &vcova ): KernelClassifier(vcova)
- {
- verbose = vcova.verbose;
- dimNullSpace = vcova.dimNullSpace;
- oneClassSetting = vcova.oneClassSetting;
-
- trainingSetStatistic.clear();
- for ( std::map<int,int>::const_iterator it = vcova.trainingSetStatistic.begin(); it != vcova.trainingSetStatistic.end(); it++ )
- {
- trainingSetStatistic.insert(pair<int,int>( (*it).first,(*it).second ));
- }
-
- nullProjectionDirections.resize( vcova.nullProjectionDirections.rows(),vcova.nullProjectionDirections.cols() );
- nullProjectionDirections.set( 0.0 );
- for(int i = 0; i < (int)vcova.nullProjectionDirections.rows(); i++)
- {
- for(int j = 0; j < (int)vcova.nullProjectionDirections.cols(); j++)
- {
- nullProjectionDirections(i,j) = vcova.nullProjectionDirections(i,j);
- }
- }
- targetPoints.clear();
- for( std::map<int,NICE::Vector>::const_iterator it = vcova.targetPoints.begin(); it != vcova.targetPoints.end(); it++ )
- {
- targetPoints.insert(pair<int,NICE::Vector>( (*it).first,(*it).second ));
- }
-
- eigenBasis.resize( vcova.eigenBasis.rows(),vcova.eigenBasis.cols() );
- eigenBasis.set( 0.0 );
- for(int i = 0; i < (int)vcova.eigenBasis.rows(); i++)
- {
- for(int j = 0; j < (int)vcova.eigenBasis.cols(); j++)
- {
- eigenBasis(i,j) = vcova.eigenBasis(i,j);
- }
- }
- }
- KCNullSpace::~KCNullSpace()
- {
- }
- void KCNullSpace::teach ( KernelData *kernelData, const NICE::Vector & y )
- {
- NICE::Vector labels(y);
- int maxLabel( (int)labels.Max());
-
- /** check if we are in a one-class setting */
- int minLabel( (int)labels.Min());
- if (maxLabel == minLabel)
- {
- oneClassSetting = true;
- maxClassNo = 1;
- if (verbose)
- std::cerr << "KCNullSpace::teach: one-class setting" << std::endl;
- /** one-class setting: add a row and a column of zeros to the kernel matrix representing dot products with origin in kernel feature space*/
- kernelData->increase_size_by_One();
- kernelData->getKernelMatrix() (kernelData->getKernelMatrix().rows()-1, kernelData->getKernelMatrix().cols()-1) = 0.0;
- labels.append(minLabel+1);
- computeTrainingSetStatistic(labels);
-
- }
- else
- {
- oneClassSetting = false;
- if (verbose)
- std::cerr << "KCNullSpace::teach: multi-class setting" << std::endl;
- computeTrainingSetStatistic(labels);
- maxClassNo = trainingSetStatistic.size()-1; // number of classes, start counting at 0
- }
-
- computeNullProjectionDirections(kernelData,labels);
-
- computeTargetPoints(kernelData,labels);
-
- if (oneClassSetting)
- {
- /** remove the value that corresponds to the dot product with the origin in the kernel feature space, since this is always equal to zero */
- nullProjectionDirections.deleteRow(nullProjectionDirections.rows()-1);
- }
- }
- std::map<int,int> * KCNullSpace::getTrainingSetStatistic()
- {
- return &trainingSetStatistic;
- }
- NICE::Matrix KCNullSpace::getNullProjectionDirections()
- {
- return nullProjectionDirections;
- }
- std::map<int,NICE::Vector> * KCNullSpace::getTargetPoints()
- {
- return &targetPoints;
- }
- int KCNullSpace::getNullSpaceDimension()
- {
- return dimNullSpace;
- }
- bool KCNullSpace::isOneClass()
- {
- return oneClassSetting;
- }
- void KCNullSpace::computeTrainingSetStatistic(const NICE::Vector & y)
- {
- trainingSetStatistic.clear();
- std::map<int,int>::iterator it;
- for ( uint i = 0 ; i < y.size(); i++ )
- {
- it = trainingSetStatistic.find ( (int)y[i] );
- if ( it == trainingSetStatistic.end() )
- {
- trainingSetStatistic.insert(it, pair<int,int>( (int)y[i],1 ));
- }
- else
- {
- it->second += 1;
- }
- }
- }
- void KCNullSpace::computeBasisUsingKernelPCA(const KernelData *kernelData)
- {
- if (verbose)
- std::cerr << "KCNullSpace::computeBasisUsingKernelPCA: compute kernel PCA basis..." << std::endl;
-
- NICE::Matrix K (kernelData->getKernelMatrix());
-
- /** let K represent dot products of zero mean data in kernel feature space */
- centerKernelMatrix(K);
- /** get eigenvectors and eigenvalues (descreasing order) of centered kernel matrix*/
- NICE::Matrix eigenVectors(K.rows(), K.cols(), 0.0);
- NICE::Vector eigenValues(K.rows(), 0.0);
- K.addIdentity(1.0);
- eigenvectorvalues(K, eigenVectors, eigenValues);
- eigenValues -= 1.0;
- // NICE::GMCovariance gm(&K);
- // NICE::EigValuesTRLAN ev;
- // ev.getEigenvalues(gm, eigenValues, eigenVectors, K.rows());
-
- /** only use eigenvectors of non-zero eigenvalues */
- int j(0);
- for (size_t i=0; i<K.rows(); i++)
- {
- if ( eigenValues(i) < 1e-12 )
- {
- eigenVectors.deleteCol(i);
- }
- else
- {
- j++;
- }
- }
- eigenValues.resize(j);
-
- /** scale eigenvectors with eigenvalues */
- double scale(0.0);
- for (size_t c=0; c<eigenVectors.cols(); c++)
- {
- scale = 1.0/sqrt(eigenValues(c));
- for (size_t r=0; r<eigenVectors.rows(); r++)
- eigenVectors(r,c) *= scale;
- }
-
- eigenBasis = eigenVectors;
-
- if (verbose)
- std::cerr << "KCNullSpace::computeBasisUsingKernelPCA: computation done" << std::endl;
-
- }
-
- void KCNullSpace::centerKernelMatrix(NICE::Matrix & kernelMatrix)
- {
- NICE::Matrix onesK (kernelMatrix.rows(), kernelMatrix.cols(), 1.0/kernelMatrix.rows());
- kernelMatrix = kernelMatrix - onesK*kernelMatrix - kernelMatrix*onesK + onesK*kernelMatrix*onesK;
- }
-
- void KCNullSpace::computeNullProjectionDirections ( const KernelData *kernelData, const NICE::Vector & y )
- {
-
- if (verbose)
- std::cerr << "KCNullSpace::computeNullProjectionDirections: compute null projection directions..." << std::endl;
-
- /** obtain Kernel PCA basis */
- computeBasisUsingKernelPCA(kernelData);
-
- /** set matrix IM=(I-M) with I being the unit matrix and M being a matrix with all entries equal to 1/n where n is the number of training samples */
- NICE::Matrix IM (y.size(),y.size(),-1.0/y.size());
- IM.addIdentity(1.0);
-
- /** compute matrix IL=(I-L) with I being the unit matrix and L being a block matrix where in each block of class samples each entry is equal to 1/numClassSamples */
- NICE::Matrix IL (y.size(),y.size(),0.0);
- IL.addIdentity(1.0);
- for (size_t c=0; c<IL.cols(); c++)
- {
- /** if sample with index r is in the same class as sample with index c, then insert the value 1/numClassSamples */
- for (size_t r=0; r<IL.rows(); r++)
- {
- if ( y(r) == y(c) )
- {
- IL(r,c) -= 1.0/trainingSetStatistic[(int)y(r)];;
- }
- }
- }
-
- /** compute Matrix H = ((I-M)*basisvecs)^T * kernelMatrix * (I-L) with I being the unit matrix */
- NICE::Matrix H (y.size(),y.size(),0.0);
- IM = IM*eigenBasis;
- H = IM.transpose() * kernelData->getKernelMatrix() * IL;
-
- /** obtain matrix T = H * H^T */
- NICE::Matrix T = H*H.transpose();
-
- /** get eigenvectors and eigenvalues (descreasing order) of T */
- NICE::Matrix eigenVectors(T.rows(), T.cols(), 0.0);
- NICE::Vector eigenValues(T.rows(), 0.0);
- T.addIdentity(1.0);
- eigenvectorvalues(T, eigenVectors, eigenValues);
- eigenValues -= 1.0;
- // NICE::GMCovariance gm(&T);
- // NICE::EigValuesTRLAN ev;
- // ev.getEigenvalues(gm, eigenValues, eigenVectors, T.rows());
-
- if (verbose)
- std::cerr << "T: " << T.rows() << " x " << T.rows() << " nan: " << T.containsNaN()<< std::endl;
-
- /** only use eigenvectors of zero eigenvalues (null space!!!) but at least one eigenvector according to the smallest eigenvalue (therefore start at index i=T.rows()-2)*/
- for (int i=T.rows()-2; i>=0; i--)
- {
- if ( eigenValues(i) > 1e-12 )
- {
- eigenVectors.deleteCol(i);
- }
- }
-
- /** compute null projection directions */
- nullProjectionDirections = IM*eigenVectors;
- dimNullSpace = nullProjectionDirections.cols();
-
- if (verbose)
- std::cerr << "KCNullSpace::computeNullProjectionDirections: computation done" << std::endl;
- }
- void KCNullSpace::computeTargetPoints ( const KernelData *kernelData, const NICE::Vector & y )
- {
- if (verbose)
- std::cerr << "KCNullSpace::computeTargetPoints: compute target points..." << std::endl;
-
- targetPoints.clear();
- NICE::Vector targetPoint (dimNullSpace, 0.0);
- int classLabel(0);
-
- if (oneClassSetting)
- {
-
- std::map<int,int>::iterator it;
- it = trainingSetStatistic.begin();
- classLabel = (*it).first;
- for (size_t i=0; i<y.size(); i++)
- {
- if ( classLabel == y(i) )
- {
- /** project each training sample in the null space */
- targetPoint += nullProjectionDirections.transpose() * kernelData->getKernelMatrix().getColumn(i);
- }
- }
- /** average the projections of all class samples due to numerical noise */
- targetPoint /= (*it).second;
-
- /** we only have one target point in an one-class setting */
- targetPoints.insert(pair<int,NICE::Vector>( classLabel,targetPoint));
-
- }
- else
- {
-
- /** create averaging vectors for each class, necessary to compute target points at the end of this method */
- std::map<int,NICE::Vector> averagingVectors;
- averagingVectors.clear();
- NICE::Vector averagingVector(y.size(),0.0);
-
- /** insert one averaging vector for each class */
- int numClassSamples(0);
- for ( std::map<int,int>::iterator it = trainingSetStatistic.begin(); it != trainingSetStatistic.end(); it++ )
- {
-
- /** create current averaging vector */
- classLabel = (*it).first;
- numClassSamples = (*it).second;
- for ( size_t j = 0; j < y.size(); j++ )
- {
- if ( classLabel == y[j] )
- {
- averagingVector(j) = 1.0/numClassSamples;
- }
- else
- {
- averagingVector(j) = 0.0;
- }
- }
-
- /** insert averaging vector for current class*/
- averagingVectors.insert(pair<int,NICE::Vector>( classLabel,averagingVector));
- }
-
- /** compute target points using previously created averaging vectors: average for each class the projections of the class samples in the null space */
- for ( std::map<int,NICE::Vector>::iterator it = averagingVectors.begin(); it != averagingVectors.end(); it++ )
- {
- targetPoint = nullProjectionDirections.transpose() * kernelData->getKernelMatrix() * (*it).second;
- targetPoints.insert(pair<int,NICE::Vector>( (*it).first,targetPoint));
- }
-
- }
-
- if (verbose)
- std::cerr << "KCNullSpace::computeTargetPoints: computation done" << std::endl;
-
- }
- ClassificationResult KCNullSpace::classifyKernel ( const NICE::Vector & kernelVector, double kernelSelf ) const
- {
- if ( targetPoints.size() <= 0 )
- fthrow(Exception, "The classifier was not trained with training data (use teach(...))");
-
- if (oneClassSetting)
- {
- return noveltyDetection ( kernelVector, kernelSelf );
- }
-
- NICE::Vector projection(dimNullSpace,0.0);
- projection = nullProjectionDirections.transpose() * kernelVector;
-
- FullVector scores ( maxClassNo+1 );
- scores.set(- std::numeric_limits<double>::max());
-
- int iter(0);
- for ( std::map<int,NICE::Vector>::const_iterator it = targetPoints.begin(); it != targetPoints.end(); it++ )
- {
- scores[iter] = 1.0-(it->second - projection).normL2();
- iter++;
- }
-
- ClassificationResult r ( scores.maxElement(), scores );
- return r;
- }
- ClassificationResult KCNullSpace::noveltyDetection ( const NICE::Vector & kernelVector, double kernelSelf ) const
- {
- if ( targetPoints.size() <= 0 )
- fthrow(Exception, "The classifier was not trained with training data (use teach(...))");
-
- NICE::Vector projection(dimNullSpace,0.0);
- projection = nullProjectionDirections.transpose() * kernelVector;
-
- FullVector scores ( 2 );
- scores.set(- std::numeric_limits<double>::max());
- double tmp_score(0.0);
-
- for ( std::map<int,NICE::Vector>::const_iterator it = targetPoints.begin(); it != targetPoints.end(); it++ )
- {
- tmp_score = 1.0-(it->second - projection).normL2();
- if (tmp_score > scores[1])
- {
- scores[1] = tmp_score;
- }
- }
-
- ClassificationResult r ( scores.maxElement(), scores );
- return r;
- }
- KCNullSpace* KCNullSpace::clone(void) const
- {
- KCNullSpace *classifier = new KCNullSpace( *this );
- return classifier;
- }
- void KCNullSpace::clear()
- {
- //nothing to clear
- }
- void KCNullSpace::restore(std::istream& ifs, int type)
- {
- ifs.precision (numeric_limits<double>::digits10 + 1);
- ifs >> maxClassNo;
- ifs >> oneClassSetting;
- ifs >> dimNullSpace;
- ifs >> eigenBasis;
-
- int k(0);
- int classLabel(0);
- int numClassSamples(0);
- ifs >> k;
- trainingSetStatistic.clear();
- for (int i=0; i<k; i++)
- {
- ifs >> classLabel;
- ifs >> numClassSamples;
- trainingSetStatistic.insert( pair<int,int>(classLabel,numClassSamples) );
- }
-
- ifs >> nullProjectionDirections;
-
- NICE::Vector targetPoint;
- ifs >> k;
- for (int i=0; i<k; i++)
- {
- ifs >> classLabel;
- ifs >> targetPoint;
- targetPoints.insert( pair<int,NICE::Vector>(classLabel,targetPoint) );
- }
-
- KernelClassifier::restore(ifs,type);
- }
- void KCNullSpace::store(std::ostream& ofs, int type) const
- {
- ofs.precision (numeric_limits<double>::digits10 + 1);
-
- ofs << maxClassNo << endl;
- ofs << oneClassSetting << endl;
- ofs << dimNullSpace << endl;
-
- ofs << eigenBasis << endl;
-
- ofs << trainingSetStatistic.size() << endl;
- for (std::map<int,int>::const_iterator it = trainingSetStatistic.begin() ; it != trainingSetStatistic.end(); it++)
- {
- ofs << (*it).first << endl;
- ofs << (*it).second << endl;
- }
-
- ofs << nullProjectionDirections << endl;
-
- ofs << targetPoints.size() << endl;
- for (std::map<int,NICE::Vector>::const_iterator it = targetPoints.begin() ; it != targetPoints.end(); it++)
- {
-
- ofs << (*it).first << endl;
- ofs << (*it).second << endl;
- }
-
- KernelClassifier::store(ofs,type);
- }
|