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