Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >快速傅里叶变换(FFT)算法【详解】[通俗易懂]

快速傅里叶变换(FFT)算法【详解】[通俗易懂]

作者头像
全栈程序员站长
发布于 2022-09-07 00:40:59
发布于 2022-09-07 00:40:59
7.4K00
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

快速傅里叶变换(Fast Fourier Transform)是信号处理与数据分析领域里最重要的算法之一。我打开一本老旧的算法书,欣赏了JW Cooley 和 John Tukey 在1965年的文章中,以看似简单的计算技巧来讲解这个东西。

本文的目标是,深入Cooley-Tukey FFT 算法,解释作为其根源的“对称性”,并以一些直观的python代码将其理论转变为实际。我希望这次研究能对这个算法的背景原理有更全面的认识。

FFT(快速傅里叶变换)本身就是离散傅里叶变换(Discrete Fourier Transform)的快速算法,使算法复杂度由原本的O(N^2) 变为 O(NlogN),离散傅里叶变换DFT,如同更为人熟悉的连续傅里叶变换,有如下的正、逆定义形式:

xn 到 Xk 的转化就是空域到频域的转换,这个转换有助于研究信号的功率谱,和使某些问题的计算更有效率。事实上,你还可以查看一下我们即将推出的天文学和统计学的图书的第十章(这里有一些图示和python代码)。作为一个例子,你可以查看下我的文章《用python求解薛定谔方程》,是如何利用FFT将原本复杂的微分方程简化。

正因为FFT在那么多领域里如此有用,python提供了很多标准工具和封装来计算它。NumPy 和 SciPy 都有经过充分测试的封装好的FFT库,分别位于子模块 numpy.fft 和 scipy.fftpack 。我所知的最快的FFT是在 FFTW包中 ,而你也可以在python的pyFFTW 包中使用它。

虽然说了这么远,但还是暂时先将这些库放一边,考虑一下怎样使用原始的python从头开始计算FFT。

计算离散傅里叶变换

简单起见,我们只关心正变换,因为逆变换也只是以很相似的方式就能做到。看一下上面的DFT表达式,它只是一个直观的线性运算:向量x的矩阵乘法,

矩阵M可以表示为

这么想的话,我们可以简单地利用矩阵乘法计算DFT:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1 import numpy as np
2 def DFT_slow(x):
3     """Compute the discrete Fourier Transform of the 1D array x"""
4     x = np.asarray(x, dtype=float)
5     N = x.shape[0]
6     n = np.arange(N)
7     k = n.reshape((N, 1))
8     M = np.exp(-2j * np.pi * k * n / N)
9     return np.dot(M, x)

对比numpy的内置FFT函数,我们来对结果进行仔细检查

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
x = np.random.random(1024)
np.allclose(DFT_slow(x), np.fft.fft(x))

输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
True

现在为了验证我们的算法有多慢,对比下两者的执行时间

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
%timeit DFT_slow(x)
 
%timeit np.fft.fft(x)

输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
10 loops, best of 3: 75.4 ms per loop
10000 loops, best of 3: 25.5 µs per loop

使用这种简化的实现方法,正如所料,我们慢了一千多倍。但问题不是这么简单。对于长度为N的输入矢量,FFT是O(N logN)级的,而我们的慢算法是O(N^2)级的。这就意味着,FFT用50毫秒能干完的活,对于我们的慢算法来说,要差不多20小时! 那么FFT是怎么提速完事的呢?答案就在于他利用了对称性。

离散傅里叶变换中的对称性

算法设计者所掌握的最重要手段之一,就是利用问题的对称性。如果你能清晰地展示问题的某一部分与另一部分相关,那么你就只需计算子结果一次,从而节省了计算成本。

Cooley 和 Tukey 正是使用这种方法导出FFT。 首先我们来看下

的值。根据上面的表达式,推出:

对于所有的整数n,exp[2π i n]=1。

最后一行展示了DFT很好的对称性:

