☰
CEEMDAN信号分解算法详解:从原理到Python实现与应用
2026/10/16 7:46:02 网站建设 项目流程

1. 这个标题到底在说什么:从一长串名字拆出真实需求

先别被这串英文吓到。38_CEEMDAN这个名字拆开看就三部分:38是资料编号,CEEMDAN才是主角。Complete Ensemble Empirical Mode Decomposition with Adaptive Noise翻译过来就是“自适应噪声完备集合经验模态分解”。它是一个信号处理算法,属于经验模态分解(EMD)家族里比较新的一代。

我在处理振动信号、心电信号、股票价格序列这类非线性、非平稳数据时经常用到它。简单说,传统的傅里叶变换默认信号是平稳的、由正弦波叠加而成,但现实里的信号几乎都不满足这个假设——齿轮箱的振动信号里有冲击成分,脑电信号里有瞬态突变,股票价格更是毫无“平稳”可言。EMD 这类方法的好处是不需要预设基函数,直接从数据本身把信号分解成若干个本征模态函数(IMF),每个 IMF 代表一个尺度上的振荡模式。

CEEMDAN 解决的则是 EMD 的一个老大难问题:模态混叠。早期 EMD 在处理间歇性信号时,一个 IMF 里可能混着好几种频率成分,物理意义就模糊了。后来有人提出 EEMD(集合经验模态分解),通过加白噪声再平均来抑制混叠,但代价是重构误差大、计算量暴涨,而且不同噪声实现分解出的 IMF 数量还可能对不上。CEEMDAN 的思路更聪明:每一层都加自适应噪声,并且每分解出一阶 IMF 就立即把噪声的残余影响去掉,所以分解是完备的,重构误差几乎为零。

这篇内容的核心价值,就是帮你从原理到代码走通 CEEMDAN。不管你是做故障诊断、生物医学信号处理,还是金融时序分析,它都能作为你数据预处理的第一站。我会把原理、实现、参数、避坑一次性讲透。

2. 从 EMD 到 CEEMDAN:三代方法的演进逻辑,搞懂这个才知道为什么选它

2.1 EMD:经验的、直观的、但是有缺陷的

EMD(Empirical Mode Decomposition)由黄锷提出,核心思想非常直观:任何信号都可以分解成若干个本征模态函数加上一个残余项。IMF 需要满足两个条件:一是极值点数目和过零点数目相等或最多相差一个;二是上下包络的均值在局部范围内趋近于零。

算法流程也不复杂,就是一遍遍筛:

  1. 找出信号的所有局部极大值和极小值;
  2. 用三次样条插值构造上包络和下包络,求均值;
  3. 原信号减掉包络均值,得到候选分量;
  4. 检查候选分量是否满足 IMF 条件,不满足就重复上面三步;
  5. 满足条件就作为一个 IMF 提取出来,剩余信号继续分解。

这个“筛”的过程让人又爱又恨。爱的是它确实能把复杂信号拆出有物理意义的成分,恨的是它有个著名的毛病——模态混叠。比如一个正弦信号上叠加了一个小尖峰脉冲,筛出来的第一个 IMF 可能会把正弦和脉冲搅在一起,你根本分不清哪个是哪个。

我刚接触 EMD 时踩过一个很典型的坑:对一个含周期性冲击的振动信号做 EMD,结果第一个 IMF 里既有高频冲击又有中频谐波,时频谱糊成一团。当时花了一整天调参数,最后才明白问题出在方法本身,不是我的数据有问题。

2.2 EEMD:用白噪声“逼”出真实模态,但是有代价

为了治模态混叠,EEMD(Ensemble EMD)想了一个非常粗暴但有效的办法:往原信号里加白噪声,再做 EMD,反复做很多次,然后把每次的 IMF 取平均。白噪声在不同尺度上都有能量,能把信号中不同频率成分“托”起来,让它们在筛的过程中被区分开。因为噪声是随机的,多次平均后噪声贡献互相抵消,剩下的就是真实模态。

