Python 有没有一种方法可以将这种动态时间扭曲算法矢量化?
动态时间扭曲算法提供了两个时间序列之间的距离概念,这两个时间序列的速度可能不同。如果我有N个序列相互比较,我可以通过两两应用算法构造一个NXN对称矩阵和一个nule对角。然而,对于长的二维序列,这是非常缓慢的。因此,我试图对代码进行矢量化,以加速矩阵计算。重要的是,我还想提取定义最佳对齐的索引 到目前为止,我的成对比较代码:Python 有没有一种方法可以将这种动态时间扭曲算法矢量化?,python,algorithm,optimization,vectorization,distance,Python,Algorithm,Optimization,Vectorization,Distance,动态时间扭曲算法提供了两个时间序列之间的距离概念,这两个时间序列的速度可能不同。如果我有N个序列相互比较,我可以通过两两应用算法构造一个NXN对称矩阵和一个nule对角。然而,对于长的二维序列,这是非常缓慢的。因此,我试图对代码进行矢量化,以加速矩阵计算。重要的是,我还想提取定义最佳对齐的索引 到目前为止,我的成对比较代码: import math import numpy as np seq1 = np.random.randint(100, size=(100, 2)) #Two dim
import math
import numpy as np
seq1 = np.random.randint(100, size=(100, 2)) #Two dim sequences
seq2 = np.random.randint(100, size=(100, 2))
def seqdist(seq1, seq2): # dynamic time warping function
ns = len(seq1)
nt = len(seq2)
D = np.zeros((ns+1, nt+1))+math.inf
D[0, 0] = 0
cost = np.zeros((ns,nt))
for i in range(ns):
for j in range(nt):
cost[i,j] = np.linalg.norm(seq1[i,:]-seq2[j,:])
D[i+1, j+1] = cost[i,j]+min([D[i, j+1], D[i+1, j], D[i, j]])
d = D[ns,nt] # distance
matchidx = [[ns-1, nt-1]] # backwards optimal alignment computation
i = ns
j = nt
for k in range(ns+nt+2):
idx = np.argmin([D[i-1, j], D[i, j-1], D[i-1, j-1]])
if idx == 0 and i > 1 and j > 0:
matchidx.append([i-2, j-1])
i -= 1
elif idx == 1 and i > 0 and j > 1:
matchidx.append([i-1, j-2])
j -= 1
elif idx == 2 and i > 1 and j > 1:
matchidx.append([i-2, j-2])
i -= 1
j -= 1
else:
break
matchidx.reverse()
return d, matchidx
[d,matchidx] = seqdist(seq1,seq2) #try it
这里有一个代码的重写,它使您的代码更适合
numba.jit
。这并不是一个完全矢量化的解决方案,但是我看到这个基准测试的加速因子是230
from numba import jit
from scipy import spatial
@jit
def D_from_cost(cost, D):
# operates on D inplace
ns, nt = cost.shape
for i in range(ns):
for j in range(nt):
D[i+1, j+1] = cost[i,j]+min(D[i, j+1], D[i+1, j], D[i, j])
# avoiding the list creation inside mean enables better jit performance
# D[i+1, j+1] = cost[i,j]+min([D[i, j+1], D[i+1, j], D[i, j]])
@jit
def get_d(D, matchidx):
ns = D.shape[0] - 1
nt = D.shape[1] - 1
d = D[ns,nt]
matchidx[0,0] = ns - 1
matchidx[0,1] = nt - 1
i = ns
j = nt
for k in range(1, ns+nt+3):
idx = 0
if not (D[i-1,j] <= D[i,j-1] and D[i-1,j] <= D[i-1,j-1]):
if D[i,j-1] <= D[i-1,j-1]:
idx = 1
else:
idx = 2
if idx == 0 and i > 1 and j > 0:
# matchidx.append([i-2, j-1])
matchidx[k,0] = i - 2
matchidx[k,1] = j - 1
i -= 1
elif idx == 1 and i > 0 and j > 1:
# matchidx.append([i-1, j-2])
matchidx[k,0] = i-1
matchidx[k,1] = j-2
j -= 1
elif idx == 2 and i > 1 and j > 1:
# matchidx.append([i-2, j-2])
matchidx[k,0] = i-2
matchidx[k,1] = j-2
i -= 1
j -= 1
else:
break
return d, matchidx[:k]
def seqdist2(seq1, seq2):
ns = len(seq1)
nt = len(seq2)
cost = spatial.distance_matrix(seq1, seq2)
# initialize and update D
D = np.full((ns+1, nt+1), np.inf)
D[0, 0] = 0
D_from_cost(cost, D)
matchidx = np.zeros((ns+nt+2,2), dtype=np.int)
d, matchidx = get_d(D, matchidx)
return d, matchidx[::-1].tolist()
assert seqdist2(seq1, seq2) == seqdist(seq1, seq2)
%timeit seqdist2(seq1, seq2) # 1000 loops, best of 3: 365 µs per loop
%timeit seqdist(seq1, seq2) # 10 loops, best of 3: 86.1 ms per loop
从numba导入jit
从scipy导入空间
@准时制
def D_来自_成本(成本,D):
#在D-inplace上运行
ns,nt=cost.shape
对于范围内的i(ns):
对于范围内的j(nt):
D[i+1,j+1]=成本[i,j]+min(D[i,j+1],D[i+1,j],D[i,j])
#避免在mean中创建列表可以实现更好的jit性能
#D[i+1,j+1]=成本[i,j]+min([D[i,j+1],D[i+1,j],D[i,j]]))
@准时制
def get_d(d,匹配IDX):
ns=D.形状[0]-1
nt=D.形状[1]-1
d=d[ns,nt]
matchidx[0,0]=ns-1
matchidx[0,1]=nt-1
i=ns
j=nt
对于范围(1,ns+nt+3)内的k:
idx=0
如果不是(D[i-1,j]0和j>1:
#matchidx.append([i-1,j-2])
matchidx[k,0]=i-1
matchidx[k,1]=j-2
j-=1
elif idx==2且i>1且j>1:
#matchidx.append([i-2,j-2])
matchidx[k,0]=i-2
matchidx[k,1]=j-2
i-=1
j-=1
其他:
打破
返回d,matchidx[:k]
def seqdist2(seq1,seq2):
ns=长度(序号1)
nt=长度(序号2)
成本=空间距离矩阵(序号1,序号2)
#初始化并更新D
D=np.full((ns+1,nt+1),np.inf)
D[0,0]=0
D_来自_成本(成本,D)
matchidx=np.zero((ns+nt+2,2),dtype=np.int)
d、 matchidx=get_d(d,matchidx)
返回d,matchidx[:-1].tolist()
断言seqdist2(seq1,seq2)=seqdist(seq1,seq2)
%timeit seqdist2(seq1,seq2)#1000个回路,每个回路的最佳值为3:365µs
%timeit seqdist(seq1,seq2)#10个环路,最佳3个:每个环路86.1毫秒
以下是一些变化:
成本
使用空间距离矩阵
计算idx
的定义被一堆丑陋的if语句所取代,这使得编译的代码更快min([D[i,j+1],D[i+1,j],D[i,j]])
被替换为min(D[i,j+1],D[i,j],D[i,j])
,也就是说,在jit
下,我们不取列表中的min,而是取三个值中的min。这导致了惊人的加速matchidx
被预先分配为numpy数组,并在输出之前被截断为正确的大小很难对前面提到的问题做出有用的评论。您可以查看一般指导原则。您可能希望确保可以复制粘贴代码并运行它,但现在情况并非如此(由于缩进问题)。除此之外,在本例中,我得到的
d
等于无穷大。如果您的代码是正确的,并且您希望获得更好的性能,那么您可能希望包含导致“动态时间扭曲算法”的有限距离的样本序列正如你所描述的。有一些缩进错误,我修正了它们。距离是有限的。我觉得很好,不知道为什么我昨天在输出中看到无限距离。非常感谢,长序列确实更快。但是,我注意到,对于不同长度的序列,会发生一件奇怪的事情,内核会重新启动示例:size=(100,2)和size=(50,2)。原始函数不是这样。好的,它只需要一个更改:nt=D.shape[0]-1到nt=D.shape[1]-1Oh。好的,索引很难。很好的捕获。让我知道更新的代码是否适用于您的基准测试。