Bootstrap

傅里叶变换、离散傅里叶变换(DFT)、快速傅里叶变换(FFT)详解

前置知识


以下内容参考《复变函数与积分变换》,如果对积分变换有所了解,完全可以跳过忽略
复数的三角表达式如下
Z = r ( c o s θ + i s i n θ ) Z=r(cos\theta+isin\theta) Z=r(cosθ+isinθ)
欧拉公式如下
e i θ = c o s θ + i s i n θ e^{i\theta}=cos\theta+isin\theta eiθ=cosθ+isinθ
所以,两式连立,我们可以得到复数的指数表达式
Z = r e i θ Z=re^{i\theta} Z=reiθ


复球面如下图,除了N点以外,任意一个复数都与复球面上的点一一对应。
在这里插入图片描述


对于任意复数z的乘幂有下列公式成立
Z n = r n e i n θ Z^n=r^ne^{in\theta} Zn=rneinθ

当r=1时,我们可得到De Moivre公式
( c o s θ + i s i n θ ) N = c o s N θ + i s i n N θ (cos\theta+isin\theta)^N=cos N\theta +isinN\theta (cosθ+isinθ)N=cosNθ+isinNθ


复数的方根
在这里插入图片描述


复变函数的几何解释

在这里插入图片描述

复变函数中的常用初等函数

1. 指数函数
在这里插入图片描述

  1. 对数函数
    在这里插入图片描述

  2. 幂函数
    在这里插入图片描述

  3. 三角函数
    在这里插入图片描述

傅里叶级数

在高等数学中我们就已经学习过傅里叶级数了,书中是这么描述的任何周期函数都可以用正弦函数和余弦函数构成的无穷级数来表示,公式是下面这样描述的
f ( t ) = a 0 + ∑ n = 1 ∞ [ a n c o s ( n ω t ) + b n c o s ( n ω t ) ] f(t)=a_0+\sum^{\infty}_{n=1}[a_ncos(n\omega t)+b_ncos(n\omega t)] f(t)=a0+n=1[ancos(nωt)+bncos(nωt)]
其中
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面的推导过程是利用三角函数集的正交性进行的,推导过程很简单,就不再说了。如果想看看推导过程的请点击这里
我们可以用上面提到的复数知识,将上述的正余弦形式的傅里叶级数,改写成复指数形式的傅里叶级数,过程如下:

欧拉公式如下
e i θ = c o s θ + i s i n θ e^{i\theta}=cos\theta+isin\theta eiθ=cosθ+isinθ
通过上述欧拉公式,得到余弦、正弦的复指数表达,如下
s i n θ = e i x − e − i x 2 i sin \theta=\frac{e^{ix}-e^{-ix}}{2i} sinθ=2ieixeix
c o s θ = e i x + e − i x 2 cos \theta=\frac{e^{ix}+e^{-ix}}{2} cosθ=2eix+eix
将上述公式代入傅里叶级数中,可以得到
f ( t ) = a 0 + ∑ n = 0 ∞ ( a n − i b n 2 e i n ω 1 t + a n + i b n 2 e − i n ω 1 t ) f(t)=a_0+\sum^{\infty}_{n=0}(\frac{a_n-ib_n}{2}e^{in\omega_1t}+\frac{a_n+ib_n}{2}e^{-in\omega_1t}) f(t)=a0+n=0(2anibneinω1t+2an+ibneinω1t)
为了将上述式子合并
F ( n ω 1 ) = a n + i b n 2 e − i n ω 1 t F(n\omega_1)=\frac{a_n+ib_n}{2}e^{-in\omega_1t} F(nω1)=2an+ibneinω1t
F ( − n ω 1 ) = a − n − i b − n 2 e i n ω 1 t F(-n\omega_1)=\frac{a_{-n}-ib_{-n}}{2}e^{in\omega_1t} F(nω1)=2anibneinω1t
且 当 n = 0 的 时 候 , b n = 0 , a n = a 0 , e − i 0 ω 1 t = 1 , 所 以 F ( 0 ) = a 0 2 且当n=0的时候,b_n=0,a_n=a_0,e^{-i0\omega_1t}=1,所以F(0)=\frac{a_0}{2} n=0bn=0,an=a0ei0ω1t=1,F(0)=2a0
所以上述式子可以合并成
f ( t ) = ∑ n = − ∞ ∞ a n + i b n 2 e − i n ω 1 t f(t)=\sum^{\infty}_{n=-\infty}\frac{a_n+ib_n}{2}e^{-in\omega_1t} f(t)=n=2an+ibneinω1t
化简一下
f ( t ) = ∑ n = − ∞ ∞ F ( n ω 1 ) e i n ω 1 t f(t)=\sum^{\infty}_{n=-\infty}F(n\omega_1)e^{in\omega_1t} f(t)=n=F(nω1)einω1t

我们再把 a n , b n a_n,b_n an,bn代入 F F F中,可以得出最终傅里叶级数变换式
F ( n ω 1 ) = 1 T 1 ∫ t 0 t 0 + T f ( t ) e − i n ω 1 t d t F(n\omega_1)=\frac{1}{T_1}\int ^{t_0+T}_{t_0}f(t)e^{-in\omega_1t}dt F(nω1)=T11t0t0+Tf(t)einω1tdt
f ( t ) = ∑ n = − ∞ ∞ F ( n ω 1 ) e i n ω 1 t f(t)=\sum^{\infty}_{n=-\infty}F(n\omega_1)e^{in\omega_1t} f(t)=n=F(nω1)einω1t

