简介:面向信号处理课程设计与毕业设计的Matlab算法实践文档,围绕鲸鱼优化算法WOA自动寻优变分模态分解VMD的模态数k与惩罚因子alpha,缓解人工调参导致的分解不稳定问题。文档中含完整源码与数据说明,覆盖边际谱、中心频率图、收敛曲线及超参数迭代过程图,便于观察每次迭代的参数变化与分解效果;代码采用参数化编程,关键参数集中可改,注释较细,Excel数据便于替换复现。压缩包共1个PDF文件,约600KB,以文档形式组织源码、图表与实验说明。已有1336人学习,适合计算机、电子信息工程、数学等专业学生用于课程设计、期末大作业或毕业设计,也可作为时序分解方向的创新点参考。
1. 三个晚上卡在 K 上:WOA-VMD 到底替你省了哪一步
前阵子帮人看一份轴承振动数据,对方用的是 EMD,分解完模态混叠得一塌糊涂,换到 VMD 之后又卡在参数上:K 取 4 中心频率挤成一团,取 8 又多出一堆没意义的毛刺,alpha 从 500 调到 3000 波形跟着变,折腾三个晚上还在原地。这类时序分解任务里最耗人的从来不是写代码,是那组超参数没有解析解。这份 Matlab 算法资源干的事很直接:用鲸鱼优化算法 WOA 在给定的 alpha、K 搜索区间里迭代,把平均包络熵压到最低,再用这组最优参数喂给变分模态分解 VMD 做正式分解,顺带把收敛曲线、中心频率图、边际谱一次画出来。适合电子信息、计算机、数学专业做课程设计、期末大作业和毕业设计的人,也适合手头有个信号处理课题、想把“参数自适应”当成创新点写进论文的。
2. 算法分工:WOA 搜参数、VMD 做分解,谁也别越界
2.1 VMD 的两个超参数为什么不能靠手调
VMD 的出发点是把一个实信号拆成 K 个有限带宽的模态分量,约束条件是所有模态加起来还原原始信号,优化目标则是每个模态的带宽之和最小。这个约束优化问题最后用增广拉格朗日加交替方向乘子法(ADMM)去解,迭代过程中真正决定成败的就是两样东西:模态数 K 和二次惩罚因子 alpha。
K 决定信号被切成几段。K 小了,两个物理意义完全不同的频率成分会被强行塞进同一个模态,中心频率图上表现为两条谱线黏在一起;K 大了,同一个成分被拆成两半,还会额外生出一堆幅值很小的高频分量,看着图很丰富,其实全是噪声。alpha 决定每个模态的带宽,值越大带宽越窄、模态越“瘦”,值越小带宽越宽、分解越糊。这两个参数是耦合的,你单独调其中一个,另一个的最优值跟着变,手调基本等于在二维平面上盲搜。
所以“自动寻优”不是炫技,是这类方法能不能稳定的前提。常见的做法是把 K 和 alpha 组成一个二维向量丢给优化器,优化器每给一组候选值就真跑一次 VMD,算一个适应度回去,迭代若干轮后取最优。代价是计算量成倍上涨,因为一次 WOA 迭代里每个个体都要完整跑一遍 VMD。
2.2 WOA 的三段式位置更新怎么落到二维搜索空间
鲸鱼优化算法的位置更新分三段。第一段是包围猎物:当系数向量 A 的绝对值小于 1 时,所有个体朝当前最优个体靠拢;第二段是随机搜索:当 A 的绝对值大于等于 1 时,个体朝种群中随机挑的另一个个体靠拢,这一段保证了全局探索能力,也是它比纯梯度类方法更适合这种黑箱适应度的地方;第三段是螺旋气泡网攻击:以一定概率走对数螺旋路径绕向最优个体,负责在最优解附近做精细搜索。
这三段落到我们这个二维问题上,就是每个“鲸鱼”的位置是一个长度为 2 的向量,第一维是 alpha,第二维是 K。控制探索和开发切换的关键参数是 a,它随迭代从 2 线性降到 0,A 由 a 和随机数共同决定,所以前期 A 的绝对值大概率大于 1、种群四处乱逛,后期大概率小于 1、整体收敛到最优附近。这个机制的好处是不用自己设计降温策略,坏处是种群小的时候(比如 pop 只取 5)随机性很强,同一份数据两次跑出来的 K 可能不一样,后面对比实验时这一点会带来麻烦。
2.3 适应度函数定生死:包络熵的写法与取舍
优化器本身不关心信号,它只认一个标量返回值。这个返回值怎么定义,直接决定最后搜出来的参数是不是你想要的。这类分解任务里最常用的是包络熵:对每个模态做希尔伯特变换取包络,把包络归一化成概率分布后算香农熵,熵越小说明该模态的包络越稀疏、周期性越强,分解质量越好。多个模态就取平均,整个适应度就是最小化平均包络熵。
function score = fun(x, f, tau, DC, init, tol) % x: WOA 传进来的 1x2 候选向量,x(1)=alpha,x(2)=K alpha = x(1); K = round(x(2)); % K 必须是整数,先取整 K = max(3, min(10, K)); % 再钳到和 lb/ub 一致的区间,防止取整后越界 if K >= numel(f)/2 score = 1e6; % 退化保护:模态数不能接近信号长度的一半 return; end % 用候选参数真跑一次 VMD [u, ~, ~] = VMD(f, alpha, tau, K, DC, init, tol); % 逐模态算包络熵,再取平均 env_entropy = zeros(1, K); for k = 1:K env = abs(hilbert(u(k, :))); % 希尔伯特变换取包络 env = env + eps; % 防止 log(0) p = env / sum(env); % 归一化成概率分布 env_entropy(k) = -sum(p .* log(p)); end score = mean(env_entropy); % 越小越好 end代码里有两个地方值得多说一句。一是round加min/max的双重钳位,WOA 是在连续空间里搜索的,它给出的 K 一定带小数,取整之后可能落到边界外,不钳位就会出现 K=11 这种超出 ub 的情况,VMD 那边直接给你报错或者返回空。二是退化保护那一行,K 逼近信号长度一半时中心频率的划分会失去意义,与其让它跑出一堆垃圾再污染最优解,不如直接返回一个大惩罚值,让优化器自己绕开。
mean也可以换成min,也就是最小包络熵准则,只盯着最稀疏的那个模态。两种都有人用,平均熵倾向于整体均衡,最小熵倾向于突出主成分,如果你的信号里有一个特别强的周期成分、其余都是弱分量,用min更容易把它单独拎出来。
3. 目录与运行链路:从 main.m 到第一张分解图
3.1 文件清单与各自的职责
这套东西拆得很干净,一个 m 文件一个职责,没有把优化器和分解内核糊在一个脚本里,这点对二次开发很友好。常见的文件构成大概是这样:
| 文件 | 作用 | 通常要不要动 |
|---|---|---|
| main.m | 主入口,读数据、设参数、调优化器、回代分解、出图 | 改数据路径和采样率 |
| WOA.m | 鲸鱼优化器,输入种群规模、迭代次数、上下界、维度、适应度句柄 | 换算法时整体替换 |
| VMD.m | 变分模态分解内核,ADMM 迭代 | 一般不动 |
| fun.m | 适应度函数,内部调用 VMD 并算包络熵 | 改适应度准则时动 |
| huatu.m | 画适应度曲线和两个参数随迭代的变化 | 换配色、改标注时动 |
| data.xlsx | 待分解的时序数据 | 换成自己的数据 |
这种拆法的价值在于:你想把 WOA 换成别的优化器,只要新算法保持[最优值, 最优点, 收敛曲线, 迭代过程]四输出的签名,main.m 一行都不用改。反过来你想换适应度准则,也只需要动 fun.m,优化器那边完全无感。
3.2 main.m 的调用链逐段拆
主程序其实就五步,但每一步都有容易翻车的地方。
clc; clear; close all; addpath(genpath(pwd)); % 把子目录也加进搜索路径 %% 1. 读数据 data = readmatrix('data.xlsx'); % 第一列时间、第二列幅值 f = data(:, 2)'; % VMD 要行向量,转置别漏 fs = 1000; % 采样频率,必须和你自己的数据对上 t = (0:length(f)-1) / fs; %% 2. VMD 的固定参数(不参与优化) tau = 0; % 对偶上升步长,含噪信号一般取 0 DC = 0; % 不强制第一个模态为直流分量 init = 1; % 中心频率均匀初始化,比全 0 稳 tol = 1e-7; % 收敛容差 %% 3. WOA 的搜索空间:alpha ∈ [800,2000],K ∈ [4,10] pop = 5; Max_iteration = 20; lb = [800, 4]; ub = [2000, 10]; dim = 2; % 两个待优化参数 fobj = @(x) fun(x, f, tau, DC, init, tol); [best_score, best_pos, curve, process] = WOA(pop, Max_iteration, lb, ub, dim, fobj); %% 4. 取整回代,用最优参数重新分解一次 alpha_opt = round(best_pos(1)); K_opt = round(best_pos(2)); [u, u_hat, omega] = VMD(f, alpha_opt, tau, K_opt, DC, init, tol); %% 5. 出图 huatu(process, 'VMD'); % 适应度曲线 + 两个参数随迭代的变化 figure; for k = 1:K_opt subplot(K_opt, 1, k); plot(t, u(k, :)); title(['IMF', num2str(k)]); end第 1 步里data(:, 2)'那个转置是真会漏的。VMD 内核按行向量切频点,你传个列向量进去,部分实现会在fft之后维度对不上,报的错还特别隐晦,看着像索引越界。采样率fs只影响画图的时间轴和中心频率换算,不影响分解本身,但你要是拿它去标频率轴又填错了,后面所有关于“这个模态大概在多少赫兹”的结论全是错的。
第 2 步的tau取值有讲究。它是增广拉格朗日里对偶变量的更新步长,取值大收敛快但容易振荡,取 0 相当于放松对偶约束,对含噪信号更稳。init取 1 表示中心频率均匀分布初始化,取 0 是全部从 0 开始,后者在某些信号上会出现多个模态收敛到同一频率的退化,所以默认给 1 更省心。
第 4 步的“重新分解一次”不能省。优化过程里最优个体可能是个带小数的 K,取整之后参数已经变了,直接用优化过程中缓存的那次分解结果,图上的中心频率和你最终报告的参数是对不上的。血泪经验是:回代这一步一定要重跑,别偷懒。
3.3 Excel 数据替换与采样率陷阱
换自己的数据时,最常见的情况是表格里不止两列——可能第一列是序号、第二列是时间戳、第三列才是幅值。这时候data(:, 2)取到的是时间,分解出来的东西看着像一条直线加噪声,你还以为是算法坏了。正确做法是先size(data)看一眼列数,或者干脆用readmatrix的Range参数只读幅值那一列。
另一个坑是数据长度。VMD 内部会做镜像延拓来抑制边界效应,延拓长度大约是信号长度的一半,如果原始数据只有几百个点,延拓之后边界附近的信息基本是假的,分解出来的首尾会明显失真。一般建议至少两千点以上,做毕设的话取一万点左右的片段比较稳妥,既能看出周期结构,又不会跑到 Matlab 的内存上限。
提示:分解前先
plot(t, f)看一眼原始波形,确认没有明显跳变、没有整段缺失值。很多“分解效果差”的问题,源头是数据本身就有断点或量纲不对。
4. 核心代码拆解:VMD 的 ADMM 迭代与 WOA 的三段式更新
4.1 VMD 主循环:频域更新 u_hat、omega、lambda
VMD 的迭代全在频域做,时域信号先做 FFT 并镜像延拓,然后在频点上逐个更新模态频谱、中心频率和对偶变量。理解这段代码的关键是记住三个更新式的物理含义:模态更新本质是一个维纳滤波,把“除当前模态之外的所有成分”当作噪声减掉;中心频率更新是该模态功率谱的重心;对偶变量负责在每轮迭代后把约束违反量反馈回去。
f_mirror = [f(floor(end/2)+1:-1:2) f f(end-1:-1:floor(end/2))]; % 镜像延拓 T = numel(f_mirror); f_hat = fftshift(fft(f_mirror)); f_hat(1:T/2) = 0; % 只保留正频率半轴 freqs = (0:T-1)/T - 0.5 - 1/T; % 归一化频率轴 u_hat = zeros(T, K); % 各模态频谱 lambda = zeros(T, 1); % 对偶变量 omega = zeros(1, K); % 中心频率 prev_sum = zeros(1, T); for iter = 1:500 prev_sum = sum(u_hat, 2).'; % 上一轮所有模态之和 for k = 1:K sum_uk = prev_sum - u_hat(:, k).'; % 去掉第 k 个模态的残差 u_hat(:, k) = (f_hat.' - sum_uk - lambda.'/2) ./ ... (1 + alpha * (freqs - omega(k)).^2); % 维纳滤波式更新 if ~DC omega(k) = abs(sum(freqs .* abs(u_hat(:, k).').^2)) / ... (sum(abs(u_hat(:, k).').^2) + eps); % 功率加权重心 end prev_sum = sum_uk + u_hat(:, k).'; % 把本轮更新值加回去 end lambda = lambda + tau * (sum(u_hat, 2) - f_hat.'); % 对偶上升 if sum(abs(u_hat(:) - u_hat_old(:)).^2) / (sum(abs(u_hat_old(:)).^2) + eps) < tol break; % 相对误差判收敛 end u_hat_old = u_hat; endsum_uk那三行的写法是这段代码里最绕的地方,也是很多人读不懂的原因。它做的是“用一个滑动累加和代替每次都重新求和”:先假设总和是上一轮的,减掉第 k 个模态就得到残差,更新完第 k 个再把它加回去,下一轮循环时这个总和就是“上一轮 k+1 到 K 加上本轮 1 到 k”,正好是当前需要的那一份。这样每轮循环只做一次加法一次减法,比每次都sum一遍快不少,模态数越多差距越明显。
收敛判据用的是相对误差,分母是上一轮的能量,分子是本轮的改变量。tol取 1e-7 是常见值,取太小迭代次数暴涨收益甚微,取 1e-3 又可能在大 K 的情况下提前停。循环上限 500 次是保险丝,正常信号两三百次内就收敛了,如果你发现每次都要跑满 500 次,八成是 alpha 太小或者信号里噪声占比过高。
4.2 WOA 的一次完整迭代
优化器这边,核心就是每个个体依次做边界检查、算适应度、更新全局最优,然后按概率走包围或螺旋。
function [Best_score, Best_pos, curve, process] = WOA(pop, Max_iter, lb, ub, dim, fobj) Best_score = inf; Best_pos = zeros(1, dim); curve = zeros(1, Max_iter); process = zeros(Max_iter, dim + 1); % 记录 [alpha, K, 适应度] X = repmat(lb, pop, 1) + rand(pop, dim) .* repmat(ub - lb, pop, 1); for t = 1:Max_iter for i = 1:pop X(i, :) = max(X(i, :), lb); % 下界回拉 X(i, :) = min(X(i, :), ub); % 上界回拉 fit = fobj(X(i, :)); % 这里会完整跑一次 VMD if fit < Best_score Best_score = fit; Best_pos = X(i, :); end end a = 2 - 2 * t / Max_iter; % 从 2 线性降到 0 a2 = -1 + t * (-1 / Max_iter); for i = 1:pop A = 2 * a * rand() - a; C = 2 * rand(); p = rand(); if p < 0.5 if abs(A) < 1 D = abs(C * Best_pos - X(i, :)); X(i, :) = Best_pos - A * D; % 包围猎物 else idx = randi(pop); D = abs(C * X(idx, :) - X(i, :)); X(i, :) = X(idx, :) - A * D; % 随机搜索 end else b = 1; l = (a2 - 1) * rand() + 1; X(i, :) = abs(Best_pos - X(i, :)) .* exp(b * l) .* cos(2 * pi * l) + Best_pos; end end process(t, :) = [Best_pos, Best_score]; curve(t) = Best_score; end end必须点明的一点:不管你手上这份代码里函数名还留着什么别的群智能算法名字,只要它保持上面这种“四输出 + 内部真调适应度函数”的骨架,主链路就是通的。换算法的时候整体替换这个文件即可,main.m那边完全不用感知。这也是我把优化器和分解内核拆开讲的原因——解耦做得好,二次开发的成本能降一个量级。
a的线性递减是整个算法的节拍器,它决定了探索和开发的时间分配。迭代次数只有 20 的时候,a每轮掉 0.1,变化很快,种群还没来得及充分探索就进入开发阶段了。这也是为什么小迭代次数下结果稳定性差,后面做对比实验时我一般会把它提到 30 到 50。
4.3 参数取整、边界钳位与适应度缓存
三个细节决定了这套代码从“能跑”到“能出可信结果”。
第一是取整时机。K 在搜索空间里是连续值,如果每算一次适应度就取整,那 4.1 和 4.4 这两个候选值会得到完全一样的结果,等于浪费了一半的种群预算。更麻烦的是 WOA 的位置更新会认为这两个点不同,继续在它们附近做小步搜索,实际上适应度平面是阶梯状的,梯度信息全是假的。常见的缓解办法是把 K 的搜索区间收窄(比如 4 到 8),让阶梯的每一级对应足够宽的连续区间。
第二是边界钳位的位置。上面代码里是在算适应度之前钳,而不是更新之后立刻钳。这样做的好处是被钳住的个体会在下一次迭代里带着一个合法位置继续参与,而不是被反复拉回边界形成一堆重复点。如果你的收敛曲线前几轮就平了,先检查钳位是不是只做了一半。
第三是适应度缓存。一次 WOA 跑 20 轮、每轮 5 个个体,就是 100 次完整 VMD,每次几百轮 ADMM 迭代,数据长一点能跑好几分钟。如果你在做参数敏感性分析,同一个(alpha, K)会被反复问到,用一个containers.Map按字符串键缓存适应度值,能省掉一大半重复计算。
persistent cache if isempty(cache) cache = containers.Map('KeyType', 'char', 'ValueType', 'double'); end key = sprintf('%.0f_%.0f', round(alpha), round(K)); if isKey(cache, key) score = cache(key); return; end % ……正常计算 score…… cache(key) = score;这段放在fun.m开头就行。注意 key 里用的是取整后的值,因为真正影响分解结果的就是取整后的参数,用原始浮点数当 key 反而命中不了。
5. 避坑与排查:五个让结果面目全非的细节
5.1 分解出来全是高频毛刺,中心频率挤成一条线
现象:跑完图一看,IMF1 到 IMF5 的波形全是细密振荡,中心频率图上几条谱线几乎重合在最高频段,低频的主成分反而没被单独分出来。
原因:K 的搜索上界给太高,同时 alpha 偏小。K 一大,VMD 会把同一段高频噪声反复切分;alpha 小意味着每个模态允许的带宽很宽,几个模态就会互相吞并,最后一起挤在高频。
解决:先把 ub 里 K 的上界压到 6 到 8,alpha 的下界从 800 提到 1000 到 1200,再跑一次看中心频率间隔。判断标准很朴素:相邻两个中心频率至少拉开 10% 以上,挤在一起的说明 K 还是大了。数据本身高频噪声重的话,分解前先做一次低通或者用tau=0放松对偶约束,效果比硬调 K 好。
5.2 收敛曲线第一轮就平到底,一动不动
现象:huatu画出来的适应度曲线从第一个点开始就是一条水平线,后续 19 轮完全没变化,但最终分解结果又不算太差。
原因:K 的阶梯效应把种群压塌了。pop 只有 5,K 的搜索区间是 4 到 10 这 7 个整数,随机初始化很可能 5 个个体取整后只落到两三个不同的 K 上,适应度自然早早收敛。另一种可能是 alpha 在这个区间内对包络熵几乎不敏感,无论怎么变适应度都差不多。
解决:先做一次网格扫描确认敏感度——固定 K,让 alpha 从 800 按 200 步长走到 2000,把适应度打出来看有没有明显谷底。如果曲线很平,说明包络熵这个准则对你的信号不敏感,换成最小包络熵或者峭度准则再试;如果有谷底但 WOA 没找到,那就是种群规模不够,pop 提到 10 以上、迭代提到 30 轮。
5.3 同一份数据两次运行结果不同
现象:代码一个字没改,第一次跑出 K=6,第二次跑出 K=8,两次的收敛曲线形状也不一样。
原因:WOA 的初始化、随机搜索的个体选择、螺旋参数 l 全都依赖rand,Matlab 启动时用的是系统时间做种子,每次都不一样。这不是 bug,是随机优化算法的固有属性。
解决:做对比实验、写论文出图之前,在main.m第一行加rng(1)固定随机流。注意不同 Matlab 版本的随机数生成器默认实现可能不同,换机器复现时要把版本也记下来。另外固定种子只保证同一次会话内可复现,如果你在并行环境里跑,每个 worker 的随机流是独立的,得在parfor内部单独设种子。
5.4 换了自己的 Excel 就报维度错或者波形完全不对
现象:readmatrix读进来之后,VMD报索引越界,或者能跑但分解出的第一个模态是一条斜线。
原因:三种情况最常见——表格不止两列,取到了时间戳而不是幅值;数据是列向量没有转置,VMD 内部按行切频点导致维度错位;采样率填错,导致中心频率换算出来的物理频率整体偏了一个量级。
解决:读数据之后立刻加三行自检,size(data)、plot(data(:, end))、fs和你原始采集设备的设置对一遍。幅值列一般是最后一列或者表头里带 amp、value 的那一列。转置这一步别省,写成f = data(:, end)';比事后 debug 便宜得多。
5.5 出图中文变方框,或者图存下来是空白
现象:title('IMF1')显示正常,但title(['模态', num2str(k)])出来一串方框;用saveas存 png,打开是纯白。
原因:Matlab 默认字体不含中文字形,不同版本默认字体不一样;saveas在某些渲染器下对含中文的图兼容性差,尤其是设置了Position之后。
解决:在脚本开头统一设set(0, 'DefaultAxesFontName', 'SimHei'),或者干脆把图里的标题改成英文。存图换成exportgraphics(gcf, 'imf.png', 'Resolution', 300),比saveas稳得多,分辨率也可控。出图前记得drawnow,不然有时候存到的是还没渲染完的空白画布。
注意:上面五条里,5.2 和 5.3 是最容易被误判成“算法有问题”的。遇到结果异常,先固定种子重跑一次、再做一次网格扫描,能排掉一半的假故障。
6. 二次开发:把单次实验改成能批量出报告的脚本
单次实验跑通之后,真正费时间的是“换个信号重来一遍”。我一般会把main.m改成两层结构:外层是一个文件列表的循环,内层才是原来的五步。参数全部收进一个结构体cfg,这样批量跑的时候只改结构体字段,不用碰逻辑代码。
files = {'data1.xlsx', 'data2.xlsx', 'data3.xlsx'}; cfg.tau = 0; cfg.DC = 0; cfg.init = 1; cfg.tol = 1e-7; cfg.pop = 10; cfg.Max_iter = 30; cfg.lb = [1000, 4]; cfg.ub = [2000, 8]; result = struct('file', {}, 'alpha', {}, 'K', {}, 'score', {}, 'curve', {}); for n = 1:numel(files) rng(1); % 每个文件都用同一个种子,保证可比 f = readmatrix(files{n}); f = f(:, end)'; fobj = @(x) fun(x, f, cfg.tau, cfg.DC, cfg.init, cfg.tol); [score, pos, curve, process] = WOA(cfg.pop, cfg.Max_iter, cfg.lb, cfg.ub, 2, fobj); result(n).file = files{n}; result(n).alpha = round(pos(1)); result(n).K = round(pos(2)); result(n).score = score; result(n).curve = curve; huatu(process, ['VMD-', num2str(n)]); exportgraphics(gcf, sprintf('fig_%d.png', n), 'Resolution', 300); close(gcf); end save('result.mat', 'result');这段的要点在rng(1)的位置。放在循环内部,每个文件都从同一个随机流开始,不同信号之间的对比才是公平的;放在循环外面,后面的文件会继承前面的随机状态,最后你没法判断差异是信号本身的还是随机性带来的。这个坑我踩过一次,两组数据的适应度差了 30%,查了半天以为是数据特性,最后发现是种子没对齐。
第二个改造方向是换优化器。因为WOA.m和main.m之间只有一层函数句柄的耦合,你把WOA换成粒子群、灰狼或者蜣螂,只要保持四输出签名,主程序一行不用改。做论文的时候拿这个做消融对比特别方便:同一个fun.m、同一份数据、同一个种子,只换优化器,跑出来的 K、alpha 和收敛曲线直接列成表,说服力比空谈“本方法更优”强得多。
第三个方向是出图。原版的huatu只画适应度和参数变化,实际上还可以加三个图:各模态的时域波形堆叠、中心频率随迭代的变化轨迹、以及原始信号与各模态之和的重构误差。重构误差那张图尤其值得加,它是最直接的分解质量自证——VMD 的约束就是模态和等于原信号,误差曲线应该贴在 1e-6 量级,如果明显偏大,说明 K 或者 tol 设置有问题。
最后说个我自己的习惯。从那以后我每次改完fun.m,都会先拿一段人造的、已知频率成分的合成信号跑一遍——三个正弦加白噪声,频率分别放在 20、60、150 Hz,看分解出来的中心频率是不是正好落在三个位置。这个“打靶”步骤花不了两分钟,但能在换数据之前就把适应度函数的逻辑错误、转置错误、频率轴换算错误全部逼出来。真实数据没有标准答案,合成信号有,先过合成信号这一关,后面调参才有意义。希望帮到你。
本文还有配套的精品资源,点击获取