☰
VMD故障特征提取复现:从文献到Python工程链路
2026/10/3 2:43:42 网站建设 项目流程

简介:这份资源面向具备一定信号处理基础与MATLAB编程能力的故障诊断学习者,围绕《基于VMD的故障特征信号提取方法》一文提供可运行的复现代码,帮助读者理解变分模态分解如何将非平稳振动信号拆解为频率局部化的模态分量,从而在噪声中分离出故障特征。压缩包共4个文件,均为m脚本,整体约5KB,其中核心算法、主流程调用、性能指标计算与频谱分析等环节分别由不同脚本承担,结构紧凑、便于逐段阅读与修改。资源不涉及轴承实验数据复现,重点落在算法实现与结果可视化上,读者可借此掌握分解流程、参数调整与特征提取的完整思路,并对照文献验证方法正确性。目前已有731人学习下载,适合希望快速上手VMD降噪与特征提取实践的研究生及工程技术人员参考。

1. 复现《基于VMD的故障特征信号提取方法》:从一篇文献到一个能跑通的信号处理链路

轴承故障、齿轮箱故障、电机转子断条,这些旋转机械的早期故障特征往往淹没在强噪声和工频干扰里。你手里有一段振动加速度信号,采样率 12.8 kHz,采样 2 秒,时域波形看上去就是一团毛刺,FFT 频谱上除了转频和倍频,几乎看不出故障冲击的周期成分。这时候很多人会想到那篇被引用了无数次的文献——《基于VMD的故障特征信号提取方法》。VMD,变分模态分解,2014 年 Dragomiretskiy 和 Zosso 提出的自适应信号分解方法,核心思路是把信号分解问题写成一个变分约束问题,通过交替方向乘子法迭代求解,把原始信号拆成若干个中心频率不同、带宽受限的模态分量。和 EMD 相比,VMD 没有模态混叠和端点效应那么玄学,分解层数 K 和惩罚因子 α 是你要拍板的关键参数。复现这篇文献,不是把公式抄一遍,而是要把“信号怎么来、参数怎么定、模态怎么选、特征怎么提”这条链路跑通。适合手里有振动数据、想做故障诊断特征工程、又不想在 EMD 的模态混叠里反复翻车的工程师。

2. VMD 的数学骨架与复现前的选型判断

2.1 变分约束怎么变成可迭代的求解步骤

VMD 把信号分解成 K 个模态分量 u_k(t),每个模态围绕一个中心频率 ω_k 振荡。构造的变分问题是:所有模态的解析信号经过希尔伯特变换后,乘以 e^{-jω_k t} 搬移到基带,再求 L2 范数的平方和,约束条件是所有模态之和等于原始信号。为了把这个约束问题变成无约束问题,引入二次惩罚因子 α 和拉格朗日乘子 λ。α 控制模态带宽,α 越大,模态带宽越窄,模态之间越不容易混叠;α 越小,模态带宽越宽,可能把多个频率成分装进同一个模态。

迭代求解用 ADMM,每一步更新 u_k、ω_k、λ。u_k 的更新在频域做,公式是:

u_k^{n+1}(ω) = (f(ω) - Σ_{i≠k} u_i(ω) + λ(ω)/2) / (1 + 2α(ω - ω_k)^2)

ω_k 的更新是模态功率谱重心:

ω_k^{n+1} = ∫_0^∞ ω |u_k(ω)|^2 dω / ∫_0^∞ |u_k(ω)|^2 dω

λ 的更新是梯度上升:

λ^{n+1}(ω) = λ^n(ω) + τ(f(ω) - Σ_k u_k^{n+1}(ω))

收敛判据是 Σ_k ||u_k^{n+1} - u_k^n||_2^2 / ||u_k^n||_2^2 < ε。实际写代码时,你不需要手推这些公式,但必须知道每个参数在迭代里起什么作用,否则调参就是盲人摸象。

2.2 为什么选 VMD 而不是 EMD 或小波

EMD 的问题在于模态混叠和端点效应。同一个信号,两端补零和补镜像,分解出来的 IMF 可能不一样,这对故障特征提取是致命的,因为你要的是可重复的周期冲击。小波变换需要选基函数和分解层数,基函数选错,故障冲击就被平滑掉了。VMD 的优势是频域求解,中心频率初始化后,模态在频域上自动分离,端点效应比 EMD 小得多。但 VMD 不是没有代价:K 值需要预设,K 太小,故障特征和背景噪声混在一个模态里;K 太大,同一个故障冲击被拆到两个模态,中心频率接近,反而不好选。常见做法是 K 从 3 开始试,看中心频率分布,如果两个模态中心频率差小于转频的 20%,就说明 K 偏大。

2.3 复现前要确认的数据格式和采样参数

文献里的方法通常假设输入是单通道振动信号,采样率已知,故障特征频率可计算。复现前先确认三件事:采样率是否满足奈奎斯特,故障冲击的带宽是否在分析范围内;信号长度是否足够,VMD 对短信号分解不稳定,一般建议至少 2048 点;有没有转速信号,如果没有,转频要从频谱里估。我一般会先画时域波形和 FFT 频谱,确认故障特征频率的大致位置,再决定 K 和 α 的搜索范围。