简单地拓展一下:

同理对于所有整数 i 。正如下面即将看到的,这个对称性能被利用于更快地计算DFT。

DFT 到 FFT:

利用对称性 Cooley 和 Tukey 证明了,DFT的计算可以分为两部分。从DFT的定义得:

我们将单个DFT分成了看起来相似两个更小的DFT。一个奇,一个偶。目前为止,我们还没有节省计算开销,每一部分都包含(N/2)∗N的计算量,总的来说,就是N^2 。

技巧就是对每一部分利用对称性。因为 k 的范围是 0≤k<N , 而 n 的范围是 0≤n<M≡N/2 , 从上面的对称性特点来看,我们只需对每个子问题作一半的计算量。O(N^2) 变成了 O(M^2) 。

但我们不是到这步就停下来,只要我们的小傅里叶变换是偶倍数,就可以再作分治,直到分解出来的子问题小到无法通过分治提高效率,接近极限时,这个递归是 O(n logn) 级的。

这个递归算法能在python里快速实现,当子问题被分解到合适大小时,再用回原本那种“慢方法”。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 1 def FFT(x):
 2     """A recursive implementation of the 1D Cooley-Tukey FFT"""
 3     x = np.asarray(x, dtype=float)
 4     N = x.shape[0]
 5  
 6     if N % 2 > 0:
 7         raise ValueError("size of x must be a power of 2")
 8     elif N <= 32:  # this cutoff should be optimized
 9         return DFT_slow(x)
10     else:
11         X_even = FFT(x[::2])
12         X_odd = FFT(x[1::2])
13         factor = np.exp(-2j * np.pi * np.arange(N) / N)
14         return np.concatenate([X_even + factor[:N / 2] * X_odd,
15                                X_even + factor[N / 2:] * X_odd])

现在我们做个快速的检查,看结果是否正确:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
x = np.random.random(1024)
np.allclose(FFT(x), np.fft.fft(x))

输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
True

然后与“慢方法”的运行时间对比下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
%timeit DFT_slow(x)
 
%timeit FFT(x)
 
%timeit np.fft.fft(x)

输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
10 loops, best of 3: 77.6 ms per loop
100 loops, best of 3: 4.07 ms per loop
10000 loops, best of 3: 24.7 µs per loop

现在的算法比之前的快了一个数量级。而且,我们的递归算法渐近于 O(n logn) 。我们实现了FFT 。

需要注意的是,我们还没做到numpy的内置FFT算法,这是意料之中的。numpy 的 fft 背后的FFTPACK算法 是以 Fortran 实现的,经过了多年的调优。此外,我们的NumPy的解决方案,同时涉及的Python堆栈递归和许多临时数组的分配,这显著地增加了计算时间。

还想加快速度的话,一个好的方法是使用Python/ NumPy的工作时,尽可能将重复计算向量化。我们是可以做到的,在计算过程中消除递归,使我们的python FFT更有效率。

向量化的NumPy

注意上面的递归FFT实现,在最底层的递归,我们做了N/32次的矩阵向量乘积。我们的算法会得益于将这些矩阵向量乘积化为一次性计算的矩阵-矩阵乘积。在每一层的递归,重复的计算也可以被向量化。因为NumPy很擅长这类操作,我们可以利用这一点来实现向量化的FFT

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 1 def FFT_vectorized(x):
 2     """A vectorized, non-recursive version of the Cooley-Tukey FFT"""
 3     x = np.asarray(x, dtype=float)
 4     N = x.shape[0]
 5  
 6     if np.log2(N) % 1 > 0:
 7         raise ValueError("size of x must be a power of 2")
 8  
 9     # N_min here is equivalent to the stopping condition above,