连续傅里叶变换

根据前述的傅里叶级数可知,任何周期函数,都可以由正弦、余弦函数共同组合而成。现在假设有一个函数(信号) f ( t ) f(t) f(t),其周期为 T 1 T_1 T1,角频率为 W 1 W_1 W1。那么我们可以表示成如下形式。
f ( t ) = a 0 2 + ∑ n = 0 ∞ [ a n c o s ( n ω 1 t ) + b n s i n ( n ω 1 t ) ] f(t)=\frac{a_0}{2}+\sum^{\infty}_{n=0}[a_ncos(n\omega_1t)+b_nsin(n\omega_1t)] f(t)=2a0+n=0[ancos(nω1t)+bnsin(nω1t)]
其中 a 0 , a n , b n a_0,a_n,b_n a0,an,bn都已经计算出来了。

但上面是傅里叶级数,并非是傅里叶变换,所谓傅里叶变换就是将傅里叶级数的分析方法给推广到任何函数中,即非周期函数中

但是如果 T T T趋于无穷,那么就会导致谱线长度变成0,所以此时,必须要引入一个新的量,称为频谱密度函数

我们将 F ( n ω 1 ) F(n\omega_1) F(nω1)两边同时乘以T_1
T 1 F ( n ω 1 ) = ∫ t 0 t 0 + T f ( t ) e − i n ω 1 t d t T_1F(n\omega_1)=\int ^{t_0+T}_{t_0}f(t)e^{-in\omega_1t}dt T1F(nω1)=t0t0+Tf(t)einω1tdt
如果该函数的不是周期函数,那么 T − > ∞ , ω 1 − > 0 T->\infty ,\omega_1->0 T>,ω1>0,所以 n ω 1 n\omega_1 nω1就趋于连续值了,我们可以用 ω \omega ω代替,谱线间隔 △ n ω 1 \bigtriangleup n \omega_1 nω1也可以描述为 d ω d\omega dω
那么这时,我们将 F ( n ω 1 ) F(n\omega_1) F(nω1)改成如下形式
F ( ω ) = T 1 F ( n ω 1 ) = lim ⁡ T 1 → ∞ ∫ − T 2 T 2 f ( t ) e − i n ω 1 t d t F(\omega)=T_1F(n\omega_1)=\lim_{T_1\to\infty}\int ^{\frac{T}{2}}_{-\frac{T}{2}}f(t)e^{-in\omega_1t}dt F(ω)=T1F(nω1)=T1lim2T2Tf(t)einω1tdt
所以,最终我们得到了傅里叶变换
F ( ω ) = ∫ − ∞ + ∞ f ( t ) e − i ω t d t F(\omega)=\int ^{+\infty}_{-\infty}f(t)e^{-i\omega t}dt F(ω)=+f(t)eiωtdt
我们也说上面的傅里叶变换是广义傅里叶变换

思考一下,为什么我们也称其为频谱密度函数,那是因为 T 1 F ( n ω 1 ) = 2 π F ( n ω 1 ) ω 1 T_1F(n\omega_1)=\frac{2\pi F(n\omega_1)}{\omega_1} T1F(nω1)=ω12πF(nω1),其中 F ( n ω 1 ) ω 1 \frac{F(n\omega_1)}{\omega_1} ω1F(nω1)指的是单位频宽下的谱线长度,也就是频谱密度

最后我们再对傅里叶反变换进行一下改变就行了
ω 1 → 0 \omega_1 \to0 \\ ω10
n ω 1 → w n\omega_1 \to w nω1w
△ ( n ω 1 ) → d w \bigtriangleup (n\omega_1) \to dw (nω1)dw
∑ ω = − ∞ ∞ d ω = ∫ − ∞ + ∞ d ω \sum^{\infty}_{\omega=-\infty}d\omega=\int ^{+\infty}_{-\infty}d\omega ω=dω=+dω
f ( t ) = ∑ n ω 1 = − ∞ ∞ F ( n ω 1 ) ω 1 e i n ω 1 t ω 1 = ∑ n ω 1 = − ∞ ∞ F ( n ω 1 ) ω 1 e i n ω 1 t d ω = ∑ ω = − ∞ ∞ F ( ω ) 2 π e i n ω 1 t d ω = 1 2 π ∫ − ∞ + ∞ F ( ω ) e i ω t d ω \begin{array}{ll} f(t)&=\sum^{\infty}_{n\omega_1=-\infty}\frac{F(n\omega_1)}{\omega_1}e^{in\omega_1t}\omega_1 \\ &\\ &=\sum^{\infty}_{n\omega_1=-\infty}\frac{F(n\omega_1)}{\omega_1}e^{in\omega_1t}d\omega\\ &\\ &=\sum^{\infty}_{\omega=-\infty}\frac{F(\omega)}{2\pi}e^{in\omega_1t}d\omega&\\ &\\ &=\frac{1}{2\pi}\int ^{+\infty}_{-\infty}F(\omega)e^{i\omega t}d\omega \end{array} f(t)=nω1=ω1F(nω1)einω1tω1=nω1=ω1F(nω1)einω1tdω=ω=2πF(ω)einω1tdω=2π1+F(ω)eiωtdω
总结一下,傅里叶变换傅里叶反变换,如下
F ( ω ) = ∫ − ∞ + ∞ f ( t ) e − i ω t d t F(\omega)=\int ^{+\infty}_{-\infty}f(t)e^{-i\omega t}dt F(ω)=+f(t)eiωtdt
f ( t ) = 1 2 π ∫ − ∞ + ∞ F ( ω ) e i ω t d ω f(t)=\frac{1}{2\pi}\int ^{+\infty}_{-\infty}F(\omega)e^{i\omega t}d\omega f(t)=2π1+F(ω)eiωtdω

