Как реализовать кросскорреляцию Pytorch 1D для длинных сигналов в области Фурье?

У меня есть серия сигналов длиной n = 36 000, на которых мне нужно выполнить взаимную корреляцию. В настоящее время реализация моего процессора в numpy немного медленная. Я слышал, что Pytorch может значительно ускорить тензорные операции и предоставляет возможность параллельного выполнения вычислений на графическом процессоре. Я хотел бы изучить этот вариант, но я не совсем уверен, как это сделать с помощью фреймворка.

Из-за длины этих сигналов я бы предпочел выполнять операцию взаимной корреляции в частотной области.

Обычно, используя numpy, я бы выполнил операцию следующим образом:

import numpy as np

signal_length=36000

# make the signals
signal_1 = np.random.uniform(-1,1, signal_length)
signal_2 = np.random.uniform(-1,1, signal_length)

# output target length of crosscorrelation
x_cor_sig_length = signal_length*2 - 1

# get optimized array length for fft computation
fast_length = np.fftpack.next_fast_len(x_cor_sig_length)

# move data into the frequency domain. axis=-1 to perform 
# along last dimension
fft_1 = np.fft.rfft(src_data, fast_length, axis=-1)
fft_2 = np.fft.rfft(src_data, fast_length, axis=-1)

# take the complex conjugate of one of the spectrums. Which one you choose depends on domain specific conventions
fft_1 = np.conj(fft_1)


fft_multiplied = fft_1 * fft_2

# back to time domain. 
prelim_correlation = np.fft.irfft(result, x_corr_sig_length, axis=-1)

# shift the signal to make it look like a proper crosscorrelation,
# and transform the output to be purely real

final_result = np.real(np.fft.fftshift(prelim_correlation),axes=-1)).astype(np.float64)

Глядя на документацию Pytorch, похоже, нет эквивалента для numpy.conj(). Я также не уверен, нужно ли/как мне реализовать fftshift после операции irfft.

Итак, как бы вы написали одномерную взаимную корреляцию в Pytorch, используя метод Фурье?


person KEVIN MENDOZA    schedule 22.06.2019    source источник


Ответы (1)


Несколько вещей, которые следует учитывать.

Интерпретатор Python очень медленный, и эти библиотеки векторизации переносят рабочую нагрузку на нативную реализацию. Чтобы иметь какое-либо значение, вам нужно иметь возможность выполнять множество операций в одной инструкции Python. Оценка вещей на графическом процессоре следует тому же принципу, хотя у графического процессора больше вычислительная мощность, он медленнее копирует данные в/из графического процессора.

Я адаптировал ваш пример для одновременной обработки нескольких сигналов.

import numpy as np
def numpy_xcorr(BATCH=1, signal_length=36000):
    # make the signals
    signal_1 = np.random.uniform(-1,1, (BATCH, signal_length))
    signal_2 = np.random.uniform(-1,1, (BATCH, signal_length))

    # output target length of crosscorrelation
    x_cor_sig_length = signal_length*2 - 1

    # get optimized array length for fft computation
    fast_length = next_fast_len(x_cor_sig_length)

    # move data into the frequency domain. axis=-1 to perform 
    # along last dimension
    fft_1 = np.fft.rfft(signal_1, fast_length, axis=-1)
    fft_2 = np.fft.rfft(signal_2 + 0.1 * signal_1, fast_length, axis=-1)

    # take the complex conjugate of one of the spectrums. 
    fft_1 = np.conj(fft_1)


    fft_multiplied = fft_1 * fft_2

    # back to time domain. 
    prelim_correlation = np.fft.irfft(fft_multiplied, fast_length, axis=-1)

    # shift the signal to make it look like a proper crosscorrelation,
    # and transform the output to be purely real

    final_result = np.fft.fftshift(np.real(prelim_correlation), axes=-1)
    return final_result, np.sum(final_result)

Начиная с torch 1.7 у нас есть torch.fft, который предоставляет интерфейс, аналогичный numpy.fft, fftshift отсутствует, но тот же результат можно получить с помощью torch.roll. Еще один момент заключается в том, что numpy по умолчанию использует 64-битную точность, а torch будет использовать 32-битную точность.

Быстрая длина состоит в выборе гладких чисел (те, которые разложены на маленькие простые числа, и я полагаю, вы знакомы с этой темой).

