KCNullSpace.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /**
  2. * @file KCNullSpace.cpp
  3. * @brief Classification and novelty detection with kernel null space methods (Kernel Null Foley Sammon Transform - KNFST)
  4. * @author Paul Bodesheim
  5. * @date 26/11/2012
  6. */
  7. #include <iostream>
  8. #include <sstream>
  9. #include "core/vector/Algorithms.h"
  10. #include "KCNullSpace.h"
  11. #include <limits>
  12. #undef DEBUG
  13. using namespace NICE;
  14. using namespace std;
  15. using namespace OBJREC;
  16. KCNullSpace::KCNullSpace( const Config *conf, Kernel *kernelFunction, const string & section )
  17. : KernelClassifier ( conf, kernelFunction )
  18. {
  19. this->maxClassNo = 0;
  20. this->verbose = conf->gB( section, "verbose", false );
  21. }
  22. KCNullSpace::KCNullSpace( const KCNullSpace &vcova ): KernelClassifier(vcova)
  23. {
  24. verbose = vcova.verbose;
  25. dimNullSpace = vcova.dimNullSpace;
  26. oneClassSetting = vcova.oneClassSetting;
  27. trainingSetStatistic.clear();
  28. for ( std::map<int,int>::const_iterator it = vcova.trainingSetStatistic.begin(); it != vcova.trainingSetStatistic.end(); it++ )
  29. {
  30. trainingSetStatistic.insert(pair<int,int>( (*it).first,(*it).second ));
  31. }
  32. nullProjectionDirections.resize( vcova.nullProjectionDirections.rows(),vcova.nullProjectionDirections.cols() );
  33. nullProjectionDirections.set( 0.0 );
  34. for(int i = 0; i < (int)vcova.nullProjectionDirections.rows(); i++)
  35. {
  36. for(int j = 0; j < (int)vcova.nullProjectionDirections.cols(); j++)
  37. {
  38. nullProjectionDirections(i,j) = vcova.nullProjectionDirections(i,j);
  39. }
  40. }
  41. targetPoints.clear();
  42. for( std::map<int,NICE::Vector>::const_iterator it = vcova.targetPoints.begin(); it != vcova.targetPoints.end(); it++ )
  43. {
  44. targetPoints.insert(pair<int,NICE::Vector>( (*it).first,(*it).second ));
  45. }
  46. eigenBasis.resize( vcova.eigenBasis.rows(),vcova.eigenBasis.cols() );
  47. eigenBasis.set( 0.0 );
  48. for(int i = 0; i < (int)vcova.eigenBasis.rows(); i++)
  49. {
  50. for(int j = 0; j < (int)vcova.eigenBasis.cols(); j++)
  51. {
  52. eigenBasis(i,j) = vcova.eigenBasis(i,j);
  53. }
  54. }
  55. }
  56. KCNullSpace::~KCNullSpace()
  57. {
  58. }
  59. void KCNullSpace::teach ( KernelData *kernelData, const NICE::Vector & y )
  60. {
  61. NICE::Vector labels(y);
  62. int maxLabel( (int)labels.Max());
  63. /** check if we are in a one-class setting */
  64. int minLabel( (int)labels.Min());
  65. if (maxLabel == minLabel)
  66. {
  67. oneClassSetting = true;
  68. maxClassNo = 1;
  69. if (verbose)
  70. std::cerr << "KCNullSpace::teach: one-class setting" << std::endl;
  71. /** one-class setting: add a row and a column of zeros to the kernel matrix representing dot products with origin in kernel feature space*/
  72. kernelData->increase_size_by_One();
  73. kernelData->getKernelMatrix() (kernelData->getKernelMatrix().rows()-1, kernelData->getKernelMatrix().cols()-1) = 0.0;
  74. labels.append(minLabel+1);
  75. computeTrainingSetStatistic(labels);
  76. }
  77. else
  78. {
  79. oneClassSetting = false;
  80. if (verbose)
  81. std::cerr << "KCNullSpace::teach: multi-class setting" << std::endl;
  82. computeTrainingSetStatistic(labels);
  83. maxClassNo = trainingSetStatistic.size()-1; // number of classes, start counting at 0
  84. }
  85. computeNullProjectionDirections(kernelData,labels);
  86. computeTargetPoints(kernelData,labels);
  87. if (oneClassSetting)
  88. {
  89. /** remove the value that corresponds to the dot product with the origin in the kernel feature space, since this is always equal to zero */
  90. nullProjectionDirections.deleteRow(nullProjectionDirections.rows()-1);
  91. }
  92. }
  93. std::map<int,int> * KCNullSpace::getTrainingSetStatistic()
  94. {
  95. return &trainingSetStatistic;
  96. }
  97. NICE::Matrix KCNullSpace::getNullProjectionDirections()
  98. {
  99. return nullProjectionDirections;
  100. }
  101. std::map<int,NICE::Vector> * KCNullSpace::getTargetPoints()
  102. {
  103. return &targetPoints;
  104. }
  105. int KCNullSpace::getNullSpaceDimension()
  106. {
  107. return dimNullSpace;
  108. }
  109. bool KCNullSpace::isOneClass()
  110. {
  111. return oneClassSetting;
  112. }
  113. void KCNullSpace::computeTrainingSetStatistic(const NICE::Vector & y)
  114. {
  115. trainingSetStatistic.clear();
  116. std::map<int,int>::iterator it;
  117. for ( uint i = 0 ; i < y.size(); i++ )
  118. {
  119. it = trainingSetStatistic.find ( (int)y[i] );
  120. if ( it == trainingSetStatistic.end() )
  121. {
  122. trainingSetStatistic.insert(it, pair<int,int>( (int)y[i],1 ));
  123. }
  124. else
  125. {
  126. it->second += 1;
  127. }
  128. }
  129. }
  130. void KCNullSpace::computeBasisUsingKernelPCA(const KernelData *kernelData)
  131. {
  132. if (verbose)
  133. std::cerr << "KCNullSpace::computeBasisUsingKernelPCA: compute kernel PCA basis..." << std::endl;
  134. NICE::Matrix K (kernelData->getKernelMatrix());
  135. /** let K represent dot products of zero mean data in kernel feature space */
  136. centerKernelMatrix(K);
  137. /** get eigenvectors and eigenvalues (descreasing order) of centered kernel matrix*/
  138. NICE::Matrix eigenVectors(K.rows(), K.cols(), 0.0);
  139. NICE::Vector eigenValues(K.rows(), 0.0);
  140. K.addIdentity(1.0);
  141. eigenvectorvalues(K, eigenVectors, eigenValues);
  142. eigenValues -= 1.0;
  143. // NICE::GMCovariance gm(&K);
  144. // NICE::EigValuesTRLAN ev;
  145. // ev.getEigenvalues(gm, eigenValues, eigenVectors, K.rows());
  146. /** only use eigenvectors of non-zero eigenvalues */
  147. int j(0);
  148. for (size_t i=0; i<K.rows(); i++)
  149. {
  150. if ( eigenValues(i) < 1e-12 )
  151. {
  152. eigenVectors.deleteCol(i);
  153. }
  154. else
  155. {
  156. j++;
  157. }
  158. }
  159. eigenValues.resize(j);
  160. /** scale eigenvectors with eigenvalues */
  161. double scale(0.0);
  162. for (size_t c=0; c<eigenVectors.cols(); c++)
  163. {
  164. scale = 1.0/sqrt(eigenValues(c));
  165. for (size_t r=0; r<eigenVectors.rows(); r++)
  166. eigenVectors(r,c) *= scale;
  167. }
  168. eigenBasis = eigenVectors;
  169. if (verbose)
  170. std::cerr << "KCNullSpace::computeBasisUsingKernelPCA: computation done" << std::endl;
  171. }
  172. void KCNullSpace::centerKernelMatrix(NICE::Matrix & kernelMatrix)
  173. {
  174. NICE::Matrix onesK (kernelMatrix.rows(), kernelMatrix.cols(), 1.0/kernelMatrix.rows());
  175. kernelMatrix = kernelMatrix - onesK*kernelMatrix - kernelMatrix*onesK + onesK*kernelMatrix*onesK;
  176. }
  177. void KCNullSpace::computeNullProjectionDirections ( const KernelData *kernelData, const NICE::Vector & y )
  178. {
  179. if (verbose)
  180. std::cerr << "KCNullSpace::computeNullProjectionDirections: compute null projection directions..." << std::endl;
  181. /** obtain Kernel PCA basis */
  182. computeBasisUsingKernelPCA(kernelData);
  183. /** 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 */
  184. NICE::Matrix IM (y.size(),y.size(),-1.0/y.size());
  185. IM.addIdentity(1.0);
  186. /** 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 */
  187. NICE::Matrix IL (y.size(),y.size(),0.0);
  188. IL.addIdentity(1.0);
  189. for (size_t c=0; c<IL.cols(); c++)
  190. {
  191. /** if sample with index r is in the same class as sample with index c, then insert the value 1/numClassSamples */
  192. for (size_t r=0; r<IL.rows(); r++)
  193. {
  194. if ( y(r) == y(c) )
  195. {
  196. IL(r,c) -= 1.0/trainingSetStatistic[(int)y(r)];;
  197. }
  198. }
  199. }
  200. /** compute Matrix H = ((I-M)*basisvecs)^T * kernelMatrix * (I-L) with I being the unit matrix */
  201. NICE::Matrix H (y.size(),y.size(),0.0);
  202. IM = IM*eigenBasis;
  203. H = IM.transpose() * kernelData->getKernelMatrix() * IL;
  204. /** obtain matrix T = H * H^T */
  205. NICE::Matrix T = H*H.transpose();
  206. /** get eigenvectors and eigenvalues (descreasing order) of T */
  207. NICE::Matrix eigenVectors(T.rows(), T.cols(), 0.0);
  208. NICE::Vector eigenValues(T.rows(), 0.0);
  209. T.addIdentity(1.0);
  210. eigenvectorvalues(T, eigenVectors, eigenValues);
  211. eigenValues -= 1.0;
  212. // NICE::GMCovariance gm(&T);
  213. // NICE::EigValuesTRLAN ev;
  214. // ev.getEigenvalues(gm, eigenValues, eigenVectors, T.rows());
  215. if (verbose)
  216. std::cerr << "T: " << T.rows() << " x " << T.rows() << " nan: " << T.containsNaN()<< std::endl;
  217. /** 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)*/
  218. for (int i=T.rows()-2; i>=0; i--)
  219. {
  220. if ( eigenValues(i) > 1e-12 )
  221. {
  222. eigenVectors.deleteCol(i);
  223. }
  224. }
  225. /** compute null projection directions */
  226. nullProjectionDirections = IM*eigenVectors;
  227. dimNullSpace = nullProjectionDirections.cols();
  228. if (verbose)
  229. std::cerr << "KCNullSpace::computeNullProjectionDirections: computation done" << std::endl;
  230. }
  231. void KCNullSpace::computeTargetPoints ( const KernelData *kernelData, const NICE::Vector & y )
  232. {
  233. if (verbose)
  234. std::cerr << "KCNullSpace::computeTargetPoints: compute target points..." << std::endl;
  235. targetPoints.clear();
  236. NICE::Vector targetPoint (dimNullSpace, 0.0);
  237. int classLabel(0);
  238. if (oneClassSetting)
  239. {
  240. std::map<int,int>::iterator it;
  241. it = trainingSetStatistic.begin();
  242. classLabel = (*it).first;
  243. for (size_t i=0; i<y.size(); i++)
  244. {
  245. if ( classLabel == y(i) )
  246. {
  247. /** project each training sample in the null space */
  248. targetPoint += nullProjectionDirections.transpose() * kernelData->getKernelMatrix().getColumn(i);
  249. }
  250. }
  251. /** average the projections of all class samples due to numerical noise */
  252. targetPoint /= (*it).second;
  253. /** we only have one target point in an one-class setting */
  254. targetPoints.insert(pair<int,NICE::Vector>( classLabel,targetPoint));
  255. }
  256. else
  257. {
  258. /** create averaging vectors for each class, necessary to compute target points at the end of this method */
  259. std::map<int,NICE::Vector> averagingVectors;
  260. averagingVectors.clear();
  261. NICE::Vector averagingVector(y.size(),0.0);
  262. /** insert one averaging vector for each class */
  263. int numClassSamples(0);
  264. for ( std::map<int,int>::iterator it = trainingSetStatistic.begin(); it != trainingSetStatistic.end(); it++ )
  265. {
  266. /** create current averaging vector */
  267. classLabel = (*it).first;
  268. numClassSamples = (*it).second;
  269. for ( size_t j = 0; j < y.size(); j++ )
  270. {
  271. if ( classLabel == y[j] )
  272. {
  273. averagingVector(j) = 1.0/numClassSamples;
  274. }
  275. else
  276. {
  277. averagingVector(j) = 0.0;
  278. }
  279. }
  280. /** insert averaging vector for current class*/
  281. averagingVectors.insert(pair<int,NICE::Vector>( classLabel,averagingVector));
  282. }
  283. /** compute target points using previously created averaging vectors: average for each class the projections of the class samples in the null space */
  284. for ( std::map<int,NICE::Vector>::iterator it = averagingVectors.begin(); it != averagingVectors.end(); it++ )
  285. {
  286. targetPoint = nullProjectionDirections.transpose() * kernelData->getKernelMatrix() * (*it).second;
  287. targetPoints.insert(pair<int,NICE::Vector>( (*it).first,targetPoint));
  288. }
  289. }
  290. if (verbose)
  291. std::cerr << "KCNullSpace::computeTargetPoints: computation done" << std::endl;
  292. }
  293. ClassificationResult KCNullSpace::classifyKernel ( const NICE::Vector & kernelVector, double kernelSelf ) const
  294. {
  295. if ( targetPoints.size() <= 0 )
  296. fthrow(Exception, "The classifier was not trained with training data (use teach(...))");
  297. if (oneClassSetting)
  298. {
  299. return noveltyDetection ( kernelVector, kernelSelf );
  300. }
  301. NICE::Vector projection(dimNullSpace,0.0);
  302. projection = nullProjectionDirections.transpose() * kernelVector;
  303. FullVector scores ( maxClassNo+1 );
  304. scores.set(- std::numeric_limits<double>::max());
  305. int iter(0);
  306. for ( std::map<int,NICE::Vector>::const_iterator it = targetPoints.begin(); it != targetPoints.end(); it++ )
  307. {
  308. scores[iter] = 1.0-(it->second - projection).normL2();
  309. iter++;
  310. }
  311. ClassificationResult r ( scores.maxElement(), scores );
  312. return r;
  313. }
  314. ClassificationResult KCNullSpace::noveltyDetection ( const NICE::Vector & kernelVector, double kernelSelf ) const
  315. {
  316. if ( targetPoints.size() <= 0 )
  317. fthrow(Exception, "The classifier was not trained with training data (use teach(...))");
  318. NICE::Vector projection(dimNullSpace,0.0);
  319. projection = nullProjectionDirections.transpose() * kernelVector;
  320. FullVector scores ( 2 );
  321. scores.set(- std::numeric_limits<double>::max());
  322. double tmp_score(0.0);
  323. for ( std::map<int,NICE::Vector>::const_iterator it = targetPoints.begin(); it != targetPoints.end(); it++ )
  324. {
  325. tmp_score = 1.0-(it->second - projection).normL2();
  326. if (tmp_score > scores[1])
  327. {
  328. scores[1] = tmp_score;
  329. }
  330. }
  331. ClassificationResult r ( scores.maxElement(), scores );
  332. return r;
  333. }
  334. KCNullSpace* KCNullSpace::clone(void) const
  335. {
  336. KCNullSpace *classifier = new KCNullSpace( *this );
  337. return classifier;
  338. }
  339. void KCNullSpace::clear()
  340. {
  341. //nothing to clear
  342. }
  343. void KCNullSpace::restore(std::istream& ifs, int type)
  344. {
  345. ifs.precision (numeric_limits<double>::digits10 + 1);
  346. ifs >> maxClassNo;
  347. ifs >> oneClassSetting;
  348. ifs >> dimNullSpace;
  349. ifs >> eigenBasis;
  350. int k(0);
  351. int classLabel(0);
  352. int numClassSamples(0);
  353. ifs >> k;
  354. trainingSetStatistic.clear();
  355. for (int i=0; i<k; i++)
  356. {
  357. ifs >> classLabel;
  358. ifs >> numClassSamples;
  359. trainingSetStatistic.insert( pair<int,int>(classLabel,numClassSamples) );
  360. }
  361. ifs >> nullProjectionDirections;
  362. NICE::Vector targetPoint;
  363. ifs >> k;
  364. for (int i=0; i<k; i++)
  365. {
  366. ifs >> classLabel;
  367. ifs >> targetPoint;
  368. targetPoints.insert( pair<int,NICE::Vector>(classLabel,targetPoint) );
  369. }
  370. KernelClassifier::restore(ifs,type);
  371. }
  372. void KCNullSpace::store(std::ostream& ofs, int type) const
  373. {
  374. ofs.precision (numeric_limits<double>::digits10 + 1);
  375. ofs << maxClassNo << endl;
  376. ofs << oneClassSetting << endl;
  377. ofs << dimNullSpace << endl;
  378. ofs << eigenBasis << endl;
  379. ofs << trainingSetStatistic.size() << endl;
  380. for (std::map<int,int>::const_iterator it = trainingSetStatistic.begin() ; it != trainingSetStatistic.end(); it++)
  381. {
  382. ofs << (*it).first << endl;
  383. ofs << (*it).second << endl;
  384. }
  385. ofs << nullProjectionDirections << endl;
  386. ofs << targetPoints.size() << endl;
  387. for (std::map<int,NICE::Vector>::const_iterator it = targetPoints.begin() ; it != targetPoints.end(); it++)
  388. {
  389. ofs << (*it).first << endl;
  390. ofs << (*it).second << endl;
  391. }
  392. KernelClassifier::store(ofs,type);
  393. }