【问题标题】:Numerical error building up when computing derivative of function repeatedly with FFT使用 FFT 重复计算函数导数时产生的数值误差
【发布时间】:2018-07-05 05:56:24
【问题描述】:

我编写了一个 C 程序,它使用 FFTW 计算函数的导数(重复)。我正在测试简单的 sin(x) 函数。每一步都计算上一步答案的导数。我观察到错误会产生,并且在 20 步后,这个数字是纯垃圾。附件是示例输出。答案(在特定点)应该是 0、+1 或 -1,但不是。

---- out ----  data '(0) = 1.000000 -0.000000 
---- out ----  data '(1) = 0.000000 -0.000000 
---- out ----  data '(2) = -1.000000 0.000000 
---- out ----  data '(3) = -0.000000 0.000000 
---- out ----  data '(4) = 1.000000 -0.000000 
---- out ----  data '(5) = 0.000000 -0.000000 
---- out ----  data '(6) = -1.000000 0.000000 
---- out ----  data '(7) = -0.000000 0.000000 
---- out ----  data '(8) = 1.000000 -0.000000 
---- out ----  data '(9) = 0.000000 -0.000000 
---- out ----  data '(10) = -1.000000 0.000000 
---- out ----  data '(11) = -0.000000 0.000000 
---- out ----  data '(12) = 1.000000 -0.000002 
---- out ----  data '(13) = 0.000007 -0.000000 
---- out ----  data '(14) = -1.000000 0.000028 
---- out ----  data '(15) = -0.000113 0.000000 
---- out ----  data '(16) = 0.999997 -0.000444 
---- out ----  data '(17) = 0.001798 -0.000000 
---- out ----  data '(18) = -0.999969 0.007110 
---- out ----  data '(19) = -0.028621 0.000004  

我无法弄清楚为什么错误会不断增加。任何建议都深表感谢。我将实函数包装成复杂数据类型并将虚部设置为零。 代码如下:

# include <stdlib.h>
# include <stdio.h>
# include <time.h>
# include <math.h>
# include <complex.h>
# include <fftw3.h>

int main ( int argc, char *argv[] ){
  int i;
  fftw_complex *in;
  fftw_complex *in2;
  fftw_complex *out;

  double pi = 3.14159265359;
  int nx = 8, k, t, tf = 20;
  double xi = 0, xf = 2*pi;
  double dx = (xf - xi)/((double)nx); // Step size

  complex double  *kx;

  fftw_plan plan_backward;
  fftw_plan plan_forward;

  in = fftw_malloc ( sizeof ( fftw_complex ) * nx );
  out = fftw_malloc ( sizeof ( fftw_complex ) * nx );
  in2 = fftw_malloc ( sizeof ( fftw_complex ) * nx );


  kx = malloc ( sizeof ( complex ) * nx );

  // only need it once, hence outside the loop
  for (k = 0; k < nx; k++){
     if (k < nx/2){
            kx[k] = I*2*pi*k/xf;
     } else if (k > nx/2){
           kx[k] = I*2*pi*(k-nx)/xf;
     } else if (k == nx/2){
        kx[k] = 0.0;
     }
  }

  // create plan outside the loop
  plan_forward = fftw_plan_dft_1d ( nx, in, out, FFTW_FORWARD, FFTW_ESTIMATE );
  plan_backward = fftw_plan_dft_1d ( nx, out, in2, FFTW_BACKWARD, FFTW_ESTIMATE );


  // initialize data
  for ( i = 0; i < nx; i++ )
  {
    in[i] = sin(i*dx) + I*0.0;  // note the complex notation.
  }
//-------------------- start time loop ---------------------------------------//

for (t = 0; t < tf; t++){
  // print input data 
  //for ( i = 0; i < nx; i++ ) { printf("initial data '(%f) = %f %f \n", i*dx, creal(in[i]), cimag(in[i]) ); }

  fftw_execute ( plan_forward );

  for ( i = 0; i < nx; i++ )
  {
    out[i] = out[i]*kx[i]; // for first order derivative
  }

  fftw_execute ( plan_backward );
  // normalize
  for ( i = 0; i < nx; i++ )
  {
    in2[i] = in2[i]/nx;
  }
    printf("---- out ----  data '(%d) = %f %f \n", t, creal(in2[0]), cimag(in2[0]) );
  // overwrite input array with this new output and loop over
  for ( i = 0; i < nx; i++ )
  {
    in[i] = in2[i];
  }
  // done with the curent loop.
}
//--------------------- end of loop ----------------------------------------//

  fftw_destroy_plan ( plan_forward );
  fftw_destroy_plan ( plan_backward );

  fftw_free ( in );
  fftw_free ( in2 );
  fftw_free ( out );

  return 0;
}