离散傅里叶级数

在学习DFT之前,先了解以下离散傅里叶级数对于过度到DFT是很有帮助的。两者之间主要的区别就是,离散傅里叶级数是用来分析周期序列,而离散傅里叶变换是用来分析有限长序列

现在假设有一个周期序列: x p ( n ) = x p ( n + r N ) x_p(n)=x_p(n+rN) xp(n)=xp(n+rN),其周期为 N N N。定义这个周期序列的离散傅里叶级数完全可以参照连续傅里叶级数。

先看看之前推导的傅里叶级数的公式
F ( n ω 1 ) = 1 T 1 ∫ t 0 t 0 + T f ( t ) e − i n ω 1 t d t F(n\omega_1)=\frac{1}{T_1}\int ^{t_0+T}_{t_0}f(t)e^{-in\omega_1t}dt F(nω1)=T11t0t0+Tf(t)einω1tdt
f ( t ) = ∑ n = − ∞ ∞ F ( n ω 1 ) e i n ω 1 t f(t)=\sum^{\infty}_{n=-\infty}F(n\omega_1)e^{in\omega_1t} f(t)=n=F(nω1)einω1t
我们可以把 1 T 1 \frac{1}{T_1} T11给提到 f ( t ) f(t) f(t)里面去,最后就会变成下面那样
F ( n ω 1 ) = ∫ t 0 t 0 + T f ( t ) e − i n ω 1 t d t F(n\omega_1)=\int ^{t_0+T}_{t_0}f(t)e^{-in\omega_1t}dt F(nω1)=t0t0+Tf(t)einω1tdt
f ( t ) = 1 T 1 ∑ n = − ∞ ∞ F ( n ω 1 ) e i n ω 1 t f(t)=\frac{1}{T_1}\sum^{\infty}_{n=-\infty}F(n\omega_1)e^{in\omega_1t} f(t)=T11n=F(nω1)einω1t
ok,现在我们通过改写上面的公式,使其支持离散周期序列。因为是离散的,所以积分符号 ∫ t 0 t 0 + T \int ^{t_0+T}_{t_0} t0t0+T要变成求和符合 ∑ n = 0 N − 1 \sum^{N-1}_{n=0} n=0N1,周期函数的频率 ω 1 \omega_1 ω1要变成 2 π N \frac{2\pi}{N} N2π,周期 T 1 T_1 T1也要变成 N N N,无限求和 ∑ n = − ∞ ∞ \sum^{\infty}_{n=-\infty} n=也要变成有限的 ∑ n = 0 N − 1 \sum^{N-1}_{n=0} n=0N1

最终改写出来的结果如下
X [ k ] = ∑ n = 0 N − 1 f ( t ) ( e − i k ( 2 π N ) ) n 【 1 式 】 X[k]=\sum^{N-1}_{n=0}f(t)(e^{-ik(\frac{2\pi}{N})})^n 【1式】 X[k]=n=0N1f(t)(eik(N2π))n1
x [ k ] = 1 N ∑ n = 0 N − 1 X [ n ] ( e i k ( 2 π N ) ) n 【 2 式 】 x[k]=\frac{1}{N}\sum^{N-1}_{n=0}X[n](e^{ik(\frac{2\pi}{N})})^n【2式】 x[k]=N1n=0N1X[n](eik(N2π))n2
对于上面的2式来说, e i k n ( 2 π N ) e^{ikn(\frac{2\pi}{N})} eikn(N2π)是构成这个式子的基,当k取1时, e k n ( 2 π N ) e^{kn(\frac{2\pi}{N})} ekn(N2π)为基频成分,k不为1时,称其为k次谐波分量。因为其周期是 N N N,所以无论怎么取,只有N个谐波分量是独立的。

为了方便,我们令 W N = e − i ( 2 π N ) W_N=e^{-i(\frac{2\pi}{N})} WN=ei(N2π)
所以上面定义的式子可以写成下面形式
X [ k ] = ∑ n = 0 N − 1 f ( t ) W n k 【 3 式 】 X[k]=\sum^{N-1}_{n=0}f(t)W^{nk} 【3式】 X[k]=n=0N1f(t)Wnk3
x [ k ] = 1 N ∑ n = 0 N − 1 X [ n ] W − n k 【 4 式 】 x[k]=\frac{1}{N}\sum^{N-1}_{n=0}X[n]W^{-nk}【4式】 x[k]=N1n=0N1X[n]Wnk4

离散傅里叶变换(DFT)

从前面我们已经知道,非周期连续函数傅里叶变换如下
F ( ω ) = ∫ − ∞ + ∞ f ( t ) e − i ω t d t F(\omega)=\int ^{+\infty}_{-\infty}f(t)e^{-i\omega t}dt F(ω)=+f(t)eiωtdt

