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