ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从EMD到CEEMDAN:噪声辅助分解的模态混叠、完备性与残余噪声

从EMD到CEEMDAN:噪声辅助分解的模态混叠、完备性与残余噪声

简介:CEEMDAN算法配套实现包面向从事非线性、非平稳信号分析的研究者与工程技术人员,聚焦EMD、EEMD在模态混叠与稳定性上的不足,提供可对照学习的自适应噪声完全集成经验模态分解实现。压缩包共5个文件,包含4个.m源码脚本和1个.mat信号数据,整体约32KB,源码覆盖分解主流程与示例调用,数据可用于复现实验并观察IMF分量。已有5935人学习下载,说明其在算法入门与实验复现中具有一定参考价值。读者可据此梳理添加自适应噪声、迭代执行EMD、集成平均及IMF提取的关键环节,理解收敛速度与分解精度的改进逻辑,并可将思路迁移至地震波解析、心电脑电处理、金融序列分析和机械故障诊断等场景,适合作为算法对比实验与课程实践的轻量素材。

1. 加噪声反而更干净:CEEMDAN 到底在修 EMD 的哪个洞

把一段含冲击的振动信号丢进 EMD(经验模态分解),跑出来的 IMF 常常让人皱眉:同一阶分量里既有几十赫兹的工频干扰,又有几百赫兹的瞬态冲击,两种尺度黏在一起,谁也没法单独拿去做包络谱。于是有人反其道而行,往信号里塞白噪声——按直觉这应该更脏,结果模态混叠反而散了。这就是 EEMD 的立论起点:噪声不是干扰,是脚手架。而 CEEMDAN 在 EEMD 之上又补了一刀,把「集合平均留下的残余噪声」和「重构对不上原信号」这两笔欠账一起还了。搞懂这条演进线,等于搞懂自适应分解里最核心的一组设计取舍:确定性、完备性、计算量,三者不可兼得。

2. EMD 的筛分机制:模态混叠为什么会自己长出来

2.1 筛分与 IMF 的两个硬条件:窄带假设从哪来

EMD 的全部野心可以压成一句话:把信号拆成若干个能算瞬时频率的分量。而「能算瞬时频率」这件事有前提——信号必须是窄带的。Huang 给的判定不玩虚的,直接落成两个可编程检查的条件:一是极值点个数与过零点个数之差不超过 1;二是上下包络线的局部均值为零。前者拦住了「骑在另一个分量上」的情况,后者拦住了「带着局部直流偏置」的情况。两条合起来,就是在没有傅里叶变换的前提下,硬造出一个窄带的定义。

筛分(sifting)就是反复把这两个条件逼出来的过程。取当前信号的局部极大值做上包络、局部极小值做下包络,两条包络取平均得到均值曲线 m(t),令 h(t) = x(t) − m(t),再对 h 重复同样的操作。每减一次均值,相当于做一次「数据自适应的去趋势」。它不依赖任何基函数,也没有固定截止频率——这正是 EMD 对非平稳信号有效的原因,也是它没有解析解、只能靠迭代逼近的原因。

用代码把这一步摊开,核心只有几十行,难的是边界和停止条件:

import numpy as np from scipy.interpolate import CubicSpline def find_extrema(x): """差分符号变化定位局部极大/极小,返回索引""" d = np.diff(x) max_idx = np.where((d[:-1] > 0) & (d[1:] <= 0))[0] + 1 min_idx = np.where((d[:-1] < 0) & (d[1:] >= 0))[0] + 1 return max_idx, min_idx def envelope(idx, val, n, mode="mirror"): """三次样条构造包络;端点先镜像延拓一个极值周期,抑制外推发散""" if mode == "mirror": t = np.concatenate(([-idx[0]], idx, [2 * (n - 1) - idx[-1]])) v = np.concatenate(([val[0]], val, [val[-1]])) else: t, v = idx, val cs = CubicSpline(t, v, bc_type="natural") # 自然边界:二阶导端点为零 return cs(np.arange(n)) def sift(x, sd_th=0.2, max_iter=10): """单次筛分:反复减均值,直到 SD 准则或迭代上限""" h = x.copy() for _ in range(max_iter): max_idx, min_idx = find_extrema(h) if len(max_idx) < 2 or len(min_idx) < 2: break # 极值点太少,无法构造包络 upper = envelope(max_idx, h[max_idx], len(h)) lower = envelope(min_idx, h[min_idx], len(h)) m = 0.5 * (upper + lower) h_new = h - m # SD 停止准则:两次筛分结果的归一化能量差 sd = np.sum((h - h_new) ** 2) / (np.sum(h ** 2) + 1e-12) h = h_new if sd < sd_th: break return h

