Cython и скорость numpy

Я использую cython для расчета корреляции в моей программе на Python. У меня есть два набора аудиоданных, и мне нужно знать разницу во времени между ними. Второй набор разрезается на основе времени начала, а затем скользит по первому набору. Есть два цикла for: один перемещает набор, а внутренний цикл вычисляет корреляцию в этой точке. Этот метод работает очень хорошо и достаточно точен.

Проблема в том, что с чистым питоном это занимает больше одной минуты. С моим кодом 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

person jushie    schedule 29.07.2009    source источник
comment
Просто из любопытства, какое количество отсчетов в каждом аудиосигнале?   -  person las3rjock    schedule 05.08.2009
comment
Для расчета задержки вырезается около 10.000 сэмплов. Это означает 10 тысяч шагов при скольжении окна.   -  person jushie    schedule 05.08.2009
comment
Ах. Прямая корреляция - O (n ^ 2), поэтому порядка 100000000 операций для n = 10000. Корреляция на основе БПФ - O (n lg n), то есть порядка 132 877 операций для n = 10 000.   -  person las3rjock    schedule 06.08.2009


Ответы (3)


Изменить:
Теперь есть scipy.signal.fftconvolve, который был бы предпочтительным подходом к реализации подхода свертки на основе БПФ, который я описываю ниже. Я оставлю исходный ответ, чтобы объяснить проблему скорости, но на практике используйте scipy.signal.fftconvolve.

Исходный ответ:
Использование БПФ и теорема свертки даст вам резкое увеличение скорости за счет преобразования задачи из O (n ^ 2) в O (n log n). Это особенно полезно для длинных наборов данных, таких как ваш, и может дать прирост скорости на 1000 секунд или намного больше, в зависимости от длины. Это также легко сделать: просто БПФ обоих сигналов умножьте и обратное БПФ произведите. numpy.correlate не использует метод БПФ в программе взаимной корреляции, и его лучше использовать с очень маленькими ядрами.

Вот пример

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 рассматривает его как ноль, а БПФ рассматривает формы волны как периодические, но если это проблема, ее можно исправить с помощью заполнения нулями.

person tom10    schedule 04.08.2009
comment
Ваше время: 10 000 или диапазон (0, 100, 0,001)? - person denis; 24.11.2009

Уловка с подобными вещами состоит в том, чтобы найти способ разделять и властвовать.

В настоящее время вы перемещаетесь в каждую позицию и проверяете каждую точку в каждой позиции - фактически операция O (n ^ 2).

Вам нужно уменьшить количество проверок каждой точки и сравнения каждой позиции с чем-то, что делает меньше работы для определения несоответствия.

Например, вы могли бы короче "это хоть близко?" фильтр, проверяющий первые несколько позиций. Если корреляция выше некоторого порога, то продолжайте, в противном случае сдайтесь и двигайтесь дальше.

У вас может быть «чек на каждую 8-ю позицию», который вы умножаете на 8. Если это слишком мало, пропустите его и двигайтесь дальше. Если это достаточно много, проверьте все значения, чтобы увидеть, нашли ли вы максимумы.

Проблема в том, сколько времени требуется для выполнения всех этих умножений - (f[<unsigned int>(i+j)] * g[j]) Фактически, вы заполняете большую матрицу всеми этими продуктами и выбираете строку с максимальной суммой. Вы не хотите подсчитывать «все» продукты. Достаточно товаров, чтобы убедиться, что вы нашли максимальную сумму.

Проблема с поиском максимумов заключается в том, что вам нужно суммировать все, чтобы увидеть, является ли оно самым большим. Если вы можете превратить это в проблему минимизации, проще отказаться от вычислений продуктов и сумм, как только промежуточный результат превышает пороговое значение.

(Думаю, это может сработать. Я не пробовал.)

Если бы вы использовали max(g)-g[j] для работы с отрицательными числами, вы бы искали самые маленькие, а не самые большие. Вы можете вычислить корреляцию для первой позиции. Все, что суммировалось с большим значением, можно было немедленно остановить - больше никаких умножений или сложений для этого смещения, переходите к другому.

person S.Lott    schedule 29.07.2009
comment
Спасибо за Ваш ответ. Я обнаружил, что numpy.correlate () улучшает производительность как минимум в 10 раз. К сожалению, тогда я не могу использовать этот малейший трюк. - person jushie; 05.08.2009

  • вы можете извлечь диапазон (размер2) из ​​внешнего цикла
  • вы можете использовать sum () вместо цикла для вычисления current_correlation
  • вы можете хранить корреляции и задержки в списке, а затем использовать max (), чтобы получить самый большой
person dugres    schedule 29.07.2009
comment
Спасибо за помощь. Сначала я использовал sum (), но numpy.correlate () для этого быстрее. Теперь я также сохраняю значения и использую max (), как вы сказали. - person jushie; 05.08.2009