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.
DTW.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 "DTW.h"
22 
23 GRT_BEGIN_NAMESPACE
24 
25 //Register the DTW module with the Classifier base class
26 RegisterClassifierModule< DTW > DTW::registerModule("DTW");
27 
28 DTW::DTW(bool useScaling,bool useNullRejection,Float nullRejectionCoeff,UINT rejectionMode,bool constrainWarpingPath,Float radius,bool offsetUsingFirstSample,bool useSmoothing,UINT smoothingFactor,Float nullRejectionLikelihoodThreshold)
29 {
30  this->useScaling=useScaling;
31  this->useNullRejection = useNullRejection;
32  this->nullRejectionCoeff = nullRejectionCoeff;
33  this->nullRejectionLikelihoodThreshold = nullRejectionLikelihoodThreshold;
34  this->rejectionMode = rejectionMode;
35  this->constrainWarpingPath = constrainWarpingPath;
36  this->radius = radius;
37  this->offsetUsingFirstSample = offsetUsingFirstSample;
38  this->useSmoothing = useSmoothing;
39  this->smoothingFactor = smoothingFactor;
40 
41  supportsNullRejection = true;
42  trained=false;
43  useZNormalisation=false;
44  constrainZNorm=false;
45  trimTrainingData = false;
46 
47  zNormConstrainThreshold=0.2;
48  trimThreshold = 0.1;
49  maximumTrimPercentage = 90;
50 
51  numTemplates=0;
52  distanceMethod=EUCLIDEAN_DIST;
53 
54  averageTemplateLength =0;
55 
56  classType = "DTW";
57  classifierType = classType;
58  classifierMode = TIMESERIES_CLASSIFIER_MODE;
59  debugLog.setProceedingText("[DEBUG NDDTW]");
60  errorLog.setProceedingText("[ERROR NDDTW]");
61  trainingLog.setProceedingText("[TRAINING NDDTW]");
62  warningLog.setProceedingText("[WARNING NDDTW]");
63 }
64 
65 DTW::DTW(const DTW &rhs){
66  *this = rhs;
67 }
68 
69 DTW::~DTW(void)
70 {
71 }
72 
73 DTW& DTW::operator=(const DTW &rhs){
74 
75  if( this != &rhs ){
76 
77  this->templatesBuffer = rhs.templatesBuffer;
78  this->distanceMatrices = rhs.distanceMatrices;
79  this->warpPaths = rhs.warpPaths;
80  this->continuousInputDataBuffer = rhs.continuousInputDataBuffer;
81  this->numTemplates = rhs.numTemplates;
82  this->useSmoothing = rhs.useSmoothing;
83  this->useZNormalisation = rhs.useZNormalisation;
84  this->constrainZNorm = rhs.constrainZNorm;
85  this->constrainWarpingPath = rhs.constrainWarpingPath;
86  this->trimTrainingData = rhs.trimTrainingData;
87  this->zNormConstrainThreshold = rhs.zNormConstrainThreshold;
88  this->radius = rhs.radius;
89  this->offsetUsingFirstSample = rhs.offsetUsingFirstSample;
90  this->trimThreshold = rhs.trimThreshold;
91  this->maximumTrimPercentage = rhs.maximumTrimPercentage;
92  this->smoothingFactor = rhs.smoothingFactor;
93  this->distanceMethod = rhs.distanceMethod;
94  this->rejectionMode = rhs.rejectionMode;
95  this->nullRejectionLikelihoodThreshold = rhs.nullRejectionLikelihoodThreshold;
96  this->averageTemplateLength = rhs.averageTemplateLength;
97 
98  //Copy the classifier variables
99  copyBaseVariables( (Classifier*)&rhs );
100  }
101  return *this;
102 }
103 
104 bool DTW::deepCopyFrom(const Classifier *classifier){
105 
106  if( classifier == NULL ) return false;
107 
108  if( this->getClassifierType() == classifier->getClassifierType() ){
109 
110  DTW *ptr = (DTW*)classifier;
111  this->templatesBuffer = ptr->templatesBuffer;
112  this->distanceMatrices = ptr->distanceMatrices;
113  this->warpPaths = ptr->warpPaths;
114  this->continuousInputDataBuffer = ptr->continuousInputDataBuffer;
115  this->numTemplates = ptr->numTemplates;
116  this->useSmoothing = ptr->useSmoothing;
117  this->useZNormalisation = ptr->useZNormalisation;
118  this->constrainZNorm = ptr->constrainZNorm;
119  this->constrainWarpingPath = ptr->constrainWarpingPath;
120  this->trimTrainingData = ptr->trimTrainingData;
121  this->zNormConstrainThreshold = ptr->zNormConstrainThreshold;
122  this->radius = ptr->radius;
123  this->offsetUsingFirstSample = ptr->offsetUsingFirstSample;
124  this->trimThreshold = ptr->trimThreshold;
125  this->maximumTrimPercentage = ptr->maximumTrimPercentage;
126  this->smoothingFactor = ptr->smoothingFactor;
127  this->distanceMethod = ptr->distanceMethod;
128  this->rejectionMode = ptr->rejectionMode;
129  this->nullRejectionLikelihoodThreshold = ptr->nullRejectionLikelihoodThreshold;
130  this->averageTemplateLength = ptr->averageTemplateLength;
131 
132  //Copy the classifier variables
133  return copyBaseVariables( classifier );
134  }
135  return false;
136 }
137 
139 bool DTW::train_(TimeSeriesClassificationData &labelledTrainingData){
140 
141  UINT bestIndex = 0;
142 
143  //Cleanup Memory
144  templatesBuffer.clear();
145  classLabels.clear();
146  trained = false;
147  continuousInputDataBuffer.clear();
148 
149  if( trimTrainingData ){
150  TimeSeriesClassificationSampleTrimmer timeSeriesTrimmer(trimThreshold,maximumTrimPercentage);
152  tempData.setNumDimensions( labelledTrainingData.getNumDimensions() );
153 
154  for(UINT i=0; i<labelledTrainingData.getNumSamples(); i++){
155  if( timeSeriesTrimmer.trimTimeSeries( labelledTrainingData[i] ) ){
156  tempData.addSample(labelledTrainingData[i].getClassLabel(), labelledTrainingData[i].getData());
157  }else{
158  trainingLog << "Removing training sample " << i << " from the dataset as it could not be trimmed!" << std::endl;
159  }
160  }
161  //Overwrite the original training data with the trimmed dataset
162  labelledTrainingData = tempData;
163  }
164 
165  if( labelledTrainingData.getNumSamples() == 0 ){
166  errorLog << "train_(TimeSeriesClassificationData &labelledTrainingData) - Can't train model as there are no samples in training data!" << std::endl;
167  return false;
168  }
169 
170  //Assign
171  numClasses = labelledTrainingData.getNumClasses();
172  numTemplates = labelledTrainingData.getNumClasses();
173  numInputDimensions = labelledTrainingData.getNumDimensions();
174  templatesBuffer.resize( numClasses );
175  classLabels.resize( numClasses );
176  nullRejectionThresholds.resize( numClasses );
177  averageTemplateLength = 0;
178 
179  //Need to copy the labelled training data incase we need to scale it or znorm it
180  TimeSeriesClassificationData trainingData( labelledTrainingData );
181 
182  //Perform any scaling or normalisation
183  ranges = trainingData.getRanges();
184  if( useScaling ) scaleData( trainingData );
185  if( useZNormalisation ) znormData( trainingData );
186 
187  //For each class, run a one-to-one DTW and find the template the best describes the data
188  for(UINT k=0; k<numTemplates; k++){
189  //Get the class label for the cth class
190  UINT classLabel = trainingData.getClassTracker()[k].classLabel;
191  TimeSeriesClassificationData classData = trainingData.getClassData( classLabel );
192  UINT numExamples = classData.getNumSamples();
193  bestIndex = 0;
194 
195  //Set the class label of this template
196  templatesBuffer[k].classLabel = classLabel;
197 
198  //Set the kth class label
199  classLabels[k] = classLabel;
200 
201  trainingLog << "Training Template: " << k << " Class: " << classLabel << std::endl;
202 
203  //Check to make sure we actually have some training examples
204  if( numExamples < 1 ){
205  errorLog << "train_(TimeSeriesClassificationData &labelledTrainingData) - Can not train model: Num of Example is < 1! Class: " << classLabel << ". Turn off null rejection if you want to use DTW with only 1 training sample per class." << std::endl;
206  return false;
207  }
208 
209  if( numExamples == 1 && useNullRejection ){
210  errorLog << "train_(TimeSeriesClassificationData &labelledTrainingData) - Can not train model as there is only 1 example in class: " << classLabel << ". Turn off null rejection if you want to use DTW with only 1 training sample per class." << std::endl;
211  return false;
212  }
213 
214  if( numExamples == 1 ){//If we have just one training example then we have to use it as the template
215  bestIndex = 0;
216  nullRejectionThresholds[k] = 0.0;//TODO-We need a better way of calculating this!
217  }else{
218  //Search for the best training example for this class
219  if( !train_NDDTW(classData,templatesBuffer[k],bestIndex) ){
220  errorLog << "train_(LabelledTimeSeriesClassificationData &labelledTrainingData) - Failed to train template for class with label: " << classLabel << std::endl;
221  return false;
222  }
223  }
224 
225  //Add the template with the best index to the buffer
226  int trainingMethod = 0;
227  if(useSmoothing) trainingMethod = 1;
228 
229  switch (trainingMethod) {
230  case(0)://Standard Training
231  templatesBuffer[k].timeSeries = classData[bestIndex].getData();
232  break;
233  case(1)://Training using Smoothing
234  //Smooth the data, reducing its size by a factor set by smoothFactor
235  smoothData(classData[ bestIndex ].getData(),smoothingFactor,templatesBuffer[k].timeSeries);
236  break;
237  default:
238  errorLog << "Can not train model: Unknown training method " << std::endl;
239  return false;
240  break;
241  }
242 
243  if( offsetUsingFirstSample ){
244  offsetTimeseries( templatesBuffer[k].timeSeries );
245  }
246 
247  //Add the average length of the training examples for this template to the overall averageTemplateLength
248  averageTemplateLength += templatesBuffer[k].averageTemplateLength;
249  }
250 
251  //Flag that the models have been trained
252  trained = true;
253  averageTemplateLength = averageTemplateLength/numTemplates;
254 
255  //Recompute the null rejection thresholds
257 
258  //Resize the prediction results to make sure it is setup for realtime prediction
259  continuousInputDataBuffer.clear();
260  continuousInputDataBuffer.resize(averageTemplateLength,VectorFloat(numInputDimensions,0));
261  classLikelihoods.resize(numTemplates,DEFAULT_NULL_LIKELIHOOD_VALUE);
262  classDistances.resize(numTemplates,0);
263  predictedClassLabel = GRT_DEFAULT_NULL_CLASS_LABEL;
264  maxLikelihood = DEFAULT_NULL_LIKELIHOOD_VALUE;
265 
266  //Training complete
267  return true;
268 }
269 
270 bool DTW::train_NDDTW(TimeSeriesClassificationData &trainingData,DTWTemplate &dtwTemplate,UINT &bestIndex){
271 
272  UINT numExamples = trainingData.getNumSamples();
273  VectorFloat results(numExamples,0.0);
274  MatrixFloat distanceResults(numExamples,numExamples);
275  dtwTemplate.averageTemplateLength = 0;
276 
277  for(UINT m=0; m<numExamples; m++){
278 
279  MatrixFloat templateA; //The m'th template
280  MatrixFloat templateB; //The n'th template
281  dtwTemplate.averageTemplateLength += trainingData[m].getLength();
282 
283  //Smooth the data if required
284  if( useSmoothing ) smoothData(trainingData[m].getData(),smoothingFactor,templateA);
285  else templateA = trainingData[m].getData();
286 
287  if( offsetUsingFirstSample ){
288  offsetTimeseries(templateA);
289  }
290 
291  for(UINT n=0; n<numExamples; n++){
292  if(m!=n){
293  //Smooth the data if required
294  if( useSmoothing ) smoothData(trainingData[n].getData(),smoothingFactor,templateB);
295  else templateB = trainingData[n].getData();
296 
297  if( offsetUsingFirstSample ){
298  offsetTimeseries(templateB);
299  }
300 
301  //Compute the distance between the two time series
302  MatrixFloat distanceMatrix(templateA.getNumRows(),templateB.getNumRows());
303  Vector< IndexDist > warpPath;
304  Float dist = computeDistance(templateA,templateB,distanceMatrix,warpPath);
305 
306  trainingLog << "Template: " << m << " Timeseries: " << n << " Dist: " << dist << std::endl;
307 
308  //Update the results values
309  distanceResults[m][n] = dist;
310  results[m] += dist;
311  }else distanceResults[m][n] = 0; //The distance is zero because the two timeseries are the same
312  }
313  }
314 
315  for(UINT m=0; m<numExamples; m++) results[m]/=(numExamples-1);
316  //Find the best average result, this is the result with the minimum value
317  bestIndex = 0;
318  Float bestAverage = results[0];
319  for(UINT m=1; m<numExamples; m++){
320  if( results[m] < bestAverage ){
321  bestAverage = results[m];
322  bestIndex = m;
323  }
324  }
325 
326  if( numExamples > 2 ){
327 
328  //Work out the threshold value for the best template
329  dtwTemplate.trainingMu = results[bestIndex];
330  dtwTemplate.trainingSigma = 0.0;
331 
332  for(UINT n=0; n<numExamples; n++){
333  if(n!=bestIndex){
334  dtwTemplate.trainingSigma += SQR( distanceResults[ bestIndex ][n] - dtwTemplate.trainingMu );
335  }
336  }
337  dtwTemplate.trainingSigma = sqrt( dtwTemplate.trainingSigma / Float(numExamples-2) );
338  }else{
339  warningLog << "_train_NDDTW(TimeSeriesClassificationData &trainingData,DTWTemplate &dtwTemplate,UINT &bestIndex - There are not enough examples to compute the trainingMu and trainingSigma for the template for class " << dtwTemplate.classLabel << std::endl;
340  dtwTemplate.trainingMu = 0.0;
341  dtwTemplate.trainingSigma = 0.0;
342  }
343 
344  //Set the average length of the training examples
345  dtwTemplate.averageTemplateLength = (UINT) (dtwTemplate.averageTemplateLength/Float(numExamples));
346 
347  trainingLog << "AverageTemplateLength: " << dtwTemplate.averageTemplateLength << std::endl;
348 
349  //Flag that the training was successfull
350  return true;
351 }
352 
353 
354 bool DTW::predict_(MatrixFloat &inputTimeSeries){
355 
356  if( !trained ){
357  errorLog << "predict_(MatrixFloat &inputTimeSeries) - The DTW templates have not been trained!" << std::endl;
358  return false;
359  }
360 
361  if( classLikelihoods.size() != numTemplates ) classLikelihoods.resize(numTemplates);
362  if( classDistances.size() != numTemplates ) classDistances.resize(numTemplates);
363 
364  predictedClassLabel = 0;
365  maxLikelihood = DEFAULT_NULL_LIKELIHOOD_VALUE;
366  for(UINT k=0; k<classLikelihoods.size(); k++){
367  classLikelihoods[k] = 0;
368  classDistances[k] = DEFAULT_NULL_LIKELIHOOD_VALUE;
369  }
370 
371  if( numInputDimensions != inputTimeSeries.getNumCols() ){
372  errorLog << "predict_(MatrixFloat &inputTimeSeries) - The number of features in the model (" << numInputDimensions << ") do not match that of the input time series (" << inputTimeSeries.getNumCols() << ")" << std::endl;
373  return false;
374  }
375 
376  //Perform any preprocessing if requried
377  MatrixFloat *timeSeriesPtr = &inputTimeSeries;
378  MatrixFloat processedTimeSeries;
379  MatrixFloat tempMatrix;
380  if(useScaling){
381  scaleData(*timeSeriesPtr,processedTimeSeries);
382  timeSeriesPtr = &processedTimeSeries;
383  }
384 
385  //Normalize the data if needed
386  if( useZNormalisation ){
387  znormData(*timeSeriesPtr,processedTimeSeries);
388  timeSeriesPtr = &processedTimeSeries;
389  }
390 
391  //Smooth the data if required
392  if( useSmoothing ){
393  smoothData(*timeSeriesPtr,smoothingFactor,tempMatrix);
394  timeSeriesPtr = &tempMatrix;
395  }
396 
397  //Offset the timeseries if required
398  if( offsetUsingFirstSample ){
399  offsetTimeseries( *timeSeriesPtr );
400  }
401 
402  //Make the prediction by finding the closest template
403  Float sum = 0;
404  if( distanceMatrices.size() != numTemplates ) distanceMatrices.resize( numTemplates );
405  if( warpPaths.size() != numTemplates ) warpPaths.resize( numTemplates );
406 
407  //Test the timeSeries against all the templates in the timeSeries buffer
408  for(UINT k=0; k<numTemplates; k++){
409  //Perform DTW
410  classDistances[k] = computeDistance(templatesBuffer[k].timeSeries,*timeSeriesPtr,distanceMatrices[k],warpPaths[k]);
411 
412  if(classDistances[k] > 1e-9)
413  {
414  classLikelihoods[k] = 1.0 / classDistances[k];
415  }
416  else
417  {
418  classLikelihoods[k] = 1e9;
419  }
420 
421  sum += classLikelihoods[k];
422  }
423 
424  //See which gave the min distance
425  UINT closestTemplateIndex = 0;
426  bestDistance = classDistances[0];
427  for(UINT k=1; k<numTemplates; k++){
428  if( classDistances[k] < bestDistance ){
429  bestDistance = classDistances[k];
430  closestTemplateIndex = k;
431  }
432  }
433 
434  //Normalize the class likelihoods and check which class has the maximum likelihood
435  UINT maxLikelihoodIndex = 0;
436  maxLikelihood = 0;
437  if( sum > 0 ){
438  for(UINT k=0; k<numTemplates; k++){
439  classLikelihoods[k] /= sum;
440  if( classLikelihoods[k] > maxLikelihood ){
441  maxLikelihood = classLikelihoods[k];
442  maxLikelihoodIndex = k;
443  }
444  }
445  }
446 
447  if( useNullRejection ){
448 
449  switch( rejectionMode ){
450  case TEMPLATE_THRESHOLDS:
451  if( bestDistance <= nullRejectionThresholds[ closestTemplateIndex ] ) predictedClassLabel = templatesBuffer[ closestTemplateIndex ].classLabel;
452  else predictedClassLabel = GRT_DEFAULT_NULL_CLASS_LABEL;
453  break;
454  case CLASS_LIKELIHOODS:
455  if( maxLikelihood >= nullRejectionLikelihoodThreshold) predictedClassLabel = templatesBuffer[ maxLikelihoodIndex ].classLabel;
456  else predictedClassLabel = GRT_DEFAULT_NULL_CLASS_LABEL;
457  break;
458  case THRESHOLDS_AND_LIKELIHOODS:
459  if( bestDistance <= nullRejectionThresholds[ closestTemplateIndex ] && maxLikelihood >= nullRejectionLikelihoodThreshold)
460  predictedClassLabel = templatesBuffer[ closestTemplateIndex ].classLabel;
461  else predictedClassLabel = GRT_DEFAULT_NULL_CLASS_LABEL;
462  break;
463  default:
464  errorLog << "predict_(MatrixFloat &timeSeries) - Unknown RejectionMode!" << std::endl;
465  return false;
466  break;
467  }
468 
469  }else predictedClassLabel = templatesBuffer[ closestTemplateIndex ].classLabel;
470 
471  return true;
472 }
473 
474 bool DTW::predict_( VectorFloat &inputVector ){
475 
476  if( !trained ){
477  errorLog << "predict_(VectorFloat &inputVector) - The model has not been trained!" << std::endl;
478  return false;
479  }
480  predictedClassLabel = 0;
481  maxLikelihood = DEFAULT_NULL_LIKELIHOOD_VALUE;
482  std::fill(classLikelihoods.begin(),classLikelihoods.end(),DEFAULT_NULL_LIKELIHOOD_VALUE);
483  std::fill(classDistances.begin(),classDistances.end(),0);
484 
485  if( numInputDimensions != inputVector.size() ){
486  errorLog << "predict_(VectorFloat &inputVector) - The number of features in the model " << numInputDimensions << " does not match that of the input Vector " << inputVector.size() << std::endl;
487  return false;
488  }
489 
490  //Add the new input to the circular buffer
491  continuousInputDataBuffer.push_back( inputVector );
492 
493  if( continuousInputDataBuffer.getNumValuesInBuffer() < averageTemplateLength ){
494  //We haven't got enough samples yet so can't do the prediction
495  return true;
496  }
497 
498  //Copy the data into a temporary matrix
499  const UINT M = continuousInputDataBuffer.getSize();
500  const UINT N = numInputDimensions;
501  MatrixFloat predictionTimeSeries(M,N);
502  for(UINT i=0; i<M; i++){
503  for(UINT j=0; j<N; j++){
504  predictionTimeSeries[i][j] = continuousInputDataBuffer[i][j];
505  }
506  }
507 
508  //Run the prediction
509  return predict( predictionTimeSeries );
510 
511 }
512 
513 bool DTW::reset(){
514  continuousInputDataBuffer.clear();
515  if( trained ){
516  continuousInputDataBuffer.resize(averageTemplateLength,VectorFloat(numInputDimensions,0));
518  }
519  return true;
520 }
521 
522 bool DTW::clear(){
523 
524  //Clear the Classifier variables
526 
527  //Clear the DTW model
528  templatesBuffer.clear();
529  distanceMatrices.clear();
530  warpPaths.clear();
531  continuousInputDataBuffer.clear();
532 
533  return true;
534 }
535 
537 
538  if(!trained) return false;
539 
540  //Copy the null rejection thresholds into one buffer so they can easily be accessed from the base class
541  nullRejectionThresholds.resize(numTemplates);
542 
543  for(UINT k=0; k<numTemplates; k++){
544  //The threshold is set as the mean distance plus gamma standard deviations
545  nullRejectionThresholds[k] = templatesBuffer[k].trainingMu + (templatesBuffer[k].trainingSigma * nullRejectionCoeff);
546  }
547 
548  return true;
549 }
550 
552 
553  if( newTemplates.size() == templatesBuffer.size() ){
554  templatesBuffer = newTemplates;
555  //Make sure the class labels have not changed
556  classLabels.resize( templatesBuffer.size() );
557  for(UINT i=0; i<templatesBuffer.size(); i++){
558  classLabels[i] = templatesBuffer[i].classLabel;
559  }
560  return true;
561  }
562  return false;
563 }
564 
566 
567 Float DTW::computeDistance(MatrixFloat &timeSeriesA,MatrixFloat &timeSeriesB,MatrixFloat &distanceMatrix,Vector< IndexDist > &warpPath){
568 
569  const int M = timeSeriesA.getNumRows();
570  const int N = timeSeriesB.getNumRows();
571  const int C = timeSeriesA.getNumCols();
572  int i,j,k,index = 0;
573  Float totalDist,v,normFactor = 0.;
574 
575  warpPath.clear();
576  if( int(distanceMatrix.getNumRows()) != M || int(distanceMatrix.getNumCols()) != N ){
577  distanceMatrix.resize(M, N);
578  }
579 
580  switch (distanceMethod) {
581  case (ABSOLUTE_DIST):
582  for(i=0; i<M; i++){
583  for(j=0; j<N; j++){
584  distanceMatrix[i][j] = 0.0;
585  for(k=0; k< C; k++){
586  distanceMatrix[i][j] += fabs(timeSeriesA[i][k]-timeSeriesB[j][k]);
587  }
588  }
589  }
590  break;
591  case (EUCLIDEAN_DIST):
592  //Calculate Euclidean Distance for all possible values
593  for(i=0; i<M; i++){
594  for(j=0; j<N; j++){
595  distanceMatrix[i][j] = 0.0;
596  for(k=0; k< C; k++){
597  distanceMatrix[i][j] += SQR( timeSeriesA[i][k]-timeSeriesB[j][k] );
598  }
599  distanceMatrix[i][j] = sqrt( distanceMatrix[i][j] );
600  }
601  }
602  break;
603  case (NORM_ABSOLUTE_DIST):
604  for(i=0; i<M; i++){
605  for(j=0; j<N; j++){
606  distanceMatrix[i][j] = 0.0;
607  for(k=0; k< C; k++){
608  distanceMatrix[i][j] += fabs(timeSeriesA[i][k]-timeSeriesB[j][k]);
609  }
610  distanceMatrix[i][j]/=N;
611  }
612  }
613  break;
614  default:
615  errorLog<<"ERROR: Unknown distance method: "<<distanceMethod<< std::endl;
616  return -1;
617  break;
618  }
619 
620  //Run the recursive search function to build the cost matrix
621  Float distance = sqrt( d(M-1,N-1,distanceMatrix,M,N) );
622 
623  if( grt_isinf(distance) || grt_isnan(distance) ){
624  warningLog << "DTW computeDistance(...) - Distance Matrix Values are INF!" << std::endl;
625  return INFINITY;
626  }
627 
628  //cout << "DIST: " << distance << std::endl;
629 
630  //The distMatrix values are negative so make them positive
631  for(i=0; i<M; i++){
632  for(j=0; j<N; j++){
633  distanceMatrix[i][j] = fabs( distanceMatrix[i][j] );
634  }
635  }
636 
637  //Now Create the Warp Path through the cost matrix, starting at the end
638  i=M-1;
639  j=N-1;
640  totalDist = distanceMatrix[i][j];
641  warpPath.push_back( IndexDist(i,j,distanceMatrix[i][j]) );
642 
643  //Use dynamic programming to navigate through the cost matrix until [0][0] has been reached
644  normFactor = 1;
645  while( true ) {
646  if( i==0 && j==0 ) break;
647  if( i==0 ){ j--; }
648  else{
649  if( j==0 ) i--;
650  else{
651  //Find the minimum cell to move to
653  index = 0;
654  if( distanceMatrix[i-1][j] < v ){ v = distanceMatrix[i-1][j]; index = 1; }
655  if( distanceMatrix[i][j-1] < v ){ v = distanceMatrix[i][j-1]; index = 2; }
656  if( distanceMatrix[i-1][j-1] <= v ){ index = 3; }
657  switch(index){
658  case(1):
659  i--;
660  break;
661  case(2):
662  j--;
663  break;
664  case(3):
665  i--;
666  j--;
667  break;
668  default:
669  warningLog << "DTW computeDistance(...) - Could not compute a warping path for the input matrix! Dist: " << distanceMatrix[i-1][j] << " i: " << i << " j: " << j << std::endl;
670  return INFINITY;
671  break;
672  }
673  }
674  }
675  normFactor++;
676  totalDist += distanceMatrix[i][j];
677  warpPath.push_back( IndexDist(i,j,distanceMatrix[i][j]) );
678  }
679 
680  return totalDist/normFactor;
681 }
682 
683 Float DTW::d(int m,int n,MatrixFloat &distanceMatrix,const int M,const int N){
684 
685  Float dist = 0;
686  //The following is based on Matlab code by Eamonn Keogh and Michael Pazzani
687 
688  //If this cell is NAN then it has already been flagged as unreachable
689  if( grt_isnan( distanceMatrix[m][n] ) ){
690  return NAN;
691  }
692 
693  if( constrainWarpingPath ){
694  Float r = ceil( grt_min(M,N)*radius );
695  //Test to see if the current cell is outside of the warping window
696  if( fabs( n-((N-1)/((M-1)/Float(m))) ) > r ){
697  if( n-((N-1)/((M-1)/Float(m))) > 0 ){
698  for(int i=0; i<m; i++){
699  for(int j=n; j<N; j++){
700  distanceMatrix[i][j] = NAN;
701  }
702  }
703  }else{
704  for(int i=m; i<M; i++){
705  for(int j=0; j<n; j++){
706  distanceMatrix[i][j] = NAN;
707  }
708  }
709  }
710  return NAN;
711  }
712  }
713 
714  //If this cell contains a negative value then it has already been searched
715  //The cost is therefore the absolute value of the negative value so return it
716  if( distanceMatrix[m][n] < 0 ){
717  dist = fabs( distanceMatrix[m][n] );
718  return dist;
719  }
720 
721  //Case 1: A warping path has reached the end
722  //Return the contribution of distance
723  //Negate the value, to record the fact that this cell has been visited
724  //End of recursion
725  if( m == 0 && n == 0 ){
726  dist = distanceMatrix[0][0];
727  distanceMatrix[0][0] = -distanceMatrix[0][0];
728  return dist;
729  }
730 
731  //Case 2: we are somewhere in the top row of the matrix
732  //Only need to consider moving left
733  if( m == 0 ){
734  Float contribDist = d(m,n-1,distanceMatrix,M,N);
735 
736  dist = distanceMatrix[m][n] + contribDist;
737 
738  distanceMatrix[m][n] = -dist;
739  return dist;
740  }else{
741  //Case 3: we are somewhere in the left column of the matrix
742  //Only need to consider moving down
743  if ( n == 0) {
744  Float contribDist = d(m-1,n,distanceMatrix,M,N);
745 
746  dist = distanceMatrix[m][n] + contribDist;
747 
748  distanceMatrix[m][n] = -dist;
749  return dist;
750  }else{
751  //Case 4: We are somewhere away from the edges so consider moving in the three main directions
752  Float contribDist1 = d(m-1,n-1,distanceMatrix,M,N);
753  Float contribDist2 = d(m-1,n,distanceMatrix,M,N);
754  Float contribDist3 = d(m,n-1,distanceMatrix,M,N);
755  Float minValue = grt_numeric_limits< Float >::max();
756  int index = 0;
757  if( contribDist1 < minValue ){ minValue = contribDist1; index = 1; }
758  if( contribDist2 < minValue ){ minValue = contribDist2; index = 2; }
759  if( contribDist3 < minValue ){ minValue = contribDist3; index = 3; }
760 
761  switch ( index ) {
762  case 1:
763  dist = distanceMatrix[m][n] + minValue;
764  break;
765  case 2:
766  dist = distanceMatrix[m][n] + minValue;
767  break;
768  case 3:
769  dist = distanceMatrix[m][n] + minValue;
770  break;
771 
772  default:
773  break;
774  }
775 
776  distanceMatrix[m][n] = -dist; //Negate the value to record that it has been visited
777  return dist;
778  }
779  }
780 
781  //This should not happen!
782  return dist;
783 }
784 
785 inline Float DTW::MIN_(Float a,Float b, Float c){
786  Float v = a;
787  if(b<v) v = b;
788  if(c<v) v = c;
789  return v;
790 }
791 
792 
794 
795 void DTW::scaleData(TimeSeriesClassificationData &trainingData){
796 
797  //Scale the data using the min and max values
798  for(UINT i=0; i<trainingData.getNumSamples(); i++){
799  scaleData( trainingData[i].getData(), trainingData[i].getData() );
800  }
801 
802 }
803 
804 void DTW::scaleData(MatrixFloat &data,MatrixFloat &scaledData){
805 
806  const UINT R = data.getNumRows();
807  const UINT C = data.getNumCols();
808 
809  if( scaledData.getNumRows() != R || scaledData.getNumCols() != C ){
810  scaledData.resize(R, C);
811  }
812 
813  //Scale the data using the min and max values
814  for(UINT i=0; i<R; i++)
815  for(UINT j=0; j<C; j++)
816  scaledData[i][j] = grt_scale(data[i][j],ranges[j].minValue,ranges[j].maxValue,0.0,1.0);
817 
818 }
819 
820 void DTW::znormData(TimeSeriesClassificationData &trainingData){
821 
822  for(UINT i=0; i<trainingData.getNumSamples(); i++){
823  znormData( trainingData[i].getData(), trainingData[i].getData() );
824  }
825 
826 }
827 
828 void DTW::znormData(MatrixFloat &data,MatrixFloat &normData){
829 
830  const UINT R = data.getNumRows();
831  const UINT C = data.getNumCols();
832 
833  if( normData.getNumRows() != R || normData.getNumCols() != C ){
834  normData.resize(R,C);
835  }
836 
837  for(UINT j=0; j<C; j++){
838  Float mean = 0.0;
839  Float stdDev = 0.0;
840 
841  //Calculate Mean
842  for(UINT i=0; i<R; i++) mean += data[i][j];
843  mean /= Float(R);
844 
845  //Calculate Std Dev
846  for(UINT i=0; i<R; i++)
847  stdDev += grt_sqr(data[i][j]-mean);
848  stdDev = grt_sqrt( stdDev / (R - 1.0) );
849 
850  if(constrainZNorm && stdDev < 0.01){
851  //Normalize the data to 0 mean
852  for(UINT i=0; i<R; i++)
853  normData[i][j] = (data[i][j] - mean);
854  }else{
855  //Normalize the data to 0 mean and standard deviation of 1
856  for(UINT i=0; i<R; i++)
857  normData[i][j] = (data[i][j] - mean) / stdDev;
858  }
859  }
860 }
861 
862 void DTW::smoothData(VectorFloat &data,UINT smoothFactor,VectorFloat &resultsData){
863 
864  const UINT M = (UINT)data.size();
865  const UINT N = (UINT) floor(Float(M)/Float(smoothFactor));
866  resultsData.resize(N,0);
867  for(UINT i=0; i<N; i++) resultsData[i]=0.0;
868 
869  if(smoothFactor==1 || M<smoothFactor){
870  resultsData = data;
871  return;
872  }
873 
874  for(UINT i=0; i<N; i++){
875  Float mean = 0.0;
876  UINT index = i*smoothFactor;
877  for(UINT x=0; x<smoothFactor; x++){
878  mean += data[index+x];
879  }
880  resultsData[i] = mean/smoothFactor;
881  }
882  //Add on the data that does not fit into the window
883  if(M%smoothFactor!=0.0){
884  Float mean = 0.0;
885  for(UINT i=N*smoothFactor; i<M; i++) mean += data[i];
886  mean/=M-(N*smoothFactor);
887  //Add one to the end of the Vector
888  VectorFloat tempVector(N+1);
889  for(UINT i=0; i<N; i++) tempVector[i] = resultsData[i];
890  tempVector[N] = mean;
891  resultsData = tempVector;
892  }
893 
894 }
895 
896 void DTW::smoothData(MatrixFloat &data,UINT smoothFactor,MatrixFloat &resultsData){
897 
898  const UINT M = data.getNumRows();
899  const UINT C = data.getNumCols();
900  const UINT N = (UINT) floor(Float(M)/Float(smoothFactor));
901  resultsData.resize(N,C);
902 
903  if(smoothFactor==1 || M<smoothFactor){
904  resultsData = data;
905  return;
906  }
907 
908  for(UINT i=0; i<N; i++){
909  for(UINT j=0; j<C; j++){
910  Float mean = 0.0;
911  int index = i*smoothFactor;
912  for(UINT x=0; x<smoothFactor; x++){
913  mean += data[index+x][j];
914  }
915  resultsData[i][j] = mean/smoothFactor;
916  }
917  }
918 
919  //Add on the data that does not fit into the window
920  if(M%smoothFactor!=0.0){
921  VectorFloat mean(C,0.0);
922  for(UINT j=0; j<C; j++){
923  for(UINT i=N*smoothFactor; i<M; i++) mean[j] += data[i][j];
924  mean[j]/=M-(N*smoothFactor);
925  }
926 
927  //Add one row to the end of the Matrix
928  MatrixFloat tempMatrix(N+1,C);
929 
930  for(UINT i=0; i<N; i++)
931  for(UINT j=0; j<C; j++)
932  tempMatrix[i][j] = resultsData[i][j];
933 
934  for(UINT j=0; j<C; j++) tempMatrix[N][j] = mean[j];
935  resultsData = tempMatrix;
936  }
937 
938 }
939 
941 
942 bool DTW::saveModelToFile( std::fstream &file ) const{
943 
944  if(!file.is_open()){
945  errorLog << "saveModelToFile( string fileName ) - Could not open file to save data" << std::endl;
946  return false;
947  }
948 
949  file << "GRT_DTW_Model_File_V2.0" << std::endl;
950 
951  //Write the classifier settings to the file
953  errorLog <<"saveModelToFile(fstream &file) - Failed to save classifier base settings to file!" << std::endl;
954  return false;
955  }
956 
957  file << "DistanceMethod: ";
958  switch(distanceMethod){
959  case(ABSOLUTE_DIST):
960  file <<ABSOLUTE_DIST<< std::endl;
961  break;
962  case(EUCLIDEAN_DIST):
963  file <<EUCLIDEAN_DIST<< std::endl;
964  break;
965  default:
966  file <<ABSOLUTE_DIST<< std::endl;
967  break;
968  }
969  file << "UseSmoothing: "<<useSmoothing<< std::endl;
970  file << "SmoothingFactor: "<<smoothingFactor<< std::endl;
971  file << "UseZNormalisation: "<<useZNormalisation<< std::endl;
972  file << "OffsetUsingFirstSample: " << offsetUsingFirstSample << std::endl;
973  file << "ConstrainWarpingPath: " << constrainWarpingPath << std::endl;
974  file << "Radius: " << radius << std::endl;
975  file << "RejectionMode: " << rejectionMode<< std::endl;
976 
977  if( trained ){
978  file << "NumberOfTemplates: " << numTemplates << std::endl;
979  file << "OverallAverageTemplateLength: " << averageTemplateLength << std::endl;
980  //Save each template
981  for(UINT i=0; i<numTemplates; i++){
982  file << "***************TEMPLATE***************" << std::endl;
983  file << "Template: " << i+1 << std::endl;
984  file << "ClassLabel: " << templatesBuffer[i].classLabel << std::endl;
985  file << "TimeSeriesLength: " << templatesBuffer[i].timeSeries.getNumRows() << std::endl;
986  file << "TemplateThreshold: " << nullRejectionThresholds[i] << std::endl;
987  file << "TrainingMu: " << templatesBuffer[i].trainingMu << std::endl;
988  file << "TrainingSigma: " << templatesBuffer[i].trainingSigma << std::endl;
989  file << "AverageTemplateLength: " << templatesBuffer[i].averageTemplateLength << std::endl;
990  file << "TimeSeries: " << std::endl;
991  for(UINT k=0; k<templatesBuffer[i].timeSeries.getNumRows(); k++){
992  for(UINT j=0; j<templatesBuffer[i].timeSeries.getNumCols(); j++){
993  file << templatesBuffer[i].timeSeries[k][j] << "\t";
994  }
995  file << std::endl;
996  }
997  }
998  }
999 
1000  return true;
1001 }
1002 
1003 bool DTW::loadModelFromFile( std::fstream &file ){
1004 
1005  std::string word;
1006  UINT timeSeriesLength;
1007  UINT ts;
1008 
1009  if(!file.is_open())
1010  {
1011  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to open file!" << std::endl;
1012  return false;
1013  }
1014 
1015  file >> word;
1016 
1017  //Check to see if we should load a legacy file
1018  if( word == "GRT_DTW_Model_File_V1.0" ){
1019  return loadLegacyModelFromFile( file );
1020  }
1021 
1022  //Check to make sure this is a file with the DTW File Format
1023  if(word != "GRT_DTW_Model_File_V2.0"){
1024  errorLog << "loadDTWModelFromFile( string fileName ) - Unknown file header!" << std::endl;
1025  return false;
1026  }
1027 
1028  //Load the base settings from the file
1030  errorLog << "loadModelFromFile(string filename) - Failed to load base settings from file!" << std::endl;
1031  return false;
1032  }
1033 
1034  //Check and load the Distance Method
1035  file >> word;
1036  if(word != "DistanceMethod:"){
1037  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find DistanceMethod!" << std::endl;
1038  return false;
1039  }
1040  file >> distanceMethod;
1041 
1042  //Check and load if Smoothing is used
1043  file >> word;
1044  if(word != "UseSmoothing:"){
1045  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find UseSmoothing!" << std::endl;
1046  return false;
1047  }
1048  file >> useSmoothing;
1049 
1050  //Check and load what the smoothing factor is
1051  file >> word;
1052  if(word != "SmoothingFactor:"){
1053  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find SmoothingFactor!" << std::endl;
1054  return false;
1055  }
1056  file >> smoothingFactor;
1057 
1058  //Check and load if ZNormalization is used
1059  file >> word;
1060  if(word != "UseZNormalisation:"){
1061  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find UseZNormalisation!" << std::endl;
1062  return false;
1063  }
1064  file >> useZNormalisation;
1065 
1066  //Check and load if OffsetUsingFirstSample is used
1067  file >> word;
1068  if(word != "OffsetUsingFirstSample:"){
1069  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find OffsetUsingFirstSample!" << std::endl;
1070  return false;
1071  }
1072  file >> offsetUsingFirstSample;
1073 
1074  //Check and load if ConstrainWarpingPath is used
1075  file >> word;
1076  if(word != "ConstrainWarpingPath:"){
1077  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find ConstrainWarpingPath!" << std::endl;
1078  return false;
1079  }
1080  file >> constrainWarpingPath;
1081 
1082  //Check and load if ZNormalization is used
1083  file >> word;
1084  if(word != "Radius:"){
1085  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find Radius!" << std::endl;
1086  return false;
1087  }
1088  file >> radius;
1089 
1090  //Check and load if Scaling is used
1091  file >> word;
1092  if(word != "RejectionMode:"){
1093  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find RejectionMode!" << std::endl;
1094  return false;
1095  }
1096  file >> rejectionMode;
1097 
1098  if( trained ){
1099 
1100  //Check and load the Number of Templates
1101  file >> word;
1102  if(word != "NumberOfTemplates:"){
1103  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find NumberOfTemplates!" << std::endl;
1104  return false;
1105  }
1106  file >> numTemplates;
1107 
1108  //Check and load the overall average template length
1109  file >> word;
1110  if(word != "OverallAverageTemplateLength:"){
1111  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find OverallAverageTemplateLength!" << std::endl;
1112  return false;
1113  }
1114  file >> averageTemplateLength;
1115 
1116  //Clean and reset the memory
1117  templatesBuffer.resize(numTemplates);
1118  classLabels.resize(numTemplates);
1119  nullRejectionThresholds.resize(numTemplates);
1120 
1121  //Load each template
1122  for(UINT i=0; i<numTemplates; i++){
1123  //Check we have the correct template
1124  file >> word;
1125  if( word != "***************TEMPLATE***************" ){
1126  clear();
1127  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find template header!" << std::endl;
1128  return false;
1129  }
1130 
1131  //Load the template number
1132  file >> word;
1133  if(word != "Template:"){
1134  clear();
1135  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find Template Number!" << std::endl;
1136  return false;
1137  }
1138 
1139  //Check the template number
1140  file >> ts;
1141  if(ts!=i+1){
1142  clear();
1143  errorLog << "loadDTWModelFromFile( string fileName ) - Invalid Template Number: " << ts << std::endl;
1144  return false;
1145  }
1146 
1147  //Get the class label of this template
1148  file >> word;
1149  if(word != "ClassLabel:"){
1150  clear();
1151  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find ClassLabel!" << std::endl;
1152  return false;
1153  }
1154  file >> templatesBuffer[i].classLabel;
1155  classLabels[i] = templatesBuffer[i].classLabel;
1156 
1157  //Get the time series length
1158  file >> word;
1159  if(word != "TimeSeriesLength:"){
1160  clear();
1161  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find TimeSeriesLength!" << std::endl;
1162  return false;
1163  }
1164  file >> timeSeriesLength;
1165 
1166  //Resize the buffers
1167  templatesBuffer[i].timeSeries.resize(timeSeriesLength,numInputDimensions);
1168 
1169  //Get the template threshold
1170  file >> word;
1171  if(word != "TemplateThreshold:"){
1172  clear();
1173  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find TemplateThreshold!" << std::endl;
1174  return false;
1175  }
1176  file >> nullRejectionThresholds[i];
1177 
1178  //Get the mu values
1179  file >> word;
1180  if(word != "TrainingMu:"){
1181  clear();
1182  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find TrainingMu!" << std::endl;
1183  return false;
1184  }
1185  file >> templatesBuffer[i].trainingMu;
1186 
1187  //Get the sigma values
1188  file >> word;
1189  if(word != "TrainingSigma:"){
1190  clear();
1191  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find TrainingSigma!" << std::endl;
1192  return false;
1193  }
1194  file >> templatesBuffer[i].trainingSigma;
1195 
1196  //Get the AverageTemplateLength value
1197  file >> word;
1198  if(word != "AverageTemplateLength:"){
1199  clear();
1200  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find AverageTemplateLength!" << std::endl;
1201  return false;
1202  }
1203  file >> templatesBuffer[i].averageTemplateLength;
1204 
1205  //Get the data
1206  file >> word;
1207  if(word != "TimeSeries:"){
1208  clear();
1209  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find template timeseries!" << std::endl;
1210  return false;
1211  }
1212  for(UINT k=0; k<timeSeriesLength; k++)
1213  for(UINT j=0; j<numInputDimensions; j++)
1214  file >> templatesBuffer[i].timeSeries[k][j];
1215  }
1216 
1217  //Resize the prediction results to make sure it is setup for realtime prediction
1218  continuousInputDataBuffer.clear();
1219  continuousInputDataBuffer.resize(averageTemplateLength,VectorFloat(numInputDimensions,0));
1220  maxLikelihood = DEFAULT_NULL_LIKELIHOOD_VALUE;
1221  bestDistance = DEFAULT_NULL_DISTANCE_VALUE;
1222  classLikelihoods.resize(numClasses,DEFAULT_NULL_LIKELIHOOD_VALUE);
1223  classDistances.resize(numClasses,DEFAULT_NULL_DISTANCE_VALUE);
1224  }
1225 
1226  return true;
1227 }
1228 bool DTW::setRejectionMode(UINT rejectionMode){
1229  if( rejectionMode == TEMPLATE_THRESHOLDS || rejectionMode == CLASS_LIKELIHOODS || rejectionMode == THRESHOLDS_AND_LIKELIHOODS ){
1230  this->rejectionMode = rejectionMode;
1231  return true;
1232  }
1233  return false;
1234 }
1235 
1236 bool DTW::setNullRejectionThreshold(Float nullRejectionLikelihoodThreshold)
1237 {
1238  this->nullRejectionLikelihoodThreshold = nullRejectionLikelihoodThreshold;
1239  return true;
1240 }
1241 
1242 bool DTW::setOffsetTimeseriesUsingFirstSample(bool offsetUsingFirstSample){
1243  this->offsetUsingFirstSample = offsetUsingFirstSample;
1244  return true;
1245 }
1246 
1247 bool DTW::setContrainWarpingPath(bool constrain){
1248  this->constrainWarpingPath = constrain;
1249  return true;
1250 }
1251 
1252 bool DTW::setWarpingRadius(Float radius){
1253  this->radius = radius;
1254  return true;
1255 }
1256 
1257 bool DTW::enableZNormalization(bool useZNormalisation,bool constrainZNorm){
1258  this->useZNormalisation = useZNormalisation;
1259  this->constrainZNorm = constrainZNorm;
1260  return true;
1261 }
1262 
1263 bool DTW::enableTrimTrainingData(bool trimTrainingData,Float trimThreshold,Float maximumTrimPercentage){
1264 
1265  if( trimThreshold < 0 || trimThreshold > 1 ){
1266  warningLog << "Failed to set trimTrainingData. The trimThreshold must be in the range of [0 1]" << std::endl;
1267  return false;
1268  }
1269  if( maximumTrimPercentage < 0 || maximumTrimPercentage > 100 ){
1270  warningLog << "Failed to set trimTrainingData. The maximumTrimPercentage must be a valid percentage in the range of [0 100]" << std::endl;
1271  return false;
1272  }
1273 
1274  this->trimTrainingData = trimTrainingData;
1275  this->trimThreshold = trimThreshold;
1276  this->maximumTrimPercentage = maximumTrimPercentage;
1277  return true;
1278 }
1279 
1280 void DTW::offsetTimeseries(MatrixFloat &timeseries){
1281  VectorFloat firstRow = timeseries.getRow(0);
1282  for(UINT i=0; i<timeseries.getNumRows(); i++){
1283  for(UINT j=0; j<timeseries.getNumCols(); j++){
1284  timeseries[i][j] -= firstRow[j];
1285  }
1286  }
1287 }
1288 
1289 bool DTW::loadLegacyModelFromFile( std::fstream &file ){
1290 
1291  std::string word;
1292  UINT timeSeriesLength;
1293  UINT ts;
1294 
1295  //Check and load the Number of Dimensions
1296  file >> word;
1297  if(word != "NumberOfDimensions:"){
1298  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find NumberOfDimensions!" << std::endl;
1299  return false;
1300  }
1301  file >> numInputDimensions;
1302 
1303  //Check and load the Number of Classes
1304  file >> word;
1305  if(word != "NumberOfClasses:"){
1306  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find NumberOfClasses!" << std::endl;
1307  return false;
1308  }
1309  file >> numClasses;
1310 
1311  //Check and load the Number of Templates
1312  file >> word;
1313  if(word != "NumberOfTemplates:"){
1314  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find NumberOfTemplates!" << std::endl;
1315  return false;
1316  }
1317  file >> numTemplates;
1318 
1319  //Check and load the Distance Method
1320  file >> word;
1321  if(word != "DistanceMethod:"){
1322  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find DistanceMethod!" << std::endl;
1323  return false;
1324  }
1325  file >> distanceMethod;
1326 
1327  //Check and load if UseNullRejection is used
1328  file >> word;
1329  if(word != "UseNullRejection:"){
1330  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find UseNullRejection!" << std::endl;
1331  return false;
1332  }
1333  file >> useNullRejection;
1334 
1335  //Check and load if Smoothing is used
1336  file >> word;
1337  if(word != "UseSmoothing:"){
1338  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find UseSmoothing!" << std::endl;
1339  return false;
1340  }
1341  file >> useSmoothing;
1342 
1343  //Check and load what the smoothing factor is
1344  file >> word;
1345  if(word != "SmoothingFactor:"){
1346  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find SmoothingFactor!" << std::endl;
1347  return false;
1348  }
1349  file >> smoothingFactor;
1350 
1351  //Check and load if Scaling is used
1352  file >> word;
1353  if(word != "UseScaling:"){
1354  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find UseScaling!" << std::endl;
1355  return false;
1356  }
1357  file >> useScaling;
1358 
1359  //Check and load if ZNormalization is used
1360  file >> word;
1361  if(word != "UseZNormalisation:"){
1362  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find UseZNormalisation!" << std::endl;
1363  return false;
1364  }
1365  file >> useZNormalisation;
1366 
1367  //Check and load if OffsetUsingFirstSample is used
1368  file >> word;
1369  if(word != "OffsetUsingFirstSample:"){
1370  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find OffsetUsingFirstSample!" << std::endl;
1371  return false;
1372  }
1373  file >> offsetUsingFirstSample;
1374 
1375  //Check and load if ConstrainWarpingPath is used
1376  file >> word;
1377  if(word != "ConstrainWarpingPath:"){
1378  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find ConstrainWarpingPath!" << std::endl;
1379  return false;
1380  }
1381  file >> constrainWarpingPath;
1382 
1383  //Check and load if ZNormalization is used
1384  file >> word;
1385  if(word != "Radius:"){
1386  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find Radius!" << std::endl;
1387  return false;
1388  }
1389  file >> radius;
1390 
1391  //Check and load if Scaling is used
1392  file >> word;
1393  if(word != "RejectionMode:"){
1394  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find RejectionMode!" << std::endl;
1395  return false;
1396  }
1397  file >> rejectionMode;
1398 
1399  //Check and load gamma
1400  file >> word;
1401  if(word != "NullRejectionCoeff:"){
1402  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find NullRejectionCoeff!" << std::endl;
1403  return false;
1404  }
1405  file >> nullRejectionCoeff;
1406 
1407  //Check and load the overall average template length
1408  file >> word;
1409  if(word != "OverallAverageTemplateLength:"){
1410  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find OverallAverageTemplateLength!" << std::endl;
1411  return false;
1412  }
1413  file >> averageTemplateLength;
1414 
1415  //Clean and reset the memory
1416  templatesBuffer.resize(numTemplates);
1417  classLabels.resize(numTemplates);
1418  nullRejectionThresholds.resize(numTemplates);
1419 
1420  //Load each template
1421  for(UINT i=0; i<numTemplates; i++){
1422  //Check we have the correct template
1423  file >> word;
1424  while(word != "Template:"){
1425  file >> word;
1426  }
1427  file >> ts;
1428 
1429  //Check the template number
1430  if(ts!=i+1){
1431  numTemplates=0;
1432  trained = false;
1433  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find Invalid Template Number!" << std::endl;
1434  return false;
1435  }
1436 
1437  //Get the class label of this template
1438  file >> word;
1439  if(word != "ClassLabel:"){
1440  numTemplates=0;
1441  trained = false;
1442  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find ClassLabel!" << std::endl;
1443  return false;
1444  }
1445  file >> templatesBuffer[i].classLabel;
1446  classLabels[i] = templatesBuffer[i].classLabel;
1447 
1448  //Get the time series length
1449  file >> word;
1450  if(word != "TimeSeriesLength:"){
1451  numTemplates=0;
1452  trained = false;
1453  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find TimeSeriesLength!" << std::endl;
1454  return false;
1455  }
1456  file >> timeSeriesLength;
1457 
1458  //Resize the buffers
1459  templatesBuffer[i].timeSeries.resize(timeSeriesLength,numInputDimensions);
1460 
1461  //Get the template threshold
1462  file >> word;
1463  if(word != "TemplateThreshold:"){
1464  numTemplates=0;
1465  trained = false;
1466  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find TemplateThreshold!" << std::endl;
1467  return false;
1468  }
1469  file >> nullRejectionThresholds[i];
1470 
1471  //Get the mu values
1472  file >> word;
1473  if(word != "TrainingMu:"){
1474  numTemplates=0;
1475  trained = false;
1476  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find TrainingMu!" << std::endl;
1477  return false;
1478  }
1479  file >> templatesBuffer[i].trainingMu;
1480 
1481  //Get the sigma values
1482  file >> word;
1483  if(word != "TrainingSigma:"){
1484  numTemplates=0;
1485  trained = false;
1486  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find TrainingSigma!" << std::endl;
1487  return false;
1488  }
1489  file >> templatesBuffer[i].trainingSigma;
1490 
1491  //Get the AverageTemplateLength value
1492  file >> word;
1493  if(word != "AverageTemplateLength:"){
1494  numTemplates=0;
1495  trained = false;
1496  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find AverageTemplateLength!" << std::endl;
1497  return false;
1498  }
1499  file >> templatesBuffer[i].averageTemplateLength;
1500 
1501  //Get the data
1502  file >> word;
1503  if(word != "TimeSeries:"){
1504  numTemplates=0;
1505  trained = false;
1506  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find template timeseries!" << std::endl;
1507  return false;
1508  }
1509  for(UINT k=0; k<timeSeriesLength; k++)
1510  for(UINT j=0; j<numInputDimensions; j++)
1511  file >> templatesBuffer[i].timeSeries[k][j];
1512 
1513  //Check for the footer
1514  file >> word;
1515  if(word != "***************************"){
1516  numTemplates=0;
1517  numClasses = 0;
1518  numInputDimensions=0;
1519  trained = false;
1520  errorLog << "loadDTWModelFromFile( string fileName ) - Failed to find template footer!" << std::endl;
1521  return false;
1522  }
1523  }
1524 
1525  //Resize the prediction results to make sure it is setup for realtime prediction
1526  continuousInputDataBuffer.clear();
1527  continuousInputDataBuffer.resize(averageTemplateLength,VectorFloat(numInputDimensions,0));
1528  maxLikelihood = DEFAULT_NULL_LIKELIHOOD_VALUE;
1529  bestDistance = DEFAULT_NULL_DISTANCE_VALUE;
1530  classLikelihoods.resize(numClasses,DEFAULT_NULL_LIKELIHOOD_VALUE);
1531  classDistances.resize(numClasses,DEFAULT_NULL_DISTANCE_VALUE);
1532 
1533  trained = true;
1534 
1535  return true;
1536 }
1537 
1538 GRT_END_NAMESPACE
virtual bool predict_(VectorFloat &inputVector)
Definition: DTW.cpp:474
bool saveBaseSettingsToFile(std::fstream &file) const
Definition: Classifier.cpp:255
bool push_back(const T &value)
virtual bool predict(VectorFloat inputVector)
Definition: MLBase.cpp:112
#define DEFAULT_NULL_LIKELIHOOD_VALUE
Definition: Classifier.h:38
virtual bool loadModelFromFile(std::fstream &file)
Definition: DTW.cpp:1003
std::string getClassifierType() const
Definition: Classifier.cpp:160
bool setRejectionMode(UINT rejectionMode)
Definition: DTW.cpp:1228
bool setNumDimensions(const UINT numDimensions)
virtual bool train_(TimeSeriesClassificationData &trainingData)
Definition: DTW.cpp:139
virtual bool resize(const unsigned int size)
Definition: Vector.h:133
bool enableTrimTrainingData(bool trimTrainingData, Float trimThreshold, Float maximumTrimPercentage)
Definition: DTW.cpp:1263
bool setContrainWarpingPath(bool constrain)
Definition: DTW.cpp:1247
bool enableZNormalization(bool useZNormalization, bool constrainZNorm=true)
Definition: DTW.cpp:1257
bool setWarpingRadius(Float radius)
Definition: DTW.cpp:1252
DTW & operator=(const DTW &rhs)
Definition: DTW.cpp:73
bool setModels(Vector< DTWTemplate > newTemplates)
Definition: DTW.cpp:551
Vector< ClassTracker > getClassTracker() const
Definition: DTW.h:91
unsigned int getNumValuesInBuffer() const
bool copyBaseVariables(const Classifier *classifier)
Definition: Classifier.cpp:92
bool loadBaseSettingsFromFile(std::fstream &file)
Definition: Classifier.cpp:302
bool setNullRejectionThreshold(Float nullRejectionLikelihoodThreshold)
Definition: DTW.cpp:1236
This class implements Dynamic Time Warping. Dynamic Time Warping (DTW) is a powerful classifier that ...
unsigned int getNumRows() const
Definition: Matrix.h:542
virtual ~DTW(void)
Definition: DTW.cpp:69
unsigned int getNumCols() const
Definition: Matrix.h:549
bool addSample(const UINT classLabel, const MatrixFloat &trainingSample)
virtual bool recomputeNullRejectionThresholds()
Definition: DTW.cpp:536
VectorFloat getRow(const unsigned int r) const
Definition: MatrixFloat.h:100
TimeSeriesClassificationData getClassData(const UINT classLabel) const
DTW(bool useScaling=false, bool useNullRejection=false, Float nullRejectionCoeff=3.0, UINT rejectionMode=DTW::TEMPLATE_THRESHOLDS, bool dtwConstrain=true, Float radius=0.2, bool offsetUsingFirstSample=false, bool useSmoothing=false, UINT smoothingFactor=5, Float nullRejectionLikelihoodThreshold=0.99)
Definition: DTW.cpp:28
virtual bool deepCopyFrom(const Classifier *classifier)
Definition: DTW.cpp:104
virtual bool reset()
Definition: DTW.cpp:513
virtual bool resize(const unsigned int r, const unsigned int c)
Definition: Matrix.h:232
bool trimTimeSeries(TimeSeriesClassificationSample &timeSeries)
Definition: DTW.h:51
Definition: Vector.h:41
virtual bool clear()
Definition: DTW.cpp:522
bool setOffsetTimeseriesUsingFirstSample(bool offsetUsingFirstSample)
Definition: DTW.cpp:1242
virtual bool clear()
Definition: Classifier.cpp:141
unsigned int getSize() const
bool resize(const unsigned int newBufferSize)
virtual bool saveModelToFile(std::fstream &file) const
Definition: DTW.cpp:942