ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

GWO优化VMD与峭度筛选:轴承故障诊断的信号处理全流程指南

GWO优化VMD与峭度筛选:轴承故障诊断的信号处理全流程指南 GWO优化VMD并基于峭度筛选分量这个组合在信号处理圈子里这几年越来越常见。我在处理振动信号、故障诊断这类任务时也踩过不少坑最终把这条路走通了。这篇就详细拆一下整个思路、关键原理和实操细节看完你直接能拿去用。1. 项目到底在解决什么问题先说场景。无论是旋转机械的轴承故障诊断还是齿轮箱的状态监测原始采集到的信号都非常“脏”里面混杂了大量噪声、其他部件的振动干扰、工频谐波等等。你要是直接把原始信号丢给分类器或者用阈值判断结果一般都不可靠。传统做法是拿带通滤波、小波变换这些工具先做预处理但问题来了——带通滤波需要你事先知道故障特征频率在哪个频段这在实际工程里往往做不到因为故障类型不同、转速不同特征频率漂移得很厉害小波变换则需要选小波基函数和分解层数选错了效果大打折扣。变分模态分解VMD就是在这个背景下被广泛使用的。它能把一个复杂信号自适应地分解成若干个具有特定中心频率的有限带宽的模态分量本质上相当于信号被“拆”成了不同频段的子信号。相比经验模态分解EMDVMD有更扎实的数学基础没有EMD那种模态混叠和端点效应的问题。但VMD有个很尴尬的短板它有两个关键参数需要提前人为指定模态分解个数K和惩罚因子alpha。这两个参数一旦设置不合理分解效果直接崩盘。K设小了信号拆不干净故障特征被淹没K设大了会出现过分解把同一个物理成分拆成好几块最后每个分量都是碎片没法看。所以就有了“GWO优化VMD”的玩法——用灰狼优化算法Grey Wolf Optimizer简称GWO自动搜索VMD的最优参数组合代替人工试错。分解完之后又面临另一个问题这么多分量哪些是有用的哪些只是噪声这时候就需要“峭度”这个指标来当裁判筛选出真正包含冲击特征的分量。这个方案的完整链路就是原始信号 → GWO寻优确定VMD参数 → VMD分解得到若干IMF分量 → 计算每个分量的峭度值 → 筛选出高峭度分量 → 对筛选后的分量做Hilbert包络解调分析 → 得到故障特征频率。我最初接触这套组合是在一次轴承外圈故障的数据分析任务里当时用默认参数的VMD分解后频谱一塌糊涂后来把参数寻优和峭度筛选加进去整个诊断链条一下子就通了。这也是我为什么愿意花时间把完整过程整理出来的原因。2. VMD分解核心原理与参数影响要理解GWO为什么能优化VMD得先搞清楚VMD的数学本质和它到底受哪些因素影响。2.1 VMD的数学本质VMD的核心思想是把信号分解问题转化为约束变分问题的求解。它假设原始信号f(t)由K个模态分量u_k(t)叠加而成每个模态都有一个中心频率ω_k。然后通过构造一个增广拉格朗日函数用交替方向乘子法ADMM迭代求解。从物理直觉上理解VMD做的其实就是“频带切割”——它在频域里把信号的能量划分到不同的窄带里每个模态被约束在中心频率附近的一个有限带宽内。这和EMD那种基于极值包络的递归筛分思路完全不同VMD一次性把所有模态都解出来避免了误差累积。一个直观的类比VMD就像一个分拣机器它拿到一堆混合的豆子原始信号知道要分成K堆模态数K于是自动找出每堆豆子的“平均颜色”中心频率和“颜色波动范围”带宽然后按最接近的颜色把豆子归堆。分拣机器的“严格程度”就对应惩罚因子alpha。2.2 模态数K的影响K是VMD里最核心也最难确定的参数。K设置得合理每个模态都能对应一个明确的物理成分K设置偏小多个频率成分会被“压缩”进同一个模态里表现为模态内频谱宽、多峰K设置偏大同一个物理成分会被拆成多个窄带模态出现“模态分裂”。实际中判断K是否合适最常见的做法是观察中心频率的分布——如果分解出来的相邻模态中心频率靠得很近甚至出现频率几乎相同的两个模态大概率就是K设大了。但这个过程很依赖经验和人工观察没法自动化。2.3 惩罚因子alpha的影响alpha在VMD目标函数中控制的是模态带宽的权重。alpha越大带宽惩罚越重每个模态的频谱越窄对噪声的鲁棒性增强但同时也可能把原本较宽的调制边带截掉alpha越小模态带宽越宽能容纳更多频率成分但噪声也更容易混进模态里。如果说K决定了分几堆alpha就决定了每堆的“松散程度”。设置不当会导致模态内包含过多与真实成分无关的细节或者反过来丢失了承载故障信息的边带成分。更麻烦的是K和alpha互相牵连——在某个K值下表现好的alpha换一个K值可能就完全失效了。这种耦合性让人工调参变得极其枯燥而且很难找到全局最优组合。2.4 为什么不推荐EMD的替代方案有些人会问既然VMD参数这么难调为什么不用EMD或EEMD它们不需要预设参数。用过的都知道EMD的模态混叠问题太严重了两个频率相近的成分经常被混进同一个IMF里而且对噪声极敏感。EEMD加了白噪声辅助能缓解混叠但计算量暴增分解结果还会受到噪声幅值和集成次数的影响反而引入了新的不确定性。相比之下VMD在分解精度和鲁棒性上都有明显优势只要参数选对了效果非常稳定。所以核心问题不是“要不要用VMD”而是“怎么把VMD参数调好”——GWO优化就是用来解决这个短板的。3. GWO优化VMD参数的设计思路3.1 灰狼优化算法的基本原理灰狼优化算法是模拟灰狼群体捕食行为的元启发式优化算法。灰狼群有严格的社会等级alpha头狼负责决策beta副头狼辅助决策delta普通狼服从指挥omega底层狼负责追踪猎物。在算法里每一只狼代表一个候选解alpha、beta、delta狼的位置相当于当前找到的最好、次好、第三好的解其他狼根据这三只狼的位置来更新自己的位置。GWO的独特之处在于它在“搜索”和“开发”之间做了很好的平衡。参数a从2线性递减到0控制着搜索步长和范围前期a大狼群大步探索避免陷入局部最优后期a小狼群小步精细搜索收敛到最优解附近。这个特性让它非常适合处理VMD参数寻优这类连续优化问题。3.2 优化变量与适应度函数的确定用GWO优化VMD首先要明确两个问题优化什么变量用什么指标评价解的好坏。优化变量很明确就是K和alpha这两个连续或离散参数。K的取值范围通常在2到15之间工程中很少需要超过15alpha的取值范围通常在200到5000之间。这里有个细节——K是整数而GWO本身是连续优化算法所以在迭代过程中需要对K做取整处理比如四舍五入到最近的整数。适应度函数是整个优化过程的关键。它的作用是告诉算法“这组参数到底好不好”。评价VMD分解效果有很多指标比如包络熵、排列熵、能量熵、峭度等。包络熵是目前用得比较多的一种它的计算方式是先对每个模态做Hilbert变换得到包络信号然后归一化计算包络信号的信息熵。故障信号通常包含周期性的冲击成分包络波形会有明显的稀疏性熵值就低反之噪声信号的包络比较杂乱熵值就高。所以“包络熵最小”就成了一个合理的目标。也有做法直接把峭度的负值作为适应度函数因为峭度越大说明冲击特征越明显。但这么做有个隐患——如果只追求峭度最大算法可能挑到那些包含单一强噪声尖峰的分量而不是真正的周期冲击。我在实际使用中更推荐用“局部包络熵最小”或者“包络熵与峭度的组合指标”这样鲁棒性更强。3.3 为什么选GWO而不是网格搜索或遗传算法参数寻优并非只能选GWO网格搜索、粒子群PSO、遗传算法GA也都有人用。但我自己的体感是GWO在这个问题上性价比最高。网格搜索在K只有十几个取值、alpha几个取值时可以暴力尝试几百种组合。但问题是每次VMD分解都要迭代几十上百次ADMM计算量并不小。全网格搜索一次可能要跑几十分钟而且网格粒度粗了容易漏掉最优区间粒度细了计算时间又翻倍。GWO一般设置15到20只狼、迭代20到30次就能以很小的计算成本找到接近全局最优的参数组合。PSO和GA当然也能用但PSO容易在迭代后期陷入局部最优收敛精度一般GA需要设置交叉概率、变异概率等一堆参数实现复杂一些。GWO代码简单几十行就能搞定、参数少只需要种群大小和最大迭代次数、收敛速度快每轮迭代只比较三只头狼位置的更新非常适合工程落地。3.4 完整优化流程描述整个GWO优化VMD的流程可以这样描述初始化灰狼种群每只狼代表一组K和alpha的候选值对每组参数执行VMD分解计算分解结果的平均包络熵作为适应度值根据适应度更新alpha、beta、delta狼的位置再更新其他狼的位置重复迭代直到达到最大迭代次数最后输出alpha狼的位置作为最优参数组合。这里还有一个工程细节值得注意一次VMD分解是对整个信号做的计算一次适应度就需要跑完一整遍VMD。所以如果种群20只狼、迭代30次等于要跑600次VMD信号长度长了计算量不容小觑。我的经验是先对信号做降采样或者截取一段代表性数据片段用于寻优确定最优参数后再对完整信号做分解。这样能把寻优时间缩短到原来的十分之一左右。4. 峭度筛选分量的逻辑与实操要点4.1 峭度为什么能当筛选指标峭度Kurtosis是描述信号分布形态的四阶统计量反映的是信号中冲击成分的强弱。高斯分布信号的峭度值等于3峭度大于3说明信号中存在比高斯分布更多的极端值——也就是尖峰脉冲。机械故障尤其是轴承早期故障产生的冲击信号会让振动波形中出现明显的周期性尖峰表现在统计特征上就是峭度值显著升高。这就像一群人走在路上正常随机分布的脚步声是均匀且杂乱的峭度不高但如果有人在队伍里每隔一段时间就用力跺一下脚这种周期性强冲击就会让整体的“尖峰程度”变大峭度就会飙升。滤波下来之后包含故障冲击的模态分量它的峭度会明显高于那些只有平稳噪声的分量。4.2 筛选阈值的确定方法有了峭度值怎么判断阈值最常用的做法是计算所有模态峭度的平均值和标准差设定一个动态阈值比如threshold mean(kurtosis) 1.5 * std(kurtosis)。超过阈值的分量被保留其余当作噪声舍弃。也可以采用更简单的排序法直接取峭度最大的前2到3个分量。这个方法适合故障特征比较明显的情况。还有一种做法是把峭度值和相关系数结合使用计算每个模态与原始信号的相关系数优先保留“峭度高且与原始信号相关性强”的分量这样能排除那些虽然峭度高但只是独立噪声尖峰的分量。4.3 一个容易被忽视的坑模态端点和过分解用VMD分解信号后直接计算峭度往往会在模态两端出现异常值。因为VMD在信号边界处拟合效果不稳定端部往往会有小幅抖动这些抖动在Hilbert包络中会被放大导致峭度出现虚高。我建议在计算峭度之前先把每个模态的首尾各截掉一部分比如10%只用中间稳定的信号段来计算这样指标更可靠。另外即使GWO优化了参数过分解仍然可能发生。这时候某些本质上属于同一物理成分的模态会被拆开各自单独算峭度时数值都比较高容易被重复保留。为了应对这种情况可以在筛选前加一步计算各模态之间的相关系数如果两个模态的相关系数超过0.7说明它们严重重叠需要合并或者只保留其中峭度更高的那个。4.4 筛选分量之后的信号重构筛选出高峭度分量后下一步通常是做包络谱分析把时域冲击通过Hilbert变换转换成包络信号再对包络信号做FFT找到故障特征频率。有些场景还需要把筛选出来的分量重构为一个信号重构后的信号再做后续分析信噪比会大幅提升故障频率在包络谱中的幅值会清晰得多。重构的方式有两种简单相加或者按峭度加权相加。我觉得在故障诊断场景下简单相加就已经足够。加权相加容易引入主观权重反而可能放大某些分量的噪声。如果目标是降噪则可以尝试用峭度作为权重做融合在某些降噪实测中效果也不错但要对比验证后再定不要盲目采用。5. 实操过程与代码级拆解5.1 信号准备与预处理我用一个简单的仿真信号来演示整个流程。信号包括三部分一个是10Hz的正弦分量模拟工频干扰一个是中心在60Hz的窄带随机噪声模拟背景噪声还有一个是周期为0.1s即10Hz重复频率的衰减冲击串模拟轴承故障冲击。import numpy as np from scipy.signal import hilbert import vmdpy fs 1000 # 采样率1000Hz t np.arange(0, 2, 1/fs) N len(t) # 工频正弦干扰 f1 10 sine_signal 1.5 * np.sin(2 * np.pi * f1 * t) # 窄带噪声通过调制产生近似窄带特性 np.random.seed(42) noise np.random.randn(N) narrow_noise noise * np.sin(2 * np.pi * 60 * t) # 周期冲击串 impact_interval 0.1 # 每0.1s一个冲击 impact_times np.arange(0.1, 2, impact_interval) impulse_signal np.zeros(N) for it in impact_times: idx int(np.where(t it)[0][0]) length int(0.03 * fs) idx_end min(idx length, N) t_local t[:idx_end - idx] decay np.exp(-50 * t_local) impulse_signal[idx:idx_end] decay * np.sin(2 * np.pi * 200 * t_local) signal sine_signal narrow_noise impulse_signal 0.2 * np.random.randn(N)这里把冲击的振荡频率设为200Hz衰减系数50冲击每0.1秒出现一次所以冲击对应的故障特征频率是10Hz每0.1s一次冲击换算成频率正好10Hz这个频率和工频干扰频率重合恰恰能检验VMD分解对同频率不同成分的区分能力。5.2 GWO优化VMD的完整代码实现接下来就是GWO寻优部分。我把关键步骤逐段说明。首先定义适应度函数。这里使用包络熵作为评价指标也可以换成峭度或组合指标def envelope_entropy(imf): analytic hilbert(imf) envelope np.abs(analytic) p envelope / np.sum(envelope) # 防止log(0) p p[p 0] entropy -np.sum(p * np.log(p)) return entropy def fitness_function(params, signal): K int(round(params[0])) alpha params[1] tau 0 DC 0 init 1 tol 1e-7 try: u, u_hat, omega vmdpy.VMD(signal, alpha, tau, K, DC, init, tol) except Exception: return 100 # 分解失败给一个很大的惩罚值 entropy_list [envelope_entropy(u[i, :]) for i in range(K)] return np.mean(entropy_list) # 取平均包络熵作为适应度包络熵的物理意义是信号包络的“确定性”程度。周期冲击信号的包络在每个冲击点处收敛尖锐整体上信息集中熵值小而噪声的包络随机起伏信息分散熵值大。所以最小化平均包络熵本质上就是在寻找一个让分解结果“最稀疏、最有冲击性”的参数组合。GWO的主体代码很短核心部分如下def gwo_optimize(signal, lb, ub, dim2, n_wolves15, max_iter25): # 初始化灰狼种群 positions np.random.uniform(lb, ub, (n_wolves, dim)) fitness np.array([fitness_function(p, signal) for p in positions]) alpha_pos positions[np.argmin(fitness)].copy() alpha_score fitness.min() beta_pos positions[np.argsort(fitness)[1]].copy() beta_score np.sort(fitness)[1] delta_pos positions[np.argsort(fitness)[2]].copy() delta_score np.sort(fitness)[2] for l in range(max_iter): a 2 - l * (2 / max_iter) # a从2线性递减到0 for i in range(n_wolves): for j in range(dim): # 对alpha、beta、delta这三只头狼的位置 r1 np.random.random() r2 np.random.random() A1 2 * a * r1 - a C1 2 * r2 D_alpha abs(C1 * alpha_pos[j] - positions[i, j]) X1 alpha_pos[j] - A1 * D_alpha r1 np.random.random() r2 np.random.random() A2 2 * a * r1 - a C2 2 * r2 D_beta abs(C2 * beta_pos[j] - positions[i, j]) X2 beta_pos[j] - A2 * D_beta r1 np.random.random() r2 np.random.random() A3 2 * a * r1 - a C3 2 * r2 D_delta abs(C3 * delta_pos[j] - positions[i, j]) X3 delta_pos[j] - A3 * D_delta positions[i, j] (X1 X2 X3) / 3 # 边界处理 positions[i] np.clip(positions[i], lb, ub) # 更新适应度 fit_i fitness_function(positions[i], signal) if fit_i alpha_score: delta_pos beta_pos.copy() delta_score beta_score beta_pos alpha_pos.copy() beta_score alpha_score alpha_pos positions[i].copy() alpha_score fit_i elif fit_i beta_score: delta_pos beta_pos.copy() delta_score beta_score beta_pos positions[i].copy() beta_score fit_i elif fit_i delta_score: delta_pos positions[i].copy() delta_score fit_i return alpha_pos, alpha_score lb np.array([2, 200]) ub np.array([15, 3000]) best_params, best_score gwo_optimize(signal, lb, ub) print(最优参数K , int(round(best_params[0])), , alpha , round(best_params[1], 2))注意几个实现细节。第一K的边界要设好2到15是经验值范围太小了分不干净太大了计算量暴涨且出现过分解第二alpha的边界范围也要合理我在很多实验中发现alpha在200到3000之间基本覆盖了常用场景太小了模态带宽很宽分不出来太大了迭代收敛很慢第三边界处理直接用np.clip简单有效。我实际跑这段代码时种群15只狼、迭代25次总共375次VMD分解对一段2秒的信号2000个采样点来说耗时大约十几秒到几十秒取决于电脑性能和VMD内部迭代次数。这个成本完全在可接受范围内。5.3 分解与峭度筛选的实现细节得到最优参数后用这些参数对完整信号做VMD分解然后计算每个模态的峭度并筛选from scipy.stats import kurtosis u, u_hat, omega vmdpy.VMD(signal, best_params[1], 0, int(round(best_params[0])), 0, 1, 1e-7) # 计算每个分量的峭度截掉两端10% trim_ratio 0.1 kurt_values [] for i in range(u.shape[0]): imf u[i, :] start_idx int(N * trim_ratio) end_idx int(N * (1 - trim_ratio)) imf_trimmed imf[start_idx:end_idx] kurt_values.append(kurtosis(imf_trimmed)) print(各分量峭度值, np.round(kurt_values, 3)) # 动态阈值筛选 mean_kurt np.mean(kurt_values) std_kurt np.std(kurt_values) threshold mean_kurt 1.5 * std_kurt selected_idx [i for i, k in enumerate(kurt_values) if k threshold] print(阈值, round(threshold, 3), 筛选出的分量序号, selected_idx)用仿真信号跑下来一般情况下峭度值最大的分量就是那个包含周期冲击的分量它的峭度通常在5到15之间而噪声分量的峭度在3左右。这个差距是非常显著的筛选阈值很容易就能把故障分量挑出来。有了筛选结果后再做包络谱分析def envelope_spectrum(imf, fs): analytic hilbert(imf) envelope np.abs(analytic) # 去除直流 envelope envelope - np.mean(envelope) spectrum np.fft.fft(envelope) freqs np.fft.fftfreq(len(envelope), 1/fs) half len(freqs) // 2 return freqs[:half], np.abs(spectrum[:half]) freqs, spec envelope_spectrum(u[selected_idx[0]], fs) # 在0~50Hz范围内找峰值对应10Hz故障特征频率包络谱里会在10Hz附近出现明显的谱峰这就是冲击的重复频率对应故障特征频率。加上单边谱分析可以进一步确认边带的间隔就是故障特征频率。6. 常见问题与排查技巧实录6.1 参数寻优结果每次都不一样GWO是随机初始化种群所以每次运行得到的最优参数可能略有差异这是个正常现象。如果K的值每次跳变很大说明适应度函数在参数空间里存在多个相近的局部最优解K的取值不够稳定。我的建议是设置固定的随机种子保证结果可复现。即使种子变了只要最终的分解效果和故障频率识别结果稳定就不需要纠结参数的细微差异。从工程应用的角度看我们关心的是结果是否可靠而不是参数是否唯一。6.2 VMD分解后某个分量中心和原始特征频率对不上这种情况常见于alpha设置过大把真实模态的带宽压得太窄导致中心频率偏移。可以适当缩小alpha的上限或者在GWO迭代结束后手动微调一下K附近的值观察中心频率的变化。如果K8和K9分解出来的中心频率差异巨大说明信号本身的频带结构不太明确建议检查一下原始信号是否有明显的趋势项或直流偏置先做去趋势处理再分解。6.3 GWO迭代过程中适应度下降很慢如果发现适应度曲线在前几次迭代就基本不变化了可能有两种原因一是种群过早收敛狼群位置都挤在了一个小区域二是在某个参数组合下VMD分解结果对参数变化不敏感导致适应度梯度很平。针对前者可以调大a的初值比如从2.5开始递减或者增大种群规模针对后者可以考虑更换适应度函数比如使用包络熵和峭度的组合指标。我用过一个组合指标效果比单用包络熵更稳定fitness entropy_mean - 0.3 * max(kurtosis_values)在包络熵最小的基础上再奖励高峭度分量的出现这样能同时兼顾“整体稀疏”和“存在明显冲击”。6.4 遇到非常长的信号优化速度太慢怎么办GWO每评估一次适应度都要跑一遍VMD信号越长ADMM迭代内部的计算量越大。如果你有一整段几分钟的振动信号直接拿全量数据去优化会非常慢。我的做法是“先截段寻优再全量分解”。具体来说先从原始信号中截取一小段包含故障冲击的典型片段比如2到5秒用这段数据跑GWO寻优得到K和alpha之后再在完整信号上执行VMD分解。这样做能节省大量时间而且由于VMD参数主要取决于信号的频带结构而不是绝对长度截段优化得到的参数在完整信号上依然适用。6.5 峭度筛选得到的分量不止一个怎么取舍实际数据中这种情况非常常见。当有两个分量的峭度都超过阈值时先别急着都保留先看一眼它们的中心频率是否接近如果接近很可能是因为K偏大造成了过分解把同一个物理成分拆成了两个。此时可以尝试把K减1重新优化看两个分量是否合并成了一个。如果两个分量的频率相差较远那它们可能分别对应不同位置的多个故障源。比如轴承内圈和外圈同时出现故障它们的特征频率不同VMD可能把它们分到不同的模态里此时两个分量都应该保留分别做包络谱分析。6.6 关于VMD工具箱和代码实现的补充说明我用的是vmdpy这个Python库接口简单函数原型如下u, u_hat, omega vmdpy.VMD(signal, alpha, tau, K, DC, init, tol)signal输入信号1维数组。alpha惩罚因子。tau噪声容忍度设为0表示严格约束。K模态数。DC是否将第一个模态作为直流分量通常设为0。init初始化方式1表示初始中心频率均匀分布。tol收敛容忍度一般1e-7已经够用。返回的u是K×N的二维矩阵每一行是一个模态分量omega是每个模态的中心频率。如果你用MATLAB官方也有VMD的代码接口更简单。核心思路完全一致把GWO和峭度筛选的流程移植过去就行。7. 完整流程的经验总结与扩展方向整套方案从GWO优化VMD参数到峭度筛选分量再到包络谱分析是一条非常完整的信号处理链路。我做过的实测数据里滚动轴承内外圈故障、齿轮局部断齿、转轴碰磨等场景这套方法都有良好的识别效果尤其在早期微弱故障的诊断上表现突出。其中最有价值的经验我总结下来有三点。第一适应度函数的选择很大程度上决定了寻优的上限不要迷信单一的包络熵或峭度组合指标在复杂信号上更稳。第二峭度筛选时一定不要忽略模态端点截断和模态间相关性检查“高峭度”不直接等于“有故障”要排除伪分量。第三工程落地时建议加入时序处理——把信号分帧、逐帧寻优分解、统计各帧筛选结果这样能提高整体的抗干扰能力。这套方法后续还可以往几个方向扩展。比如用二维VMD处理时频图像或者把GWO换成多目标灰狼优化算法同时优化多个评价指标还能结合深度神经网络做自动故障分类。不管怎么扩展核心思路都是一样的用优化手段解决参数选择问题用统计指标筛选有效分量最后让物理量说话。我在实际项目中最大的体会是信号处理工具并不怕“老”怕的是“生硬套用”。GWO和VMD都是成熟方法把它们合理地串起来、加一层峭度筛选就能解决很多实际诊断问题。希望这篇里的代码和避坑经验能让你少走弯路。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进