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.
LDA.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 "LDA.h"
22 
23 GRT_BEGIN_NAMESPACE
24 
25 LDA::LDA(bool useScaling,bool useNullRejection,Float nullRejectionCoeff)
26 {
27  this->useScaling = useScaling;
28  this->useNullRejection = useNullRejection;
29  this->nullRejectionCoeff = nullRejectionCoeff;
30  classType = "LDA";
31  classifierType = classType;
32  classifierMode = STANDARD_CLASSIFIER_MODE;
33  debugLog.setProceedingText("[DEBUG LDA]");
34  errorLog.setProceedingText("[ERROR LDA]");
35  trainingLog.setProceedingText("[TRAINING LDA]");
36  warningLog.setProceedingText("[WARNING LDA]");
37 }
38 
39 LDA::~LDA(void)
40 {
41 }
42 
43 bool LDA::train(ClassificationData trainingData){
44 
45  errorLog << "SORRY - this module is still under development and can't be used yet!" << std::endl;
46  return false;
47 
48  //Reset any previous model
49  numInputDimensions = 0;
50  numClasses = 0;
51  models.clear();
52  classLabels.clear();
53  trained = false;
54 
55  if( trainingData.getNumSamples() == 0 ){
56  errorLog << "train(LabelledClassificationData trainingData) - There is no training data to train the model!" << std::endl;
57  return false;
58  }
59 
60  numInputDimensions = trainingData.getNumDimensions();
61  numClasses = trainingData.getNumClasses();
62 
63  //Calculate the between scatter matrix
64  MatrixFloat SB = computeBetweenClassScatterMatrix( trainingData );
65 
66  //Calculate the within scatter matrix
67  MatrixFloat SW = computeWithinClassScatterMatrix( trainingData );
68 
69 
70  /*
71 
72 
73  //Counters and stat containers
74  vector< UINT > groupLabels(numClasses);
75  VectorDouble groupCounters(numClasses);
76  VectorDouble priorProb(numClasses);
77  MatrixFloat groupMeans(numClasses,numFeatures);
78  MatrixFloat pCov(numFeatures,numFeatures);
79  MatrixFloat pCovInv(numFeatures,numFeatures);
80  MatrixFloat modelCoeff(numClasses,numFeatures+1);
81 
82  pCov.setAllValues(0);
83  modelCoeff.setAllValues(0);
84 
85  //Set the class labels and counters
86  for(UINT k=0; k<numClasses; k++){
87  groupLabels[k] = trainingData.getClassTracker()[k].classLabel;
88  groupCounters[k] = trainingData.getClassTracker()[k].counter;
89  }
90 
91  //Loop over the classes to compute the group stats
92  for(UINT k=0; k<numClasses; k++){
93  LabelledClassificationData classData = trainingData.getClassData( groupLabels[k] );
94  MatrixFloat cov(numFeatures,numFeatures);
95 
96  //Compute class mu
97  for(UINT j=0; j<numFeatures; j++){
98  groupMeans[k][j] = 0;
99  for(UINT i=0; i<classData.getNumSamples(); i++){
100  groupMeans[k][j] += classData[i][j];
101  }
102  groupMeans[k][j] /= Float(classData.getNumSamples());
103  }
104 
105  //Compute the class covariance
106  for(UINT m=0; m<numFeatures; m++){
107  for(UINT n=0; n<numFeatures; n++){
108  cov[m][n] = 0;
109  for(UINT i=0; i<classData.getNumSamples(); i++){
110  cov[m][n] += (classData[i][m]-groupMeans[k][m]) * (classData[i][n]-groupMeans[k][n]);
111  }
112  cov[m][n] /= Float(classData.getNumSamples()-1);
113  }
114  }
115 
116  debugLog << "Group Cov:\n";
117  for(UINT m=0; m<numFeatures; m++){
118  for(UINT n=0; n<numFeatures; n++){
119  debugLog << cov[m][n] << "\t";
120  }debugLog << "\n";
121  }debugLog << std::endl;
122 
123  //Set the prior probability for this class (which is just 1/numClasses)
124  priorProb[k] = 1.0/Float(numClasses);
125 
126  //Update the main covariance matrix
127  Float weight = ((classData.getNumSamples() - 1) / Float(trainingData.getNumSamples() - numClasses) );
128  debugLog << "Weight: " << weight << std::endl;
129  for(UINT m=0; m<numFeatures; m++){
130  for(UINT n=0; n<numFeatures; n++){
131  pCov[m][n] += weight * cov[m][n];
132  }
133  }
134  }
135 
136  for(UINT k=0; k<numClasses; k++){
137  debugLog << "GroupMu: " << groupLabels[k] << "\t";
138  for(UINT j=0; j<numFeatures; j++){
139  debugLog << groupMeans[k][j] << "\t";
140  }debugLog << std::endl;
141  }
142 
143  debugLog << "pCov:\n";
144  for(UINT m=0; m<numFeatures; m++){
145  for(UINT n=0; n<numFeatures; n++){
146  debugLog << pCov[m][n] << "\t";
147  }debugLog << "\n";
148  }debugLog << std::endl;
149 
150  //Invert the pCov matrix
151  LUDecomposition matrixInverter(pCov);
152  if( !matrixInverter.inverse(pCovInv) ){
153  errorLog << "Failed to invert pCov Matrix!" << std::endl;
154  return false;
155  }
156 
157  //Loop over classes to calculate linear discriminant coefficients
158  Float sum = 0;
159  vector< Float > temp(numFeatures);
160  for(UINT k=0; k<numClasses; k++){
161  //Compute the temporary vector
162  for(UINT j=0; j<numFeatures; j++){
163  temp[j] = 0;
164  for(UINT m=0; m<numFeatures; m++){
165  temp[j] += groupMeans[k][m] * pCovInv[m][j];
166  }
167  }
168 
169  //Compute the model coefficients
170  sum = 0;
171  for(UINT j=0; j<numFeatures; j++){
172  sum += temp[j]*groupMeans[k][j];
173  }
174  modelCoeff[k][0] = -0.5 * sum + log( priorProb[k] );
175 
176  for(UINT j=0; j<numFeatures; j++){
177  modelCoeff[k][j+1] = temp[j];
178  }
179  }
180 
181  //Setup the models for realtime prediction
182  models.resize(numClasses);
183  classLabels.resize(numClasses);
184 
185  for(UINT k=0; k<numClasses; k++){
186  classLabels[k] = groupLabels[k];
187  models[k].classLabel = groupLabels[k];
188  models[k].priorProb = priorProb[k];
189  models[k].weights = modelCoeff.getRowVector(k);
190  }
191 
192  //Flag that the models were successfully trained
193  trained = true;
194  */
195 
196  return true;
197 }
198 
199 bool LDA::predict(VectorDouble inputVector){
200 
201  if( !trained ){
202  errorLog << "predict(vector< Float > inputVector) - LDA Model Not Trained!" << std::endl;
203  return false;
204  }
205 
206  predictedClassLabel = 0;
207  maxLikelihood = -10000;
208 
209  if( !trained ) return false;
210 
211  if( inputVector.getSize() != numInputDimensions ){
212  errorLog << "predict(vector< Float > inputVector) - The size of the input vector (" << inputVector.getSize() << ") does not match the num features in the model (" << numInputDimensions << std::endl;
213  return false;
214  }
215 
216  //Make sure the likelihoods and distances vectors have been assigned
217  if( classLikelihoods.getSize() != numClasses || classDistances.getSize() != numClasses ){
218  classLikelihoods.resize(numClasses);
219  classDistances.resize(numClasses);
220  }
221 
222  //Compute the linear scores for each class
223  bestDistance = 0;
224  maxLikelihood = 0;
225  UINT bestIndex = 0;
226  Float sum = 0;
227  for(UINT k=0; k<numClasses; k++){
228 
229  for(UINT j=0; j<numInputDimensions+1; j++){
230  if( j==0 ) classDistances[k] = models[k].weights[j];
231  else classDistances[k] += inputVector[j-1] * models[k].weights[j];
232  }
233  classLikelihoods[k] = exp( classDistances[k] );
234  sum += classLikelihoods[k];
235 
236  if( classLikelihoods[k] > maxLikelihood ){
237  bestIndex = k;
238  maxLikelihood = classLikelihoods[k];
239  }
240  }
241 
242  //Normalize the likelihoods
243  for(UINT k=0; k<numClasses; k++){
244  classLikelihoods[k] /= sum;
245  }
246 
247  maxLikelihood = classLikelihoods[ bestIndex ];
248 
249  predictedClassLabel = models[ bestIndex ].classLabel;
250 
251  return true;
252 }
253 
254 bool LDA::saveModelToFile( std::fstream &file ) const{
255 
256  if(!file.is_open())
257  {
258  errorLog <<"saveModelToFile(fstream &file) - Could not open file to save model" << std::endl;
259  return false;
260  }
261 
262  //Write the header info
263  file<<"GRT_LDA_MODEL_FILE_V1.0\n";
264  file<<"NumFeatures: "<<numInputDimensions<< std::endl;
265  file<<"NumClasses: "<<numClasses<< std::endl;
266  file <<"UseScaling: " << useScaling << std::endl;
267  file<<"UseNullRejection: " << useNullRejection << std::endl;
268 
270  if( useScaling ){
271  file << "Ranges: \n";
272  for(UINT n=0; n<ranges.size(); n++){
273  file << ranges[n].minValue << "\t" << ranges[n].maxValue << std::endl;
274  }
275  }
276 
277  //Write each of the models
278  for(UINT k=0; k<numClasses; k++){
279  file<<"ClassLabel: "<<models[k].classLabel<< std::endl;
280  file<<"PriorProbability: "<<models[k].priorProb<< std::endl;
281  file<<"Weights: ";
282 
283  for(UINT j=0; j<models[k].getNumDimensions(); j++){
284  file << "\t" << models[k].weights[j];
285  }file<< std::endl;
286  }
287 
288  return true;
289 }
290 
291 bool LDA::loadModelFromFile( std::fstream &file ){
292 
293  trained = false;
294  numInputDimensions = 0;
295  numClasses = 0;
296  models.clear();
297  classLabels.clear();
298 
299  if(!file.is_open())
300  {
301  errorLog << "loadModelFromFile(fstream &file) - The file is not open!" << std::endl;
302  return false;
303  }
304 
305  std::string word;
306 
307  //Find the file type header
308  file >> word;
309  if(word != "GRT_LDA_MODEL_FILE_V1.0"){
310  errorLog << "loadModelFromFile(fstream &file) - Could not find Model File Header" << std::endl;
311  return false;
312  }
313 
314  file >> word;
315  if(word != "NumFeatures:"){
316  errorLog << "loadModelFromFile(fstream &file) - Could not find NumFeatures " << std::endl;
317  return false;
318  }
319  file >> numInputDimensions;
320 
321  file >> word;
322  if(word != "NumClasses:"){
323  errorLog << "loadModelFromFile(fstream &file) - Could not find NumClasses" << std::endl;
324  return false;
325  }
326  file >> numClasses;
327 
328  file >> word;
329  if(word != "UseScaling:"){
330  errorLog << "loadModelFromFile(fstream &file) - Could not find UseScaling" << std::endl;
331  return false;
332  }
333  file >> useScaling;
334 
335  file >> word;
336  if(word != "UseNullRejection:"){
337  errorLog << "loadModelFromFile(fstream &file) - Could not find UseNullRejection" << std::endl;
338  return false;
339  }
340  file >> useNullRejection;
341 
343  if( useScaling ){
344  //Resize the ranges buffer
345  ranges.resize(numInputDimensions);
346 
347  file >> word;
348  if(word != "Ranges:"){
349  errorLog << "loadModelFromFile(fstream &file) - Could not find the Ranges" << std::endl;
350  return false;
351  }
352  for(UINT n=0; n<ranges.size(); n++){
353  file >> ranges[n].minValue;
354  file >> ranges[n].maxValue;
355  }
356  }
357 
358  //Resize the buffer
359  models.resize(numClasses);
360  classLabels.resize(numClasses);
361 
362  //Load each of the K models
363  for(UINT k=0; k<numClasses; k++){
364  file >> word;
365  if(word != "ClassLabel:"){
366  errorLog << "loadModelFromFile(fstream &file) - Could not find ClassLabel for the "<<k+1<<"th model" << std::endl;
367  return false;
368  }
369  file >> models[k].classLabel;
370  classLabels[k] = models[k].classLabel;
371 
372  file >> word;
373  if(word != "PriorProbability:"){
374  errorLog << "loadModelFromFile(fstream &file) - Could not find the PriorProbability for the "<<k+1<<"th model" << std::endl;
375  return false;
376  }
377  file >> models[k].priorProb;
378 
379  models[k].weights.resize(numInputDimensions+1);
380 
381  //Load the weights
382  file >> word;
383  if(word != "Weights:"){
384  errorLog << "loadModelFromFile(fstream &file) - Could not find the Weights vector for the "<<k+1<<"th model" << std::endl;
385  return false;
386  }
387 
388  //Load Weights
389  for(UINT j=0; j<numInputDimensions+1; j++){
390  Float value;
391  file >> value;
392  models[k].weights[j] = value;
393  }
394  }
395 
396  //Resize the prediction results to make sure it is setup for realtime prediction
397  maxLikelihood = DEFAULT_NULL_LIKELIHOOD_VALUE;
398  bestDistance = DEFAULT_NULL_DISTANCE_VALUE;
399  classLikelihoods.resize(numClasses,DEFAULT_NULL_LIKELIHOOD_VALUE);
400  classDistances.resize(numClasses,DEFAULT_NULL_DISTANCE_VALUE);
401 
402  trained = true;
403 
404  return true;
405 }
406 
407 MatrixFloat LDA::computeBetweenClassScatterMatrix( ClassificationData &data ){
408 
409  MatrixFloat sb(numInputDimensions,numInputDimensions);
410  MatrixFloat classMean = data.getClassMean();
411  VectorDouble totalMean = data.getMean();
412  sb.setAllValues( 0 );
413 
414  for(UINT k=0; k<numClasses; k++){
415 
416  UINT numSamplesInClass = data.getClassTracker()[k].counter;
417 
418  for(UINT m=0; m<numInputDimensions; m++){
419  for(UINT n=0; n<numInputDimensions; n++){
420  sb[m][n] += (classMean[k][m]-totalMean[m]) * (classMean[k][n]-totalMean[n]) * Float(numSamplesInClass);
421  }
422  }
423  }
424 
425  return sb;
426 }
427 
428 MatrixFloat LDA::computeWithinClassScatterMatrix( ClassificationData &data ){
429 
430  MatrixFloat sw(numInputDimensions,numInputDimensions);
431  sw.setAllValues( 0 );
432 
433  for(UINT k=0; k<numClasses; k++){
434 
435  //Compute the scatter matrix for class k
436  ClassificationData classData = data.getClassData( data.getClassTracker()[k].classLabel );
437  MatrixFloat scatterMatrix = classData.getCovarianceMatrix();
438 
439  //Add this to the main scatter matrix
440  for(UINT m=0; m<numInputDimensions; m++){
441  for(UINT n=0; n<numInputDimensions; n++){
442  sw[m][n] += scatterMatrix[m][n];
443  }
444  }
445  }
446 
447  return sw;
448 }
449 
450 GRT_END_NAMESPACE
451 
#define DEFAULT_NULL_LIKELIHOOD_VALUE
Definition: Classifier.h:38
Vector< ClassTracker > getClassTracker() const
ClassificationData getClassData(const UINT classLabel) const
virtual bool resize(const unsigned int size)
Definition: Vector.h:133
This class implements the Linear Discriminant Analysis Classification algorithm.
MatrixFloat getClassMean() const
virtual bool predict(VectorDouble inputVector)
Definition: LDA.cpp:199
unsigned int getSize() const
Definition: Vector.h:193
MatrixFloat getCovarianceMatrix() const
UINT getNumSamples() const
virtual bool train(ClassificationData trainingData)
Definition: LDA.cpp:43
LDA(bool useScaling=false, bool useNullRejection=true, Float nullRejectionCoeff=10.0)
Definition: LDA.cpp:25
virtual ~LDA(void)
Definition: LDA.cpp:39
UINT getNumDimensions() const
UINT getNumClasses() const
virtual bool saveModelToFile(std::fstream &file) const
Definition: LDA.cpp:254
virtual bool loadModelFromFile(std::fstream &file)
Definition: LDA.cpp:291
VectorFloat getMean() const