1. 项目概述与方案选型思路
1.1 这套"四件套"到底解决了什么问题
做设备故障诊断的人,十有八九都被振动信号折磨过。传感器装上去容易,数据采回来也不难,难的是怎么从一堆非线性、非平稳的波形里分辨出"这是正常状态""这是轴承外圈磨损""这是齿轮断齿"。直接用原始波形塞给深度学习模型?我试过,效果不能说没有,但极不稳定,一个轻微的转速波动就能让训练好的模型当场失效。原因很简单,原始振动信号里的噪声、谐波、随机冲击混在一起,模型分不清哪些是故障特征,哪些是环境干扰。
所以我当时搭这条流水线时给自己定了个原则:不要指望单一模型解决所有事,而是把"故障识别"拆成几个明确的子任务,让每个子任务用最擅长的工具去解决。这就有了这套组合——先用EEMD把混合信号一层层剥开,再用MPE把每个分量变成可量化的复杂度特征,接着用KPCA把高维特征压缩成更紧凑的表达,最后交给BILSTM去学习时序模式并输出故障类别。
这套方案解决的核心问题,是让非平稳、非线性振动信号变成"机器能真正看懂的故障指纹"。它适合的目标读者也很明确:正在做旋转机械故障诊断、状态监测、PHM方向的工程师或同学,或者是刚把信号处理和深度学习过了一遍、但不知道怎么把它们串起来的初学者。
1.2 为什么是EEMD、MPE、KPCA、BILSTM?每步的"备胎"是谁
选这套组合不是拍脑袋,每一步我都纠结过也对比过,这里把我踩过的选项拉出来说清楚。
先看信号分解这一步。备选方案主要有EMD、VMD和小波分解。EMD最老牌,但它有个著名的毛病叫模态混叠——一个分量里混着不同频率的成分,故障特征会被搅在一起。VMD需要预设模态数量K,你事先并不知道这段信号里到底藏着几个有效成分,K设错了,要么分解不充分,要么过度分解。小波分解则要选小波基函数和分解层数,选起来非常主观,做一次两次还行,批量处理时根本调不动。EEMD通过往信号里加白噪声再做多次EMD取平均,既保住了EMD的自适应性,又极大缓解了模态混叠,这是它胜出的关键理由。
再看出特征这一步。很多人习惯直接提取均值、方差、峰值因子这些统计量,但这些指标对早期微弱故障几乎无感,信号换了台设备可能就不灵了。近似熵和样本熵也能刻画复杂度,但计算速度慢,样本熵在长序列上还能直接算到爆内存。MPE在排列熵基础上加了粗粒化过程,既能捕捉信号模式的有序性,又能覆盖多时间尺度,计算量还友好,实际用下来稳定得多。
再看降维这一步。PCA是默认选项,但它是线性的,振动信号的特征在原始空间里根本不是线性可分结构,硬降维会把关键区分度压掉。t-SNE可视化效果好,但每次运行结果不完全一致,不适合放进需要复现的工程流程。KPCA用核函数把数据映射到高维空间再做PCA,本质上是在"非线性特征空间里做线性降维",既保留了非线性关系,又可控可复现。
最后是分类模型。LSTM我一开始就在用,但它只看过去的信息,而故障冲击在时间上是有前后呼应关系的——冲击后必然有一段衰减摆动。BILSTM同时从正向和反向读取序列,等于让模型在判断某一时刻的状态时,既看前面的趋势,也看后面的余波,这在我们的数据上确实比LSTM更稳。CNN更擅长提取局部空间模式,但振动信号的一维时序依赖关系,还是BILSTM捕捉得更自然。
1.3 方案适用范围与局限性
这套组合在旋转机械上表现出的潜力最大,轴承、齿轮箱、电机这类周期性旋转设备,故障冲击会周期性出现在振动信号里,EEMD- MPE - KPCA - BILSTM的长链条提取模式很对症。
但它也不是万能的。EEMD计算量大,一条几秒长的信号分解可能要跑几十秒,对实时性要求高的在线监测场景,这个时延就得认真掂量。另外,BILSTM训练需要一定量的样本,如果只有几十条故障样本,那更建议把模型换成GRU甚至随机森林,效果反而可能更好。还有低频缓变故障、间歇性故障,这套流程未必能拿得住。
2. 核心技术原理拆解
2.1 EEMD:用白噪声强行"抹平"模态混叠
要理解EEMD,先得知道EMD在做什么。EMD把一个复杂信号拆成若干个本征模态函数(IMF)和残差,每个IMF在任意时刻只有一个频率成分,且上下包络对称。这个分解过程不依赖预设基函数,完全由信号自身驱动,这是它相比小波最大的优势。但缺点是,当信号里存在间歇性成分或噪声干扰时,同一个IMF里会同时出现两个尺度差很大的模式,这就是模态混叠。
EEMD的办法其实很质朴:既然噪声会让分解不稳定,那就主动往信号里加白噪声,做很多次EMD,再把结果平均。白噪声的频谱均匀覆盖在整个频带上,加入后等于给信号提供了均匀分布的"参考尺度",原本容易跑的极值点被强制拉回到规律位置。为了抵消加噪的影响,EEMD会重复集成几十上百次,每次用不同的随机白噪声,最后平均后噪声彼此抵消,真实成分保留下来。
EEMD两个关键参数,一个是加入噪声的幅值,通常取原始信号标准差的0.1到0.4倍;另一个是集成次数,一般设置在100到300之间。幅值太大了,重构信号会失真;太小了,起不到抑制模态混叠的作用。集成次数多,分解结果更稳定,但计算时间线性增长。实操中我在0.2倍标准差和200次集成这个组合上效果比较均衡,你可以把这个当起点,再根据信号复杂度微调。
2.2 MPE:在多个尺度上量化信号复杂度
先讲排列熵(PE)。它的思路是把一个长度为N的时间序列,按嵌入维数m和延迟τ划成一个个子序列,每个子序列内部按数值大小排序,得到一个排列模式,统计所有模式的频率分布,然后套Shannon熵公式计算。排列熵越小,说明信号越规律;越大则越随机。
但单尺度的PE有个问题:它只在一个时间尺度上观察信号。而故障信号往往既包含高速冲击的局部细节,又包含整体的缓慢趋势变化。MPE的做法是把原始序列做"粗粒化",依次用尺度s=1,2,3…对新序列求平均,形成新信号,再对每个粗粒化序列计算PE。这样比原始的熵特征多了一个谱的视角,能把"多尺度复杂度"完整刻画出来。
MPE三个参数要留心:嵌入维数m通常取3到7,m太小排列模式太少,区分度不够,m太大计算量膨胀而且对微小变化过敏;延迟τ通常取1,取了更大的τ容易错过短周期结构;尺度因子s我习惯取6到20,这跟采样率和主频率有关,下面实操部分我会细讲怎么定。
2.3 KPCA:非线性映射到高维再做线性降维
PCA本质是找数据方差最大的正交方向,然后把数据投影上去。可振动信号的特征分布常常是弯曲的、嵌套的,线性超平面切不开。KPCA的思路是在原空间找一个核函数,把样本映射到高维(甚至无穷维)特征空间,在高维空间里数据变得"更接近线性可分",再执行标准PCA。
常用核函数有高斯径向基(RBF)、多项式核、Sigmoid核。RBF是最常用的,它的带宽参数γ需要调,太大容易过拟合,太小则所有样本都挤在一起失去区分度。降维的维度一般由累计贡献率决定,我习惯把阈值定为95%,也就是保留能解释95%方差的最少主成分数。这样既压缩特征又不过度损失信息,后面模型训练时的维度负担也会小很多。
KernelPCA有个容易忽略的点:核矩阵需要中心化,sklearn里的实现会自动处理,但如果你自己写PCA流程,这里非常容易忘记去均值操作,导致降维结果完全跑偏。
2.4 BILSTM:双向看序列才有完整上下文
LSTM通过输入门、遗忘门和输出门控制信息流动,能记住几百步之前的有效信号,又不会像RNN那样梯度爆炸或消失。但单向LSTM有个结构上的盲区——对于当前时刻的隐藏状态,它只能看到过去,看不到未来。
故障信号恰恰是前后强关联的:一次冲击发生后,紧接着会有衰减振荡,后面逐渐平复;正常状态则一直平稳。BILSTM的正向层从序列开头向后扫描,反向层从末尾向前扫描,最后把两个方向的隐状态拼接起来作为当前时刻的表征。这样模型判断"这一帧像不像故障"时,既结合了故障冲击的到达趋势,又结合了冲击后余波的信息,特征表达更立体。
实操中建议BILSTM层不要叠太多,一到两层就够了,隐层节点64或128基于经验比较够用。层数太深反而容易过拟合,尤其故障样本数量本就不大。
3. 实操流程与实现
3.1 数据准备与预处理
先说数据从哪来。最理想的是你能拿到实验室环境下转台台架上不同工况下的振动信号,包含正常、内圈故障、外圈故障、滚动体故障等各类别。如果暂时没有原始数据资源,也可以用常见公开的轴承数据集做验证,比如某大学和某公司的合作数据集(这里不展开具体来源,避免误导),里面振动信号是按故障类别分好的。
拿到原始信号后的预处理我总结成三步:
- 剔除明显异常段。传感器敲击、接线松动造成的尖峰毛刺,要在分解前手动砍掉。
- 切分样本。我习惯把连续的振动序列切成固定长度,每段样本对应一个故障标签。样本长度可以定为偶数比如2048个点,这既要让样本包含足够多的故障冲击周期,又要保证样本量够模型训练用。
- 归一化到零均值、单位方差。这一步对神经网络训练很重要,尤其是后面要经过EEMD和KPCA,输入尺度差异太大会导致梯度不稳定。
我一般会把数据集按 7:2:1 划分成训练集、验证集、测试集,且保证每个故障类别的比例在各集合中大致一致,防止类别分布失衡影响训练判断。
3.2 EEMD分解与IMF筛选
这里给出一个实用的EEMD分解片段:
from PyEMD import EEMD import numpy as np # signal为预处理后的振动信号,一维数组 eemd = EEMD() # trials表示集成次数,noise_width对应白噪声标准差占比 imfs = eemd.eemd(signal, trials=200, noise_width=0.2) # imfs 的 shape通常是 (n_imfs, n_points)分解后你拿到一组IMF,但并不是每个IMF都值得送进下一步。我的筛选逻辑有两个:
第一,IMF与原始信号的相关系数。相关系数高的IMF保留了更多原始信息,剔除相关系数低于0.1的IMF和最后的残余项。第二,能量占比。IMF能量占原信号总能量比低于某个阈值(比如1%)可以直接移掉,它们大概率是噪声模块。
实操中常出现的问题是分解得到的IMF数量过多,比如十几个,全送进特征提取会导致特征向量极长,加大后面计算量。建议保留前6到8个能量大且与原始信号相关性高的IMF,后面的低频趋势项和噪声项不参与特征提取。
3.3 MPE特征生成
对每个筛选后的IMF,分别计算它的多尺度排列熵。计算流程分两步:先粗粒化,再在每条粗粒化序列上算排列熵。
from math import factorial def permutation_entropy(sequence, m=5, delay=1): n = len(sequence) ordered = np.array([sequence[i:i+m] for i in range(n - (m-1)*delay)]) patterns = np.array([np.argsort(ordered[i]).tolist() for i in range(len(ordered))]) unique, counts = np.unique(patterns, axis=0, return_counts=True) prob = counts / counts.sum() # 归一化 return -np.sum(prob * np.log(prob)) / np.log(factorial(m)) def coarse_graining(sequence, scale): n = len(sequence) segments = n // scale return np.mean(sequence[:segments*scale].reshape(segments, scale), axis=1)对每个IMF,依次用尺度s=1到S计算粗粒化序列,再在每条粗粒化序列上算排列熵。最后把所有IMF的所有尺度熵拼接起来,构成一个长度为n_imfs * S的特征向量。比如保留6个IMF、尺度S取10,那每个样本的特征维度就是60,这就是送入KPCA的原始输入。
写这段代码时踩过一个坑:排列模式统计时np.argsort默认从小到大排序,但如果序列长度特别短,某些模式的排列会被截断,算出来的熵值偏小。保险做法是保证每个尺度下的粗粒化序列长度不低于m! + 20,否则应该缩短尺度范围。
3.4 KPCA降维与数据归一化
MPE特征算出来后通常维度不小,并且各维度的数值范围不一定在一个数量级。用KPCA之前先做一次标准化,把每列的均值拉回0、方差拉回1,这一步不能省。然后:
from sklearn.decomposition import KernelPCA from sklearn.preprocessing import StandardScaler X_scaled = StandardScaler().fit_transform(X_mpe) kpca = KernelPCA(kernel='rbf', gamma=0.1, fit_inverse_transform=False) X_kpca = kpca.fit_transform(X_scaled)sklearn的KernelPCA不会直接告诉你累计贡献率,你可以通过查看核矩阵的特征值来推。简单做法是试几个n_components,比如10、20、30,比较降维后数据可解释方差,或者直接按经验压到10~15维。我做的时候把维度定到12维,累计贡献率基本能到95%。如果降维后发现分类准确率上不去,优先调整gamma,而不是盲目增加维度。
这里特别注意:KernelPCA是无监督的,它并不关心你的标签。所以降维后最好再做一次简单的二维可视化看一眼不同类别是否分得开。如果数据点混成一团,大概率是gamma选择问题,要么是MPE特征本身区分度不足。
3.5 BILSTM网络的构建与训练
降维后的特征维度是12维,但BILSTM期望输入是三维结构:(样本数, 时间步, 特征维度)。这里有个容易卡壳的点——MPE特征本身不是时序序列,它是针对每个IMF算出的一个向量,怎么把它塑造成时间步长的形式?
我的做法是把KPCA降维后的特征看作一个"伪序列",让BILSTM对特征之间的顺序关联建模。更常见的另一种做法是保留IMF顺序,把每个IMF视作一个时间步,特征维度是KPCA降维后的维数。搭配起来是这样:
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Bidirectional, LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam model = Sequential() model.add(Bidirectional(LSTM(units=64, return_sequences=True), input_shape=(n_imfs, 12))) model.add(Dropout(0.2)) model.add(Bidirectional(LSTM(units=32))) model.add(Dropout(0.2)) model.add(Dense(16, activation='relu')) model.add(Dense(num_classes, activation='softmax')) model.compile(optimizer=Adam(learning_rate=0.001), loss='sparse_categorical_crossentropy', metrics=['accuracy'])训练时我用早停(EarlyStopping)监控验证集损失,patience=15,这样既防止过拟合,又能省下不少调参时间。batch_size设32,epoch上限100,有早停在基本不会刷满100轮。梯度裁剪可以开,clipnorm=1.0,尤其在损失曲线上出现尖刺抖动时特别管用。
3.6 效果评估与对比
训练完在测试集上评估,除了看整体准确率,我建议必须输出混淆矩阵。故障诊断最忌讳的是"整体准确率很高,但某两个相似故障类别几乎分不出来"。我记得自己第一次跑完,整体准确率到了97%,但仔细看混淆矩阵,内圈故障和滚动体故障之间混了七八个样本,这个信息如果不看矩阵根本发现不了。
对比实验的话,至少拉三个模板:只用BILSTM吃原始信号;EEMD- MPE - PCA - BILSTM;EEMD- MPE - KPCA - BILSTM(完整方案)。实测下来完整方案在分类准确率上比直接喂原始信号的方法高出不少,也比PCA替代KPCA的版本有提升,代价主要是训练时间略增加。这也是我觉得KPCA没白选的证据。
4. 常见问题与排查技巧实录
4.1 EEMD分解慢得像蜗牛
EEMD要几百次加噪并重复分解,数据一长,耗时极陡。如果你跟我一样信手nio就开了300次集成,做好等待准备。两个解决办法:一是把集成次数降到100次,效果差距通常不大;二是缩小样本长度,在能包含足够多故障周期的前提下尽量短。另外可以尝试并行化,把多次EMD的任务丢给多进程,这个优化几乎是线性提速,值得做。
4.2 分解结果出现端点飞翅
EEMD对信号两端极其敏感,端点处的极值点比中间少,分解出的包络容易在两端乱抖,这就是端点效应。破解思路有两个:先试信号两端各加一段镜像延拓再分解,分解完了裁掉延拓部分;还有一个是尽量别让样本太短,样本长度大于5个主周期时端点效应的影响力会大大降低。
4.3 MPE尺度因子到底取多少
尺度因子太小,多尺度信息提取不充分;太大,粗粒化序列太短,熵值失真。我的经验是看采样率和主频。比如采样率12kHz,轴承故障频率在100Hz量级,每个故障周期有上百个点,尺度取8到15能覆盖到故障冲击所在的时间尺度。调试技巧是先跑几个尺度值,画出不同类别在不同尺度下的熵曲线,挑出类别差异最大的区间。
4.4 KPCA之后故障类别还是分不开
先别急着堆叠更多特征。大概率是gamma没调好。可以尝试在1e-3到1之间按对数间隔列表搜索,配合交叉验证看类别可分性。其次检查MPE的特征是否稳定,同一个故障类别的不同样本之间熵值波动大不大,如果波动大,根源在MPE参数或IMF筛选,而不是KPCA。
4.5 BILSTM老是过拟合
故障样本量通常不大,BILSTM参数多,过拟合是家常便饭。我的建议按优先级排:
- 早停一定要用,这是省时省力的第一道防线。
- Dropout给到0.2到0.3,太多的话模型学不进去。
- 把两层BILSTM降为一层,参数减半,效果往往不降反升。
- 数据增强,给每段样本加一点白噪声、时序扰动,故障诊断里这是被低估的武器。
4.6 标签不平横:正常样本多到爆,故障样本只有几十条
尝试过类别加权,把故障类别的loss权重调高,但效果有限。后来用滑窗的办法加大故障样本扩充率,配合适当的数据增强,才明显改观。如果故障样本实在太少,建议直接放弃BILSTM,改用传统机器学习分类器如随机森林,配合MPE特征往往也能取得不错的效果——这算是我给你的"备用方案"。
| 问题 | 现象 | 推荐排查顺序 |
|---|---|---|
| EEMD过慢 | 单个样本分解几十秒 | 降低集成次数 → 缩短样本长度 → 并行化 |
| 端点效应 | IMF两端剧烈抖动 | 镜像延拓 → 加长样本 |
| 特征区分度不足 | 不同类别熵曲线重叠 | 调尺度因子 → 重新筛选IMF |
| KPCA降维效果差 | 降维后样本重叠严重 | 调gamma → 核对标准化 → 检查MPE稳定性 |
| BILSTM过拟合 | 训练准确率很高,测试下降多 | 早停 → Dropout → 减少网络层数 → 数据增强 |
| 类别严重不均衡 | 少数类完全学不到 | 样本扩充 → 类别加权 → 换轻量模型 |
再分享一个屡试不爽的小技巧:把EEMD、MPE、KPCA这几个预处理阶段的结果都缓存到磁盘上。BILSTM调参动不动就要重跑几十轮,如果每次都重新分解、重算熵,时间成本完全不可接受。把预处理阶段做好缓存之后,后面调模型就像换显卡跑游戏一样流畅。这条建议真心值得你在项目一开始就落地。
另外,模态混叠虽然被EEMD缓解了,但不是完全消除。实在遇到顽固混叠时,把IMF再做一次小波软阈值去噪,把混叠进去的次级成分剥掉,效果常常出乎意料。这只是个配角技巧,但关键时刻真能救一把。
我个人在实际操作中的体会是:这套流程的价值不在某一步有多惊艳,而是把信号层面的物理规律、特征层面的复杂度描述、降维层面的非线性结构保留和分类层面的时序依赖学习组合成了一整条严丝合缝的链路。前期搭好后,后面换数据换设备,只需要微调参数,基本盘是非常稳的。
做项目时如果数据资源紧张,不妨先跑通一个更简化的版本,用EEMD加MPE加上随机森林,把预处理和特征验证好,再把分类器换成BILSTM也不迟。这条路我走过,实测能让整个项目周期缩短一大半,你试过就能体会到。