听起来很完美,但用过就知道问题不少:

  • 计算开销大:加噪声做 EMD 需要重复几十次甚至上百次,每次都要做完整的筛选循环,信号一长就等得人发慌;
  • 重构不精确:平均后的 IMF 加起来不等于原始信号,因为每次分解的残余项都不一样,所以在金融、医学这类对数值精度敏感的领域影响很大;
  • IMF 数量可能不一致:不同次分解出来的 IMF 个数可能不同,平均操作没法对齐,最后只能按最小数量截断,损失信息;
  • 噪声幅值难定:噪声幅值设置太大会破坏信号,太小又起不到抑制混叠的作用,只能凭经验试。

2.3 CEEMDAN:完备性、自适应、每个环节都可复现

CEEMDAN 改进了 EEMD 的“先分解后平均”思路,变成“边分解边对噪声做自适应处理”。具体差别可以这样理解:EEMD 是每次完整分解完再平均;而 CEEMDAN 是每提取出一阶 IMF,就对噪声做一次特殊的 EMD 分解,用这次分解结果修正残余信号,然后再进入下一轮。每一轮加的噪声都是“自适应”的,会根据当前残余信号的能量调整幅值。

因此它带来了三个实打实的好处:

  1. 完备性:所有 IMF 加上残余项能精确重构原始信号,这在 EEMD 里根本做不到;
  2. IMF 数量稳定:同一参数下多次运行,分解结果长度一致,不像 EEMD 那样跑三次三个样;
  3. 混叠抑制效果好:每一层噪声都经过当前残余能量调制,信号中的间歇性成分能被更干净地分开。

从实际使用角度,数据长度几百到几万个点都能处理。不过它也有自己的门槛——参数比较多,而且官方原始代码是 MATLAB 的,很多人想用 Python 就得绕路。这个我后面单独说。

3. CEEMDAN 核心流程拆解:每一层噪声是怎么“自适应”进去的

3.1 第一层:先拿到第一个 IMF

CEEMDAN 的算法流程,我尽量用说人话的方式描述:

第一步,对原信号加一组白噪声(幅值由用户设定,比如噪声标准差与信号标准差之比取 0.2),然后对每个加噪信号分别做 EMD,取第一阶 IMF,再对这组 IMF 求平均,得到 CEEMDAN 的第一个 IMF,记为IMF1。

这只是预热。真正的关键在第二步。

3.2 核心机制:对噪声的“残余”再做一次 EMD

用原始信号减去IMF1,得到残余r1。接下来要对r1加噪声继续分解,但加的不是原始白噪声,而是对这个白噪声做一次 EMD,取它的第k阶 IMF——这个k恰好对应当前要提取的 IMF 序号。也就是说,第一轮加的是白噪声的 EMD 第1阶分量,第二轮加的是白噪声的 EMD 第2阶分量,以此类推。噪声的“形态”被提前拆解了,再叠加到残余信号上,就能保证分解过程不引入与新模态混叠的额外频率成分。

这正是 CEEMDAN 和 EEMD 的根本区别。EEMD 每次都往原信号加完整的白噪声,而 CEEMDAN 只往残余信号加白噪声的某一阶 IMF,这个 IMF 的尺度与当前分解层匹配,所以叫“自适应噪声”。

3.3 循环终止条件:筛选的尽头在哪里

分解不会无限进行下去。判断条件通常有三个,只要满足任意一个就可以停:

  • 残余信号的极值点数目不超过 2 个;
  • 残余信号的能量低于初始信号的某个比例(比如 1%);
  • 分解达到预设的最大 IMF 层数。

在 Python 实现里,为了速度通常用“极值点数≤2”作为默认终止条件。数据越光滑、噪声越小,分解出来的 IMF 层数越少;反之,信号高频细节多、噪声幅值设得高,层数会相应增加。我实测下来,一个 1024 点的振动信号,默认参数下大概能分出 8~11 层。层数不是越多越好——后面的 IMF 大多是低频趋势项,物理意义逐渐减弱。

3.4 一个完整的分解示例:以含工频干扰的心电信号为例

举个我实际跑过的例子。模拟一组心电信号,叠加 50Hz 工频干扰和随机基线漂移,总长度 2000 点,采样率 500Hz。用 CEEMDAN 分解后得到:

