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