可恶啊,又让我想起这玩意儿了,然后又忘记怎么推了,只能回去查一查了。其实我困扰的是,CNN的卷积为啥叫卷积啊,卷积不是 h ( t ) = ∑ k f ( k ) g ( t − k ) h(t)=\mathop{\sum}\limits_kf(k)g(t-k) h(t)=k∑f(k)g(t−k)吗,那个卷积核,分明可以直接对应元素相乘吗,网上有些图居然也是直接乘了。我以为卷积也有什么快速算法啊,可是,普通的卷积又不具有傅里叶变换的性质。
其实傅里叶变换也忘得差不多了,不过,就先这样推着吧。
参考:The Fast Fourier Transform and its Applications
Notation
W
N
=
e
x
p
{
2
π
i
N
}
W_N = exp\{\frac{2\pi i}{N}\}
WN=exp{N2πi}
X
(
j
)
=
∑
n
=
0
N
−
1
A
(
n
)
W
N
j
n
X(j)=\mathop{\sum}\limits_{n=0}^{N-1}A(n)W_N^{jn}
X(j)=n=0∑N−1A(n)WNjn
A
(
n
)
=
1
N
∑
n
=
0
N
−
1
X
(
j
)
W
N
−
j
n
A(n)=\frac{1}{N}\mathop{\sum}\limits_{n=0}^{N-1}X(j)W_N^{-jn}
A(n)=N1n=0∑N−1X(j)WN−jn
性质
W
N
N
=
1
,
W
N
j
+
N
=
W
N
j
W_N^N=1, \quad W_N^{j+N}=W_N^j
WNN=1,WNj+N=WNj
W
N
j
=
W
N
j
 
m
o
d
 
N
W_N^j=W_N^{j \:mod\:N}
WNj=WNjmodN
X
(
j
)
=
X
(
j
 
m
o
d
 
N
)
X(j)=X(j\:mod\:N)
X(j)=X(jmodN)
A
(
n
)
=
A
(
n
 
m
o
d
 
N
)
A(n) = A(n \: mod\: N)
A(n)=A(nmodN)
总而言之,有个周期
N
N
N
∑
n
=
0
N
−
1
W
N
n
j
W
N
m
j
=
N
i
f
 
n
=
m
 
m
o
d
 