10     # and should be a power of 2
11     N_min = min(N, 32)
12  
13     # Perform an O[N^2] DFT on all length-N_min sub-problems at once
14     n = np.arange(N_min)
15     k = n[:, None]
16     M = np.exp(-2j * np.pi * n * k / N_min)
17     X = np.dot(M, x.reshape((N_min, -1)))
18  
19     # build-up each level of the recursive calculation all at once
20     while X.shape[0] < N:
21         X_even = X[:, :X.shape[1] / 2]
22         X_odd = X[:, X.shape[1] / 2:]
23         factor = np.exp(-1j * np.pi * np.arange(X.shape[0])
24                         / X.shape[0])[:, None]
25         X = np.vstack([X_even + factor * X_odd,
26                        X_even - factor * X_odd])
27  
28     return X.ravel()
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
x = np.random.random(1024)
np.allclose(FFT_vectorized(x), np.fft.fft(x))

输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
True

因为我们的算法效率更大幅地提升了,所以来做个更大的测试(不包括DFT_slow)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
x = np.random.random(1024 * 16)
%timeit FFT(x)
 
%timeit FFT_vectorized(x)
 
%timeit np.fft.fft(x)

输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
10 loops, best of 3: 72.8 ms per loop
100 loops, best of 3: 4.11 ms per loop
1000 loops, best of 3: 505 µs per loop

我们的实现又提升了一个级别。这里我们是以 FFTPACK中大约10以内的因数基准,用了仅仅几十行 Python + NumPy代码。虽然没有相应的计算来证明, Python版本是远优于 FFTPACK源,这个你可以从这里浏览到。

