GestureRecognitionToolkit  Version: 1.0 Revision: 04-03-15
The Gesture Recognition Toolkit (GRT) is a cross-platform, open-source, c++ machine learning library for real-time gesture recognition.
SelfOrganizingMap.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 "SelfOrganizingMap.h"
22 
23 using namespace std;
24 
25 namespace GRT{
26 
27 //Register the SelfOrganizingMap class with the Clusterer base class
28 RegisterClustererModule< SelfOrganizingMap > SelfOrganizingMap::registerModule("SelfOrganizingMap");
29 
30 SelfOrganizingMap::SelfOrganizingMap( const UINT networkSize, const UINT networkTypology, const UINT maxNumEpochs, const double alphaStart, const double alphaEnd ){
31 
32  this->numClusters = networkSize;
33  this->networkTypology = networkTypology;
34  this->maxNumEpochs = maxNumEpochs;
35  this->alphaStart = alphaStart;
36  this->alphaEnd = alphaEnd;
37 
38  classType = "SelfOrganizingMap";
39  clustererType = classType;
40  debugLog.setProceedingText("[DEBUG SelfOrganizingMap]");
41  errorLog.setProceedingText("[ERROR SelfOrganizingMap]");
42  trainingLog.setProceedingText("[TRAINING SelfOrganizingMap]");
43  warningLog.setProceedingText("[WARNING SelfOrganizingMap]");
44 }
45 
46 SelfOrganizingMap::SelfOrganizingMap(const SelfOrganizingMap &rhs){
47 
48  classType = "SelfOrganizingMap";
49  clustererType = classType;
50  debugLog.setProceedingText("[DEBUG KMeans]");
51  errorLog.setProceedingText("[ERROR KMeans]");
52  trainingLog.setProceedingText("[TRAINING KMeans]");
53  warningLog.setProceedingText("[WARNING KMeans]");
54 
55  if( this != &rhs ){
56 
57  this->networkTypology = rhs.networkTypology;
58  this->alphaStart = rhs.alphaStart;
59  this->alphaEnd = rhs.alphaEnd;
60 
61  //Clone the Clusterer variables
62  copyBaseVariables( (Clusterer*)&rhs );
63  }
64 }
65 
66 SelfOrganizingMap::~SelfOrganizingMap(){
67 
68 }
69 
70 SelfOrganizingMap& SelfOrganizingMap::operator=(const SelfOrganizingMap &rhs){
71 
72  if( this != &rhs ){
73 
74  this->networkTypology = rhs.networkTypology;
75  this->alphaStart = rhs.alphaStart;
76  this->alphaEnd = rhs.alphaEnd;
77 
78  //Clone the Clusterer variables
79  copyBaseVariables( (Clusterer*)&rhs );
80  }
81 
82  return *this;
83 }
84 
85 bool SelfOrganizingMap::deepCopyFrom(const Clusterer *clusterer){
86 
87  if( clusterer == NULL ) return false;
88 
89  if( this->getClustererType() == clusterer->getClustererType() ){
90  //Clone the SelfOrganizingMap values
91  SelfOrganizingMap *ptr = (SelfOrganizingMap*)clusterer;
92 
93  this->networkTypology = ptr->networkTypology;
94  this->alphaStart = ptr->alphaStart;
95  this->alphaEnd = ptr->alphaEnd;
96 
97  //Clone the Clusterer variables
98  return copyBaseVariables( clusterer );
99  }
100 
101  return false;
102 }
103 
104 bool SelfOrganizingMap::reset(){
105 
106  //Reset the base class
107  Clusterer::reset();
108 
109  return true;
110 }
111 
112 bool SelfOrganizingMap::clear(){
113 
114  //Reset the base class
115  Clusterer::clear();
116 
117  //Clear the SelfOrganizingMap models
118  neurons.clear();
119  networkWeights.clear();
120 
121  return true;
122 }
123 
124 bool SelfOrganizingMap::train_( MatrixDouble &data ){
125 
126  //Clear any previous models
127  clear();
128 
129  const UINT M = data.getNumRows();
130  const UINT N = data.getNumCols();
131  numInputDimensions = N;
132  numOutputDimensions = numClusters;
133  Random rand;
134 
135  //Setup the neurons
136  neurons.resize( numClusters );
137 
138  if( neurons.size() != numClusters ){
139  errorLog << "train_( MatrixDouble &data ) - Failed to resize neurons vector, there might not be enough memory!" << endl;
140  return false;
141  }
142 
143  for(UINT j=0; j<numClusters; j++){
144 
145  //Init the neuron
146  neurons[j].init( N, 0.5 );
147 
148  //Set the weights as a random training example
149  neurons[j].weights = data.getRowVector( rand.getRandomNumberInt(0, M) );
150  }
151 
152  //Setup the network weights
153  switch( networkTypology ){
154  case RANDOM_NETWORK:
155  networkWeights.resize(numClusters, numClusters);
156 
157  //Set the diagonal weights as 1 (as i==j)
158  for(UINT i=0; i<numClusters; i++){
159  networkWeights[i][i] = 1;
160  }
161 
162  //Randomize the other weights
163  UINT indexA = 0;
164  UINT indexB = 0;
165  double weight = 0;
166  for(UINT i=0; i<numClusters*numClusters; i++){
167  indexA = rand.getRandomNumberInt(0, numClusters);
168  indexB = rand.getRandomNumberInt(0, numClusters);
169 
170  //Make sure the two random indexs are the same (as this is a diagonal and should be 1)
171  if( indexA != indexB ){
172  //Pick a random weight between these two neurons
173  weight = rand.getRandomNumberUniform(0,1);
174 
175  //The weight betwen neurons a and b is the mirrored
176  networkWeights[indexA][indexB] = weight;
177  networkWeights[indexB][indexA] = weight;
178  }
179  }
180  break;
181  }
182 
183  //Scale the data if needed
184  ranges = data.getRanges();
185  if( useScaling ){
186  for(UINT i=0; i<M; i++){
187  for(UINT j=0; j<numInputDimensions; j++){
188  data[i][j] = scale(data[i][j],ranges[j].minValue,ranges[j].maxValue,0,1);
189  }
190  }
191  }
192 
193  double error = 0;
194  double lastError = 0;
195  double trainingSampleError = 0;
196  double delta = 0;
197  double minChange = 0;
198  double weightUpdate = 0;
199  double weightUpdateSum = 0;
200  double alpha = 1.0;
201  double neuronDiff = 0;
202  UINT iter = 0;
203  bool keepTraining = true;
204  VectorDouble trainingSample;
205  vector< UINT > randomTrainingOrder(M);
206 
207  //In most cases, the training data is grouped into classes (100 samples for class 1, followed by 100 samples for class 2, etc.)
208  //This can cause a problem for stochastic gradient descent algorithm. To avoid this issue, we randomly shuffle the order of the
209  //training samples. This random order is then used at each epoch.
210  for(UINT i=0; i<M; i++){
211  randomTrainingOrder[i] = i;
212  }
213  std::random_shuffle(randomTrainingOrder.begin(), randomTrainingOrder.end());
214 
215  //Enter the main training loop
216  while( keepTraining ){
217 
218  //Update alpha based on the current iteration
219  alpha = Util::scale(iter,0,maxNumEpochs,alphaStart,alphaEnd);
220 
221  //Run one epoch of training using the online best-matching-unit algorithm
222  error = 0;
223  for(UINT i=0; i<M; i++){
224 
225  trainingSampleError = 0;
226 
227  //Get the i'th random training sample
228  trainingSample = data.getRowVector( randomTrainingOrder[i] );
229 
230  //Find the best matching unit
231  double dist = 0;
232  double bestDist = numeric_limits<double>::max();
233  UINT bestIndex = 0;
234  for(UINT j=0; j<numClusters; j++){
235  dist = neurons[j].getSquaredWeightDistance( trainingSample );
236  if( dist < bestDist ){
237  bestDist = dist;
238  bestIndex = j;
239  }
240  }
241 
242  //Update the weights based on the distance to the winning neuron
243  //Neurons closer to the winning neuron will have their weights update more
244  for(UINT j=0; j<numClusters; j++){
245 
246  //Update the weights for the j'th neuron
247  weightUpdateSum = 0;
248  neuronDiff = 0;
249  for(UINT n=0; n<N; n++){
250  neuronDiff = trainingSample[n] - neurons[j][n];
251  weightUpdate = networkWeights[bestIndex][j] * alpha * neuronDiff;
252  neurons[j][n] += weightUpdate;
253  weightUpdateSum += neuronDiff;
254  }
255 
256  trainingSampleError += SQR( weightUpdateSum );
257  }
258 
259  error += sqrt( trainingSampleError / numClusters );
260  }
261 
262  //Compute the error
263  delta = fabs( error-lastError );
264  lastError = error;
265 
266  //Check to see if we should stop
267  if( delta <= minChange ){
268  converged = true;
269  keepTraining = false;
270  }
271 
272  if( grt_isinf( error ) ){
273  errorLog << "train_(MatrixDouble &data) - Training failed! Error is NAN!" << endl;
274  return false;
275  }
276 
277  if( ++iter >= maxNumEpochs ){
278  keepTraining = false;
279  }
280 
281  trainingLog << "Epoch: " << iter << " Squared Error: " << error << " Delta: " << delta << " Alpha: " << alpha << endl;
282  }
283 
284  numTrainingIterationsToConverge = iter;
285  trained = true;
286 
287  return true;
288 }
289 
290 bool SelfOrganizingMap::train_(ClassificationData &trainingData){
291  MatrixDouble data = trainingData.getDataAsMatrixDouble();
292  return train_(data);
293 }
294 
295 bool SelfOrganizingMap::train_(UnlabelledData &trainingData){
296  MatrixDouble data = trainingData.getDataAsMatrixDouble();
297  return train_(data);
298 }
299 
300 bool SelfOrganizingMap::map_( VectorDouble &x ){
301 
302  if( !trained ){
303  return false;
304  }
305 
306  if( useScaling ){
307  for(UINT i=0; i<numInputDimensions; i++){
308  x[i] = scale(x[i], ranges[i].minValue, ranges[i].maxValue, 0, 1);
309  }
310  }
311 
312  if( mappedData.size() != numClusters )
313  mappedData.resize( numClusters );
314 
315  for(UINT i=0; i<numClusters; i++){
316  mappedData[i] = neurons[i].fire( x );
317  }
318 
319  return true;
320 }
321 
322 bool SelfOrganizingMap::saveModelToFile(fstream &file) const{
323 
324  if( !trained ){
325  errorLog << "saveModelToFile(fstream &file) - Can't save model to file, the model has not been trained!" << endl;
326  return false;
327  }
328 
329  file << "GRT_SELF_ORGANIZING_MAP_MODEL_FILE_V1.0\n";
330 
331  if( !saveClustererSettingsToFile( file ) ){
332  errorLog << "saveModelToFile(fstream &file) - Failed to save cluster settings to file!" << endl;
333  return false;
334  }
335 
336  file << "NetworkTypology: " << networkTypology << endl;
337  file << "AlphaStart: " << alphaStart <<endl;
338  file << "AlphaEnd: " << alphaEnd <<endl;
339 
340  if( trained ){
341  file << "NetworkWeights: \n";
342  for(UINT i=0; i<networkWeights.getNumRows(); i++){
343  for(UINT j=0; j<networkWeights.getNumCols(); j++){
344  file << networkWeights[i][j];
345  if( j<networkWeights.getNumCols()-1 ) file << "\t";
346  }
347  file << "\n";
348  }
349 
350  file << "Neurons: \n";
351  for(UINT i=0; i<neurons.size(); i++){
352  if( !neurons[i].saveNeuronToFile( file ) ){
353  errorLog << "saveModelToFile(fstream &file) - Failed to save neuron to file!" << endl;
354  return false;
355  }
356  }
357  }
358 
359  return true;
360 
361 }
362 
363 bool SelfOrganizingMap::loadModelFromFile(fstream &file){
364 
365  //Clear any previous model
366  clear();
367 
368  string word;
369  file >> word;
370  if( word != "GRT_SELF_ORGANIZING_MAP_MODEL_FILE_V1.0" ){
371  errorLog << "loadModelFromFile(fstream &file) - Failed to load file header!" << endl;
372  return false;
373  }
374 
375  if( !loadClustererSettingsFromFile( file ) ){
376  errorLog << "loadModelFromFile(fstream &file) - Failed to load cluster settings from file!" << endl;
377  return false;
378  }
379 
380  file >> word;
381  if( word != "NetworkTypology:" ){
382  errorLog << "loadModelFromFile(fstream &file) - Failed to load NetworkTypology header!" << endl;
383  return false;
384  }
385  file >> networkTypology;
386 
387  file >> word;
388  if( word != "AlphaStart:" ){
389  errorLog << "loadModelFromFile(fstream &file) - Failed to load AlphaStart header!" << endl;
390  return false;
391  }
392  file >> alphaStart;
393 
394  file >> word;
395  if( word != "AlphaEnd:" ){
396  errorLog << "loadModelFromFile(fstream &file) - Failed to load alphaEnd header!" << endl;
397  return false;
398  }
399  file >> alphaEnd;
400 
401  //Load the model if it has been trained
402  if( trained ){
403  file >> word;
404  if( word != "NetworkWeights:" ){
405  errorLog << "loadModelFromFile(fstream &file) - Failed to load NetworkWeights header!" << endl;
406  return false;
407  }
408 
409  networkWeights.resize(numClusters, numClusters);
410  for(UINT i=0; i<networkWeights.getNumRows(); i++){
411  for(UINT j=0; j<networkWeights.getNumCols(); j++){
412  file >> networkWeights[i][j];
413  }
414  }
415 
416  file >> word;
417  if( word != "Neurons:" ){
418  errorLog << "loadModelFromFile(fstream &file) - Failed to load Neurons header!" << endl;
419  return false;
420  }
421 
422  neurons.resize(numClusters);
423  for(UINT i=0; i<neurons.size(); i++){
424  if( !neurons[i].loadNeuronFromFile( file ) ){
425  errorLog << "loadModelFromFile(fstream &file) - Failed to save neuron to file!" << endl;
426  return false;
427  }
428  }
429  }
430 
431  return true;
432 }
433 
434 bool SelfOrganizingMap::validateNetworkTypology( const UINT networkTypology ){
435  if( networkTypology == RANDOM_NETWORK ) return true;
436 
437  warningLog << "validateNetworkTypology(const UINT networkTypology) - Unknown networkTypology!" << endl;
438 
439  return false;
440 }
441 
442 UINT SelfOrganizingMap::getNetworkSize() const{
443  return numClusters;
444 }
445 
446 double SelfOrganizingMap::getAlphaStart() const{
447  return alphaStart;
448 }
449 
450 double SelfOrganizingMap::getAlphaEnd() const{
451  return alphaEnd;
452 }
453 
454 VectorDouble SelfOrganizingMap::getMappedData() const{
455  return mappedData;
456 }
457 
458 vector< GaussNeuron > SelfOrganizingMap::getNeurons() const{
459  return neurons;
460 }
461 
462 const vector< GaussNeuron >& SelfOrganizingMap::getNeuronsRef() const{
463  return neurons;
464 }
465 
466 MatrixDouble SelfOrganizingMap::getNetworkWeights() const{
467  return networkWeights;
468 }
469 
470 bool SelfOrganizingMap::setNetworkSize( const UINT networkSize ){
471  if( networkSize > 0 ){
472  this->numClusters = networkSize;
473  return true;
474  }
475 
476  warningLog << "setNetworkSize(const UINT networkSize) - The networkSize must be greater than 0!" << endl;
477 
478  return false;
479 }
480 
481 bool SelfOrganizingMap::setNetworkTypology( const UINT networkTypology ){
482  if( validateNetworkTypology( networkTypology ) ){
483  this->networkTypology = networkTypology;
484  return true;
485  }
486  return false;
487 }
488 
489 bool SelfOrganizingMap::setAlphaStart( const double alphaStart ){
490 
491  if( alphaStart > 0 ){
492  this->alphaStart = alphaStart;
493  return true;
494  }
495 
496  warningLog << "setAlphaStart(const double alphaStart) - AlphaStart must be greater than zero!" << endl;
497 
498  return false;
499 }
500 
501 bool SelfOrganizingMap::setAlphaEnd( const double alphaEnd ){
502 
503  if( alphaEnd > 0 ){
504  this->alphaEnd = alphaEnd;
505  return true;
506  }
507 
508  warningLog << "setAlphaEnd(const double alphaEnd) - AlphaEnd must be greater than zero!" << endl;
509 
510  return false;
511 }
512 
513 } //End of namespace GRT
This class implements the Self Oganizing Map clustering algorithm.
Definition: AdaBoost.cpp:25
unsigned int getNumCols() const
Definition: Matrix.h:538
string getClustererType() const
Definition: Clusterer.cpp:257
MatrixDouble getDataAsMatrixDouble() const
int getRandomNumberInt(int minRange, int maxRange)
Definition: Random.h:87
double getRandomNumberUniform(double minRange=0.0, double maxRange=1.0)
Definition: Random.h:197
MatrixDouble getDataAsMatrixDouble() const
unsigned int getNumRows() const
Definition: Matrix.h:531
std::vector< MinMax > getRanges() const
std::vector< T > getRowVector(const unsigned int r) const
Definition: Matrix.h:173