使用 gcc source.c -lfftw3 -lm 编译

更新:这是 M_PI 循环 25 次的输出。同样的错误累积。

---- out ----  data '(0) = 1.000000 0.000000 
---- out ----  data '(1) = -0.000000 -0.000000 
---- out ----  data '(2) = -1.000000 -0.000000 
---- out ----  data '(3) = 0.000000 0.000000 
---- out ----  data '(4) = 1.000000 0.000000 
---- out ----  data '(5) = -0.000000 -0.000000 
---- out ----  data '(6) = -1.000000 -0.000000 
---- out ----  data '(7) = 0.000000 0.000000 
---- out ----  data '(8) = 1.000000 0.000000 
---- out ----  data '(9) = -0.000000 -0.000000 
---- out ----  data '(10) = -1.000000 -0.000000 
---- out ----  data '(11) = 0.000000 0.000000 
---- out ----  data '(12) = 1.000000 0.000000 
---- out ----  data '(13) = -0.000000 -0.000000 
---- out ----  data '(14) = -1.000000 -0.000000 
---- out ----  data '(15) = 0.000000 0.000000 
---- out ----  data '(16) = 1.000000 0.000001 
---- out ----  data '(17) = -0.000002 -0.000000 
---- out ----  data '(18) = -0.999999 -0.000008 
---- out ----  data '(19) = 0.000033 0.000004 
---- out ----  data '(20) = 0.999984 0.000132 
---- out ----  data '(21) = -0.000527 -0.000069 
---- out ----  data '(22) = -0.999735 -0.002104 
---- out ----  data '(23) = 0.008427 0.001099 
---- out ----  data '(24) = 0.995697 0.033667 

【问题讨论】:

  • 您的 pi 值不够准确。您应该使用 math.h 中定义的 M_PI
  • 是的,确实,如果 pi 的值不够准确,信号就不再是连续周期性的,会出现杂散的高频。然后,采用导数(斜坡滤波器)放大这些频率,使信噪比显着降低。要解决此问题,请将斜坡滤波器与低通滤波器结合使用,如 owlnet.rice.edu/~elec539/Projects97/cult/node4.html 所示
  • 更改为 M_PI 确实有帮助,但作用不大。错误确实减少了。这是最后两次迭代的 M_PI 输出。 ----出----数据'(18)= -0.999999 -0.000008 ----出----数据'(19)= 0.000033 0.000004。如果我超过 20,那么错误仍然会不断累积。

标签: c fft rounding fftw


【解决方案1】:

确实,优化 pi 的值并不能解决您的问题,即使它提高了 20 次导数的准确性。 问题在于重复微分滤波器会扩大小误差。 为了限制这个问题,可以建议引入低通滤波器以及使用四倍精度。引入条件数的概念有助于理解错误被夸大的方式并相应地设置过滤器。 尽管如此,计算 20 次导数仍将是一场噩梦,因为 computing the 20th derivative is ill-conditionned:这根本不可能,即使对于最干净的实验输入...

1.小错误总是存在的。

导数滤波器,也称为斜坡滤波器,比小频率更能膨胀高频。重复使用斜坡滤波器会显着放大高频上的最轻微误差。

让我们通过打印频率来查看小的初始误差

printf("---- out ----  data '(%d) %d = %20g %20g \n", t,i, creal(out[i]), cimag(out[i]) );

当使用pi=3.14159265359 时,您会得到:

---- out ----  data '(0) 0 =         -2.06712e-13                    0 
---- out ----  data '(0) 1 =          6.20699e-13                   -4 
---- out ----  data '(0) 2 =         -2.06823e-13          2.92322e-13 
---- out ----  data '(0) 3 =         -2.07053e-13          1.03695e-13 
---- out ----  data '(0) 4 =         -2.06934e-13                    0 
---- out ----  data '(0) 5 =         -2.07053e-13         -1.03695e-13 
---- out ----  data '(0) 6 =         -2.06823e-13         -2.92322e-13 
---- out ----  data '(0) 7 =          6.20699e-13                    4 

由于 pi 的缺失数字引起的不连续性,所有频率都有小的非空值,这些值通过求导而膨胀。

由于使用了pi=M_PI,这些初始错误更小,但仍然非空:

---- out ----  data '(0) 0 =          1.14424e-17                    0 
---- out ----  data '(0) 1 =         -4.36483e-16                   -4 
---- out ----  data '(0) 2 =          1.22465e-16         -1.11022e-16 
---- out ----  data '(0) 3 =          1.91554e-16         -4.44089e-16 
---- out ----  data '(0) 4 =          2.33487e-16                    0 
---- out ----  data '(0) 5 =          1.91554e-16          4.44089e-16 
---- out ----  data '(0) 6 =          1.22465e-16          1.11022e-16 
---- out ----  data '(0) 7 =         -4.36483e-16                    4 

