GestureRecognitionToolkit  Version: 1.0 Revision: 04-03-15
The Gesture Recognition Toolkit (GRT) is a cross-platform, open-source, c++ machine learning library for real-time gesture recognition.
KMeansFeatures.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 "KMeansFeatures.h"
22 
23 namespace GRT{
24 
25 //Register your module with the FeatureExtraction base class
26 RegisterFeatureExtractionModule< KMeansFeatures > KMeansFeatures::registerModule("KMeansFeatures");
27 
28 KMeansFeatures::KMeansFeatures(const vector< UINT > numClustersPerLayer,const double alpha,const bool useScaling){
29 
30  classType = "KMeansFeatures";
31  featureExtractionType = classType;
32 
33  debugLog.setProceedingText("[DEBUG KMeansFeatures]");
34  errorLog.setProceedingText("[ERROR KMeansFeatures]");
35  warningLog.setProceedingText("[WARNING KMeansFeatures]");
36 
37  this->numClustersPerLayer = numClustersPerLayer;
38  this->alpha = alpha;
39  this->useScaling = useScaling;
40 
41  if( numClustersPerLayer.size() > 0 ){
42  init( numClustersPerLayer );
43  }
44 }
45 
47 
48  classType = "KMeansFeatures";
49  featureExtractionType = classType;
50 
51  debugLog.setProceedingText("[DEBUG KMeansFeatures]");
52  errorLog.setProceedingText("[ERROR KMeansFeatures]");
53  warningLog.setProceedingText("[WARNING KMeansFeatures]");
54 
55  //Invoke the equals operator to copy the data from the rhs instance to this instance
56  *this = rhs;
57 }
58 
60  //Here you should add any specific code to cleanup your custom feature extraction module if needed
61 }
62 
64  if(this!=&rhs){
65  //Here you should copy any class variables from the rhs instance to this instance
66  this->numClustersPerLayer = rhs.numClustersPerLayer;
67 
68  //Copy the base variables
70  }
71  return *this;
72 }
73 
74 bool KMeansFeatures::deepCopyFrom(const FeatureExtraction *featureExtraction){
75 
76  if( featureExtraction == NULL ) return false;
77 
78  if( this->getFeatureExtractionType() == featureExtraction->getFeatureExtractionType() ){
79 
80  //Cast the feature extraction pointer to a pointer to your custom feature extraction module
81  //Then invoke the equals operator
82  *this = *(KMeansFeatures*)featureExtraction;
83 
84  return true;
85  }
86 
87  errorLog << "clone(FeatureExtraction *featureExtraction) - FeatureExtraction Types Do Not Match!" << endl;
88 
89  return false;
90 }
91 
92 bool KMeansFeatures::computeFeatures(const VectorDouble &inputVector){
93 
94  VectorDouble data( numInputDimensions );
95 
96  //Scale the input data if needed, if not just copy it
97  if( useScaling ){
98  for(UINT j=0; j<numInputDimensions; j++){
99  data[j] = scale(inputVector[j],ranges[j].minValue,ranges[j].maxValue,0,1);
100  }
101  }else{
102  for(UINT j=0; j<numInputDimensions; j++){
103  data[j] = inputVector[j];
104  }
105  }
106 
107  const UINT numLayers = getNumLayers();
108  for(UINT layer=0; layer<numLayers; layer++){
109  if( !projectDataThroughLayer(data, featureVector, layer) ){
110  errorLog << "computeFeatures(const VectorDouble &inputVector) - Failed to project data through layer: " << layer << endl;
111  return false;
112  }
113 
114  //The output of the current layer will become the input to the next layer unless it is the last layer
115  if( layer+1 < numLayers ){
116  data = featureVector;
117  }
118  }
119 
120  return true;
121 }
122 
124  return true;
125 }
126 
127 bool KMeansFeatures::saveModelToFile(string filename) const{
128 
129  std::fstream file;
130  file.open(filename.c_str(), std::ios::out);
131 
132  if( !saveModelToFile( file ) ){
133  return false;
134  }
135 
136  file.close();
137 
138  return true;
139 }
140 
141 bool KMeansFeatures::loadModelFromFile(string filename){
142 
143  std::fstream file;
144  file.open(filename.c_str(), std::ios::in);
145 
146  if( !loadModelFromFile( file ) ){
147  return false;
148  }
149 
150  //Close the file
151  file.close();
152 
153  return true;
154 }
155 
156 bool KMeansFeatures::saveModelToFile(fstream &file) const{
157 
158  if( !file.is_open() ){
159  errorLog << "saveModelToFile(fstream &file) - The file is not open!" << endl;
160  return false;
161  }
162 
163  //First, you should add a header (with no spaces) e.g.
164  file << "KMEANS_FEATURES_FILE_V1.0" << endl;
165 
166  //Second, you should save the base feature extraction settings to the file
168  errorLog << "saveFeatureExtractionSettingsToFile(fstream &file) - Failed to save base feature extraction settings to file!" << endl;
169  return false;
170  }
171 
172  file << "NumLayers: " << getNumLayers() << endl;
173  file << "NumClustersPerLayer: ";
174  for(UINT i=0; i<numClustersPerLayer.size(); i++){
175  file << " " << numClustersPerLayer[i];
176  }
177  file << endl;
178 
179  file << "Alpha: " << alpha << endl;
180 
181  if( trained ){
182  file << "Ranges: ";
183  for(UINT i=0; i<ranges.size(); i++){
184  file << ranges[i].minValue << " " << ranges[i].maxValue << " ";
185  }
186  file << endl;
187 
188  file << "Clusters: " << endl;
189  for(UINT k=0; k<clusters.size(); k++){
190  file << "NumRows: " << clusters[k].getNumRows() << endl;
191  file << "NumCols: " << clusters[k].getNumCols() << endl;
192  for(UINT i=0; i<clusters[k].getNumRows(); i++){
193  for(UINT j=0; j<clusters[k].getNumCols(); j++){
194  file << clusters[k][i][j];
195  if( j+1 < clusters[k].getNumCols() )
196  file << "\t";
197  }
198  file << endl;
199  }
200  }
201  }
202 
203  return true;
204 }
205 
207 
208  clear();
209 
210  if( !file.is_open() ){
211  errorLog << "loadModelFromFile(fstream &file) - The file is not open!" << endl;
212  return false;
213  }
214 
215  string word;
216  UINT numLayers = 0;
217  UINT numRows = 0;
218  UINT numCols = 0;
219 
220  //First, you should read and validate the header
221  file >> word;
222  if( word != "KMEANS_FEATURES_FILE_V1.0" ){
223  errorLog << "loadModelFromFile(fstream &file) - Invalid file format!" << endl;
224  return false;
225  }
226 
227  //Second, you should load the base feature extraction settings to the file
229  errorLog << "loadFeatureExtractionSettingsFromFile(fstream &file) - Failed to load base feature extraction settings from file!" << endl;
230  return false;
231  }
232 
233  //Load the number of layers
234  file >> word;
235  if( word != "NumLayers:" ){
236  errorLog << "loadModelFromFile(fstream &file) - Failed to read NumLayers header!" << endl;
237  return false;
238  }
239  file >> numLayers;
240  numClustersPerLayer.resize( numLayers );
241 
242  //Load the number clusters per layer
243  file >> word;
244  if( word != "NumClustersPerLayer:" ){
245  errorLog << "loadModelFromFile(fstream &file) - Failed to read NumClustersPerLayer header!" << endl;
246  return false;
247  }
248  for(UINT i=0; i<numClustersPerLayer.size(); i++){
249  file >> numClustersPerLayer[i];
250  }
251 
252  //Load the alpha parameter
253  file >> word;
254  if( word != "Alpha:" ){
255  errorLog << "loadModelFromFile(fstream &file) - Failed to read Alpha header!" << endl;
256  return false;
257  }
258  file >> alpha;
259 
260  //If the model has been trained then load it
261  if( trained ){
262 
263  //Load the Ranges
264  file >> word;
265  if( word != "Ranges:" ){
266  errorLog << "loadModelFromFile(fstream &file) - Failed to read Ranges header!" << endl;
267  return false;
268  }
269  ranges.resize(numInputDimensions);
270  for(UINT i=0; i<ranges.size(); i++){
271  file >> ranges[i].minValue;
272  file >> ranges[i].maxValue;
273  }
274 
275  //Load the Clusters
276  file >> word;
277  if( word != "Clusters:" ){
278  errorLog << "loadModelFromFile(fstream &file) - Failed to read Clusters header!" << endl;
279  return false;
280  }
281  clusters.resize( numLayers );
282 
283  for(UINT k=0; k<clusters.size(); k++){
284 
285  //Load the NumRows
286  file >> word;
287  if( word != "NumRows:" ){
288  errorLog << "loadModelFromFile(fstream &file) - Failed to read NumRows header!" << endl;
289  return false;
290  }
291  file >> numRows;
292 
293  //Load the NumCols
294  file >> word;
295  if( word != "NumCols:" ){
296  errorLog << "loadModelFromFile(fstream &file) - Failed to read NumCols header!" << endl;
297  return false;
298  }
299  file >> numCols;
300 
301  clusters[k].resize(numRows, numCols);
302  for(UINT i=0; i<clusters[k].getNumRows(); i++){
303  for(UINT j=0; j<clusters[k].getNumCols(); j++){
304  file >> clusters[k][i][j];
305  }
306  }
307  }
308  }
309 
310  return true;
311 }
312 
313 bool KMeansFeatures::init( const vector< UINT > numClustersPerLayer ){
314 
315  clear();
316 
317  if( numClustersPerLayer.size() == 0 ) return false;
318 
319  this->numClustersPerLayer = numClustersPerLayer;
320  numInputDimensions = 0; //This will be 0 until the KMeansFeatures has been trained
321  numOutputDimensions = 0; //This will be 0 until the KMeansFeatures has been trained
322 
323  //Flag that the feature extraction has been initialized but not trained
324  initialized = true;
325  trained = false;
326 
327  return true;
328 }
329 
331  MatrixDouble data = trainingData.getDataAsMatrixDouble();
332  return train_( data );
333 }
334 
336  MatrixDouble data = trainingData.getDataAsMatrixDouble();
337  return train_( data );
338 }
339 
341  MatrixDouble data = trainingData.getDataAsMatrixDouble();
342  return train_( data );
343 }
344 
346  MatrixDouble data = trainingData.getDataAsMatrixDouble();
347  return train_( data );
348 }
349 
351 
352  if( !initialized ){
353  errorLog << "train_(MatrixDouble &trainingData) - The quantizer has not been initialized!" << endl;
354  return false;
355  }
356 
357  //Reset any previous model
358  featureDataReady = false;
359 
360  const UINT M = trainingData.getNumRows();
361  const UINT N = trainingData.getNumCols();
362 
363  numInputDimensions = N;
364  numOutputDimensions = numClustersPerLayer[ numClustersPerLayer.size()-1 ];
365 
366  //Scale the input data if needed
367  ranges = trainingData.getRanges();
368  if( useScaling ){
369  for(UINT i=0; i<M; i++){
370  for(UINT j=0; j<N; j++){
371  trainingData[i][j] = scale(trainingData[i][j],ranges[j].minValue,ranges[j].maxValue,0,1.0);
372  }
373  }
374  }
375 
376  //Train the KMeans model at each layer
377  const UINT K = (UINT)numClustersPerLayer.size();
378  for(UINT k=0; k<K; k++){
379  KMeans kmeans;
380  kmeans.setNumClusters( numClustersPerLayer[k] );
381  kmeans.setComputeTheta( true );
382  kmeans.setMinChange( minChange );
383  kmeans.setMinNumEpochs( minNumEpochs );
384  kmeans.setMaxNumEpochs( maxNumEpochs );
385 
386  trainingLog << "Layer " << k+1 << "/" << K << " NumClusters: " << numClustersPerLayer[k] << endl;
387  if( !kmeans.train_( trainingData ) ){
388  errorLog << "train_(MatrixDouble &trainingData) - Failed to train kmeans model at layer: " << k << endl;
389  return false;
390  }
391 
392  //Save the clusters
393  clusters.push_back( kmeans.getClusters() );
394 
395  //Project the data through the current layer to use as training data for the next layer
396  if( k+1 != K ){
397  MatrixDouble data( M, numClustersPerLayer[k] );
398  VectorDouble input( trainingData.getNumCols() );
399  VectorDouble output( data.getNumCols() );
400 
401  for(UINT i=0; i<M; i++){
402 
403  //Copy the data into the sample
404  for(UINT j=0; j<input.size(); j++){
405  input[j] = trainingData[i][j];
406  }
407 
408  //Project the sample through the current layer
409  if( !projectDataThroughLayer( input, output, k ) ){
410  errorLog << "train_(MatrixDouble &trainingData) - Failed to project sample through layer: " << k << endl;
411  return false;
412  }
413 
414  //Copy the result into the training data for the next layer
415  for(UINT j=0; j<output.size(); j++){
416  data[i][j] = output[j];
417  }
418  }
419 
420  //Swap the data for the next layer
421  trainingData = data;
422 
423  }
424 
425  }
426 
427  //Flag that the kmeans model has been trained
428  trained = true;
429  featureVector.resize( numOutputDimensions, 0 );
430 
431  return true;
432 }
433 
434 bool KMeansFeatures::projectDataThroughLayer( const VectorDouble &input, VectorDouble &output, const UINT layer ){
435 
436  if( layer >= clusters.size() ){
437  errorLog << "projectDataThroughLayer(...) - Layer out of bounds! It should be less than: " << clusters.size() << endl;
438  return false;
439  }
440 
441  const UINT M = clusters[ layer ].getNumRows();
442  const UINT N = clusters[ layer ].getNumCols();
443 
444  if( input.size() != N ){
445  errorLog << "projectDataThroughLayer(...) - The size of the input vector (" << input.size() << ") does not match the size: " << N << endl;
446  return false;
447  }
448 
449  //Make sure the output vector size is OK
450  if( output.size() != M ){
451  output.resize( M );
452  }
453 
454  UINT i,j = 0;
455  //double gamma = 2.0*SQR(alpha);
456  //double gamma = 2.0*SQR( 1 );
457  for(i=0; i<M; i++){
458  output[i] = 0;
459  for(j=0; j<N; j++){
460  output[i] += SQR( input[j] - clusters[layer][i][j] );
461  //output[i] += input[j] * clusters[layer][i][j];
462  }
463  //cout << "i: " << i << " sum: " << output[i] << " output: " << 1.0/(1.0+exp(-output[i])) << endl;
464  //cout << "i: " << i << " sum: " << output[i] << " output: " << exp( -output[i] / gamma ) << endl;
465  //output[i] = exp( -output[i] / gamma );
466  //output[i] = 1.0/(1.0+exp(-output[i]));
467  output[i] = sqrt( output[i] ); //L2 Norm
468 
469  }
470 
471  return true;
472 }
473 
474 UINT KMeansFeatures::getNumLayers() const{
475  return (UINT)numClustersPerLayer.size();
476 }
477 
478 UINT KMeansFeatures::getLayerSize(const UINT layerIndex) const{
479  if( layerIndex >= numClustersPerLayer.size() ){
480  warningLog << "LayerIndex is out of bounds. It must be less than the number of layers: " << numClustersPerLayer.size() << endl;
481  return 0;
482  }
483  return numClustersPerLayer[layerIndex];
484 }
485 
486 vector< MatrixDouble > KMeansFeatures::getClusters() const{
487  return clusters;
488 }
489 
490 }//End of namespace GRT
virtual bool saveModelToFile(string filename) const
KMeansFeatures(const vector< UINT > numClustersPerLayer=vector< UINT >(1, 100), const double alpha=0.2, const bool useScaling=true)
bool setMaxNumEpochs(const UINT maxNumEpochs)
Definition: MLBase.cpp:237
Definition: AdaBoost.cpp:25
unsigned int getNumCols() const
Definition: Matrix.h:538
virtual bool computeFeatures(const VectorDouble &inputVector)
bool setMinChange(const double minChange)
Definition: MLBase.cpp:251
bool setNumClusters(const UINT numClusters)
Definition: Clusterer.cpp:263
string getFeatureExtractionType() const
double scale(const double &x, const double &minSource, const double &maxSource, const double &minTarget, const double &maxTarget, const bool constrain=false)
Definition: MLBase.h:339
MatrixDouble getDataAsMatrixDouble() const
bool loadFeatureExtractionSettingsFromFile(fstream &file)
MatrixDouble getDataAsMatrixDouble() const
virtual bool reset()
virtual bool train_(MatrixDouble &data)
Definition: KMeans.cpp:162
unsigned int getNumRows() const
Definition: Matrix.h:531
bool saveFeatureExtractionSettingsToFile(fstream &file) const
std::vector< MinMax > getRanges() const
virtual bool deepCopyFrom(const FeatureExtraction *featureExtraction)
bool setMinNumEpochs(const UINT minNumEpochs)
Definition: MLBase.cpp:246
bool copyBaseVariables(const FeatureExtraction *featureExtractionModule)
virtual bool loadModelFromFile(string filename)
virtual bool train_(ClassificationData &trainingData)
KMeansFeatures & operator=(const KMeansFeatures &rhs)