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