sd_th取 0.2~0.3 是行业默认,max_iter必须设上限是因为过度筛分会把 IMF 磨成纯粹的调幅调频信号,物理意义反而丢失——冲击成分的瞬态特征会被「筛」平。这一点很容易踩坑:把 SD 阈值调到 0.05 以下,看着 IMF 更「干净」了,实际上故障特征已经被磨没了。bc_type="natural"只是保底,真正决定端点行为的是延拓方式,下一节展开。

2.2 三次样条与端点效应:误差怎么沿着迭代往中间爬

包络拟合用三次样条几乎是默认选择,理由是它二阶连续、能跟上极值点的曲率变化。代价是样条在数据区间之外是发散的——信号两端如果没有极值点,样条只能靠外推,而外推的曲线会剧烈上翘或下坠。这就是端点效应的源头。

误差的传播有两条路径,很多人只注意到第一条。第一条是向内:每次筛分减均值时,端点附近的错误包络值被减进 h(t),下一轮极值点定位就可能偏移,误差一轮一轮往里带。第二条是向外:每提取出一阶 IMF,残差 r(t) = x(t) − c(t) 的两端就累积一次误差,等到分解低频分量时,两端已经偏得离谱,表现为低频 IMF 头尾翘起,数值甚至超出原信号范围。

常见的对策是延拓,思路都是「让样条在端点附近有据可依」:镜像延拓把端点附近的极值对称翻到外侧,极值点延拓直接在端点补一个估计极值,自回归延拓用信号自身的相关性外推一段。还有一种工程做法是加窗——对信号两端乘一个缓升缓降的窗,人为把端点压到零,代价是丢掉一小段数据。我一般会这么理解这三类方案:延拓是「补数据」,加窗是「弃数据」,前者保长度但引入假设,后者保真实但损失样本,选哪个取决于你后面要做什么分析。

2.3 模态混叠:极值点分布不均才是病根

模态混叠的典型表现有两种:一个 IMF 里同时含尺度差异很大的成分,或者同一尺度成分散落在多个相邻 IMF 里。它在时域上看着像「一阶分量忽然抖了一下」,在 Hilbert 谱上看着像同一条频率脊线断成几截、跳到别的阶上去。

病根不在筛分算法,在极值点的分布。包络只在有极值的地方被定义,如果信号前半段只有低频、后半段只有高频,那么插值出来的均值曲线会把后半段的高频振荡「抄」进低频分量里——因为在那个时间段,极值点全由高频提供,包络自然跟着高频走。间歇性成分(比如周期性出现的冲击)是最容易触发这个问题的:它在时间轴上是稀疏的,极值点在时间轴上分布极不均匀。

这里有个容易被忽略的性质:EMD 是确定性算法,给定信号输出唯一。所以模态混叠不是随机误差,是系统性的结构缺陷,重跑一百次结果一模一样。这解释了后面所有改进为什么都走「随机化 + 集合平均」这条路——只有引入随机性,才能用统计手段去修一个确定性的结构缺陷。判断是否混叠,实用做法是看每个 IMF 的瞬时频率方差,或者直接把各阶 IMF 画在同一张时频图上找脊线断裂。

3. EEMD 的统计化改造:白噪声凭什么能抹平间歇性

3.1 白噪声的二进滤波器组:加噪等于给极值点重新铺格子

EEMD 的做法看着粗暴:往原信号里加一组高斯白噪声,做一次 EMD,重复 N 次,把每次得到的第 k 阶 IMF 取平均作为最终的第 k 阶。噪声在平均里被压掉,信号被保留。