这些小错误就像之前的错误一样被夸大了,问题并没有完全解决。让我们尝试在第一个循环期间将这些频率归零:

if(t==0){
     for (k = 0; k < nx; k++){
         if (k==1 || nx-k==1){
            out[k] = I*4.0;
         }else{
            out[k] =0.0;
         }
     }
}

这一次,第一个循环中唯一的非零频率 t=0 是正确的。我们来看第二个循环:

---- out ----  data '(1) 0 =                    0                    0 
---- out ----  data '(1) 1 =                   -4                    0 
---- out ----  data '(1) 2 =                    0                    0 
---- out ----  data '(1) 3 =         -4.44089e-16                    0 
---- out ----  data '(1) 4 =                    0                    0 
---- out ----  data '(1) 5 =          4.44089e-16                    0 
---- out ----  data '(1) 6 =                    0                    0 
---- out ----  data '(1) 7 =                    4                    0 

由于 DFT 后向/前向变换和缩放期间的有限精度计算,会出现小错误并被夸大。再次。

2。为了限制错误的增长,可以引入过滤。

大多数实验输入都受到大高频噪声的影响,可以通过应用低通滤波器(例如巴特沃斯滤波器)来减少这种噪声。有关详细信息和替代方案,请参阅https://www.hindawi.com/journals/ijbi/2011/693795/。该滤波器的特征是一个截止频率kc和一个指数,斜坡滤波器的频率响应修改如下:

    //parameters of Butterworth Filter:
    double kc=3;
    double n=16;
    // only need it once, hence outside the loop
    for (k = 0; k < nx; k++){
      if (k < nx/2){
        // add low pass filter:
        kx[k] = I*2*pi*k/xf;
        kx[k]*=1./(1.+pow(k/kc,n));
      } else if (k > nx/2){
        kx[k] = I*2*pi*(k-nx)/xf;
        kx[k]*=1./(1.+pow((nx-k)/kc,n));
      } else if (k == nx/2){
        kx[k] = 0.0;
      }
    }

使用这些参数,20 次导数的误差从 5.27e-7 至 1.22e-12。

通过不回到导数之间的真实空间,可以进行另一项改进。这样,避免了浮点计算期间的大量舍入误差。在这种特殊情况下,将输入频率归零可确保误差保持为零,但这有点人为......从实际的角度来看,如果输入信号是在实空间中提供的,则几乎需要使用滤波器来计算导数.

3.由于导数滤波器的条件数,误差正在增长

导数是一个线性应用程序,它的特征是condition number。假设输入在所有频率上都受到错误eps 的困扰。如果第一频率放大了alpha,则频率k 放大了k*alpha。因此,每次应用导数时,信噪比都要除以比率 kc(最大频率),称为条件数。如果滤波重复20次,信噪比除以kc^20。

双精度数约为eps=10e-14 精确:这是您可以获得的最佳信噪比!大多数实验输入会比这差得多。例如,灰度图像通常使用 16bit=65536 灰度级进行采样。结果,灰度图像最多eps=1/65536精确。类似地,典型的音频位深度为 24,对应于 eps=6e-8。对于几乎分析的输入,可以建议四倍精度,其精度约为 esp=1e-34... 让我们找到频率,使得 kc^20*eps

eps=10e-14    kc=5
eps=1/65536   kc=1
eps=1/2^24    kc=2
esp=1e-34     kc=44

因此,如果输入是双精度的,充其量只有 20 次导数的 4 个频率是显着的...... 所有高于 4 次的频率都必须通过强大的低-通过过滤器。因此,可以建议使用四精度:请参阅fftw's documentation 编译 fftw 以获得 gcc 的四精度类型 __float128 链接到 quadmath如果输入是图像,计算 20 次导数就超出了范围:没有一个频率会很重要!

【讨论】:

  • 我同意你的回答。谢谢!!!最终,我想在循环中使用 FFTW 来计算一个术语的一些导数或拉普拉斯算子。该术语本身在循环中发生变化,因此它与计算起始函数的 20 次导数不同。我以为我会通过这里发布的代码来测试最终实现,结果证明是一个极端情况。
  • 不客气!事实上,这个问题对于一阶导数和拉普拉斯算子并不重要,尽管在实验输入的情况下经常应用过滤。确实存在有价值且可靠的基于 FFT 的算法来计算各种线性微分问题的解决方案,其中 Green 算子以正弦输入而闻名。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-10
  • 2012-01-05
  • 2013-02-19
  • 1970-01-01
  • 2019-10-02
相关资源
最近更新 更多