Скорость Cython и numpy
Я использую cython для вычисления корреляции в моей программе python. У меня есть два набора аудиоданных, и мне нужно знать разницу во времени между ними. Второй набор вырезается в зависимости от времени начала и затем перемещается по первому набору. Существует два типа for-loops: один слайд установлен, а внутренний цикл вычисляет корреляцию в этой точке. Этот метод работает очень хорошо, и он достаточно точен.
Проблема в том, что с чистым 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
Ответы
Ответ 1
Edit:
Там теперь scipy.signal.fftconvolve
, что было бы предпочтительным подходом к выполнению подхода свертки на основе FFT, который я опишу ниже. Я оставлю исходный ответ, чтобы объяснить проблему скорости, но на практике используйте scipy.signal.fftconvolve
.
Оригинальный ответ:
Используя FFTs и теорема о свертке, вы получите резкую прирост скорости за счет преобразования проблемы от 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 рассматривает его как ноль, а БПФ обрабатывает формы сигналов как периодические, но если это проблема, то это может быть исправлено нулевым заполнением.
Ответ 2
Трюк с такой штукой - найти способ разделить и победить.
В настоящее время вы перемещаетесь в каждую позицию и проверяете каждую точку в каждой позиции - эффективно O (n ^ 2).
Вам нужно уменьшить проверку каждой точки и сопоставление каждой позиции с чем-то, что делает меньше работы, чтобы определить несоответствие.
Например, у вас может быть более короткое "это даже близко"? фильтр, который проверяет первые несколько позиций. Если корреляция выше некоторого порогового значения, тогда продолжайте идти иначе и продолжайте движение.
У вас может быть "проверка каждой восьмой позиции", которую вы умножаете на 8. Если это слишком мало, пропустите ее и перейдите. Если это достаточно высоко, проверьте все значения, чтобы узнать, были ли вы найдены максимумы.
Проблема - это время, необходимое для выполнения всех этих умножений - (f[<unsigned int>(i+j)] * g[j]
). По сути, вы заполняете большую матрицу всеми этими продуктами и выбираете строку с максимальной суммой. Вы не хотите вычислять "все" продукты. Всего достаточно продуктов, чтобы убедиться, что вы нашли максимальную сумму.
Проблема с поиском максимумов заключается в том, что вы должны суммировать все, чтобы увидеть, насколько она самая большая. Если вы можете превратить это в проблему минимизации, проще отказаться от вычислительных продуктов и сумм, если промежуточный результат превышает пороговое значение.
(Я думаю, это может сработать. Я не пробовал.)
Если вы использовали max(g)-g[j]
для работы с отрицательными номерами, вы бы искали самые маленькие, а не самые большие. Вы можете вычислить корреляцию для первой позиции. Все, что суммируется с большим значением, может быть немедленно остановлено - больше не умножается или не добавляется для этого смещения, смещение в другое.
Ответ 3
- вы можете извлечь диапазон (размер2) из внешнего цикла
- вы можете использовать sum() вместо цикла для вычисления current_correlation
- вы можете сохранить корреляции и задержки в списке, а затем использовать max(), чтобы получить самый большой