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