/** * @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 #include #include "core/vector/Algorithms.h" #include "KCNullSpace.h" #include #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::const_iterator it = vcova.trainingSetStatistic.begin(); it != vcova.trainingSetStatistic.end(); it++ ) { trainingSetStatistic.insert(pair( (*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::const_iterator it = vcova.targetPoints.begin(); it != vcova.targetPoints.end(); it++ ) { targetPoints.insert(pair( (*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 * KCNullSpace::getTrainingSetStatistic() { return &trainingSetStatistic; } NICE::Matrix KCNullSpace::getNullProjectionDirections() { return nullProjectionDirections; } std::map * 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::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)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; igetKernelMatrix() * 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::iterator it; it = trainingSetStatistic.begin(); classLabel = (*it).first; for (size_t i=0; igetKernelMatrix().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( classLabel,targetPoint)); } else { /** create averaging vectors for each class, necessary to compute target points at the end of this method */ std::map averagingVectors; averagingVectors.clear(); NICE::Vector averagingVector(y.size(),0.0); /** insert one averaging vector for each class */ int numClassSamples(0); for ( std::map::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( 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::iterator it = averagingVectors.begin(); it != averagingVectors.end(); it++ ) { targetPoint = nullProjectionDirections.transpose() * kernelData->getKernelMatrix() * (*it).second; targetPoints.insert(pair( (*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::max()); int iter(0); for ( std::map::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::max()); double tmp_score(0.0); for ( std::map::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::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> classLabel; ifs >> numClassSamples; trainingSetStatistic.insert( pair(classLabel,numClassSamples) ); } ifs >> nullProjectionDirections; NICE::Vector targetPoint; ifs >> k; for (int i=0; i> classLabel; ifs >> targetPoint; targetPoints.insert( pair(classLabel,targetPoint) ); } KernelClassifier::restore(ifs,type); } void KCNullSpace::store(std::ostream& ofs, int type) const { ofs.precision (numeric_limits::digits10 + 1); ofs << maxClassNo << endl; ofs << oneClassSetting << endl; ofs << dimNullSpace << endl; ofs << eigenBasis << endl; ofs << trainingSetStatistic.size() << endl; for (std::map::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::const_iterator it = targetPoints.begin() ; it != targetPoints.end(); it++) { ofs << (*it).first << endl; ofs << (*it).second << endl; } KernelClassifier::store(ofs,type); }