那么 FFTPACK是怎么获得这个最后一点的加速的呢?也许它只是一个详细的记录簿, FFTPACK花了大量时间来保证任何的子计算能够被复用。我们这里的numpy版本涉及到额外的内存的分配和复制,对于如Fortran的一些低级语言就能够很容易的控制和最小化内存的使用。并且Cooley-Tukey算法还能够使其分成超过两部分(正如我们这里用到的Cooley-Tukey FFT基2算法),而且,其它更为先进的FFT算法或许也可以能够得到应用,包括基于卷积的从根本上不同的方法(例如Bluestein的算法和Rader的算法)。结合以上的思路延伸和方法,就可使阵列大小即使不满足2的幂,FFT也能快速执行。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/155732.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
NumPy Essentials 带注释源码 六、NumPy 中的傅里叶分析
# 来源:NumPy Essentials ch6 绘图函数 import matplotlib.pyplot as plt import numpy as np def show(ori_func, ft, sampling_period = 5): n = len(ori_func) interval = sampling_period / n # 绘制原始函数 plt.subplot(2, 1, 1) plt.plot(np.arange(0,
ApacheCN_飞龙
2019/02/15
9240
NumPy Essentials 带注释源码 六、NumPy 中的傅里叶分析
傅里叶变换算法和Python代码实现
傅立叶变换是物理学家、数学家、工程师和计算机科学家常用的最有用的工具之一。本篇文章我们将使用Python来实现一个连续函数的傅立叶变换。
deephub
2024/03/20
4590
傅里叶变换算法和Python代码实现
Python实现快速傅里叶变换(FFT)
这里做一下记录,关于FFT就不做介绍了,直接贴上代码,有详细注释的了: import numpy as np from scipy.fftpack import fft,ifft import matplotlib.pyplot as plt import seaborn #采样点选择1400个,因为设置的信号频率分量最高为600赫兹,根据采样定理知采样频率要大于信号频率2倍,所以这里设置采样频率为1400赫兹(即一秒内有1400个采样点,一样意思的) x=np.linspace(0,1,1400)
py3study
2020/01/13
2.5K0
快速傅里叶变换
算法:快速傅里叶变换(Fast Fourier Transform,FFT)是利用计算机计算离散傅里叶变换(DFT)的高效、快速计算方法的统称。
裴来凡
2022/05/29
3420
快速傅里叶变换
OpenCV系列之傅里叶变换 | 三十
傅立叶变换用于分析各种滤波器的频率特性。对于图像,使用2D离散傅里叶变换(DFT)查找频域。一种称为快速傅立叶变换(FFT)的快速算法用于DFT的计算。关于这些的详细信息可以在任何图像处理或信号处理教科书中找到。请参阅其他资源部分。
磐创AI
2019/12/23
1.6K0
OpenCV系列之傅里叶变换 | 三十
【STM32F407的DSP教程】第25章 DSP变换运算-快速傅里叶变换原理(FFT)
在数字信号处理中常常需要用到离散傅立叶变换(DFT),以获取信号的频域特征。尽管传统的DFT算法能够获取信号频域特征,但是算法计算量大,耗时长,不利于计算机实时对信号进行处理。因此导致DFT被发现以来,在很长的一段时间内都不能被应用到实际工程项目中,直到一种快速的离散傅立叶计算方法——FFT被发现,离散是傅立叶变换才在实际的工程中得到广泛应用。需要强调的是,FFT并不是一种新的频域特征获取方式,而是DFT的一种快速实现算法。
Simon223
2020/05/27
1.2K0
【STM32F407的DSP教程】第25章    DSP变换运算-快速傅里叶变换原理(FFT)
转:fft算法(快速傅里叶变换算法)
FFT (Fast Fourier Transform) 是一种快速傅里叶变换算法。它是用来将一个信号从时域转换到频域的算法。这个算法通过分治策略,将一个长度为 N 的复数序列分解成 N/2 个长度为 2 的复数序列,然后对这些小的序列分别进行 FFT 计算。
啵啵鳐
2023/07/04
5080
离散傅立叶变换的Python实现
离散傅里叶变换(Discrete Fourier Transform,缩写为DFT),是指傅里叶变换在时域和频域上都呈现离散的形式,将时域信号的采样变换为在离散时间傅里叶变换(DTFT)频域的采样。在形式上,变换两端(时域和频域上)的序列是有限长的,而实际上这两组序列都应当被认为是离散周期信号的主值序列。即使对有限长的离散信号做DFT,也应当对其经过周期延拓成为周期信号再进行变换。在实际应用中,通常采用快速傅里叶变换来高效计算DFT。
曼亚灿
2023/07/31
1.5K0
离散傅立叶变换的Python实现
使用傅立叶变换清理时间序列数据噪声
傅立叶变换是一种从完全不同的角度查看数据的强大方法:从时域到频域。 但是这个强大的运算用它的数学方程看起来很可怕。
deephub
2021/10/20
4.4K0
使用傅立叶变换清理时间序列数据噪声
基于python的快速傅里叶变换FFT(
基于python的快速傅里叶变换FFT(二) 本文在上一篇博客的基础上进一步探究正弦函数及其FFT变换。
py3study
2020/02/10
2.7K0
信号处理之频谱原理与python实现
EEG信号是大脑神经元电活动的直接反应,包含着丰富的信息,但EEG信号幅值小,其中又混杂有噪声干扰,如何从EEG信号中抽取我们所感兴趣的信号是一个极为重要的问题。自1932年Dietch首先提出用傅里叶变换方法来分析EEG信号,该领域相继引入了频域分析、时域分析等脑电分析的经典方法。
脑机接口社区
2020/06/30
2.1K0
信号处理之频谱原理与python实现
使用python进行傅里叶FFT-频谱分析详细教程
说明:本文适合信号处理方面有一定的基础的人阅读,能够理解什么时候傅里叶级数和傅里叶变换,能够理解他们的核心思想以及基本原理,能够理解到底什么是“频率域”,能够从频率的角度分析信号。
小草AI
2019/06/02
24.1K0
NumPy 基础知识 :6~10
除其他事项外,傅立叶分析通常用于数字信号处理。 这要归功于它在将输入信号(时域)分离为以离散频率(频域)起作用的分量方面如此强大。 开发了另一种快速算法来计算离散傅里叶变换(DFT),这就是众所周知的快速傅里叶变换(FFT),它为分析及其应用提供了更多可能性。 NumPy 针对数字计算,也支持 FFT。 让我们尝试使用 NumPy 在应用上进行一些傅立叶分析! 注意,本章假定不熟悉信号处理或傅立叶方法。
ApacheCN_飞龙
2023/04/23
2.5K0
NumPy 基础知识 :6~10
你还弄不懂的傅里叶变换,神经网络只用了30多行代码就学会了
在我们的生活中,大到天体观测、小到MP3播放器上的频谱,没有傅里叶变换都无法实现。
量子位
2021/06/17
1.1K0
傅里叶变换
是描述数学函数或物理信号对时间的关系。例如一个信号的时域波形可以表达信号随着时间的变化。是真实世界,是惟一实际存在的域。因为我们的经历都是在时域中发展和验证的,已经习惯于事件按时间的先后顺序地发生。
为为为什么
2022/08/05
1.7K0
傅里叶变换
面试官让你使用 scipy.fft 进行Fourier Transform,你会吗
傅立叶变换是许多应用中的重要工具,尤其是在科学计算和数据科学中。因此,SciPy 长期以来一直提供它的实现及其相关转换。最初,SciPy 提供了该scipy.fftpack模块,但后来他们更新了他们的实现并将其移到了scipy.fft模块中。
查理不是猹
2021/12/17
1.3K0
傅里叶变换的图像应用--学好了用处大~
傅里叶变换,一个听起来高大上的名词。初学之时也是云里雾里,一旦学成,应用及其广泛,图像、信号、声波、深度学习等各领域都存在它的身影,包括在地学中,它也能有很大的用处~至于哪些方面?不展开啦
一个有趣的灵魂W
2020/09/15
5670
傅里叶变换的图像应用--学好了用处大~
从DTFT到DFS,从DFS到DFT,从DFT到FFT,从一维到二维
因为要移植CSK得写快速傅里叶变换的算法,还是二维的,以前在pc平台上只需调用库就可以了,只是有点印象原信号和变换之后代表的是什么,但是对于离散傅里叶变换的来龙去脉忘得已经差不多了,最近要用到,于是重新来学习一遍,翻出了自己大三当时录的吴镇扬老师讲的数字信号处理的视频,DFT-FFT这里老师讲了有10讲之多,但每讲都不是很长,20分钟左右,这里记录一下学习的过程,前面的推导有点多,简书又打不了公式,mathtype的直接复制也不过来,截图又太麻烦,也为了自己再推导一遍,手写了前面一部分的内容。图片形式传上来。 简单说几句:DTFT有了之后为什么还要搞出来一个DFT呢,其根本原因就是因为DTFT的频域是连续的,无法用计算机进行处理。根据我们之前得到的的傅里叶变换的规律:
和蔼的zhxing
2018/09/04
2K0
从DTFT到DFS,从DFS到DFT,从DFT到FFT,从一维到二维
OpenCV快速傅里叶变换(FFT)用于图像和视频流的模糊检测
翻译自【OpenCV Fast Fourier Transform (FFT) for blur detection in images and video streams】,原文链接:
周旋
2020/09/22
3.2K1
卷积神经网络中的傅里叶变换:1024x1024 的傅里叶卷积
卷积神经网络 (CNN) 得到了广泛的应用并且事实证明他是非常成功的。但是卷积的计算很低效,滑动窗口需要很多计算并且限制了过滤器的大小,通常在 [3,3] 到 [7,7] 之间的小核限制了感受野(最近才出现的大核卷积可以参考我们以前的文章),并且需要许多层来捕获输入张量的全局上下文(例如 2D 图像)。图像越大小核的的表现就越差。这就是为什么很难找到处理输入高分辨率图像的 CNN模型。
deephub
2022/11/11
1.6K0
卷积神经网络中的傅里叶变换:1024x1024 的傅里叶卷积
相关推荐
NumPy Essentials 带注释源码 六、NumPy 中的傅里叶分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验