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.
LinearRegression.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 "LinearRegression.h"
22 
23 GRT_BEGIN_NAMESPACE
24 
25 //Register the LinearRegression module with the Classifier base class
26 RegisterRegressifierModule< LinearRegression > LinearRegression::registerModule("LinearRegression");
27 
29 {
30  this->useScaling = useScaling;
31  minChange = 1.0e-5;
32  maxNumEpochs = 500;
33  learningRate = 0.01;
34  classType = "LinearRegression";
35  regressifierType = classType;
36  debugLog.setProceedingText("[DEBUG LinearRegression]");
37  errorLog.setProceedingText("[ERROR LinearRegression]");
38  trainingLog.setProceedingText("[TRAINING LinearRegression]");
39  warningLog.setProceedingText("[WARNING LinearRegression]");
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 
57 bool LinearRegression::deepCopyFrom(const Regressifier *regressifier){
58 
59  if( regressifier == NULL ) return false;
60 
61  if( this->getRegressifierType() == regressifier->getRegressifierType() ){
62 
63  const LinearRegression *ptr = dynamic_cast<const LinearRegression*>(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 lastError = 0;
119  Float delta = 0;
120  UINT iter = 0;
121  bool keepTraining = true;
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] - 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-lastError );
163  lastError = totalSquaredTrainingError;
164 
165  //Check to see if we should stop
166  if( delta <= minChange ){
167  keepTraining = false;
168  }
169 
170  if( grt_isinf( totalSquaredTrainingError ) || grt_isnan( totalSquaredTrainingError ) ){
171  errorLog << "train_(RegressionData &trainingData) - Training failed! Total squared training 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;
172  return false;
173  }
174 
175  if( ++iter >= maxNumEpochs ){
176  keepTraining = 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.size() != numInputDimensions ){
206  errorLog << "predict_(VectorFloat &inputVector) - The size of the input Vector (" << int( inputVector.size() ) << ") 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] = scale(inputVector[n], inputVectorRanges[n].minValue, inputVectorRanges[n].maxValue, 0, 1);
213  }
214  }
215 
216  regressionData[0] = w0;
217  for(UINT j=0; j<numInputDimensions; j++){
218  regressionData[0] += inputVector[j] * w[j];
219  }
220 
221  if( useScaling ){
222  for(UINT n=0; n<numOutputDimensions; n++){
223  regressionData[n] = scale(regressionData[n], 0, 1, targetVectorRanges[n].minValue, targetVectorRanges[n].maxValue);
224  }
225  }
226 
227  return true;
228 }
229 
230 bool LinearRegression::saveModelToFile( std::fstream &file ) const{
231 
232  if(!file.is_open())
233  {
234  errorLog << "loadModelFromFile(fstream &file) - The file is not open!" << std::endl;
235  return false;
236  }
237 
238  //Write the header info
239  file<<"GRT_LINEAR_REGRESSION_MODEL_FILE_V2.0\n";
240 
241  //Write the regressifier settings to the file
243  errorLog <<"saveModelToFile(fstream &file) - Failed to save Regressifier base settings to file!" << std::endl;
244  return false;
245  }
246 
247  if( trained ){
248  file << "Weights: ";
249  file << w0;
250  for(UINT j=0; j<numInputDimensions; j++){
251  file << " " << w[j];
252  }
253  file << std::endl;
254  }
255 
256  return true;
257 }
258 
259 bool LinearRegression::loadModelFromFile( std::fstream &file ){
260 
261  clear();
262 
263  if(!file.is_open())
264  {
265  errorLog << "loadModelFromFile( fstream &file ) - Could not open file to load model" << std::endl;
266  return false;
267  }
268 
269  std::string word;
270 
271  //Find the file type header
272  file >> word;
273 
274  //Check to see if we should load a legacy file
275  if( word == "GRT_LINEAR_REGRESSION_MODEL_FILE_V1.0" ){
276  return loadLegacyModelFromFile( file );
277  }
278 
279  if( word != "GRT_LINEAR_REGRESSION_MODEL_FILE_V2.0" ){
280  errorLog << "loadModelFromFile( fstream &file ) - Could not find Model File Header" << std::endl;
281  return false;
282  }
283 
284  //Load the regressifier settings from the file
286  errorLog <<"loadModelFromFile( fstream &file ) - Failed to save Regressifier base settings to file!" << std::endl;
287  return false;
288  }
289 
290  if( trained ){
291 
292  //Resize the weights
293  w.resize(numInputDimensions);
294 
295  //Load the weights
296  file >> word;
297  if(word != "Weights:"){
298  errorLog << "loadModelFromFile( fstream &file ) - Could not find the Weights!" << std::endl;
299  return false;
300  }
301 
302  file >> w0;
303  for(UINT j=0; j<numInputDimensions; j++){
304  file >> w[j];
305 
306  }
307  }
308 
309  return true;
310 }
311 
312 bool LinearRegression::setMaxNumIterations(const UINT maxNumIterations){
313  return setMaxNumEpochs( maxNumIterations );
314 }
315 
317  return getMaxNumEpochs();
318 }
319 
320 bool LinearRegression::loadLegacyModelFromFile( std::fstream &file ){
321 
322  std::string word;
323 
324  file >> word;
325  if(word != "NumFeatures:"){
326  errorLog << "loadLegacyModelFromFile( fstream &file ) - Could not find NumFeatures!" << std::endl;
327  return false;
328  }
329  file >> numInputDimensions;
330 
331  file >> word;
332  if(word != "NumOutputDimensions:"){
333  errorLog << "loadLegacyModelFromFile( fstream &file ) - Could not find NumOutputDimensions!" << std::endl;
334  return false;
335  }
336  file >> numOutputDimensions;
337 
338  file >> word;
339  if(word != "UseScaling:"){
340  errorLog << "loadLegacyModelFromFile( fstream &file ) - Could not find UseScaling!" << std::endl;
341  return false;
342  }
343  file >> useScaling;
344 
346  if( useScaling ){
347  //Resize the ranges buffer
348  inputVectorRanges.resize(numInputDimensions);
349  targetVectorRanges.resize(numOutputDimensions);
350 
351  //Load the ranges
352  file >> word;
353  if(word != "InputVectorRanges:"){
354  file.close();
355  errorLog << "loadLegacyModelFromFile( fstream &file ) - Failed to find InputVectorRanges!" << std::endl;
356  return false;
357  }
358  for(UINT j=0; j<inputVectorRanges.size(); j++){
359  file >> inputVectorRanges[j].minValue;
360  file >> inputVectorRanges[j].maxValue;
361  }
362 
363  file >> word;
364  if(word != "OutputVectorRanges:"){
365  file.close();
366  errorLog << "loadLegacyModelFromFile( fstream &file ) - Failed to find OutputVectorRanges!" << std::endl;
367  return false;
368  }
369  for(UINT j=0; j<targetVectorRanges.size(); j++){
370  file >> targetVectorRanges[j].minValue;
371  file >> targetVectorRanges[j].maxValue;
372  }
373  }
374 
375  //Resize the weights
376  w.resize(numInputDimensions);
377 
378  //Load the weights
379  file >> word;
380  if(word != "Weights:"){
381  errorLog << "loadLegacyModelFromFile( fstream &file ) - Could not find the Weights!" << std::endl;
382  return false;
383  }
384 
385  file >> w0;
386  for(UINT j=0; j<numInputDimensions; j++){
387  file >> w[j];
388 
389  }
390 
391  //Resize the regression data Vector
392  regressionData.resize(1,0);
393 
394  //Flag that the model has been trained
395  trained = true;
396 
397  return true;
398 }
399 
400 GRT_END_NAMESPACE
401 
This class implements the Linear Regression algorithm. Linear Regression is a simple but effective re...
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 bool saveModelToFile(std::fstream &file) const
virtual bool predict_(VectorFloat &inputVector)
LinearRegression(bool useScaling=false)
Vector< MinMax > getInputRanges() const
Definition: Random.h:40
virtual bool resize(const unsigned int size)
Definition: Vector.h:133
virtual ~LinearRegression(void)
bool copyBaseVariables(const Regressifier *regressifier)
UINT getNumInputDimensions() const
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 loadBaseSettingsFromFile(std::fstream &file)
LinearRegression & operator=(const LinearRegression &rhs)
bool setMaxNumIterations(const UINT maxNumIterations)
virtual bool train_(RegressionData &trainingData)
Float getRandomNumberUniform(Float minRange=0.0, Float maxRange=1.0)
Definition: Random.h:198
virtual bool deepCopyFrom(const Regressifier *regressifier)
bool loadLegacyModelFromFile(std::fstream &file)
bool setMaxNumEpochs(const UINT maxNumEpochs)
Definition: MLBase.cpp:268
UINT getMaxNumIterations() const
virtual bool loadModelFromFile(std::fstream &file)
UINT getNumSamples() const
virtual bool clear()