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