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