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.
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 GRT_BEGIN_NAMESPACE
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 Float 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!" << std::endl;
88 
89  return false;
90 }
91 
93 
94  VectorFloat 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 VectorFloat &inputVector) - Failed to project data through layer: " << layer << std::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( std::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( std::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( std::fstream &file ) const{
157 
158  if( !file.is_open() ){
159  errorLog << "saveModelToFile(fstream &file) - The file is not open!" << std::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" << std::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!" << std::endl;
169  return false;
170  }
171 
172  file << "NumLayers: " << getNumLayers() << std::endl;
173  file << "NumClustersPerLayer: ";
174  for(UINT i=0; i<numClustersPerLayer.getSize(); i++){
175  file << " " << numClustersPerLayer[i];
176  }
177  file << std::endl;
178 
179  file << "Alpha: " << alpha << std::endl;
180 
181  if( trained ){
182  file << "Ranges: ";
183  for(UINT i=0; i<ranges.getSize(); i++){
184  file << ranges[i].minValue << " " << ranges[i].maxValue << " ";
185  }
186  file << std::endl;
187 
188  file << "Clusters: " << std::endl;
189  for(UINT k=0; k<clusters.getSize(); k++){
190  file << "NumRows: " << clusters[k].getNumRows() << std::endl;
191  file << "NumCols: " << clusters[k].getNumCols() << std::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 << std::endl;
199  }
200  }
201  }
202 
203  return true;
204 }
205 
206 bool KMeansFeatures::loadModelFromFile( std::fstream &file ){
207 
208  clear();
209 
210  if( !file.is_open() ){
211  errorLog << "loadModelFromFile(fstream &file) - The file is not open!" << std::endl;
212  return false;
213  }
214 
215  std::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!" << std::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!" << std::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!" << std::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!" << std::endl;
246  return false;
247  }
248  for(UINT i=0; i<numClustersPerLayer.getSize(); 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!" << std::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!" << std::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!" << std::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!" << std::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!" << std::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  MatrixFloat data = trainingData.getDataAsMatrixFloat();
332  return train_( data );
333 }
334 
336  MatrixFloat data = trainingData.getDataAsMatrixFloat();
337  return train_( data );
338 }
339 
341  MatrixFloat data = trainingData.getDataAsMatrixFloat();
342  return train_( data );
343 }
344 
346  MatrixFloat data = trainingData.getDataAsMatrixFloat();
347  return train_( data );
348 }
349 
351 
352  if( !initialized ){
353  errorLog << "train_(MatrixFloat &trainingData) - The quantizer has not been initialized!" << std::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.getSize()-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] = grt_scale(trainingData[i][j],ranges[j].minValue,ranges[j].maxValue,0.0,1.0);
372  }
373  }
374  }
375 
376  //Train the KMeans model at each layer
377  const UINT K = numClustersPerLayer.getSize();
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] << std::endl;
387  if( !kmeans.train_( trainingData ) ){
388  errorLog << "train_(MatrixFloat &trainingData) - Failed to train kmeans model at layer: " << k << std::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  MatrixFloat data( M, numClustersPerLayer[k] );
398  VectorFloat input( trainingData.getNumCols() );
399  VectorFloat 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.getSize(); 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_(MatrixFloat &trainingData) - Failed to project sample through layer: " << k << std::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.getSize(); 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  //Flag that the kmeans model has been trained
427  trained = true;
428  featureVector.resize( numOutputDimensions, 0 );
429 
430  return true;
431 }
432 
433 bool KMeansFeatures::projectDataThroughLayer( const VectorFloat &input, VectorFloat &output, const UINT layer ){
434 
435  if( layer >= clusters.getSize() ){
436  errorLog << "projectDataThroughLayer(...) - Layer out of bounds! It should be less than: " << clusters.getSize() << std::endl;
437  return false;
438  }
439 
440  const UINT M = clusters[ layer ].getNumRows();
441  const UINT N = clusters[ layer ].getNumCols();
442 
443  if( input.getSize() != N ){
444  errorLog << "projectDataThroughLayer(...) - The size of the input Vector (" << input.getSize() << ") does not match the size: " << N << std::endl;
445  return false;
446  }
447 
448  //Make sure the output Vector size is OK
449  if( output.getSize() != M ){
450  output.resize( M );
451  }
452 
453  UINT i,j = 0;
454  //Float gamma = 2.0*SQR(alpha);
455  //Float gamma = 2.0*SQR( 1 );
456  for(i=0; i<M; i++){
457  output[i] = 0;
458  for(j=0; j<N; j++){
459  output[i] += grt_sqr( input[j] - clusters[layer][i][j] );
460  //output[i] += input[j] * clusters[layer][i][j];
461  }
462  //cout << "i: " << i << " sum: " << output[i] << " output: " << 1.0/(1.0+exp(-output[i])) << std::endl;
463  //cout << "i: " << i << " sum: " << output[i] << " output: " << exp( -output[i] / gamma ) << std::endl;
464  //output[i] = exp( -output[i] / gamma );
465  //output[i] = 1.0/(1.0+exp(-output[i]));
466  output[i] = grt_sqrt( output[i] ); //L2 Norm
467 
468  }
469 
470  return true;
471 }
472 
473 UINT KMeansFeatures::getNumLayers() const{
474  return numClustersPerLayer.getSize();
475 }
476 
477 UINT KMeansFeatures::getLayerSize(const UINT layerIndex) const{
478  if( layerIndex >= numClustersPerLayer.getSize() ){
479  warningLog << "LayerIndex is out of bounds. It must be less than the number of layers: " << numClustersPerLayer.getSize() << std::endl;
480  return 0;
481  }
482  return numClustersPerLayer[layerIndex];
483 }
484 
485 Vector< MatrixFloat > KMeansFeatures::getClusters() const{
486  return clusters;
487 }
488 
489 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:339
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:162
virtual bool train_(ClassificationData &trainingData)
virtual bool computeFeatures(const VectorFloat &inputVector)
bool setMinChange(const Float minChange)
Definition: MLBase.cpp:282
unsigned int getSize() const
Definition: Vector.h:193
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:277
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:268
virtual bool reset()
bool setNumClusters(const UINT numClusters)
Definition: Clusterer.cpp:265
virtual bool saveModelToFile(std::string filename) const