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.
Softmax.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 "Softmax.h"
23 
24 GRT_BEGIN_NAMESPACE
25 
26 //Define the string that will be used to identify the object
27 const std::string Softmax::id = "Softmax";
28 std::string Softmax::getId() { return Softmax::id; }
29 
30 //Register the Softmax module with the Classifier base class
31 RegisterClassifierModule< Softmax > Softmax::registerModule( Softmax::getId() );
32 
33 Softmax::Softmax(const bool useScaling,const Float learningRate,const Float minChange,const UINT maxNumEpochs,const UINT batchSize) : Classifier( Softmax::getId() )
34 {
35  this->useScaling = useScaling;
36  this->learningRate = learningRate;
37  this->minChange = minChange;
38  this->maxNumEpochs = maxNumEpochs;
39  this->batchSize = batchSize;
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  this->batchSize = rhs.batchSize;
56  this->models = rhs.models;
57 
58  //Copy the base classifier variables
59  copyBaseVariables( (Classifier*)&rhs );
60  }
61  return *this;
62 }
63 
64 bool Softmax::deepCopyFrom(const Classifier *classifier){
65 
66  if( classifier == NULL ) return false;
67 
68  if( this->getId() == classifier->getId() ){
69  const Softmax *ptr = dynamic_cast<const Softmax*>(classifier);
70 
71  this->batchSize = ptr->batchSize;
72  this->models = ptr->models;
73 
74  //Copy the base classifier variables
75  return copyBaseVariables( classifier );
76  }
77  return false;
78 }
79 
80 bool Softmax::train_(ClassificationData &trainingData){
81 
82  //Clear any previous model
83  clear();
84 
85  const unsigned int M = trainingData.getNumSamples();
86  const unsigned int N = trainingData.getNumDimensions();
87  const unsigned int K = trainingData.getNumClasses();
88 
89  if( M == 0 ){
90  errorLog << __GRT_LOG__ << " Training data has zero samples!" << std::endl;
91  return false;
92  }
93 
94  numInputDimensions = N;
95  numOutputDimensions = K;
96  numClasses = K;
97  models.resize(K);
98  classLabels.resize(K);
99  ranges = trainingData.getRanges();
100  ClassificationData validationData;
101 
102  //Scale the training data if needed
103  if( useScaling ){
104  //Scale the training data between 0 and 1
105  trainingData.scale(0, 1);
106  }
107 
108  if( useValidationSet ){
109  validationData = trainingData.split( 100-validationSetSize );
110  }
111 
112  //Train a regression model for each class in the training data
113  for(UINT k=0; k<numClasses; k++){
114 
115  //Set the class label
116  classLabels[k] = trainingData.getClassTracker()[k].classLabel;
117 
118  //Train the model
119  if( !trainSoftmaxModel(classLabels[k],models[k],trainingData) ){
120  errorLog << __GRT_LOG__ << " Failed to train model for class: " << classLabels[k] << std::endl;
121  return false;
122  }
123  }
124 
125  //Flag that the models have been trained
126  trained = true;
127  converged = true;
128 
129  //Compute the final training stats
130  trainingSetAccuracy = 0;
131  validationSetAccuracy = 0;
132 
133  //If scaling was on, then the data will already be scaled, so turn it off temporially so we can test the model accuracy
134  bool scalingState = useScaling;
135  useScaling = false;
136  if( !computeAccuracy( trainingData, trainingSetAccuracy ) ){
137  trained = false;
138  converged = false;
139  errorLog << __GRT_LOG__ << " Failed to compute training set accuracy! Failed to fully train model!" << std::endl;
140  return false;
141  }
142 
143  if( useValidationSet ){
144  if( !computeAccuracy( validationData, validationSetAccuracy ) ){
145  trained = false;
146  converged = false;
147  errorLog << __GRT_LOG__ << " Failed to compute validation set accuracy! Failed to fully train model!" << std::endl;
148  return false;
149  }
150  }
151 
152  trainingLog << "Training set accuracy: " << trainingSetAccuracy << std::endl;
153 
154  if( useValidationSet ){
155  trainingLog << "Validation set accuracy: " << validationSetAccuracy << std::endl;
156  }
157 
158  //Reset the scaling state for future prediction
159  useScaling = scalingState;
160 
161  return trained;
162 }
163 
164 bool Softmax::predict_(VectorFloat &inputVector){
165 
166  if( !trained ){
167  errorLog << __GRT_LOG__ << " Model Not Trained!" << std::endl;
168  return false;
169  }
170 
171  predictedClassLabel = 0;
172  maxLikelihood = -10000;
173 
174  if( !trained ) return false;
175 
176  if( inputVector.getSize() != numInputDimensions ){
177  errorLog << __GRT_LOG__ << " The size of the input vector (" << inputVector.getSize() << ") does not match the num features in the model (" << numInputDimensions << std::endl;
178  return false;
179  }
180 
181  if( useScaling ){
182  for(UINT n=0; n<numInputDimensions; n++){
183  inputVector[n] = scale(inputVector[n], ranges[n].minValue, ranges[n].maxValue, 0, 1);
184  }
185  }
186 
187  if( classLikelihoods.size() != numClasses ) classLikelihoods.resize(numClasses,0);
188  if( classDistances.size() != numClasses ) classDistances.resize(numClasses,0);
189 
190  //Loop over each class and compute the likelihood of the input data coming from class k. Pick the class with the highest likelihood
191  Float sum = 0;
192  Float bestEstimate = -grt_numeric_limits< Float >::max();
193  UINT bestIndex = 0;
194  for(UINT k=0; k<numClasses; k++){
195  Float estimate = models[k].compute( inputVector );
196 
197  if( estimate > bestEstimate ){
198  bestEstimate = estimate;
199  bestIndex = k;
200  }
201 
202  classDistances[k] = estimate;
203  classLikelihoods[k] = estimate;
204  sum += estimate;
205  }
206 
207  if( sum > 1.0e-5 ){
208  for(UINT k=0; k<numClasses; k++){
209  classLikelihoods[k] /= sum;
210  }
211  }else{
212  //If the sum is less than the value above then none of the models found a positive class
213  maxLikelihood = bestEstimate;
214  predictedClassLabel = GRT_DEFAULT_NULL_CLASS_LABEL;
215  return true;
216  }
217  maxLikelihood = classLikelihoods[bestIndex];
218  predictedClassLabel = classLabels[bestIndex];
219 
220  return true;
221 }
222 
223 bool Softmax::trainSoftmaxModel(UINT classLabel,SoftmaxModel &model,ClassificationData &data){
224 
225  Float error = 0;
226  Float errorSum = 0;
227  Float lastErrorSum = 0;
228  Float delta = 0;
229  const UINT N = data.getNumDimensions();
230  const UINT M = data.getNumSamples();
231  UINT iter = 0;
232  bool keepTraining = true;
233  Random random;
234  VectorFloat y(M);
235  VectorFloat batchMean(N);
236  Vector< UINT > randomTrainingOrder(M);
237  Vector< VectorFloat > batchData(batchSize,VectorFloat(N));
238 
239  //Init the model
240  model.init( classLabel, N );
241 
242  //Setup the target vector, the input data is relabelled as positive samples (with label 1.0) and negative samples (with label 0.0)
243  for(UINT i=0; i<M; i++){
244  y[i] = data[i].getClassLabel()==classLabel ? 1.0 : 0;
245  }
246 
247  //In most cases, the training data is grouped into classes (100 samples for class 1, followed by 100 samples for class 2, etc.)
248  //This can cause a problem for stochastic gradient descent algorithm. To avoid this issue, we randomly shuffle the order of the
249  //training samples. This random order is then used at each epoch.
250  for(UINT i=0; i<M; i++){
251  randomTrainingOrder[i] = i;
252  }
253  std::random_shuffle(randomTrainingOrder.begin(), randomTrainingOrder.end());
254 
255  //Clear any previous training results
256  trainingResults.clear();
257  trainingResults.reserve( maxNumEpochs );
258  TrainingResult epochResult;
259 
260  //Run the main stochastic gradient descent training algorithm
261  while( keepTraining ){
262 
263  //Run one epoch of training using stochastic gradient descent
264  errorSum = 0;
265  UINT m=0;
266  while( m < M ){
267  //Get the batch data for this update
268  UINT roundSize = m+batchSize < M ? batchSize : M-m;
269  batchMean.fill(0.0);
270  for(UINT i=0; i<roundSize; i++){
271  for(UINT j=0; j<N; j++){
272  batchData[i][j] = data[ randomTrainingOrder[m+i] ][j];
273  batchMean[j] += batchData[i][j];
274  }
275  }
276 
277  for(UINT j=0; j<N; j++) batchMean[j] /= roundSize;
278 
279  //Compute the error on this batch, given the current weights
280  error = 0.0;
281  for(UINT i=0; i<roundSize; i++){
282  error += y[ randomTrainingOrder[m+i] ] - model.compute( batchData[i] );
283  }
284  error /= roundSize;
285  errorSum += error;
286 
287  //Update the weights
288  for(UINT j=0; j<N; j++){
289  model.w[j] += learningRate * error * batchMean[j];
290  }
291  model.w0 += learningRate * error;
292 
293  m += roundSize;
294  }
295 
296  //Compute the error
297  delta = fabs( errorSum-lastErrorSum );
298  lastErrorSum = errorSum;
299 
300  //Check to see if we should stop
301  if( delta <= minChange ){
302  keepTraining = false;
303  }
304 
305  if( ++iter >= maxNumEpochs ){
306  keepTraining = false;
307  }
308 
309  trainingLog << "Class: " << classLabel << " Epoch: " << iter << " TotalError: " << errorSum << " Delta: " << delta << std::endl;
310  epochResult.setClassificationResult( iter, errorSum, this );
311  trainingResults.push_back( epochResult );
312  }
313 
314  return true;
315 }
316 
318 
319  //Clear the Classifier variables
321 
322  //Clear the Softmax model
323  models.clear();
324 
325  return true;
326 }
327 
328 bool Softmax::save( std::fstream &file ) const{
329 
330  if(!file.is_open())
331  {
332  errorLog << __GRT_LOG__ << " The file is not open!" << std::endl;
333  return false;
334  }
335 
336  //Write the header info
337  file<<"GRT_SOFTMAX_MODEL_FILE_V2.0\n";
338 
339  //Write the classifier settings to the file
341  errorLog << __GRT_LOG__ << " Failed to save classifier base settings to file!" << std::endl;
342  return false;
343  }
344 
345  if( trained ){
346  file << "Models:\n";
347  for(UINT k=0; k<numClasses; k++){
348  file << "ClassLabel: " << models[k].classLabel << std::endl;
349  file << "Weights: " << models[k].w0;
350  for(UINT n=0; n<numInputDimensions; n++){
351  file << " " << models[k].w[n];
352  }
353  file << std::endl;
354  }
355  }
356 
357  return true;
358 }
359 
360 bool Softmax::load( std::fstream &file ){
361 
362  trained = false;
363  numInputDimensions = 0;
364  numClasses = 0;
365  models.clear();
366  classLabels.clear();
367 
368  if(!file.is_open())
369  {
370  errorLog << __GRT_LOG__ << " Could not open file to load model" << std::endl;
371  return false;
372  }
373 
374  std::string word;
375 
376  file >> word;
377 
378  //Check to see if we should load a legacy file
379  if( word == "GRT_SOFTMAX_MODEL_FILE_V1.0" ){
380  return loadLegacyModelFromFile( file );
381  }
382 
383  //Find the file type header
384  if(word != "GRT_SOFTMAX_MODEL_FILE_V2.0"){
385  errorLog << __GRT_LOG__ << " Could not find Model File Header" << std::endl;
386  return false;
387  }
388 
389  //Load the base settings from the file
391  errorLog << __GRT_LOG__ << " Failed to load base settings from file!" << std::endl;
392  return false;
393  }
394 
395  if( trained ){
396  //Resize the buffer
397  models.resize(numClasses);
398  classLabels.resize(numClasses);
399 
400  //Load the models
401  file >> word;
402  if(word != "Models:"){
403  errorLog << __GRT_LOG__ << " Could not find the Models!" << std::endl;
404  return false;
405  }
406 
407  for(UINT k=0; k<numClasses; k++){
408  file >> word;
409  if(word != "ClassLabel:"){
410  errorLog << __GRT_LOG__ << " Could not find the ClassLabel for model: " << k << "!" << std::endl;
411  return false;
412  }
413  file >> models[k].classLabel;
414  classLabels[k] = models[k].classLabel;
415 
416  file >> word;
417  if(word != "Weights:"){
418  errorLog << __GRT_LOG__ << " Could not find the Weights for model: " << k << "!" << std::endl;
419  return false;
420  }
421  file >> models[k].w0;
422 
423  models[k].N = numInputDimensions;
424  models[k].w.resize( numInputDimensions );
425  for(UINT n=0; n<numInputDimensions; n++){
426  file >> models[k].w[n];
427  }
428  }
429 
430  //Recompute the null rejection thresholds
432 
433  //Resize the prediction results to make sure it is setup for realtime prediction
434  maxLikelihood = DEFAULT_NULL_LIKELIHOOD_VALUE;
435  bestDistance = DEFAULT_NULL_DISTANCE_VALUE;
436  classLikelihoods.resize(numClasses,DEFAULT_NULL_LIKELIHOOD_VALUE);
437  classDistances.resize(numClasses,DEFAULT_NULL_DISTANCE_VALUE);
438  }
439 
440  return true;
441 }
442 
444  return models;
445 }
446 
447 bool Softmax::loadLegacyModelFromFile( std::fstream &file ){
448 
449  std::string word;
450 
451  file >> word;
452  if(word != "NumFeatures:"){
453  errorLog << __GRT_LOG__ << " Could not find NumFeatures!" << std::endl;
454  return false;
455  }
456  file >> numInputDimensions;
457 
458  file >> word;
459  if(word != "NumClasses:"){
460  errorLog << __GRT_LOG__ << " Could not find NumClasses!" << std::endl;
461  return false;
462  }
463  file >> numClasses;
464 
465  file >> word;
466  if(word != "UseScaling:"){
467  errorLog << __GRT_LOG__ << " Could not find UseScaling!" << std::endl;
468  return false;
469  }
470  file >> useScaling;
471 
472  file >> word;
473  if(word != "UseNullRejection:"){
474  errorLog << __GRT_LOG__ << " Could not find UseNullRejection!" << std::endl;
475  return false;
476  }
477  file >> useNullRejection;
478 
480  if( useScaling ){
481  //Resize the ranges buffer
482  ranges.resize(numInputDimensions);
483 
484  file >> word;
485  if(word != "Ranges:"){
486  errorLog << __GRT_LOG__ << " Could not find the Ranges!" << std::endl;
487  return false;
488  }
489  for(UINT n=0; n<ranges.getSize(); n++){
490  file >> ranges[n].minValue;
491  file >> ranges[n].maxValue;
492  }
493  }
494 
495  //Resize the buffer
496  models.resize(numClasses);
497  classLabels.resize(numClasses);
498 
499  //Load the models
500  file >> word;
501  if(word != "Models:"){
502  errorLog << __GRT_LOG__ << " Could not find the Models!" << std::endl;
503  return false;
504  }
505 
506  for(UINT k=0; k<numClasses; k++){
507  file >> word;
508  if(word != "ClassLabel:"){
509  errorLog << __GRT_LOG__ << " Could not find the ClassLabel for model: " << k << "!" << std::endl;
510  return false;
511  }
512  file >> models[k].classLabel;
513  classLabels[k] = models[k].classLabel;
514 
515  file >> word;
516  if(word != "Weights:"){
517  errorLog << __GRT_LOG__ << " Could not find the Weights for model: " << k << "!" << std::endl;
518  return false;
519  }
520  file >> models[k].w0;
521 
522  models[k].N = numInputDimensions;
523  models[k].w.resize( numInputDimensions );
524  for(UINT n=0; n<numInputDimensions; n++){
525  file >> models[k].w[n];
526  }
527  }
528 
529  //Recompute the null rejection thresholds
531 
532  //Resize the prediction results to make sure it is setup for realtime prediction
533  maxLikelihood = DEFAULT_NULL_LIKELIHOOD_VALUE;
534  bestDistance = DEFAULT_NULL_DISTANCE_VALUE;
535  classLikelihoods.resize(numClasses,DEFAULT_NULL_LIKELIHOOD_VALUE);
536  classDistances.resize(numClasses,DEFAULT_NULL_DISTANCE_VALUE);
537 
538  //Flag that the model has been trained
539  trained = true;
540 
541  return true;
542 }
543 
544 GRT_END_NAMESPACE
bool setClassificationResult(unsigned int trainingIteration, Float accuracy, MLBase *trainer)
bool saveBaseSettingsToFile(std::fstream &file) const
Definition: Classifier.cpp:274
Softmax(const bool useScaling=false, const Float learningRate=0.1, const Float minChange=1.0e-10, const UINT maxNumEpochs=1000, const UINT batchSize=50)
Definition: Softmax.cpp:33
std::string getId() const
Definition: GRTBase.cpp:85
static std::string getId()
Definition: Softmax.cpp:28
virtual bool recomputeNullRejectionThresholds()
Definition: Classifier.h:255
#define DEFAULT_NULL_LIKELIHOOD_VALUE
Definition: Classifier.h:33
virtual ~Softmax(void)
Definition: Softmax.cpp:49
bool loadLegacyModelFromFile(std::fstream &file)
Definition: Softmax.cpp:447
Vector< ClassTracker > getClassTracker() const
This file contains the Random class, a useful wrapper for generating cross platform random functions...
Definition: Random.h:46
virtual bool resize(const unsigned int size)
Definition: Vector.h:133
UINT getSize() const
Definition: Vector.h:201
virtual bool clear()
Definition: Softmax.cpp:317
Vector< SoftmaxModel > getModels() const
Definition: Softmax.cpp:443
virtual bool predict_(VectorFloat &inputVector)
Definition: Softmax.cpp:164
virtual bool computeAccuracy(const ClassificationData &data, Float &accuracy)
Definition: Classifier.cpp:171
Softmax & operator=(const Softmax &rhs)
Definition: Softmax.cpp:53
UINT getNumSamples() const
bool fill(const T &value)
Definition: Vector.h:175
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 load(std::fstream &file)
Definition: Softmax.cpp:360
Vector< MinMax > getRanges() const
ClassificationData split(const UINT splitPercentage, const bool useStratifiedSampling=false)
virtual bool deepCopyFrom(const Classifier *classifier)
Definition: Softmax.cpp:64
bool scale(const Float minTarget, const Float maxTarget)
virtual bool clear()
Definition: Classifier.cpp:151
virtual bool train_(ClassificationData &trainingData)
Definition: Softmax.cpp:80
virtual bool save(std::fstream &file) const
Definition: Softmax.cpp:328
This is the main base class that all GRT Classification algorithms should inherit from...
Definition: Classifier.h:41
Float scale(const Float &x, const Float &minSource, const Float &maxSource, const Float &minTarget, const Float &maxTarget, const bool constrain=false)
Definition: GRTBase.h:184