拿到一段乱七八糟的振动数据,脑子里的第一个念头不是“上深度学习”,而是“先把它拆开,看看里面到底藏了几个成分”。频率和幅值都随时间乱变,非线性、非平稳,经典傅里叶变换打个频谱出来,糊成一片,看不出个所以然;EMD倒是能拆,可拆起来又慢又玄,模态混叠和端点飞翼让人头大。后来我在项目里认真试了一组方法,其中用到经验傅里叶分解(Empirical Fourier Decomposition, EFD),算是把这类问题处理得既清楚又顺手。这篇文章就是把我在实际使用中的理解、实现细节和踩过的坑一次讲明白,重点围绕“非线性非平稳时间序列”的分解场景展开,适合做信号处理、故障诊断、生物医学数据分析的朋友参考,想快速复现的话,后面的Python示例可以直接抄作业。
1. 为什么需要经验傅里叶分解
1.1 传统傅里叶变换在非平稳信号面前的无力感
先讲讲最基础的痛点。FFT是全局操作,它把一个时间信号展开成一系列固定频率的复正弦叠加,得到的是整个时间段内的“平均频谱”。对平稳信号来说,这个平均是有意义的;可一旦信号里出现了频率漂移、幅值突变、间歇性冲击,FFT的结果就变成了所有事件在频域上的叠加投影,时间信息完全丢失。
我举个例子,一个轴承在正常运行时期有稳定的振动特征频率,发生局部损伤后会产生周期性冲击,冲击还会激发系统的高频共振。整个信号从时间波形上看是“突变”的,从频率上看是“分时出现”的。FFT能告诉你存在哪些频率分量,却没办法告诉你它们分别在什么时间段出现、彼此的幅值包络怎么变。很多做工程诊断的朋友都遇到过:频谱图上峰不少,但逐个去对应故障特征频率时,宽度、边带、漂移互相叠在一起,根本分不清。
这里的关键词就是“非线性非平稳时间序列”。非线性意味着瞬时频率并非恒定,非平稳意味着统计特性随时间变化。传统傅里叶分析假定信号是线性和平稳的,这个前提一旦不满足,直接用FFT只能得到一个“混响”式的频谱。于是,大家开始转向自适应分解方法,希望把信号自动拆成一组有物理意义的分量,再逐个做时频分析。
1.2 EMD明明很流行,为什么还要找替代方案
经验模态分解(EMD)大家应该不陌生,它是自适应的,不需要预设基函数,能把任意信号分解成一组本征模态函数(IMF)。理论上,每个IMF代表一个单分量的调幅调频信号,分解结果可以做Hilbert谱分析。
但EMD有个绕不开的问题:它靠“筛选”迭代产生IMF,这个过程既没有数学上的全局最优保证,又对噪声和采样参数非常敏感。实际操作中常见毛病包括:模态混叠,就是一个IMF里混进了多个时间尺度的成分;端点效应,就是信号两端因插值产生的飞翼;还有每分解一次都是一轮迭代,数据一长计算就慢。更烦的是,两个频率成分离得比较近时,EMD往往分不开,你得先做集合平均也就是EEMD才能稍微缓解,但EEMD的计算量更大,参数还更敏感。
我在一次工程分析里,拿一段实测的变转速振动信号试EMD,前三个IMF的频率和幅值互相牵扯,怎么调参数都不理想。后来换了频域分割的思路,一下子就把主要模式和边带分得干干净净。这让我意识到,时域的“盲目筛选”不如频域的“先观察后分割”直观可控。
1.3 EFD的核心思想:按频谱“山峰”切蛋糕
经验傅里叶分解的思路其实不复杂,一句话概括:先对信号做傅里叶变换得到幅度谱,把这个幅度谱看成一片起伏的山脉,每个主要“山峰”对应信号里的一个主导频率成分;然后自动找出山峰之间的“山谷”作为分界线,把频谱切成若干连续的频段;最后对每个频段做带通滤波并从频域重建,得到一系列模态分量。
“经验”两个字就体现在频谱分割是数据驱动的,峰值位置、边界位置都是根据当前信号本身的频谱形状自动确定的,而不是预先指定固定频段。相比固定频带的滤波器组,它自适应地贴合信号特征;相比EMD,它直接在频域里完成模态划分,理论框架清晰,计算上就是一次FFT加几次逆FFT,速度非常快。
这个“切蛋糕”的思路对非线性调幅调频信号尤其有效。调幅信号在频谱上表现为中心频率两侧出现对称边带,只要把整个边带划进同一个频段,逆变换后就能恢复出完整的调幅成分,后续再通过Hilbert变换解调出包络和瞬时频率。这点比EMD的分解表现稳定得多。
1.4 EFD与EMD、EWT、VMD的横向对比
实际选型时,我会把EFD和它几个“近亲”放在一起比较,这样判断起来更有底。
| 方法 | 理论基础 | 是否需要预设模态数 | 主要缺点 | 计算速度 |
|---|---|---|---|---|
| EMD | 经验筛选 | 不需要 | 模态混叠、端点效应、递归迭代慢 | 慢 |
| EWT | 小波框架+频带分割 | 需要 | 边界选择依赖频谱特征 | 快 |
| VMD | 变分优化 | 需要 | 模态数和惩罚参数敏感 | 中等 |
| EFD | 傅里叶频域分割 | 通常需要 | 对边界敏感,密集谱峰容易分错 | 很快 |
EWT(经验小波变换)和EFD的关系最近,都是频谱分割思路,区别在于EWT用小波滤波器组实现,EFD直接用傅里叶频域掩码,本质上更像是“自适应滤波器组分解”。VMD通过变分优化把中心频率和带宽同时求出来,鲁棒性更好,但多个超参数调起来很吃力。就我的经验,信号频谱本身就是“山峰型”的时候,EFD是最快最直接的;频谱糊成一团、峰谷不明显的时候,VMD的优化能力反而更强。
我个人现在的工作流是:第一遍先用EFD快速看分解结构和模态个数,确定了信号构成之后,再决定要不要用VMD做精细分解。这个组合拳既快又稳。
2. EFD的完整算法流程:从频谱边界到模态分量
2.1 预处理和频谱峰值定位:边界的命根子
EFD的成败,一大半取决于频谱边界选得好不好。边界选对了,每个模态就是一个窄带信号;边界选错了,两个成分会被硬拆进同一个模态,或者一个成分被拦腰切断。
第一步的预处理很简单但很关键:信号先减去均值,去掉直流分量。不要小看这一步,直流分量如果不处理,它会占据谱峰搜索的最高峰位置,导致后续边界全乱。实测数据如果有明显趋势,最好先做一次趋势项去除,避免低频大能量干扰峰检测。
接下来对去均值后的信号做FFT,取单边幅度谱。峰值定位不能直接找原始幅度谱的局部极大值,因为真实信号往往有噪声,会在主峰旁边生成大量假峰。我有两个常用办法:
第一个是“平滑后找峰”。对幅度谱做一次移动平均平滑,窗口宽度一般取总谱线数的1%到3%。平滑后主峰会保留,毛刺会被抹掉,然后再用局部极大值搜索找到候选峰。这个办法简单直接,适合噪声不严重的信号。
第二个是“峰值聚类”。先找到所有局部极大值,按幅度从大到小排序,然后从最大的峰开始逐个检查:如果当前峰与已选峰的距离太近,说明它们属于同一个宽频带,只保留幅度较大的那个。距离阈值一般取整段频谱长度的2%左右,也就是50Hz采样率下,相隔少于1Hz的峰会被合并。
选峰完成后,边界就在相邻两个峰值之间的最低点处。最稳妥的做法是取两个峰之间的最小幅度谱位置作为分割点,代码简化时也可以取二者中点,实际效果差别不大。要知道,频带边界最终决定了模态带宽和分解结果,这里值得多花几分钟做可视化检查。
2.2 滤波器组设计与频域重构:注意过渡带和共轭对称
确定了N个边界后,频谱被切成N个频段,每个频段对应一个模态。提取模态的方式是构造频域掩码:在当前频段内置1,频段外置0,然后与原始信号FFT结果相乘,再逆FFT回来。这个过程本质上是用了理想的“矩形带通滤波器”。
但这里有个细节新手容易踩:直接用矩形窗做频域截断,逆变换后模态两端会出现吉布斯振铃,也就是由于频带边缘突变产生的高频波动。工程上解决的办法是给掩码的边缘加过渡带,把“0到1”的跳变变成一段有限长度的斜坡过渡。过渡带宽度一般占频段宽度的5%到10%,既能压制振铃,又不会让相邻模态串扰太多。
频域相乘时,必须保证掩码在正负频率上完全对称,否则逆FFT结果会出现虚部不为零的情况。实现时先构造正频率部分掩码,再把它翻转复制到负频率部分。如果信号采样频率较高、数据长度较大,直接把掩码设计成实数序列,既简单又高效。
整个过程直观理解就是:我在频域里画了几个“彩色玻璃窗”,窗口覆盖的频谱能量被完整拿回时域成为模态。每个模态依然是长度与原始信号一致的实信号,保存了带宽内的全部时变信息。
2.3 模态的后续使用:Hilbert变换提取瞬时频率与包络
EFD分解得到的模态分量,如果拿来做时频分析,还需要最后一步“解调”。最常用的工具是Hilbert变换。
对任意模态分量s(t),做Hilbert变换后构造解析信号s(t)+j·H[s(t)],这个复数信号的幅值就是瞬时包络,相位对时间求导得到瞬时角频率,除以2π就是瞬时频率。这相当于把每个模态从“一条波形”变成“一个随时间变化的幅值和一条随时间变化的频率曲线”。
我曾用这个流程处理一段包含变转速信息的振动信号,第二个模态分解出来是明显的调幅波,Hilbert解调后得到的包络频率与转频完全对应,瞬时频率也平滑地跟着转速走。这验证了EFD分解结果是有物理意义的,而不是纯粹的数学拆分。
在实际工程中,这步做完就可以直接进入诊断环节了:对包络信号再做一次FFT,得到包络谱,故障特征频率会在包络谱中清晰可见;瞬时频率曲线则可以用于转速估计、阶比跟踪分析。
3. 动手复现:一个可以直接跑的Python示例
3.1 主函数实现与关键参数说明
下面这段代码是我在实际项目中精简后的版本,思路保留了EFD的核心流程:找峰、定边界、频域掩码滤波。可以直接在Jupyter Notebook里跑,依赖只有numpy和scipy。
import numpy as np from scipy.fft import fft, ifft, fftfreq def efd_decompose(x, fs=1.0, num_modes=None, transition_ratio=0.1): """ 经验傅里叶分解(简化版) 参数 ---- x : 1维ndarray,输入信号 fs : 采样频率(Hz) num_modes : 期望分解的模态数,默认自动选择 transition_ratio : 过渡带宽度占比,0.05~0.15之间比较稳妥 返回 ---- modes : 分解得到的模态分量数组,shape=(num_modes, len(x)) boundaries : 频带边界索引(对应单边频谱的索引) """ # 去直流 x = x - np.mean(x) n = len(x) half_n = n // 2 # FFT得到单边频谱 X = fft(x) mag = np.abs(X[:half_n]) freqs = fftfreq(n, d=1/fs)[:half_n] # 找局部极大值点 peaks = [] for i in range(1, half_n - 1): if mag[i] > mag[i-1] and mag[i] > mag[i+1]: peaks.append(i) # 按幅度排序,保留能量较大的峰 peaks = sorted(peaks, key=lambda idx: mag[idx], reverse=True) # 如果没指定模态数,取前5个显著峰 if num_modes is None: num_modes = min(5, len(peaks)) # 保留前num_modes个峰,按频率从小到大排序 selected_peaks = sorted(peaks[:num_modes]) # 在相邻峰之间取中点作为边界 boundaries = [0] for i in range(len(selected_peaks) - 1): mid = (selected_peaks[i] + selected_peaks[i+1]) // 2 boundaries.append(mid) boundaries.append(half_n) # 构造滤波器组并提取模态 modes = [] for k in range(len(boundaries) - 1): mask = np.zeros(half_n) start = boundaries[k] end = boundaries[k+1] # 过渡带宽度 trans = max(1, int(transition_ratio * (end - start))) # 上升沿 lo_edge = max(0, start - trans) for i in range(lo_edge, start): mask[i] = (i - lo_edge) / (start - lo_edge) # 通带 mask[start:end] = 1.0 # 下降沿 hi_edge = min(half_n, end + trans) for i in range(end, hi_edge): mask[i] = 1.0 - (i - end) / (hi_edge - end) # 构建对称掩码并提取模态 full_mask = np.concatenate([mask, mask[1:][::-1]]) mode = np.real(ifft(X * full_mask)) modes.append(mode) return np.array(modes), boundaries, freqs, mag几个参数的使用心得:num_modes不指定时会自动取前5个峰,这个默认值适合先快速看结构;正式分析时我几乎总是手动指定模态数,原因下面会说。transition_ratio我习惯取0.08左右,太小振铃压不住,太大相邻模态会互相渗透,分解结果的相关性升高。
这段代码是教学用的简化版,没有做峰值聚类,所以信号中存在密集边带峰时,自动选峰可能会把同一个模态的边带拆开。正式使用时,我会在找峰之前先对mag做一次移动平均平滑,把毛刺峰抹平,再进入峰值排序环节。
3.2 用调幅信号验证分解效果
为了验证EFD的效果,我构造了一个典型的非线性非平稳合成信号,包含三个成分,其中一个是明显的调幅分量:
fs = 1000 t = np.arange(0, 2, 1/fs) # 调幅分量,载波150Hz,调制频率4Hz am_component = (1 + 0.5 * np.cos(2 * np.pi * 4 * t)) * np.cos(2 * np.pi * 150 * t) # 两个纯余弦分量 c1 = np.cos(2 * np.pi * 60 * t) c2 = 0.6 * np.cos(2 * np.pi * 300 * t) # 加一点白噪声 rng = np.random.default_rng(42) noise = 0.05 * rng.standard_normal(len(t)) x = c1 + am_component + c2 + noise # 手动指定3个模态 modes, boundaries, freqs, mag = efd_decompose(x, fs=fs, num_modes=3)这里手动指定3个模态,原因很实际:频谱上150Hz附近会出现135Hz和165Hz两个边带峰,它们本质上属于同一个调幅成分,自动选峰很可能把其中一个边带当成独立模态,导致分解结构混乱。手动指定3个模态后,EFD会找到60Hz、150Hz、300Hz三个主峰,并把150Hz附近所有相关谱线全部划进第二个频段。
分解结果的解读非常直观:第一个模态是一条近似60Hz的正弦波;第二个模态的波形能看到明显的“鼓包”,幅值随时间起伏,这个起伏频率就是包络里的4Hz调制信号;第三个模态是300Hz的纯余弦。把三个模态相加再和原始信号对比,重构误差几乎只有噪声级别。
接下来用Hilbert变换对第二个模态做解调:
from scipy.signal import hilbert analytic = hilbert(modes[1]) envelope = np.abs(analytic) instant_freq = np.diff(np.unwrap(np.angle(analytic))) / (2 * np.pi) * fs解调出的包络频谱里,4Hz处会出现一个明显的峰,说明EFD完整保留了调幅成分,没有把它拆散。这个实验验证了EFD对非线性调制信号的有效性,也说明了“模态数指定”在代码实现中的重要程度。
3.3 从实验室信号到工程信号:故障诊断的基本流程
同样的流程放到真实的滚动轴承故障数据里,思路完全一致。先对原始振动加速度信号做EFD分解,得到若干个窄带模态;然后对每个模态做Hilbert包络解调,找到能量最高、冲击特征最明显的那个模态;最后对这个模态的包络谱做FFT,在包络谱中寻找轴承故障特征频率及其倍频。
这个流程比直接对整个原始信号做包络谱更有效,因为EFD先滤掉了无关频带和背景噪声,再解调时信噪比会明显提升。我处理过一组外圈故障数据,直接在原始信号上做包络谱,故障频率几乎被噪声淹没;先EFD分解,取包含高频共振的模态再解调,特征频率一目了然。
实际工程中,我建议把EFD和“模态数选择”一起做成“半自动流水线”:第一次用默认参数跑出分解结构,人工看一眼频谱和模态波形,确定真正的模态数,再固定参数批量处理同类信号。这样既保证了自动化程度,又保留了关键环节的人工把关。
4. 避坑指南:我实际调用EFD时踩过的那些坑
4.1 模态数量怎么定,才能不让分解变成“硬切”
EFD和VMD一样,存在需要指定模态数的问题。模态数太少,两个频率成分会挤在同一个模态里;模态数太多,一个宽频带会被切出很多碎屑模态。我的经验是三个判断标准。
第一,看频谱峰的数量。明显高出周围底噪的峰有几个,模态数大致就取几个,这是最直接的依据。第二,看模态的正交性。分解完成后计算各模态之间的相关系数矩阵,如果两个模态的相关系数明显偏高,说明它们本应合并,减少模态数重跑。第三,看重构误差。把所有模态加在一起还原原始信号,误差太大说明分解不充分或参数不合理。
没有“正确答案”的时候,我宁可让模态数稍微多一两个,也不能过少。多出来的碎屑模态后续可以人工合并,少切了则会把不同频率成分混在一起,补救起来更麻烦。
4.2 边界选不好?先平滑频谱,再考虑尺度空间
原始频谱噪声重、毛刺多的时候,峰值搜索会找到一大堆假峰,边界自然就乱了。第一反应不是去调峰值阈值,而是对频谱做平滑。移动平均窗口宽度一般从1%开始试,观察主峰数量的稳定区间,如果窗口过宽把真实主峰也抹掉了,再往下调。
更严谨的方案叫尺度空间法,本质是“在不同平滑程度下观察峰的变化”,保留那些在不同平滑尺度下都能存活下来的峰。这些峰才是真正稳定的模式,毛刺噪声峰则会在尺度增大时快速消失。这个方法更稳健,但计算复杂度高一些,适合处理那些频谱结构模糊、峰谷不明显的困难信号。
边界的分割点我也建议从“中点”改成“谷值点”。相邻两个峰之间取幅度最小的位置作为边界,能把两个模态的泄漏降到最低。代码里写中点图省事,工程上还是做谷值点搜索更考究。
4.3 端点振铃和吉布斯现象,如何尽量压制
EFD用的是FFT,FFT隐含周期延拓。如果信号首尾不连续,逆FFT得到的模态两端会出现明显振荡,这就是吉布斯现象。它和EMD的端点效应表现类似,但成因完全不同。
处理办法不是简单加窗,因为加窗会破坏信号本身的信息。我更常用的做法是“镜像延拓”:在分解之前把信号左右各延伸一段镜像数据,分解完成后再把模态两端的多余部分裁掉。镜像延拓保证了首尾连续性,又不会引入太多虚假信息,是性价比最高的方案。
如果信号特别敏感,延拓后还要检查模态两端的包络是否异常突起。出现突起时,我会把过渡带宽度适当调大,再重跑一次分解。这些细节叠加起来,能明显改善模态在端点处的质量。
4.4 EFD、EWT、VMD到底怎么选
选型没有绝对的“最强”,只有“最合适”。我把自己的选型逻辑分享出来供参考:信号频谱清晰、主峰突出、频率成分明显分隔,首选EFD,因为快且解释性强;信号频谱模糊、主峰不明显、噪声强烈,选VMD,让变分优化去找合理的带宽;需要完整的多分辨率小波分析,选EWT,它在滤波器设计上更灵活。
EFD的强项是极快的计算速度和直观的频带解释,弱项是依赖频谱边界质量。VMD的优点是鲁棒性,弱项是参数太多、调参成本高。我自己的习惯是先EFD快速看结构,再用VMD精细化验证,两者结合的结果几乎从未让我失望。
最后再分享一个小技巧:分解之前一定先把信号的采样频率、数据长度、单位这些元信息记录下来。很多人只关心分解算法本身,却忽略了采样频率对边界频率解释的决定性作用。边界索引换算成物理频率,依赖的就是fs这个参数。项目里我吃过一次亏,边界画对了,频率标错了,整个诊断结论全偏,从那以后,数据头信息永远和分解结果放在一起。