def next_fast_len(n, factors=[2, 3, 5, 7]):
    '''
      Returns the minimum integer not smaller than n that can
      be written as a product (possibly with repettitions) of
      the given factors.
    '''
    best = float('inf')
    stack = [1]
    while len(stack):
        a = stack.pop()
        if a >= n:
            if a < best:
                best = a;
                if best == n:
                    break; # no reason to keep searching
        else:
            for p in factors:
                b = a * p;
                if b < best:
                    stack.append(b)
    return best;

Затем идет реализация факела

import torch;
import torch.fft
def torch_xcorr(BATCH=1, signal_length=36000, device='cpu', factors=[2,3,5], dtype=torch.float):
    signal_length=36000
    # torch.rand is random in the range (0, 1)
    signal_1 = 1 - 2*torch.rand((BATCH, signal_length), device=device, dtype=dtype)
    signal_2 = 1 - 2*torch.rand((BATCH, signal_length), device=device, dtype=dtype)

    # just make the cross correlation more interesting
    signal_2 += 0.1 * signal_1;

    # output target length of crosscorrelation
    x_cor_sig_length = signal_length*2 - 1

    # get optimized array length for fft computation
    fast_length = next_fast_len(x_cor_sig_length, [2, 3])

    # the last signal_ndim axes (1,2 or 3) will be transformed
    fft_1 = torch.fft.rfft(signal_1, fast_length, dim=-1)
    fft_2 = torch.fft.rfft(signal_2, fast_length, dim=-1)

    # take the complex conjugate of one of the spectrums. Which one you choose depends on domain specific conventions

    fft_multiplied = torch.conj(fft_1) * fft_2

    # back to time domain. 
    prelim_correlation = torch.fft.irfft(fft_multiplied, dim=-1)

    # shift the signal to make it look like a proper crosscorrelation,
    # and transform the output to be purely real

    final_result = torch.roll(prelim_correlation, (fast_length//2,))
    return final_result, torch.sum(final_result);

А вот код для проверки результатов

import time
funcs = {'numpy-f64': lambda b: numpy_xcorr(b, factors=[2,3,5], dtype=np.float64), 
         'numpy-f32': lambda b: numpy_xcorr(b, factors=[2,3,5], dtype=np.float32), 
         'torch-cpu-f64': lambda b: torch_xcorr(b, device='cpu', factors=[2,3,5], dtype=torch.float64), 
         'torch-cpu': lambda b: torch_xcorr(b, device='cpu', factors=[2,3,5], dtype=torch.float32), 
         'torch-gpu-f64': lambda b: torch_xcorr(b, device='cuda', factors=[2,3,5], dtype=torch.float64),
         'torch-gpu': lambda b: torch_xcorr(b, device='cuda', factors=[2,3,5], dtype=torch.float32),
         }
times ={}
for batch in [1, 10, 100]:
    times[batch] = {}
    for l, f in funcs.items():
        t0 = time.time()
        t1, t2 = f(batch)
        tf = time.time()
        del t1
        del t2
        times[batch][l] = 1000 * (tf - t0) / batch;

Я получил следующие результаты

Время выполнения для 32-битной и 64-битной точности, разного размера пакета, с использованием numpy, pytorch-cpu и pytorch-gpu, с длиной плавного БПФ 5

Время выполнения для 32-битной и 64-битной точности, разного размера пакета, с использованием numpy, pytorch-cpu и pytorch-gpu, с длиной плавного БПФ 5

И что меня удивило, так это результат, когда числа не такие гладкие, например. при использовании 17-кратной гладкой длины реализация факела настолько лучше, что я использовал здесь логарифмическую шкалу (с размером пакета 100 GPU факела был в 10000 раз быстрее, чем numpy с размером пакета 1).

Время выполнения для 32-битной и 64-битной точности, разного размера пакета, с использованием numpy, pytorch-cpu и pytorch-gpu, с длиной плавного БПФ 5

Помните, что эти функции генерируют данные в графическом процессоре, в общем, мы хотим скопировать окончательные результаты в ЦП, если учесть время, затрачиваемое на копирование конечного результата в ЦП, я наблюдал время до 10 раз выше, чем вычисление кросс-корреляции (случайное генерация данных + три БПФ).

person Bob    schedule 09.02.2021