/* * RapidMiner * * Copyright (C) 2001-2008 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.operator.similarity.attributebased; import java.util.ArrayList; import com.rapidminer.example.ExampleSet; import com.rapidminer.example.Tools; import com.rapidminer.operator.OperatorException; /** * A similarity measure based on "Dynamic Time Warping". The DTW distance is mapped to a similarity measure using f(x)= 1 - (x / (1 + x)). Feature * weights are also supported. * * @author Piotr Kasprzak * @version $Id: DTWSimilarity.java,v 1.6 2008/09/12 10:30:47 tobiasmalbrecht Exp $ */ public class DTWSimilarity extends AbstractRealValueBasedSimilarity { private static final long serialVersionUID = 1382144431606583122L; protected double pointDistance(int i, int j, double[] ts1, double[] ts2) { double diff = ts1[i] - ts2[j]; return (diff * diff); } protected double distance2Similarity(double x) { return (1.0 - (x / (1 + x))); } public double similarity(double[] e1, double[] e2) { ArrayList<Double> l1 = new ArrayList<Double>(); ArrayList<Double> l2 = new ArrayList<Double>(); int i, j; /** Filter NaNs */ for (i = 0; i < e1.length; i++) { double value = e1[i]; if (!Double.isNaN(value)) { l1.add(value); } } for (i = 0; i < e2.length; i++) { double value = e2[i]; if (!Double.isNaN(value)) { l2.add(value); } } /** Transform the examples to vectors */ double[] ts1 = new double[l1.size()]; double[] ts2 = new double[l2.size()]; for (i = 0; i < ts1.length; i++) { ts1[i] = l1.get(i); } for (i = 0; i < ts2.length; i++) { ts2[i] = l2.get(i); } /** Build a point-to-point distance matrix */ double[][] dP2P = new double[ts1.length][ts2.length]; for (i = 0; i < ts1.length; i++) { for (j = 0; j < ts2.length; j++) { dP2P[i][j] = pointDistance(i, j, ts1, ts2); } } /** Check for some special cases due to ultra short time series */ if (ts1.length == 0 || ts2.length == 0) { return Double.NaN; } if (ts1.length == 1 && ts2.length == 1) { return distance2Similarity(Math.sqrt(dP2P[0][0])); } /** * Build the optimal distance matrix using a dynamic programming approach */ double[][] D = new double[ts1.length][ts2.length]; D[0][0] = dP2P[0][0]; // Starting point for (i = 1; i < ts1.length; i++) { // Fill the first column of our // distance matrix with optimal // values D[i][0] = dP2P[i][0] + D[i - 1][0]; } if (ts2.length == 1) { // TS2 is a point double sum = 0; for (i = 0; i < ts1.length; i++) { sum += D[i][0]; } return distance2Similarity(Math.sqrt(sum) / ts1.length); } for (j = 1; j < ts2.length; j++) { // Fill the first row of our // distance matrix with optimal // values D[0][j] = dP2P[0][j] + D[0][j - 1]; } if (ts1.length == 1) { // TS1 is a point double sum = 0; for (j = 0; j < ts2.length; j++) { sum += D[0][j]; } return distance2Similarity(Math.sqrt(sum) / ts2.length); } for (i = 1; i < ts1.length; i++) { // Fill the rest for (j = 1; j < ts2.length; j++) { double[] steps = { D[i - 1][j - 1], D[i - 1][j], D[i][j - 1] }; double min = Math.min(steps[0], Math.min(steps[1], steps[2])); D[i][j] = dP2P[i][j] + min; } } /** * Calculate the distance between the two time series through optimal alignment. */ i = ts1.length - 1; j = ts2.length - 1; int k = 1; double dist = D[i][j]; while (i + j > 2) { if (i == 0) { j--; } else if (j == 0) { i--; } else { double[] steps = { D[i - 1][j - 1], D[i - 1][j], D[i][j - 1] }; double min = Math.min(steps[0], Math.min(steps[1], steps[2])); if (min == steps[0]) { i--; j--; } else if (min == steps[1]) { i--; } else if (min == steps[2]) { j--; } } k++; dist += D[i][j]; } return distance2Similarity(Math.sqrt(dist) / k); } public void init(ExampleSet es) throws OperatorException { Tools.onlyNumericalAttributes(es, "DTW similarity"); super.init(es); } public boolean isDistance() { return false; } }