阶数对应物理含义频率范围(约)
IMF1高频噪声和细节毛刺100Hz 以上
IMF2-3工频干扰及谐波50~100Hz
IMF4-6QRS 波群主体5~40Hz
IMF7-9P 波、T 波等低频包络0.5~5Hz
残余基线漂移趋势<0.5Hz

这个结果说明:如果做心电去噪,直接删掉 IMF1-3 再重构,就能在保留 QRS 波群的同时消除工频干扰。如果做基线校正,把残余项去掉就行。这就是 CEEMDAN 被称为“预处理神器”的原因——分解本身不是目的,按需筛选 IMF 再重构才是目的。

4. Python 实现:不会 MATLAB 也能跑通,完整代码直接抄

4.1 公开库、第三方实现,怎么选

CEEMDAN 的原始 MATLAB 代码来自法国团队的公开版本,用的人很多。Python 这边没有一个官方正统的实现,但有两个主要选择:

  1. PyEMD库:封装了 EMD、EEMD、CEEMDAN,API 清晰,和 sklearn 风格接近,安装方便,我这里最推荐;
  2. emd库:更新一些,支持更多诊断和可视化,但接口变化快,网上的老教程容易过期。

注意,PyEMD里的 CEEMDAN 实现相比论文原版做了一些工程优化,比如并行集合计算、更智能的噪声幅值设置,这些不会影响原理理解,但能让计算快不少。我建议初学直接用它,理解原理后再去看原始代码不迟。

4.2 安装与最小示例

pip install EMD-signal

注意包名是EMD-signal(带横杠),导入的时候用PyEMD(无横杠),这是一个非常容易踩的坑。装好后直接用:

import numpy as np from PyEMD import CEEMDAN # 生成测试信号:两个正弦 + 随机噪声 t = np.linspace(0, 1, 1000) signal = np.sin(2 * np.pi * 10 * t) + 0.5 * np.sin(2 * np.pi * 50 * t) + 0.1 * np.random.randn(1000) # 初始化 CEEMDAN ceemdan = CEEMDAN(trials=100, epsilon=0.005) imfs = ceemdan(signal) # 结果:每行是一个 IMF,最后一行是残余 print(f"分解出 {imfs.shape[0]} 个分量") for i, imf in enumerate(imfs): print(f"IMF{i+1}: 能量占比 {np.sum(imf**2) / np.sum(signal**2):.4f}")

只要几行就能跑通。trials是集合次数,epsilon是噪声幅值(对应原文里的Nstd)。这段代码输出每个 IMF 的能量占比,可以用来快速判断哪些分量重要。

4.3 关键参数的含义与推荐值,我踩过坑才总结出来的

CEEMDAN()的常用参数,我列一张表:

参数含义典型值我的实测建议
trials集合平均的次数100数据点少时用 50 就够,点长或噪声大时用 200~500,太大会严重拖慢速度
epsilon噪声幅值(与信号标准差之比)0.0050.001~0.05 之间,太大破坏低频 IMF,太小压不住模态混叠
extrema_detection极值检测方式"simple"处理含噪数据时可换"parabol",结果会更平滑但也更慢
SC_tol筛选中停止条件容差1e-6一般不用动,默认即可

epsilon是最需要反复试的参数。我的经验是:先设 0.005,看分解结果是否出现明显的模态混叠(比如某个 IMF 的频谱出现两个不相关的峰),有就调大;如果 IMF 波形明显被噪声污染,就调小。这个过程虽然听起来不太“科学”,但在实际工程里非常有效。

4.4 分解效果可视化:图示比数字直观一百倍

import matplotlib.pyplot as plt plt.figure(figsize=(12, 8)) for i, imf in enumerate(imfs): plt.subplot(imfs.shape[0], 1, i+1) plt.plot(t, imf, linewidth=0.8) plt.ylabel(f"IMF{i+1}") plt.xlabel("时间/s") plt.tight_layout() plt.show()

画出来你会看到:高频的 IMF1 波形杂乱,中频的 IMF3/4 能明显看出周期性,低频的残余项是一条缓变趋势线。看到这个分层效果,你才能理解为什么 CEEMDAN 被称为“自适应的梳子”——它把不同频率的内容梳成了整齐的几缕。

