
简介这份PDF文档面向从事机械故障诊断、信号处理与机器学习应用的研究生、工程师及科研人员系统讲解如何将CEEMD分解与BP神经网络结合用于大数据背景下滚动轴承的故障识别。内容围绕信号降噪、特征提取与模式识别展开属于深度学习与数据建模方向的进阶参考资料。资源包内仅含1个PDF文件大小约253KB便于下载后直接阅读与存档。文档重点阐述CEEMD对原始振动信号的降噪与重构流程并选取峭度指标、峰值指数、重心频率、均方频率等时频域参数作为网络输入再通过调整网络层数、节点数与初始权值构建诊断模型对比实际输出与理论输出推断轴承状态。读者可从中获取完整的建模思路、特征选取依据与大数据7V特征的分析视角理解BP网络在非线性逼近与预测中的优势为自身课题或工程实践提供可借鉴的方法框架。目前已有142人学习。1. 轴承故障诊断为什么总在“看着像”和“说不清”之间反复横跳设备维护圈子里有个共识轴承故障诊断最难的从来不是“有没有坏”而是“坏到什么程度、还能撑多久”。振动信号一进采集卡时域波形看着毛刺变多频谱上转频倍频冒出一堆边带可你问现场老师傅“这轴承还能不能跑一周”他大概率回你一句“再观察观察”。问题出在哪出在原始振动信号本身就是个黑匣子——非平稳、非线性、强噪声故障特征频率经常被调制和淹没单靠FFT或者包络谱早期微弱故障根本抓不住。CEEMD-BP这条路线就是冲着这个痛点去的。CEEMD互补集合经验模态分解负责把混叠的振动信号拆成若干本征模态函数IMF把不同时间尺度的冲击成分分离出来BP神经网络负责从这些IMF里学出“故障类型—特征向量”的映射关系。整套流程落地下来不需要昂贵的深度网络训练卡一台普通工控机就能跑适合中小型旋转设备的在线监测改造。这篇文章面向的是手里有振动数据、想搭一套能复现的故障诊断流程的工程师从信号分解到网络训练参数怎么设、坑在哪我按自己踩过的顺序讲清楚。2. CEEMD把振动信号拆开IMF分量到底怎么选、怎么算2.1 从EMD到CEEMD为什么非要加那对正负白噪声EMD的核心思路很朴素任何复杂信号都能拆成有限个IMF每个IMF满足两个条件——极值点数和过零点数相差不超过1且上下包络均值为零。但EMD有个祖传毛病叫模态混叠同一个IMF里混进了时间尺度差异很大的成分或者相近尺度的成分被拆到了不同IMF里。早期故障的冲击成分能量弱一旦混叠后面BP网络拿到的特征就是错的训练再久也白搭。EEMD的解法是往信号里加白噪声利用噪声的均匀分布特性把信号投影到不同尺度上多次平均后噪声抵消。但EEMD残留噪声多重构误差大。CEEMD的做法更巧妙每次加一对正负相反的白噪声成对做EMD然后取平均。正负噪声在平均时相互抵消重构误差能压到10^-16量级同时保留了EEMD抗混叠的能力。我一般会跟新人说CEEMD不是新算法是EEMD的“后悔药”——把EEMD没擦干净的噪声尾巴给收了。2.2 用Python跑通CEEMD的最小代码下面这段代码是我在多个项目里反复用的CEEMD分解骨架依赖PyEMD库。注意CEEMDAN和CEEMD是两个类前者是自适应噪声版本后者是互补集合版本标题里说的是CEEMD但实际工程中CEEMDAN更常用因为它在低采样率下分解更稳定。我两个都列出来你按数据情况选。import numpy as np from PyEMD import CEEMDAN, CEEMD from scipy.signal import detrend # 模拟一段轴承外圈故障振动信号转频30Hz故障特征频率120Hz加噪声 fs 12000 # 采样频率轴承振动常用12kHz或25.6kHz t np.linspace(0, 1, fs, endpointFalse) fault_freq 120 impact np.sin(2 * np.pi * fault_freq * t) * (1 0.5 * np.sin(2 * np.pi * 30 * t)) noise 0.3 * np.random.randn(len(t)) signal detrend(impact noise) # 去趋势避免IMF出现趋势项 # CEEMDAN分解 trials控制噪声实现次数epsilon控制噪声幅度 ceemdan CEEMDAN(trials50, epsilon0.05) imfs ceemdan(signal) # 如果要用CEEMD换成下面这行 # ceemd CEEMD(trials50, epsilon0.05) # imfs ceemd(signal) print(f分解得到 {imfs.shape[0]} 个IMF分量) for i, imf in enumerate(imfs): # 计算每个IMF的均方根值用于后续筛选 rms np.sqrt(np.mean(imf ** 2)) print(fIMF {i1}: RMS {rms:.4f})这段代码的逻辑分三步先去趋势防止EMD把直流分量当成一个IMF然后调CEEMDANtrials50表示做50次加噪分解再平均epsilon0.05是噪声幅度相对于信号标准差的倍数最后输出每个IMF的RMS值。参数上trials低于30时重构误差明显变大高于100计算时间翻倍但精度提升有限我一般取50。epsilon设0.05到0.2之间信号信噪比低就取大一点但超过0.3会把微弱冲击淹没。2.3 IMF筛选不是所有分量都配进BP网络分解完得到8到12个IMF是常态但BP网络的输入维度不能直接堆这么多。高频IMF往往是噪声低频IMF可能是趋势项或转频干扰。我常用的筛选策略是计算每个IMF与原始信号的相关系数再结合峭度值。峭度对冲击成分敏感故障冲击的峭度通常大于3噪声IMF的峭度接近3。from scipy.stats import kurtosis def select_imfs(imfs, signal, kurt_th3.0, corr_th0.1): 筛选IMF峭度大于阈值且与原始信号相关系数大于阈值 selected [] for i, imf in enumerate(imfs): kurt kurtosis(imf, fisherFalse) # fisherFalse得到标准峭度正态分布为3 corr np.corrcoef(imf, signal)[0, 1] if kurt kurt_th and abs(corr) corr_th: selected.append(i) print(f选中 IMF {i1}: 峭度{kurt:.2f}, 相关系数{corr:.3f}) else: print(f剔除 IMF {i1}: 峭度{kurt:.2f}, 相关系数{corr:.3f}) return selected selected_idx select_imfs(imfs, signal)这里kurt_th3.0是经验值实际数据里如果冲击特别弱可以降到2.5corr_th0.1是防止把完全无关的分量选进来。选中的IMF数量一般控制在3到6个太少特征不够太多BP网络容易过拟合。选完之后每个IMF提取时域特征——均方根、峭度、峰值因子、波形因子再加上频域特征——重心频率、均方频率组成特征向量。3. BP神经网络训练特征向量怎么喂、参数怎么调3.1 特征向量构造与归一化别让量纲毁了训练从选中的IMF里提取的特征量纲差异可能很大。均方根可能是0.01量级峭度是3到10重心频率是几千赫兹。直接喂给BP网络梯度下降会被大量纲特征主导小量纲特征相当于没起作用。我一般用最大最小归一化把所有特征压到[0,1]区间。from sklearn.preprocessing import MinMaxScaler def extract_features(imfs, selected_idx, fs): 从选中的IMF提取时域和频域特征 features [] for idx in selected_idx: imf imfs[idx] # 时域特征 rms np.sqrt(np.mean(imf ** 2)) kurt kurtosis(imf, fisherFalse) peak np.max(np.abs(imf)) crest peak / (rms 1e-10) # 峰值因子 # 频域特征 fft_vals np.abs(np.fft.rfft(imf)) freqs np.fft.rfftfreq(len(imf), 1/fs) centroid np.sum(freqs * fft_vals) / (np.sum(fft_vals) 1e-10) features.extend([rms, kurt, crest, centroid]) return np.array(features).reshape(1, -1) # 假设有正常、内圈故障、外圈故障三类样本 # 这里用模拟数据演示特征矩阵构造 X np.random.rand(300, 16) # 300个样本每个样本4个IMF×4个特征16维 y np.random.randint(0, 3, 300) # 3类标签 scaler MinMaxScaler() X_scaled scaler.fit_transform(X) print(f归一化后特征范围: [{X_scaled.min():.2f}, {X_scaled.max():.2f}])特征维度等于选中IMF数量乘以每个IMF的特征数。我一般选4个IMF每个IMF提4个特征总共16维输入。BP网络输入层节点数就是16输出层节点数等于故障类别数。如果做的是“正常/早期/中期/晚期”四分类输出层就是4个节点用softmax激活。3.2 BP网络结构设计与训练参数BP网络的结构选择有个经验公式隐层节点数在输入层和输出层节点数之间常用sqrt(输入×输出)1到2×输入1之间试。16维输入、4类输出隐层取10到20个节点比较合适。我一般用单隐层因为特征维度不高双隐层容易过拟合且训练慢。from sklearn.neural_network import MLPClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 划分训练集和测试集 stratify保证类别比例一致 X_train, X_test, y_train, y_test train_test_split( X_scaled, y, test_size0.3, random_state42, stratifyy ) # BP网络 hidden_layer_sizes(12,) 单隐层12节点 mlp MLPClassifier( hidden_layer_sizes(12,), activationrelu, # 隐层激活函数 relu比sigmoid收敛快 solveradam, # 优化器 adam对学习率不敏感 learning_rate_init0.001, max_iter500, # 最大迭代次数 early_stoppingTrue, # 验证集损失不下降就提前停 validation_fraction0.1, random_state42 ) mlp.fit(X_train, y_train) y_pred mlp.predict(X_test) print(classification_report(y_test, y_pred, digits4))参数上hidden_layer_sizes(12,)里的12是隐层节点数我一般从10开始试每次加2看验证集准确率什么时候不再提升。activationrelu在特征维度不高时比tanh收敛快但如果你发现训练损失震荡换成tanh试试。solveradam是默认选择learning_rate_init0.001是adam的经典值数据量小于1000时不用改。early_stoppingTrue很重要轴承故障样本往往不多没有早停很容易过拟合训练集准确率99%但测试集只有70%的情况我见过太多次。3.3 训练结果怎么看混淆矩阵比准确率更有信息量准确率只告诉你“对了多少”混淆矩阵告诉你“错在哪”。轴承故障诊断里把内圈故障误判成外圈故障和把早期故障误判成正常后果完全不同。前者只是故障类型分错后者可能导致漏检。from sklearn.metrics import confusion_matrix import pandas as pd cm confusion_matrix(y_test, y_pred) cm_df pd.DataFrame(cm, index[正常, 内圈, 外圈], columns[正常, 内圈, 外圈]) print(cm_df) # 计算每个类别的召回率 recall cm.diagonal() / cm.sum(axis1) for i, r in enumerate(recall): print(f类别 {i} 召回率: {r:.4f})如果正常类召回率低于0.9说明网络把正常样本误判成故障了可能是特征里噪声成分太多回去检查IMF筛选的峭度阈值是不是太低。如果早期故障召回率低说明特征对微弱冲击不敏感考虑加包络谱特征或者用CEEMDAN替代CEEMD。4. 避坑与排查CEEMD-BP落地时最容易翻车的五个地方4.1 分解层数过多导致BP输入维度爆炸现象CEEMD分解出15个IMF全选进去BP网络输入层150维训练时间半小时起步测试准确率还不到60%。原因高频IMF是噪声低频IMF是趋势项真正携带故障信息的只有中间3到5个分量。全选等于把噪声喂给网络网络在噪声上过拟合。解决用2.3节的峭度相关系数双阈值筛选峭度阈值从3.0开始试如果选中的IMF少于3个降到2.5相关系数阈值0.1是底线再低就是瞎选。选完之后画一下每个IMF的时域波形肉眼确认有没有明显的冲击成分。4.2 白噪声幅度设太大把微弱冲击淹没现象CEEMD分解后早期故障的冲击成分在IMF里看不到了BP网络对早期故障的召回率接近0。原因epsilon参数设成了0.5甚至更高白噪声幅度超过了故障冲击的幅度分解时噪声主导了模态分离。解决epsilon控制在0.05到0.2之间。如果信号信噪比极低先做一次带通滤波或者小波降噪再进CEEMD。我一般会先算一下原始信号的峭度如果峭度小于3说明冲击成分已经很弱了epsilon取0.05trials加到80用更多次平均来压噪声。4.3 训练集和测试集按时间顺序划分导致数据泄漏现象测试集准确率95%上线后实际诊断准确率只有60%。原因把同一段连续采集的数据随机切分训练集和测试集里有相邻时间点的样本特征高度相似相当于开卷考试。解决按时间段划分前70%时间的数据做训练后30%做测试。如果数据来自多次采集按采集批次划分。train_test_split里的random_state只能保证可复现不能防止泄漏。4.4 BP网络隐层节点数拍脑袋定现象隐层节点设了50个训练集准确率100%测试集65%典型过拟合。原因特征维度只有16隐层50个节点参数量远大于样本量网络把训练样本背下来了。解决隐层节点数从sqrt(16×4)1≈9开始试每次加2画一条“隐层节点数-验证集准确率”曲线取曲线拐点。我一般不会超过20个节点。如果拐点不明显加L2正则化MLPClassifier里设alpha0.001。4.5 忽略类别不平衡导致少数类故障被吞现象正常样本500个早期故障只有30个训练完网络把所有样本都判成正常准确率还有85%。原因BP网络的损失函数对多数类样本更敏感少数类梯度被淹没。解决在MLPClassifier里没有直接的class_weight参数但可以手动过采样少数类或者用SMOTE生成合成样本。我一般先算一下各类样本比例如果最大类是最小类的5倍以上就做过采样。另外评估指标不要看准确率看宏平均召回率。5. 从能跑到好用CEEMD-BP调优的三个进阶技巧5.1 用包络谱替换部分时域特征时域特征对早期故障的敏感度有限包络谱能提取冲击的调制信息。具体做法是对选中的IMF做Hilbert变换取包络再对包络做FFT取故障特征频率处的幅值作为特征。这个特征对内圈、外圈故障的区分度比峭度更高。代码上就是在extract_features里加一段from scipy.signal import hilbert def envelope_spectrum_feature(imf, fs, fault_freq): 提取包络谱在故障特征频率处的幅值 analytic hilbert(imf) envelope np.abs(analytic) env_fft np.abs(np.fft.rfft(envelope)) freqs np.fft.rfftfreq(len(envelope), 1/fs) # 找故障特征频率最近的频点 idx np.argmin(np.abs(freqs - fault_freq)) return env_fft[idx] # 在extract_features里对每个IMF调用 # env_feat envelope_spectrum_feature(imf, fs, fault_freq120) # features.append(env_feat)fault_freq需要根据轴承几何参数和转速算出来外圈故障特征频率BPFO、内圈BPFI、滚动体BSF都有对应公式。这个特征加进去早期故障召回率通常能提5到10个百分点。5.2 用交叉验证替代单次划分单次train_test_split的结果波动很大换个random_state准确率可能差10%。用5折交叉验证取平均结果更稳。MLPClassifier本身不直接支持交叉验证但可以用sklearn.model_selection.cross_val_score包一层。from sklearn.model_selection import cross_val_score mlp_cv MLPClassifier( hidden_layer_sizes(12,), activationrelu, solveradam, max_iter500, early_stoppingTrue, random_state42 ) # 5折交叉验证 scoring用f1_macro应对类别不平衡 scores cross_val_score(mlp_cv, X_scaled, y, cv5, scoringf1_macro) print(f5折F1宏平均: {scores.mean():.4f} ± {scores.std():.4f})如果标准差超过0.05说明数据分布不均匀或者样本量太少考虑增加样本或者做分层抽样。5.3 保存模型和归一化参数别每次重新训练训练好的BP网络和归一化器要一起保存上线时直接加载不然每次诊断都要重新训练现场根本等不起。用joblib保存import joblib # 保存模型和归一化器 joblib.dump(mlp, bp_bearing_model.pkl) joblib.dump(scaler, minmax_scaler.pkl) # 加载时 mlp_loaded joblib.load(bp_bearing_model.pkl) scaler_loaded joblib.load(minmax_scaler.pkl) # 新数据诊断 new_features np.random.rand(1, 16) # 新样本特征 new_scaled scaler_loaded.transform(new_features) pred mlp_loaded.predict(new_scaled) print(f诊断结果: {pred})注意归一化器必须和训练时是同一个用测试数据重新fit归一化器是常见错误会导致特征分布偏移。我习惯把归一化器和模型打包成一个字典一起保存加载时不会漏。这套流程我在几个旋转设备监测项目里跑过从数据采集到模型上线一个人两周能搭完原型。最耗时的不是写代码是调IMF筛选阈值和隐层节点数这两个参数直接决定模型是能用还是不能用。我自己的习惯是每次换数据集先把CEEMD分解结果画出来看一眼确认冲击成分在哪个IMF里再定筛选策略比盲调参数快得多。希望帮到你。本文还有配套的精品资源点击获取