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.
KMeans.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 "KMeans.h"
23 
24 GRT_BEGIN_NAMESPACE
25 
26 //Register the KMeans class with the Clusterer base class
27 RegisterClustererModule< KMeans > KMeans::registerModule("KMeans");
28 
29 //Constructor,destructor
30 KMeans::KMeans(const UINT numClusters,const UINT minNumEpochs,const UINT maxNumEpochs,const Float minChange,const bool computeTheta){
31 
32  this->numClusters = numClusters;
33  this->minNumEpochs = minNumEpochs;
34  this->maxNumEpochs = maxNumEpochs;
35  this->minChange = minChange;
36  this->computeTheta = computeTheta;
37 
39  nchg = 0;
40  finalTheta = 0;
41  numTrainingIterationsToConverge = 0;
42  trained = false;
43 
44  classType = "KMeans";
45  clustererType = classType;
46  debugLog.setProceedingText("[DEBUG KMeans]");
47  errorLog.setProceedingText("[ERROR KMeans]");
48  trainingLog.setProceedingText("[TRAINING KMeans]");
49  warningLog.setProceedingText("[WARNING KMeans]");
50 }
51 
52 KMeans::KMeans(const KMeans &rhs){
53 
54  classType = "KMeans";
55  clustererType = classType;
56  debugLog.setProceedingText("[DEBUG KMeans]");
57  errorLog.setProceedingText("[ERROR KMeans]");
58  trainingLog.setProceedingText("[TRAINING KMeans]");
59  warningLog.setProceedingText("[WARNING KMeans]");
60 
61  if( this != &rhs ){
62 
64  this->nchg = rhs.nchg;
65  this->computeTheta = rhs.computeTheta;
66  this->finalTheta = rhs.finalTheta;
67  this->clusters = rhs.clusters;
68  this->assign = rhs.assign;
69  this->count = rhs.count;
70  this->thetaTracker = rhs.thetaTracker;
71 
72  //Clone the Clusterer variables
73  copyBaseVariables( (Clusterer*)&rhs );
74  }
75 
76 }
77 
79 }
80 
82 
83  if( this != &rhs ){
84 
86  this->nchg = rhs.nchg;
87  this->computeTheta = rhs.computeTheta;
88  this->finalTheta = rhs.finalTheta;
89  this->clusters = rhs.clusters;
90  this->assign = rhs.assign;
91  this->count = rhs.count;
92  this->thetaTracker = rhs.thetaTracker;
93 
94  //Clone the Clusterer variables
95  copyBaseVariables( (Clusterer*)&rhs );
96  }
97 
98  return *this;
99 }
100 
101 bool KMeans::deepCopyFrom(const Clusterer *clusterer){
102 
103  if( clusterer == NULL ) return false;
104 
105  if( this->getClustererType() == clusterer->getClustererType() ){
106  //Clone the KMeans values
107  KMeans *ptr = (KMeans*)clusterer;
108 
110  this->nchg = ptr->nchg;
111  this->computeTheta = ptr->computeTheta;
112  this->finalTheta = ptr->finalTheta;
113  this->clusters = ptr->clusters;
114  this->assign = ptr->assign;
115  this->count = ptr->count;
116  this->thetaTracker = ptr->thetaTracker;
117 
118  //Clone the Clusterer variables
119  return copyBaseVariables( clusterer );
120  }
121  return false;
122 }
123 
124 bool KMeans::train_(ClassificationData &trainingData){
125 
126  if( trainingData.getNumSamples() == 0 ){
127  errorLog << "train_(ClassificationData &trainingData) - The training data is empty!" << std::endl;
128  return false;
129  }
130 
131  //Set the numClusters as the number of classes in the training data
132  numClusters = trainingData.getNumClasses();
133 
134  //Convert the labelled training data to a training matrix
135  UINT M = trainingData.getNumSamples();
136  UINT N = trainingData.getNumDimensions();
137  MatrixFloat data(M,N);
138  for(UINT i=0; i<M; i++){
139  for(UINT j=0; j<N; j++){
140  data[i][j] = trainingData[i][j];
141  }
142  }
143 
144  //Run the K-Means algorithm
145  return train_( data );
146 }
147 
148 bool KMeans::train_(UnlabelledData &trainingData){
149 
150  //Convert the training data into one matrix
151  UINT M = trainingData.getNumSamples();
152  UINT N = trainingData.getNumDimensions();
153  MatrixFloat data(M,N);
154  for(UINT i=0; i<M; i++){
155  for(UINT j=0; j<N; j++){
156  data[i][j] = trainingData[i][j];
157  }
158  }
159 
160  return train_(data);
161 }
162 
164 
165  trained = false;
166 
167  if( numClusters == 0 ){
168  errorLog << "train_(MatrixFloat &data) - Failed to train model. NumClusters is zero!" << std::endl;
169  return false;
170  }
171 
172  if( data.getNumRows() == 0 || data.getNumCols() == 0 ){
173  errorLog << "train_(MatrixFloat &data) - The number of rows or columns in the data is zero!" << std::endl;
174  return false;
175  }
176 
178  numInputDimensions = data.getNumCols();
179 
180  clusters.resize(numClusters,numInputDimensions);
181  assign.resize(numTrainingSamples);
182  count.resize(numClusters);
183 
184  //Randomly pick k data points as the starting clusters
185  Random random;
187  for(UINT i=0; i<numTrainingSamples; i++) randIndexs[i] = i;
188  std::random_shuffle(randIndexs.begin(), randIndexs.end());
189 
190  //Copy the clusters
191  for(UINT k=0; k<numClusters; k++){
192  for(UINT j=0; j<numInputDimensions; j++){
193  clusters[k][j] = data[ randIndexs[k] ][j];
194  }
195  }
196 
197  return trainModel( data );
198 }
199 
200 bool KMeans::predict_(VectorFloat &inputVector){
201 
202  if( !trained ){
203  return false;
204  }
205 
206  if( inputVector.getSize() != numInputDimensions ){
207  return false;
208  }
209 
210  if( useScaling ){
211  for(UINT n=0; n<numInputDimensions; n++){
212  inputVector[n] = grt_scale(inputVector[n], ranges[n].minValue, ranges[n].maxValue, 0.0, 1.0);
213  }
214  }
215 
216  const Float sigma = 1.0;
217  const Float gamma = 1.0 / (2.0*grt_sqr(sigma));
218  Float sum = 0;
219  Float dist = 0;
220  UINT minIndex = 0;
221  bestDistance = grt_numeric_limits< Float >::max();
223  maxLikelihood = 0;
224  if( clusterLikelihoods.getSize() != numClusters )
225  clusterLikelihoods.resize( numClusters );
226  if( clusterDistances.getSize() != numClusters )
227  clusterDistances.resize( numClusters );
228 
229  for(UINT i=0; i<numClusters; i++){
230 
231  //We don't need to compute the sqrt as it works without it and is faster
232  dist = 0;
233  for(UINT j=0; j<numInputDimensions; j++){
234  dist += grt_sqr( inputVector[j]-clusters[i][j] );
235  }
236 
237  clusterDistances[i] = dist;
238  clusterLikelihoods[i] = exp( - grt_sqr(gamma * dist) ); //1.0/(1.0+dist); //This will give us a value close to 1 for a dist of 0, and a value closer to 0 when the dist is large
239 
240  sum += clusterLikelihoods[i];
241 
242  if( dist < bestDistance ){
243  bestDistance = dist;
244  minIndex = i;
245  }
246  }
247 
248  //Normalize the likelihood
249  for(UINT i=0; i<numClusters; i++){
250  clusterLikelihoods[i] /= sum;
251  }
252 
253  predictedClusterLabel = clusterLabels[ minIndex ];
254  maxLikelihood = clusterLikelihoods[ minIndex ];
255 
256  return true;
257 }
258 
260 
261  if( numClusters == 0 ){
262  errorLog << "trainModel(MatrixFloat &data) - Failed to train model. NumClusters is zero!" << std::endl;
263  return false;
264  }
265 
266  if( clusters.getNumRows() != numClusters ){
267  errorLog << "trainModel(MatrixFloat &data) - Failed to train model. The number of rows in the cluster matrix does not match the number of clusters! You should need to initalize the clusters matrix first before calling this function!" << std::endl;
268  return false;
269  }
270 
271  if( clusters.getNumCols() != numInputDimensions ){
272  errorLog << "trainModel(MatrixFloat &data) - Failed to train model. The number of columns in the cluster matrix does not match the number of input dimensions! You should need to initalize the clusters matrix first before calling this function!" << std::endl;
273  return false;
274  }
275 
276  Timer timer;
277  UINT currentIter = 0;
278  UINT numChanged = 0;
279  bool keepTraining = true;
280  Float theta = 0;
281  Float lastTheta = 0;
282  Float delta = 0;
283  Float startTime = 0;
284  thetaTracker.clear();
285  finalTheta = 0;
286  numTrainingIterationsToConverge = 0;
287  trained = false;
288  converged = false;
289 
290  //Scale the data if needed
291  ranges = data.getRanges();
292  if( useScaling ){
293  data.scale(0,1);
294  }
295 
296  //Init the assign and count Vectors
297  //Assign is set to K+1 so that the nChanged values in the eStep at the first iteration will be updated correctly
298  for(UINT m=0; m<numTrainingSamples; m++) assign[m] = numClusters+1;
299  for(UINT k=0; k<numClusters; k++) count[k] = 0;
300 
301  //Run the training loop
302  timer.start();
303  while( keepTraining ){
304  startTime = timer.getMilliSeconds();
305 
306  //Compute the E step
307  numChanged = estep( data );
308 
309  //Compute the M step
310  mstep( data );
311 
312  //Update the iteration counter
313  currentIter++;
314 
315  //Compute theta if needed
316  if( computeTheta ){
317  theta = calculateTheta(data);
318  delta = lastTheta - theta;
319  lastTheta = theta;
320  }else theta = delta = 0;
321 
322  //Check convergance
323  if( numChanged == 0 && currentIter > minNumEpochs ){ converged = true; keepTraining = false; }
324  if( currentIter >= maxNumEpochs ){ keepTraining = false; }
325  if( fabs( delta ) < minChange && computeTheta && currentIter > minNumEpochs ){ converged = true; keepTraining = false; }
326  if( computeTheta ) thetaTracker.push_back( theta );
327 
328  trainingLog << "Epoch: " << currentIter << "/" << maxNumEpochs;
329  trainingLog << " Epoch time: " << (timer.getMilliSeconds()-startTime)/1000.0 << " seconds";
330  trainingLog << " Theta: " << theta << " Delta: " << delta << std::endl;
331  }
332  trainingLog << "Model Trained at epoch: " << currentIter << " with a theta value of: " << theta << std::endl;
333 
334  finalTheta = theta;
335  numTrainingIterationsToConverge = currentIter;
336  trained = true;
337 
338  //Setup the cluster labels
339  clusterLabels.resize(numClusters);
340  for(UINT i=0; i<numClusters; i++){
341  clusterLabels[i] = i+1;
342  }
343  clusterLikelihoods.resize(numClusters,0);
344  clusterDistances.resize(numClusters,0);
345 
346  return true;
347 }
348 
349 UINT KMeans::estep(const MatrixFloat &data) {
350  UINT k,m,n,kmin;
351  Float dmin,d;
352  nchg = 0;
353  kmin = 0;
354 
355  //Reset Count
356  for (k=0; k < numClusters; k++) count[k] = 0;
357 
358  //Search for the closest center and reasign if needed
359  for (m=0; m < numTrainingSamples; m++) {
360  dmin = 9.99e+99; //Set dmin to a really big value
361  for (k=0; k < numClusters; k++) {
362  d = 0.0;
363  for (n=0; n < numInputDimensions; n++)
364  d += grt_sqr( data[m][n]-clusters[k][n] );
365  if (d <= dmin){ dmin = d; kmin = k; }
366  }
367  if ( kmin != assign[m] ){
368  nchg++;
369  assign[m] = kmin;
370  }
371  count[kmin]++;
372  }
373  return nchg;
374 }
375 
376 void KMeans::mstep(const MatrixFloat &data) {
377  UINT n,k,m;
378 
379  //Reset means to zero
380  for (k=0; k<numClusters; k++)
381  for (n=0;n<numInputDimensions;n++)
382  clusters[k][n] = 0.;
383 
384  //Get new mean by adding assigned data points and dividing by the number of values in each cluster
385  for(m=0; m < numTrainingSamples; m++)
386  for(n=0; n < numInputDimensions; n++)
387  clusters[ assign[m] ][n] += data[m][n];
388 
389  for (k=0; k < numClusters; k++) {
390  if (count[k] > 0){
391  Float countNorm = 1.0 / count[k];
392  for (n=0; n < numInputDimensions; n++){
393  clusters[k][n] *= countNorm;
394  }
395  }
396  }
397 }
398 
399 Float KMeans::calculateTheta(const MatrixFloat &data){
400 
401  Float theta = 0;
402  Float sum = 0;
403  UINT m,n,k = 0;
404  for(m=0; m < numTrainingSamples; m++){
405  k = assign[m];
406  sum = 0;
407  for(n=0; n < numInputDimensions; n++){
408  sum += grt_sqr(clusters[k][n] - data[m][n]);
409  }
410  theta += grt_sqrt(sum);
411  }
412  theta /= numTrainingSamples;
413 
414  return theta;
415 
416 }
417 
418 bool KMeans::saveModelToFile( std::fstream &file ) const{
419 
420  if( !file.is_open() ){
421  errorLog << "saveModelToFile(fstream &file) - Failed to save model, file is not open!" << std::endl;
422  return false;
423  }
424 
425  file << "GRT_KMEANS_MODEL_FILE_V1.0\n";
426 
427  if( !saveClustererSettingsToFile( file ) ){
428  errorLog << "saveModelToFile(fstream &file) - Failed to save clusterer settings to file!" << std::endl;
429  return false;
430  }
431 
432  if( trained ){
433  file << "Clusters:\n";
434 
435  for(UINT k=0; k<numClusters; k++){
436  for(UINT n=0; n<numInputDimensions; n++){
437  file << clusters[k][n] << "\t";
438  }file << std::endl;
439  }
440  }
441 
442  return true;
443 
444 }
445 
446 bool KMeans::loadModelFromFile( std::fstream &file ){
447 
448  //Clear any previous model
449  clear();
450 
451  if(!file.is_open()){
452  errorLog << "loadModelFromFile(string filename) - Failed to open file!" << std::endl;
453  return false;
454  }
455 
456  std::string word;
457  file >> word;
458  if( word != "GRT_KMEANS_MODEL_FILE_V1.0" ){
459  return false;
460  }
461 
462  if( !loadClustererSettingsFromFile( file ) ){
463  errorLog << "loadModelFromFile(string filename) - Failed to open file!" << std::endl;
464  return false;
465  }
466 
467  if( trained ){
468  file >> word;
469  if( word != "Clusters:" ){
470  return false;
471  }
472 
473  //Resize the buffers
474  clusters.resize(numClusters,numInputDimensions);
475 
476  //Load the data
477  for(UINT k=0; k<numClusters; k++){
478  for(UINT n=0; n<numInputDimensions; n++){
479  file >> clusters[k][n];
480  }
481  }
482  }
483 
484  return true;
485 }
486 
489 
490  numTrainingSamples = 0;
491  nchg = 0;
492  finalTheta = 0;
493  thetaTracker.clear();
494  assign.clear();
495  count.clear();
496 
497  return true;
498 }
499 
502 
503  numTrainingSamples = 0;
504  nchg = 0;
505  finalTheta = 0;
506  thetaTracker.clear();
507  assign.clear();
508  count.clear();
509  clusters.clear();
510 
511  return true;
512 }
513 
514 bool KMeans::setComputeTheta(const bool computeTheta){
515  this->computeTheta = computeTheta;
516  return true;
517 }
518 
519 bool KMeans::setClusters(const MatrixFloat &clusters){
520  clear();
521  numClusters = clusters.getNumRows();
522  numInputDimensions = clusters.getNumCols();
523  this->clusters = clusters;
524  return true;
525 }
526 
527 GRT_END_NAMESPACE
528 
void clear()
Definition: Matrix.h:522
virtual bool saveModelToFile(std::fstream &file) const
Definition: KMeans.cpp:418
KMeans(const UINT numClusters=10, const UINT minNumEpochs=5, const UINT maxNumEpochs=1000, const Float minChange=1.0e-5, const bool computeTheta=true)
Definition: KMeans.cpp:30
Definition: Timer.h:43
virtual bool predict_(VectorFloat &inputVector)
Definition: KMeans.cpp:200
virtual bool clear()
Definition: KMeans.cpp:500
Definition: Random.h:40
std::string getClustererType() const
Definition: Clusterer.cpp:260
bool scale(const Float minTarget, const Float maxTarget)
UINT getNumDimensions() const
virtual bool resize(const unsigned int size)
Definition: Vector.h:133
virtual bool train_(MatrixFloat &data)
Definition: KMeans.cpp:163
UINT getNumSamples() const
UINT getSize() const
Definition: Vector.h:191
bool copyBaseVariables(const Clusterer *clusterer)
Definition: Clusterer.cpp:87
signed long getMilliSeconds()
Definition: Timer.h:117
UINT nchg
Number of values changes.
Definition: KMeans.h:191
bool loadClustererSettingsFromFile(std::fstream &file)
Definition: Clusterer.cpp:179
bool setClusters(const MatrixFloat &clusters)
Definition: KMeans.cpp:519
This class implements the KMeans clustering algorithm.
virtual bool loadModelFromFile(std::fstream &file)
Definition: KMeans.cpp:446
UINT getNumSamples() const
UINT predictedClusterLabel
Stores the predicted cluster label from the most recent predict( )
Definition: Clusterer.h:250
UINT numTrainingSamples
Number of training examples.
Definition: KMeans.h:190
bool saveClustererSettingsToFile(std::fstream &file) const
Definition: Clusterer.cpp:157
virtual bool reset()
Definition: Clusterer.cpp:128
UINT numClusters
Number of clusters in the model.
Definition: Clusterer.h:249
unsigned int getNumRows() const
Definition: Matrix.h:542
UINT getNumDimensions() const
UINT getNumClasses() const
unsigned int getNumCols() const
Definition: Matrix.h:549
bool trainModel(MatrixFloat &data)
Definition: KMeans.cpp:259
virtual ~KMeans()
Definition: KMeans.cpp:78
bool start()
Definition: Timer.h:64
Vector< MinMax > getRanges() const
virtual bool deepCopyFrom(const Clusterer *clusterer)
Definition: KMeans.cpp:101
Definition: KMeans.h:41
virtual bool resize(const unsigned int r, const unsigned int c)
Definition: Matrix.h:232
KMeans & operator=(const KMeans &rhs)
Definition: KMeans.cpp:81
virtual bool reset()
Definition: KMeans.cpp:487
virtual bool clear()
Definition: Clusterer.cpp:142