GestureRecognitionToolkit  Version: 0.2.5
The Gesture Recognition Toolkit (GRT) is a cross-platform, open-source, c++ machine learning library for real-time gesture recognition.
MinDist.cpp
1 /*
2 GRT MIT License
3 Copyright (c) <2012> <Nicholas Gillian, Media Lab, MIT>
4 
5 Permission is hereby granted, free of charge, to any person obtaining a copy of this software
6 and associated documentation files (the "Software"), to deal in the Software without restriction,
7 including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
9 subject to the following conditions:
10 
11 The above copyright notice and this permission notice shall be included in all copies or substantial
12 portions of the Software.
13 
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15 LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
17 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
18 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 */
20 
21 #define GRT_DLL_EXPORTS
22 #include "MinDist.h"
23 
24 GRT_BEGIN_NAMESPACE
25 
26 //Define the string that will be used to identify the object
27 const std::string MinDist::id = "MinDist";
28 std::string MinDist::getId() { return MinDist::id; }
29 
30 //Register the MinDist module with the Classifier base class
31 RegisterClassifierModule< MinDist > MinDist::registerModule( MinDist::getId() );
32 
33 MinDist::MinDist(bool useScaling,bool useNullRejection,Float nullRejectionCoeff,UINT numClusters) : Classifier( MinDist::getId() )
34 {
35  this->useScaling = useScaling;
36  this->useNullRejection = useNullRejection;
37  this->nullRejectionCoeff = nullRejectionCoeff;
38  this->numClusters = numClusters;
39  supportsNullRejection = true;
40  classifierMode = STANDARD_CLASSIFIER_MODE;
41 }
42 
44 {
45  classifierMode = STANDARD_CLASSIFIER_MODE;
46  *this = rhs;
47 }
48 
50 {
51 }
52 
54  if( this != &rhs ){
55  //MinDist variables
56  this->numClusters = rhs.numClusters;
57  this->models = rhs.models;
58 
59  //Classifier variables
60  copyBaseVariables( (Classifier*)&rhs );
61  }
62  return *this;
63 }
64 
65 bool MinDist::deepCopyFrom(const Classifier *classifier){
66 
67  if( classifier == NULL ) return false;
68 
69  if( this->getId() == classifier->getId() ){
70  //Invoke the equals operator
71  const MinDist *ptr = dynamic_cast<const MinDist*>(classifier);
72 
73  this->numClusters = ptr->numClusters;
74  this->models = ptr->models;
75 
76  //Classifier variables
77  return copyBaseVariables( classifier );
78  }
79 
80  return false;
81 }
82 
83 bool MinDist::train_(ClassificationData &trainingData){
84 
85  //Clear any previous models
86  clear();
87 
88  const unsigned int M = trainingData.getNumSamples();
89  const unsigned int N = trainingData.getNumDimensions();
90  const unsigned int K = trainingData.getNumClasses();
91 
92  if( M == 0 ){
93  errorLog << __GRT_LOG__ << " Training data has zero samples!" << std::endl;
94  return false;
95  }
96 
97  if( M <= numClusters ){
98  errorLog << __GRT_LOG__ << " There are not enough training samples for the number of clusters. Either reduce the number of clusters or increase the number of training samples!" << std::endl;
99  return false;
100  }
101 
102  numInputDimensions = N;
103  numOutputDimensions = K;
104  numClasses = K;
105  models.resize(K);
106  classLabels.resize(K);
107  nullRejectionThresholds.resize(K);
108  ranges = trainingData.getRanges();
109  ClassificationData validationData;
110 
111  //Scale the training data if needed
112  if( useScaling ){
113  //Scale the training data between 0 and 1
114  trainingData.scale(0, 1);
115  }
116 
117  if( useValidationSet ){
118  validationData = trainingData.split( 100-validationSetSize );
119  }
120 
121  //Train each of the models
122  for(UINT k=0; k<numClasses; k++){
123 
124  trainingLog << "Training model for class: " << trainingData.getClassTracker()[k].classLabel << std::endl;
125 
126  //Pass the logging state onto the kmeans algorithm
127  models[k].setTrainingLoggingEnabled( this->getTrainingLoggingEnabled() );
128 
129  //Get the class label for the kth class
130  UINT classLabel = trainingData.getClassTracker()[k].classLabel;
131 
132  //Set the kth class label
133  classLabels[k] = classLabel;
134 
135  //Get all the training data for this class
136  ClassificationData classData = trainingData.getClassData(classLabel);
137  MatrixFloat data(classData.getNumSamples(),N);
138 
139  //Copy the training data into a matrix
140  for(UINT i=0; i<data.getNumRows(); i++){
141  for(UINT j=0; j<data.getNumCols(); j++){
142  data[i][j] = classData[i][j];
143  }
144  }
145 
146  //Train the model for this class
147  models[k].setGamma( nullRejectionCoeff );
148  if( !models[k].train(classLabel,data,numClusters,minChange,maxNumEpochs) ){
149  errorLog << __GRT_LOG__ << " Failed to train model for class: " << classLabel;
150  errorLog << ". This is might be because this class does not have enough training samples! You should reduce the number of clusters or increase the number of training samples for this class." << std::endl;
151  models.clear();
152  return false;
153  }
154 
155  //Set the null rejection threshold
156  nullRejectionThresholds[k] = models[k].getRejectionThreshold();
157  }
158 
159  //Flag that the models have been trained
160  trained = true;
161  converged = true;
162 
163  //Compute the final training stats
164  trainingSetAccuracy = 0;
165  validationSetAccuracy = 0;
166 
167  //If scaling was on, then the data will already be scaled, so turn it off temporially so we can test the model accuracy
168  bool scalingState = useScaling;
169  useScaling = false;
170  if( !computeAccuracy( trainingData, trainingSetAccuracy ) ){
171  trained = false;
172  converged = false;
173  errorLog << __GRT_LOG__ << " Failed to compute training set accuracy! Failed to fully train model!" << std::endl;
174  return false;
175  }
176 
177  if( useValidationSet ){
178  if( !computeAccuracy( validationData, validationSetAccuracy ) ){
179  trained = false;
180  converged = false;
181  errorLog << __GRT_LOG__ << " Failed to compute validation set accuracy! Failed to fully train model!" << std::endl;
182  return false;
183  }
184 
185  }
186 
187  trainingLog << "Training set accuracy: " << trainingSetAccuracy << std::endl;
188 
189  if( useValidationSet ){
190  trainingLog << "Validation set accuracy: " << validationSetAccuracy << std::endl;
191  }
192 
193  //Reset the scaling state for future prediction
194  useScaling = scalingState;
195 
196  return trained;
197 }
198 
199 bool MinDist::predict_(VectorFloat &inputVector){
200 
201  predictedClassLabel = 0;
202  maxLikelihood = 0;
203 
204  if( !trained ){
205  errorLog << "predict_(VectorFloat &inputVector) - MinDist Model Not Trained!" << std::endl;
206  return false;
207  }
208 
209  if( inputVector.size() != numInputDimensions ){
210  errorLog << "predict_(VectorFloat &inputVector) - The size of the input vector (" << inputVector.size() << ") does not match the num features in the model (" << numInputDimensions << std::endl;
211  return false;
212  }
213 
214  if( useScaling ){
215  for(UINT n=0; n<numInputDimensions; n++){
216  inputVector[n] = grt_scale(inputVector[n], ranges[n].minValue, ranges[n].maxValue, 0.0, 1.0);
217  }
218  }
219 
220  if( classLikelihoods.size() != numClasses ) classLikelihoods.resize(numClasses,0);
221  if( classDistances.size() != numClasses ) classDistances.resize(numClasses,0);
222 
223  Float sum = 0;
224  Float minDist = grt_numeric_limits< Float >::max();
225  for(UINT k=0; k<numClasses; k++){
226  //Compute the distance for class k
227  classDistances[k] = models[k].predict( inputVector );
228 
229  //Keep track of the best value
230  if( classDistances[k] < minDist ){
231  minDist = classDistances[k];
232  predictedClassLabel = k;
233  }
234 
235  //Set the class likelihoods as 1.0 / dist[k], the small number is to stop divide by zero
236  classLikelihoods[k] = 1.0 / (classDistances[k] + 0.0001);
237  sum += classLikelihoods[k];
238  }
239 
240  //Normalize the classlikelihoods
241  if( sum != 0 ){
242  for(UINT k=0; k<numClasses; k++){
243  classLikelihoods[k] /= sum;
244  }
245  maxLikelihood = classLikelihoods[predictedClassLabel];
246  }else maxLikelihood = classLikelihoods[predictedClassLabel];
247 
248  if( useNullRejection ){
249  //Check to see if the best result is greater than the models threshold
250  if( minDist <= models[predictedClassLabel].getRejectionThreshold() ) predictedClassLabel = models[predictedClassLabel].getClassLabel();
251  else predictedClassLabel = GRT_DEFAULT_NULL_CLASS_LABEL;
252  }else predictedClassLabel = models[predictedClassLabel].getClassLabel();
253 
254  return true;
255 }
256 
258 
259  //Clear the Classifier variables
261 
262  //Clear the MinDist variables
263  models.clear();
264 
265  return true;
266 }
267 
269 
270  if( trained ){
271  for(UINT k=0; k<numClasses; k++) {
272  models[k].setGamma( nullRejectionCoeff );
273  models[k].recomputeThresholdValue();
274  }
275  return true;
276  }
277  return false;
278 }
279 
280 bool MinDist::setNullRejectionCoeff(Float nullRejectionCoeff){
281 
282  if( nullRejectionCoeff > 0 ){
283  this->nullRejectionCoeff = nullRejectionCoeff;
285  return true;
286  }
287  return false;
288 }
289 
291  return numClusters;
292 }
293 
295  return models;
296 }
297 
298 bool MinDist::save( std::fstream &file ) const{
299 
300  if(!file.is_open())
301  {
302  errorLog <<"save(fstream &file) - The file is not open!" << std::endl;
303  return false;
304  }
305 
306  //Write the header info
307  file<<"GRT_MINDIST_MODEL_FILE_V2.0\n";
308 
309  //Write the classifier settings to the file
311  errorLog <<"save(fstream &file) - Failed to save classifier base settings to file!" << std::endl;
312  return false;
313  }
314 
315  if( trained ){
316 
317  //Write each of the models
318  for(UINT k=0; k<numClasses; k++){
319  file << "ClassLabel: " << models[k].getClassLabel() << std::endl;
320  file << "NumClusters: " << models[k].getNumClusters() << std::endl;
321  file << "RejectionThreshold: " << models[k].getRejectionThreshold() << std::endl;
322  file << "Gamma: " << models[k].getGamma() << std::endl;
323  file << "TrainingMu: " << models[k].getTrainingMu() << std::endl;
324  file << "TrainingSigma: " << models[k].getTrainingSigma() << std::endl;
325  file << "ClusterData:" << std::endl;
326  Matrix<Float> clusters = models[k].getClusters();
327  for(UINT i=0; i<models[k].getNumClusters(); i++){
328  for(UINT j=0; j<models[k].getNumFeatures(); j++){
329  file << clusters[i][j] << "\t";
330  }
331  file << std::endl;
332  }
333  }
334 
335  }
336 
337  return true;
338 }
339 
340 bool MinDist::load( std::fstream &file ){
341 
342  clear();
343 
344  if(!file.is_open())
345  {
346  errorLog << "load(string filename) - Could not open file to load model" << std::endl;
347  return false;
348  }
349 
350  std::string word;
351 
352  //Load the file header
353  file >> word;
354 
355  //Check to see if we should load a legacy file
356  if( word == "GRT_MINDIST_MODEL_FILE_V1.0" ){
357  return loadLegacyModelFromFile( file );
358  }
359 
360  //Find the file type header
361  if(word != "GRT_MINDIST_MODEL_FILE_V2.0"){
362  errorLog << "load(string filename) - Could not find Model File Header" << std::endl;
363  return false;
364  }
365 
366  //Load the base settings from the file
368  errorLog << "load(string filename) - Failed to load base settings from file!" << std::endl;
369  return false;
370  }
371 
372  if( trained ){
373 
374  //Resize the buffer
375  models.resize(numClasses);
376  classLabels.resize(numClasses);
377 
378  //Load each of the K models
379  for(UINT k=0; k<numClasses; k++){
380  Float rejectionThreshold;
381  Float gamma;
382  Float trainingSigma;
383  Float trainingMu;
384 
385  file >> word;
386  if( word != "ClassLabel:" ){
387  errorLog << "load(string filename) - Could not load the class label for class " << k << std::endl;
388  return false;
389  }
390  file >> classLabels[k];
391 
392  file >> word;
393  if( word != "NumClusters:" ){
394  errorLog << "load(string filename) - Could not load the NumClusters for class " << k << std::endl;
395  return false;
396  }
397  file >> numClusters;
398 
399  file >> word;
400  if( word != "RejectionThreshold:" ){
401  errorLog << "load(string filename) - Could not load the RejectionThreshold for class " << k << std::endl;
402  return false;
403  }
404  file >> rejectionThreshold;
405 
406  file >> word;
407  if( word != "Gamma:" ){
408  errorLog << "load(string filename) - Could not load the Gamma for class " << k << std::endl;
409  return false;
410  }
411  file >> gamma;
412 
413  file >> word;
414  if( word != "TrainingMu:" ){
415  errorLog << "load(string filename) - Could not load the TrainingMu for class " << k << std::endl;
416  return false;
417  }
418  file >> trainingMu;
419 
420  file >> word;
421  if( word != "TrainingSigma:" ){
422  errorLog << "load(string filename) - Could not load the TrainingSigma for class " << k << std::endl;
423  return false;
424  }
425  file >> trainingSigma;
426 
427  file >> word;
428  if( word != "ClusterData:" ){
429  errorLog << "load(string filename) - Could not load the ClusterData for class " << k << std::endl;
430  return false;
431  }
432 
433  //Load the cluster data
434  MatrixFloat clusters(numClusters,numInputDimensions);
435  for(UINT i=0; i<numClusters; i++){
436  for(UINT j=0; j<numInputDimensions; j++){
437  file >> clusters[i][j];
438  }
439  }
440 
441  models[k].setClassLabel( classLabels[k] );
442  models[k].setClusters( clusters );
443  models[k].setGamma( gamma );
444  models[k].setRejectionThreshold( rejectionThreshold );
445  models[k].setTrainingSigma( trainingSigma );
446  models[k].setTrainingMu( trainingMu );
447  }
448 
449  //Recompute the null rejection thresholds
451 
452  //Resize the prediction results to make sure it is setup for realtime prediction
453  maxLikelihood = DEFAULT_NULL_LIKELIHOOD_VALUE;
454  bestDistance = DEFAULT_NULL_DISTANCE_VALUE;
455  classLikelihoods.resize(numClasses,DEFAULT_NULL_LIKELIHOOD_VALUE);
456  classDistances.resize(numClasses,DEFAULT_NULL_DISTANCE_VALUE);
457  }
458 
459  return true;
460 }
461 
462 bool MinDist::setNumClusters(UINT numClusters){
463  if( numClusters > 0 ){
464  this->numClusters = numClusters;
465  return true;
466  }
467  return true;
468 }
469 
470 bool MinDist::loadLegacyModelFromFile( std::fstream &file ){
471 
472  std::string word;
473 
474  file >> word;
475  if(word != "NumFeatures:"){
476  errorLog << "load(string filename) - Could not find NumFeatures " << std::endl;
477  return false;
478  }
479  file >> numInputDimensions;
480 
481  file >> word;
482  if(word != "NumClasses:"){
483  errorLog << "load(string filename) - Could not find NumClasses" << std::endl;
484  return false;
485  }
486  file >> numClasses;
487 
488  file >> word;
489  if(word != "UseScaling:"){
490  errorLog << "load(string filename) - Could not find UseScaling" << std::endl;
491  return false;
492  }
493  file >> useScaling;
494 
495  file >> word;
496  if(word != "UseNullRejection:"){
497  errorLog << "load(string filename) - Could not find UseNullRejection" << std::endl;
498  return false;
499  }
500  file >> useNullRejection;
501 
503  if( useScaling ){
504  //Resize the ranges buffer
505  ranges.resize(numInputDimensions);
506 
507  file >> word;
508  if(word != "Ranges:"){
509  errorLog << "load(string filename) - Could not find the Ranges" << std::endl;
510  return false;
511  }
512  for(UINT n=0; n<ranges.size(); n++){
513  file >> ranges[n].minValue;
514  file >> ranges[n].maxValue;
515  }
516  }
517 
518  //Resize the buffer
519  models.resize(numClasses);
520  classLabels.resize(numClasses);
521 
522  //Load each of the K models
523  for(UINT k=0; k<numClasses; k++){
524  Float rejectionThreshold;
525  Float gamma;
526  Float trainingSigma;
527  Float trainingMu;
528 
529  file >> word;
530  if( word != "ClassLabel:" ){
531  errorLog << "load(string filename) - Could not load the class label for class " << k << std::endl;
532  return false;
533  }
534  file >> classLabels[k];
535 
536  file >> word;
537  if( word != "NumClusters:" ){
538  errorLog << "load(string filename) - Could not load the NumClusters for class " << k << std::endl;
539  return false;
540  }
541  file >> numClusters;
542 
543  file >> word;
544  if( word != "RejectionThreshold:" ){
545  errorLog << "load(string filename) - Could not load the RejectionThreshold for class " << k << std::endl;
546  return false;
547  }
548  file >> rejectionThreshold;
549 
550  file >> word;
551  if( word != "Gamma:" ){
552  errorLog << "load(string filename) - Could not load the Gamma for class " << k << std::endl;
553  return false;
554  }
555  file >> gamma;
556 
557  file >> word;
558  if( word != "TrainingMu:" ){
559  errorLog << "load(string filename) - Could not load the TrainingMu for class " << k << std::endl;
560  return false;
561  }
562  file >> trainingMu;
563 
564  file >> word;
565  if( word != "TrainingSigma:" ){
566  errorLog << "load(string filename) - Could not load the TrainingSigma for class " << k << std::endl;
567  return false;
568  }
569  file >> trainingSigma;
570 
571  file >> word;
572  if( word != "ClusterData:" ){
573  errorLog << "load(string filename) - Could not load the ClusterData for class " << k << std::endl;
574  return false;
575  }
576 
577  //Load the cluster data
578  MatrixFloat clusters(numClusters,numInputDimensions);
579  for(UINT i=0; i<numClusters; i++){
580  for(UINT j=0; j<numInputDimensions; j++){
581  file >> clusters[i][j];
582  }
583  }
584 
585  models[k].setClassLabel( classLabels[k] );
586  models[k].setClusters( clusters );
587  models[k].setGamma( gamma );
588  models[k].setRejectionThreshold( rejectionThreshold );
589  models[k].setTrainingSigma( trainingSigma );
590  models[k].setTrainingMu( trainingMu );
591  }
592 
593  //Recompute the null rejection thresholds
595 
596  //Resize the prediction results to make sure it is setup for realtime prediction
597  maxLikelihood = DEFAULT_NULL_LIKELIHOOD_VALUE;
598  bestDistance = DEFAULT_NULL_DISTANCE_VALUE;
599  classLikelihoods.resize(numClasses,DEFAULT_NULL_LIKELIHOOD_VALUE);
600  classDistances.resize(numClasses,DEFAULT_NULL_DISTANCE_VALUE);
601 
602  trained = true;
603 
604  return true;
605 }
606 
607 GRT_END_NAMESPACE
bool saveBaseSettingsToFile(std::fstream &file) const
Definition: Classifier.cpp:274
virtual bool deepCopyFrom(const Classifier *classifier)
Definition: MinDist.cpp:65
std::string getId() const
Definition: GRTBase.cpp:85
#define DEFAULT_NULL_LIKELIHOOD_VALUE
Definition: Classifier.h:33
Vector< MinDistModel > getModels() const
Definition: MinDist.cpp:294
bool getTrainingLoggingEnabled() const
Definition: MLBase.cpp:312
MinDist & operator=(const MinDist &rhs)
Definition: MinDist.cpp:53
Vector< ClassTracker > getClassTracker() const
ClassificationData getClassData(const UINT classLabel) const
virtual bool resize(const unsigned int size)
Definition: Vector.h:133
virtual bool train(ClassificationData trainingData)
Definition: MLBase.cpp:107
virtual bool clear()
Definition: MinDist.cpp:257
virtual bool predict_(VectorFloat &inputVector)
Definition: MinDist.cpp:199
virtual bool save(std::fstream &file) const
Definition: MinDist.cpp:298
virtual bool computeAccuracy(const ClassificationData &data, Float &accuracy)
Definition: Classifier.cpp:171
UINT getNumSamples() const
virtual bool load(std::fstream &file)
Definition: MinDist.cpp:340
static std::string getId()
Definition: MinDist.cpp:28
bool copyBaseVariables(const Classifier *classifier)
Definition: Classifier.cpp:101
bool loadBaseSettingsFromFile(std::fstream &file)
Definition: Classifier.cpp:321
UINT getNumDimensions() const
UINT getNumClasses() const
virtual bool recomputeNullRejectionThresholds()
Definition: MinDist.cpp:268
bool loadLegacyModelFromFile(std::fstream &file)
Definition: MinDist.cpp:470
Vector< MinMax > getRanges() const
ClassificationData split(const UINT splitPercentage, const bool useStratifiedSampling=false)
virtual bool train_(ClassificationData &trainingData)
Definition: MinDist.cpp:83
virtual ~MinDist(void)
Definition: MinDist.cpp:49
bool scale(const Float minTarget, const Float maxTarget)
virtual bool clear()
Definition: Classifier.cpp:151
MinDist(bool useScaling=false, bool useNullRejection=false, Float nullRejectionCoeff=10.0, UINT numClusters=10)
Definition: MinDist.cpp:33
virtual bool setNullRejectionCoeff(Float nullRejectionCoeff)
Definition: MinDist.cpp:280
This is the main base class that all GRT Classification algorithms should inherit from...
Definition: Classifier.h:41
UINT getNumClusters() const
Definition: MinDist.cpp:290
bool setNumClusters(UINT numClusters)
Definition: MinDist.cpp:462