【问题标题】:Is there a way to vectorise this dynamic time warping algorithm?有没有办法对这种动态时间规整算法进行矢量化?
【发布时间】:2021-02-11 16:31:06
【问题描述】:

动态时间扭曲算法提供了两个时间序列之间的距离概念,这两个时间序列的速度可能会有所不同。如果我有 N 个序列要相互比较,我可以通过成对应用算法来构造一个具有 nule 对角线的 NXN 对称矩阵。然而,这对于长的二维序列来说非常慢。因此,我正在尝试对代码进行矢量化以加快此矩阵计算。重要的是,我还想提取定义最佳对齐方式的索引。

到目前为止我的成对比较代码:

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

【问题讨论】:

  • 如前所述,很难在问题上做出有用的 cmets。您可以查看here 了解一般指南。您可能希望确保可以复制粘贴代码并运行它,但现在情况并非如此(由于缩进问题)。
  • 除此之外,我得到d 在这个例子中等于无穷大。如果您的代码是正确的并且您想要更好的性能,您可能需要包含样本序列,这些序列会导致与您描述的“动态时间规整算法”相关的有限距离。
  • 有些缩进错了,我修好了。距离是有限的。
  • 对我来说看起来不错,不知道为什么我昨天在输出中看到无限距离。

标签: python algorithm optimization vectorization distance


【解决方案1】:

这是对代码的重新编写,使其更适合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

以下是一些变化:

  1. cost 使用 spatial.distance_matrix 计算得出。
  2. idx 的定义被一堆丑陋的 if 语句所取代,这使得编译代码更快。
  3. min([D[i, j+1], D[i+1, j], D[i, j]])min(D[i, j+1], D[i+1, j], D[i, j]) 替换,即我们不取列表中的最小值,而是取三个值中的最小值。这导致在 jit 下的加速令人惊讶。
  4. matchidx 被预先分配为一个 numpy 数组,并在输出前截断为正确的大小。

【讨论】:

  • 非常感谢,长序列确实更快。但是,我注意到对于不同长度序列的情况会发生奇怪的事情,内核会重新启动。例如:size=(100, 2) 和 size=(50, 2)。原始函数不是这种情况。
  • 好的,它只需要改变:nt = D.shape[0] - 1 到 nt = D.shape[1] - 1
  • 哦,索引很难。接得好。让我知道更新后的代码是否适用于您的基准测试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-27
相关资源
最近更新 更多