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