它成立的技术支点有两个,缺一不可。第一个是白噪声在频域上是平的,任何频段都有能量,所以它能在时间轴上把所有位置的极值点密度「填匀」——原本某段只有高频极值、某段只有低频极值的信号,加噪之后每一段的极值点都变得稠密且尺度混合,包络均值不再被单一尺度绑架。噪声在这里扮演的是「均匀参考系」的角色,让不同尺度的成分在同一套极值网格上被公平对待。

第二个支点更隐蔽:白噪声经 EMD 分解后,各阶 IMF 的能量近似按二倍频程分布,等价于一组二进滤波器组。这个性质意味着,一次 EMD 分解实际上是把信号投影到一组中心频率近似倍频的频带上,而这些频带的边界由数据自身决定。加噪之后再分解,相当于用一个尺度均匀的「探测器阵列」去扫信号,间歇成分被分散投影到多个尺度上,集合平均时各自归位。

注意:二进滤波器组是对白噪声统计意义上的近似结论,对确定性信号不严格成立。用它来指导参数选择可以,用它来推导误差上界会翻车。

3.2 两个旋钮:噪声幅值与集合数怎么定

EEMD 只有两个关键参数,但它们的耦合关系经常被忽视。噪声幅值比 a 定义为加入噪声的标准差与信号标准差之比,常用 0.2~0.5;集合数 NE 是一次分解重复的次数,常用 100~200。

经验公式给了一个量化的取舍:e = a / √NE,其中 e 是集合平均后的相对误差。代入 a = 0.2、NE = 100,得 e = 0.02;想要 1% 的误差,NE 得提到 400。这个公式的真正含义不是「照着填」,而是告诉你两件事:噪声加得越猛,需要的集合数越多(平方关系,成本涨得很快);集合数增加带来的误差下降是 1/√NE 的,属于典型的边际递减——NE 从 100 加到 400 只换来误差减半,计算时间却翻了四倍。

选 a 的经验是看信号的频谱重心:高频成分主导时 a 取小(0.1~0.2),否则噪声会把高频细节糊掉;低频缓变信号 a 可以取大(0.3~0.5),因为噪声主要影响高频段,对低频分量的干扰小。另一个判据是看信号是否含强冲击:冲击是宽带瞬态,加噪后容易和冲击的高频成分混在一起,这类信号 a 要压到 0.1 以下,靠加大 NE 补精度。

def eemd(x, NE=100, ratio=0.2, max_imf=8, seed=0): """EEMD:整体加噪 -> 完整 EMD -> 逐阶集合平均""" rng = np.random.default_rng(seed) # 固定种子,保证可复现 sigma = np.std(x) acc = [] # 按阶累积 IMF for _ in range(NE): w = rng.standard_normal(len(x)) * sigma * ratio imfs = emd_decompose(x + w, max_imf) # 对加噪信号做完整分解 for k, c in enumerate(imfs): if k == len(acc): acc.append(np.zeros_like(x)) acc[k] += c imfs_avg = [c / NE for c in acc] # 集合平均:噪声方差降到 1/NE resid = x - np.sum(imfs_avg, axis=0) # 用减法制残差,保证可重构 return imfs_avg, resid

ratio是相对sigma的,这一点很关键——如果实现里把它当绝对值用,遇到幅度量级 1e-3 的传感器信号,加进去的噪声直接等于把信号淹了。seed必须固定,否则同一份数据跑两次,IMF 阶数都可能不一样,论文复现和工程验收都会翻车。最后用减法反算残差而不是直接用分解出的残差,是为了让 ΣIMF + resid 精确等于原信号。

3.3 集合平均之后的那笔账:残余噪声为什么还不掉

EEMD 常被说成「解决了模态混叠」,但它没说的是代价。加进去的噪声不会因为有 NE 次平均就消失,它只是从标准差 a 降到了 a/√NE。这意味着一阶 IMF(高频、能量小)里始终叠着一层宽带底噪,在包络谱上表现为一片抬起来的背景,弱故障特征很容易被这层底噪盖住。

更麻烦的是完备性。严格来说,加了噪声之后分解,再平均,结果不等于原信号的分解——重构误差不为零,且这个误差随 a 增大而增大。有些实现干脆用「原信号减去所有 IMF」来凑残差,把误差全塞进残差项里,看起来重构完美了,实际上残差里混进了本该属于 IMF 的噪声成分,趋势项被污染。

