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