├── README ├── setup.py ├── bin └── test_dtw.py └── src ├── __init__.py └── fast.pyx /README: -------------------------------------------------------------------------------- 1 | Dynamic Time Warping in Python 2 | 3 | Author: Jeremy Stober 4 | Contact: stober@gmail.com 5 | Version: 0.1 6 | 7 | This is a collection of dynamic time warping algorithms (including 8 | related algorithms like edit-distance). 9 | 10 | Requires: numpy -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on May 5, 2009 3 | 4 | @author: stober 5 | ''' 6 | 7 | from distutils.core import setup 8 | from distutils.extension import Extension 9 | from Cython.Distutils import build_ext 10 | import numpy 11 | import os 12 | 13 | np_lib = os.path.dirname(numpy.__file__) 14 | np_inc = [os.path.join(np_lib, 'core/include')] 15 | 16 | ext_modules = [Extension("dtw.fast",["src/fast.pyx"], include_dirs=np_inc)] 17 | 18 | setup(name='dtw', 19 | version='0.1', 20 | description='Dynamic Time Warping', 21 | author='Jeremy Stober', 22 | author_email='stober@cs.utexas.edu', 23 | package_dir={'dtw':'src'}, 24 | packages=['dtw'], 25 | cmdclass = {'build_ext' : build_ext}, 26 | ext_modules = ext_modules 27 | ) 28 | -------------------------------------------------------------------------------- /bin/test_dtw.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ 3 | Author: Jeremy M. Stober 4 | Program: TEST_DTW.PY 5 | Date: Wednesday, March 7 2012 6 | Description: Test DTW algorithms. 7 | """ 8 | 9 | import numpy as np 10 | from dtw import * 11 | import dtw.fast 12 | import mlpy.dtw 13 | import numpy.random as npr 14 | import pylab 15 | 16 | if __name__ == '__main__': 17 | 18 | import itertools 19 | 20 | # create sequences of related sequences 21 | x = npr.normal(0,15,(10,2)) 22 | e = npr.normal(0,1,(10,2)) 23 | y = x + e 24 | 25 | xa = [] 26 | t = np.array([0.0,0.0]) 27 | for i in x: 28 | t += i 29 | xa.append(t.copy()) 30 | 31 | ya = [] 32 | t = np.array([0.0,0.0]) 33 | for i in x + e: 34 | t += i 35 | ya.append(t.copy()) 36 | 37 | 38 | xa = np.array(xa) 39 | ya = np.array(ya) 40 | 41 | pylab.plot(xa[:,0],xa[:,1]) 42 | pylab.plot(ya[:,0],ya[:,1]) 43 | print "Slow Version" 44 | print dtw_distance(xa,ya) #,[1.0,1.0,0.0]) 45 | print "Fast Version" 46 | print dtw.fast.dtw_fast(xa,ya) 47 | #print "MLPY" 48 | #print mlpy.dtw.dtw_std(xa,ya) 49 | print "Fast Version 2D" 50 | print dtw.fast.dtw_fast_2d(xa,ya) 51 | 52 | pylab.show() 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ 3 | Author: Jeremy M. Stober 4 | Program: DTW.PY 5 | Date: Tuesday, February 15 2011 6 | Description: Dynamic Time Warping 7 | """ 8 | 9 | import numpy as np 10 | import numpy.linalg as la 11 | 12 | def equalize(s,t,default=None): 13 | # equalize the length of two strings by appending a default value 14 | 15 | if len(s) < len(t): 16 | s.extend([default] * (len(t) - len(s))) 17 | elif len(t) < len(s): 18 | t.extend([default] * (len(s) - len(t))) 19 | else: 20 | pass # same length 21 | 22 | return s,t 23 | 24 | def non_dtw_distance(s,t,default=None,costf=None): 25 | # Don't run dynamic time warping, instead just compare actions 26 | # using the provided cost function and post-pend to make the 27 | # sequences the same length. 28 | 29 | s,t = equalize(s,t,default) 30 | 31 | return sum([costf(a,b) for a,b in zip(s,t)]) 32 | 33 | def initialize_dmatrix(rows,cols): 34 | d = np.zeros((rows,cols),dtype='float') 35 | 36 | d[:,0] = 1e6 37 | d[0,:] = 1e6 38 | d[0,0] = 0 39 | 40 | return d 41 | 42 | def initialize_ematrix(rows,cols): 43 | d = np.zeros((rows,cols),dtype='float') 44 | 45 | d[:,0] = np.arange(rows) 46 | d[0,:] = np.arange(cols) 47 | 48 | return d 49 | 50 | def edit_distance(s,t): 51 | n = len(s) 52 | m = len(t) 53 | d = initialize_ematrix(n+1,m+1) 54 | 55 | for i in range(1,n+1): 56 | for j in range(1,m+1): 57 | if s[i-1] == t[j-1]: 58 | d[i,j] = d[i-1,j-1] 59 | else: 60 | d[i,j] = min(d[i-1,j] + 1, d[i,j-1] + 1, d[i-1,j-1] + 1) 61 | 62 | return d[n,m] 63 | 64 | def edit_distance_vc(s,t, costs = (1,1,1)): 65 | # edit distance with variable costs 66 | n = len(s) 67 | m = len(t) 68 | d = initialize_ematrix(n+1,m+1) 69 | 70 | for i in range(1,n+1): 71 | for j in range(1,m+1): 72 | if s[i-1] == t[j-1]: 73 | d[i,j] = d[i-1,j-1] 74 | else: 75 | d[i,j] = min(d[i-1,j] + costs[0], d[i,j-1] + costs[1], d[i-1,j-1] + costs[2]) 76 | 77 | return d[n,m] 78 | 79 | 80 | 81 | def etw_distance(list1, list2, params, costf=lambda x,y: la.norm(x - y)): 82 | """ 83 | etw_distance : extended time warping 84 | Use dynamic time warping but apply a cost to (insertion, deletion, match) 85 | """ 86 | 87 | n = len(list1) 88 | m = len(list2) 89 | dtw = initialize_dmatrix(n+1,m+1) 90 | 91 | icost = params[0] 92 | dcost = params[1] 93 | mcost = params[2] 94 | 95 | for (i,x) in enumerate(list1): 96 | i += 1 97 | for (j,y) in enumerate(list2): 98 | j += 1 99 | 100 | cost = costf(x,y) 101 | dtw[i,j] = cost + min(dtw[i-1,j] + icost, dtw[i,j-1] + dcost, dtw[i-1][j-1] + mcost) 102 | 103 | return dtw[n,m] 104 | 105 | def dtw_distance(list1, list2, costf=lambda x,y: la.norm(x - y) ): 106 | 107 | n = len(list1) 108 | m = len(list2) 109 | dtw = initialize_dmatrix(n+1,m+1) 110 | 111 | for (i,x) in enumerate(list1): 112 | i += 1 113 | for (j,y) in enumerate(list2): 114 | j += 1 115 | 116 | cost = costf(x,y) 117 | dtw[i,j] = cost + min(dtw[i-1,j],dtw[i,j-1],dtw[i-1][j-1]) 118 | 119 | return dtw[n,m] 120 | 121 | def dtw_wdistance(list1, list2, w, costf=lambda x,y: la.norm(x - y)): 122 | 123 | n = len(list1) 124 | m = len(list2) 125 | dtw = initialize_dmatrix(n+1,m+1) 126 | 127 | for (i,x) in enumerate(list1): 128 | for j in range(max(0,i-w),min(m,i+w)): 129 | y = list2[j] 130 | cost = costf(x,y) 131 | dtw[i,j] = cost + min(dtw[i-1,j],dtw[i,j-1],dtw[i-1][j-1]) 132 | 133 | return dtw[n-1,m-1] 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/fast.pyx: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.linalg as la 3 | cimport numpy as np 4 | 5 | DTYPE = np.double 6 | ctypedef np.double_t DTYPE_t 7 | 8 | ITYPE = np.int 9 | ctypedef np.int_t ITYPE_t 10 | 11 | cdef double min3(double a, double b, double c): 12 | cdef double m = a 13 | if b < m: 14 | m = b 15 | if c < m: 16 | m = c 17 | return m 18 | 19 | cdef int imin3(int a, int b, int c): 20 | cdef int m = a 21 | if b < m: 22 | m = b 23 | if c < m: 24 | m = c 25 | return m 26 | 27 | 28 | cdef double dist(np.ndarray[DTYPE_t,ndim=1] x, np.ndarray[DTYPE_t,ndim=1] y): 29 | 30 | cdef int length = len(x) 31 | cdef unsigned int i 32 | cdef DTYPE_t d = 0 33 | 34 | for i in range(length): 35 | d += (x[i] - y[i]) ** 2 36 | 37 | return np.sqrt(d) 38 | 39 | def edit_distance(np.ndarray[ITYPE_t,ndim=1] s, np.ndarray[ITYPE_t,ndim=1] t): 40 | cdef int n = s.shape[0] 41 | cdef int m = t.shape[0] 42 | cdef np.ndarray[ITYPE_t,ndim=2] ed = np.zeros((n+1,m+1), dtype=ITYPE) 43 | 44 | ed[:,0] = np.arange(n+1) 45 | ed[0,:] = np.arange(m+1) 46 | 47 | cdef unsigned int i,j 48 | 49 | for i in range(n): 50 | for j in range(m): 51 | if s[i] == t[j]: 52 | ed[i+1,j+1] = ed[i,j] 53 | else: 54 | ed[i+1,j+1] = imin3(ed[i,j+1] + 1, ed[i+1,j] + 1, ed[i,j] + 1) 55 | 56 | return ed[n,m] 57 | 58 | 59 | def edit_distance_vc(np.ndarray[ITYPE_t,ndim=1] s, np.ndarray[ITYPE_t,ndim=1] t, int a, int b, int c): 60 | cdef int n = s.shape[0] 61 | cdef int m = t.shape[0] 62 | cdef np.ndarray[ITYPE_t,ndim=2] ed = np.zeros((n+1,m+1), dtype=ITYPE) 63 | 64 | ed[:,0] = np.arange(n+1) 65 | ed[0,:] = np.arange(m+1) 66 | 67 | cdef unsigned int i,j 68 | 69 | for i in range(n): 70 | for j in range(m): 71 | if s[i] == t[j]: 72 | ed[i+1,j+1] = ed[i,j] 73 | else: 74 | ed[i+1,j+1] = imin3(ed[i,j+1] + a, ed[i+1,j] + b, ed[i,j] + c) 75 | 76 | return ed[n,m] 77 | 78 | 79 | def dtw_fast(np.ndarray s, np.ndarray t): 80 | 81 | cdef int nrows = s.shape[0] 82 | cdef int ncols = t.shape[0] 83 | 84 | cdef np.ndarray[DTYPE_t,ndim=2] dtw = np.zeros((nrows+1,ncols+1), dtype = DTYPE) 85 | 86 | dtw[:,0] = 1e6 87 | dtw[0,:] = 1e6 88 | dtw[0,0] = 0.0 89 | 90 | cdef unsigned int i,j 91 | cdef DTYPE_t cost 92 | 93 | for i in range(nrows): 94 | for j in range(ncols): 95 | cost = la.norm(s[i] - t[j]) 96 | dtw[i+1,j+1] = cost + min3(dtw[i,j+1],dtw[i+1,j],dtw[i,j]) 97 | 98 | return dtw[nrows,ncols] 99 | 100 | 101 | def dtw_fast_2d(np.ndarray[DTYPE_t,ndim=2] s, np.ndarray[DTYPE_t,ndim=2] t): 102 | 103 | cdef int nrows = s.shape[0] 104 | cdef int ncols = t.shape[0] 105 | 106 | cdef np.ndarray[DTYPE_t,ndim=2] dtw = np.zeros((nrows+1,ncols+1), dtype = DTYPE) 107 | 108 | dtw[:,0] = 1e6 109 | dtw[0,:] = 1e6 110 | dtw[0,0] = 0.0 111 | 112 | cdef unsigned int i,j 113 | cdef DTYPE_t cost 114 | 115 | for i in range(nrows): 116 | for j in range(ncols): 117 | cost = dist(s[i],t[j]) 118 | dtw[i+1,j+1] = cost + min3(dtw[i,j+1],dtw[i+1,j],dtw[i,j]) 119 | 120 | return dtw[nrows,ncols] 121 | 122 | 123 | # very fast 124 | def dtw_fast_1d(np.ndarray[DTYPE_t,ndim=1] s, np.ndarray[DTYPE_t,ndim=1] t): 125 | 126 | cdef int nrows = s.shape[0] 127 | cdef int ncols = t.shape[0] 128 | 129 | cdef np.ndarray[DTYPE_t,ndim=2] dtw = np.zeros((nrows+1,ncols+1), dtype = DTYPE) 130 | 131 | dtw[:,0] = 1e6 132 | dtw[0,:] = 1e6 133 | dtw[0,0] = 0.0 134 | 135 | cdef unsigned int i,j 136 | cdef DTYPE_t cost 137 | 138 | for i in range(nrows): 139 | for j in range(ncols): 140 | cost = abs(s[i] - t[j]) 141 | dtw[i+1,j+1] = cost + min3(dtw[i,j+1],dtw[i+1,j],dtw[i,j]) 142 | 143 | return dtw[nrows,ncols] 144 | --------------------------------------------------------------------------------