当初在一台离心泵的轴承座上采集振动数据,时域波形像一坨毛线,包络谱里只有转频和几根看不出名堂的谱线。拆检之后才发现外圈已经有一道明显的剥落。问题出在哪?不是传感器装错了,而是处理思路不对——传统频谱分析对瞬态冲击天然不友好,轴承故障产生的那种“哒哒哒”的周期冲击,能量被摊在了几千赫兹的宽带范围里,单根谱线幅值很小,很容易被噪声淹没。
后来我把思路从“频域滤波”换成了“稀疏表示”,用学习字典做原子匹配、用加权稀疏系数做成分筛选,重新处理同一段数据,故障特征频率在包络谱里直接就立起来了。这篇文章把我踩过的坑、每个环节为什么这样设计、参数怎么调、代码怎么组织,完整拆开讲一遍。适合正在做振动信号处理、设备故障诊断方向,或者刚接触稀疏表示但不知道怎么落地到轴承场景的读者。
1. 为什么故障冲击在数据里“看不见”:信号模型与稀疏表示的基本逻辑
1.1 轴承故障信号的构成:冲击、噪声、转频三股势力的角力
要搞清楚稀疏表示为什么能解决轴承诊断问题,先要看清振动信号里到底有什么。传感器贴在轴承座上采集到的信号,基本可以拆成三个部分:
- 周期性故障冲击成分 s(t):滚动体滚过缺陷时产生的瞬态冲击,周期由转速和轴承几何参数决定,是我们要找的东西;
- 确定性周期成分 c(t):转频及其谐波、齿轮啮合频率、皮带通过频率等,它们严格同步于旋转周期;
- 噪声与随机干扰 n(t):环境振动、流体噪声、随机脉冲、传感器电噪声等。
轴承故障冲击在时域上只占据很短的持续时间,占空比很低,大部分时间段其实是“安静”的。这种信号在频域里很难看——冲击包含大量频率成分,能量弥散到几千赫兹的带宽中,单根FFT谱线的幅值往往低于噪声底。而传统做法是先用带通滤波器选出共振带,再做包络解调,这本身体现了一个思想:故障信息不是表现在某几条特征谱线上,而是集中在一个瞬态波形里。
稀疏表示的底层假设恰好和这个物理事实吻合。它认为信号可以被一组“原子”的少量叠加逼近,绝大多数系数接近零。故障冲击是瞬态的、局域的、间歇的,正是一种高度稀疏的信号。我们只要让算法从一堆候选波形里挑出最像冲击的那几个原子,就可以把故障成分从背景噪声中单独剥离出来。
1.2 稀疏表示为什么能“挑”出故障冲击:一个抓壮丁的比喻
可以把稀疏表示理解成抓壮丁。字典 D 就是一份候选名单,里面有正弦波、脉冲、衰减振荡等各种波形的原子。我们手里的振动信号 x 是最终需要凑齐的人头数。普通的最小二乘求解,会让每个人都出一份力,平均分摊误差,结果是谁都不像、谁都有份;而稀疏求解要求尽量用最少的壮丁凑齐目标,剩下的误差再大也不用每个人去补。
匹配追踪类的算法本质就是贪心选择:第一轮挑与残差相关性最强的原子,第二轮在剔除它贡献的信号以后继续挑下一个。故障冲击因为是突发的瞬态波形,总有一个原子与它的相似度极高,第一轮就能被选中。而噪声和转频成分没有这种“一击命中”的特性,要么是频谱摊得太开,要么是波形太平缓,难以用单个原子高效表示。对轴承诊断而言,这个过程有点像“定向抓取”——把冲击波形直接从混合信号里抠出来。
但要说明一个关键前提:稀疏表示只在字典与信号高度匹配的时候才有效。如果字典里的原子都是纯正弦波,想精确表示一个带衰减的窄带冲击,需要十几个原子一起堆,稀疏性就无从谈起。这也是为什么后面要引入学习字典。
1.3 为什么光靠稀疏还不够:普通稀疏的三大软肋
我最初直接用标准OMP配合DCT字典处理数据,效果有一部分,但离“能用”还差得远。普通稀疏求解在本场景下有三个明显软肋:
- 第一,对噪声不设防。L1范数的凸松弛虽然在数学上保证了稀疏解,但实际噪声里总有些片段的能量比较高,会被算法当成“有用成分”选进字典系数里。尤其在低信噪比下,稀疏解会逐渐退化成对噪声的过拟合。
- 第二,对随机冲击没有辨识能力。现场振动信号里经常有随机碰磨、流体脉动造成的非周期冲击,它们在时域上和故障冲击长得几乎一样。普通稀疏法没办法区分“有规律的冲击串”和“随机出现的单次冲击”,结果会把两者一起重构出来,包络谱上多出一堆假谱线。
- 第三,对故障冲击的幅度变化不敏感。实际故障从萌生到发展到后期,冲击幅值忽大忽小,部分小幅值冲击含有重要的早期损伤信息。统一惩罚所有系数会让算法倾向于保留大幅度成分,把早期微弱冲击直接当成噪声丢掉。
这三点让我意识到,单纯的稀疏表示不是终点,需要给每个系数额外“加权重”——让算法知道哪些成分应该优先保留,哪些成分应该重点压制。
2. 加权稀疏是怎么“加权”的:两类权重来源与具体设计规则
2.1 权重从哪来:迭代重加权与物理先验两条路线
加权稀疏的核心是把原来求解 min ||α||₁ 的问题改造成 min ||w⊙α||₁,其中 w 是一个与系数一一对应的权重向量。权重大的系数被压得更狠,权重小的系数更容易被保留。关键问题在于:权重 w 怎么设定。
我实际用过两条路线,各有适用场景。
第一条是迭代重加权(Iterative Reweighted L1)。先跑一次普通稀疏求解,得到初始系数 α⁽⁰⁾,然后按公式 wᵢ = 1/(|αᵢ| + ε₀) 更新权重。上一轮系数越大的位置,权重就被调得越小,下一轮这些成分被保留的代价更低;上一轮基本为零的位置,权重约等于 1/ε₀,数值很大,下一轮基本不会被选进去。这样反复迭代三到五次,能量会越来越集中到少数几个真正的冲击分量上。ε₀ 是个防除零的小常数,太小会数值不稳,太大则权重区分度不足。
第二条是物理先验加权。滚动轴承的故障特征频率可以按转速和几何参数算出来,比如外圈故障特征频率 BPFO、内圈 BPFI、滚动体 BSG、保持架 FTF。既然知道了故障冲击应该出现在哪些频率位置,在字典设计时就把这些频率对应原子的权重降低,让算法在相同的惩罚代价下优先选择它们。更进一步的变体是“谐波增强”,不仅对基频原子降权,对二倍频、三倍频、分数谐波附近一小段频带内的原子也做不同程度的降权。
2.2 两类权重怎么结合:先物理后数据的两阶段策略
两条路线不是二选一,我推荐两阶段结合。第一阶段先用物理先验加权跑一次稀疏分解,得到初步系数,目的不是直接出结果,而是得到一个“大致在故障频率上有响应”的基准解;第二阶段用迭代重加权,把上一轮的系数分布作为权重更新的起点。这样既有物理约束兜底——如果转频计算不准或轴承型号搞错了,物理先验会将错就错,数据分析权重能拉回来一点;又有数据自适应能力——真实故障会有许多非理想的边带和谐波,单纯靠先验设定覆盖不全。
权重向量还需要定期归一化。如果不归一化,w 的尺度直接乘到系数上,实际效果等价于改变了正则化强度 λ,会让算法要么过度稀疏、要么过度放松。我的做法是每轮迭代后让 w 的均值保持在 1 附近,这样 λ 的语义始终一致,调参时不会因为权重尺度变化而反复试错。
2.3 加权对求解流程的影响:一个容易被忽略的收敛问题
加权稀疏不是简单地把 OMP 的停止条件改一改就行。标准 OMP 每轮选择与残差相关性最强的原子,这个逻辑本身不包含权重。要在 OMP 里融入权重,常见做法是把问题改造成加权形式:把原信号 x 和字典 D 都乘上权重矩阵的平方根,然后用标准 OMP 求解变换后的问题,最后再把系数映射回去。具体过程是:
- 设 W = diag(√w);
- 求解 min ||x̃ - D̃α||₂² + λ||α||₁,其中 x̃ = Wx,D̃ = WD;
- 得到系数 α 后,真正对应的原子权重已经内化到变换里了。
实测下来这个处理方式稳定,不会破坏 OMP 的选原子逻辑。但我要强调一个收敛上的细节:迭代重加权的第 2 轮起,如果字典 D̃ 的条件数变差,OMP 选原子时会出现相邻轮次来回切换同一个原子组的情况。此时不要盲目加大迭代次数,建议每轮迭代后计算重构信号与原始信号的信噪比,连续两轮信噪比提升不足 0.5dB 就停。跑过一组对比,固定迭代 20 轮和自适应终止的结果几乎一样,但自适应终止省了近一半计算时间。
3. 学习字典:从“选模板”到“学模板”的关键一步
3.1 解析字典 vs 学习字典:为什么正弦基在这里吃亏
前面说 DCT 字典表示冲击需要堆很多原子,根源在于解析字典是“通用模板”,不是为轴承冲击设计的。DCT 的原子是不同频率的余弦波,本质假设是信号由平稳周期成分组成;小波包字典稍好一些,能描述瞬态,但小波基的形状是固定的小波函数,和真实故障冲击的衰减振荡形态不一定吻合。
学习字典的思路完全不同:直接从训练数据里学出一组原子,让原子本身逼近待处理信号的典型形态。对轴承故障来说,训练样本是从振动信号里切出来的一段段时域波形,其中包含大量正常振动、噪声和故障冲击的混合体。学出来的字典原子,往往直接就是一个带指数衰减的振荡波形——这不就是故障冲击的天然模板吗?
使用学习字典还有另一层好处:原子形态与故障机制相关。我经常把训练好的字典原子画出来看,外圈故障训练出来的原子通常集中在某个高频共振带附近,而内圈故障多了明显的调制包络形状。这等于在诊断前先白拿了一层特征可视化信息。
3.2 K-SVD 的基本原理与实现要点
K-SVD 是目前最常用的字典学习方法,名字拆开看:K 指字典原子个数,SVD 指用奇异值分解更新字典。整个流程是两阶段交替优化:
- 稀疏编码阶段:固定当前字典,用 OMP 对每个训练样本求解稀疏系数;
- 字典更新阶段:逐个原子更新。更新第 j 个原子时,找出所有用到了这个原子的样本,计算在这些样本上的残差,对残差矩阵做 SVD 分解,用最大的左奇异向量作为新原子、对应奇异值更新系数。
这个“逐个更新”的设计非常巧妙。它保证每次更新都针对某个原子的专属残差,不会像同时更新所有原子那样互相干扰。实际实现里需要特别留两个心:一是如果一个原子长期没有被任何样本选中,要重新初始化,否则字典里会堆积一堆“死原子”;二是初始化方式不要用随机矩阵,直接从原始信号里随机抽几个训练样本作为初始原子,收敛速度会快很多。
3.3 分帧、标准化、原子尺度:三个容易翻车的细节
学习字典不像普通机器学习那样把特征喂进去就行,信号处理里的预处理直接决定字典学到的是“故障特征”还是“噪声特征”。
第一个细节是分帧。轴承信号很长,不能整段丢进 K-SVD。需要按原子长度 N 切帧,相邻帧之间有 50% 到 75% 的重叠。重叠率太低会丢掉冲击的相位关系,重叠率太高则训练样本冗余度大,计算量成倍上升,收益不明显。我常用 50% 重叠,N 取 128 到 512 个采样点。帧长如果小于冲击衰减时间的一半,原子学出来的是“残缺波形”;大于冲击间隔,原子会包含两三个冲击,反而模糊了单个冲击的形态。
第二个细节是标准化。每帧必须先减掉均值,再做功率归一化。帧不去均值,字典里会学出直流分量原子,完全没有物理意义;不做归一化,大幅值帧会主导字典更新,小幅值冲击帧的形态信息被压制。
第三个细节是原子尺度要与包络解调兼容。原子长度直接决定了后续重构信号能分辨的频率精度。假设采样率 25.6kHz,原子长度 256,那么单个原子的有效频带宽度约 100Hz。如果故障特征频率的边带间隔小于这个值,重构后边带会被抹平。我一般会让原子长度对应的时间至少覆盖3个冲击周期长度,保证频域分辨率够用。
4. 从数据到结论:完整诊断流程与代码结构
4.1 全流程的六个环节
把上述方法串起来,一个完整可用的诊断流程如下:
- 数据采集与窥窗检查。确认采样率、采集时长、转速信息,剔除明显断线、削顶、直流偏置异常的数据段;
- 分帧与标准化。按原子长度切帧,去均值、归一化功率,生成训练样本集;
- 字典学习。用 K-SVD 学出字典,原子个数按经验公式配置(见下文参数表);
- 加权稀疏编码。结合物理先验初值和迭代重加权策略,对整段信号做稀疏分解;
- 重构故障冲击成分。将加权筛选后的系数与原子的线性组合重建为纯净冲击信号;
- 包络谱分析与故障定位。对重构信号做 Hilbert 变换取包络,再计算 FFT,在包络谱中搜索 BPFO、BPFI、BSF、FTF 及其谐波。
第六步不是可有可无的验证,而是真正的诊断结论输出。稀疏表示本身只是把故障成分从混合信号里分离出来,判断“轴承到底坏没坏、坏在哪个元件”,最终还是要靠包络谱里的特征频率与理论计算值对照。
4.2 Python 代码骨架与核心参数
我用的是 Python 实现,核心三块:字典学习类、加权稀疏编码函数、包络谱提取函数。骨架如下:
import numpy as np from scipy import signal def standardize(frames): # 去均值 + 功率归一化 frames = frames - frames.mean(axis=1, keepdims=True) norm = np.linalg.norm(frames, axis=1, keepdims=True) return frames / np.maximum(norm, 1e-12) def ksvd_learn(frames, m=128, k=8, iters=10): # frames: (N_frames, N_atom) n = frames.shape[1] D = frames[np.random.choice(frames.shape[0], m, replace=False)].T X = frames.T # n x num_frames for _ in range(iters): # 稀疏编码阶段 coef = omp_batch(X, D, k) # 字典更新阶段 for j in range(m): idx = np.nonzero(coef[j, :])[0] if len(idx) == 0: D[:, j] = X[:, np.random.randint(X.shape[1])] continue R = X[:, idx] - D @ coef[:, idx] + D[:, j:j+1] * coef[j, j:j+1] U, S, Vt = np.linalg.svd(R) D[:, j] = U[:, 0] coef[j, idx] = S[0] * Vt[0, :] return D def weighted_omp(x, D, w, k=8, eps0=1e-6): # 加权OMP:利用矩阵变换把权重内化 W = np.diag(np.sqrt(w)) Dt = W @ D xt = W @ x alpha = omp(xt, Dt, k) # 标准OMP return alpha def envelope_spectrum(sig, fs): analytic = signal.hilbert(sig) env = np.abs(analytic) spec = np.abs(np.fft.rfft(env)) freq = np.fft.rfftfreq(len(env), 1/fs) return freq, spec实际工程中我会把ksvd_learn里的 OMP 和weighted_omp里的 OMP 统一封装,避免重复实现。上面的结构只是为了把三个模块拆开,方便理解。
4.3 参数设置参考表与调试经验
我整理了一张参数参考表,方向和量级可以直接复用。
| 参数 | 含义 | 推荐值 | 说明 |
|---|---|---|---|
| fs | 采样率 | 12.8kHz ~ 51.2kHz | 需要覆盖轴承共振带,至少含 3~5 倍故障特征频率 |
| N | 原子长度 | 128 ~ 512 点 | 覆盖 3 个以上冲击周期,太短丢形态,太长丢精度 |
| overlap | 分帧重叠率 | 50% | 保证冲击跨帧时不丢失 |
| m | 字典原子数 | 64 ~ 256 | 过小表达力不够,过大会学过噪声 |
| k | 稀疏度 | 3 ~ 8 | 每帧最多用 k 个原子 |
| iter | K-SVD 迭代次数 | 8 ~ 15 | 观察重构误差,收敛就停 |
| ε₀ | 重加权稳定常数 | 1e-4 ~ 1e-6 | 太小数值不稳,太大权重不均匀 |
| λ | 稀疏惩罚系数 | 0.05~0.2 倍的 max(Dᵀx) | 用相对值统一调参,不用绝对值 |
调试中最常见的现象是包络谱里故障特征频率不出来,排查顺序我建议是:先看分帧参数有没有覆盖冲击长度,再看稀疏度是否设得太小导致冲击被拆散,最后检查加权在迭代过程中权重是否发生了漂移。我自己踩过最深的坑是把 K-SVD 的迭代次数开到了 40,结果字典慢慢把噪声也学成了原子,重构信号包络谱里到处都是假峰,降回 12 次迭代后才恢复正常。
5. 实测效果:三组方法对比能看出什么
5.1 对比设计:普通 OMP、加权稀疏、学习字典加权
为了评估各环节的实际贡献,我在同一组现场数据上跑了三套方案。数据来自一套齿轮箱测试台架上的滚动轴承,采样率 25.6kHz,采集时长约 10 秒,转速约 1500rpm,根据轴承几何参数估算外圈故障特征频率 BPFO 约为 103.7Hz。三套方案分别是:
- 方案 A:固定 DCT 字典 + 标准 OMP,这是许多论文的基线做法;
- 方案 B:固定 DCT 字典 + 加权稀疏(物理先验 + 迭代重加权);
- 方案 C:K-SVD 学习字典 + 加权稀疏,也就是本文完整流程。
三套方案都输出重构信号的包络谱,我对比的是三个指标:BPFO 谱峰幅值、包络谱信噪比(谱峰幅值与周边平均噪声之比)、可识别的谐波数量。
5.2 包络谱结果解读
结果差距非常明显。方案 A 的包络谱在 103.7Hz 处确实有谱峰,但旁边还有一组幅值更高的干扰峰,如果不带先验知识,很容易把干扰峰当故障特征,误诊率不低。方案 B 的 103.7Hz 谱峰成了全谱最高峰,二倍频 207.4Hz 也开始显现,包络谱信噪比相比方案 A 提升了约 8dB。方案 C 的包络谱最干净:103.7、207.4、311.1 三根谱线清晰可见,整体噪声底比方案 A 低了一个量级以上。
方案 C 优势的来源值得多说一句。K-SVD 学到的字典原子是一组衰减振荡波形,它们的中心频率恰好落在轴承外圈共振带附近,加权稀疏在此基础上选择与故障冲击形态最匹配的原子,重构信号几乎等于“把故障冲击直接滤了出来”。相比之下,方案 B 用 DCT 字典虽然有物理先验加权帮衬,但正弦原子描述瞬态冲击的效率太低,同样的稀疏度下残差里的故障能量还是不少,总有一部分冲击信息留在噪声里出不来。
我后来把方案 C 的字典原子可视化,发现两件有意思的事:一是大部分原子不是单一频率的正弦波,而是带有明显衰减包络的窄带振荡,和理论上的冲击响应模型完全吻合;二是不同原子之间的中心频率差大约几十赫兹,相当于 K-SVD 自动做了一个共振带内的频率细分。这种细分是人工带通滤波很难预设的,因为滤波器的频带划分是刚性的,而信号里的冲击波形是连续变化的。
5.3 两个很容易误判的场景
第一个场景是内圈故障。内圈故障信号有强烈的调制效应——故障点随内圈旋转,载荷区周期性变化,冲击幅值被低频调制。如果直接套用上述流程,偶尔会发现加权迭代把调制包络的低频成分当成主成分,导致重构信号偏向包络而非冲击。我的处理是把帧长加大到覆盖多个调制周期,让调制包络作为一个整体被稀疏表示出来,再用包络谱定位时就不会丢失调制带来的边带信息。
第二个场景是严重变转速。转速波动会让故障特征频率不再是常数,而是随时间漂移的曲线。此时把物理先验加权直接用在整段信号上会出问题——先验设定的频率位置已经过时。我的方式是先将信号按转速脉动切成多个准平稳段,每段单独计算特征频率和权重。虽然计算量变大了,但包络谱里的谱峰不再模糊成一团。
还有一个隐藏很深的问题:随机冲击。如果数据里有非周期性的随机碰磨,加权稀疏偶尔会把单次冲击保留下来。它们在包络谱上不形成整倍数谐波序列,就像一根孤立的谱线。识别这种假峰的办法是检查谐波关系:故障特征频率一定会伴随二倍频、三倍频,而随机冲击孤立出现、不构成序列。我在实际判断时有一条硬性规则——只有基频和高次谐波同时存在,且间隔严格等于基频,才判为轴承故障。
我个人在实际调这套流程时,觉得最花时间的不是 K-SVD 的迭代,而是加权策略里 ε₀ 和初始权重这两处的小参数。多组实验之后形成的经验是:先用解析字典 + 物理先验跑一遍,确认包络谱里存在候选特征频率,再切换成学习字典 + 迭代重加权放大特征,由粗到细分两步走。另外一个小技巧是把每轮训练好的字典原子保存成图片,观察原子是否逐渐向噪声形态退化,这个视觉信号比数值指标更早提醒你参数出了问题。整套方法在常规轴承故障诊断场景里稳定可用,如果再往下走,把字典学习做成跨工况迁移,是本项目最自然的扩展方向。