5. 实战案例:用 CEEMDAN 做轴承故障特征提取,完整可复现

5.1 问题定义

滚动轴承故障是最经典的诊断场景。一个内圈故障信号里,除了正常的旋转频率,还会叠加故障特征频率(比如内圈故障特征频率 BPFI)及其倍频,同时混有大量噪声。难点在于:特征频率可能很微弱,直接看频谱根本找不到。

我模拟一个简化的轴承故障信号:基频 30Hz,BPFI 为 120Hz,阻尼衰减冲击成分重复出现,再加上 0.5 倍幅值的随机噪声。采样率 4000Hz,时长 2 秒。

5.2 分解 + 筛选 + 包络谱分析的完整代码

import numpy as np from PyEMD import CEEMDAN from scipy.signal import hilbert, find_peaks # 模拟轴承故障信号 fs = 4000 t = np.arange(0, 2, 1/fs) def_impact = np.zeros_like(t) for i in range(0, len(t), int(fs/120)): # 每 1/120 秒一个冲击 tt = t[i:i+200] - t[i] if i+200 < len(t) else t[i:] - t[i] def_impact[i:i+len(tt)] = np.exp(-tt/0.002) * np.sin(2*np.pi*1200*tt) signal = 0.3*np.sin(2*np.pi*30*t) + def_impact + 0.5*np.random.randn(len(t)) # CEEMDAN 分解 ceemdan = CEEMDAN(trials=100, epsilon=0.005) imfs = ceemdan(signal) # 对所有 IMF 做包络谱,找故障特征频率 for i, imf in enumerate(imfs): analytic = hilbert(imf) # 希尔伯特变换求包络 envelope = np.abs(analytic) spectrum = np.abs(np.fft.fft(envelope))[:len(t)//2] freqs = np.fft.fftfreq(len(t), 1/fs)[:len(t)//2] peaks, _ = find_peaks(spectrum, height=np.max(spectrum)*0.1) # 检查 120Hz 附近是否出现峰值 if np.any(np.abs(freqs[peaks] - 120) < 3): print(f"IMF{i+1}: 检测到 120Hz 特征频率!")

关键点在于,CEEMDAN 先把冲击成分和旋转成分分离到不同 IMF,然后对每个 IMF 做包络谱分析,只搜索目标频率附近的能量峰。如果直接在原始信号上做包络谱,噪声会把特征频率淹得干干净净。

5.3 实验结果:不是所有 IMF 都要看

跑完我得到这样的结果:IMF1 基本是纯噪声,包络谱杂乱;IMF2 ~ IMF4 能量集中在 120Hz 附近,能明显看到故障特征频率;IMF5 及以上以 30Hz 旋转频率为主。

所以筛选策略非常明确:选能量占比高、且集中在目标频带内的 IMF,其他分量直接舍弃。这个“选不选”的判断标准,就是看包络谱里目标频率处的峰高是否达到噪声底的三倍以上。如果达不到,就换下一层 IMF,直到找到为止。

6. 我用 CEEMDAN 时踩过的 5 个坑,每一个都是血泪教训

6.1 IMF 数量不稳定的锅,不在算法,在参数

用 CEEMDAN 分解同一段数据,如果你发现每次跑出来的 IMF 数量不一样,首要怀疑对象是trials太小。集合次数少,平均效果不稳定,偶尔一次极端分解结果就会影响最终数量。trials至少保证 100,我的经验是信号含强随机噪声时用 300 以上,IMF 数量才稳定。

另一个容易被忽略的因素是数据精度。如果信号是自己生成后保存成 float16,极值检测的精度会被破坏,分解结果就会波动。先转成 float64 再喂给算法。

6.2 边缘效应:首尾数据凭空多出大振幅

CEEMDAN 用的是三次样条包络,在信号首尾处容易出现极端包络值,导致 IMF 首尾出现假的巨大振荡。我处理过一段 10 秒的振动信号,分出来的 IMF1 在首尾 0.2 秒内振幅比中间大了 20 倍,一看就是边缘效应。

解决方案有两个:

  1. 数据延拓:分解前把数据左右各镜像延长 5%,分解后裁剪掉延拓部分。在CEEMDAN里可以通过extend_style参数实现,能明显改善边缘;
  2. 边缘截断:分解后直接丢弃每个 IMF 的首尾 2% 采样点。对大多数后续分析影响很小,简单粗暴。

