ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

轴承故障诊断全链路实战:CWRU数据处理与模型部署

轴承故障诊断全链路实战:CWRU数据处理与模型部署 上个月帮一个师弟看故障诊断的毕业设计他当着我的面打开了CWRU轴承数据集的.mat文件然后说了句让我印象很深的话“文件能读但我拿它训练网络acc能到99%换一批数据就废了。”这话我太熟了。CWRU轴承数据集几乎是轴承故障诊断领域公认的“Hello World”公开、权威、标签齐全但这恰恰也是它最大的迷惑性文件好读、模型好训真正能拉开差距的是从数据加载到模型部署这条完整链路里那些没有人替你踩的坑。这篇文章我就把这条链路完整走一遍重点讲清楚.mat文件怎么读、长信号怎么切成样本、特征怎么算、模型怎么保存以及最后怎么把模型封装成一个真正能被其他程序调用的API服务。适合刚接触CWRU、被.mat文件卡住以及想从“跑通demo”走向“落地部署”的读者。1. CWRU轴承数据集为什么故障诊断入门都从它开始1.1 数据集的物理背景与实验装置CWRU是Case Western Reserve University凯斯西储大学的缩写这个数据集来自该校轴承数据中心的公开实验数据。实验台的核心是一台2马力电机电机通过扭矩传感器和测功机连接负载在电机壳体、轴承座等位置安装加速度计采集振动信号。实验用轴承是SKF 6205-2RS深沟球轴承分别在驱动端和风扇端布置传感器所以你会看到DEDrive End和FEFan End两类数据字段有的版本还带BABase加速度计数据。这套装置的巧妙之处在于故障注入方式用电火花加工在轴承内圈、外圈、滚动体上人为制造单点损伤故障直径分为0.007英寸、0.014英寸、0.021英寸等几个等级。对应到工程语言就是故障越重冲击能量越大振动信号的特征也越明显。采样频率有12kHz和48kHz两档负载从0到3马力变化对应转速大约从1797rpm降到1730rpm。换句话说这份数据集天然就覆盖了“不同故障位置 × 不同故障程度 × 不同工况”的组合这也是它长期霸榜故障诊断入门首选的原因。很多教程会直接告诉你“加载.mat训练CNN准确率99%”却很少有人解释为什么这份数据这么好训。答案其实简单实验室环境下转速稳定、噪声小、故障类型单一信号里的周期性冲击成分非常规律任何包含冲击敏感特征的模型都能轻易抓住其中的差异。理解这一点比刷一个高准确率重要得多。1.2 数据文件命名规则与状态标签体系CWRU的数据文件格式看起来有点乱但其实有规律。以驱动端12k数据为例正常基线数据文件通常保存为类似97.mat这样不带X的名字里面字段名为X097_DE_time、X097_FE_time故障数据文件常用三位数字编号比如105.mat里面的字段是X105_DE_time等。48k目录下的文件命名规律类似只是换了目录和采样率。我建议不要依赖记忆去对应每个编号代表什么状态而是先做一份数据说明卡文件示例故障位置故障直径英寸负载HP状态标签97.mat无无0Normal105.mat内圈0.0070IR_007130.mat外圈0.0070OR_007120.mat滚动体0.0070B_007注意这张表是我在常用数据划分基础上整理出来的参考示例不同渠道下载的版本可能编号略有差异动手前一定先用自己的脚本把每个.mat的字段名和长度打印出来核对一遍。外圈故障还有不同损伤位置比如6点钟方向、3点钟方向有些版本在文件名里会体现有些需要从官方文档里查。1.3 为什么要先做一份“数据说明卡”你会觉得这步多余但就我的经验90%的翻车事故都发生在标签环节。CWRU的.mat文件名和故障状态的映射关系分散在官方PDF和社区帖子里如果不提前整理成结构化的映射表等代码写多了再回头补标签非常容易张冠李戴。我的做法是直接把映射表存成CSVimport csv mapping [ {file: 97.mat, fault: normal, location: none, diameter: 0, load: 0}, {file: 105.mat, fault: inner_race, location: ir, diameter: 0.007, load: 0}, # 继续补充... ] with open(cwru_mapping.csv, w, newline) as f: writer csv.DictWriter(f, fieldnames[file, fault, location, diameter, load]) writer.writeheader() writer.writerows(mapping)后续所有代码都从这张CSV读标签只改CSV不动代码能省下大量返工时间。2. 环境搭建先想清楚再动手不要盲目pip install2.1 为什么第一步不是pip install很多新手拿到CWRU数据后的第一反应是打开终端敲pip install numpy pandas scipy scikit-learn装完发现版本冲突、Python解释器对不上、模型跑着跑着内存爆了。这里的问题不是“没装依赖”而是“没有隔离环境”。Python项目之间的依赖冲突是常态A项目需要TensorFlow 2.15B项目可能还需要TensorFlow 1.15如果共用一个全局环境要么互相覆盖要么根本没法运行。CWRU体检项目虽然依赖不算特别复杂但既然要走到模型部署一个干净的环境意味着后面踩的坑更少排查问题更快。我见过有人因为盲目pip install在当前环境里装了几百个包最后连import sklearn都会报错——半天时间全耗在环境上了。磨刀不误砍柴工先搭环境再动手是实战项目的第一课。2.2 用venv还是AnacondavenvPython自带的轻量级虚拟环境只隔离site-packages不管理Python版本。优点是轻、省心适合单机小项目。Anacondaconda不仅管理Python包还能管理Python解释器本身创建conda create -n cwru python3.9就能拿到一个独立完整的Python环境对科学计算栈尤其友好。缺点是体积大、命令稍重。我的建议是直接用Anaconda或Miniconda。原因在于科学计算库numpy、scipy、scikit-learn在conda源里通常有预编译的wheel或二进制包安装后很少遇到编译报错。而venv在装某些依赖时经常会因为缺少系统库而卡住排查成本高。# 创建并激活独立环境 conda create -n cwru python3.9 -y conda activate cwru2.3 依赖清单与版本选择这个项目我实际用到的依赖和用途如下依赖库用途版本建议numpy数组运算1.24scipy读取.mat、信号处理1.10pandas数据整理1.5scikit-learn传统分类模型、数据划分、评估1.3matplotlib可视化3.7joblib模型持久化1.3fastapi/uvicornAPI服务0.100pytorch1D-CNN可选2.0安装命令conda install numpy scipy pandas scikit-learn matplotlib joblib -y pip install fastapi uvicorn这里我特意没有把PyTorch放进基础依赖原因在后面的章节会展开CWRU上传统机器学习模型已经足够强深度学习是锦上添花不需要一上来就背一个几十个G的框架。3. 数据加载把.mat文件吃干榨净3.1 用scipy读取CWRU的mat文件CWRU数据集绝大多数文件是MATLAB v5格式Python里最常用的读取工具是scipy.io.loadmat。先写一个最小脚本看看文件内部长什么样import scipy.io as sio data sio.loadmat(97.mat, matlab_compatibleTrue) for key in data.keys(): if not key.startswith(__): print(key, data[key].shape, data[key].dtype)正常情况下你会看到X097_DE_time、X097_FE_time这样的变量shape是(1, N)或(N, 1)dtype是float64。随后把DE信号取出来压平de_signal data[X097_DE_time].flatten() print(f信号长度: {len(de_signal)}, 采样率: 12000 Hz)这里有两个容易踩的坑。第一loadmat对某些包含结构体struct的.mat文件默认会解析成numpy.void嵌套对象需要加上squeeze_meTrue, struct_as_recordTrue等参数才能读顺。第二CWRU部分文件的字段名里带_time后缀不要想当然地写死文件名最好代码里动态匹配_DE_time结尾的字段避免不同版本字段名不一致导致KeyError。3.2 滑动窗口切分样本数量与重叠率的权衡拿到一段连续振动信号后不能直接把整段丢给模型。轴承故障诊断通常把长信号切成固定长度的短窗口每个窗口就是一个独立样本。为什么因为模型的输入要求定长而且我们需要大量样本来支撑训练。切窗函数写起来很简单import numpy as np def sliding_window(signal, window_size1024, stride512): n len(signal) return np.array([signal[i:i window_size] for i in range(0, n - window_size 1, stride)])窗口长度和步长重叠率的选择是第一个关键决策。窗口太短可能截不到完整的故障冲击周期窗口太长样本数变少而且可能包含非平稳成分。在12kHz采样率下电机转速约1797rpm对应旋转频率约30Hz一个旋转周期大约400个采样点。窗口取1024点意味着约2.5个旋转周期能较好地包含内圈、外圈、滚动体故障的冲击特征。步长取512点重叠50%样本数翻倍同时保留一定冗余。我的经验是先把窗口定成1024点、步长512点跑通全流程之后再去调优。不要一开始就追求最优超参链路跑通最重要。3.3 标签构建把文件编号映射为故障状态切出来的窗口本身没有标签需要结合之前整理的映射表来生成。示例逻辑import pandas as pd mapping_df pd.read_csv(cwru_mapping.csv) labels, windows [], [] for _, row in mapping_df.iterrows(): mat_path fdata/{row[file]} raw sio.loadmat(mat_path, matlab_compatibleTrue) de_field [k for k in raw.keys() if k.endswith(_DE_time)][0] signal raw[de_field].flatten() for win in sliding_window(signal, 1024, 512): windows.append(win) labels.append(row[fault]) X np.array(windows, dtypenp.float32) y np.array(labels)这段代码跑完你就有了X样本 × 1024点和y标签字符串。为了给模型用标签还需要数字编码用sklearn.preprocessing.LabelEncoder即可。注意这里生成的X是不需要人工特征的原始信号后面接1D-CNN可以如果要做传统机器学习还要再走一轮特征提取见下一节。4. 特征工程振动信号如何“说话”4.1 时域特征为什么是轴承诊断的基本盘传统机器学习不能直接吃高维原始信号因为维度太高、样本量相对有限。这时候需要从每个窗口里提取统计特征把1024个点的窗口压缩成一组低维数字。时域特征是最直观的一类因为轴承故障的本质是周期性冲击冲击会直接改变信号的幅值分布和波动形态。常用特征包括均值零漂、方差、峰峰值、均方根值、峭度、偏度、峰值因子、波形因子、脉冲因子、裕度因子。其中峭度对冲击特别敏感健康轴承的峭度接近3出现早期剥落时峭度会跳升峰值因子和脉冲因子反映了信号中的尖峰程度均方根值代表振动能量水平。计算函数可以一次性封装def time_features(x): mean np.mean(x) std np.std(x) rms np.sqrt(np.mean(x ** 2)) peak np.max(np.abs(x)) peak_peak np.max(x) - np.min(x) skewness np.mean((x - mean) ** 3) / (std ** 3) kurtosis np.mean((x - mean) ** 4) / (std ** 4) crest_factor peak / rms impulse_factor peak / mean if abs(mean) 1e-8 else 0 shape_factor rms / (mean 1e-8) clearance_factor peak / (np.mean(np.sqrt(np.abs(x))) ** 2 1e-8) return [mean, std, rms, peak, peak_peak, skewness, kurtosis, crest_factor, impulse_factor, shape_factor, clearance_factor]4.2 频域与包络谱提取共振带的思路频域特征揭示信号的周期性。对窗口做FFT后可以得到幅值谱其中故障特征频率处会出现峰值。但实际轴承信号中故障冲击激发的是传感器或轴承结构的高频共振直接在原始频谱里找低频故障特征往往不够明显。工程上的破解方法是“共振解调”先带通滤波把高频共振带选出来再用Hilbert变换提取包络信号对包络做FFT得到包络谱此时故障特征频率才清晰可见。虽然这么做需要额外调滤波器参数但对于CWRU这类实验数据原始FFT的频带能量特征已经能提供很强的判别力。我的惯用做法是把0-Nyquist频段分成若干个频带统计每个频带的能量占比再加一个频谱质心def freq_features(x, fs12000): spectrum np.fft.rfft(x) amp np.abs(spectrum) freqs np.fft.rfftfreq(len(x), d1/fs) # 分成8个频带求每个频带能量 bands np.array_split(amp ** 2, 8) band_energy [np.sum(b) for b in bands] total_energy np.sum(band_energy) 1e-8 band_ratio [e / total_energy for e in band_energy] centroid np.sum(freqs * amp) / (np.sum(amp) 1e-8) return band_ratio [centroid]把时域特征和频域特征拼接每个窗口可以得到约20维的特征向量。这样处理之后模型输入小训练快部署时也只需在服务侧保留同样一套特征函数。4.3 特征矩阵构建与标准化注意事项提取完特征用pandas或numpy拼成矩阵。此时必须注意一个原则标准化只允许在训练集上fit然后应用到测试集。很多人习惯先对全量数据做StandardScaler再划分训练测试集这属于信息泄露会让评估结果虚高。正确写法是from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X_feat np.array([extract_all_features(w) for w in X]) X_train, X_test, y_train, y_test train_test_split( X_feat, y, test_size0.2, random_state42, stratifyy ) scaler StandardScaler().fit(X_train) X_train_scaled scaler.transform(X_train) X_test_scaled scaler.transform(X_test)关于划分方式这里有个隐藏的地雷。如果直接random_state42随机切分训练集和测试集中会同时出现来自同一个.mat文件的相邻窗口这些窗口高度相关模型等于在“作弊”得到的准确率虚高。这个问题我在第5.3节会专门展开。5. 模型训练传统机器学习也能打好基准5.1 先用传统机器学习模型建立基准CWRU样本特征和故障类别之间的映射关系非常清晰传统机器学习完全能打好基准没必要一上来就扛深度学习。随机森林Random Forest是我的首选对特征尺度不敏感能处理非线性关系还天然给出特征重要性方便排查哪些特征真正有判别力。SVM在高维小样本上也表现好但调参稍麻烦XGBoost更强但更容易过拟合。先上随机森林from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, accuracy_score model RandomForestClassifier(n_estimators300, max_depth12, random_state42) model.fit(X_train_scaled, y_train) y_pred model.predict(X_test_scaled) print(fAccuracy: {accuracy_score(y_test, y_pred):.4f}) print(classification_report(y_test, y_pred))在CWRU上跑这种实验准确率通常能到95%以上。但我必须泼盆冷水这个数字只说明你的代码流程没问题不能说明模型对现场数据鲁棒。原因就是前面提到的随机划分不当带来的高估以及实验室数据本身的干净性。5.2 1D-CNN的简易实现当数据量大一点时如果特征工程做得比较粗糙可以直接让1D-CNN从原始窗口里自动学习特征。PyTorch写一个轻量级模型只需要几十行import torch.nn as nn class CNN1D(nn.Module): def __init__(self, num_classes4): super().__init__() self.conv1 nn.Sequential( nn.Conv1d(1, 32, kernel_size5, stride2), nn.ReLU(), nn.MaxPool1d(2) ) self.conv2 nn.Sequential( nn.Conv1d(32, 64, kernel_size3, stride2), nn.ReLU(), nn.MaxPool1d(2) ) self.flatten nn.Flatten() self.fc nn.Sequential( nn.Linear(64 * 63, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, num_classes) ) def forward(self, x): x self.conv1(x) x self.conv2(x) x self.flatten(x) return self.fc(x)训练时注意把输入reshape成(batch, 1, window_size)损失函数用交叉熵。CNN在CWRU上往往能逼近100%准确率但代价是需要更多数据和更长训练时间。我的建议是先把传统机器学习流程跑通得到一份可解释的基准结果再考虑CNN提点。这样出了问题也好定位是特征问题还是模型问题。5.3 评估时最容易踩的雷数据划分顺序这一节是整个CWRU实战里我最想强调的。很多人会写train_test_split(X, y, test_size0.2)然后高高兴兴看到99%的准确率。但前面说过CWRU同一个.mat文件切出的相邻窗口之间相关性极高随机划分等于把“同一个人的两张照片”一张放在训练集一张放在测试集模特在测试集里“还记得”。正确的做法是按文件或按工况划分保证训练集和测试集来自完全不同的数据文件。比如用负载0、1、2的数据训练用负载3的数据测试或者按文件名分组后用GroupShuffleSplitfrom sklearn.model_selection import GroupShuffleSplit gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, test_idx next(gss.split(X_feat, y, groupsfile_ids))这种更能反映真实场景现场安装一套新设备模型看到的是没见过的数据分布不完全等同于某个已知文件的截断片段。我实测下来随机划分准确率可能在99%按文件划分后可能掉到95%左右但后者才是可信的泛化性能。6. 模型部署从joblib到可调用的API服务6.1 为什么选择FastAPI而不是Flask训练完模型下一步不是发论文而是让它能被其他程序调用。FastAPI是我现在最常用的部署框架原因有三一是自带OpenAPI文档浏览器访问/docs就能看到接口说明和调试按钮比Flask手写文档省事太多二是基于ASGI天然支持异步并发性能明显优于Flask三是配合Pydantic做请求参数类型校验非法的JSON请求会在入口就被拒绝不用自己在代码里写一堆解析判断。如果部署目标是极小型嵌入式设备可以考虑用ONNX Runtime替代FastAPI直接跑推理但作为通用服务方案FastAPI是最稳的选择。6.2 本地推理服务的实现与接口设计以随机森林模型为例训练完先持久化import joblib joblib.dump(model, rf_model.joblib) joblib.dump(scaler, scaler.joblib)然后写FastAPI服务。这里我推荐服务端只接收原始窗口的特征列表而把特征提取放到客户端——不是因为它更简单而是因为“原始窗口 → 特征”这段逻辑如果在服务端重复实现很容易和训练时不一致。更稳妥的设计是两边共用同一个特征函数文件确保顺序、参数完全一致from fastapi import FastAPI from pydantic import BaseModel import joblib import numpy as np app FastAPI(titleCWRU Bearing Fault Diagnosis API) model joblib.load(rf_model.joblib) scaler joblib.load(scaler.joblib) class FeatureRequest(BaseModel): features: list[float] # 长度必须与训练特征一致 class PredictResponse(BaseModel): label: str probabilities: dict app.post(/predict, response_modelPredictResponse) def predict(req: FeatureRequest): x np.array(req.features).reshape(1, -1) x_scaled scaler.transform(x) pred model.predict(x_scaled)[0] proba model.predict_proba(x_scaled)[0] return PredictResponse( labelpred, probabilities{str(cls): float(p) for cls, p in zip(model.classes_, proba)} )启动服务只需一行命令uvicorn api:app --host 0.0.0.0 --port 8000这样设计的好处是客户端可以是Python脚本、手机App、前端页面只要它能把窗口特征构造成JSON发过来就行。用curl测一下curl -X POST http://127.0.0.1:8000/predict \ -H Content-Type: application/json \ -d {features: [0.01, 0.2, 1.5, 0.3, ...]}如果想让接口接收原始窗口数据服务端自动提取特征也可以但一定要确认特征提取函数和训练时完全一致。一旦函数里某个参数的顺序变了模型看到的分布就变了预测结果直接崩。传输窗口数据还有一个性能代价1024个float64在JSON里传输比20维特征大得多时延更高。所以我个人更推荐特征前置的模式尤其适合手机端或边缘端。6.3 性能验证与常见报错处理服务上线后先做一轮冒烟测试再到http://127.0.0.1:8000/docs用Swagger UI直接点“Try it out”发请求。常见的报错和对应排查经验我列一下报错现象常见原因处理方式请求返回422Pydantic校验失败features长度不对或类型不对检查请求字段名和类型和Request模型对齐模型predict时报维度错误输入reshape后的第二维和训练特征数不一致打印scaler.mean_.shape确认特征维度启动时找不到模块部署环境缺依赖用requirements.txt锁定版本或直接用docker镜像前端/手机请求被浏览器拦截缺少CORS配置在FastAPI里加CORSMiddleware跨域问题在网页和移动端调用时非常常见加段中间件就能解决from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins[*], allow_methods[*], allow_headers[*], )性能方面随机森林单条推理在毫秒级FastAPI启动后同时处理几十个并发请求问题不大。如果将来模型换成CNN或更大模型可以用ONNX Runtime做推理加速再用gunicorn/uvicorn多worker部署。7. 落地经验那些文档里不写的坑7.1 从实验室到现场数据分布漂移问题CWRU上面跑得再好也不代表这套模型直接拿到工厂产线上能用。实验室数据转速稳定、负载恒定、故障类型单一真实电机往往伴随变转速、变负载、变噪声和复合故障数据分布和CWRU完全不同。很多工程师把CWRU训出的高acc模型拿到现场第一周效果还行第二周就开始频繁误报原因就是分布漂移。解决思路包括积累现场数据做增量训练加入域适应算法或者退一步把模型输出从“类别标签”改成“异常置信度”在阈值上做保守设计。部署不是终点持续的数据闭环才是。7.2 给手机端/边缘端调用留好路子题目里提到“手机端怎么调用电脑部署的模型”这正好是FastAPI方案的加分项。电脑上执行uvicorn api:app --host 0.0.0.0 --port 8000后把电脑IP和端口暴露给手机手机端只要是同一局域网或能访问到该IP就可以用HTTP POST调接口。iPhone/Android App里写一个网络请求发特征数组过去拿到JSON解析出标签和概率即可——模型本体完全不需要塞进手机。如果部署目标是树莓派、RK3588这类边缘设备建议把模型导出为ONNX用ONNX Runtime推理能明显减少环境和依赖体积。这类轻量部署思路和CWRU流程是相通的。7.3 我踩过的几个坑最后把实操中踩过的坑集中说一下希望能帮你省几个小时的调试时间。scipy读.mat的格式坑CWRU部分.mat里的变量是二维数组一不留神就把(1, N)当成(N,)处理。每次读取后务必flatten()并检查长度。随机划分导致准确率虚高这是CWRU项目里最典型的陷阱。解决方法是按文件划分或按负载划分宁可用低一点的准确率换真实的泛化能力。标准化fit错数据scaler必须在训练集上fit在测试集上只做transform。如果你对全量数据做了fit模型的评估结果就是虚标的。特征顺序不一致部署时如果特征函数被复制了一份一旦改了某个特征的顺序训练和推理就完全对不上了。把特征提取函数抽成独立模块让训练脚本和API共用同一个函数是最保险的做法。依赖污染不要在生产环境用Anaconda的base环境一定要新建虚拟环境或者直接用Docker封装镜像把requirements.txt固定下来。部署环境越干净线上问题越少。最后再分享一点CWRU轴承数据集是一个绝佳的起点但它真正值得学习的不是“把acc刷到99%”而是让你理解一条完整链路数据怎么读、样本怎么切、特征怎么算、模型怎么训、服务怎么部署。把这些环节都打通之后你再看其他开源数据集——PHM2012、江南大学轴承数据等思路是完全一样的。我个人最大的体会是花在数据组织和数据划分上的时间回报远高于花在调模型超参上的时间。数据链路理清楚模型和部署都是顺水推舟的事。
RELATED READING

延伸阅读

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