单位冲激函数的傅里叶变换如下
F [ δ ( t − t 0 ) ] = ∫ − ∞ + ∞ δ ( t − t 0 ) e − i ω t d t = e − i ω t 0 \begin{array}{ll} F[\delta(t-t_0)]&=\int ^{+\infty}_{-\infty}\delta(t-t_0)e^{-i\omega t}dt \\ &\\ &=e^{-i\omega t_0} \end{array} F[δ(tt0)]=+δ(tt0)eiωtdt=eiωt0

假设现在有一个冲激串,如下
S △ T ( t ) = ∑ n = − ∞ + ∞ δ ( t − n △ T ) S_{\bigtriangleup T}(t)=\sum^{+\infty}_{n=-\infty}\delta(t-n\bigtriangleup T) ST(t)=n=+δ(tnT)
这个冲击串表示,每隔 △ T \bigtriangleup T T时间,都会采样一样,其函数图像如下

在这里插入图片描述(图像参考《数字图像处理》冈萨雷斯)

所谓离散二字,就是要求数据是离散的,而非连续的,我们可以使用一个周期串,对一个信号(函数)进行采样,这样就可以得到一个离散的数据了

现在我们要对上述周期串函数求傅里叶变换,这里的只的傅里叶变换是指广义的傅里叶变换,因为这个函数很明显它是不满足狄利克雷条件的。求法如下

因为 S △ T ( t ) S_{\bigtriangleup T}(t) ST(t)是一个周期函数,所以我们可以把它表示成一个傅里叶级数的形式

S △ T ( t ) = ∑ n = − ∞ + ∞ C n e i n ω s t S_{\bigtriangleup T}(t)=\sum^{+\infty}_{n=-\infty}C_ne^{in\omega_s t} ST(t)=n=+Cneinωst

C n C_n Cn可以利用下述公式求出
C n = 1 △ T ∫ − △ T 2 △ T 2 S △ T e − i n ω s t C_n=\frac{1}{\bigtriangleup T}\int ^{\frac{\bigtriangleup T}{2}}_{-\frac{\bigtriangleup T}{2}}S_{\bigtriangleup T}e^{-in\omega_s t} Cn=T12T2TSTeinωst
注意,上述积分区间,周期窜只采样了一次。而且是在0点处采样的,所以除了0点,其他点处的函数值都为0
C n = 1 △ T e 0 = 1 △ T C_n=\frac{1}{\bigtriangleup T}e^{0}=\frac{1}{\bigtriangleup T} Cn=T1e0=T1
然后我们可以得到
S △ T ( t ) = ∑ n = − ∞ + ∞ 1 △ T e i n ω s t = 1 △ T ∑ n = − ∞ + ∞ e i n ω s t S_{\bigtriangleup T}(t)=\sum^{+\infty}_{n=-\infty}\frac{1}{\bigtriangleup T}e^{in\omega_s t}=\frac{1}{\bigtriangleup T}\sum^{+\infty}_{n=-\infty}e^{in\omega_s t} ST(t)=n=+T1einωst=T1n=+einωst
然后再对上述式子进行傅里叶变换

F [ S △ T ( t ) ] = F [ ∑ n = − ∞ + ∞ 1 △ T e i n ω s t ] d t = ∑ n = − ∞ + ∞ F [ 1 △ T e i n ω s t ] = 1 △ T ∑ n = − ∞ + ∞ F [ e i n ω s t ] = 1 △ T ∑ n = − ∞ + ∞ δ ( ω − n ω s ) \begin{array}{ll} F[S_{\bigtriangleup T}(t)]&=F[\sum^{+\infty}_{n=-\infty}\frac{1}{\bigtriangleup T}e^{in\omega_s t}]dt \\ &\\ &=\sum^{+\infty}_{n=-\infty}F[\frac{1}{\bigtriangleup T}e^{in\omega_s t}]\\ &\\ &=\frac{1}{\bigtriangleup T}\sum^{+\infty}_{n=-\infty}F[e^{in\omega_s t}] &\\&\\ &=\frac{1}{\bigtriangleup T}\sum^{+\infty}_{n=-\infty}\delta(\omega-n\omega_s) \end{array} F[ST(t)]=F[n=+T1einωst]dt=n=+F[T1einωst]=T1n=+F[einωst]=T1n=+δ(ωnωs)
从上述式子可以看到,当对一个冲激窜进行傅里叶变换之后,其频谱图任然是一个冲激窜,但是其幅度变为了原来的 1 △ T \frac{1}{\bigtriangleup T} T1

