这次来学习一下更一般性的FFT分解
FFT的一般性分解
除了常见的 2 基底 FFT 以外,对于更一般的 N=N1N2 问题,也有办法在理论上进行拆解,将问题化为数个 N1 与 N2 点离散傅里叶变换问题。特别的是,透过互质在数论上的特性,对于 N1 与 N2 互质的情况,可以进一步节省运算。
下面目前只讨论 N1N2 非互质的情况。
为避免之后的符号混淆,我们先将 N1N2 置换为 P1P2。
接着定义要拆分的问题,要拆分的问题为 N 点离散傅里叶变换,将 f[n] 转换为 F[m]:
F[m]=n=0∑N−1f[n]e−iN2πmnm,n=0,1,⋯,N−1
直观地说,这个 N 点离散傅里叶变换,将由 n 作为参数的函数 f[n] ,转换成由 m 作为参数的函数 F[m],并且 m,n 都有 N 个可能的数值。
待定义好要拆分的问题,便可以开始讨论如何进行拆分,基本概念是将有 N 个可能的数值的 m,n ,分别化为使用两个参数进行描述的函数 m=[m1,m2],n=[n1,n2],并借此将原问题化为二维度离散傅里叶变换,便可使用数个较小的离散傅里叶变换问题描述整个过程。
而一种很直觉的转换方式,便是透过将 m,n 分别除以 P2,P1,以商数与余数来做为参数描述 m,n 的值:
n=n1P1+n2n1,m1=0,1,⋯,P2−1m=m1+m2P2n2,m2=0,1,⋯,P1−1
其中 n1 作为将 n 除以 P1 的商数,与作为 m 除以 P2 的余数的 m1 相同,具有 P2 个可能数值,同理 n2 与 m2 有 P1 个可能数值。
将上诉的参数代换及 N=P1P2 带入原式,可以得到:
F[m1+m2P2]=n1=0∑P2−1n2=0∑P1−1f[n1P1+n2]e−iP1P22π(m1+m2P2)(n1P1+n2)
将右边的指数部分乘开并分项化简可以得到:
F[m1+m2P2]=n1=0∑P2−1n2=0∑P1−1f[n1P1+n2]e−iP22πm1n1e−iP12πm2n2e−iP1P22πm1n2e−i2πm2n1
最后透过 e−i2π=1 与 P1P2=N,可以得到:
F[m1+m2P2]=n1=0∑P2−1n2=0∑P1−1f[n1P1+n2]e−iP22πm1n1e−iP12πm2n2e−iN2πm1n2
观察上式,并加上括号辅助厘清运算顺序我们可以得到:
F[m1+m2P2]=n2=0∑P1−1{{n1=0∑P2−1f[n1P1+n2]e−iP22πm1n1}e−iN2πm1n2}e−iP12πm2n2
最内层的运算可以视为将双参数函数 f[n1,n2] 中的一个参数 n1,透过离散傅里叶变换取代为由 m1 描述,得到一个新函数 G1[m1,n2](这步因为对每个不同 n2,都需要做一次将 n1 取代为 m1 的转换,共需要 P1 个 P2 点离散傅里叶变换):
F[m1+m2P2]=n2=0∑P1−1{G1[m1,n2]e−iN2πm1n2}e−iP12πm2n2
下一步运算可以视为单纯的乘法,将 G1[m1,n2] 与 e−iN2πm1n2 相乘,得到 G2[m1,n2](这步需要的计算量视 Nm1n2 的特殊性而会有变化):
F[m1+m2P2]=n2=0∑P1−1G2[m1,n2]e−iP12πm2n2
最后的运算可以视为将函数 G2[m1,n2] 中的 n2 ,透过离散傅里叶变换取代为由 m2 描述,得到一个新函数 G3[m1,m2](这步因为对每个不同 m1,都需要做一次将 n2 取代为 m2 的转换,共需要 P2 个 P1 点离散傅里叶变换):
F[m(=m1+m2P2)]=G3[m1,m2]
这样就仅使用 P1 与 P2 点离散傅里叶变换,描述了原先的 N 点离散傅里叶变换。
接下来举一个实际例子帮助理解上面的流程
假设输入为
这是一个 8 点的 FFT,直接调用 scipy 库里的 fft 求得答案为:
1 2 3 4 5 6 7
| import numpy as np from scipy.fft import fft
np.set_printoptions(precision=2, suppress=True, linewidth=400) x = np.array([1, 2, 3, 4, 5, 6, 7, 8]) ans = fft(x) print("ans=", ans)
|
1
| ans= [36.-0.j -4.+9.66j -4.+4.j -4.+1.66j -4.-0.j -4.-1.66j -4.-4.j -4.-9.66j]
|
然后用我们上面提到的一般性分解来解决这个问题,将 8 分解为 4 乘 2,也就是 N=8,P1=4,P2=2。
n=n1∗4+n2n1,m1=0,1m=m1+m2∗2n2,m2=0,1,2,3
F[m1+m2∗2]=n2=0∑3{{n1=0∑1f[n1∗4+n2]e−iN22πm1n1}e−iN2πm1n2}e−iN12πm2n2
最内层是
G1[m1,n2]=n1=0∑1f[n1∗4+n2]e−iN22πm1n1
可以列出每项 G1 由哪些 f 得到:
G1[0,0]⇐f[0,0]&f[1,0],G1[1,0]⇐f[0,0]&f[1,0]G1[0,1]⇐f[0,1]&f[1,1],G1[1,1]⇐f[0,1]&f[1,1]G1[0,2]⇐f[0,2]&f[1,2],G1[1,2]⇐f[0,2]&f[1,2]G1[0,3]⇐f[0,3]&f[1,3],G1[1,3]⇐f[0,3]&f[1,3]
然后是乘以旋转因子:
G2[m1,n2]=G1[m1,n2]e−iN2πm1n2
最后是
F[m1+m2∗2]=G3[m1,m2]=n2=0∑3G2[m1,n2]e−iN12πm2n2
同样列出每项 G3 由哪些 G2 得到:
G3[0,0]&G3[0,1]&G3[0,2]&G3[0,3]⇐G2[0,0]&G2[0,1]&G2[0,2]&G2[0,3]G3[1,0]&G3[1,1]&G3[1,2]&G3[1,3]⇐G2[1,0]&G2[1,1]&G2[1,2]&G2[1,3]
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| import numpy as np import cmath
np.set_printoptions(precision=2, suppress=True, linewidth=400) x = np.array([1, 2, 3, 4, 5, 6, 7, 8])
def DFT1(input, N, N1, N2): output = np.array([0 + 0j] * N) for n2 in range(0, N1): for m1 in range(0, N2): w = complex(1, 0) wn = cmath.exp(-1j * 2 * cmath.pi * m1 / N2) for n1 in range(0, N2): output[m1 * N1 + n2] += input[n1 * N1 + n2] * w w *= wn return output
def DFT2(input, N, N1, N2): output = np.array([0 + 0j] * N) for m1 in range(0, N2): for m2 in range(0, N1): w = complex(1, 0) wn = cmath.exp(-1j * 2 * cmath.pi * m2 / N1) for n2 in range(0, N1): output[m1 + m2 * N2] += ( input[m1 * N1 + n2] * w ) w *= wn return output
N = 8 N1 = 4 N2 = 2 f = x f = DFT1(f, N, N1, N2) print("G1=", f) for m1 in range(0, N2): for n2 in range(0, N1): f[m1 * N1 + n2] *= cmath.exp(-1j * 2 * cmath.pi * m1 * n2 / N) print("G2=", f) f = DFT2(f, N, N1, N2) print("G3=", f)
|
输出:
1 2 3
| G1= [ 6.+0.j 8.+0.j 10.+0.j 12.+0.j -4.-0.j -4.-0.j -4.-0.j -4.-0.j] G2= [ 6. +0.j 8. +0.j 10. +0.j 12. +0.j -4. -0.j -2.83+2.83j -0. +4.j 2.83+2.83j] G3= [36.+0.j -4.+9.66j -4.+4.j -4.+1.66j -4.-0.j -4.-1.66j -4.-4.j -4.-9.66j]
|
好吧,我承认光看上面的公式和代码并不能直观地理解到底做了什么,所以下面我们结合一张图来直观的展示每一步的计算
输入为:
f=[15263748]
对输入的每一列做 DFT,也就是 4 次 2 点 DFT:
G1=[6+0j−4−0j8+0j−4−0j10+0j−4−0j12+0j−4−0j]
对每个位置的值乘以相应的旋转因子:
G2=[6+0j−4−0j8+0j−2.83+2.83j10+0j0+4j12+0j2.83+2.83j]
转置:
G2=⎣⎢⎢⎢⎡6+0j8+0j10+0j12+0j−4−0j−2.83+2.83j0+4j2.83+2.83j⎦⎥⎥⎥⎤
对 G2 的每一列做 DFT,也就是 2 次 4 点 DFT :
G3=⎣⎢⎢⎢⎡36+0j−4+4j−4+0j−4−4j−4+9.66j−4+1.66j−4−1.66j−4−9.66j⎦⎥⎥⎥⎤
验证代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import numpy as np import cmath from scipy.fft import fft
np.set_printoptions(precision=2, suppress=True, linewidth=400) x = np.array([1, 2, 3, 4, 5, 6, 7, 8]) ans = fft(x) print("ans=", ans) res = np.array([0 + 0j] * 8) res[::4] = fft(x[::4]) res[1::4] = fft(x[1::4]) res[2::4] = fft(x[2::4]) res[3::4] = fft(x[3::4]) print("G1=", res) for i in range(0, 2): for j in range(0, 4): res[i * 4 + j] *= cmath.exp(-1j * 2 * cmath.pi * i * j / 8) print("G2=", res) res = res.reshape(2, 4).transpose().flatten() res[::2] = fft(res[::2]) res[1::2] = fft(res[1::2]) print("G3=", res)
|
输出:
1 2 3 4
| ans= [36.-0.j -4.+9.66j -4.+4.j -4.+1.66j -4.-0.j -4.-1.66j -4.-4.j -4.-9.66j] G1= [ 6.-0.j 8.-0.j 10.-0.j 12.-0.j -4.-0.j -4.-0.j -4.-0.j -4.-0.j] G2= [ 6. +0.j 8. +0.j 10. +0.j 12. +0.j -4. -0.j -2.83+2.83j -0. +4.j 2.83+2.83j] G3= [36.+0.j -4.+9.66j -4.+4.j -4.+1.66j -4.+0.j -4.-1.66j -4.-4.j -4.-9.66j]
|
两份代码 G1,G2,G3 均相同,说明计算过程的直观体现确实如上图所示。
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| import numpy as np import cmath from scipy.fft import fft
np.set_printoptions(precision=2, suppress=True, linewidth=400) x = np.array([1, 2, 3, 4, 5, 6, 7, 8]) ans = fft(x) print("ans=", ans) res = np.array([0 + 0j] * 8) res[::4] = fft(x[::4]) res[1::4] = fft(x[1::4]) res[2::4] = fft(x[2::4]) res[3::4] = fft(x[3::4]) print("G1=", res) for i in range(0, 2): for j in range(0, 4): res[i * 4 + j] *= cmath.exp(-1j * 2 * cmath.pi * i * j / 8) print("G2=", res) res = res.reshape(2, 4).transpose().flatten() res[::2] = fft(res[::2]) res[1::2] = fft(res[1::2]) print("G3=", res)
def DFT1(input, N, N1, N2): output = np.array([0 + 0j] * N) for n2 in range(0, N1): for m1 in range(0, N2): w = complex(1, 0) wn = cmath.exp(-1j * 2 * cmath.pi * m1 / N2) for n1 in range(0, N2): output[m1 * N1 + n2] += input[n1 * N1 + n2] * w w *= wn return output
def DFT2(input, N, N1, N2): output = np.array([0 + 0j] * N) for m1 in range(0, N2): for m2 in range(0, N1): w = complex(1, 0) wn = cmath.exp(-1j * 2 * cmath.pi * m2 / N1) for n2 in range(0, N1): output[m1 + m2 * N2] += ( input[m1 * N1 + n2] * w ) w *= wn return output
N = 8 N1 = 4 N2 = 2 f = x f = DFT1(f, N, N1, N2) print("G1=", f) for m1 in range(0, N2): for n2 in range(0, N1): f[m1 * N1 + n2] *= cmath.exp(-1j * 2 * cmath.pi * m1 * n2 / N) print("G2=", f) f = DFT2(f, N, N1, N2) print("G3=", f)
|
参考资料
- 库利-图基快速傅里叶变换算法 - 维基百科,自由的百科全书
- Cooley–Tukey FFT algorithm - Wikipedia