简介:这份资源面向信号处理方向的学生、科研人员与工程技术人员,提供一套带完整中文注释的CEEMD(完全集合经验模态分解)MATLAB实现,用于解决非线性、非平稳时间序列的分解与分析问题。代码覆盖初始化参数设置、白噪声注入、EMD迭代提取IMF、残差计算、噪声分量分离及结果可视化等关键环节,注释有助于读者对照原理理解每一步的实现逻辑,适合作为学习CEEMD算法或将其迁移到地震数据、生物信号、机械故障诊断等场景的参考脚本。资源包共4个文件,以m代码文件为主体,另含zip与rar压缩包及一份readme说明文档,整体约1.03MB,体积轻便、便于本地解压运行。目前已有1754人学习下载,读者可据此快速获得可运行的分解流程与参数配置思路,减少从理论到代码的转换成本。
1. 拿到一份带注释的 CEEMD 程序,先别急着跑
信号分解这活儿,最怕的不是算法难,而是拿到一份没注释的代码——变量名全是x1、x2、tmp,跑出来一堆 IMF 分量,你根本不知道哪个是噪声、哪个是有效成分。这份「加注释完整 CEEMD 程序的 matlab 代码」解决的正是这个痛点:它把互补集合经验模态分解(CEEMD)的完整流程拆开写清楚,每一段循环、每一个参数都标了用途,适合手上有一维振动、心电、语音或地震信号,想快速做分解又不想从零啃论文的从业者。matlab 环境下直接能跑,注释密度高,属于那种「先看懂再改」的底稿型资源,而不是黑匣子工具箱。
CEEMD 本身是 EMD 的改进版,核心是往原始信号里成对加入正负白噪声,再分别做 EMD,最后取平均抵消噪声残留。相比原始 EMD 的模态混叠,CEEMD 能把相近频率的成分分得更干净;相比 EEMD,它用互补噪声对把重构误差压得更低。这份代码的价值不在于算法多新,而在于把「加噪—分解—平均—重构」这条链路用注释摊开,让你知道每一行在干什么、参数改了会怎样。下面按「先立住原理、再动手复现、最后避坑」的顺序拆。
2. CEEMD 的分解链路:从加噪对到 IMF 平均
2.1 为什么是互补噪声对,而不是单侧加噪
原始 EMD 的问题在于极值点分布不均时会出现模态混叠——同一个 IMF 里混进两个时间尺度的成分,或者一个成分被拆到两个 IMF 里。EEMD 的思路是多次加白噪声再平均,利用噪声把极值点「填匀」,但每次加的都是独立噪声,重构时残留的噪声误差随集成次数下降得慢。CEEMD 的关键改动是:每次集成加一对幅值相反的白噪声,即+n(t)和-n(t),分别分解后把对应 IMF 取平均。由于噪声成对出现,一阶近似下噪声项相互抵消,重构误差显著降低,集成次数可以比 EEMD 少很多。
这份代码里,加噪对体现在主循环中:外层循环控制集成次数NE,内层对每个噪声样本生成正负两份信号。注释里会标出imf_plus和imf_minus两个变量,最后(imf_plus + imf_minus) / 2得到该次集成的 IMF。理解这一点,后面调参才有方向——NE不是越大越好,成对结构让它在 50~100 量级就能收敛,盲目堆到几百只是浪费时间。
2.2 主流程的四个阶段与对应代码骨架
完整链路分四段:读信号并预处理、设置 CEEMD 参数、集成循环分解、IMF 平均与重构校验。代码骨架大致如下(变量名以实际注释为准,这里给的是典型结构):
% 阶段一:读入信号,去均值,保证零均值输入 x = x(:)'; % 转行向量,避免维度错位 x = x - mean(x); % 去直流,EMD 对非零均值敏感 N = length(x); % 阶段二:CEEMD 参数 NE = 100; % 集成次数,成对噪声下 50~100 足够 NR = 5; % 每层 IMF 的筛选迭代上限 noise_std = 0.2; % 噪声幅值系数,相对信号标准差 max_imf = 8; % 最大 IMF 数,防止过分解 % 阶段三:集成循环,正负噪声成对分解 imf_sum = zeros(max_imf, N); for k = 1:NE n = noise_std * std(x) * randn(1, N); % 生成白噪声 [imf_p, ~] = emd_core(x + n, NR, max_imf); % 正噪声分解 [imf_m, ~] = emd_core(x - n, NR, max_imf); % 负噪声分解 imf_sum = imf_sum + (imf_p + imf_m) / 2; % 成对平均后累加 end imf = imf_sum / NE; % 集成平均,得到最终 IMF % 阶段四:重构校验,残差应接近零 residual = x - sum(imf, 1); fprintf('重构最大误差: %.3e\n', max(abs(residual)));逻辑说明:阶段一去均值是硬性前提,EMD 系列对非零均值信号会产生虚假趋势项;阶段二四个参数决定分解质量和耗时,NE与noise_std是最常调的两个;阶段三的emd_core是单次 EMD 的筛选过程,注释里会标出停止准则(通常是相邻筛选结果的标准差比阈值);阶段四的重构校验是判断代码是否跑对的第一道关卡,误差在1e-10量级说明平均逻辑没问题,偏大就要查噪声对是否真的成对、IMF 数是否截断过早。
参数说明:noise_std取 0.2 是常见起点,信号信噪比高时可降到 0.1,噪声主导信号可升到 0.3;NR控制单次 EMD 的筛选深度,太小 IMF 不纯,太大计算翻倍且可能过筛;max_imf建议按log2(N)估算上限,一维信号通常 6~10 层足够。
2.3 单次 EMD 内核:筛选停止准则怎么写
emd_core是整份代码里最值得逐行读的部分,因为它决定了每个 IMF 的纯度。典型实现是:找极值点、拟合上下包络、减去包络均值、判断是否满足 IMF 条件、不满足则继续筛选。停止准则常见两种——标准差准则(连续两次筛选结果的标准差比小于阈值,如 0.2~0.3)和极值点-过零点数准则(两者差值不超过 1)。这份代码的注释会明确标出用的是哪一种,改阈值时直接找对应变量即可。
function [imf, res] = emd_core(x, NR, max_imf) imf = []; res = x; for m = 1:max_imf h = res; for it = 1:NR [env_max, env_min] = envelope_fit(h); % 极值点三次样条拟合 mean_env = (env_max + env_min) / 2; h_new = h - mean_env; sd = sum((h - h_new).^2) / (sum(h.^2) + eps); h = h_new; if sd < 0.2 % 标准差停止准则 break; end end imf = [imf; h]; res = res - h; if sum(abs(res)) < 1e-8 || num_extrema(res) < 3 break; % 残差过小或极值不足,停止 end end end逻辑说明:内层it循环是筛选,sd是连续两次结果的归一化标准差,低于 0.2 认为当前分量已足够像 IMF;外层m循环逐层剥离,每剥一层更新残差res;终止条件两个——残差能量过小或极值点少于 3 个(无法再拟合包络)。参数上,NR给 5~10 是稳妥区间,sd阈值 0.2 偏严、0.3 偏松,看你对 IMF 纯度的要求。
提示:
envelope_fit里三次样条对端点极敏感,信号首尾没有极值时会外插出虚假包络,这是后面避坑章节要重点说的点。
3. 在 matlab 里跑通并验证:从合成信号到真实数据
3.1 先用合成信号做已知答案校验
拿到任何分解代码,第一步都不该上真实数据,而是造一个已知成分的合成信号,看分解结果能不能对上。比如两个不同频率的正弦叠加,再加一点趋势项,理想情况下前两层 IMF 应分别对应两个正弦,残差对应趋势。这样你能确认代码的层序、幅值、相位没搞反。
fs = 1000; t = (0:N-1)/fs; s1 = 0.8 * sin(2*pi*15*t); % 15 Hz 成分 s2 = 0.5 * sin(2*pi*60*t); % 60 Hz 成分 trend = 0.002 * t; % 缓慢趋势 x_test = s1 + s2 + trend; % 调用第 2 章的 CEEMD 主流程 % ...(参数同上,NE=80, noise_std=0.2) % 校验:看前两层 IMF 的主频 for i = 1:3 Y = fft(imf(i,:)); [~, idx] = max(abs(Y(1:N/2))); fprintf('IMF%d 主频: %.1f Hz\n', i, (idx-1)*fs/N); end逻辑说明:合成信号的好处是每个成分的频率已知,FFT 找主频就能判断 IMF 有没有分对。正常情况下 IMF1 主频接近 60 Hz、IMF2 接近 15 Hz(高频先出),残差里剩趋势。如果 IMF1 和 IMF2 主频都乱,多半是noise_std太小导致模态混叠没打散,或者NE不够。参数上,合成信号干净,NE=80足够;真实信号噪声大时要往上加。
3.2 真实信号的预处理与层数选择
真实数据上手前,预处理不能省:去趋势(detrend)、必要时带通滤波去掉工频干扰、统一采样率。层数选择上,不要迷信「分到残差只剩单调趋势」——过分解会把噪声也拆成 IMF,后几层往往是无效分量。常见做法是看每层 IMF 的能量占比或相关系数,能量骤降的那一层之后基本可以丢弃。
| 检查项 | 正常表现 | 异常信号与对策 |
|---|---|---|
| 重构误差 | 1e-10量级 | 偏大查噪声对是否成对、IMF 是否截断 |
| IMF 层序 | 高频在前、低频在后 | 反了查累加顺序或平均逻辑 |
| 后几层能量 | 逐层下降至接近零 | 不降反升说明过分解,减max_imf |
| 端点包络 | 首尾无明显飞点 | 飞点严重做端点延拓或镜像延拓 |
这张表是我每次跑完新数据必过的四道关,尤其端点飞点,肉眼一看 IMF 首尾翘起来就知道包络拟合出问题了。
3.3 集成次数与噪声幅值的实操取值
NE和noise_std是耗时和质量的两个旋钮。经验取值:noise_std从 0.2 起调,信号里高频噪声重就降到 0.1~0.15,低频成分弱就升到 0.25~0.3;NE在成对结构下 50~100 收敛,超过 150 收益很小。可以写个小循环扫一遍,看重构误差和 IMF 主频稳定性,别凭感觉拍。
for ns = [0.1 0.2 0.3] for ne = [50 100 150] % 跑 CEEMD,记录重构误差和 IMF1 主频 % 结果存表对比,选误差小且主频稳定的组合 end end逻辑说明:这个扫描不是让你每次都跑,而是在新类型信号上定一次参数基线,之后同类数据沿用。参数说明:ns太小混叠打不散,太大则噪声残留进 IMF;ne太小平均不充分,太大纯耗时间。扫完把结果记下来,下次直接复用,省得反复试。
4. 避坑与排查:这份代码最容易翻车的五个地方
4.1 现象:重构误差大得离谱,IMF 加起来对不上原信号
原因:最常见是噪声对没真正成对——有人图省事把x - n写成x - randn(...)重新生成了一份噪声,正负不对应,平均抵消失效。其次是 IMF 数被max_imf截断,后面的分量没算进去,重构自然缺一块。解决:检查内层两次分解用的是不是同一个n;把max_imf调大或改成按残差极值点自动停止;跑完必打重构误差,1e-10量级才算过。
4.2 现象:IMF 首尾翘起明显飞点,FFT 出现虚假低频
原因:三次样条拟合包络时,信号端点不是极值点,样条外插出虚假包络,减均值后端点被拉偏。这是 EMD 系列的通病,不是这份代码写错。解决:做端点延拓——镜像延拓或极值延拓,把信号首尾各延一段再分解,最后截掉延拓部分;简单点也可以对首尾若干点做加窗衰减。注释里如果标了延拓开关,直接打开。
4.3 现象:模态混叠还在,相近频率成分挤在同一个 IMF
原因:noise_std给小了,噪声不足以把极值点填匀;或者NE太少,平均不充分。解决:先把noise_std从 0.2 提到 0.25~0.3 试,再把NE加到 100~150;还不行就检查信号本身是不是两个成分频率太近(比如相差不到一个倍频程),这种情况 CEEMD 也难完全分开,得考虑带通预分离。
4.4 现象:跑得特别慢,几分钟出不来结果
原因:NE和NR双高,加上max_imf给太大,单次 EMD 的样条拟合次数爆炸。解决:成对结构下NE降到 50~80;NR给 5 足够,别设 20;max_imf按log2(N)估上限。另外 matlab 里样条拟合是热点,能向量化的循环尽量向量化,别在筛选内层做字符串拼接或动态扩容。
4.5 现象:换一组数据结果完全不一样,参数像玄学
原因:noise_std用的是绝对幅值而不是相对信号标准差,信号量级一变,等效噪声强度就变了。解决:统一写成noise_std * std(x) * randn(...),让噪声幅值随信号自适应;同时固定随机种子(rng(0))便于复现对比。这条血泪经验值得记牢——不固定种子,两次跑的结果对不上,排查时根本分不清是参数问题还是随机性。
5. 进阶:把 CEEMD 结果落到特征提取与批量处理
分解出 IMF 只是中间产物,真正要用起来得往下走一步。常见做法是对每层 IMF 算时频特征——瞬时频率(Hilbert 变换)、能量占比、样本熵,拼成特征向量喂给分类器。这份代码的注释结构清晰,加特征提取很顺手:在 IMF 平均之后插一段循环即可。
features = []; for i = 1:size(imf, 1) h = hilbert(imf(i,:)); inst_freq = diff(unwrap(angle(h))) * fs / (2*pi); % 瞬时频率 energy_ratio = sum(imf(i,:).^2) / sum(x.^2); % 能量占比 samp_ent = sample_entropy(imf(i,:), 2, 0.2*std(imf(i,:))); features = [features; mean(inst_freq), energy_ratio, samp_ent]; end逻辑说明:hilbert构造解析信号取瞬时频率,energy_ratio反映该层在原始信号里的权重,样本熵衡量复杂度;三个特征拼起来能区分不同工况。参数上,样本熵的容限r取0.2*std是通用起点,嵌入维数m=2够用。批量处理时把读文件、分解、特征提取、存表串成函数,外层套dir遍历,注意每次分解前rng固定种子,否则同一文件两次结果不一致。
验证方法上,我习惯留一段已知标签的数据做回代:特征进分类器,看混淆矩阵,如果某类总是分错,回头查是不是对应 IMF 层被过分解或欠分解。这套「分解—特征—回代」闭环跑通,才算真正把这份代码用起来了。从那以后我每次拿到新的分解类代码,都强制先跑合成信号校验、再固定种子扫参数、最后留验证集回代,三步缺一不可,省下的排查时间远比多花的几分钟值。希望帮到你。
本文还有配套的精品资源,点击获取