【问题标题】:Cython and numpy speedCython 和 numpy 速度
【发布时间】:2010-11-15 01:24:25
【问题描述】:

我在我的 python 程序中使用 cython 进行相关性计算。我有两个音频数据集,我需要知道它们之间的时间差。第二组根据开始时间进行切割,然后滑过第一组。有两个 for 循环:一个滑动集合,内部循环计算该点的相关性。这种方法效果很好,也够准确。

问题在于,对于纯 python,这需要超过一分钟。使用我的 cython 代码,大约需要 17 秒。这还是太多了。你有任何提示如何加速这段代码:

import numpy as np
cimport numpy as np

cimport cython

FTYPE = np.float
ctypedef np.float_t FTYPE_t

@cython.boundscheck(False)
def delay(np.ndarray[FTYPE_t, ndim=1] f, np.ndarray[FTYPE_t, ndim=1] g):
    cdef int size1 = f.shape[0]
    cdef int size2 = g.shape[0]
    cdef int max_correlation = 0
    cdef int delay = 0
    cdef int current_correlation, i, j

    # Move second data set frame by frame
    for i in range(0, size1 - size2):
        current_correlation = 0

        # Calculate correlation at that point
        for j in range(size2):
            current_correlation += f[<unsigned int>(i+j)] * g[j]

        # Check if current correlation is highest so far
        if current_correlation > max_correlation:
            max_correlation = current_correlation
            delay = i

    return delay

【问题讨论】:

  • 只是出于好奇,每个音频信号中的样本数是多少?
  • 大约 10.000 个样本被切割用于延迟计算。这意味着窗口滑动 10k 步。
  • 啊。直接相关性为 O(n^2),因此对于 n = 10,000,大约需要 100,000,000 次操作。基于 FFT 的相关性为 O(n lg n),因此对于 n = 10,000,大约有 132,877 次操作。

标签: python numpy cython


【解决方案1】:

编辑:
现在有scipy.signal.fftconvolve,这将是我在下面描述的基于 FFT 的卷积方法的首选方法。我会留下原来的答案来解释速度问题,但在实践中使用scipy.signal.fftconvolve

原答案:
使用 FFTconvolution theorem 可以将问题从 O(n^2) 转换为 O(n log n),从而显着提高速度。这对于像您这样的长数据集特别有用,并且可以提供 1000 秒或更多的速度增益,具体取决于长度。这也很容易做到:只需对两个信号进行 FFT、乘法和逆 FFT 乘积。 numpy.correlate 在互相关例程中不使用 FFT 方法,更适合用于非常小的内核。

这是一个例子

from timeit import Timer
from numpy import *

times = arange(0, 100, .001)

xdata = 1.*sin(2*pi*1.*times) + .5*sin(2*pi*1.1*times + 1.)
ydata = .5*sin(2*pi*1.1*times)

def xcorr(x, y):
    return correlate(x, y, mode='same')

def fftxcorr(x, y):
    fx, fy = fft.fft(x), fft.fft(y[::-1])
    fxfy = fx*fy
    xy = fft.ifft(fxfy)
    return xy

if __name__ == "__main__":
    N = 10
    t = Timer("xcorr(xdata, ydata)", "from __main__ import xcorr, xdata, ydata")
    print 'xcorr', t.timeit(number=N)/N
    t = Timer("fftxcorr(xdata, ydata)", "from __main__ import fftxcorr, xdata, ydata")
    print 'fftxcorr', t.timeit(number=N)/N

它给出了每个周期的运行时间(以秒为单位,对于 10,000 长波形)

xcorr 34.3761689901
fftxcorr 0.0768054962158

很明显 fftxcorr 方法要快得多。

如果您绘制结果,您会发现它们在接近零时移时非常相似。但是请注意,随着您离得越远,xcorr 会减少,而 fftxcorr 不会。这是因为如何处理波形移动时不重叠的波形部分有点模棱两可。 xcorr 将其视为零,FFT 将波形视为周期性的,但如果这是一个问题,可以通过零填充来解决。

【讨论】:

  • 你的时间是 10,000 还是 arange(0, 100, .001) ?
【解决方案2】:

这种事情的诀窍是找到一种分而治之的方法。

目前,您正在滑动到每个位置并检查每个位置的每个点 - 实际上是 O( n ^ 2 ) 操作。

您需要减少对 每个 点的检查和对 每个 位置的比较,以减少确定不匹配的工作量。

例如,您可以使用更短的“这是否更接近?”检查前几个位置的过滤器。如果相关性高于某个阈值,则继续前进,否则放弃并继续前进。

您可以将“每第 8 个位置检查一次”乘以 8。如果这太低,请跳过它并继续。如果这足够高,则检查所有值以查看是否找到最大值。

问题是进行所有这些乘法运算所需的时间 -- (f[&lt;unsigned int&gt;(i+j)] * g[j]) 实际上,您正在用所有这些乘积填充一个大矩阵并选择总和最大的行。您不想计算“所有”产品。足够的产品,以确保您找到了最大金额。

找到最大值的问题是您必须对所有内容求和以查看它是否最大。如果您可以将其转化为最小化问题,那么一旦中间结果超过阈值,就更容易放弃计算产品和求和。

(我认为这可能有效。我没有尝试过。)

如果您使用max(g)-g[j] 处理负数,您会寻找最小的,而不是最大的。您可以计算第一个位置的相关性。任何总和较大的值都可以立即停止 - 不再对该偏移量进行乘法或加法,而是转移到另一个。

【讨论】:

  • 感谢您的回答。我发现 numpy.correlate() 将性能提高了至少 10 倍。不幸的是,我无法使用最小的一招。
【解决方案3】:
  • 您可以从外部循环中提取范围(size2)
  • 您可以使用 sum() 而不是循环来计算 current_correlation
  • 您可以将相关性和延迟存储在一个列表中,然后使用 max() 来获取最大的一个

【讨论】:

  • 感谢您的帮助。首先我使用 sum() 但 numpy.correlate() 更快。现在我还存储了值并按照您所说的使用 max()。
猜你喜欢
  • 2011-12-09
  • 1970-01-01
  • 1970-01-01
  • 2013-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多