簡體   English   中英

用FFT重復計算函數導數時的數值誤差

[英]Numerical error building up when computing derivative of function repeatedly with FFT

我編寫了一個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的價值並不能解決你的問題,即使它提高了20階導數的准確性。 問題是通過重復導數濾波器來增加小錯誤。 為了限制此問題,可以建議引入低通濾波器以及使用四倍精度。 引入條件數的概念有助於理解錯誤的膨脹方式並相應地設置過濾器。 盡管如此,計算20階導數仍將是一場噩夢,因為計算20階導數是不合理的 :即使對於最干凈的實驗輸入,這根本不可能......

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.由於微分濾波器的條件數,誤差正在增加

該導數是線性應用,其特征在於條件數 假設輸入受到所有頻率上的錯誤eps困擾。 如果第一頻率被因子alpha放大,則頻率k被放大因子k*alpha 因此,每次應用導數時,信噪比除以比率kc(最大頻率),稱為條件數。 如果濾波器重復20次,則信噪比除以kc ^ 20。

雙精度數約為eps=10e-14精度:這是您可以獲得的最佳信噪比! 大多數實驗投入都會比這更糟糕。 例如,通常使用16位= 65536灰度級對灰度圖像進行采樣。 結果,灰度圖像的精度最多為eps=1/65536 類似地,典型的音頻比特深度是24,對應於eps = 6e-8。 對於接近分析的輸入,可以建議四倍精度,精度約為esp = 1e-34 ...讓我們找到kc ^ 20 * eps <1的頻率:

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的文檔,以便為gcc的四元精度類型__float128鏈接四元組編譯fftw 如果輸入是圖像,那么計算20階導數就超出了范圍:沒有一個頻率會變得很重要!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM