ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CEEMDAN信号分解实战:从EMD到自适应噪声,Python调参与故障诊断

CEEMDAN信号分解实战:从EMD到自适应噪声,Python调参与故障诊断 简介CEEMDAN算法资源包面向信号处理、机械故障诊断、生物医学与金融时序分析等研究人员解决EMD/EEMD在非线性非平稳信号分解中的模态混叠问题。作为EMD和EEMD的进一步改进CEEMDAN通过引入与信号相位相关的自适应噪声在多次迭代中不断优化IMF提取过程并借助平均策略提升分解稳定性从而更精确地分离不同频率成分对心电、地震波等复杂信号的振荡频率与振幅变化分析尤为适用。压缩包共5个文件以MATLAB脚本为主辅以一个.mat测试数据文件整体仅32KB包含CEEMDAN、EEMD、EMD三套实现代码、国际会议ICASSP示例演示以及心电信号测试数据代码结构清晰直接运行即可快速掌握调用流程与参数设置便于对照改进效果。已有5930人学习下载适合具备一定MATLAB基础、希望将CEEMDAN应用于实际复杂信号分析的研究生与工程师。1. CEEMDANEEMD 和 EMD 的改进终点信号分解选它最省心做振动信号分析、轴承故障诊断的人大概率经历过被 EMD 模态混叠支配的阶段一段实测信号拆出来的 IMF 里粘着两三个频率成分怎么筛都分不干净换成 EEMD 加白噪声模态分开了但每次跑出来的 IMF 数量对不上残差还带着噪声尾巴。CEEMDAN完全自适应噪声集合经验模态分解就是冲这两个痛点来的——它在每一级残差上加入由 EMD 分解得到的自适应噪声逐层逼近真实模态既保留集合平均的抗混叠能力又保证分解完备、结果稳定。这篇笔记从 EMD 讲起把 EEMD、CEEMDAN 的差异说透再落到 Python 代码、参数调优和避坑记录适合拿实测信号做分解的从业者。2. 从 EMD 到 CEEMDAN三次迭代分别解决了哪三个问题EMD 是 Huang 在 1998 年提出的经验模态分解CEEMDAN 是沿着“EMD → EEMD → CEEMDAN”这条线改出来的。要理解 CEEMDAN 为什么这么设计得先把前两代算法的“病”看清楚。这一章不堆公式只讲清楚每个版本做了什么、留下了什么问题。2.1 EMD 的筛分过程与模态混叠Hilbert-Huang 的第一道坎EMD 的核心假设是任何复杂信号都能拆成若干个本征模态函数IMF加一个残差。IMF 要满足两个条件一是过零点数和极值点数相等或最多差 1二是上下包络的均值处处为 0。筛分过程是这样的先找出信号的所有局部极大值和极小值用三次样条分别拟合出上包络和下包络取两者平均得到 m1用 x(t) - m1 得到 h1如果 h1 不满足 IMF 条件就重复这套操作直到满足为止得到第一个 IMF再对残差继续筛。这套迭代叫 sifting是整个 EMD 家族的地基。问题出在哪儿当信号里两个频率成分靠得比较近时包络拟合出来的均值会把它们“捏”在一起筛出来的一个 IMF 里同时带着两个模态这就是模态混叠。混叠的直接后果是后续做 Hilbert 变换算瞬时频率的时候会出现负频率或频率跳变。还有一个衍生问题端点附近没有极值点包络只能靠样条外推外推结果容易过冲导致 IMF 两端大幅摆动这叫端点效应。我第一次拿 EMD 跑齿轮箱振动数据第一个 IMF 的包络谱里既有啮合频率又有转频边带怎么解释都牵强后来才明白这是模态混叠不是信号本身的问题。后来做多了才发现EMD 这套东西边界情况极多参数稍微不合适出来的模态就没法从物理上解释这是它最大的使用门槛。2.2 EEMD 的白噪声平均法为什么加了噪声反而能分离模态Wu 和 Huang 在 2009 年提出 EEMD思路相当反直觉往信号里加白噪声做 EMD重复 N 次再把结果平均。白噪声的频谱均匀铺满整个频带它的加入会把不同尺度的信号成分“顶”到各自的参考尺度上让原本粘在一起的模态被强制分开而白噪声本身是随机成分多次平均后相互抵消。但 EEMD 有两个先天毛病。第一平均后残余的白噪声无法完全消除高频段尤其明显每个 IMF 里都带着一层噪声底。第二每次加噪声后都做完整的 EMDN 次分解得到的 IMF 数量可能不一样平均时只能按序号对齐一旦某次分解多出一个 IMF后面的全错位这就是所谓的“分解不完备”。计算量也不乐观每次试验都是一次完整筛分100 次试验就是 100 遍 EMD。我跑一组 10 万点的数据EEMD 一次要一分多钟来回调参数的时间比算数据的时间还长。还有一个容易被忽略的问题EEMD 的集合平均只保证统计意义上的稳定单次试验之间的差异非常大如果你只跑几十次试验结果基本不可复现。用一个不严谨但好记的说法EEMD 是“先把噪声撒进整个信号再靠平均把噪声捞出来”捞不干净是常态。2.3 CEEMDAN 的自适应噪声把“先加后减”改成“逐级逼近”CEEMDAN 是 Torres 等人在 2011 年提出的全称 Complete Ensemble Empirical Mode Decomposition with Adaptive Noise。它和 EEMD 最核心的差别有两点。第一噪声不是一次性加在原始信号上而是加在每一级残差上幅值随残差能量自适应变化。第二加进去的不是纯白噪声而是先对白噪声做一次 EMD取出它的第一个 IMF记作 E1(w^i)再按系数加到残差上。这样做的好处是噪声分量的尺度与信号当前残差处于同一水平不会出现噪声尺度与信号尺度不匹配导致的虚假模态。完整流程可以概括成四步对 x ε0·w^i 做 EMD得到 N 组第一个 IMF平均后得到 IMF1算残差 r1 x - IMF1对 r1 ε1·E1(w^i) 做 EMD平均得到 IMF2如此逐级推进直到残差不再满足分解条件。“逐级”这个设计决定了 CEEMDAN 的分解是完备的——所有 IMF 加回来就是原始信号不存在 EEMD 里“尾部对不齐”的问题。这也是工程上选它最多的原因分解结果可以直接拿去做重构验证不用怀疑中间丢没丢信息。实测下来同一段信号 CEEMDAN 多跑几次IMF 数量稳定首层模态也不会像 EEMD 那样频繁混叠这才是它能替代前两代的底气。3. 用 Python 落地 CEEMDANPyEMD 安装、调用与参数对照Python 生态里做 CEEMDAN 最常用的是 PyEMD 库底层依赖 numpy 和 scipy一个库同时实现了 EMD、EEMD、CEEMDAN 三个类。这一章直接讲怎么装、怎么调、参数怎么设。3.1 环境准备与 PyEMD 安装安装命令很简单包名注意别搞混PyPI 上叫 EMD-signal导入名是 PyEMD。pip install EMD-signal装完验证一下能不能正常导入三个核心类python -c from PyEMD import EMD, EEMD, CEEMDAN; print(ok)逻辑说明PyEMD 的 CEEMDAN 类内部会持有自己的 EMD 实例来做噪声分解和残差筛分所以导入时 EMD 一定要在。如果是在 conda 环境里装建议先把 numpy、scipy 用 conda 装好再 pip 装 EMD-signal能少踩不少编译依赖的坑。参数说明PyEMD 版本迭代比较快不同小版本之间的 API 有差异老版本里 CEEMDAN 要调用 ceemdan.ceemdan(sig) 方法新版本直接用 ceemdan(sig) 就行。代码里建议用可调用对象的形式兼容性更好也能少写一层方法名。3.2 三段代码对照同一个信号三种算法各解一次用一个合成的混合信号做对照5 Hz 正弦 20 Hz 正弦再加少量随机噪声模拟振动信号里“低频转频 高频冲击分量”的简化形态。import numpy as np from PyEMD import EMD, EEMD, CEEMDAN # 构造测试信号1 秒采样 1000 点含 5Hz 和 20Hz 两个分量 t np.linspace(0, 1, 1000) sig np.sin(2 * np.pi * 5 * t) 0.5 * np.sin(2 * np.pi * 20 * t) sig sig 0.05 * np.random.randn(t.size) # 加一点采样底噪 # EMD直接筛分不设任何集合参数 emd EMD() imfs_emd emd(sig) # EEMD200 次试验噪声幅值为信号标准差的 0.1 倍 eemd EEMD(trials200, noise_width0.1) imfs_eemd eemd(sig) # CEEMDAN200 次试验自适应噪声系数 0.075 ceemdan CEEMDAN(trials200, epsilon0.075) imfs_ceemdan ceemdan(sig) print(EMD IMF 数量:, imfs_emd.shape[0]) print(EEMD IMF 数量:, imfs_eemd.shape[0]) print(CEEMDAN IMF 数量:, imfs_ceemdan.shape[0])逻辑说明三段调用的结构完全一样——实例化、把信号传进去、拿回一个二维数组每行是一个 IMF按频率从高到低排列。差异全部体现在构造参数上。EEMD 的 noise_width 是白噪声幅值相对信号标准差的倍数CEEMDAN 的 epsilon 是每级自适应噪声的幅值系数。跑完打印 IMF 数量通常结果是 EMD 最少EEMD 和 CEEMDAN 差不多但 CEEMDAN 多跑几次数量稳定EEMD 偶发多一个或少一个。参数说明trials 是集合试验次数我一般先用 100 快速看趋势定稿实验提到 300 到 500。噪声幅值的经验区间是信号标准差的 0.01 到 0.2 倍信号信噪比越低噪声系数要越小否则真实模态会被噪声淹没这属于分解里的玄学区间后面避坑章节会细说。3.3 参数怎么选trials、epsilon 与 max_imfs 的实用取值把三个算法最常用的参数收敛到一张表里方便对照参数适用算法作用经验取值trialsEEMD / CEEMDAN集合平均次数100~500noise_widthEEMD白噪声幅值 / 信号标准差0.05~0.2epsilonCEEMDAN每级自适应噪声系数0.01~0.15max_imfs三者通用最大 IMF 数量6~12提示epsilon 和 noise_width 语义不同不要把一个算法的取值直接套到另一个上。EEMD 的 0.2 放到 CEEMDAN 里通常会明显过噪。选参逻辑我一般这么走先用 EMD 快速拆一遍看信号大概能出几个有效模态确定 max_imfs 的下限然后用 CEEMDAN(trials100, epsilon0.05) 跑一遍看 IMF 数量稳不稳定再逐步加大 epsilon观察新增的 IMF 是真实成分还是噪声碎片——如果出现大量贴近零点的小幅振荡说明噪声系数过头了往回退一档。max_imfs 建议一开始就设好比等它自动拆到底再截断要省事得多也能避免把残余噪声硬拆成伪模态。熟手可能已经注意到PyEMD 的 CEEMDAN 还暴露了 ext_EMD 参数允许你把一个自定义的 EMD 实例传进去精细控制内部的筛分迭代次数和样条参数。这个在避坑章节会展开讲。4. 实战把轴承振动信号拆成 IMF 并锁定故障频率上一章讲的是跑通这一章讲的是跑对。实测数据和合成信号差别很大从预处理到分解再到包络谱分析每一步都有固定的动作要领。4.1 数据准备去均值、去趋势、降采样三步预处理实测振动信号里有传感器偏置导致的直流分量有转频带来的低频趋势还有采集系统的高频底噪。直接喂给 CEEMDAN 不是不行但会多拆出几个没有物理意义的低频 IMF浪费计算量还干扰判断。from scipy.io import loadmat from scipy.signal import detrend, resample import numpy as np # 假设你手里的数据是 .mat 格式键名为 vibration data loadmat(bearing_data.mat)[vibration].flatten() fs 25600 # 原始采样率单位 Hz # 1. 去均值消除直流偏置 data data - np.mean(data) # 2. 去趋势用线性拟合去掉缓慢变化的趋势项 data detrend(data, typelinear) # 3. 降采样从 25.6kHz 降到 2560Hz保留故障特征频带的裕量 step 10 data resample(data, int(len(data) / step)) fs_new fs // step print(预处理后数据长度:, len(data), 新采样率:, fs_new)逻辑说明去均值是必须的CEEMDAN 的包络拟合基于极值直流分量会让包络整体平移第一个 IMF 里多出近似直流的成分。detrend 去掉线性趋势防止把趋势当成低频频段拆出来。resample 在包络谱分析前做降采样前要估算故障特征频率的最高可能值留出 5~10 倍裕量。参数说明fs 和降采样倍数要根据自己的设备改。一般原则是降采样后的采样率不低于目标频率带宽的 2.56 倍这是振动分析里常用的频率分辨率经验值。比如轴承内圈故障特征频率算出来大约 200 Hz降到 2560 Hz 就足够看清它的边带结构。4.2 CEEMDAN 分解与质量判断三条判据看着选预处理完直接分解然后立刻做质量检查不要等画完图才发现分解是废的。from PyEMD import CEEMDAN ceemdan CEEMDAN(trials200, epsilon0.08) imfs ceemdan(data, max_imfs10) # 重构相对误差CEEMDAN 完备性最直接的检验 residual data - np.sum(imfs, axis0) rel_err np.sqrt(np.mean(residual**2)) / np.std(data) print(重构相对误差:, round(rel_err, 6))逻辑说明CEEMDAN 完备性意味着残差应当接近零。rel_err 如果超过 1%先检查 trials 是否太小或者 max_imfs 被截断导致信号没拆完。我一般把相对误差小于 0.5% 作为分解可信的门槛超过这个值就回去调参不带着侥幸往下走。判断分解质量我主要看三件事一是上面这个重构误差二是每个 IMF 的频谱是否落在互不重叠的频带三是 IMF 数量是否稳定——同一段数据多跑几次数量变化超过 1 个就要警惕。第一次跑出来的结果只是“能看”三次重跑都稳定才叫“能用”。4.3 从 IMF 到瞬时频率Hilbert 变换与包络谱定位故障频率拆出 IMF 之后最常用的下一步是包络谱分析对每个 IMF 做 Hilbert 变换得到解析信号取模得到包络再对包络做 FFT得到包络谱。轴承故障的特征频率外圈 BPFO、内圈 BPFI在包络谱里会以尖峰形式出现。from scipy.signal import hilbert import numpy as np # 对第一个 IMF 做包络谱分析 imf imfs[0] analytic hilbert(imf) envelope np.abs(analytic) env_fft np.abs(np.fft.rfft(envelope)) freqs np.fft.rfftfreq(len(envelope), d1/fs_new) # 跳过前 5 个频率点找到最大峰值 peak_idx np.argmax(env_fft[5:]) 5 print(包络谱峰值频率:, round(freqs[peak_idx], 1), Hz)逻辑说明hilbert 函数直接返回解析信号np.abs 取模就是包络。包络谱的峰值频率对应的是调制频率也就是故障冲击的重复频率。注意 freq 轴用的是降采样后的采样率 fs_new别用回原采样率否则频率全错位这个坑我踩过一次之后每次都会检查一遍。参数说明峰值搜索前先跳过前 5 个频率点是为了滤掉包络谱里的直流和极低频成分。实际项目中把 BPFO/BPFI 计算值和峰值做对比误差在 ±2% 以内就可以认为锁定故障。如果峰值落在转频附近而不是特征频率附近多半是分解不彻底转频成分混到了这个 IMF 里。5. 避坑手记CEEMDAN 的五个高频翻车现场CEEMDAN 不是开箱即用的工具参数、边界、噪声每一项都能让结果翻车。下面这五条是从实际项目里攒出来的记录每一条都按现象、原因、解决的顺序写清楚。5.1 分解结果不稳定同一段信号两次跑完全不一样现象同一段数据连续跑两次 CEEMDANIMF 数量和波形都有差异第一个 IMF 尤其明显重跑一次对不上上一次的模态。原因CEEMDAN 本质是随机过程每轮加的白噪声不同。trials 不够时平均结果没有收敛到稳定值另一个隐藏原因是 epsilon 设置过大噪声分量在平均后没有完全抵消。解决先把 trials 提到 300 以上确认稳定性再逐步降低 epsilon。规律是噪声幅值越大需要的 trials 越多两者近似线性关系。我常用的组合是 trials300、epsilon0.05 到 0.08跑 10 万点数据大约 30 秒可以接受。5.2 端点飞翼首尾两端飘得不像话现象分解出的 IMF 在数据开头和结尾出现大幅度摆动幅度甚至超过信号本身中间段看起来正常。原因包络拟合用的是三次样条端点附近没有极值点做约束样条外推会产生过冲这就是端点效应。CEEMDAN 虽然改进了噪声加入方式但底层的 EMD 筛分同样继承了这个毛病。解决常见做法是端点延拓——在分解前给信号两端各延伸一段镜像数据分解完再裁掉。PyEMD 的 EMD 类支持通过传 ext_EMD 实例进去控制内部行为你可以构造一个做端点延拓的 EMD 传给 CEEMDAN这样噪声分解和残差筛分都走同一套延拓逻辑。更省事的替代方案是分段处理把长信号切成有重叠的段分别分解只取中间部分。从那以后我每次跑长信号前都先看一眼端点再决定要不要延拓。5.3 过分解把噪声也拆成了 IMF现象分解出的 IMF 数量特别多最后几个 IMF 波形杂乱频谱铺满整个频带看不出任何集中峰。原因max_imfs 没设上限CEEMDAN 会把残差一路拆到底直到残差没有两个极值为止。这个过程中噪声不具备完整模态结构但筛分过程仍会把它“硬拆”成若干伪 IMF。解决先跑一次快速分解看真实模态集中在前面几个 IMF然后设置 max_imfs 截断。判断截断位置的方法是计算每个 IMF 与原始信号的互相关系数从某个位置开始系数都低于 0.1后面基本就是噪声碎片。我一般把 max_imfs 设为 8 到 10既能覆盖常见故障信号又不会拆出太多垃圾模态。5.4 直接把 EEMD 的噪声参数套给 CEEMDAN现象把 EEMD 的 noise_width0.2 直接换成 CEEMDAN(epsilon0.2)分解结果多出大量异常尖峰重构误差反而变大。原因两个参数的语义不一样。EEMD 的 noise_width 是加在原始信号上的白噪声幅值相对标准差的比例CEEMDAN 的 epsilon 是加在残差上的自适应噪声系数它还要经过 EMD 分解出 E1(w^i)实际作用到信号的噪声能量远小于 epsilon 表面值。直接套用同一个值相当于放大了噪声能量。解决CEEMDAN 的 epsilon 通常取 EEMD noise_width 的 1/2 到 1/3再结合重构误差和包络谱质量回调。我一般从 0.05 起步每次加 0.025看结果变化再定。这个参数直接影响分解质量值得多花两轮试验去定比后面返工划算。5.5 分解完只盯 IMF 不看残差现象IMF 看起来漂亮但拿 IMF 重构信号误差大得离谱或者残差里还藏着明显的周期性成分。原因CEEMDAN 的完备性是有前提的——trials 足够、噪声参数合适、max_imfs 没有在模态没拆完时截断。任何一环没满足残差里都会有结构信息。解决每次分解后强制检查 residual data - np.sum(imfs, axis0)算它的能量占比和频谱。如果残差里还有明显单频峰说明欠分解需要降低 epsilon 或增大 max_imfs。这个检查我固定写进每个项目的处理函数里跑完自动打印眼睛看不到的问题让数字说话。6. 残差检查与模态重构验证 CEEMDAN 结果是否可信的固定动作CEEMDAN 跟 EMD、EEMD 最大的不同是完备性这是它的卖点也是验证它结果最方便的入口。我把它拆成三个固定检查动作重构误差、残差频谱、模态间相关系数。import numpy as np def check_ceemdan(signal, imfs, fs_new): # 1. 重构误差 residual signal - np.sum(imfs, axis0) rel_err np.sqrt(np.mean(residual**2)) / np.std(signal) # 2. 残差频谱找残余单频峰 spec np.abs(np.fft.rfft(residual)) freqs np.fft.rfftfreq(len(residual), d1/fs_new) peak_idx np.argmax(spec[5:]) 5 peak_freq freqs[peak_idx] peak_energy spec[peak_idx] / np.sum(spec) # 3. 模态间相关系数矩阵 n imfs.shape[0] corr np.corrcoef(imfs) max_off_diag np.abs(corr - np.eye(n)).max() print(f重构误差: {rel_err:.4%}) print(f残差主峰频率: {peak_freq:.1f} Hz, 能量占比: {peak_energy:.4%}) print(f模态间最大相关系数: {max_off_diag:.3f}) return rel_err, peak_freq, max_off_diag逻辑说明重构误差衡量完备性超过 0.5% 说明分解没做干净残差主峰频率如果落在信号的特征频率附近说明有真实模态被漏拆这时候要回退参数模态间最大相关系数超过 0.3说明两个 IMF 之间严重重叠分解并不独立。三个指标分别卡住完备性、完整性和正交性比单看一张分解图靠谱得多。这个固定动作省掉过我一次大麻烦之前跑一组齿轮箱数据CEEMDAN 拆出来的前三个 IMF 看着很像样结果残差里还杵着一个 23 Hz 的峰——那是齿面磨损故障的真实特征频率被参数没调好的分解漏掉了如果直接拿前几个 IMF 去做包络谱这一处故障就彻底丢了。从那以后我每次跑完 CEEMDAN 都强制走一遍这三连检查确认残差干净了才敢拿 IMF 去做后续分析。希望帮到你。本文还有配套的精品资源点击获取
返回列表