还有一个理论层面的隐患:EMD 是非线性变换,对 N 个加噪信号分别分解得到 N 组 IMF,再逐点平均,平均后的分量不保证仍然满足 IMF 的两个条件。实践中大多数情况下它够用,但在严格的分析场景(比如要拿 IMF 去做瞬时频率的物理解释)里,这个「近似合法」是需要说清楚的。CEEMDAN 的改动,本质上就是冲着这两笔账去的。

4. CEEMDAN 的逐阶残差:噪声为什么必须先过一次 EMD

4.1 把 EMD 算子化:E_k(·) 这个记号带来什么

CEEMDAN 的第一个贡献是把 EMD 从「一次性动作」变成「可调用的算子」。记 E_k(x) 为对信号 x 做 EMD 后取第 k 阶 IMF。这个记号看着只是写法变化,实际改变了算法结构:既然可以单独取某一阶,就不必每次都对整段信号做完整分解。

有了这个算子,EEMD 的结构问题就暴露得很清楚。EEMD 是对原信号加一次噪声、做完整分解、最后逐阶平均,这意味着第 k 阶 IMF 的噪声污染来自「同一个白噪声经 EMD 后剩下的第 k 阶分量」。白噪声的能量按二进分布集中在高频,越往高阶,噪声的残余能量越小但依然存在,而且它是通过一次完整的分解「顺带」留下来的,无法针对性清除。

CEEMDAN 把顺序倒过来:不是先分解再平均,而是逐阶「加噪 → 只取第一阶 → 平均 → 减残差」。每一阶只关心当前这一阶的噪声,上一阶处理完立刻从残差里剥掉,噪声没有机会往后面的阶累积。

4.2 逐级残差加噪:为什么噪声要先过一遍 EMD

第一阶的处理和 EEMD 一致:IMF₁ = (1/N) Σ E₁(x + ε₀·wᵢ),然后 r₁ = x − IMF₁。真正的改动从第二阶开始:

IMF_k = (1/N) Σ E₁( r_{k−1} + ε_{k−1} · E_{k−1}(wᵢ) )

注意括号里那一项,加进去的不是白噪声 wᵢ,而是白噪声经过 EMD 之后取的第 k−1 阶分量。这是整个算法最容易被略过、也最值得琢磨的一处设计。

理由在于尺度匹配。第 k 阶残差 r_{k−1} 里剩下的主要是较低频的成分,而白噪声的能量集中在高频。如果直接把白噪声加到 r_{k−1} 上,噪声的大部分能量落在残差里已经不存在的频段,对当前阶的极值点分布几乎没有贡献——白加了;更糟的是,这些高频噪声会在后续筛分中被当作独立分量提出来,变成新的残余噪声留在低阶 IMF 里。先用 E_{k−1}(w) 把噪声「搬」到与残差相近的尺度上,加进去才真正改变了当前阶的极值点分布,加噪效率才高。

另一个工程上的好处是计算量可控:每次内部只取 E₁(第一阶 IMF),意味着内部 EMD 做几轮筛分就可以停,不必分解到底。这一点在集合数 N 取几百时非常实在。

def ceemdan(x, NE=100, eps0=0.2, max_imf=10, seed=0): """CEEMDAN:逐阶加噪、只取第一阶 IMF、立刻更新残差""" rng = np.random.default_rng(seed) sigma_x = np.std(x) imfs, r = [], x.copy() for k in range(1, max_imf + 1): max_idx, min_idx = find_extrema(r) if len(max_idx) + len(min_idx) < 3: break # 残差已无足够极值,退化为趋势项 # 幅值系数随残差尺度自适应:残差越小,噪声绝对幅度越小 eps = eps0 * np.std(r) / (sigma_x + 1e-12) acc = np.zeros_like(x) for _ in range(NE): w = rng.standard_normal(len(x)) # 关键:k=1 用白噪声本身,k>=2 用白噪声的第 k-1 阶 IMF n_k = w if k == 1 else emd_imf(w, k - 1) acc += emd_imf(r + eps * n_k * sigma_x, 1) imf_k = acc / NE imfs.append(imf_k) r = r - imf_k # 精确减法,保证完备性 return imfs, r # r 是趋势项,必须一起返回