3. 用 Python 跑通 VMD 分解的最小代码链路

3.1 安装 vmdpy 并加载振动信号

Python 里现成的 VMD 实现不多,vmdpy 是一个轻量包,直接 pip 安装。如果你不想装包,也可以把文献里的伪代码翻译成 numpy,但 vmdpy 的接口更接近工程习惯。

pip install vmdpy numpy scipy matplotlib

加载数据时,注意信号要转成 float64,去掉直流分量。直流分量会让第一个模态中心频率跑到 0,影响后续模态选择。

import numpy as np from vmdpy import VMD import matplotlib.pyplot as plt # 假设 data 是 N×1 的振动信号,采样率 fs fs = 12800 data = np.loadtxt('bearing_vibration.txt') signal = data[:, 1].astype(np.float64) signal = signal - np.mean(signal) # 去直流 N = len(signal) t = np.arange(N) / fs

这段代码做了三件事:读取文本格式的振动数据,取第二列作为信号,去均值。参数 fs 必须和采集系统一致,否则后续故障特征频率对不上。如果数据是 CSV 或 MAT 格式,用 pandas 或 scipy.io.loadmat 替换 loadtxt 即可。

3.2 设置 K、alpha、tau 和初始化方式

VMD 函数的签名是 VMD(signal, alpha, tau, K, DC, init, tol)。每个参数的含义和常用取值如下:

参数含义常用取值影响
alpha带宽约束2000越大带宽越窄,模态越独立
tau对偶上升步长00 表示无噪声容忍,通常设 0
K模态数3~8根据中心频率分布调整
DC是否包含直流0去直流后设 0
init中心频率初始化11 表示均匀初始化
tol收敛容差1e-7越小迭代越久
alpha = 2000 tau = 0 K = 5 DC = 0 init = 1 tol = 1e-7 u, u_hat, omega = VMD(signal, alpha, tau, K, DC, init, tol)

u 是分解后的模态矩阵,形状 K×N,每一行是一个模态的时域波形。u_hat 是频域模态,omega 是每次迭代的中心频率记录,最后一列是最终中心频率。K=5 是轴承故障诊断里比较常用的起点,alpha=2000 对应带宽约 500 Hz,适合中高频故障冲击。

3.3 画中心频率分布图判断 K 是否合理

分解完不要急着做包络谱,先看中心频率。如果两个模态的中心频率太近,说明 K 偏大;如果最后一个模态中心频率还在故障特征频带内,说明 K 偏小。

plt.figure() plt.plot(omega[:, -1], 'o-') plt.xlabel('模态序号') plt.ylabel('中心频率 (Hz)') plt.title('VMD 中心频率分布') plt.grid(True) plt.show() # 打印最终中心频率 print('最终中心频率:', omega[:, -1])

如果中心频率从低到高排列,且相邻差值比较均匀,说明 K 选得合适。如果出现两个模态中心频率几乎重合,把 K 减 1 再试。如果最高中心频率离故障特征频率还有距离,把 K 加 1。这个判断过程比看重构误差更直接,因为故障特征提取关心的是模态是否覆盖了故障频带,而不是整体重构精度。

4. 从模态到故障特征:包络谱与特征频率对齐

4.1 选哪个模态做包络分析

VMD 分解出 K 个模态后,不是每个模态都包含故障特征。轴承故障冲击会激发高频共振,故障特征频率出现在共振频带的包络里。所以你要选中心频率在高频段、且时域波形有明显周期冲击的模态。常见做法是计算每个模态的峭度,峭度最大的模态通常对应故障冲击。

from scipy.stats import kurtosis kurt_values = [] for i in range(K): kurt_values.append(kurtosis(u[i, :], fisher=True)) best_mode = np.argmax(kurt_values) print('峭度值:', kurt_values) print('选中的模态:', best_mode)

峭度对冲击敏感,正常轴承振动接近高斯分布,峭度约 0;故障冲击让峭度增大。但峭度不是唯一标准,如果两个模态峭度接近,选中心频率更接近共振频带的那个。

4.2 包络谱计算与故障特征频率标注

选好模态后,做希尔伯特变换取包络,再对包络做 FFT,看故障特征频率及其倍频。

from scipy.signal import hilbert mode = u[best_mode, :] envelope = np.abs(hilbert(mode)) envelope = envelope - np.mean(envelope) n = len(envelope) freq = np.fft.rfftfreq(n, 1/fs) envelope_spectrum = np.abs(np.fft.rfft(envelope)) / n plt.figure() plt.plot(freq, envelope_spectrum) plt.xlabel('频率 (Hz)') plt.ylabel('幅值') plt.title('包络谱') plt.xlim(0, 500) plt.grid(True) plt.show()

包络谱上,如果在外圈故障特征频率 BPFO 及其 2 倍频、3 倍频处出现峰值,说明故障特征被成功提取。BPFO 的计算公式是:

BPFO = (n/2) × fr × (1 - (d/D) × cosθ)

