我的代码调用了许多“差异函数”来计算“Yin algorithm”(基频提取器)。
差异函数(论文中的等式6)定义为:
这是我对差异函数的实现:
def differenceFunction(x, W, tau_max):
df = [0] * tau_max
for tau in range(1, tau_max):
for j in range(0, W - tau):
tmp = long(x[j] - x[j + tau])
df[tau] += tmp * tmp
return df
例如:
x = np.random.randint(0, high=32000, size=2048, dtype='int16')
W = 2048
tau_max = 106
differenceFunction(x, W, tau_max)
有没有办法优化这个双循环计算(仅使用python,最好没有其他库而不是numpy)?
编辑:更改代码以避免索引错误(j循环,@艾略特答案)
EDIT2:更改代码以使用x [0](j循环,@ hynekcer评论)
答案 0 :(得分:6)
首先,您应该考虑数组的边界。您最初编写的代码将获得IndexError
。
您可以通过矢量化内循环来获得显着的加速
import numpy as np
# original version
def differenceFunction_2loop(x, W, tau_max):
df = np.zeros(tau_max, np.long)
for tau in range(1, tau_max):
for j in range(0, W - tau): # -tau eliminates the IndexError
tmp = np.long(x[j] -x[j + tau])
df[tau] += np.square(tmp)
return df
# vectorized inner loop
def differenceFunction_1loop(x, W, tau_max):
df = np.zeros(tau_max, np.long)
for tau in range(1, tau_max):
tmp = (x[:-tau]) - (x[tau:]).astype(np.long)
df[tau] = np.dot(tmp, tmp)
return df
x = np.random.randint(0, high=32000, size=2048, dtype='int16')
W = 2048
tau_max = 106
twoloop = differenceFunction_2loop(x, W, tau_max)
oneloop = differenceFunction_1loop(x, W, tau_max)
# confirm that the result comes out the same.
print(np.all(twoloop == oneloop))
# True
现在进行一些基准测试。在ipython
我得到以下
In [103]: %timeit twoloop = differenceFunction_2loop(x, W, tau_max)
1 loop, best of 3: 2.35 s per loop
In [104]: %timeit oneloop = differenceFunction_1loop(x, W, tau_max)
100 loops, best of 3: 8.23 ms per loop
所以,大约加速300倍。
答案 1 :(得分:6)
编辑:速度提升至220μs - 请参见最后编辑 - 直接版
Autocorrelation function或类似地通过卷积可以轻松评估所需的计算。 Wiener-Khinchin定理允许用两个快速傅立叶变换(FFT)计算自相关,时间复杂度 O(n log n)。 我使用来自fftconvolve包的加速卷积函数Scipy。一个优点是,它很容易解释为什么它的工作原理。一切都是矢量化的,在Python解释器级别没有循环。
from scipy.signal import fftconvolve
def difference_by_convol(x, W, tau_max):
x = np.array(x, np.float64)
w = x.size
x_cumsum = np.concatenate((np.array([0.]), (x * x).cumsum()))
conv = fftconvolve(x, x[::-1])
df = x_cumsum[w:0:-1] + x_cumsum[w] - x_cumsum[:w] - 2 * conv[w - 1:]
return df[:tau_max + 1]
differenceFunction_1loop
函数相比:FFT更快:430μs与原始1170μs相比。大约tau_max >= 40
开始加快。数值精度很高。与精确整数结果相比,最高相对误差小于1E-14。 (因此可以很容易地舍入到完全长整数解。)tau_max
对于算法并不重要。它最终只限制输出。索引0处的零元素被添加到输出中,因为索引应该在Python中以0开始。W
在Python中并不重要。尺寸最好被反省。correlate(x, x) = convolve(x, reversed(x)
。convolve
会自动选择此方法或基于估算速度更快的直接方法。”这种启发式方法不适用于这种情况,因为卷积评估的tau
比tau_max
要多得多,并且它必须比直接方法快得多的FFT。证明:(对于Pythonistas: - )
最初的天真实现可以写成:
df = [sum((x[j] - x[j + t]) ** 2 for j in range(w - t)) for t in range(tau_max + 1)]
其中tau_max < w
。
按规则(a - b)**2 == a**2 + b**2 - 2 * a * b
df = [ sum(x[j] ** 2 for j in range(w - t))
+ sum(x[j] ** 2 for j in range(t, w))
- 2 * sum(x[j] * x[j + t] for j in range(w - t))
for t in range(tau_max + 1)]
在x_cumsum = [sum(x[j] ** 2 for j in range(i)) for i in range(w + 1)]
的帮助下替换前两个元素,可以在线性时间内轻松计算。用输出大小为sum(x[j] * x[j + t] for j in range(w - t))
的卷积conv = convolvefft(x, reversed(x), mode='full')
替换len(x) + len(x) - 1
。
df = [x_cumsum[w - t] + x_cumsum[w] - x_cumsum[t]
- 2 * convolve(x, x[::-1])[w - 1 + t]
for t in range(tau_max + 1)]
通过矢量表达式进行优化:
df = x_cumsum[w:0:-1] + x_cumsum[w] - x_cumsum[:w] - 2 * conv[w - 1:]
每个步骤也可以通过测试数据进行测试和比较
编辑:通过Numpy FFT直接实施解决方案。
def difference_fft(x, W, tau_max):
x = np.array(x, np.float64)
w = x.size
tau_max = min(tau_max, w)
x_cumsum = np.concatenate((np.array([0.]), (x * x).cumsum()))
size = w + tau_max
p2 = (size // 32).bit_length()
nice_numbers = (16, 18, 20, 24, 25, 27, 30, 32)
size_pad = min(x * 2 ** p2 for x in nice_numbers if x * 2 ** p2 >= size)
fc = np.fft.rfft(x, size_pad)
conv = np.fft.irfft(fc * fc.conjugate())[:tau_max]
return x_cumsum[w:w - tau_max:-1] + x_cumsum[w] - x_cumsum[:tau_max] - 2 * conv
它比我以前的解决方案快两倍以上,因为卷积的长度被限制在W + tau_max
之后具有小素数因子的最接近的“漂亮”数字,而不是评估为满2 * W
。也没有必要像使用`fftconvolve(x,reversed(x))那样两次转换相同的数据。
In [211]: %timeit differenceFunction_1loop(x, W, tau_max)
1.1 ms ± 4.51 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [212]: %timeit difference_by_convol(x, W, tau_max)
431 µs ± 5.69 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [213]: %timeit difference_fft(x, W, tau_max)
218 µs ± 685 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
对于tau_max&gt; = 20,最新的解决方案比Eliot的difference_by_convol更快。由于开销成本的比例相似,该比率在很大程度上不依赖于数据大小。
答案 2 :(得分:1)
与优化算法相反,您可以使用numba.jit优化解释器:
import timeit
import numpy as np
from numba import jit
def differenceFunction(x, W, tau_max):
df = [0] * tau_max
for tau in range(1, tau_max):
for j in range(0, W - tau):
tmp = int(x[j] - x[j + tau])
df[tau] += tmp * tmp
return df
@jit
def differenceFunction2(x, W, tau_max):
df = np.ndarray(shape=(tau_max,))
for tau in range(1, tau_max):
for j in range(0, W - tau):
tmp = int(x[j] - x[j + tau])
df[tau] += tmp * tmp
return df
x = np.random.randint(0, high=32000, size=2048, dtype='int16')
W = 2048
tau_max = 106
differenceFunction(x, W, tau_max)
print('old',
timeit.timeit('differenceFunction(x, W, tau_max)', 'from __main__ import differenceFunction, x, W, tau_max',
number=20) / 20)
print('new',
timeit.timeit('differenceFunction2(x, W, tau_max)', 'from __main__ import differenceFunction2, x, W, tau_max',
number=20) / 20)
结果:
old 0.18265145074453273
new 0.016223197058214667
您可以结合算法优化和numba.jit
以获得更好的结果。
答案 3 :(得分:1)
这是使用列表理解的另一种方法。它大约不到原始函数所用时间的十分之一,但没有超过Elliot's answer。无论如何,只是把它放在那里。
import numpy as np
import time
# original version
def differenceFunction_2loop(x, W, tau_max):
df = np.zeros(tau_max, np.long)
for tau in range(1, tau_max):
for j in range(0, W - tau): # -tau eliminates the IndexError
tmp = np.long(x[j] -x[j + tau])
df[tau] += np.square(tmp)
return df
# vectorized inner loop
def differenceFunction_1loop(x, W, tau_max):
df = np.zeros(tau_max, np.long)
for tau in range(1, tau_max):
tmp = (x[:-tau]) - (x[tau:]).astype(np.long)
df[tau] = np.dot(tmp, tmp)
return df
# with list comprehension
def differenceFunction_1loop_listcomp(x, W, tau_max):
df = [sum(((x[:-tau]) - (x[tau:]).astype(np.long))**2) for tau in range(1, tau_max)]
return [0] + df[:]
x = np.random.randint(0, high=32000, size=2048, dtype='int16')
W = 2048
tau_max = 106
s = time.clock()
twoloop = differenceFunction_2loop(x, W, tau_max)
print(time.clock() - s)
s = time.clock()
oneloop = differenceFunction_1loop(x, W, tau_max)
print(time.clock() - s)
s = time.clock()
listcomprehension = differenceFunction_1loop_listcomp(x, W, tau_max)
print(time.clock() - s)
# confirm that the result comes out the same.
print(np.all(twoloop == listcomprehension))
# True
表现结果(约):
differenceFunction_2loop() = 0.47s
differenceFunction_1loop() = 0.003s
differenceFunction_1loop_listcomp() = 0.033s
答案 4 :(得分:0)
我不知道如何找到嵌套循环问题的替代方法,但对于算术函数,您可以使用numpy库。它比手动操作更快。
import numpy as np
tmp = np.subtract(long(x[j] ,x[j + tau])
答案 5 :(得分:0)
我会做这样的事情:
>>> x = np.random.randint(0, high=32000, size=2048, dtype='int16')
>>> tau_max = 106
>>> res = np.square((x[tau_max:] - x[:-tau_max]))
但我确信这不是最快的方法。
答案 6 :(得分:0)
我试图弄清楚最快的答案,而我只是想出了一个更快,更简单的解决方案。
def autocorrelation(x):
result = np.correlate(x, x, mode='full')
return result[result.size // 2:]
def difference(x):
return np.dot(x, x) + (x * x)[::-1].cumsum()[::-1] - 2 * autocorrelation(x)
解决方案基于the YIN paper中定义的difference
函数。
%%timeit
difference(frame)
140 µs ± 438 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)