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.
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 //Register the LogisticRegression module with the Classifier base class
27 RegisterRegressifierModule< LogisticRegression > LogisticRegression::registerModule("LogisticRegression");
28 
30 {
31  this->useScaling = useScaling;
32  minChange = 1.0e-5;
33  maxNumEpochs = 500;
34  learningRate = 0.01;
35  classType = "LogisticRegression";
36  regressifierType = classType;
37  debugLog.setProceedingText("[DEBUG LogisticRegression]");
38  errorLog.setProceedingText("[ERROR LogisticRegression]");
39  trainingLog.setProceedingText("[TRAINING LogisticRegression]");
40  warningLog.setProceedingText("[WARNING LogisticRegression]");
41 }
42 
44 {
45 }
46 
48  if( this != &rhs ){
49  this->w0 = rhs.w0;
50  this->w = rhs.w;
51 
52  //Copy the base variables
54  }
55  return *this;
56 }
57 
59 
60  if( regressifier == NULL ) return false;
61 
62  if( this->getRegressifierType() == regressifier->getRegressifierType() ){
63  const LogisticRegression *ptr = dynamic_cast<const LogisticRegression*>(regressifier);
64 
65  this->w0 = ptr->w0;
66  this->w = ptr->w;
67 
68  //Copy the base variables
69  return copyBaseVariables( regressifier );
70  }
71  return false;
72 }
73 
75 
76  const unsigned int M = trainingData.getNumSamples();
77  const unsigned int N = trainingData.getNumInputDimensions();
78  const unsigned int K = trainingData.getNumTargetDimensions();
79  trained = false;
80  trainingResults.clear();
81 
82  if( M == 0 ){
83  errorLog << "train_(RegressionData trainingData) - Training data has zero samples!" << std::endl;
84  return false;
85  }
86 
87  if( K == 0 ){
88  errorLog << "train_(RegressionData trainingData) - The number of target dimensions is not 1!" << std::endl;
89  return false;
90  }
91 
92  numInputDimensions = N;
93  numOutputDimensions = 1; //Logistic Regression will have 1 output
94  inputVectorRanges.clear();
95  targetVectorRanges.clear();
96 
97  //Scale the training and validation data, if needed
98  if( useScaling ){
99  //Find the ranges for the input data
100  inputVectorRanges = trainingData.getInputRanges();
101 
102  //Find the ranges for the target data
103  targetVectorRanges = trainingData.getTargetRanges();
104 
105  //Scale the training data
106  trainingData.scale(inputVectorRanges,targetVectorRanges,0.0,1.0);
107  }
108 
109  //Reset the weights
110  Random rand;
111  w0 = rand.getRandomNumberUniform(-0.1,0.1);
112  w.resize(N);
113  for(UINT j=0; j<N; j++){
114  w[j] = rand.getRandomNumberUniform(-0.1,0.1);
115  }
116 
117  Float error = 0;
118  Float lastSquaredError = 0;
119  Float delta = 0;
120  UINT iter = 0;
121  bool keepTraining = true;
122  Random random;
123  Vector< UINT > randomTrainingOrder(M);
124  TrainingResult result;
125  trainingResults.reserve(M);
126 
127  //In most cases, the training data is grouped into classes (100 samples for class 1, followed by 100 samples for class 2, etc.)
128  //This can cause a problem for stochastic gradient descent algorithm. To avoid this issue, we randomly shuffle the order of the
129  //training samples. This random order is then used at each epoch.
130  for(UINT i=0; i<M; i++){
131  randomTrainingOrder[i] = i;
132  }
133  std::random_shuffle(randomTrainingOrder.begin(), randomTrainingOrder.end());
134 
135  //Run the main stochastic gradient descent training algorithm
136  while( keepTraining ){
137 
138  //Run one epoch of training using stochastic gradient descent
139  totalSquaredTrainingError = 0;
140  for(UINT m=0; m<M; m++){
141 
142  //Select the random sample
143  UINT i = randomTrainingOrder[m];
144 
145  //Compute the error, given the current weights
146  VectorFloat x = trainingData[i].getInputVector();
147  VectorFloat y = trainingData[i].getTargetVector();
148  Float h = w0;
149  for(UINT j=0; j<N; j++){
150  h += x[j] * w[j];
151  }
152  error = y[0] - sigmoid( h );
153  totalSquaredTrainingError += SQR(error);
154 
155  //Update the weights
156  for(UINT j=0; j<N; j++){
157  w[j] += learningRate * error * x[j];
158  }
159  w0 += learningRate * error;
160  }
161 
162  //Compute the error
163  delta = fabs( totalSquaredTrainingError-lastSquaredError );
164  lastSquaredError = totalSquaredTrainingError;
165 
166  //Check to see if we should stop
167  if( delta <= minChange ){
168  keepTraining = false;
169  }
170 
171  if( ++iter >= maxNumEpochs ){
172  keepTraining = false;
173  }
174 
175  if( grt_isinf( totalSquaredTrainingError ) || grt_isnan( totalSquaredTrainingError ) ){
176  errorLog << "train_(RegressionData &trainingData) - Training failed! Total squared 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;
177  return false;
178  }
179 
180  //Store the training results
181  rootMeanSquaredTrainingError = sqrt( totalSquaredTrainingError / Float(M) );
182  result.setRegressionResult(iter,totalSquaredTrainingError,rootMeanSquaredTrainingError,this);
183  trainingResults.push_back( result );
184 
185  //Notify any observers of the new result
186  trainingResultsObserverManager.notifyObservers( result );
187 
188  trainingLog << "Epoch: " << iter << " SSE: " << totalSquaredTrainingError << " Delta: " << delta << std::endl;
189  }
190 
191  //Flag that the algorithm has been trained
192  regressionData.resize(1,0);
193  trained = true;
194  return trained;
195 }
196 
198 
199  if( !trained ){
200  errorLog << "predict_(VectorFloat &inputVector) - Model Not Trained!" << std::endl;
201  return false;
202  }
203 
204  if( !trained ) return false;
205 
206  if( inputVector.getSize() != numInputDimensions ){
207  errorLog << "predict_(VectorFloat &inputVector) - The size of the input Vector (" << inputVector.getSize() << ") does not match the num features in the model (" << numInputDimensions << std::endl;
208  return false;
209  }
210 
211  if( useScaling ){
212  for(UINT n=0; n<numInputDimensions; n++){
213  inputVector[n] = grt_scale(inputVector[n], inputVectorRanges[n].minValue, inputVectorRanges[n].maxValue, 0.0, 1.0);
214  }
215  }
216 
217  regressionData[0] = w0;
218  for(UINT j=0; j<numInputDimensions; j++){
219  regressionData[0] += inputVector[j] * w[j];
220  }
221  Float sum = regressionData[0];
222  regressionData[0] = sigmoid( regressionData[0] );
223  std::cout << "reg sum: " << sum << " sig: " << regressionData[0] << std::endl;
224  if( useScaling ){
225  for(UINT n=0; n<numOutputDimensions; n++){
226  regressionData[n] = grt_scale(regressionData[n], 0.0, 1.0, targetVectorRanges[n].minValue, targetVectorRanges[n].maxValue);
227  }
228  }
229 
230  return true;
231 }
232 
233 bool LogisticRegression::save( std::fstream &file ) const{
234 
235  if(!file.is_open())
236  {
237  errorLog << "save(fstream &file) - The file is not open!" << std::endl;
238  return false;
239  }
240 
241  //Write the header info
242  file<<"GRT_LOGISTIC_REGRESSION_MODEL_FILE_V2.0\n";
243 
244  //Write the regressifier settings to the file
246  errorLog <<"save(fstream &file) - Failed to save Regressifier base settings to file!" << std::endl;
247  return false;
248  }
249 
250  if( trained ){
251  file << "Weights: ";
252  file << w0;
253  for(UINT j=0; j<numInputDimensions; j++){
254  file << " " << w[j];
255  }
256  file << std::endl;
257  }
258 
259  return true;
260 }
261 
262 bool LogisticRegression::load( std::fstream &file ){
263 
264  trained = false;
265  numInputDimensions = 0;
266  w0 = 0;
267  w.clear();
268 
269  if(!file.is_open())
270  {
271  errorLog << "load(string filename) - Could not open file to load model" << std::endl;
272  return false;
273  }
274 
275  std::string word;
276 
277  //Find the file type header
278  file >> word;
279 
280  //Check to see if we should load a legacy file
281  if( word == "GRT_LOGISTIC_REGRESSION_MODEL_FILE_V1.0" ){
282  return loadLegacyModelFromFile( file );
283  }
284 
285  if( word != "GRT_LOGISTIC_REGRESSION_MODEL_FILE_V2.0" ){
286  errorLog << "load( fstream &file ) - Could not find Model File Header" << std::endl;
287  return false;
288  }
289 
290  //Load the regressifier settings from the file
292  errorLog <<"load( fstream &file ) - Failed to save Regressifier base settings to file!" << std::endl;
293  return false;
294  }
295 
296  if( trained ){
297 
298  //Resize the weights
299  w.resize(numInputDimensions);
300 
301  //Load the weights
302  file >> word;
303  if(word != "Weights:"){
304  errorLog << "load( fstream &file ) - Could not find the Weights!" << std::endl;
305  return false;
306  }
307 
308  file >> w0;
309  for(UINT j=0; j<numInputDimensions; j++){
310  file >> w[j];
311 
312  }
313  }
314 
315  return true;
316 }
317 
319  return getMaxNumEpochs();
320 }
321 
322 bool LogisticRegression::setMaxNumIterations(const UINT maxNumIterations){
323  return setMaxNumEpochs( maxNumIterations );
324 }
325 
326 Float LogisticRegression::sigmoid(const Float x) const{
327  return 1.0 / (1 + exp(-x));
328 }
329 
331 
332  std::string word;
333 
334  file >> word;
335  if(word != "NumFeatures:"){
336  errorLog << "loadLegacyModelFromFile( fstream &file ) - Could not find NumFeatures!" << std::endl;
337  return false;
338  }
339  file >> numInputDimensions;
340 
341  file >> word;
342  if(word != "NumOutputDimensions:"){
343  errorLog << "loadLegacyModelFromFile( fstream &file ) - Could not find NumOutputDimensions!" << std::endl;
344  return false;
345  }
346  file >> numOutputDimensions;
347 
348  file >> word;
349  if(word != "UseScaling:"){
350  errorLog << "loadLegacyModelFromFile( fstream &file ) - Could not find UseScaling!" << std::endl;
351  return false;
352  }
353  file >> useScaling;
354 
356  if( useScaling ){
357  //Resize the ranges buffer
358  inputVectorRanges.resize(numInputDimensions);
359  targetVectorRanges.resize(numOutputDimensions);
360 
361  //Load the ranges
362  file >> word;
363  if(word != "InputVectorRanges:"){
364  file.close();
365  errorLog << "loadLegacyModelFromFile( fstream &file ) - Failed to find InputVectorRanges!" << std::endl;
366  return false;
367  }
368  for(UINT j=0; j<inputVectorRanges.getSize(); j++){
369  file >> inputVectorRanges[j].minValue;
370  file >> inputVectorRanges[j].maxValue;
371  }
372 
373  file >> word;
374  if(word != "OutputVectorRanges:"){
375  file.close();
376  errorLog << "loadLegacyModelFromFile( fstream &file ) - Failed to find OutputVectorRanges!" << std::endl;
377  return false;
378  }
379  for(UINT j=0; j<targetVectorRanges.getSize(); j++){
380  file >> targetVectorRanges[j].minValue;
381  file >> targetVectorRanges[j].maxValue;
382  }
383  }
384 
385  //Resize the weights
386  w.resize(numInputDimensions);
387 
388  //Load the weights
389  file >> word;
390  if(word != "Weights:"){
391  errorLog << "loadLegacyModelFromFile( fstream &file ) - Could not find the Weights!" << std::endl;
392  return false;
393  }
394 
395  file >> w0;
396  for(UINT j=0; j<numInputDimensions; j++){
397  file >> w[j];
398 
399  }
400 
401  //Resize the regression data Vector
402  regressionData.resize(1,0);
403 
404  //Flag that the model has been trained
405  trained = true;
406 
407  return true;
408 }
409 
410 GRT_END_NAMESPACE
UINT getMaxNumIterations() const
LogisticRegression(const bool useScaling=true)
Vector< MinMax > getInputRanges() const
Definition: Random.h:40
virtual bool predict_(VectorFloat &inputVector)
virtual bool resize(const unsigned int size)
Definition: Vector.h:133
UINT getSize() const
Definition: Vector.h:191
LogisticRegression & operator=(const LogisticRegression &rhs)
virtual bool load(std::fstream &file)
bool copyBaseVariables(const Regressifier *regressifier)
This class implements the Logistic Regression algorithm. Logistic Regression is a simple but effectiv...
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:227
UINT getNumTargetDimensions() const
std::string getRegressifierType() const
bool loadLegacyModelFromFile(std::fstream &file)
bool loadBaseSettingsFromFile(std::fstream &file)
VectorFloat w
The weights vector.
bool setMaxNumIterations(UINT maxNumIterations)
Float getRandomNumberUniform(Float minRange=0.0, Float maxRange=1.0)
Definition: Random.h:198
virtual bool deepCopyFrom(const Regressifier *regressifier)
bool setMaxNumEpochs(const UINT maxNumEpochs)
Definition: MLBase.cpp:273
virtual bool save(std::fstream &file) const
UINT getNumSamples() const