现在我们来用冲激串,对原始数据进行抽样,公式如下
p ( t ) = f ( t ) S △ T ( t ) = ∑ n = − ∞ + ∞ f ( t ) δ ( t − n △ T ) = f ( k △ T ) \begin{array}{ll} p(t)=f(t)S_{\bigtriangleup T}(t)&=\sum^{+\infty}_{n=-\infty}f(t)\delta(t-n\bigtriangleup T)\\ &\\ &=f(k\bigtriangleup T) \end{array} p(t)=f(t)ST(t)=n=+f(t)δ(tnT)=f(kT)
在这里插入图片描述
采样完成之后,数据就成离散的了。现在我们考虑对其进行傅里叶变换,因为 F [ p ( t ) ] F[p(t)] F[p(t)]是一周期连续函数,所以我们只需要在一个周期内采样即可
F [ p ( t ) ] = 1 △ T ∫ 0 △ T f ( t ) δ ( t − n △ T ) e − i ω t d t = ∑ n = 0 M − 1 f ( n △ T ) e − i n ω △ T = ∑ n = 0 M − 1 f n e − i n ω △ T \begin{array}{ll} F[p(t)]&=\frac{1}{\bigtriangleup T}\int ^{\bigtriangleup T}_{0}f(t)\delta(t-n\bigtriangleup T)e^{-i\omega t}dt\\ &\\ &=\sum^{M-1}_{n=0}f(n\bigtriangleup T)e^{-in\omega \bigtriangleup T}\\ &\\ &=\sum^{M-1}_{n=0}f_ne^{-in\omega \bigtriangleup T}\\ \end{array} F[p(t)]=T10Tf(t)δ(tnT)eiωtdt=n=0M1f(nT)einωT=n=0M1fneinωT
因为对一个周期进行积分,所以 t t t就变成一个周期。假设采集M个点。所以公式变成如下
F ( m ) = ∑ n = 0 M − 1 f n e − i n ω △ T = ∑ n = 0 M − 1 f n e − i n 2 π △ T △ T m M = ∑ n = 0 M − 1 f n e − i 2 π n m M F(m)=\sum^{M-1}_{n=0}f_ne^{-in\omega \bigtriangleup T}=\sum^{M-1}_{n=0}f_ne^{-in\frac{2\pi}{\bigtriangleup T}\bigtriangleup T}\frac{m}{M}=\sum^{M-1}_{n=0}f_ne^{-i2\pi n\frac{m}{M}} F(m)=n=0M1fneinωT=n=0M1fneinT2πTMm=n=0M1fnei2πnMm

经过上述变换之后,最终就可以得到离散的傅里叶频谱图

如果我们想用经过离散傅里叶变换之后得到的数据经过反变换得到原始数据的话,可以用傅里叶反变换
f ( x ) = 1 M ∑ m = 0 M − 1 F ( m ) e j 2 π m x M f(x)=\frac{1}{M}\sum^{M-1}_{m=0}F(m)e^{j2\pi m\frac{x}{M}} f(x)=M1m=0M1F(m)ej2πmMx

下面用python实现DFTIDFT,在此之前还要写一个复数类,支持复数的加法、减法、乘法以即求模运算

复数类

'复数类'
class Complex:
    #初始化复数类,第一个参数为实部,第二个参数为虚部
    def __init__(self,r,i=0):
        self.Re=r
        self.Im=i
    #重载加法符
    def __add__(self, other):
        real=self.Re+other.Re
        imginary=self.Im+other.Im
        return Complex(real,imginary)
    #重载减法符
    def __sub__(self, other):
        real=self.Re-other.Re
        imginary=self.Im-other.Im
        return Complex(real,imginary)
    #重载乘法符
    def __mul__(self, other):
        real=self.Re*other.Re-self.Im*other.Im
        imginary=self.Re*other.Im+self.Im*other.Re
        return Complex(real,imginary)
    #取模
    def mo(self):
        return np.sqrt((self.Re**2+self.Im**2))
    #得到实部
    def getRe(self):
        return self.Re
    # 得到虚部
    def getIm(self):
        return self.Im
    def __repr__(self):
        return self.__str__()
    def __str__(self):
        if self.Re==0:
            return str(round(self.Im,2)) + "i"
        if self.Im==0:
            return str(round(self.Re,2))
        if self.Im>0:
            return str(round(self.Re,2))+"+"+str(round(self.Im,2))+"i"
        if self.Im < 0:
            return str(round(self.Re,2)) +str(round(self.Im,2)) + "i"

另外提一下,DFTIDFT还可以写成矩阵的形式(参考《信号与系统 下》郑里君)
在这里插入图片描述
DFT和IDFT

def DFT(fs):
    res=np.empty(shape=len(fs),dtype=Complex)
    M=len(fs)
    for m in range(0,len(fs)):
        f_m=Complex(0)
        for n in range(0,M):
            f_m=f_m+fs[n]*Complex(np.cos(-2.*np.pi*n*m/M),np.sin(-2.*np.pi*n*m/M))

        res[m]=f_m
    return res
def IDFT(fm):
    M=len(fm)
    fn=np.empty(shape=M,dtype=Complex)
    for n in range(0,M):
        fn[n]=Complex(0)
        for m in range(0,M):
            fn[n]=fn[n]+fm[m]*Complex(np.cos(2*np.pi*n*m/M),np.sin(2*np.pi*n*m/M))
        fn[n]=fn[n]*Complex(1/M)
    return fn

运行结果
在这里插入图片描述

快速傅里叶变换(FTT)

FTT算法,中文名称为快速傅里叶变换,这个算法是DFT的加速版,DFT的时间复杂度是 Θ ( n 2 ) \Theta(n^2) Θ(n2),而FTT的时间复杂度为 Θ ( n l g n ) \Theta(nlgn) Θ(nlgn),这个速度的提升就相当于从冒泡排序升级到归并排序(这里用归并排序做比较是因为FTT的原理是分治,与归并很像)。

这里总结了《算法导论》中FTT的思想方法,在第三十章,可以自己去看。

