高速フーリエ変換(Fast Fourier Transform、FFT)とは、高速な離散フーリエ変換である。
高速フーリエ変換とは、離散フーリエ変換(DFT)を高速に行うためのアルゴリズムの総称である。DFTはO(N2)程度の計算量が要求されるが、FFTならばO(NlogN)程度まで計算量を削減できる。どれくらいかと言うと、通常のDFTは4096個のデータで100秒くらいかかるが、FFTなら20ミリ秒くらいで終わってしまうほど。
プログラムで実装する場合はクイックソートのような再帰的プログラムが簡潔になる。具体的には、データの列から半分の長さの列を2つ作り、それぞれの列に再帰的にプログラムを適用させる。
以下に記載する基本原理ではデータ数が2の冪のものに限定されるが、任意の自然数Nに対しO(NlogN)の計算量でより高速に計算できる改良型アルゴリズムが存在する。
1の原始N乗根のひとつをWN(=exp(-2πi/N))とする。(WNを回転子と呼ぶ。)
データ数Nの列a=[f(0), f(1), f(2), ... ,f(N-2),f(N-1)]に対する通常のフーリエ変換は以下の式で表される。
F(u)=(1/N)ΣN-1n=0f(n)WNnu=F(f)(u)
つまり、離散フーリエ変換Fにより、数列a=[f(0), ... ,f(N-1)]が数列A=[F(0), ... ,F(N-1) ]に移されるということである。
F : a → A
この計算では、N個のデータaからN個のデータAを作るために、(N回の積+N回の和)×N回≒O(N2)の計算が要求される。
高速フーリエ変換では半分の長さの配列に分割していく作業をする。1回半分にすると(N/2回の積+N/2回の和)×N/2回×2であり一度分割すると計算回数はおよそ半分になる。最終的に1配列あたりO(log2N)の計算量と、分割する作業がN-1回なので、全体としてO(Nlog2N)程度になる。
ここで、uを偶数と奇数の場合に場合分けしてみる。2N'=Nと置く。
N×F(2u')=ΣN-1n=0f(n)WNn×2u'
=ΣN'-1n=0f(n)W2N'2nu'+Σ2N'-1n=N'f(n)W2N'2nu'
=ΣN'-1n=0f(n)WN'nu'+ΣN'-1n=0f(n+N')WN'(n+N')u'
=ΣN'-1n=0f(n)WN'nu'+ΣN'-1n=0f(n+N')WN'N'u'WN'nu'
=ΣN'-1n=0(f(n)+f(n+N'))WN'nu'
N×F(2u'+1)=ΣN-1n=0f(n)WNn(2u'+1)
=ΣN'-1n=0f(n)W2N'n(2u'+1)+Σ2N'-1n=N'f(n)W2N'n(2u'+1)
=ΣN'-1n=0f(n)W2N'2nu'W2N'2n+ΣN'-1n=0f(n+N')W2N'(n+N')(2u'+1)
=ΣN'-1n=0f(n)WN'nu'WNn+ΣN'-1n=0f(n+N')W2N'2nu'W2N'2N'u'W2N'nW2N'N'
=ΣN'-1n=0f(n)WN'nWN'nu'+ΣN'-1n=0f(n+N')WN'nu'×1×WN'n×(-1)
=ΣN'-1n=0{(f(n)-f(n+N'))WN'n}WN'nu
ここで、
f'0(n)=f(n)+f(n+N')
f'1(n)=(f(n)-f(n+N'))WN'n
とおくと、
F(2u')=(1/2N')ΣN'-1n=0f'0(n)WN'nu'= (1/2)F(f'0)(u')
F(2u'+1)=(1/2N')ΣN'-1n=0f'1(n)WN'nu'= (1/2)F(f'1)(u')
となり、 奇数、偶数の場合それぞれにFを作用させることができる。
つまり、元の長さNの配列aを長さN/2=N'のa'0とa'1に合成&分解し、その後それぞれにフーリエ変換を施せばよい。Nが2で割り切れるかぎり、この作業を再帰的に適用させる事ができる。
Nが2の冪であるときは最終的にフーリエ変換により、F:[f(0), f(1)]→[(f(0)+f(1))/2, (f(0)-f(1))/2]という計算を生成された配列の数だけ繰り返すこととなる。
M>0、N>1の自然数とする。
長さN=2MLの配列aから長さN'=2M-1Lの配列a0, a1を生成する作用素をSM-1と置く。SM-1:a→a0, a1。 各成分は、
f0(n)=f(n)+f(n+N')
f1(n)=(f(n)-f(n+N'))WNn
である。
aにフーリエ変換FM-1を施した配列Aの各成分は、偶数、奇数で分類することができ、2N'=Nとすると、
F(2u')=(1/2N')ΣN'-1n=0(f(n)+f(n+N'))WN'nu'=ΣN'-1n=0f0(n)WN'nu'=(1/2)F(f0)(u')
F(2u'+1)=(1/2N')ΣN-1n=0((f(n)-f(n+N'))WNn)WN'nu'=ΣN-1n=0f1(n)WN'nu'=(1/2)F(f1)(u')
となる。
長さ2MNの配列Aを上記偶数成分、奇数成分で並び替える作用素をTM-1と置く。TM-1:A→A。各成分は、
F(u)=F(2u')
F(u+N’)=F(2u'+1)
とする。
→ | aを受け取る → ↓ |
長さが2(または 途中で打ち切り)→ |
aにフーリエ変換 |
↑ | aの長さが2で割りきれる ↓ |
↓ | |
↑ | a0, a1を生成 ↓ |
Aを得て終了 | |
↑ | ←a0、a1でスタートに戻る |
例として16個のデータからなる配列に対するアルゴリズムを図示する。
元 デ | タ |
f-(0) f-(1) f-(2) f-(3) f-(4) f-(5) f-(6) f-(7) f-(8) f-(9) f-(10) f-(11) f-(12) f-(13) f-(14) f-(15) |
→ S2 |
f0(0) f0(1) f0(2) f0(3) f0(4) f0(5) f0(6) f0(7) f1(0) f1(1) f1(2) f1(3) f1(4) f1(5) f1(6) f1(7) |
→ S1 |
f0,0(0) f0,0(1) f0,0(2) f0,0(3) f0,1(0) f0,1(1) f0,1(2) f0,1(3) f1,0(0) f1,0(1) f1,0(2) f1,0(3) f1,1(0) f1,1(1) f1,1(2) f1,1(3) |
→ S0 |
f0,0,0(0) f0,0,0(1) f0,0,1(0) f0,0,1(1) f0,1,0(0) f0,1,0(1) f0,1,1(0) f0,1,1(1) f1,0,0(0) f1,0,0(1) f1,0,1(0) f1,0,1(1) f1,1,0(0) f1,1,0(1) f1,1,1(0) f1,1,1(1) |
|
↓F3 | ↓F2 | ↓F1 | ↓F0 | |||||
変 換 後 |
F(0) F(1) F(2) F(3) F(4) F(5) F(6) F(7) F(8) F(9) F(10) F(11) F(12) F(13) F(14) F(15) |
→ T2 |
F(0) F(2) F(4) F(6) F(8) F(10) F(12) F(14) F(1) F(3) F(5) F(7) F(9) F(11) F(13) F(15) |
→ T1 |
F(0) F(4) F(8) F(12) F(2) F(6) F(10) F(14) F(1) F(5) F(9) F(13) F(3) F(7) F(11) F(15) |
→ T0 |
F(0) F(8) F(4) F(12) F(2) F(10) F(6) F(14) F(1) F(9) F(5) F(13) F(3) F(11) F(7) F(15) |
fm0, m1, m2(0)とfm0, m1, m2(1)をあわせると、f(0)からf(15)までの成分をすべて1度ずつ(位相をずらしながら)含んでいる。この時の和と積の取り方を表したグラフが蝶の羽のように見えることから、バタフライ演算と呼ばれたりする。
Nが2の冪だけでなく、pの冪であるとき、式は以下のようになる。N'p=Nと置く。0≦m<p-1。
F(pu'+m)=ΣN'-1n=0(Σp-1k=0WpkWNmnf(n+kN'))WN'u'n
上記の式に沿ってaからa0,a1,…,ap-1を生成し、データ数がpで割り切れなくなった時点で通常のフーリエ変換を施す、というアルゴリズムになる。
解説したのはp=2のパターン。p=4の場合が最も高速になる様子。
表からDFTとFFTでは得られる配列F(u)の順番が異なることがわかる。
aにSを作用させて生成した配列の添え字とかっこ内の数字を見てみると、たとえば(0番目から数えて)11番目の要素f1,0,1(1)は数字が1,0,1,1と並んでおり、2進数で1011は10進数で11となる。6番目の要素f0,1,1(0)は0110であり、これは10進数で6である。
つまり、n番目の要素は2進数でnと一致するように0,1が並んでいるのである。
一方、AにTを作用させて並び替えた配列は、11番目はF(13)、6番目はF(6)である。実は、13は2進数で1101であり、ちょうど11と逆転している。他のすべての数も、2番目の要素は2進数表記を逆順にした数字になっている。
以下のデータ数16の場合の表を見るとわかりやすい。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
→ 2 進 数 化 |
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 |
→ ビ ッ ト 反 転 |
0000 1000 0100 1100 0010 1010 0110 1110 0001 1001 0101 1101 0011 1011 0111 1111 |
→ 10 進 数 化 |
0 8 4 12 2 10 6 14 1 9 5 13 3 11 7 15 |
この変換をビット反転と呼ぶ。FFTで計算した後には元の並びと一致させるためAにビット反転を施してやる必要がある。
FFTは複素フーリエ変換なので複素数を扱える言語で無ければ実数からなるデータは実数列1つとすべてが0の配列の2つを渡すことになる。
エクセルVBAの例:長さNの配列Re(), Im()の2つをFFT()に渡し、フーリエ変換させて書き換える。
実際には安全性のためNが2の冪かどうかなどを確認する行をはさむ必要がある。
'=======================================メイン=========================
End Function
Function FFT(Re(), Im(), N)
Call FFTsub(Re(), Im(), N, 0)
Call BitInversion(Re(), N)
Call BitInversion(Im(), N)
For I = 0 to N - 1
Re(I) = Re(I) / N '最後にまとめて係数をかける
Im(I) = Im(I) / N
Next
End Function
'=======================================FFTアルゴリズム====================
Function FFTsub(Re(), Im(), N, St) ' Stは分割された配列の先頭
Theta = 2 * 3.14159265358 / N
If N >1 Then
For I=0 To N / 2 - 1
A0 = Re(I + St) + Re(I + St + N / 2)
A1 = Im(I + St) + Im(I + St + N / 2)
B0 = Re(I + St) - Re(I + St + N / 2)
B1 = Im(I + St) - Im(I + St + N / 2)
Re(I + St) = A0
Im(I + St) = A1
Re(I + St + N / 2) = B0 * Cos(Theta * I) + B1 * Sin(Theta * I)
Im(I + St + N / 2) = B1 * Cos(Theta * I) - B0 * Sin(Theta * I)
Next I
Call FFTsub(Re(),Im(),N / 2, St) '再帰呼び出し
Call FFTsub(Re(),Im(),N / 2 , St + N / 2) '再帰呼び出し
End If
End Function
'=======================================配列のビット反転======================
Function BitInversion(F(), N)
Dim Ary() As Variant
ReDim Ary(N)
For I = 0 To N - 1
Ary(I) = F(I)
Next I
For I = 0 To N - 1
F(I) = Ary(BIsub(I, N))
Next I
'========================================N以下の自然数をビット反転=============
Function BIsub(Num, N)
A = N - 1
B = 0
C = Num
While A > 0
A = A \ 2
B = B * 2 + (C Mod 2)
C = C \ 2
Wend
BIsub = B
End Function
フーリエ変換と同様にして、逆フーリエ変換も定義できる。逆変換は、
f(n)=ΣN-1u=0F(u)W-Nun=F'(F)(n)
と定義されるので、
f(2n')=ΣN-1u=0(F(u)+F(u+N'))W-N'un'=F'(F0)(n)
f(2n'+1)=ΣN-1u=0((F(u)-F(u+N'))W-N'u)W-N'un'=F'(F1)(n)
となる。計算の流れもほぼ同じ。したがって、プログラムは一部だけ変形させ、ほぼそのまま流用できる。
'=======================================メイン=========================
Function FFTI(Re(), Im(), N) '名前変更
Call FFTIsub(Re(), Im(), N, 0)
Call BitInversion(Re(), N)
Call BitInversion(Im(), N)
'最後に掛ける係数を1にする
End Function
'===================================逆FFTアルゴリズム====================
Function FFTIsub(Re(), Im(), N, St) '名前変更
Theta = 2 * 3.14159265358 / N
If N >1 Then
For I=0 To N / 2 - 1
A0 = Re(I + St) + Re(I + St + N / 2)
A1 = Im(I + St) + Im(I + St + N / 2)
B0 = Re(I + St) - Re(I + St + N / 2)
B1 = Im(I + St) - Im(I + St + N / 2)
Re(I + St) = A0
Im(I + St) = A1
Re(I + St + N / 2) = B0 * Cos(Theta * I) - B1 * Sin(Theta * I) '±逆転
Im(I + St + N / 2) = B1 * Cos(Theta * I) + B0 * Sin(Theta * I) '±逆転
Next I
Call FFTIsub(Re(),Im(),N / 2, St) '呼び出し関数変更
Call FFTIsub(Re(),Im(),N / 2 , St + N / 2) '呼び出し関数変更
End If
End Function
掲示板
提供: リーボー電子
提供: 文雄
提供: ムヒ
提供: 樹葉 緑
提供: 菊池謙二№%‰¼½¾⅓⅔⅛⅜⅝⅞
急上昇ワード改
最終更新:2025/03/30(日) 01:00
最終更新:2025/03/30(日) 00:00
ウォッチリストに追加しました!
すでにウォッチリストに
入っています。
追加に失敗しました。
ほめた!
ほめるを取消しました。
ほめるに失敗しました。
ほめるの取消しに失敗しました。