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.
LogisticRegression.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 "LogisticRegression.h"
23 
24 GRT_BEGIN_NAMESPACE
25 
26 //Define the string that will be used to identify the object
27 const std::string LogisticRegression::id = "LogisticRegression";
28 std::string LogisticRegression::getId() { return LogisticRegression::id; }
29 
30 //Register the LogisticRegression module with the Classifier base class
32 
33 LogisticRegression::LogisticRegression(const bool useScaling,const Float learningRate,const Float minChange,const UINT batchSize,const UINT maxNumEpochs,const UINT minNumEpochs) : Regressifier( LogisticRegression::getId() )
34 {
35  this->useScaling = useScaling;
36  this->learningRate = learningRate;
37  this->minChange = minChange;
38  this->batchSize = batchSize;
39  this->minNumEpochs = minNumEpochs;
40  this->maxNumEpochs = maxNumEpochs;
41 }
42 
44 {
45  *this = rhs;
46 }
47 
49 {
50 }
51 
53  if( this != &rhs ){
54  this->w0 = rhs.w0;
55  this->w = rhs.w;
56 
57  //Copy the base variables
58  copyBaseVariables( dynamic_cast<const Regressifier*>(&rhs) );
59  }
60  return *this;
61 }
62 
64 
65  if( regressifier == NULL ) return false;
66 
67  if( this->getId() == regressifier->getId() ){
68  const LogisticRegression *ptr = dynamic_cast<const LogisticRegression*>(regressifier);
69 
70  this->w0 = ptr->w0;
71  this->w = ptr->w;
72 
73  //Copy the base variables
74  return copyBaseVariables( regressifier );
75  }
76  return false;
77 }
78 
80 
81  //Create a validation dataset, if needed
82  RegressionData validationData;
83  if( useValidationSet ){
84  validationData = trainingData.split( 100 - validationSetSize );
85  }
86 
87  const unsigned int M = trainingData.getNumSamples();
88  const unsigned int N = trainingData.getNumInputDimensions();
89  const unsigned int K = trainingData.getNumTargetDimensions();
90  trained = false;
91  trainingResults.clear();
92 
93  if( M == 0 ){
94  errorLog << "train_(RegressionData trainingData) - Training data has zero samples!" << std::endl;
95  return false;
96  }
97 
98  if( K == 0 ){
99  errorLog << "train_(RegressionData trainingData) - The number of target dimensions is not 1!" << std::endl;
100  return false;
101  }
102 
103  numInputDimensions = N;
104  numOutputDimensions = 1; //Logistic Regression will have 1 output
105  inputVectorRanges.clear();
106  targetVectorRanges.clear();
107 
108  //Scale the training and validation data, if needed
109  if( useScaling ){
110  //Find the ranges for the input data
111  inputVectorRanges = trainingData.getInputRanges();
112 
113  //Find the ranges for the target data
114  targetVectorRanges = trainingData.getTargetRanges();
115 
116  //Scale the training data
117  trainingData.scale(inputVectorRanges,targetVectorRanges,0.0,1.0);
118 
119  //Scale the validation data
120  if( useValidationSet ){
121  validationData.scale(inputVectorRanges,targetVectorRanges,0.0,1.0);
122  }
123  }
124 
125  //Reset the weights
126  Random rand;
127  w0 = 0; //rand.getUniform(-0.1,0.1);
128  w.resize(N);
129  for(UINT j=0; j<N; j++){
130  w[j] = 0; //rand.getUniform(-0.1,0.1);
131  }
132 
133  //If the batch size is zero, then it should be set to the size of the training dataset (aka 1 batch == 1 epoch)
134  if( batchSize == 0 || batchSize > M ){
135  batchSize = M;
136  }
137 
138  Float error = 0;
139  Float lastError = 0;
140  Float delta = 0;
141  Float batchError = 0;
142  Float h = 0;
143  UINT iter = 0;
144  UINT epoch = 0;
145  UINT batchStartIndex = 0;
146  UINT batchEndIndex = 0;
147  UINT numSamplesInBatch = 0;
148  const UINT numValidationSamples = validationData.getNumSamples();
149  bool keepTraining = true;
150  Vector< UINT > randomTrainingOrder(M);
151  TrainingResult result;
152  trainingResults.reserve(M);
153  MatrixFloat batchInputData(batchSize,N);
154  VectorFloat meanInputData(N);
155  VectorFloat batchTargetData(batchSize);
156 
157  //In most cases, the training data is grouped into classes (100 samples for class 1, followed by 100 samples for class 2, etc.)
158  //This can cause a problem for stochastic gradient descent algorithm. To avoid this issue, we randomly shuffle the order of the
159  //training samples. This random order is then used at each epoch.
160  for(UINT i=0; i<M; i++){
161  randomTrainingOrder[i] = i;
162  }
163  std::random_shuffle(randomTrainingOrder.begin(), randomTrainingOrder.end());
164 
165  //Run the main stochastic gradient descent training algorithm
166  while( keepTraining ){
167 
168  //Run one epoch of training using stochastic gradient descent (with mini-batches)
169  batchStartIndex = 0;
170  batchEndIndex = 0;
171  while( batchStartIndex < M && keepTraining ){
172 
173  rmsTrainingError = 0.0;
174  rmsValidationError = 0.0;
175 
176  //Update the batch counters
177  batchEndIndex = batchStartIndex + batchSize;
178  if( batchEndIndex > M ) batchEndIndex = M;
179  numSamplesInBatch = batchEndIndex-batchStartIndex;
180 
181  //Reset the mean buffer
182  meanInputData.fill(0.0);
183 
184  //Copy the batch data to the temporary storage
185  for(UINT n=0; n<numSamplesInBatch; n++){
186  const VectorFloat &x = trainingData[randomTrainingOrder[batchStartIndex+n]].getInputVector();
187  const VectorFloat &y = trainingData[randomTrainingOrder[batchStartIndex+n]].getTargetVector();
188 
189  for(UINT j=0; j<N; j++){
190  batchInputData[n][j] = x[j];
191  meanInputData[j] += x[j];
192  }
193 
194  batchTargetData[n] = y[0];
195  }
196 
197  //Compute the average input for this batch
198  for(UINT j=0; j<N; j++){
199  meanInputData[j] /= static_cast<Float>(numSamplesInBatch);
200  }
201 
202  //Compute the error for each sample in the batch, given the current weights
203  batchError = 0;
204  for(UINT n=0; n<numSamplesInBatch; n++){
205  h = w0;
206  for(UINT j=0; j<N; j++){
207  h += batchInputData[n][j] * w[j];
208  }
209  error = batchTargetData[n] - sigmoid( h );
210  batchError += error;
211  rmsTrainingError += SQR(error);
212  }
213  //The batch error is the average error across the batch
214  batchError /= static_cast<Float>(numSamplesInBatch);
215 
216  //Update the weights based on the average error across the batch
217  for(UINT j=0; j<N; j++){
218  w[j] += learningRate * batchError * meanInputData[j];
219  }
220  w0 += learningRate * batchError;
221 
222  //Compute the error on the validation set if needed
223  if( useValidationSet ){
224  for(UINT i=0; i<numValidationSamples; i++){
225  //Compute the error, given the current weights
226  const VectorFloat &x = validationData[i].getInputVector();
227  const VectorFloat &y = validationData[i].getTargetVector();
228  h = w0;
229  for(UINT j=0; j<N; j++){
230  h += x[j] * w[j];
231  }
232  error = y[0] - sigmoid( h );
233  rmsValidationError += SQR(error);
234  }
235  rmsValidationError = sqrt( rmsValidationError / static_cast<Float>(numValidationSamples) );
236  }
237 
238  //Compute the error
239  rmsTrainingError = sqrt( rmsTrainingError / static_cast<Float>(numSamplesInBatch) );
240  delta = iter > 0 ? fabs( rmsTrainingError-lastError ) : rmsTrainingError;
241  lastError = rmsTrainingError;
242 
243  //Check to see if we should stop
244  if( delta <= minChange && epoch >= minNumEpochs ){
245  keepTraining = false;
246  }
247 
248  if( grt_isinf( rmsTrainingError ) || grt_isnan( rmsTrainingError ) ){
249  errorLog << __GRT_LOG__ << " Training failed! RMS error is NAN. If scaling is not enabled then you should try to scale your data and see if this solves the issue." << std::endl;
250  return false;
251  }
252 
253  //Update the counters and batch index
254  iter++;
255  batchStartIndex = batchEndIndex;
256 
257  //Store the training results
258  result.setRegressionResult(epoch,rmsTrainingError,rmsValidationError,this);
259  trainingResults.push_back( result );
260 
261  //Notify any observers of the new result
262  trainingResultsObserverManager.notifyObservers( result );
263 
264  trainingLog << "Epoch: " << epoch << " | Iter: " << iter << " | RMS Training Error: " << rmsTrainingError << " | Delta: " << delta;
265  if( useValidationSet ){
266  trainingLog << " | RMS Validation Error: " << rmsValidationError;
267  }
268  trainingLog << std::endl;
269  }
270 
271  if( ++epoch >= maxNumEpochs ){
272  keepTraining = false;
273  }
274  }
275 
276  //Flag that the algorithm has been trained
277  regressionData.resize(1,0);
278  trained = true;
279  return trained;
280 }
281 
283 
284  if( !trained ){
285  errorLog << __GRT_LOG__ << " Model Not Trained!" << std::endl;
286  return false;
287  }
288 
289  if( !trained ) return false;
290 
291  if( inputVector.getSize() != numInputDimensions ){
292  errorLog << __GRT_LOG__ << " The size of the input Vector (" << inputVector.getSize() << ") does not match the num features in the model (" << numInputDimensions << std::endl;
293  return false;
294  }
295 
296  if( useScaling ){
297  for(UINT n=0; n<numInputDimensions; n++){
298  inputVector[n] = grt_scale(inputVector[n], inputVectorRanges[n].minValue, inputVectorRanges[n].maxValue, 0.0, 1.0);
299  }
300  }
301 
302  regressionData[0] = w0;
303  for(UINT j=0; j<numInputDimensions; j++){
304  regressionData[0] += inputVector[j] * w[j];
305  }
306  regressionData[0] = sigmoid( regressionData[0] );
307  if( useScaling ){
308  for(UINT n=0; n<numOutputDimensions; n++){
309  regressionData[n] = grt_scale(regressionData[n], 0.0, 1.0, targetVectorRanges[n].minValue, targetVectorRanges[n].maxValue);
310  }
311  }
312 
313  return true;
314 }
315 
316 bool LogisticRegression::save( std::fstream &file ) const{
317 
318  if(!file.is_open())
319  {
320  errorLog << __GRT_LOG__ << " The file is not open!" << std::endl;
321  return false;
322  }
323 
324  //Write the header info
325  file<<"GRT_LOGISTIC_REGRESSION_MODEL_FILE_V2.0\n";
326 
327  //Write the regressifier settings to the file
329  errorLog << __GRT_LOG__ << " Failed to save Regressifier base settings to file!" << std::endl;
330  return false;
331  }
332 
333  if( trained ){
334  file << "Weights: ";
335  file << w0;
336  for(UINT j=0; j<numInputDimensions; j++){
337  file << " " << w[j];
338  }
339  file << std::endl;
340  }
341 
342  return true;
343 }
344 
345 bool LogisticRegression::load( std::fstream &file ){
346 
347  trained = false;
348  numInputDimensions = 0;
349  w0 = 0;
350  w.clear();
351 
352  if(!file.is_open())
353  {
354  errorLog << __GRT_LOG__ << " Could not open file to load model" << std::endl;
355  return false;
356  }
357 
358  std::string word;
359 
360  //Find the file type header
361  file >> word;
362 
363  //Check to see if we should load a legacy file
364  if( word == "GRT_LOGISTIC_REGRESSION_MODEL_FILE_V1.0" ){
365  return loadLegacyModelFromFile( file );
366  }
367 
368  if( word != "GRT_LOGISTIC_REGRESSION_MODEL_FILE_V2.0" ){
369  errorLog << __GRT_LOG__ << " Could not find Model File Header" << std::endl;
370  return false;
371  }
372 
373  //Load the regressifier settings from the file
375  errorLog << __GRT_LOG__ << " Failed to save Regressifier base settings to file!" << std::endl;
376  return false;
377  }
378 
379  if( trained ){
380 
381  //Resize the weights
382  w.resize(numInputDimensions);
383 
384  //Load the weights
385  file >> word;
386  if(word != "Weights:"){
387  errorLog << __GRT_LOG__ << " Could not find the Weights!" << std::endl;
388  return false;
389  }
390 
391  file >> w0;
392  for(UINT j=0; j<numInputDimensions; j++){
393  file >> w[j];
394 
395  }
396  }
397 
398  return true;
399 }
400 
402  return getMaxNumEpochs();
403 }
404 
405 bool LogisticRegression::setMaxNumIterations(const UINT maxNumIterations){
406  return setMaxNumEpochs( maxNumIterations );
407 }
408 
409 Float LogisticRegression::sigmoid(const Float x) const{
410  return 1.0 / (1 + exp(-x));
411 }
412 
414 
415  std::string word;
416 
417  file >> word;
418  if(word != "NumFeatures:"){
419  errorLog << __GRT_LOG__ << " Could not find NumFeatures!" << std::endl;
420  return false;
421  }
422  file >> numInputDimensions;
423 
424  file >> word;
425  if(word != "NumOutputDimensions:"){
426  errorLog << __GRT_LOG__ << " Could not find NumOutputDimensions!" << std::endl;
427  return false;
428  }
429  file >> numOutputDimensions;
430 
431  file >> word;
432  if(word != "UseScaling:"){
433  errorLog << __GRT_LOG__ << " Could not find UseScaling!" << std::endl;
434  return false;
435  }
436  file >> useScaling;
437 
439  if( useScaling ){
440  //Resize the ranges buffer
441  inputVectorRanges.resize(numInputDimensions);
442  targetVectorRanges.resize(numOutputDimensions);
443 
444  //Load the ranges
445  file >> word;
446  if(word != "InputVectorRanges:"){
447  file.close();
448  errorLog << __GRT_LOG__ << " Failed to find InputVectorRanges!" << std::endl;
449  return false;
450  }
451  for(UINT j=0; j<inputVectorRanges.getSize(); j++){
452  file >> inputVectorRanges[j].minValue;
453  file >> inputVectorRanges[j].maxValue;
454  }
455 
456  file >> word;
457  if(word != "OutputVectorRanges:"){
458  file.close();
459  errorLog << __GRT_LOG__ << " Failed to find OutputVectorRanges!" << std::endl;
460  return false;
461  }
462  for(UINT j=0; j<targetVectorRanges.getSize(); j++){
463  file >> targetVectorRanges[j].minValue;
464  file >> targetVectorRanges[j].maxValue;
465  }
466  }
467 
468  //Resize the weights
469  w.resize(numInputDimensions);
470 
471  //Load the weights
472  file >> word;
473  if(word != "Weights:"){
474  errorLog << __GRT_LOG__ << " Could not find the Weights!" << std::endl;
475  return false;
476  }
477 
478  file >> w0;
479  for(UINT j=0; j<numInputDimensions; j++){
480  file >> w[j];
481 
482  }
483 
484  //Resize the regression data Vector
485  regressionData.resize(1,0);
486 
487  //Flag that the model has been trained
488  trained = true;
489 
490  return true;
491 }
492 
493 GRT_END_NAMESPACE
UINT getMaxNumIterations() const
std::string getId() const
Definition: GRTBase.cpp:85
bool setRegressionResult(unsigned int trainingIteration, Float totalSquaredTrainingError, Float rootMeanSquaredTrainingError, MLBase *trainer)
Vector< MinMax > getInputRanges() const
This file contains the Random class, a useful wrapper for generating cross platform random functions...
Definition: Random.h:46
virtual bool predict_(VectorFloat &inputVector)
virtual bool resize(const unsigned int size)
Definition: Vector.h:133
UINT getSize() const
Definition: Vector.h:201
LogisticRegression & operator=(const LogisticRegression &rhs)
virtual bool load(std::fstream &file)
bool copyBaseVariables(const Regressifier *regressifier)
UINT getNumInputDimensions() const
virtual bool train_(RegressionData &trainingData)
virtual ~LogisticRegression(void)
Vector< MinMax > getTargetRanges() const
bool saveBaseSettingsToFile(std::fstream &file) const
bool scale(const Float minTarget, const Float maxTarget)
UINT getMaxNumEpochs() const
Definition: MLBase.cpp:246
UINT getNumTargetDimensions() const
LogisticRegression(const bool useScaling=true, const Float learningRate=0.01, const Float minChange=1.0e-5, const UINT batchSize=1, const UINT maxNumEpochs=500, const UINT minNumEpochs=1)
bool fill(const T &value)
Definition: Vector.h:175
bool loadLegacyModelFromFile(std::fstream &file)
bool loadBaseSettingsFromFile(std::fstream &file)
VectorFloat w
The weights vector.
bool setMaxNumIterations(UINT maxNumIterations)
RegressionData split(const UINT trainingSizePercentage)
virtual bool deepCopyFrom(const Regressifier *regressifier)
bool setMaxNumEpochs(const UINT maxNumEpochs)
Definition: MLBase.cpp:320
virtual bool save(std::fstream &file) const
static std::string getId()
UINT getNumSamples() const