在《算法导论》中,作者是通过多项式乘法来逐步引导出快速傅里叶变换,但我觉得还不如直接坦荡的说明其算法思想来的快,然后再做一些应用。

先将DFT的公式进行一下改造,使其看的更清楚一些
F ( m ) = ∑ n = 0 M − 1 f n e − i 2 π n m M F(m)=\sum^{M-1}_{n=0}f_ne^{-i2\pi n\frac{m}{M}} F(m)=n=0M1fnei2πnMm
我们可以用
ω M m = e − i 2 π m M \omega_{M}^m=e^{-i2\pi\frac{m}{M}}\\ ωMm=ei2πMm
所以,改造之后,DFT公式变为
F ( m ) = ∑ n = 0 M − 1 f n ( ω M m ) n F(m)=\sum^{M-1}_{n=0}f_n(\omega_{M}^m)^n F(m)=n=0M1fn(ωMm)n
现在假设我们有一个序列 f f f [ a 0 , a 1 , a 2 , a 3 , a 4 , a 5 , a 6 , . . . . . . , a n ] [a_0,a_1,a_2,a_3,a_4,a_5,a_6,......,a_n] [a0,a1,a2,a3,a4,a5,a6,......,an],其中n是2的次幂。其中 [ F 0 , F 1 , F 2 , F 3 , F 4 , F 5 , F 6 , . . . . . . F n ] [F_0,F_1,F_2,F_3,F_4,F_5,F_6,......F_n] [F0,F1,F2,F3,F4,F5,F6,......Fn]为对应的离散傅里叶变换序列

拿出单独拿出一个序列来看
F k = ∑ n = 0 M − 1 f n ( ω M k ) n = a 0 ( ω M k ) 0 + a 1 ( ω M k ) 1 + a 2 ( ω M k ) 2 + a 3 ( ω M k ) 3 + . + a M − 1 ( ω M k ) M − 1 F_k=\sum^{M-1}_{n=0}f_n(\omega_{M}^k)^n=a_0(\omega_{M}^k)^0+a_1(\omega_{M}^k)^1+a_2(\omega_{M}^k)^2+a_3(\omega_{M}^k)^3+.+a_{M-1}(\omega_{M}^k)^{M-1} Fk=n=0M1fn(ωMk)n=a0(ωMk)0+a1(ωMk)1+a2(ωMk)2+a3(ωMk)3+.+aM1(ωMk)M1
对上面的式子,如果我们写程序计算的话,需要一个M次循环,因为一共有M这样的数据要计算,所以一共要计算 M 2 M^2 M2次。而FFT就是要优化上面的式子,使其只计算 l g M lgM lgM次。FTT的做法大概如下

先将奇数项和偶数项分别提出来
A 0 = a 0 ( ω M k ) 0 + a 2 ( ω M k ) 2 + a 4 ( ω M k ) 4 + . + a M − 2 ( ω M k ) M − 2 A 1 = a 1 ( ω M k ) 1 + a 3 ( ω M k ) 3 + a 5 ( ω M k ) 5 + . + a M − 1 ( ω M − 1 k ) M − 1 A_0=a_0(\omega_{M}^k)^0+a_2(\omega_{M}^k)^2+a_4(\omega_{M}^k)^4+.+a_{M-2}(\omega_{M}^k)^{M-2}\\ A_1=a_1(\omega_{M}^k)^1+a_3(\omega_{M}^k)^3+a_5(\omega_{M}^k)^5+.+a_{M-1}(\omega_{M-1}^k)^{M-1} A0=a0(ωMk)0+a2(ωMk)2+a4(ωMk)4+.+aM2(ωMk)M2A1=a1(ωMk)1+a3(ωMk)3+a5(ωMk)5+.+aM1(ωM1k)M1
再对 A 1 ( 奇 数 项 ) A_1(奇数项) A1进行一下变换
A 1 = ( ω M k ) ( a 1 ( ω M k ) 0 + a 3 ( ω M k ) 2 + a 5 ( ω M k ) 4 + . + a M − 1 ( ω M k ) M − 2 ) A_1=(\omega_{M}^k)(a_1(\omega_{M}^k)^0+a_3(\omega_{M}^k)^2+a_5(\omega_{M}^k)^4+.+a_{M-1}(\omega_{M}^k)^{M-2}) A1=(ωMk)(a1(ωMk)0+a3(ωMk)2+a5(ωMk)4+.+aM1(ωMk)M2)
所以,最后原来的 F k F_k Fk就称下面
F k = A 0 + ω M k A 1 F_k=A_0+\omega_{M}^kA_1 Fk=A0+ωMkA1
由消去引理,可得
A 0 = a 0 ( ω M 2 k ) 0 + a 2 ( ω M 2 k ) 1 + a 4 ( ω M 2 k ) 2 + . + a n ( ω M 2 k ) M − 2 2 A_0=a_0(\omega_{\frac{M}{2}}^k)^0+a_2(\omega_{\frac{M}{2}}^k)^1+a_4(\omega_{\frac{M}{2}}^k)^2+.+a_n(\omega_{\frac{M}{2}}^k)^{\frac{M-2}{2}}\\ A0=a0(ω2Mk)0+a2(ω2Mk)1+a4(ω2Mk)2+.+an(ω2Mk)2M2