其中 n 是滚动体个数,fr 是转频,d 是滚动体直径,D 是节圆直径,θ 是接触角。这些参数从轴承型号手册里查。如果包络谱峰值和 BPFO 对不上,先检查转频估得准不准,再检查模态选得对不对。

4.3 用重构信号验证分解是否丢特征

VMD 的约束是所有模态之和等于原信号,但迭代收敛后会有微小残差。你可以把选中的模态和相邻模态相加,看重构信号在故障频带的能量是否保留。

reconstructed = np.sum(u, axis=0) residual = signal - reconstructed print('残差能量占比:', np.sum(residual**2) / np.sum(signal**2))

残差能量占比一般小于 1%。如果残差很大,说明迭代没收敛,把 tol 调小或者增加迭代次数。如果残差正常但包络谱没峰值,问题出在模态选择或传感器安装方向,不是 VMD 本身。

5. 复现 VMD 故障特征提取时最容易翻车的五个地方

5.1 现象:分解出的模态中心频率全挤在低频,高频段没有模态

原因:alpha 设得太大,带宽约束过强,高频模态被抑制;或者 K 太小,高频故障频带没有被单独分出来。解决:先把 alpha 降到 1000 以下试,再把 K 加到 6 或 7,观察中心频率是否往高频扩展。如果还是不行,检查信号里高频成分是否被低通滤波掉了。

5.2 现象:包络谱上故障特征频率处没有峰值,反而在转频处有大峰值

原因:选错了模态。峭度最大的模态不一定包含故障特征,可能只是转频调制。解决:不要只看峭度,把每个模态的包络谱都画出来,找 BPFO 处有峰值的那个。如果所有模态都没有,检查传感器是不是装在径向,故障冲击是否被衰减。

5.3 现象:同一段信号,两次运行 VMD 结果不一样

原因:中心频率初始化方式不同,或者信号长度不是 2 的整数次幂,FFT 补零导致频域分辨率变化。解决:固定 init=1,信号长度截取到 2 的整数次幂,比如 8192 点。如果还不行,检查 numpy 版本,不同版本的 FFT 实现可能有微小差异。

5.4 现象:K 增大到 8 以上,分解时间急剧增加,模态中心频率出现重合

原因:K 过大导致模态分裂,同一个频率成分被拆到两个模态,ADMM 迭代在两者之间振荡。解决:K 不要超过 8,一般 4 到 6 足够。如果故障特征频带很宽,优先调 alpha,而不是加 K。

5.5 现象:包络谱峰值和理论 BPFO 差几十赫兹

原因:转频估不准。转频通常从频谱里找最大峰值,但电机滑差、皮带打滑都会让实际转频偏移。解决:用转速计信号,或者从时域波形里数周期冲击间隔,反推转频。如果转频误差超过 2%,BPFO 的倍频对不上,特征提取就失去意义。

6. 把 VMD 嵌进在线监测链路:参数固化与批量验证

复现文献的终点不是跑通一段代码,而是把参数固化下来,能对同型号设备批量处理。我一般会做三件事:第一,用正常状态数据确定 K 和 alpha 的基线,正常数据分解后中心频率分布稳定,故障数据才会出现新的高频模态;第二,把峭度、包络谱峰值、BPFO 处信噪比三个指标写成函数,批量跑历史数据,看哪个指标对早期故障最敏感;第三,把 VMD 分解和包络谱计算封装成类,输入原始信号和轴承参数,输出故障特征频率处的幅值。

class VMDEnvelopeExtractor: def __init__(self, fs, alpha=2000, K=5, tol=1e-7): self.fs = fs self.alpha = alpha self.K = K self.tol = tol def extract(self, signal, bpfo): signal = signal - np.mean(signal) u, _, omega = VMD(signal, self.alpha, 0, self.K, 0, 1, self.tol) kurt = [kurtosis(u[i], fisher=True) for i in range(self.K)] best = np.argmax(kurt) envelope = np.abs(hilbert(u[best])) envelope = envelope - np.mean(envelope) n = len(envelope) freq = np.fft.rfftfreq(n, 1/self.fs) spectrum = np.abs(np.fft.rfft(envelope)) / n idx = np.argmin(np.abs(freq - bpfo)) return spectrum[idx], omega[:, -1], best

这个类把分解、模态选择、包络谱计算串起来,返回 BPFO 处的幅值、中心频率分布和选中的模态序号。批量跑的时候,如果某个样本的 BPFO 幅值突然增大,同时中心频率分布出现新的高频模态,就触发报警。参数固化后,不要频繁改 K 和 alpha,否则报警阈值失去意义。如果设备工况变化大,比如变转速,VMD 之前要先做阶次跟踪,把时域信号转成角域信号,否则故障特征频率会 smear。

最后说一个我自己的习惯:每次复现一篇文献,我都会先用仿真信号验证代码正确性,再上实测数据。仿真信号用两个正弦加一个周期冲击,冲击间隔对应 BPFO,加高斯白噪声。如果 VMD 能把冲击模态分出来,包络谱峰值在 BPFO 处,说明代码没问题。实测数据翻车,多半是传感器、转速或轴承参数的问题,不是算法的问题。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询