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