A 1 = ( ω M k ) ( a 1 ( ω M 2 k ) 0 + a 3 ( ω M 2 k ) 1 + a 5 ( ω M 2 k ) 2 + . + a M − 2 ( ω M 2 k ) M − 2 2 ) A_1=(\omega_{M}^k) (a_1(\omega_{\frac{M}{2}}^k)^0+a_3(\omega_{\frac{M}{2}}^k)^1+a_5(\omega_{\frac{M}{2}}^k)^2+.+a_{M-2}(\omega_{\frac{M}{2}}^k)^{\frac{M-2}{2}}) A1=(ωMk)(a1(ω2Mk)0+a3(ω2Mk)1+a5(ω2Mk)2+.+aM2(ω2Mk)2M2)

仔细观察 A 0 A_0 A0 A 1 A_1 A1,对于 A 0 A_0 A0来说,就相当于对剩下一半偶数部分的序列进行一次DFT,对于 A 1 A_1 A1来说就是对剩下一半奇数部分序列进行一次DFT的结果在乘以 ω M k \omega^k_M ωMk

D F T ( f m ) = D F T ( f 偶 数 部 分 ) + 因 子 ∗ D F T ( f 奇 数 部 分 ) DFT(f_m)=DFT(f_{偶数部分})+因子*DFT(f_{奇数部分}) DFT(fm)=DFT(f)+DFT(f)
整个过程就是把对n个数据的DFT转化成2个对 n 2 \frac{n}{2} 2n个数据的DFT

还有一个最重要的步骤,在《算法导论》里没有明确指出。

因为上面部分虽说计算速度加快,但始终只计算了序列中的一个 F k F_k FkDFT,但是仔细观察公式,可以得出下面结论
F K + M 2 = ∑ n = 0 M − 1 f n ( ω M K + M 2 ) n = a 0 ( ω M K + M 2 ) 0 + a 1 ( ω M K + M 2 ) 1 + a 2 ( ω M K + M 2 ) 2 + a 3 ( ω M K + M 2 ) 3 + . + a M − 1 ( ω M K + M 2 ) M − 1 F_{K+\frac{M}{2}}=\sum^{M-1}_{n=0}f_n(\omega_{M}^{K+\frac{M}{2}})^n=a_0(\omega_{M}^{K+\frac{M}{2}})^0+a_1(\omega_{M}^{K+\frac{M}{2}})^1+a_2(\omega_{M}^{K+\frac{M}{2}})^2+a_3(\omega_{M}^{K+\frac{M}{2}})^3+.+a_{M-1}(\omega_{M}^{K+\frac{M}{2}})^{M-1} FK+2M=n=0M1fn(ωMK+2M)n=a0(ωMK+2M)0+a1(ωMK+2M)1+a2(ωMK+2M)2+a3(ωMK+2M)3+.+aM1(ωMK+2M)M1
其中
W M M 2 = − 1 W_M^{\frac{M}{2}}=-1 WM2M=1
所以
W M K + M 2 = W M K W M M 2 = − W M K W_M^{K+\frac{M}{2}}=W^K_MW_M^{\frac{M}{2}}=-W^K_M WMK+2M=WMKWM2M=WMK
所以上面式子通过代换可以变成如下
F K + M 2 = ∑ n = 0 M − 1 f n ( ω M K + M 2 ) n = a 0 − a 1 ( ω M K ) 1 + a 2 ( ω M K ) 2 − a 3 ( ω M K ) 3 + . . . − a M − 1 ( ω M K ) M − 1 F_{K+\frac{M}{2}}=\sum^{M-1}_{n=0}f_n(\omega_{M}^{K+\frac{M}{2}})^n=a_0-a_1(\omega_{M}^{K})^1+a_2(\omega_{M}^{K})^2-a_3(\omega_{M}^{K})^3+...-a_{M-1}(\omega_{M}^{K})^{M-1} FK+2M=n=0M1fn(ωMK+2M)n=a0a1(ωMK)1+a2(ωMK)2a3(ωMK)3+...aM1(ωMK)M1
观察可以发现,偶数列的系数相比较 F k F_k Fk来说没有改变,而奇数列取负了。
F k + M 2 = A 0 − ω M k A 1 F_{k+\frac{M}{2}}=A_0-\omega_{M}^kA_1 Fk+2M=A0ωMkA1
也就说,只要我们得到其中一个数据的傅里叶变换,通过迭代乘以单位复数根可以很快求出整个序列的离散傅里叶变换。
在这里插入图片描述
至于IFFT,即快速傅里叶逆变换与上述过程几乎完全一样,具体改动看代码,懒得叙述

python代码实现如下

def FFT(f):
    n=len(f)
    if n==1:
        return f
    w_0=Complex(1)
    f_0=f[0::2]
    f_1=f[1::2]
    y=np.empty(shape=n,dtype=Complex)
    y_0=FFT(f_0)
    y_1=FFT(f_1)
    mid=int(n/2)
    for k in range(0,mid):
        w= Complex(np.cos(-2.*k*np.pi / n), np.sin(-2.*k*np.pi / n))
        y[k]=y_0[k]+w*y_1[k]
        y[k+mid] =y_0[k] - w * y_1[k]
    return y

IFFT

