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.
HierarchicalClustering.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 "HierarchicalClustering.h"
22 
23 GRT_BEGIN_NAMESPACE
24 
25 //Register the HierarchicalClustering class with the Clusterer base class
26 RegisterClustererModule< HierarchicalClustering > HierarchicalClustering::registerModule("HierarchicalClustering");
27 
29  M = N = 0;
30  classType = "HierarchicalClustering";
31  clustererType = classType;
32  debugLog.setProceedingText("[DEBUG HierarchicalClustering]");
33  errorLog.setProceedingText("[ERROR HierarchicalClustering]");
34  trainingLog.setProceedingText("[TRAINING HierarchicalClustering]");
35  warningLog.setProceedingText("[WARNING HierarchicalClustering]");
36 }
37 
39  classType = "HierarchicalClustering";
40  clustererType = classType;
41  debugLog.setProceedingText("[DEBUG HierarchicalClustering]");
42  errorLog.setProceedingText("[ERROR HierarchicalClustering]");
43  trainingLog.setProceedingText("[TRAINING HierarchicalClustering]");
44  warningLog.setProceedingText("[WARNING HierarchicalClustering]");
45  *this = rhs;
46 }
47 
49 
50 }
51 
53 
54  if( this != &rhs ){
55 
56  this->M = rhs.M;
57  this->N = rhs.N;
58  this->clusters = rhs.clusters;
59  this->distanceMatrix = rhs.distanceMatrix;
60 
61  //Clone the Clusterer variables
62  copyBaseVariables( (Clusterer*)&rhs );
63  }
64 
65  return *this;
66 }
67 
69 
70  if( clusterer == NULL ) return false;
71 
72  if( this->getClustererType() == clusterer->getClustererType() ){
73  //Clone the HierarchicalClustering values
75 
76  this->M = ptr->M;
77  this->N = ptr->N;
78  this->clusters = ptr->clusters;
79  this->distanceMatrix = ptr->distanceMatrix;
80 
81  //Clone the Clusterer variables
82  return copyBaseVariables( clusterer );
83  }
84  return false;
85 }
86 
88 
90 
91  return true;
92 }
93 
95 
97 
98  M = 0;
99  N = 0;
100  clusters.clear();
101  distanceMatrix.clear();
102 
103  return true;
104 }
105 
107 
108  if( trainingData.getNumSamples() == 0 ){
109  return false;
110  }
111 
112  //Convert the labelled training data to a training matrix
113  M = trainingData.getNumSamples();
114  N = trainingData.getNumDimensions();
115 
116  MatrixFloat data(M,N);
117  for(UINT i=0; i<M; i++){
118  for(UINT j=0; j<N; j++){
119  data[i][j] = trainingData[i][j];
120  }
121  }
122 
123  return train_( data );
124 }
125 
127 
128  if( trainingData.getNumSamples() == 0 ){
129  return false;
130  }
131 
132  //Convert the training data into one matrix
133  M = trainingData.getNumSamples();
134  N = trainingData.getNumDimensions();
135 
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  return train( data );
144 }
145 
147 
148  trained = false;
149  clusters.clear();
150  distanceMatrix.clear();
151 
152  if( data.getNumRows() == 0 || data.getNumCols() == 0 ){
153  return false;
154  }
155 
156  //Set the rows and columns
157  M = data.getNumRows();
158  N = data.getNumCols();
159 
160  //Build the distance matrix
161  distanceMatrix.resize(M,M);
162 
163  //Build the distance matrix
164  for(UINT i=0; i<M; i++){
165  for(UINT j=0; j<M; j++){
166  if( i== j ) distanceMatrix[i][j] = grt_numeric_limits< Float >::max();
167  else{
168  distanceMatrix[i][j] = squaredEuclideanDistance(data[i], data[j]);
169  }
170  }
171  }
172 
173  //Build the initial clusters, at the start each sample gets its own cluster
174  UINT uniqueClusterID = 0;
175  Vector< ClusterInfo > clusterData(M);
176  for(UINT i=0; i<M; i++){
177  clusterData[i].uniqueClusterID = uniqueClusterID++;
178  clusterData[i].addSampleToCluster(i);
179  }
180 
181  trainingLog << "Starting clustering..." << std::endl;
182 
183  //Create the first cluster level, each sample is it's own cluster
184  UINT level = 0;
185  ClusterLevel newLevel;
186  newLevel.level = level;
187  for(UINT i=0; i<M; i++){
188  newLevel.clusters.push_back( clusterData[i] );
189  }
190  clusters.push_back( newLevel );
191 
192  //Move to level 1 and start the search
193  level++;
194  bool keepClustering = true;
195 
196  while( keepClustering ){
197 
198  //Find the closest two clusters within the cluster data
199  Float minDist = grt_numeric_limits< Float >::max();
200  Vector< Vector< UINT > > clusterPairs;
201  UINT K = (UINT)clusterData.size();
202  for(UINT i=0; i<K; i++){
203  for(UINT j=0; j<K; j++){
204  if( i != j ){
205  Float dist = computeClusterDistance( clusterData[i], clusterData[j] );
206 
207  if( dist < minDist ){
208  minDist = dist;
209  Vector< UINT > clusterPair(2);
210  clusterPair[0] = i;
211  clusterPair[1] = j;
212  clusterPairs.clear();
213  clusterPairs.push_back( clusterPair );
214  }
215 
216  }
217  }
218  }
219 
220  if( minDist == grt_numeric_limits< Float >::max() ){
221  keepClustering = false;
222  warningLog << "train_(MatrixFloat &data) - Failed to find any cluster at level: " << level << std::endl;
223  return false;
224  }else{
225 
226  //Merge the two closest clusters together and create a new level
227  ClusterLevel newLevel;
228  newLevel.level = level;
229 
230  //Create the new cluster
231  ClusterInfo newCluster;
232  newCluster.uniqueClusterID = uniqueClusterID++;
233 
234  const UINT numClusterPairs = clusterPairs.getSize();
235 
236  for(UINT k=0; k<numClusterPairs; k++){
237  //Add all the samples in the first cluster to the new cluster
238  UINT numSamplesInClusterA = clusterData[ clusterPairs[k][0] ].getNumSamplesInCluster();
239  for(UINT i=0; i<numSamplesInClusterA; i++){
240  UINT index = clusterData[ clusterPairs[k][0] ][ i ];
241  newCluster.addSampleToCluster( index );
242  }
243 
244  //Add all the samples in the second cluster to the new cluster
245  UINT numSamplesInClusterB = clusterData[ clusterPairs[k][1] ].getNumSamplesInCluster();
246  for(UINT i=0; i<numSamplesInClusterB; i++){
247  UINT index = clusterData[ clusterPairs[k][1] ][ i ];
248  newCluster.addSampleToCluster( index );
249  }
250 
251  //Compute the cluster variance
252  newCluster.clusterVariance = computeClusterVariance( newCluster, data );
253 
254  //Remove the two cluster pairs (so they will not be used in the next search
255  UINT idA = clusterData[ clusterPairs[k][0] ].getUniqueClusterID();
256  UINT idB = clusterData[ clusterPairs[k][1] ].getUniqueClusterID();
257  UINT numRemoved = 0;
258  Vector< ClusterInfo >::iterator iter = clusterData.begin();
259  while( iter != clusterData.end() ){
260  if( iter->getUniqueClusterID() == idA || iter->getUniqueClusterID() == idB ){
261  iter = clusterData.erase( iter );
262  if( ++numRemoved >= 2 ) break;
263  }else iter++;
264  }
265  }
266 
267  //Add the merged cluster to the clusterData
268  clusterData.push_back( newCluster );
269 
270  //Add the new level and cluster data to the main cluster buffer
271  newLevel.clusters.push_back( newCluster );
272 
273  clusters.push_back( newLevel );
274 
275  //Update the level
276  level++;
277  }
278 
279  //Check to see if we should stop clustering
280  if( level >= M ){
281  keepClustering = false;
282  }
283 
284  if( clusterData.size() == 0 ){
285  keepClustering = false;
286  }
287 
288  trainingLog << "Cluster level: " << level << " Number of clusters: " << clusters.back().getNumClusters() << std::endl;
289  }
290 
291  //Flag that the model is trained
292  trained = true;
293 
294  //Setup the cluster labels
295  clusterLabels.resize(numClusters);
296  for(UINT i=0; i<numClusters; i++){
297  clusterLabels[i] = i+1;
298  }
299  clusterLikelihoods.resize(numClusters,0);
300  clusterDistances.resize(numClusters,0);
301 
302  return true;
303 }
304 
305 bool HierarchicalClustering::printModel(){
306 
307  UINT K = (UINT)clusters.size();
308 
309  std::cout << "Hierarchical Clustering Model\n\n";
310  for(UINT k=0; k<K; k++){
311  UINT numClusters = clusters[k].getNumClusters();
312  UINT numSamples = 0;
313  for(UINT i=0; i<numClusters; i++){
314  numSamples += clusters[k][i].getNumSamplesInCluster();
315  }
316 
317  std::cout << "Level: " << clusters[k].level << "\tNumClusters: " << numClusters << "\tNumSamples: " << numSamples << std::endl;
318  for(UINT i=0; i<numClusters; i++){
319  std::cout << "ClusterVariance: " << clusters[k][i].clusterVariance << std::endl;
320  std::cout << "Indexs: ";
321  UINT numSamplesInCluster = clusters[k][i].getNumSamplesInCluster();
322  for(UINT j=0; j<numSamplesInCluster; j++){
323  std::cout << clusters[k][i][j] << "\t";
324  }
325  std::cout << std::endl;
326  }
327  }
328 
329  return true;
330 }
331 
332 Float HierarchicalClustering::squaredEuclideanDistance(const Float *a,const Float *b){
333  Float dist = 0;
334  for(UINT i=0; i<N; i++){
335  dist += SQR( a[i] - b[i] );
336  }
337  return dist;
338 }
339 
340 Float HierarchicalClustering::computeClusterDistance( const ClusterInfo &clusterA, const ClusterInfo &clusterB ){
341 
342  Float minDist = grt_numeric_limits< Float >::max();
343  const UINT numSamplesA = clusterA.getNumSamplesInCluster();
344  const UINT numSamplesB = clusterB.getNumSamplesInCluster();
345 
346  //Find the minimum distance between the two clusters
347  for(UINT i=0; i<numSamplesA; i++){
348  for(UINT j=0; j<numSamplesB; j++){
349  if( distanceMatrix[ clusterA[i] ][ clusterB[j] ] < minDist ){
350  minDist = distanceMatrix[ clusterA[i] ][ clusterB[j] ];
351  }
352  }
353  }
354 
355  return minDist;
356 }
357 
358 Float HierarchicalClustering::computeClusterVariance( const ClusterInfo &cluster, const MatrixFloat &data ){
359 
360  VectorFloat mean(N,0);
361  VectorFloat std(N,0);
362 
363  //Compute the mean
364  UINT numSamples = cluster.getNumSamplesInCluster();
365  for(UINT j=0; j<N; j++){
366  for(UINT i=0; i<numSamples; i++){
367  UINT index = cluster[i];
368  mean[j] += data[ index ][j];
369  }
370  mean[j] /= Float( numSamples );
371  }
372 
373  //Compute the std dev
374  for(UINT j=0; j<N; j++){
375  for(UINT i=0; i<numSamples; i++){
376  std[j] += grt_sqr( data[ cluster[i] ][j] - mean[j] );
377  }
378  std[j] = grt_sqrt( std[j] / Float( numSamples-1 ) );
379  }
380 
381  Float variance = 0;
382  for(UINT j=0; j<N; j++){
383  variance += std[j];
384  }
385  return variance/N;
386 }
387 
388 bool HierarchicalClustering::saveModelToFile( std::fstream &file ) const{
389 
390  if( !file.is_open() ){
391  errorLog << "saveModelToFile(string filename) - Failed to open file!" << std::endl;
392  return false;
393  }
394 
395  file << "GRT_HIERARCHICAL_CLUSTERING_FILE_V1.0\n";
396 
397  if( !saveClustererSettingsToFile( file ) ){
398  errorLog << "saveModelToFile(fstream &file) - Failed to save cluster settings to file!" << std::endl;
399  return false;
400  }
401 
402  if( trained ){
403  file << "M: " << M << std::endl;
404  file << "N: " << N << std::endl;
405  file << "NumLevels: " << clusters.getSize() << std::endl;
406 
407  for(UINT i=0; i<clusters.getSize(); i++){
408  file << "Level: " << clusters[i].getLevel() << std::endl;
409  file << "NumClusters: " << clusters[i].getNumClusters() << std::endl;
410  }
411  }
412 
413  return true;
414 
415 }
416 
417 bool HierarchicalClustering::loadModelFromFile( std::fstream &file ){
418 
419  std::string word;
420 
421  //Clear any previous model
422  clear();
423 
424  file >> word;
425  if( word != "GRT_HIERARCHICAL_CLUSTERING_FILE_V1.0" ){
426  return false;
427  }
428 
429  if( !loadClustererSettingsFromFile( file ) ){
430  errorLog << "loadModelFromFile(fstream &file) - Failed to load cluster settings from file!" << std::endl;
431  return false;
432  }
433 
434  return true;
435 }
436 
437 GRT_END_NAMESPACE
void clear()
Definition: Matrix.h:522
virtual bool loadModelFromFile(std::fstream &file)
std::string getClustererType() const
Definition: Clusterer.cpp:259
virtual bool saveModelToFile(std::fstream &file) const
UINT getNumDimensions() const
virtual bool resize(const unsigned int size)
Definition: Vector.h:133
virtual bool train(ClassificationData trainingData)
Definition: MLBase.cpp:88
UINT getNumSamples() const
bool copyBaseVariables(const Clusterer *clusterer)
Definition: Clusterer.cpp:86
bool loadClustererSettingsFromFile(std::fstream &file)
Definition: Clusterer.cpp:178
unsigned int getSize() const
Definition: Vector.h:193
UINT getNumSamples() const
virtual bool train_(MatrixFloat &trainingData)
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
unsigned int getNumCols() const
Definition: Matrix.h:549
virtual bool deepCopyFrom(const Clusterer *clusterer)
HierarchicalClustering & operator=(const HierarchicalClustering &rhs)
virtual bool resize(const unsigned int r, const unsigned int c)
Definition: Matrix.h:232
virtual bool clear()
Definition: Clusterer.cpp:141
This class implements a basic Hierarchial Clustering algorithm.