
简介CEEMDAN自适应噪声完备集合经验模态分解是EMD与EEMD的进一步改进算法面向从事非线性、非平稳信号处理的研究生、工程师与科研人员用于解决传统EMD易出现的模态混合问题提升分解精度与稳定性。资源包共5个文件以4个m脚本文件和1个mat数据文件为主脚本涵盖emd、eemd、ceemdan及示例程序mat文件提供心电信号等实测数据压缩包约32KB轻量便于直接运行与二次开发。目前已有5933人学习下载热度较高。通过阅读与运行这些代码读者可理解自适应噪声的引入方式、迭代分解与集合平均流程掌握从EMD到EEMD再到CEEMDAN的演进思路并可将方法迁移至地震波解析、心电脑电分析、金融时序与机械故障诊断等场景快速搭建自己的模态分解实验。1. CEEMDAN 到底改进了什么从模态混叠到重构误差如果你处理过轴承振动、心电、地震波或者风速这类非平稳信号大概率被 EMD 的模态混叠折磨过。原始 EMD 靠极值点插值构造上下包络一旦信号里出现间歇性高频成分极值点分布就会跳变一个 IMF 里混进两个时间尺度完全不同的分量后面做包络谱、做时频分析全部失真。EEMD 的思路是往信号里加白噪声靠多次加噪的统计平均把混叠压下去代价是计算量翻十几倍而且加噪次数和幅值两个参数全靠经验拍。CEEMDANComplete Ensemble EMD with Adaptive Noise在 EEMD 基础上做了两处关键改动一是噪声分阶段加入每一阶 IMF 提取时只对当前残差加噪二是引入自适应噪声系数让噪声幅值随残差能量自动缩放。结果是重构误差从 EEMD 的百分之几降到千分之几量级同时 IMF 的正交性明显改善。这篇笔记面向已经用过 EMD/EEMD、想换到 CEEMDAN 但不确定参数怎么设、代码怎么落地的工程师把选型理由、最小可复现代码、参数边界和踩坑记录一次讲清。2. EMD、EEMD、CEEMDAN 的递进关系与选型判断2.1 从筛分过程看 EMD 为什么会混叠EMD 的核心是筛分找出信号所有局部极大值和极小值用三次样条插值分别拟合上包络和下包络取均值得到 m(t)用 x(t) - m(t) 反复迭代直到满足 IMF 判据。问题出在插值这一步——当信号里存在间断高频扰动比如轴承早期故障的冲击成分极值点会突然密集样条在局部剧烈震荡包络均值把高频能量泄漏到低频 IMF 里。这就是模态混叠的物理来源不是实现 bug是算法结构决定的。判断是否发生混叠有个实用方法对每个 IMF 做 Hilbert 变换看瞬时频率正常 IMF 的瞬时频率应该围绕一个中心值窄带波动如果某个 IMF 的瞬时频率出现跨倍频程的跳变基本可以确认混叠。另一个指标是相邻 IMF 的相关系数正常情况相邻分量相关性很低混叠时会出现异常高相关。2.2 EEMD 用噪声平均换稳定性代价在哪EEMD 的做法是原始信号叠加一组高斯白噪声做 EMD 分解重复 N 次对每次得到的同阶 IMF 取平均。白噪声的频谱均匀分布给信号提供了一个均匀的极值点参考网格缓解了间歇成分导致的极值跳变。噪声在平均过程中相互抵消理论上 N 越大残留越小。代价有三个。第一是计算量N 通常取 100 到 500等于把 EMD 跑几百遍。第二是参数敏感噪声幅值一般取信号标准差的 0.2 倍但这个经验值对冲击型信号偏小、对平稳信号偏大。第三是重构不完整EEMD 分解后把所有 IMF 加回去不等于原信号残留噪声和筛分误差会累积重构误差通常在 1% 到 5% 之间。做精密测量时这个误差不可接受。2.3 CEEMDAN 的自适应噪声机制CEEMDAN 的改进逻辑是不再一次性加噪而是逐阶处理。提取第一阶 IMF 时对信号加噪做 EMD取第一阶 IMF 平均然后计算残差 r1 x - IMF1对 r1 加噪继续提取第二阶 IMF以此类推。每一阶的噪声幅值根据当前残差的能量自适应调整残差能量大时噪声小残差能量小时噪声适当放大以维持极值点分布。这个机制带来两个直接好处。一是重构误差显著降低因为每阶只处理当前残差噪声不会跨阶累积。二是 IMF 的物理意义更清晰第一阶对应最高频成分后续阶依次对应更低频不会出现 EEMD 里同一阶 IMF 在不同加噪次数下对应不同频带的情况。选型判断可以按这个标准如果只是做粗略趋势提取EMD 够用如果信号非平稳性强但精度要求一般EEMD 可以接受如果要做定量分析、重构误差要求低于 1%、或者后续要接深度学习做分类直接上 CEEMDAN。计算量方面CEEMDAN 比 EEMD 略高因为每阶都要做多次 EMD但比 EEMD 跑 500 次加噪还是省不少。2.4 用 Python 跑通 CEEMDAN 的最小代码目前 Python 生态里 EMD 系列算法主要靠 PyEMD 库CEEMDAN 在 PyEMD 里有现成实现。安装命令pip install EMD-signal最小可复现脚本用合成信号验证分解效果import numpy as np from PyEMD import EMD, EEMD, CEEMDAN import matplotlib.pyplot as plt # 构造合成信号两个不同频率分量 间歇冲击 t np.linspace(0, 1, 1000) # 低频分量 sig_low np.sin(2 * np.pi * 5 * t) # 高频分量 sig_high 0.5 * np.sin(2 * np.pi * 50 * t) # 间歇冲击在 0.3-0.4 秒区间加入 sig_burst np.zeros_like(t) burst_idx (t 0.3) (t 0.4) sig_burst[burst_idx] 0.8 * np.sin(2 * np.pi * 120 * t[burst_idx]) signal sig_low sig_high sig_burst # CEEMDAN 分解 ceemdan CEEMDAN() # 关键参数trials 控制加噪次数epsilon 控制噪声幅值 ceemdan.trials 100 ceemdan.epsilon 0.005 IMFs ceemdan(signal) # 重构验证 reconstructed np.sum(IMFs, axis0) recon_error np.linalg.norm(signal - reconstructed) / np.linalg.norm(signal) print(f重构相对误差: {recon_error:.6f}) print(fIMF 数量: {IMFs.shape[0]}) # 绘制前四阶 IMF fig, axes plt.subplots(4, 1, figsize(10, 8)) for i in range(min(4, IMFs.shape[0])): axes[i].plot(t, IMFs[i]) axes[i].set_ylabel(fIMF {i1}) plt.tight_layout() plt.show()这段代码的逻辑说明合成信号故意包含一个 0.3 到 0.4 秒的间歇高频冲击这是最容易触发模态混叠的场景。ceemdan.trials 100表示每阶 IMF 提取时做 100 次加噪 EMD这个值越大结果越稳定但计算越慢。ceemdan.epsilon 0.005是噪声幅值系数相对于信号标准差的倍数设太小压不住混叠设太大残留噪声明显。重构误差用相对 L2 范数衡量正常应该在 0.001 到 0.01 之间。如果跑出来误差超过 0.05检查 trials 是否太小或者信号是否包含 NaN。参数调整建议对于采样率 1000 Hz 以上的振动信号trials 取 100 到 200 足够对于低频生理信号trials 可以降到 50 因为信号本身极值点少。epsilon 的取值和信号的信噪比有关信噪比高时取 0.002 到 0.005信噪比低时取 0.01 到 0.02。PyEMD 的 CEEMDAN 默认参数是 trials100、epsilon0.005大多数场景可以直接用。3. 参数怎么设trials、epsilon 与筛分停止准则3.1 trials 的收敛性判断方法trials 不是越大越好存在一个收益递减点。判断方法固定 epsilon让 trials 从 20 开始按 20 递增到 200每次计算重构误差和 IMF 正交性指标画收敛曲线。正交性指标用这个公式def orthogonality_index(IMFs): 计算 IMF 正交性指标越接近 0 越好 n IMFs.shape[0] total_energy np.sum(IMFs ** 2) cross_energy 0 for i in range(n): for j in range(i 1, n): cross_energy np.abs(np.sum(IMFs[i] * IMFs[j])) return cross_energy / total_energy # 测试不同 trials 下的表现 for trials in [20, 40, 60, 80, 100, 150, 200]: ceemdan CEEMDAN() ceemdan.trials trials ceemdan.epsilon 0.005 IMFs ceemdan(signal) recon np.sum(IMFs, axis0) err np.linalg.norm(signal - recon) / np.linalg.norm(signal) oi orthogonality_index(IMFs) print(ftrials{trials:3d} 重构误差{err:.6f} 正交性{oi:.6f})典型结果trials 从 20 增到 80 时重构误差快速下降80 到 150 之间变化很小超过 150 基本持平。所以工程上 trials 取 100 是性价比最高的点。如果信号特别短少于 500 个采样点trials 可以降到 50因为极值点本来就少加噪次数多了反而引入冗余计算。3.2 epsilon 与信号幅值的关系epsilon 是噪声幅值相对于信号标准差的系数。PyEMD 内部实现是噪声标准差 epsilon × 信号标准差。所以 epsilon 的物理含义是噪声能量占比的平方根。设 epsilon 0.005意味着噪声标准差是信号标准差的 0.5%噪声能量占比约 0.0025%。这个值不能拍脑袋。如果信号本身信噪比很低比如实测振动信号里故障特征被背景噪声淹没epsilon 要适当放大到 0.01 到 0.02让白噪声提供的极值点网格能盖过背景噪声的干扰。如果信号很干净比如实验室台架信号epsilon 取 0.002 到 0.005 就够取大了反而在 IMF 里留下可见的噪声残留。一个实用的自适应策略先算信号的标准差 σ再算信号的高频段能量占比。如果高频能量占比超过 30%epsilon 取 0.01否则取 0.005。这个规则不是理论最优但在我处理过的轴承和齿轮箱信号上比固定值稳定。3.3 筛分停止准则对 IMF 数量的影响EMD 系列算法在筛分时需要一个停止条件常用的是标准差准则当连续两次筛分结果的相对标准差小于阈值时停止。PyEMD 里 EMD 的默认阈值是 0.2CEEMDAN 继承了这个设置。这个值直接影响 IMF 数量和分解粒度。阈值设小比如 0.05筛分迭代次数增加每个 IMF 更“纯”但计算量上升而且可能把同一物理过程拆成两个 IMF。阈值设大比如 0.3筛分不充分IMF 里残留趋势项。工程上 0.2 到 0.3 是常用区间对于要接包络谱分析的场景建议用 0.2对于只做趋势提取的场景 0.3 可以接受。修改方法from PyEMD import CEEMDAN ceemdan CEEMDAN() # 设置筛分停止阈值 ceemdan.EMD.FIXE_H 0 # 关闭固定迭代次数模式 ceemdan.EMD.std_thr 0.2 # 标准差阈值注意 PyEMD 的 CEEMDAN 内部嵌套了一个 EMD 实例参数要通过ceemdan.EMD访问。这个设计容易踩坑很多人直接改ceemdan.std_thr发现不生效原因就在这里。3.4 分解层数怎么定从残差单调性判断CEEMDAN 不需要预先指定分解层数它会自动分解到残差满足停止条件通常是残差极值点少于 2 个或者残差变成单调函数。但实际使用中经常需要限制最大层数因为最后几阶 IMF 可能已经是无意义的趋势项。判断方法看残差的极值点数量。当残差极值点少于 3 个时后续 IMF 基本是趋势项可以停止。代码里可以这样控制def ceemdan_with_limit(signal, max_imf8): 限制最大 IMF 数量避免过度分解 ceemdan CEEMDAN() ceemdan.trials 100 ceemdan.epsilon 0.005 IMFs ceemdan(signal) # 如果分解出的 IMF 超过 max_imf只保留前 max_imf 阶 # 剩余部分归入残差 if IMFs.shape[0] max_imf: kept IMFs[:max_imf] residual signal - np.sum(kept, axis0) return kept, residual return IMFs, np.zeros_like(signal) IMFs, residual ceemdan_with_limit(signal, max_imf6) print(f保留 IMF 数: {IMFs.shape[0]}, 残差能量占比: {np.sum(residual**2)/np.sum(signal**2):.4f})残差能量占比是个有用指标。如果残差能量占比超过 10%说明限制层数太狠丢掉了有意义的低频成分如果低于 1%说明后面几阶本来就是噪声或趋势限制是合理的。4. 避坑与排查CEEMDAN 落地时最容易翻车的五个地方4.1 现象分解结果每次运行都不一样原因CEEMDAN 内部有随机加噪过程如果没有固定随机种子每次调用产生的噪声序列不同导致 IMF 有微小差异。这在调试阶段很让人抓狂明明参数没变结果却变了。解决在调用前固定 numpy 随机种子。import numpy as np np.random.seed(42) IMFs ceemdan(signal)注意 PyEMD 内部可能使用自己的随机数生成器如果固定 numpy 种子后结果仍然波动检查 PyEMD 版本是否支持ceemdan.noise_seed参数。部分版本需要显式设置ceemdan.noise_seed 42。4.2 现象IMF 数量异常多超过 15 阶原因信号里包含大量高频噪声或者采样率过高导致极值点极其密集筛分过程不断提取出高频 IMF。另一个可能是信号存在直流偏置极值点分布被扭曲。解决先做去均值处理再根据采样率做适当降采样。对于振动信号如果采样率是 20 kHz 但关注频带在 2 kHz 以下先降采样到 5 kHz 再分解。去均值的代码signal signal - np.mean(signal) # 可选带通滤波限制频带 from scipy.signal import butter, filtfilt b, a butter(4, [10, 2000], btypeband, fs5000) signal_filtered filtfilt(b, a, signal)4.3 现象重构误差大于 5%原因trials 太小、epsilon 太大、或者信号包含 NaN/Inf。还有一种容易被忽略的情况信号长度不是 2 的幂次PyEMD 内部做 FFT 时补零导致边界效应。解决按顺序排查。先检查np.isnan(signal).any()和np.isinf(signal).any()。然后增大 trials 到 200 看误差是否下降。如果还不行把信号截断到 2 的幂次长度再试。边界效应明显的信号可以在两端加镜像延拓def mirror_extend(signal, extend_len): 镜像延拓减少边界效应 left signal[:extend_len][::-1] right signal[-extend_len:][::-1] return np.concatenate([left, signal, right]) signal_ext mirror_extend(signal, 100) IMFs_ext ceemdan(signal_ext) # 去掉延拓部分 IMFs IMFs_ext[:, 100:-100]4.4 现象某个 IMF 的瞬时频率出现负值原因Hilbert 变换对非窄带信号会产生负瞬时频率这通常意味着该 IMF 仍然包含多个时间尺度筛分不充分。也可能是该 IMF 幅值接近零数值噪声导致相位跳变。解决先检查该 IMF 的幅值范围如果幅值比相邻 IMF 小两个数量级说明它基本是数值噪声可以直接丢弃。如果幅值正常但瞬时频率有负值降低筛分停止阈值重新分解ceemdan.EMD.std_thr 0.1 # 更严格的筛分4.5 现象CEEMDAN 比 EEMD 慢很多原因CEEMDAN 每提取一阶 IMF 都要做 trials 次 EMD总计算量是 trials × IMF 阶数。如果信号长、IMF 多耗时会显著增加。解决三个方向。一是降低 trials 到 50对大多数信号精度损失可接受。二是对信号做降采样只要关注频带满足奈奎斯特条件。三是用并行加速PyEMD 本身不支持多进程但可以把不同 trials 拆到多进程里手动实现。最简单的加速是限制最大 IMF 层数因为后面几阶的计算量占比很大但信息量很低。# 限制层数的加速版本 ceemdan CEEMDAN() ceemdan.trials 50 ceemdan.epsilon 0.005 # 通过 max_imf 参数限制部分版本支持 IMFs ceemdan(signal, max_imf6)5. 用正交性指标验证分解质量一个可复用的评估脚本分解做完不是终点得有个量化标准判断这次分解到底靠不靠谱。我一般用三个指标重构误差、正交性指标、IMF 能量分布。重构误差前面讲过了这里重点说正交性和能量分布怎么用来做验证。正交性指标衡量的是不同 IMF 之间是否“干净”分离。理论上 IMF 之间应该正交实际因为筛分误差和噪声残留正交性指标在 0.01 到 0.1 之间都算正常超过 0.2 说明混叠严重。能量分布看的是各阶 IMF 的能量占比正常分解应该呈现从高频到低频的递减趋势如果中间某阶能量异常突出可能是该频带存在强干扰或者分解层数不对。下面是一个完整的评估脚本可以直接套用到你的信号上import numpy as np from PyEMD import CEEMDAN def evaluate_ceemdan(signal, trials100, epsilon0.005): CEEMDAN 分解质量评估 np.random.seed(42) ceemdan CEEMDAN() ceemdan.trials trials ceemdan.epsilon epsilon IMFs ceemdan(signal) n_imf IMFs.shape[0] # 指标1重构误差 recon np.sum(IMFs, axis0) recon_err np.linalg.norm(signal - recon) / np.linalg.norm(signal) # 指标2正交性指标 total_energy np.sum(IMFs ** 2) cross_energy 0 for i in range(n_imf): for j in range(i 1, n_imf): cross_energy np.abs(np.sum(IMFs[i] * IMFs[j])) ortho_idx cross_energy / total_energy # 指标3各阶能量占比 energy_ratio np.sum(IMFs ** 2, axis1) / total_energy # 指标4相邻 IMF 相关系数 adj_corr [] for i in range(n_imf - 1): c np.corrcoef(IMFs[i], IMFs[i1])[0, 1] adj_corr.append(c) print(fIMF 阶数: {n_imf}) print(f重构误差: {recon_err:.6f}) print(f正交性指标: {ortho_idx:.6f}) print(f能量占比: {np.round(energy_ratio, 4)}) print(f相邻相关系数: {np.round(adj_corr, 4)}) # 判定 if recon_err 0.01: print([警告] 重构误差偏大建议增大 trials 或减小 epsilon) if ortho_idx 0.2: print([警告] 正交性差可能存在模态混叠) if max(adj_corr) 0.5: print([警告] 相邻 IMF 高度相关分解不充分) return IMFs, recon_err, ortho_idx # 使用示例 IMFs, err, oi evaluate_ceemdan(signal, trials100, epsilon0.005)这个脚本的判定逻辑来自我处理几十组振动信号后的经验阈值。重构误差 0.01 是分界线低于它说明分解基本无损正交性 0.2 是警戒线超过它说明 IMF 之间有明显的能量泄漏相邻相关系数 0.5 是异常线正常分解相邻 IMF 的相关系数应该在 0.1 以下因为相邻阶对应不同时间尺度。有一个容易忽略的点正交性指标对 IMF 数量敏感。IMF 越多交叉项越多指标天然偏大。所以比较不同分解结果时要确保 IMF 数量相近或者用归一化后的指标。我一般会在评估前先限制最大 IMF 层数让不同参数下的分解结果可比。最后说一个实际使用中的习惯每次换新信号类型时先跑一遍评估脚本把 trials 从 50 到 200 扫一遍看三个指标的变化曲线找到拐点再定参数。不要直接套用上一个项目的参数信号特性变了最优参数也会变。这个习惯帮我省了很多返工时间希望帮到你。本文还有配套的精品资源点击获取