def IFFT(f):
    n=len(f)
    if n==1:
        return f
    w_0=Complex(1)
    f_0=f[0::2]
    f_1=f[1::2]
    y=np.empty(shape=n,dtype=Complex)
    y_0=IFFT(f_0)
    y_1=IFFT(f_1)
    mid=int(n/2)
    for k in range(0,mid):
        w= Complex(np.cos(2.*k*np.pi / n), np.sin(2.*k*np.pi / n))
        y[k]=(y_0[k]+w*y_1[k])/n
        y[k+mid] =(y_0[k] - w * y_1[k])/n
    return y

运行结果
在这里插入图片描述
现在来考虑《算法导论》中,这个FFT用来加速多项式乘法的应用,我觉得《算法导论》中并没有把这个问题完全说清楚。

首先看看普通的多项式乘法是如何进行实现的

    def multiplication(self,polynomial): # 多项式的乘法
        res=Polynomial(polynomial.degree+self.degree)
        for x in range(0,len(self.__coefficients__)):
            for y in range(0,len(polynomial.__coefficients__)):
                res.__coefficients__[x+y]=res.__coefficients__[x+y]+self.__coefficients__[x]*polynomial.__coefficients__[y]
        return res

就是直接套两个循环,简单粗暴,效率低下,时间效率为 Θ ( n 2 ) \Theta(n^2) Θ(n2)

多项式乘法,实际上就是两个多项式的系数进行卷积运算。先来看看离散的卷积公式
x ( n ) ∗ h ( n ) = ∑ i = 0 ∞ x ( i ) h ( n − i ) x(n)*h(n)=\sum_{i=0}^{\infty}x(i)h(n-i) x(n)h(n)=i=0x(i)h(ni)
再来举一个简单的例子,计算如下多项式乘法
C ( x ) = ( x 3 + 2 x 2 + 2 x + 1 ) ( x 3 + x 2 + x + 1 ) C(x)=(x^3+2x^2+2x+1)(x^3+x^2+x+1) C(x)=(x3+2x2+2x+1)(x3+x2+x+1)
现在我们令 x [ n ] = { 1 , 2 , 2 , 1 } , h [ n ] = { 1 , 1 , 1 , 1 } , x[n]=\{1,2,2,1\},h[n]=\{1,1,1,1\}, x[n]={1,2,2,1},h[n]={1,1,1,1},分别代表 x 3 到 x 0 x^3到x_0 x3x0的系数

现在我们来利用卷积,来计算上面多项式乘法的结果,其中 C [ i ] C[i] C[i]代表 x i x^i xi的系数

在这里插入图片描述
C [ 0 ] = ∑ i = 0 ∞ x ( i ) h ( 0 − i ) = 1 C[0]=\sum_{i=0}^{\infty}x(i)h(0-i)=1 C[0]=i=0x(i)h(0i)=1,示意图如下
在这里插入图片描述
C [ 1 ] = ∑ i = 0 ∞ x ( i ) h ( 1 − i ) = = 1 × 1 + 2 × 1 = 3 C[1]=\sum_{i=0}^{\infty}x(i)h(1-i)==1\times 1+2\times1=3 C[1]=i=0x(i)h(1i)==1×1+2×1=3,示意图如下
在这里插入图片描述
C [ 2 ] = ∑ i = 0 ∞ x ( i ) h ( 3 − i ) = = 1 × 1 + 2 × 1 + 2 × 1 = 5 C[2]=\sum_{i=0}^{\infty}x(i)h(3-i)==1\times 1+2\times1+2\times1=5 C[2]=i=0x(i)h(3i)==1×1+2×1+2×1=5,示意图如下
在这里插入图片描述
后面的就不继续画图演示了。但是这边又出现了一个问题,如果按照上述卷积公式来计算系数,只能只算到 x 4 x_4 x4就无法计算了,所以再进行卷积之前,我们要对 x [ n ] 、 h [ n ] x[n]、h[n] x[n]h[n]进行加倍次数界,用例子来说的话,本来 x [ n ] = { 1 , 2 , 2 , 1 } x[n]=\{1,2,2,1\} x[n]={1,2,2,1},但是加倍次数界之后就变成, x [ n ] = { 1 , 2 , 2 , 1 , 0 , 0 , 0 , 0 } x[n]=\{1,2,2,1,0,0,0,0\} x[n]={1,2,2,1,0,0,0,0}

总言而止,上面的例子就是为了让清楚,实际上多项式的乘法对应的就是多项式系数之间的卷积运算

而根据卷积定理(想要了解的,可以去看《信号与系统》第三章)
x ( n ) ∗ h ( n ) = D F T [ x ( n ) ] × D F T [ h ( n ) ] x(n)*h(n)=DFT[x(n)] \times DFT[h(n)] x(n)h(n)=DFT[x(n)]×DFT[h(n)]
也就是,空间域里的卷积运算,对应着频率中的乘积运算。

所以用FFT加速多项式乘法的原理就是,把空间域中复杂的卷积给转换到频率去进行乘法运算,然后再通过逆变换,就可以得到在空间域中的卷积运算的结果了。

def multiplication(x,h):
    fft_x=np.fft.fft(x) #对一个方程的系数进行傅里叶变换
    fft_h=np.fft.fft(h)#对第二个方程的系数进行傅里叶变换
    fft_res=fft_x*fft_h #对两个FFT变换结果进行相乘 (numpy中已经实现了数组相乘)
    return np.fft.ifft(fft_res)

上面的算法,执行了2次FFT和一次IFFT,所以一共消耗时间 3 n l o g 3nlog 3nlog

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;