def soft_dtw(ts1, ts2, gamma=1.): r"""Compute Soft-DTW metric between two time series. Soft-DTW was originally presented in [1]_ and is discussed in more details in our :ref:`user-guide page on DTW and its variants<dtw>`. Soft-DTW is computed as: .. math:: \text{soft-DTW}_{\gamma}(X, Y) = \min_{\pi}{}^\gamma \sum_{(i, j) \in \pi} \|X_i, Y_j\|^2 where :math:`\min^\gamma` is the soft-min operator of parameter :math:`\gamma`. In the limit case :math:`\gamma = 0`, :math:`\min^\gamma` reduces to a hard-min operator and soft-DTW is defined as the square of the DTW similarity measure. Parameters ---------- ts1 A time series ts2 Another time series gamma : float (default 1.) Gamma paraneter for Soft-DTW Returns ------- float Similarity Examples -------- >>> soft_dtw([1, 2, 2, 3], ... [1., 2., 3., 4.], ... gamma=1.) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS -0.89... >>> soft_dtw([1, 2, 3, 3], ... [1., 2., 2.1, 3.2], ... gamma=0.01) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS 0.089... See Also -------- cdist_soft_dtw : Cross similarity matrix between time series datasets References ---------- .. [1] M. Cuturi, M. Blondel "Soft-DTW: a Differentiable Loss Function for Time-Series," ICML 2017. """ if gamma == 0.: return dtw(ts1, ts2)**2 return SoftDTW(SquaredEuclidean(ts1[:ts_size(ts1)], ts2[:ts_size(ts2)]), gamma=gamma).compute()
def _fit_one_init(self, X, x_squared_norms, rs): n_ts, _, d = X.shape sz = min([ts_size(ts) for ts in X]) if hasattr(self.init, '__array__'): self.cluster_centers_ = self.init.copy() elif self.init == "k-means++": self.cluster_centers_ = _k_init(X[:, :sz, :].reshape( (n_ts, -1)), self.n_clusters, x_squared_norms, rs).reshape( (-1, sz, d)) elif self.init == "random": indices = rs.choice(X.shape[0], self.n_clusters) self.cluster_centers_ = X[indices].copy() else: raise ValueError("Value %r for parameter 'init' is invalid" % self.init) self.cluster_centers_ = _check_full_length(self.cluster_centers_) old_inertia = numpy.inf for it in range(self.max_iter): self._assign(X) if self.verbose: print("%.3f" % self.inertia_, end=" --> ") self._update_centroids(X) if numpy.abs(old_inertia - self.inertia_) < self.tol: break old_inertia = self.inertia_ if self.verbose: print("") return self
def _mm_update_barycenter(X, diag_sum_v_k, list_w_k): """Update barycenters using the formula from Algorithm 2 in [1]_. Parameters ---------- X : numpy.array of shape (n, sz, d) Time-series to be averaged diag_sum_v_k : numpy.array of shape (barycenter_size, ) sum of weighted :math:`V^{(k)}` diagonals (as a vector) list_w_k : list of numpy.array of shape (barycenter_size, sz_k) list of weighted :math:`W^{(k)}` matrices Returns ------- numpy.array of shape (barycenter_size, d) Updated barycenter References ---------- .. [1] D. Schultz and B. Jain. Nonsmooth Analysis and Subgradient Methods for Averaging in Dynamic Time Warping Spaces. Pattern Recognition, 74, 340-358. """ d = X.shape[2] barycenter_size = diag_sum_v_k.shape[0] sum_w_x = numpy.zeros((barycenter_size, d)) for k, (w_k, x_k) in enumerate(zip(list_w_k, X)): sum_w_x += w_k.dot(x_k[:ts_size(x_k)]) barycenter = numpy.diag(1. / diag_sum_v_k).dot(sum_w_x) return barycenter
def _check_full_length(centroids): """Check that provided centroids are full-length (ie. not padded with nans). If some centroids are found to be padded with nans, the last value is repeated until the end. Examples -------- >>> centroids = to_time_series_dataset([[1, 2, 3], [1, 2, 3, 4, 5]]) >>> _check_full_length(centroids) array([[[ 1.], [ 2.], [ 3.], [ 3.], [ 3.]], <BLANKLINE> [[ 1.], [ 2.], [ 3.], [ 4.], [ 5.]]]) """ centroids_ = numpy.empty(centroids.shape) n, max_sz = centroids.shape[:2] for i in range(n): sz = ts_size(centroids[i]) centroids_[i, :sz] = centroids[i, :sz] if sz < max_sz: centroids_[i, sz:] = centroids[i, sz - 1] return centroids_
def fit_transform(self, X, **kwargs): """Fit to data, then transform it. Parameters ---------- X : array-like Time series dataset to be resampled. Returns ------- numpy.ndarray Resampled time series dataset. """ X_ = to_time_series_dataset(X) n_ts, sz, d = X_.shape equal_size = check_equal_size(X_) X_out = numpy.empty((n_ts, self.sz_, d)) for i in range(X_.shape[0]): xnew = numpy.linspace(0, 1, self.sz_) if not equal_size: sz = ts_size(X_[i]) for di in range(d): f = interp1d(numpy.linspace(0, 1, sz), X_[i, :sz, di], kind="slinear") X_out[i, :, di] = f(xnew) return X_out
def _check_series_length(self, X): """Ensures that time series in X matches the following requirements: - their length is greater than the size of the longest shapelet - (at predict time) their length is lower than the maximum allowed length, as set by self.max_size """ sizes = numpy.array([ts_size(Xi) for Xi in X]) self._min_sz_fit = sizes.min() if self.n_shapelets_per_size is not None: max_sz_shp = max(self.n_shapelets_per_size.keys()) if max_sz_shp > self._min_sz_fit: raise ValueError("Sizes in X do not match maximum " "shapelet size: there is at least one " "series in X that is shorter than one of the " "shapelets. Shortest time series is of " "length {} and longest shapelet is of length " "{}".format(self._min_sz_fit, max_sz_shp)) if hasattr(self, 'model_') or self.max_size is not None: # Model is already fitted max_sz_X = sizes.max() if hasattr(self, 'model_'): max_size = self._X_fit_dims[1] else: max_size = self.max_size if max_size < max_sz_X: raise ValueError("Sizes in X do not match maximum allowed " "size as set by max_size. " "Longest time series is of " "length {} and max_size is " "{}".format(max_sz_X, max_size))
def soft_dtw(ts1, ts2, gamma=1.): r"""Compute Soft-DTW metric between two time series. Soft-DTW was originally presented in [1]_. Parameters ---------- ts1 A time series ts2 Another time series gamma : float (default 1.) Gamma paraneter for Soft-DTW Returns ------- float Similarity Examples -------- >>> soft_dtw([1, 2, 2, 3], ... [1., 2., 3., 4.], ... gamma=1.) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS -0.89... >>> soft_dtw([1, 2, 3, 3], ... [1., 2., 2.1, 3.2], ... gamma=0.01) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS 0.089... See Also -------- cdist_soft_dtw : Cross similarity matrix between time series datasets References ---------- .. [1] M. Cuturi, M. Blondel "Soft-DTW: a Differentiable Loss Function for Time-Series," ICML 2017. """ if gamma == 0.: return dtw(ts1, ts2) return SoftDTW(SquaredEuclidean(ts1[:ts_size(ts1)], ts2[:ts_size(ts2)]), gamma=gamma).compute()
def _transform(self, X, y=None): n_ts, sz, d = X.shape X_transformed = numpy.empty((n_ts, self.n_segments, d)) for i_ts in range(n_ts): sz_segment = ts_size(X[i_ts]) // self.n_segments for i_seg in range(self.n_segments): start = i_seg * sz_segment end = start + sz_segment segment = X[i_ts, start:end, :] X_transformed[i_ts, i_seg, :] = segment.mean(axis=0) return X_transformed
def sigma_gak(dataset, n_samples=100, random_state=None): r"""Compute sigma value to be used for GAK. This method was originally presented in [1]_. Parameters ---------- dataset A dataset of time series n_samples : int (default: 100) Number of samples on which median distance should be estimated random_state : integer or numpy.RandomState or None (default: None) The generator used to draw the samples. If an integer is given, it fixes the seed. Defaults to the global numpy random number generator. Returns ------- float Suggested bandwidth (:math:`\\sigma`) for the Global Alignment kernel Examples -------- >>> dataset = [[1, 2, 2, 3], [1., 2., 3., 4.]] >>> sigma_gak(dataset=dataset, ... n_samples=200, ... random_state=0) # doctest: +ELLIPSIS 2.0... See Also -------- gak : Compute Global Alignment kernel cdist_gak : Compute cross-similarity matrix using Global Alignment kernel References ---------- .. [1] M. Cuturi, "Fast global alignment kernels," ICML 2011. """ random_state = check_random_state(random_state) dataset = to_time_series_dataset(dataset) n_ts, sz, d = dataset.shape if not check_equal_size(dataset): sz = numpy.min([ts_size(ts) for ts in dataset]) if n_ts * sz < n_samples: replace = True else: replace = False sample_indices = random_state.choice(n_ts * sz, size=n_samples, replace=replace) dists = pdist(dataset[:, :sz, :].reshape((-1, d))[sample_indices], metric="euclidean") return numpy.median(dists) * numpy.sqrt(sz)
def _kmeans_init_shapelets(X, n_shapelets, shp_len, n_draw=10000): n_ts, sz, d = X.shape indices_ts = numpy.random.choice(n_ts, size=n_draw, replace=True) indices_time = numpy.array( [numpy.random.choice(ts_size(ts) - shp_len + 1, size=1)[0] for ts in X[indices_ts]] ) subseries = numpy.zeros((n_draw, shp_len, d)) for i in range(n_draw): subseries[i] = X[indices_ts[i], indices_time[i]:indices_time[i] + shp_len] return TimeSeriesKMeans(n_clusters=n_shapelets, metric="euclidean", verbose=False).fit(subseries).cluster_centers_
def VisualizeShapelets(self): ''' visualize all of shapelets learned by shapelet classifier ''' plt.figure() for i, sz in enumerate(self.shapelet_sizes.keys()): plt.subplot(len(self.shapelet_sizes), 1, i + 1) plt.title("%d shapelets of size %d" % (self.shapelet_sizes[sz], sz)) for shapelet in self.shapelet_clf.shapelets_: if ts_size(shapelet) == sz: plt.plot(shapelet.ravel()) plt.xlim([0, max(self.shapelet_sizes.keys())]) plt.tight_layout() plt.show()
def _check_full_length(centroids): """Check that provided centroids are full-length (ie. not padded with nans). If some centroids are found to be padded with nans, the last value is repeated until the end. """ centroids_ = numpy.empty(centroids.shape) n, max_sz = centroids.shape[:2] for i in range(n): sz = ts_size(centroids[i]) centroids_[i, :sz] = centroids[i, :sz] if sz < max_sz: centroids_[i, sz:] = centroids[i, sz - 1] return centroids_
def _subgradient_update_barycenter(X, list_diag_v_k, list_w_k, weights_sum, barycenter, eta): """Update barycenters using the formula from Algorithm 1 in [1]_. Parameters ---------- X : numpy.array of shape (n, sz, d) Time-series to be averaged list_diag_v_k : list of numpy.array of shape (barycenter_size, ) list of weighted :math:`V^{(k)}` diagonals (as vectors) list_w_k : list of numpy.array of shape (barycenter_size, sz_k) list of weighted :math:`W^{(k)}` matrices weights_sum : float sum of weights applied to matrices :math:`V^{(k)}` and :math:`W^{(k)}` barycenter : numpy.array of shape (barycenter_size, d) Barycenter as computed at the previous iteration of the algorithm eta : float Step-size for the subgradient descent algorithm Returns ------- numpy.array of shape (barycenter_size, d) Updated barycenter References ---------- .. [1] D. Schultz and B. Jain. Nonsmooth Analysis and Subgradient Methods for Averaging in Dynamic Time Warping Spaces. Pattern Recognition, 74, 340-358. """ d = X.shape[2] barycenter_size = barycenter.shape[0] delta_bar = numpy.zeros((barycenter_size, d)) for k, (v_k, w_k, x_k) in enumerate(zip(list_diag_v_k, list_w_k, X)): delta_bar += v_k.reshape((-1, 1)) * barycenter delta_bar -= w_k.dot(x_k[:ts_size(x_k)]) barycenter -= (2. * eta / weights_sum) * delta_bar return barycenter
def _fit_one_init(self, X, x_squared_norms, rs): n_ts, _, d = X.shape sz = min([ts_size(ts) for ts in X]) self.cluster_centers_ = _k_init(X[:, :sz, :].reshape((n_ts, -1)), self.n_clusters, x_squared_norms, rs).reshape((-1, sz, d)) old_inertia = numpy.inf for it in range(self.max_iter): self._assign(X) if self.verbose: print("%.3f" % self.inertia_, end=" --> ") self._update_centroids(X) if numpy.abs(old_inertia - self.inertia_) < self.tol: break old_inertia = self.inertia_ if self.verbose: print("") return self
def cdist_soft_dtw(dataset1, dataset2=None, gamma=1.): r"""Compute cross-similarity matrix using Soft-DTW metric. Soft-DTW was originally presented in [1]_ and is discussed in more details in our :ref:`user-guide page on DTW and its variants<dtw>`. Soft-DTW is computed as: .. math:: \text{soft-DTW}_{\gamma}(X, Y) = \min_{\pi}{}^\gamma \sum_{(i, j) \in \pi} \|X_i, Y_j\|^2 where :math:`\min^\gamma` is the soft-min operator of parameter :math:`\gamma`. In the limit case :math:`\gamma = 0`, :math:`\min^\gamma` reduces to a hard-min operator and soft-DTW is defined as the square of the DTW similarity measure. Parameters ---------- dataset1 A dataset of time series dataset2 Another dataset of time series gamma : float (default 1.) Gamma paraneter for Soft-DTW Returns ------- numpy.ndarray Cross-similarity matrix Examples -------- >>> cdist_soft_dtw([[1, 2, 2, 3], [1., 2., 3., 4.]], gamma=.01) array([[-0.01098612, 1. ], [ 1. , 0. ]]) >>> cdist_soft_dtw([[1, 2, 2, 3], [1., 2., 3., 4.]], ... [[1, 2, 2, 3], [1., 2., 3., 4.]], gamma=.01) array([[-0.01098612, 1. ], [ 1. , 0. ]]) See Also -------- soft_dtw : Compute Soft-DTW cdist_soft_dtw_normalized : Cross similarity matrix between time series datasets using a normalized version of Soft-DTW References ---------- .. [1] M. Cuturi, M. Blondel "Soft-DTW: a Differentiable Loss Function for Time-Series," ICML 2017. """ dataset1 = to_time_series_dataset(dataset1, dtype=numpy.float64) self_similarity = False if dataset2 is None: dataset2 = dataset1 self_similarity = True else: dataset2 = to_time_series_dataset(dataset2, dtype=numpy.float64) dists = numpy.empty((dataset1.shape[0], dataset2.shape[0])) equal_size_ds1 = check_equal_size(dataset1) equal_size_ds2 = check_equal_size(dataset2) for i, ts1 in enumerate(dataset1): if equal_size_ds1: ts1_short = ts1 else: ts1_short = ts1[:ts_size(ts1)] for j, ts2 in enumerate(dataset2): if equal_size_ds2: ts2_short = ts2 else: ts2_short = ts2[:ts_size(ts2)] if self_similarity and j < i: dists[i, j] = dists[j, i] else: dists[i, j] = soft_dtw(ts1_short, ts2_short, gamma=gamma) return dists
def cdist_soft_dtw(dataset1, dataset2=None, gamma=1.): """Compute cross-similarity matrix using Soft-DTW metric. Soft-DTW was originally presented in [1]_. Parameters ---------- dataset1 A dataset of time series dataset2 Another dataset of time series gamma : float (default 1.) Gamma paraneter for Soft-DTW Returns ------- numpy.ndarray Cross-similarity matrix Examples -------- >>> cdist_soft_dtw([[1, 2, 2, 3], [1., 2., 3., 4.]], gamma=.01) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS array([[-0.01..., 1. ], [ 1. , 0. ]]) >>> cdist_soft_dtw([[1, 2, 2, 3], [1., 2., 3., 4.]], [[1, 2, 2, 3], [1., 2., 3., 4.]], gamma=.01) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS array([[-0.01..., 1. ], [ 1. , 0. ]]) See Also -------- soft_dtw : Compute Soft-DTW cdist_soft_dtw_normalized : Cross similarity matrix between time series datasets using a normalized version of Soft-DTW References ---------- .. [1] M. Cuturi, M. Blondel "Soft-DTW: a Differentiable Loss Function for Time-Series," ICML 2017. """ dataset1 = to_time_series_dataset(dataset1, dtype=numpy.float64) self_similarity = False if dataset2 is None: dataset2 = dataset1 self_similarity = True else: dataset2 = to_time_series_dataset(dataset2, dtype=numpy.float64) dists = numpy.empty((dataset1.shape[0], dataset2.shape[0])) equal_size_ds1 = check_equal_size(dataset1) equal_size_ds2 = check_equal_size(dataset2) for i, ts1 in enumerate(dataset1): if equal_size_ds1: ts1_short = ts1 else: ts1_short = ts1[:ts_size(ts1)] for j, ts2 in enumerate(dataset2): if equal_size_ds2: ts2_short = ts2 else: ts2_short = ts2[:ts_size(ts2)] if self_similarity and j < i: dists[i, j] = dists[j, i] else: dists[i, j] = soft_dtw(ts1_short, ts2_short, gamma=gamma) return dists
def test_kmeans(): n, sz, d = 15, 10, 3 rng = np.random.RandomState(0) time_series = rng.randn(n, sz, d) km = TimeSeriesKMeans(n_clusters=3, metric="euclidean", max_iter=5, verbose=False, random_state=rng).fit(time_series) dists = cdist(time_series.reshape((n, -1)), km.cluster_centers_.reshape((3, -1))) np.testing.assert_allclose(km.labels_, dists.argmin(axis=1)) np.testing.assert_allclose(km.labels_, km.predict(time_series)) km_dba = TimeSeriesKMeans(n_clusters=3, metric="dtw", max_iter=5, verbose=False, random_state=rng).fit(time_series) dists = cdist_dtw(time_series, km_dba.cluster_centers_) np.testing.assert_allclose(km_dba.labels_, dists.argmin(axis=1)) np.testing.assert_allclose(km_dba.labels_, km_dba.predict(time_series)) km_sdtw = TimeSeriesKMeans(n_clusters=3, metric="softdtw", max_iter=5, verbose=False, random_state=rng).fit(time_series) dists = cdist_soft_dtw(time_series, km_sdtw.cluster_centers_) np.testing.assert_allclose(km_sdtw.labels_, dists.argmin(axis=1)) np.testing.assert_allclose(km_sdtw.labels_, km_sdtw.predict(time_series)) km_nofit = TimeSeriesKMeans(n_clusters=101, verbose=False, random_state=rng).fit(time_series) assert(km_nofit._X_fit is None) X_bis = to_time_series_dataset([[1, 2, 3, 4], [1, 2, 3], [2, 5, 6, 7, 8, 9]]) TimeSeriesKMeans(n_clusters=2, verbose=False, max_iter=5, metric="softdtw", random_state=0).fit(X_bis) TimeSeriesKMeans(n_clusters=2, verbose=False, max_iter=5, metric="dtw", random_state=0, init="random").fit(X_bis) TimeSeriesKMeans(n_clusters=2, verbose=False, max_iter=5, metric="dtw", random_state=0, init="k-means++").fit(X_bis) TimeSeriesKMeans(n_clusters=2, verbose=False, max_iter=5, metric="dtw", init=X_bis[:2]).fit(X_bis) # Barycenter size (nb of timestamps) # Case 1. kmeans++ / random init n, sz, d = 15, 10, 1 n_clusters = 3 time_series = rng.randn(n, sz, d) sizes_all_same_series = [sz] * n_clusters km_euc = TimeSeriesKMeans(n_clusters=3, metric="euclidean", max_iter=5, verbose=False, init="k-means++", random_state=rng).fit(time_series) np.testing.assert_equal(sizes_all_same_series, [ts_size(b) for b in km_euc.cluster_centers_]) km_dba = TimeSeriesKMeans(n_clusters=3, metric="dtw", max_iter=5, verbose=False, init="random", random_state=rng).fit(time_series) np.testing.assert_equal(sizes_all_same_series, [ts_size(b) for b in km_dba.cluster_centers_]) # Case 2. forced init barys = to_time_series_dataset([[1., 2., 3.], [1., 2., 2., 3., 4.], [3., 2., 1.]]) sizes_all_same_bary = [barys.shape[1]] * n_clusters # If Euclidean is used, barycenters size should be that of the input series km_euc = TimeSeriesKMeans(n_clusters=3, metric="euclidean", max_iter=5, verbose=False, init=barys, random_state=rng) np.testing.assert_raises(ValueError, km_euc.fit, time_series) km_dba = TimeSeriesKMeans(n_clusters=3, metric="dtw", max_iter=5, verbose=False, init=barys, random_state=rng).fit(time_series) np.testing.assert_equal(sizes_all_same_bary, [ts_size(b) for b in km_dba.cluster_centers_]) km_sdtw = TimeSeriesKMeans(n_clusters=3, metric="softdtw", max_iter=5, verbose=False, init=barys, random_state=rng).fit(time_series) np.testing.assert_equal(sizes_all_same_bary, [ts_size(b) for b in km_sdtw.cluster_centers_]) # A simple dataset, can we extract the correct number of clusters? time_series = to_time_series_dataset([[1, 2, 3], [7, 8, 9, 11], [.1, .2, 2.], [1, 1, 1, 9], [10, 20, 30, 1000]]) preds = TimeSeriesKMeans(n_clusters=3, metric="dtw", max_iter=5, random_state=rng).fit_predict(time_series) np.testing.assert_equal(set(preds), set(range(3))) preds = TimeSeriesKMeans(n_clusters=4, metric="dtw", max_iter=5, random_state=rng).fit_predict(time_series) np.testing.assert_equal(set(preds), set(range(4)))
# Define the model using parameters provided by the authors (except that we use # fewer iterations here) shp_clf = ShapeletModel(n_shapelets_per_size=shapelet_sizes, optimizer=Adagrad(lr=.1), weight_regularizer=.01, max_iter=200, verbose=0) shp_clf.fit(X_train, y_train) predicted_labels = shp_clf.predict(X_test) print("Correct classification rate:", accuracy_score(y_test, predicted_labels)) plt.figure() for i, sz in enumerate(shapelet_sizes.keys()): plt.subplot(len(shapelet_sizes), 1, i + 1) plt.title("%d shapelets of size %d" % (shapelet_sizes[sz], sz)) for shp in shp_clf.shapelets_: if ts_size(shp) == sz: plt.plot(shp.ravel()) plt.xlim([0, max(shapelet_sizes.keys()) - 1]) plt.tight_layout() plt.show() # The loss history is accessible via the `model` attribute that is a keras # model plt.figure() plt.plot(numpy.arange(1, 201), shp_clf.model_.history.history["loss"]) plt.title("Evolution of cross-entropy loss during training") plt.xlabel("Epochs") plt.show()
def soft_dtw_alignment(ts1, ts2, gamma=1.): r"""Compute Soft-DTW metric between two time series and return both the similarity measure and the alignment matrix. Soft-DTW was originally presented in [1]_ and is discussed in more details in our :ref:`user-guide page on DTW and its variants<dtw>`. Soft-DTW is computed as: .. math:: \text{soft-DTW}_{\gamma}(X, Y) = \min_{\pi}{}^\gamma \sum_{(i, j) \in \pi} \|X_i, Y_j\|^2 where :math:`\min^\gamma` is the soft-min operator of parameter :math:`\gamma`. In the limit case :math:`\gamma = 0`, :math:`\min^\gamma` reduces to a hard-min operator and soft-DTW is defined as the square of the DTW similarity measure. Parameters ---------- ts1 A time series ts2 Another time series gamma : float (default 1.) Gamma paraneter for Soft-DTW Returns ------- numpy.ndarray Soft-alignment matrix float Similarity Examples -------- >>> a, dist = soft_dtw_alignment([1, 2, 2, 3], ... [1., 2., 3., 4.], ... gamma=1.) # doctest: +ELLIPSIS >>> dist -0.89... >>> a # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE array([[1.00...e+00, 1.88...e-01, 2.83...e-04, 4.19...e-11], [3.40...e-01, 8.17...e-01, 8.87...e-02, 3.94...e-05], [5.05...e-02, 7.09...e-01, 5.30...e-01, 6.98...e-03], [1.37...e-04, 1.31...e-01, 7.30...e-01, 1.00...e+00]]) See Also -------- soft_dtw : Returns soft-DTW score alone References ---------- .. [1] M. Cuturi, M. Blondel "Soft-DTW: a Differentiable Loss Function for Time-Series," ICML 2017. """ if gamma == 0.: path, dist = dtw_path(ts1, ts2) dist_sq = dist**2 a = numpy.zeros((ts_size(ts1), ts_size(ts2))) for i, j in path: a[i, j] = 1. else: sdtw = SoftDTW(SquaredEuclidean(ts1[:ts_size(ts1)], ts2[:ts_size(ts2)]), gamma=gamma) dist_sq = sdtw.compute() a = sdtw.grad() return a, dist_sq