C++ 用FFT(FFTW)计算两个函数的卷积

C++ 用FFT(FFTW)计算两个函数的卷积,c++,math,fft,fftw,biological-neural-network,C++,Math,Fft,Fftw,Biological Neural Network,我正在尝试使用FFT加速神经模拟器的计算 方程式为: (1) \sum(j=1到N)(w(i-j)*s_NMDA[j]) 其中,s_NMDA是长度为N的向量,w的定义如下: (2) w(j)=tanh[1/(2*σ*p)]*exp(-abs(j)/(σ*p)] 其中σ和p是常数 (是否有更好的方法在stackoverflow上渲染方程式?) 必须对N个神经元进行计算,因为(1)只取决于绝对距离abs(i-j),所以应该可以使用FFT(卷积定理)来计算 我曾尝试使用FFTW实现这一点,但结果与预期

我正在尝试使用FFT加速神经模拟器的计算

方程式为:

(1) \sum(j=1到N)(w(i-j)*s_NMDA[j])

其中,s_NMDA是长度为N的向量,w的定义如下:

(2) w(j)=tanh[1/(2*σ*p)]*exp(-abs(j)/(σ*p)]

其中σ和p是常数

(是否有更好的方法在stackoverflow上渲染方程式?)

必须对N个神经元进行计算,因为(1)只取决于绝对距离abs(i-j),所以应该可以使用FFT(卷积定理)来计算

我曾尝试使用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);
结果似乎几乎是正确的,但误差随着神经元数量的增加而增加。而且,对于位置(i)非常低或非常高,结果似乎更准确。这里发生了什么

更新:根据Oli Charlesworth的建议,我以八度音阶实现了该算法,以查看它是一个实现问题还是一个数学问题:

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
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

结果基本相同。因此,我对卷积定理/FFT的理解肯定有问题。

要通过FFT卷积2个信号,通常需要执行以下操作:

  • 根据需要向每个信号添加尽可能多的零,使其长度成为原始信号的累积长度-1(即卷积结果的长度)
  • 如果您的FFT库要求输入长度为2的幂,则向每个信号添加尽可能多的零以满足该要求
  • 计算信号1的DFT(通过FFT)
  • 计算信号2的DFT(通过FFT)
  • 将两个DFT元素相乘。顺便说一句,它应该是复数乘法
  • 计算相乘DFT的逆DFT(通过FFT)。这将是卷积结果
  • 在您的代码中,我在所有3个FFT中都看到了
    FFTW\u FORWARD
    。我猜如果这不是问题,它就是问题的一部分。最后一个FFT应该是“向后”,而不是“向前”

    另外,我认为在第二个表达式中需要“+”而不是“-”:


    我终于解决了这个问题,非常感谢Alex和Oli Charlesworth的建议

    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.023036
       0.023111
       0.023183
       0.023253
       0.022537
       0.022627
       0.022714
       0.022798
       0.022880
    
    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],我就完成了:)


    StackOverflow不直接支持Latex。但是,你可以使用像这样的网站,它可以动态生成图像。至于核心问题,我认为你首先需要确定你是在看数学问题还是FFTW问题。我建议用Matlab/Octave之类的东西来原型化你的算法。或者,您可以尝试使用朴素的DFT算法(大约7行代码)执行计算。如果这是有效的,那么你就知道你对FFTW做了错事!谢谢你的建议。不幸的是,我不能包括图片,因为我还没有足够的声誉。我将尝试用倍频程实现一个简单的原型,并比较结果。谢谢你的帮助。我代码中的两个错误你是对的。不过,它们只是问题的一部分。距离数组的顺序还有另一个问题,但我在接下来的五个小时内无法回答我自己的问题,因为我的声誉不到10个,所以我想我必须等待,然后才能发布它…所以,您的解决方案不再涉及C(++)代码和FFTW了?我想,我想说的是,对StackOverflow问题的回答应该回答最初的问题,而不仅仅是“谢谢,它现在起作用了”,这就是这个“答案”的外观。你可以回答自己的问题,这很好,但这应该是一个答案,而不仅仅是一次对话。对此,我们有意见,我认为这是“答案”的归属。如果别人的回答对你有所帮助,请投票支持和/或接受任何合适的答案。这就是它的工作原理。嘿,正如我的问题更新中所述,这个问题原来是一个数学问题,而不是我最初认为的FFTW问题。虽然亚历克斯的回答对我很有帮助,但并没有解决我面临的实际问题,所以我没有接受他的回答。我想给他投票,但我没有足够的声望去做。为什么我的答案只包含MATLAB代码和C++代码,因为人们可以自己尝试MATLAB代码,而我原来问题中的C++代码是一个更大程序的一部分而不是自己运行的。如果我错了请纠正我,我是新来的。我也可以发布一个更新的C++版本,但是我不真正明白这一点,因为除了亚历克斯提到的两个问题之外,代码没有什么问题,它只是没有达到我最初预期的那样。关键是,如果没有人的答案真的帮助了你或者你有最好的答案,你可以自由回答你自己的问题。(甚至接受它)。但是我不认为这里是这样的,因为你已经指出了C++实现中的2个主要错误。
    convolution = ifft(convolution_f)(n:end)