6.3 噪声幅值大则模态变形,小则混叠依旧

epsilon不是随便填的。我做过一组对比实验:对同一个信号分别用 0.001、0.01、0.05 三个噪声幅值分解,结果差异巨大。

epsilonIMF1 形态重构误差分解层数
0.001混叠严重,频谱出现双峰0.00017
0.01各 IMF 清晰分离0.0019
0.05IMF1 含明显噪声颗粒0.00811

结论是,没有“最优固定值”,需要根据信号特性和噪声水平去试。我的建议是,先用默认值(0.01 或 0.005)跑一遍,观察各 IMF 的频谱,再微调。

6.4 分解出的 IMF 不是越多越好

很多初学者以为 IMF 层数越多分解越“彻底”,这是误区。最后几个 IMF 往往是残余项的低频趋势,比如一台运行正常但轻微发热的设备,振动信号最终会分出两三组低频分量,它们可能只是温度漂移造成的缓变基线,物理意义不太大。

我自己的经验判断标准:观察残余项波形,如果它是一条平滑曲线且变化缓慢,就可以停了。如果残余项仍然有明显的振荡成分,说明分解不充分,可能需要调大噪声幅值或增加 trials。

6.5 Python 库版本不同,结果可能对不上

PyEMD的底层实现有过多次调整,不同版本对相同参数可能给出略有差异的结果。如果你要复现别人的结果,最好把版本也锁定。我在一个项目里就吃过这个亏:按教程写代码,结果 IMF 3 的波形怎么都对不上,最后发现是库版本不同导致极值检测逻辑有差异。

建议统一做法:pip freeze > requirements.txt记录版本,或者在文章里标注具体版本号。涉及到多人协作的时候,这能省掉大量“我明明和你一样”的抓狂时间。

7. 和 EMD、EEMD、VMD 放在一起看,你才知道什么时候该用它

我整理了一张常用分解方法对比表,方便快速决策:

方法优点缺点适用场景
EMD简单快速,无需参数模态混叠严重信号成分简单、无强噪声
EEMD抑制混叠,原理直观重构误差大、速度慢中短期数据分析,不要求重构精度
CEEMDAN完备重构、混叠抑制好、分解稳定参数敏感、速度较慢需要精确重构、IMF 物理意义明确的场景
VMD非递归、频带划分明确需要提前指定模态数 K已知频率成分个数,希望规则化分解

我的体会是:CEEMDAN 不是万能的,但它几乎在所有需要“分解—筛选—重构”的信号处理任务里都够用。金融时序的波动成分分析、结构健康监测的损伤识别、电力系统的谐波检测,我都试过,整体表现非常稳定。

如果追求速度或者信号很短很短,VMD 反而更合适——只要先谱分析确定模态数 K 就行。但如果模态数不确定、又要求分解完备,那就是 CEEMDAN 的主场。

8. 最后一个建议:从这一步开始你的 CEEMDAN 之路

这一段写给你,尤其是刚看到这里、还没动手敲代码的朋友。

不要先纠结算法里每一行数学公式是怎么推导的,先用模拟信号把流程跑通,再看真实数据效果,最后回头补理论,这是最高效的学习路径。模拟信号的好处是“标准答案”在手——你知道有几个分量、频率是多少,分解完直接对比,立刻知道算法是在干活还是添乱。

我的建议路径是:跑一遍 4.2 的最简示例;然后换成 5.2 的故障诊断案例,把每个 IMF 画出来看波形,观察边缘效应和模态混叠是什么形态;之后用自己的真实数据,从默认参数开始,微调口径;最后,你觉得哪一步“不对劲”,再翻开论文对应章节查原因。

我在实际项目里体会最深的一点是:CEEMDAN 的效果好不好,七分在参数调试,三分在算法本身。一个调好参数的 CEEMDAN,能把信号里微弱的故障特征从强噪声里捞出来;一个没调好的,可能把一个干净信号拆成一堆无意义的碎料。数据预处理这件事,从来都是慢工出细活。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询