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