eps0是基准幅值比,eps = eps0 · σ(r)/σ(x)是自适应缩放:随着阶数上升,残差幅度递减,如果 ε 固定不变,噪声的相对强度会越来越大,最后把残差淹没,分解出一堆由噪声驱动的虚假 IMF。emd_imf(w, k-1)这一行是算法灵魂,注释里写清楚它的作用——尺度对齐。循环退出条件用极值点数量而不是固定阶数,是因为不同信号的天然阶数不同,硬编max_imf会在某些信号上切掉有效分量。

4.3 ε_k 与停止条件:什么时候该收手

噪声幅值系数的取法在文献里有几套,实用的有两套。一套是固定 ε,简单但前面说的缺陷明显;另一套是按残差标准差自适应,即 ε_k = ε₀ · σ(r_k)/σ(x),这也是多数开源实现的默认。后者的物理含义是:始终保持噪声与当前残差有一个固定的信噪比,不让噪声在低阶分量里相对变强。

停止条件比幅值系数更容易被忽视。三条常用判据:残差的极值点少于 3 个(无法构造上下包络,再分解没有意义);残差已经单调或近似单调;达到预设的最大阶数。第一条最本质,它对应的是「残差已经退化成趋势项」这个物理事实。工程上还会加一条能量判据:当某一阶 IMF 的能量占比低于阈值(比如 0.1%)时停止,避免继续分解纯噪声。

需要提醒的是,CEEMDAN 的阶数通常比 EEMD 少。因为每阶都做了尺度对齐,噪声不会在低阶累积出虚假分量,分解自然更早收敛。如果发现自己的 CEEMDAN 跑出十几阶,先别怀疑算法,去看eps0是不是设太大了。

4.4 完备性与残余噪声:两笔账分开算

CEEMDAN 最实在的优势是完备性。因为残差是精确减出来的,r_k = r_{k−1} − IMF_k 是恒等式,所以 x = Σ IMF_k + r_K 严格成立,重构误差只受浮点精度影响。这跟 EEMD 里「加噪导致重构对不上」是本质区别——EEMD 的重构误差是算法性的,CEEMDAN 的不是。

但完备性不等于干净。IMF 里仍然残留着噪声,量级还是 O(ε/√NE),只是它不再体现为重构误差,而是体现为每个 IMF 上的宽带底噪。这两笔账必须分开算:重构误差看 ΣIMF + r 与原信号的差值,残余噪声看 IMF 在无信号频段的能量。只看前者会得出「完美」的错误结论。

后续还有改进版本(比如在噪声加权方式上再做调整的变体),思路是把「加噪—取第一阶—平均」这套流程里的平均次数分配得更聪明,让低阶 IMF 拿到更多平均次数。这类改进的收益在弱特征提取场景下明显,代价是实现复杂度上升,选型时要看你的信噪比是否真的到了那一步。

5. 落地排查:CEEMDAN 从跑通到可信之间的五道坎

5.1 重构对不上:末阶残差被当成噪声扔了

现象很典型:把imfs列表加起来,和原信号差一条缓慢的曲线,差值方差不为零,低频段尤其明显。

原因几乎都是同一个——CEEMDAN 的循环是因为极值点不够而提前退出的,剩下的r是趋势项,不是噪声。它包含了信号里所有的直流和缓变成分,能量往往比前几阶 IMF 还大。很多人只保存imfs,把r丢了。

解决办法是让函数显式返回残差,并在重构校验时强制加上:x_rec = np.sum(imfs, axis=0) + r。更稳妥的做法是在函数内部就断言np.allclose(x_rec, x),把这个约束焊死在代码里,而不是靠使用者记得。

5.2 同一份数据跑两次结果不一样

现象是 IMF 的阶数、波形、能量分布全都变了,看着像算法不稳定。

原因是每次都重新初始化了随机数生成器,或者干脆没设种子。CEEMDAN 的随机性来自加噪,这是设计特性不是缺陷,但可复现性是工程底线。

解决方式有两层:代码层固定seed并在日志里打印出来;流程层把NE、eps0、seed、信号长度一起写进结果元数据。做对比实验时,不同算法之间要用同一组噪声样本,否则你比的是噪声差异不是算法差异。

