Вычисление свертки двух функций с использованием FFT (FFTW)
Я пытаюсь ускорить вычисления для нейронного симулятора с использованием БПФ.
Уравнение:
(1) \ сумма (от j=1 до N) (w(i - j) * s_NMDA[j])
где s_NMDA - вектор длины N, а w определяется как:
(2) w (j) = tanh [1 / (2 * сигма * p)] * exp(-abs(j) / (сигма * p)]
где сигма и р постоянные.
(Есть ли лучший способ визуализации уравнений на стеке потока?)
Расчет должен быть сделан для N нейронов. Поскольку (1) зависит только от абсолютного расстояния abs(i - j), его можно рассчитать, используя БПФ (теорема свертки).
Я пытался реализовать это с помощью FFTW, но результаты не совпадают с ожидаемыми результатами. Я никогда не использовал FFTW раньше, и теперь я не уверен, что моя реализация неверна, если мои предположения о теореме о свертке неверны.
void f_I_NMDA_FFT(
const double **states, // states[i][6] == s_NMDA[i]
const unsigned int numNeurons)
{
fftw_complex *distances, *sNMDAs, *convolution;
fftw_complex *distances_f, *sNMDAs_f, *convolution_f;
fftw_plan p, pinv;
const double scale = 1./numNeurons;
distances = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * numNeurons);
sNMDAs = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * numNeurons);
convolution = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * numNeurons);
distances_f = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * numNeurons);
sNMDAs_f = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * numNeurons);
convolution_f = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * numNeurons);
// fill input array for distances
for (unsigned int i = 0; i < numNeurons; ++i)
{
distances[i][0] = w(i);
distances[i][1] = 0;
}
// fill input array for sNMDAs
for (unsigned int i = 0; i < numNeurons; ++i)
{
sNMDAs[i][0] = states[i][6];
sNMDAs[i][1] = 0;
}
p = fftw_plan_dft_1d(numNeurons,
distances,
distances_f,
FFTW_FORWARD,
FFTW_ESTIMATE);
fftw_execute(p);
p = fftw_plan_dft_1d(numNeurons,
sNMDAs,
sNMDAs_f,
FFTW_FORWARD,
FFTW_ESTIMATE);
fftw_execute(p);
// convolution in frequency domain
for(unsigned int i = 0; i < numNeurons; ++i)
{
convolution_f[i][0] = (distances_f[i][0] * sNMDAs_f[i][0]
- distances_f[i][1] * sNMDAs_f[i][1]) * scale;
convolution_f[i][1] = (distances_f[i][0] * sNMDAs_f[i][1]
- distances_f[i][1] * sNMDAs_f[i][0]) * scale;
}
pinv = fftw_plan_dft_1d(numNeurons,
convolution_f,
convolution,
FFTW_FORWARD,
FFTW_ESTIMATE);
fftw_execute(pinv);
// compute and compare with expected result
for (unsigned int i = 0; i < numNeurons; ++i)
{
double expected = 0;
for (int j = 0; j < numNeurons; ++j)
{
expected += w(i - j) * states[j][6];
}
printf("i=%d, FFT: r%f, i%f : Expected: %f\n", i, convolution[i][0], convolution[i][1], expected);
}
fftw_destroy_plan(p);
fftw_destroy_plan(pinv);
fftw_free(distances), fftw_free(sNMDAs), fftw_free(convolution);
fftw_free(distances_f), fftw_free(sNMDAs_f), fftw_free(convolution_f);
Вот пример вывода для 20 нейронов:
i=0, FFT: r0.042309, i0.000000 : Expected: 0.041504
i=1, FFT: r0.042389, i0.000000 : Expected: 0.042639
i=2, FFT: r0.042466, i0.000000 : Expected: 0.043633
i=3, FFT: r0.042543, i0.000000 : Expected: 0.044487
i=4, FFT: r0.041940, i0.000000 : Expected: 0.045203
i=5, FFT: r0.041334, i0.000000 : Expected: 0.045963
i=6, FFT: r0.041405, i0.000000 : Expected: 0.046585
i=7, FFT: r0.041472, i0.000000 : Expected: 0.047070
i=8, FFT: r0.041537, i0.000000 : Expected: 0.047419
i=9, FFT: r0.041600, i0.000000 : Expected: 0.047631
i=10, FFT: r0.041660, i0.000000 : Expected: 0.047708
i=11, FFT: r0.041717, i0.000000 : Expected: 0.047649
i=12, FFT: r0.041773, i0.000000 : Expected: 0.047454
i=13, FFT: r0.041826, i0.000000 : Expected: 0.047123
i=14, FFT: r0.041877, i0.000000 : Expected: 0.046656
i=15, FFT: r0.041926, i0.000000 : Expected: 0.046052
i=16, FFT: r0.041294, i0.000000 : Expected: 0.045310
i=17, FFT: r0.042059, i0.000000 : Expected: 0.044430
i=18, FFT: r0.042144, i0.000000 : Expected: 0.043412
i=19, FFT: r0.042228, i0.000000 : Expected: 0.042253
Результаты кажутся почти правильными, но ошибка увеличивается с числом нейронов. Кроме того, результаты кажутся более точными для позиций (i), которые являются очень низкими или очень высокими. Что тут происходит?
Обновление: как предложил Оли Чарльзуорт, я реализовал алгоритм в октаве, чтобы увидеть, является ли это реализацией или математической проблемой:
input = [0.186775; 0.186775; 0.186775; 0.186775; 0.186775; 0; 0.186775; 0.186775; 0.186775; 0.186775];
function ret = _w(i)
ret = tanh(1 / (2* 1 * 32)) * exp(-abs(i) / (1 * 32));
end
for i = linspace(1, 10, 10)
expected = 0;
for j = linspace(1, 10, 10)
expected += _w(i-j) * input(j);
end
expected
end
distances = _w(transpose(linspace(0, 9, 10)));
input_f = fft(input);
distances_f = fft(distances);
convolution_f = input_f .* distances_f;
convolution = ifft(convolution_f)
Результаты:
expected = 0.022959
expected = 0.023506
expected = 0.023893
expected = 0.024121
expected = 0.024190
expected = 0.024100
expected = 0.024034
expected = 0.023808
expected = 0.023424
expected = 0.022880
convolution =
0.022959
0.023036
0.023111
0.023183
0.023253
0.022537
0.022627
0.022714
0.022798
0.022880
Результаты очень похожи. Следовательно, должно быть что-то не так с моим пониманием теоремы о свертке / БПФ.
2 ответа
Я наконец решил проблему, большое спасибо Алексу и Оли Чарльзуорту за ваши предложения!
function ret = _w(i)
ret = tanh(1 / (2* 1 * 32)) * exp(-abs(i) / (1 * 32));
end
_input = [0.186775; 0.186775; 0.186775; 0.186775; 0.186775; 0; 0.186775; 0.186775; 0.186775; 0.186775];
n = size(_input)(1);
input = _input;
for i = linspace(1, n, n)
expected = 0;
for j = linspace(1, n, n)
expected += _w(i-j) * input(j);
end
expected
end
input = vertcat(_input, zeros((2*n)-n-1,1));
distances = _w(transpose(linspace(0, (2*n)-n-1, n)));
distances = vertcat(flipud(distances), distances(2:end));
input_f = fft(input);
distances_f = fft(distances);
convolution_f = input_f .* distances_f;
convolution = ifft(convolution_f)(n:end)
Результаты:
expected = 0.022959
expected = 0.023506
expected = 0.023893
expected = 0.024121
expected = 0.024190
expected = 0.024100
expected = 0.024034
expected = 0.023808
expected = 0.023424
expected = 0.022880
convolution =
0.022959
0.023506
0.023893
0.024121
0.024190
0.024100
0.024034
0.023808
0.023424
0.022880
Я в основном забыл правильно заказать массив расстояний. Если кому-то интересно, я могу предоставить более подробное объяснение позже.
Обновление: (объяснение)
Вот как выглядел мой вектор расстояний (для 5 нейронов) изначально:
i = 1 2 3 4 5
| _w(0) | _w(1) | _w(2) | _w(3) | _w(4) |
На этот вектор я применил ядро, например:
| 0.1 | 0.1 | 0.0 | 0.2 | 0.3 |
Поскольку я использовал циклическую свертку, результат для первого нейрона _w (0) был:
0,0 * _w(2) + 0,1 * _w(1) + 0,1 * _w(0) + 0,1 * _w(1) + 0,0 * _w (2)
Но это неверно, результат должен быть:
0,1 * _w(0) + 0,1 * _w(1) + 0,0 * _w(2) + 0,2 * _w(3) + 0,3 * _w(4)
Чтобы добиться этого, мне нужно было "отразить" мой вектор расстояний и добавить некоторые дополнения к ядру:
input = vertcat(_input, zeros((2*n)-n-1,1));
distances = _w(transpose(linspace(0, (2*n)-n-1, n)));
distances = _w(transpose(linspace(0, (2*n)-n-1, n)));
i = 1 2 3 4 5 6 7 8 9
| _w(4) | _w(3) | _w(2) | _w(1) | _w(0) | _w(1) | _w(2) | _w(3) | _w(4) |
| 0.1 | 0.1 | 0.0 | 0.2 | 0.3 | 0 | 0 | 0 | 0 |
Теперь, если я применю свертку, результаты для i = [5:9] - это именно те результаты, которые я искал, так что мне останется только отказаться от [1:4], и все готово:)
convolution = ifft(convolution_f)(n:end)
Для свертки 2 сигналов через БПФ вам, как правило, нужно сделать следующее:
- Добавьте столько нулей к каждому сигналу, сколько необходимо, чтобы его длина стала суммарной длиной исходных сигналов - 1 (это длина результата свертки).
- Если вашей библиотеке FFT требуется, чтобы входные длины были степенями 2, добавьте к каждому сигналу столько нулей, сколько необходимо для удовлетворения этого требования.
- Рассчитать ДПФ сигнала 1 (через БПФ).
- Рассчитать ДПФ сигнала 2 (через БПФ).
- Умножьте два ДПФ поэлементно. Это должно быть сложное умножение, кстати.
- Рассчитать обратное ДПФ (через БПФ) умноженных ДПФ. Это будет твоим результатом свертки.
В вашем коде я вижу FFTW_FORWARD
во всех 3 БПФ. Я думаю, если это не проблема, это часть этого. Последний БПФ должен быть "назад", а не "вперед".
Кроме того, я думаю, что вам нужно "+" во втором выражении, а не "-":
convolution_f[i][0] = (distances_f[i][0] * sNMDAs_f[i][0]
- distances_f[i][1] * sNMDAs_f[i][1]) * scale;
convolution_f[i][1] = (distances_f[i][0] * sNMDAs_f[i][1]
- distances_f[i][1] * sNMDAs_f[i][0]) * scale;