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