GestureRecognitionToolkit  Version: 0.2.0
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 //Register the MinDist module with the Classifier base class
27 RegisterClassifierModule< MinDist > MinDist::registerModule("MinDist");
28 
29 MinDist::MinDist(bool useScaling,bool useNullRejection,Float nullRejectionCoeff,UINT numClusters)
30 {
31  this->useScaling = useScaling;
32  this->useNullRejection = useNullRejection;
33  this->nullRejectionCoeff = nullRejectionCoeff;
34  this->numClusters = numClusters;
35  supportsNullRejection = true;
36  classType = "MinDist";
37  classifierType = classType;
38  classifierMode = STANDARD_CLASSIFIER_MODE;
39  debugLog.setProceedingText("[DEBUG MinDist]");
40  errorLog.setProceedingText("[ERROR MinDist]");
41  trainingLog.setProceedingText("[TRAINING MinDist]");
42  warningLog.setProceedingText("[WARNING MinDist]");
43 }
44 
46  classType = "MinDist";
47  classifierType = classType;
48  classifierMode = STANDARD_CLASSIFIER_MODE;
49  debugLog.setProceedingText("[DEBUG MinDist]");
50  errorLog.setProceedingText("[ERROR MinDist]");
51  trainingLog.setProceedingText("[TRAINING MinDist]");
52  warningLog.setProceedingText("[WARNING MinDist]");
53  *this = rhs;
54 }
55 
57 {
58 }
59 
61  if( this != &rhs ){
62  //MinDist variables
63  this->numClusters = rhs.numClusters;
64  this->models = rhs.models;
65 
66  //Classifier variables
67  copyBaseVariables( (Classifier*)&rhs );
68  }
69  return *this;
70 }
71 
72 bool MinDist::deepCopyFrom(const Classifier *classifier){
73 
74  if( classifier == NULL ) return false;
75 
76  if( this->getClassifierType() == classifier->getClassifierType() ){
77  //Invoke the equals operator
78  MinDist *ptr = (MinDist*)classifier;
79 
80  this->numClusters = ptr->numClusters;
81  this->models = ptr->models;
82 
83  //Classifier variables
84  return copyBaseVariables( classifier );
85  }
86 
87  return false;
88 }
89 
90 bool MinDist::train_(ClassificationData &trainingData){
91 
92  //Clear any previous models
93  clear();
94 
95  const unsigned int M = trainingData.getNumSamples();
96  const unsigned int N = trainingData.getNumDimensions();
97  const unsigned int K = trainingData.getNumClasses();
98 
99  if( M == 0 ){
100  errorLog << "train_(trainingData &labelledTrainingData) - Training data has zero samples!" << std::endl;
101  return false;
102  }
103 
104  if( M <= numClusters ){
105  errorLog << "train_(trainingData &labelledTrainingData) - 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;
106  return false;
107  }
108 
109  numInputDimensions = N;
110  numClasses = K;
111  models.resize(K);
112  classLabels.resize(K);
113  nullRejectionThresholds.resize(K);
114  ranges = trainingData.getRanges();
115 
116  //Scale the training data if needed
117  if( useScaling ){
118  //Scale the training data between 0 and 1
119  trainingData.scale(0, 1);
120  }
121 
122  //Train each of the models
123  for(UINT k=0; k<numClasses; k++){
124 
125  trainingLog << "Training model for class: " << trainingData.getClassTracker()[k].classLabel << std::endl;
126 
127  //Get the class label for the kth class
128  UINT classLabel = trainingData.getClassTracker()[k].classLabel;
129 
130  //Set the kth class label
131  classLabels[k] = classLabel;
132 
133  //Get all the training data for this class
134  ClassificationData classData = trainingData.getClassData(classLabel);
135  MatrixFloat data(classData.getNumSamples(),N);
136 
137  //Copy the training data into a matrix
138  for(UINT i=0; i<data.getNumRows(); i++){
139  for(UINT j=0; j<data.getNumCols(); j++){
140  data[i][j] = classData[i][j];
141  }
142  }
143 
144  //Train the model for this class
145  models[k].setGamma( nullRejectionCoeff );
146  if( !models[k].train(classLabel,data,numClusters,minChange,maxNumEpochs) ){
147  errorLog << "train_(ClassificationData &labelledTrainingData) - Failed to train model for class: " << classLabel;
148  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;
149  models.clear();
150  return false;
151  }
152 
153  //Set the null rejection threshold
154  nullRejectionThresholds[k] = models[k].getRejectionThreshold();
155 
156  }
157 
158  trained = true;
159  return true;
160 }
161 
162 
163 bool MinDist::predict_(VectorFloat &inputVector){
164 
165  predictedClassLabel = 0;
166  maxLikelihood = 0;
167 
168  if( !trained ){
169  errorLog << "predict_(VectorFloat &inputVector) - MinDist Model Not Trained!" << std::endl;
170  return false;
171  }
172 
173  if( inputVector.size() != numInputDimensions ){
174  errorLog << "predict_(VectorFloat &inputVector) - The size of the input vector (" << inputVector.size() << ") does not match the num features in the model (" << numInputDimensions << std::endl;
175  return false;
176  }
177 
178  if( useScaling ){
179  for(UINT n=0; n<numInputDimensions; n++){
180  inputVector[n] = grt_scale(inputVector[n], ranges[n].minValue, ranges[n].maxValue, 0.0, 1.0);
181  }
182  }
183 
184  if( classLikelihoods.size() != numClasses ) classLikelihoods.resize(numClasses,0);
185  if( classDistances.size() != numClasses ) classDistances.resize(numClasses,0);
186 
187  Float sum = 0;
188  Float minDist = grt_numeric_limits< Float >::max();
189  for(UINT k=0; k<numClasses; k++){
190  //Compute the distance for class k
191  classDistances[k] = models[k].predict( inputVector );
192 
193  //Keep track of the best value
194  if( classDistances[k] < minDist ){
195  minDist = classDistances[k];
196  predictedClassLabel = k;
197  }
198 
199  //Set the class likelihoods as 1.0 / dist[k], the small number is to stop divide by zero
200  classLikelihoods[k] = 1.0 / (classDistances[k] + 0.0001);
201  sum += classLikelihoods[k];
202  }
203 
204  //Normalize the classlikelihoods
205  if( sum != 0 ){
206  for(UINT k=0; k<numClasses; k++){
207  classLikelihoods[k] /= sum;
208  }
209  maxLikelihood = classLikelihoods[predictedClassLabel];
210  }else maxLikelihood = classLikelihoods[predictedClassLabel];
211 
212  if( useNullRejection ){
213  //Check to see if the best result is greater than the models threshold
214  if( minDist <= models[predictedClassLabel].getRejectionThreshold() ) predictedClassLabel = models[predictedClassLabel].getClassLabel();
215  else predictedClassLabel = GRT_DEFAULT_NULL_CLASS_LABEL;
216  }else predictedClassLabel = models[predictedClassLabel].getClassLabel();
217 
218  return true;
219 }
220 
222 
223  //Clear the Classifier variables
225 
226  //Clear the MinDist variables
227  models.clear();
228 
229  return true;
230 }
231 
233 
234  if( trained ){
235  for(UINT k=0; k<numClasses; k++) {
236  models[k].setGamma( nullRejectionCoeff );
237  models[k].recomputeThresholdValue();
238  }
239  return true;
240  }
241  return false;
242 }
243 
244 bool MinDist::setNullRejectionCoeff(Float nullRejectionCoeff){
245 
246  if( nullRejectionCoeff > 0 ){
247  this->nullRejectionCoeff = nullRejectionCoeff;
249  return true;
250  }
251  return false;
252 }
253 
255  return numClusters;
256 }
257 
259  return models;
260 }
261 
262 bool MinDist::save( std::fstream &file ) const{
263 
264  if(!file.is_open())
265  {
266  errorLog <<"save(fstream &file) - The file is not open!" << std::endl;
267  return false;
268  }
269 
270  //Write the header info
271  file<<"GRT_MINDIST_MODEL_FILE_V2.0\n";
272 
273  //Write the classifier settings to the file
275  errorLog <<"save(fstream &file) - Failed to save classifier base settings to file!" << std::endl;
276  return false;
277  }
278 
279  if( trained ){
280 
281  //Write each of the models
282  for(UINT k=0; k<numClasses; k++){
283  file << "ClassLabel: " << models[k].getClassLabel() << std::endl;
284  file << "NumClusters: " << models[k].getNumClusters() << std::endl;
285  file << "RejectionThreshold: " << models[k].getRejectionThreshold() << std::endl;
286  file << "Gamma: " << models[k].getGamma() << std::endl;
287  file << "TrainingMu: " << models[k].getTrainingMu() << std::endl;
288  file << "TrainingSigma: " << models[k].getTrainingSigma() << std::endl;
289  file << "ClusterData:" << std::endl;
290  Matrix<Float> clusters = models[k].getClusters();
291  for(UINT i=0; i<models[k].getNumClusters(); i++){
292  for(UINT j=0; j<models[k].getNumFeatures(); j++){
293  file << clusters[i][j] << "\t";
294  }
295  file << std::endl;
296  }
297  }
298 
299  }
300 
301  return true;
302 }
303 
304 bool MinDist::load( std::fstream &file ){
305 
306  clear();
307 
308  if(!file.is_open())
309  {
310  errorLog << "load(string filename) - Could not open file to load model" << std::endl;
311  return false;
312  }
313 
314  std::string word;
315 
316  //Load the file header
317  file >> word;
318 
319  //Check to see if we should load a legacy file
320  if( word == "GRT_MINDIST_MODEL_FILE_V1.0" ){
321  return loadLegacyModelFromFile( file );
322  }
323 
324  //Find the file type header
325  if(word != "GRT_MINDIST_MODEL_FILE_V2.0"){
326  errorLog << "load(string filename) - Could not find Model File Header" << std::endl;
327  return false;
328  }
329 
330  //Load the base settings from the file
332  errorLog << "load(string filename) - Failed to load base settings from file!" << std::endl;
333  return false;
334  }
335 
336  if( trained ){
337 
338  //Resize the buffer
339  models.resize(numClasses);
340  classLabels.resize(numClasses);
341 
342  //Load each of the K models
343  for(UINT k=0; k<numClasses; k++){
344  Float rejectionThreshold;
345  Float gamma;
346  Float trainingSigma;
347  Float trainingMu;
348 
349  file >> word;
350  if( word != "ClassLabel:" ){
351  errorLog << "load(string filename) - Could not load the class label for class " << k << std::endl;
352  return false;
353  }
354  file >> classLabels[k];
355 
356  file >> word;
357  if( word != "NumClusters:" ){
358  errorLog << "load(string filename) - Could not load the NumClusters for class " << k << std::endl;
359  return false;
360  }
361  file >> numClusters;
362 
363  file >> word;
364  if( word != "RejectionThreshold:" ){
365  errorLog << "load(string filename) - Could not load the RejectionThreshold for class " << k << std::endl;
366  return false;
367  }
368  file >> rejectionThreshold;
369 
370  file >> word;
371  if( word != "Gamma:" ){
372  errorLog << "load(string filename) - Could not load the Gamma for class " << k << std::endl;
373  return false;
374  }
375  file >> gamma;
376 
377  file >> word;
378  if( word != "TrainingMu:" ){
379  errorLog << "load(string filename) - Could not load the TrainingMu for class " << k << std::endl;
380  return false;
381  }
382  file >> trainingMu;
383 
384  file >> word;
385  if( word != "TrainingSigma:" ){
386  errorLog << "load(string filename) - Could not load the TrainingSigma for class " << k << std::endl;
387  return false;
388  }
389  file >> trainingSigma;
390 
391  file >> word;
392  if( word != "ClusterData:" ){
393  errorLog << "load(string filename) - Could not load the ClusterData for class " << k << std::endl;
394  return false;
395  }
396 
397  //Load the cluster data
398  MatrixFloat clusters(numClusters,numInputDimensions);
399  for(UINT i=0; i<numClusters; i++){
400  for(UINT j=0; j<numInputDimensions; j++){
401  file >> clusters[i][j];
402  }
403  }
404 
405  models[k].setClassLabel( classLabels[k] );
406  models[k].setClusters( clusters );
407  models[k].setGamma( gamma );
408  models[k].setRejectionThreshold( rejectionThreshold );
409  models[k].setTrainingSigma( trainingSigma );
410  models[k].setTrainingMu( trainingMu );
411  }
412 
413  //Recompute the null rejection thresholds
415 
416  //Resize the prediction results to make sure it is setup for realtime prediction
417  maxLikelihood = DEFAULT_NULL_LIKELIHOOD_VALUE;
418  bestDistance = DEFAULT_NULL_DISTANCE_VALUE;
419  classLikelihoods.resize(numClasses,DEFAULT_NULL_LIKELIHOOD_VALUE);
420  classDistances.resize(numClasses,DEFAULT_NULL_DISTANCE_VALUE);
421  }
422 
423  return true;
424 }
425 
426 bool MinDist::setNumClusters(UINT numClusters){
427  if( numClusters > 0 ){
428  this->numClusters = numClusters;
429  return true;
430  }
431  return true;
432 }
433 
434 bool MinDist::loadLegacyModelFromFile( std::fstream &file ){
435 
436  std::string word;
437 
438  file >> word;
439  if(word != "NumFeatures:"){
440  errorLog << "load(string filename) - Could not find NumFeatures " << std::endl;
441  return false;
442  }
443  file >> numInputDimensions;
444 
445  file >> word;
446  if(word != "NumClasses:"){
447  errorLog << "load(string filename) - Could not find NumClasses" << std::endl;
448  return false;
449  }
450  file >> numClasses;
451 
452  file >> word;
453  if(word != "UseScaling:"){
454  errorLog << "load(string filename) - Could not find UseScaling" << std::endl;
455  return false;
456  }
457  file >> useScaling;
458 
459  file >> word;
460  if(word != "UseNullRejection:"){
461  errorLog << "load(string filename) - Could not find UseNullRejection" << std::endl;
462  return false;
463  }
464  file >> useNullRejection;
465 
467  if( useScaling ){
468  //Resize the ranges buffer
469  ranges.resize(numInputDimensions);
470 
471  file >> word;
472  if(word != "Ranges:"){
473  errorLog << "load(string filename) - Could not find the Ranges" << std::endl;
474  return false;
475  }
476  for(UINT n=0; n<ranges.size(); n++){
477  file >> ranges[n].minValue;
478  file >> ranges[n].maxValue;
479  }
480  }
481 
482  //Resize the buffer
483  models.resize(numClasses);
484  classLabels.resize(numClasses);
485 
486  //Load each of the K models
487  for(UINT k=0; k<numClasses; k++){
488  Float rejectionThreshold;
489  Float gamma;
490  Float trainingSigma;
491  Float trainingMu;
492 
493  file >> word;
494  if( word != "ClassLabel:" ){
495  errorLog << "load(string filename) - Could not load the class label for class " << k << std::endl;
496  return false;
497  }
498  file >> classLabels[k];
499 
500  file >> word;
501  if( word != "NumClusters:" ){
502  errorLog << "load(string filename) - Could not load the NumClusters for class " << k << std::endl;
503  return false;
504  }
505  file >> numClusters;
506 
507  file >> word;
508  if( word != "RejectionThreshold:" ){
509  errorLog << "load(string filename) - Could not load the RejectionThreshold for class " << k << std::endl;
510  return false;
511  }
512  file >> rejectionThreshold;
513 
514  file >> word;
515  if( word != "Gamma:" ){
516  errorLog << "load(string filename) - Could not load the Gamma for class " << k << std::endl;
517  return false;
518  }
519  file >> gamma;
520 
521  file >> word;
522  if( word != "TrainingMu:" ){
523  errorLog << "load(string filename) - Could not load the TrainingMu for class " << k << std::endl;
524  return false;
525  }
526  file >> trainingMu;
527 
528  file >> word;
529  if( word != "TrainingSigma:" ){
530  errorLog << "load(string filename) - Could not load the TrainingSigma for class " << k << std::endl;
531  return false;
532  }
533  file >> trainingSigma;
534 
535  file >> word;
536  if( word != "ClusterData:" ){
537  errorLog << "load(string filename) - Could not load the ClusterData for class " << k << std::endl;
538  return false;
539  }
540 
541  //Load the cluster data
542  MatrixFloat clusters(numClusters,numInputDimensions);
543  for(UINT i=0; i<numClusters; i++){
544  for(UINT j=0; j<numInputDimensions; j++){
545  file >> clusters[i][j];
546  }
547  }
548 
549  models[k].setClassLabel( classLabels[k] );
550  models[k].setClusters( clusters );
551  models[k].setGamma( gamma );
552  models[k].setRejectionThreshold( rejectionThreshold );
553  models[k].setTrainingSigma( trainingSigma );
554  models[k].setTrainingMu( trainingMu );
555  }
556 
557  //Recompute the null rejection thresholds
559 
560  //Resize the prediction results to make sure it is setup for realtime prediction
561  maxLikelihood = DEFAULT_NULL_LIKELIHOOD_VALUE;
562  bestDistance = DEFAULT_NULL_DISTANCE_VALUE;
563  classLikelihoods.resize(numClasses,DEFAULT_NULL_LIKELIHOOD_VALUE);
564  classDistances.resize(numClasses,DEFAULT_NULL_DISTANCE_VALUE);
565 
566  trained = true;
567 
568  return true;
569 }
570 
571 GRT_END_NAMESPACE
bool saveBaseSettingsToFile(std::fstream &file) const
Definition: Classifier.cpp:256
virtual bool deepCopyFrom(const Classifier *classifier)
Definition: MinDist.cpp:72
#define DEFAULT_NULL_LIKELIHOOD_VALUE
Definition: Classifier.h:38
Vector< MinDistModel > getModels() const
Definition: MinDist.cpp:258
MinDist & operator=(const MinDist &rhs)
Definition: MinDist.cpp:60
std::string getClassifierType() const
Definition: Classifier.cpp:161
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:89
virtual bool clear()
Definition: MinDist.cpp:221
virtual bool predict_(VectorFloat &inputVector)
Definition: MinDist.cpp:163
virtual bool save(std::fstream &file) const
Definition: MinDist.cpp:262
UINT getNumSamples() const
virtual bool load(std::fstream &file)
Definition: MinDist.cpp:304
bool copyBaseVariables(const Classifier *classifier)
Definition: Classifier.cpp:93
bool loadBaseSettingsFromFile(std::fstream &file)
Definition: Classifier.cpp:303
UINT getNumDimensions() const
UINT getNumClasses() const
virtual bool recomputeNullRejectionThresholds()
Definition: MinDist.cpp:232
bool loadLegacyModelFromFile(std::fstream &file)
Definition: MinDist.cpp:434
Vector< MinMax > getRanges() const
virtual bool train_(ClassificationData &trainingData)
Definition: MinDist.cpp:90
virtual ~MinDist(void)
Definition: MinDist.cpp:56
bool scale(const Float minTarget, const Float maxTarget)
virtual bool clear()
Definition: Classifier.cpp:142
This class implements the MinDist classifier algorithm.
MinDist(bool useScaling=false, bool useNullRejection=false, Float nullRejectionCoeff=10.0, UINT numClusters=10)
Definition: MinDist.cpp:29
virtual bool setNullRejectionCoeff(Float nullRejectionCoeff)
Definition: MinDist.cpp:244
UINT getNumClusters() const
Definition: MinDist.cpp:254
bool setNumClusters(UINT numClusters)
Definition: MinDist.cpp:426