N
否
则
0
\mathop{\sum}\limits_{n=0}^{N-1}W_N^{nj}W_N^{mj}=N \quad if \: n=m\: mod\:N否则0
n=0∑N−1WNnjWNmj=Nifn=mmodN否则0
FFT推导
假
设
N
=
r
×
s
,
那
么
存
在
j
1
,
j
0
,
对
于
任
意
的
j
可
用
下
列
式
子
表
示
:
假设N= r\times s,那么存在j_1,j_0,对于任意的j可用下列式子表示:
假设N=r×s,那么存在j1,j0,对于任意的j可用下列式子表示:
j
=
j
1
r
+
j
0
j
1
=
0
,
1
,
…
,
s
−
1
,
j
0
=
0
,
1
,
…
,
r
−
1
j = j_1r+j_0 \quad j_1=0,1,\ldots,s-1,\quad j_0=0,1,\ldots,r-1
j=j1r+j0j1=0,1,…,s−1,j0=0,1,…,r−1
同
样
,
对
于
n
来
说
,
存
在
n
1
,
n
0
:
同样,对于n来说,存在n_1, n_0:
同样,对于n来说,存在n1,n0:
n
=
n
1
s
+
n
0
n
1
=
0
,
1
,
…
,
r
−
1
,
n
0
=
0
,
1
,
…
,
s
−
1
n = n_1s + n_0 \quad n_1=0,1,\ldots,r-1,\quad n_0=0,1,\ldots,s-1
n=n1s+n0n1=0,1,…,r−1,n0=0,1,…,s−1
W
N
j
n
=
W
N
j
1
n
1
r
s
W
N
j
1
r
n
0
W
N
j
0
n
1
s
W
N
j
0
n
0
=
W
s
j
1
n
0
W
r
j
0
n
1
W
N
j
0
n
0
W_N^{jn}=W_N^{j_1n_1rs}W_N^{j_1rn_0}W_N^{j_0n_1s}W_N^{j_0n_0}=W_s^{j_1n_0}W_r^{j_0n_1}W_N^{j_0n_0}
WNjn=WNj1n1rsWNj1rn0WNj0n1sWNj0n0=Wsj1n0Wrj0n1WNj0n0
X
(
j
)
=
X
(
j
1
,
j
0
)
=
∑
n
0
=
0
s
−
1
∑
n
1
=
0
r
−
1
A
(
n
1
,
n
0
)
W
r
j
0
n
1
W
N
j
0
n
0
W
s
j
1
n
0
X(j)=X(j_1,j_0)=\mathop{\sum}\limits_{n_0=0}^{s-1}\mathop{\sum}\limits_{n_1=0}^{r-1}A(n_1,n_0)W_r^{j_0n_1}W_N^{j_0n_0}W_s^{j_1n_0}
X(j)=X(j1,j0)=n0=0∑s−1n1=0∑r−1A(n1,n0)Wrj0n1WNj0n0Wsj1n0
令
:
A
1
(
j
0
,
n
0
)
=
W
N
j
0
n
0
∑
n
1
=
0
r
−
1
A
(
n
1
,
n
0
)
W
r
j
0
n
1
令:A_1(j_0, n_0)=W_N^{j_0n_0}\mathop{\sum}\limits_{n_1=0}^{r-1}A(n_1,n_0)W_r^{j_0n_1}
令:A1(j0,n0)=WNj0n0n1=0∑r−1A(n1,n0)Wrj0n1
X
(
j
1
,
j
0
)
=
∑
n
0
=
0
s
−
1
A
1
(
n
1
,
n
0
)
W
s
j
1
n
0
X(j_1,j_0)=\mathop{\sum}\limits_{n_0=0}^{s-1}A_1(n_1,n_0)W_s^{j_1n_0}
X(j1,j0)=n0=0∑s−1A1(n1,n0)Wsj1n0
让
我
们
来
看
看
这
第
一
次
分
解
所
消
耗
的
计
算
量
,
计
算
一
个
A
1
(
j
0
,
n
0
)
消
耗
r
次
,
那
么
计
算
全
部
的
A
1
就
是
r
s
r
=
N
r
次
。
在
A
1
全
部
知
道
的
情
况
下
,
计
算
一
个
X
(
j
1
,
j
0
)
是
s
次
,
计
算
所
有
的
X
需
要
r
s
s
=
N
s
次
,
故
本
次
分
解
消
耗
N
(
r
+
s
)
次
计
算
。
如
果
不
分
解
的
话
,
大
概
是
N
2
级
别
。
让我们来看看这第一次分解所消耗的计算量,计算一个A_1(j_0,n_0)消耗r次,那么计算全部的A_1就是rsr=Nr次。在A_1全部知道的情况下,计算一个X(j_1,j_0)是s次,计算所有的X需要rss=Ns次,故本次分解消耗N(r+s)次计算。如果不分解的话,大概是N^2级别。
让我们来看看这第一次分解所消耗的计算量,计算一个A1(j0,n0)消耗r次,那么计算全部的A1就是rsr=Nr次。在A1全部知道的情况下,计算一个X(j1,j0)是s次,计算所有的X需要rss=Ns次,故本次分解消耗N(r+s)次计算。如果不分解的话,大概是N2级别。
更
加
恐
怖
的
是
这
是
第
一
次
分
解
,
可
以
看
到
,
X
(
j
1
,
j
0
)
成
了
周
期
为
s
的
傅
里
叶
变
换
,
A
1
(
j
0
,
n
0
)
成
了
周
期
为
r
的
傅
里
叶
变
换
,
所
以
,
如
果
,
r
,
s
还
能
够
分
解
的
话
,
计
算
量
还
能
进
一
步
减
少
。
更加恐怖的是这是第一次分解,可以看到,X(j_1,j_0)成了周期为s的傅里叶变换,A_1(j_0,n_0)成了 周期为r的傅里叶变换,所以,如果,r,s还能够分解的话,计算量还能进一步减少。
更加恐怖的是这是第一次分解,可以看到,X(j1,j0)成了周期为s的傅里叶变换,A1(j0,n0)成了周期为r的傅里叶变换,所以,如果,r,s还能够分解的话,计算量还能进一步减少。
假
设
:
N
=
r
m
,
s
=
r
m
−
1
,
那
么
,
计
算
A
1
总
共
消
耗
N
r
=
r
m
+
1
次
,
这
时
X
变
为
周
期
为
s
=
r
m
−
1
的
傅
里
叶
变
换
,
可
以
预
见
,
将
s
分
解
为
r
m
−
2
×
r
,
计
算
A
2
应
当
消
耗
r
m
次
,
但
第
二
次
需
要
计
算
假设:N=r^m,s=r^{m-1},那么,计算A_1总共消耗Nr=r^{m+1}次,这时X变为周期为s=r^{m-1}的傅里叶变换,可以预见,将s分解为r^{m-2}\times r,计算A_2应当消耗r^{m}次,但第二次需要计算
假设:N=rm,s=rm−1,那么,计算A1总共消耗Nr=rm+1次,这时X变为周期为s=rm−1的傅里叶变换,可以预见,将s分解为rm−2×r,计算A2应当消耗rm次,但第二次需要计算r
个
这
种
情
况
(
因
为
X
(
j
1
,
j
0
)
总
共
有
N
个
,
分
成
了
r
个
周
期
为
s
的
傅
里
叶
变
换
)
,
所
以
第
二
次
消
耗
的
计
算
量
也
为
个这种情况(因为X(j_1,j_0)总共有N个,分成了r个周期为s的傅里叶变换),所以第二次消耗的计算量也为
个这种情况(因为X(j1,j0)总共有N个,分成了r个周期为s的傅里叶变换),所以第二次消耗的计算量也为r^{m+1}
,
以
此
类
推
,
最
后
结
果
为
:
,以此类推,最后结果为:
,以此类推,最后结果为:
m
∗
r
m
+
1
=
m
N
r
m * r^{m+1}=mNr
m∗rm+1=mNr
又
N
=
r
m
,
所
以
m
=
l
o
g
r
N
,
多
么
牛
啊
,
原
本
二
次
的
计
算
量
近
似
线
性
了
!
N=r^m,所以m=log_rN,多么牛啊,原本二次的计算量近似线性了!
N=rm,所以m=logrN,多么牛啊,原本二次的计算量近似线性了!
一
般
情
况
下
,
N
=
r
1
×
r
2
×
⋯
r
m
,
最
后
的
计
算
量
为
N
(
r
1
+
r
2
+
…
+
r
m
)
一般情况下,N=r_1\times r_2\times \cdots r_m,最后的计算量为N(r_1+r_2+\ldots +r_m)
一般情况下,N=r1×r2×⋯rm,最后的计算量为N(r1+r2+…+rm)
论文里的推导过程
代码
import numpy as np
import time
from scipy.fftpack import fft, ifft
def number_fc(N): #因式分解 比较蠢的方法 我在数论里好像看到过更好的就这样吧
for i in range(2, int(np.sqrt(N)) + 1):
if N % i == 0:
s = i
r = N / i
return int(s), int(r)
return 0, 0
def conv(x, k): #普通的运算
N = len(x)
w = np.array([np.exp(-2 * i * k * np.pi * 1j / N) for i in range(N)]) #论文中是+的 不是-的 但是scipy库里的是-的所以我这里也取-
return x @ w
def w_s_N(j0, j1, s, N): #求 怎么说呢 看懂式子就懂这个了
w_s_j1 = np.array([np.exp(-2 * n0 * j1 * np.pi * 1j / s) for n0 in range(s)])
w_N_n0 = np.array([np.exp(-2 * n0 * j0 * np.pi * 1j / N) for n0 in range(s)])
return w_s_j1 * w_N_n0
def FFT(x): #FFT
N = len(x)
s, r = number_fc(N)
if not s: #当s或r=0也就是N不能再分解了求直接返回傅里叶变换后的
return np.array([conv(x, k) for k in range(N)])
else:
A0 = np.zeros(N, dtype=complex) #相当于X_j1_j0
A1 = np.array([FFT(x[n0::s]) for n0 in range(s)]) #计算A1_j0_n0 输出的是一个矩阵,s*r的,每个元素是一个A1_J0_N0
for j1 in range(s):
for j0 in range(r):
A0[j1 * r + j0] = A1[:, j0] @ w_s_N(j0, j1, s, N)
return A0
测试代码:
N = 4096
x = np.arange(N)
t1 = time.time()
y1 = FFT(x)
t2 = time.time()
print(t2 - t1) #0.5311074256896973
t1 = time.time()
y2 = [conv(x, k) for k in range(N)]
t2 = time.time()
print(t2-t1) #26.705339193344116
t1 = time.time()
y3 = fft(x)
t2 = time.time()
print(t2 - t1) #0.0
从上面可以看到,当N = 4096的时候,二者的差距已经十分明显了。第三个方案是,scipy库里面的,即便N都这么大了,依然动不了他分毫。大概是我写代码的水平还是太low了吧。
在写代码的时候,对于时间复杂的计算也有了新的认识,不是那么想当然的,果然实践才是检验真理的唯一标准。