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