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