做滚动轴承故障诊断方向的人,几乎没有绕不开凯斯西储大学轴承数据中心这套公开数据集的。无论你是刚入门的学生,还是做设备状态监测的工程师,CWRU这套数据基本都是第一次做实验验证时的默认基准。我最初接触“滚动轴承故障诊断系统”这个概念时,也对着这套数据踩了不少坑:文件怎么读、特征怎么提、模型怎么选,每一步都有隐藏的取舍。这篇博文就围绕“基于凯斯西储大学数据设计一套滚动轴承故障诊断系统”这件事,把整个流程完整拆开,从数据预处理、特征频率计算、特征工程,到模型训练和坑点排查,都给你整理清楚,适合想快速搭建一套可复现诊断流程的人参考。
1. 项目目标与整体方案设计
1.1 滚动轴承故障诊断是个什么问题
滚动轴承是旋转机械里最容易出问题的部件之一。设备在运行过程中,轴承的损伤早期往往非常微弱,不会直接导致停机,但振动信号里已经出现了周期性的冲击痕迹。诊断系统的目标,就是从这些振动信号中判断轴承到底处于哪种状态:正常、内圈故障、外圈故障,还是滚动体故障。
如果把问题定义得再细一点,CWRU数据里每种故障还分了三种损伤尺寸:0.007英寸、0.014英寸、0.021英寸,对应大约0.18毫米、0.36毫米和0.53毫米。所以实际项目里有两种常见的任务设定:
- 四分类:正常、内圈故障、外圈故障、滚动体故障,四种状态。
- 十分类:内圈、外圈、滚动体各三种损伤尺寸,加上正常状态,共十类。
多数论文和实验选十分类,因为还能顺便评估模型对故障严重程度的识别能力。我做的这个系统也按十分类来设计。从本质上看,这就是一个有监督的多分类问题:用带标签的振动信号训练模型,让模型学会区分不同故障模式。
1.2 系统整体流程:一条完整的流水线
很多人以为“故障诊断系统”就是训练一个分类器,准确率高就完事。实际上,一套能跑的诊断系统远不止一个模型,而是从原始信号到诊断结论的一整条流水线。
完整流程是这样的:原始振动信号 → 滑动窗口切分 → 特征提取 → 特征标准化 → 分类模型训练 → 评估与分析。到了实际推理阶段,新来的在线信号也要走同一套特征管道,先切窗、提特征、标准化,再送入模型,输出故障类型。
你可以把整个过程理解成“听声识病”。老工程师拿听诊棒贴在轴承座上,听到有规律的“哒哒哒”冲击声,就知道是外圈还是内圈出了问题。模型做的就是类似的事,只不过它“听”的是加速度传感器采集的振动数据,学的是信号里故障冲击的频率“指纹”。这套系统设计的核心,是先建立一个稳定可靠的特征提取管道,再把分类模型插进去,换模型很容易,但特征管道一旦定下来,前后必须完全一致。
1.3 方案选型:为什么先做特征工程再用机器学习
刚开始做这个项目时,我纠结过要不要直接上深度学习。CWRU数据集不算大,单类数据就几段几十秒的信号,这种体量下做端到端的深度网络容易过拟合,而且调参成本极高。更关键的问题是,深度学习模型不好解释——你很难说清楚它到底学到了什么。
所以我的方案定为:特征工程 + 传统机器学习作为主干,深度学习作为后续扩展对照。 这个选择的好处是:
- 可控可解释:时域峭度、包络谱特征这些都有明确的物理含义,出了问题能追溯。
- 小样本友好:在CWRU这种数据规模下,SVM和随机森林绰绰有余。
- 速度快:不用GPU,几秒钟就完成训练。
等这套系统跑通了,你真想加对比实验,再去搭卷积网络不迟。但地基一定要打稳。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据解析与预处理:CWRU数据集的正确打开方式
2.1 数据集的基本结构与核心参数
CWRU数据采集自一个电机驱动系统试验台:电机、扭矩传感器、测功机组成负载系统,加速度传感器分别安装在驱动端和风扇端的壳体上。信号以.mat格式存储,用Python读进来通常能看到DE、FE、BA等字段,分别对应驱动端加速度信号、风扇端加速度信号和基座加速度信号。
采样频率方面,驱动端常用12kHz,数据文件里一段信号大约持续10秒。转速随负载变化:0马力时约1797转/分,3马力时降为1730转/分。这些参数对后面计算特征频率非常重要。
下面这个表是我整理的核心参数速查:
| 参数 | 数值 |
|---|---|
| 轴承型号 | SKF 6205-2RS深沟球轴承 |
| 滚珠数 | 9 |
| 滚珠直径 | 7.94 mm |
| 节圆直径 | 39.04 mm |
| 接触角 | 0° |
| 故障类型 | 内圈、外圈、滚动体 |
| 故障尺寸 | 0.007/0.014/0.021英寸 |
| 负载范围 | 0~3马力 |
| 常用采样率 | 12 kHz |
这里有个细节容易让新手犯迷糊:外圈故障数据还分了3个安装位置——3点钟、6点钟、12点钟方向。其中6点钟方向正好在承载区中心,故障冲击最明显。不同位置的数据特征会有差异,做分类时你可以把它们合并为“外圈故障”一类,也可以单独标注成不同类别。我建议先合并处理,把任务控制得简单一点。
2.2 数据加载与样本划分:警惕看不见的数据泄漏
CWRU数据的读取很简单,没什么难度:
python复制import scipy.io as sio
import numpy as np
data = sio.loadmat('IR007_0.mat')
de = data['DE'].flatten() # 驱动端加速度信号
fs = 12000
print(de.shape)
真正坑人的是“样本划分方式”。CWRU每个.mat文件是一段连续时间信号,如果直接把整段信号切成一堆窗口,然后随机打乱、划分训练集和测试集,那么同一个文件切出来的相邻窗口极可能同时出现在训练集和测试集中。这些窗口之间的差异很小,模型几乎等于开卷考试,测试准确率可以高达99%以上,但换个负载数据一测就崩。
我在项目里用了两种划分策略:
- 按文件划分:每个.mat文件只允许被切进训练集或测试集中的一个,保证一个文件里的所有窗口都落在同一侧。
- 按工况划分:用0马力的数据训练,用1、2、3马力的数据测试,直接验证跨负载泛化能力。
我更推荐第二种方式。CWRU虽然是实验室数据,但跨负载测试已经能暴露出很多模型“背答案”的问题。真正的诊断系统要面对现场工况变化,如果模型一换工况就失效,那工程价值就很低。
2.3 轴承特征频率计算:先算特征频率,再谈特征工程
很多人直接提取特征丢给模型,完全不看故障特征频率,结果模型跑通了,却压根不知道轴承的故障信号长什么样。这一步虽然不需要直接做成特征输入模型,但它是你分析结果、排查问题的重要标尺。
滚动轴承的故障特征频率由几何参数和转频决定,常见四个:
- 保持架故障频率FTF:fr/2 × (1 - d/D cosα)
- 外圈故障频率BPFO:n/2 × fr × (1 - d/D cosα)
- 内圈故障频率BPFI:n/2 × fr × (1 + d/D cosα)
- 滚动体故障频率BSF:D/(2d) × fr × (1 - (d/D cosα)²)
公式里fr是轴频,n是滚珠数,d是滚珠直径,D是节圆直径。拿1797转/分这个工况具体算一下,轴频fr = 1797/60 ≈ 29.95Hz,代入6205轴承的参数,结果大约为:
- BPFO ≈ 107.36 Hz
- BPFI ≈ 162.19 Hz
- BSF ≈ 70.56 Hz
- FTF ≈ 11.93 Hz
有了这几个数,你在包络谱上看到谱峰落在107Hz附近,就能直接判断是外圈故障。这个“谱峰对齐”的验证步骤,后面排查问题时会反复用到。
3. 特征工程与诊断模型选型
3.1 时域特征提取:抓住包络的“尖峰与冲击”
时域特征直接从振动信号的时间序列上计算,它们计算快、物理含义明确,是诊断系统必不可少的底层特征。我在项目中提取的时域特征主要有这样几类:
| 特征 | 计算公式 | 物理意义 |
|---|---|---|
| 均方根值RMS | sqrt(mean(x²)) | 信号整体能量水平 |
| 峭度Kurtosis | mean((x-μ)⁴)/σ⁴ | 信号冲击性程度 |
| 偏度Skewness | mean((x-μ)³)/σ³ | 信号分布不对称程度 |
| 峰值 | max(x) | 瞬时冲击幅度 |
| 峰峰值 | max(x)-min(x) | 振动波动范围 |
| 波峰因子 | max(x)/RMS | 冲击相对于平稳振动的强度 |
正常轴承的振动接近高斯分布,峭度约为3;一旦出现点蚀或剥落,冲击成分会拉高峭度,很多早期诊断文章都用峭度做指标。这就像你敲一个完好的轴承声音很“闷”,敲有故障的轴承声音会“脆”一样,峭度捕捉到的就是这种“脆”。
建议在原始信号和包络信号上各提取一遍时域特征。包络信号是由Hilbert变换解调出来的,它把高频冲击痕迹“整流”成低频包络,在包络上算峭度和RMS,往往比原始信号更灵敏。
3.2 频域与包络谱特征:解调出被“藏起来”的故障
直接对原始振动信号做FFT看频谱,常常看不到明显的故障频率。原因是故障冲击会激起轴承或结构的固有高频振动,故障特征频率以调制边带的形式藏在固有频率附近,原始频谱里它并不扎眼。
包络分析(也叫包络解调)就是用来解决这个问题的:先用Hilbert变换求出解析信号的幅度包络,把调制信号解调出来,再对包络做FFT,故障特征频率就会以清晰谱峰的形式出现在低频段。
python复制from scipy.signal import hilbert
analytic_signal = hilbert(signal_window)
envelope = np.abs(analytic_signal)
env_spectrum = np.abs(np.fft.rfft(envelope))
freqs = np.fft.rfftfreq(len(envelope), 1 / fs)
这一步非常有效。我在项目中会提取包络谱里几个关键频段的能量值:BPFO附近、BPFI附近、BSF附近以及各自二倍频、三倍频位置的幅值。这些直接作为特征送入模型,比原始频谱全谱特征更紧凑、更有区分度。
需要提醒的是,包络谱对窗长比较敏感,窗太短频率分辨率不够,特征频率附近的两条谱线可能糊在一起。我一般把窗长设为2048点以上,配合滑动窗重叠,必要时可以多段平均来稳定谱线。
3.3 模型选型对比:从SVM到随机森林再到一维卷积
特征准备好之后,分类器的选择直接影响最终效果。我在这个项目里重点对比了三种模型:
| 模型 | 优势 | 需要留意的点 |
|---|---|---|
| SVM(RBF核) | 小样本高维表现好,决策边界灵活 | 特征必须先标准化,核参数C和gamma需要调 |
| 随机森林 | 不需要标准化,能输出特征重要性 | 特征数量多时噪声特征可能干扰分割 |
| 1D-CNN | 端到端自动提特征,上限高 | 数据量需求大,划分方式容易带来虚高准确率 |
我给的建议是:先用随机森林或SVM出一个baseline,记录准确率和混淆矩阵,确保整个数据管道没有问题,再考虑上深度学习。实际项目中,SVM在十分类任务上通常能达到非常高的准确率,而且训练只需几秒。CWRU数据的一个特点是类别相对均衡,分类器不需要处理明显的样本不平衡问题,但如果将来换到现场数据,得先检查类别分布。
模型选择背后还有一个原则:诊断系统的价值不在于把准确率刷到99.9%,而在于换一个工况、换一台设备时,模型还能不能保持稳定。这点在后面的跨负载验证里体现得很明显。
4. 系统实现:从原始信号到诊断结果的完整流程
4.1 环境准备与数据加载代码
我用的环境是Python 3.9,核心依赖numpy、scipy、scikit-learn、matplotlib。加载数据的方法前面已经写过,这里补一个更完整的读取逻辑:遍历文件夹下所有.mat文件,按文件名前缀判断标签。
python复制import os
import glob
import scipy.io as sio
import numpy as np
fs = 12000
win_len = 2048
stride = 512
def load_cwru_mat(file_path, sensor='DE'):
data = sio.loadmat(file_path)
signal = data[sensor].flatten()
return signal
files = glob.glob('data/*.mat')
for f in sorted(files):
name = os.path.basename(f)
# 根据文件名前缀解析标签
print(name)
CWRU数据在不同版本里的命名有些差异,有的直接叫97.mat、107.mat这种数字编号,有的带Normal、IR、OR前缀。写代码时最好先打印文件名,确认命名规则再写标签解析函数。
4.2 切窗与特征提取实现
切窗是数据增强的关键步骤。一个10秒的信号有约12万个采样点,按2048点窗长、512点步长切分,能得到两百多个窗口样本。窗长要覆盖几个旋转周期:转速1797转/分时,转一圈约400个采样点,2048点窗口能覆盖5圈左右,足够捕捉多次故障冲击。
python复制def sliding_window(signal, win_len=2048, stride=512):
n = len(signal)
n_windows = (n - win_len) // stride + 1
windows = []
labels = []
for i in range(n_windows):
start = i * stride
end = start + win_len
windows.append(signal[start:end])
return np.array(windows)
特征提取部分,我写了一个统一的函数,从每个窗口里提取时域统计量和包络谱频段能量:
python复制from scipy.stats import skew, kurtosis
from scipy.signal import hilbert
def extract_features(x):
feat = {}
feat['rms'] = np.sqrt(np.mean(x ** 2))
feat['peak'] = np.max(np.abs(x))
feat['kurtosis'] = kurtosis(x)
feat['skew'] = skew(x)
feat['crest_factor'] = feat['peak'] / (feat['rms'] + 1e-10)
env = np.abs(hilbert(x))
env_spec = np.abs(np.fft.rfft(env))
freqs = np.fft.rfftfreq(len(env), 1 / fs)
# 在BPFO、BPFI、BSF附近取峰值能量作为特征
for name, cf in [('bpfo', 107.36), ('bpfi', 162.19), ('bsf', 70.56)]:
mask = (freqs > cf - 3) & (freqs < cf + 3)
feat[name] = np.max(env_spec[mask]) if np.any(mask) else 0
for harmonic in [2, 3]:
mask_h = (freqs > cf * harmonic - 3) & (freqs < cf * harmonic + 3)
feat[f'{name}_h{harmonic}'] = np.max(env_spec[mask_h]) if np.any(mask_h) else 0
return feat
注意我加了1e-10防止除零,频段也没取太多,避免特征维度爆炸导致机器学习模型过拟合。这组特征总共十几个维度,已经能支撑不错的分类效果。
4.3 模型训练、评估与结果分析
特征提取完成后,所有样本的特征向量拼成矩阵X,标签放y,然后开始训练。这里的关键点是我在第2.2节强调的划分策略:不要随机打乱全部数据再切分,而要按文件或按工况分。
python复制from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.metrics import classification_report, confusion_matrix
X = np.array(all_features)
y = np.array(all_labels)
# 按工况划分:0马力训练,其他负载测试
train_mask = np.array([loads[i] == 0 for i in range(len(y))])
test_mask = ~train_mask
pipe = make_pipeline(StandardScaler(), SVC(kernel='rbf', C=10, gamma='scale'))
pipe.fit(X[train_mask], y[train_mask])
y_pred = pipe.predict(X[test_mask])
print(classification_report(y[test_mask], y_pred))
当采用同一负载随机划分时,SVM和随机森林在十分类上很容易跑到98%以上;但如果改成0马力训练、1到3马力测试,准确率可能会降到80%以下,甚至更低。这个落差会让你非常直观地理解“数据泄漏”有多可怕。
画混淆矩阵也是诊断模型问题的重要方式:
python复制import matplotlib.pyplot as plt
from sklearn.metrics import ConfusionMatrixDisplay
ConfusionMatrixDisplay.from_predictions(y_test, y_pred, normalize='true')
plt.show()
看混淆矩阵时,重点关注哪些类别之间互相混淆。最常见的混淆发生在不同外圈故障位置之间,以及小尺寸故障和正常状态之间。这有助于你判断是特征不够,还是数据本身区分度就低。
5. 常见问题与避坑实录
5.1 准确率虚高:数据泄漏问题
这是我见过最多人踩的坑,也是CWRU项目里最典型的问题。表现是:训练集测试集随机打乱划分后,模型准确率超过99%,你兴奋地以为系统已经建好了。但一旦换成跨负载验证,准确率立刻断崖式下跌。
原因前面分析过:同一个连续信号切出的相邻窗口高度相似,随机划分导致训练集和测试集“串题”了。对策很简单,永远按文件或按工况段划分数据。我在项目里甚至会把不同故障位置的外圈数据也分成独立的段来切分,避免位置信息泄漏。
5.2 包络谱里看不到故障频率
用包络谱分析单个窗口时,有时会发现在BPFO或BPFI位置没有明显的谱峰。常见原因有三个:
- 窗长太短,频率分辨率不够。12kHz采样下,2048点窗长对应的频率分辨率约5.86Hz,而某些谐波间隔只有几Hz,糊在一起后峰值不明显。可以加窗长或重叠。
- 外圈故障位于非承载区。CWRU的3点钟和12点钟位置不在负载区,冲击信号弱得多。这种数据不适合用包络谱单看,更适合交给分类器学习。
- 个别窗口恰好没有捕捉到冲击周期。解决方法是多窗口包络谱平均,不要迷信单窗结果。
5.3 特征标准化的影响
SVM的RBF核基于距离计算,特征量纲差距过大会让数值大的特征完全主导决策边界。所以在SVM管道里一定要加StandardScaler,而且标准化要fit在训练集上,不能用全量数据fit。
随机森林不需要标准化,因为它基于特征分割。如果你在一个管道里同时测试多个模型,我习惯把标准化单独封装成Pipeline的一步,避免切换模型时漏掉预处理。
5.4 复现性和自动化细节
做项目时还有一个容易被忽略的点:随机种子。SVM本身基本是确定性的,但数据切分、随机森林、深度学习都有随机性。项目开始时固定所有随机种子,否则实验结果无法复现,后面写报告也会很头疼。
另外我建议把整个流程封装成配置文件:数据路径、采样率、窗长、步长、特征列表、模型参数、训练/测试划分方式都写进配置里。这样每次换数据集或调参,不用改代码逻辑,只改配置就能重新跑通。
5.5 常见问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 随机划分准确率极高但跨负载崩盘 | 数据泄漏 | 按文件或按工况划分 |
| 包络谱在特征频率处没有明显峰 | 窗长太短、位置不在承载区 | 加窗长、多段平均 |
| SVM效果差 | 特征未标准化 | 在管道中加入StandardScaler |
| 混淆矩阵中故障位置类混淆严重 | 数据标签粒度过细 | 合并同类故障位置 |
| 实验结果无法复现 | 随机种子未固定 | 全局设置random_state |
最后聊两句我的体会。做这个项目踩得最深的坑,就是在一开始被99%的高准确率迷惑了,忽略了对数据划分方式的审视。直到做了跨负载实验才意识到,真正有价值的不是训练集上的准确率,而是模型面对数据分布变化时的稳定性。对于滚动轴承故障诊断系统,特征管道和划分策略的可靠性,比模型本身更重要。后续如果想继续扩展,可以往两个方向走:一是引入迁移学习或领域自适应,减小不同工况之间的分布差异;二是从“分类”升级到“退化趋势预测”,把故障从萌芽到失效的整个变化过程量化出来。这套基于CWRU数据搭起来的地基,后续做很多方向都能用得上。