5.3 集合数不够,IMF 里全是毛刺

现象是第一阶 IMF 的能量异常高,包络谱上出现一片抬起来的宽带底噪,弱特征被盖住。

原因是残余噪声按 ε/√NE 衰减,NE = 50 时残余噪声是 NE = 200 时的两倍。默认值往往按「能跑就行」设,不按「能信」设。

解决思路是按目标精度反推 NE:先估一个可接受的残余噪声水平,再解 √NE = ε/e。另一个低成本技巧是给第一阶 IMF 单独加倍集合数——高频阶的噪声最难压,而它恰好是多数故障诊断最关心的那阶。

5.4 小幅值信号加噪后分量被淹没

现象是信号幅度在 1e-3 量级,用默认参数跑完,IMF 波形明显畸变,甚至分解出纯噪声阶。

根因是幅值系数被当成了绝对值。有些实现里ratio直接乘一个常数,而不是乘std(x),信号量级一小,噪声的信噪比就崩了。

解决方式是所有幅值系数一律相对std(x)归一化,并在分解前打印一次实际信噪比20·log10(σ(x)/(ε·σ(x)))做自检。如果信号本身含强冲击,还要把eps0压到 0.1 以下,靠 NE 补精度。

5.5 端点翘飞,低频 IMF 超出信号范围

现象是最后几阶 IMF 的两端数值剧烈偏离,画出来像两只翘起的角,做趋势分析时误差很大。

这是三次样条外推的老问题,CEEMDAN 每阶都要重新构造包络,端点误差会被反复引入,比 EMD 更敏感。

解决办法是延拓,镜像或极值点延拓都行,关键是延拓长度要覆盖至少一个极值周期。如果信号本身很短(不足 100 点),延拓反而是负担,这时更实际的做法是两端各裁掉 5%~10% 再分析——牺牲样本换稳定,在短数据分析里通常是划算的。

6. 噪声辅助法的设计取舍:什么时候该停手

把三代算法放在一张表里,取舍关系就一目了然了:

维度EMDEEMDCEEMDAN
确定性完全确定随机(依赖种子)随机(依赖种子)
模态混叠严重明显改善明显改善
完备性严格完备不完备(加噪导致)严格完备
残余噪声无O(a/√NE),各阶均匀O(ε/√NE),低阶更小
关键参数停止准则a、NEε₀、NE、停止条件
计算量1 次分解NE 次完整分解每阶 NE 次浅分解

表格里最值得盯的是最后一行。EEMD 的 NE 次是「完整分解」,CEEMDAN 的是「每阶 NE 次只取第一阶的浅分解」——单次便宜,但阶数一多,总次数是 NE × K。这意味着 CEEMDAN 的计算量并不天然优于 EEMD,在阶数多的信号上甚至更慢。选型时如果只看到「CEEMDAN 更先进」就换,很容易在实时场景里翻车。

判断分解是否可信,我会看三个指标:重构误差‖x − ΣIMF − r‖/‖x‖,应当在 1e-12 量级;正交性指标IO = Σ_{i≠j} Σ_t cᵢ(t)cⱼ(t) / Σ_t x²(t),理想值接近 0,实际常在 0.01~0.05,超过 0.1 说明分量之间有严重泄漏;以及各阶 IMF 的瞬时频率方差,方差大意味着模态混叠没压住。三个指标一起看,比单看波形靠谱得多。

还有一类场景根本不该用噪声辅助:数据极短(极值点不足十几个)、信号本身是强冲击序列(加噪会污染冲击的宽带特征)、以及需要严格可复现的在线处理。这些情况下 EMD 加良好的端点处理和停止准则反而更合适——不是所有问题都值得引入随机性。

血泪经验是从一次「看起来完美」的重构开始的。当时 CEEMDAN 的重构误差在 1e-15,我直接拿 IMF 去做包络谱,结果弱特征完全找不到;回头把 IMF 的功率谱画出来才发现,第一阶上盖着一层比信号还高的宽带底噪,NE 只设了 30。从那以后我每次跑完噪声辅助分解,都强制走一遍「重构校验 + 正交性 + 无信号频段底噪」三件套,参数和种子一起落盘,再谈结果。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进