☰
CWRU轴承数据集故障诊断实战:Python读取、特征提取与机器学习建模
2026/10/14 9:30:50 网站建设 项目流程

简介:凯斯西储大学(CWRU)轴承数据集是机械故障诊断领域广泛使用的经典公开数据集,面向设备健康监测与智能诊断研究人员。资源收录了正常状态及内圈、外圈、滚珠故障,涵盖不同故障直径和多个测点位置的振动信号,并配有整理好的Python程序、使用说明文本及预处理脚本。借助配套程序,用户可完成从原始数据读取、噪声去除、滤波标准化到特征提取的全流程,进而利用支持向量机、随机森林或神经网络等方法构建故障分类模型,并进行交叉验证与性能评估。压缩包共172个文件,其中165个mat格式为原始振动数据,3个py脚本负责数据整理与特征提取,2个txt文件说明使用方法和数据细节,2个pyc为辅助模块,整体大小约230.77MB。目前已有24538人学习,适合机械、自动化专业师生及工程技术人员用于故障诊断算法验证、特征工程实践和模型训练。

1. 凯斯西储轴承数据集:故障诊断入门的第一份标准考题

做设备健康管理和故障诊断的工程师,练手数据大概率都绕不开凯斯西储大学(CWRU)的轴承数据集。它被很多人叫作故障诊断领域的 MNIST:一份数据里同时覆盖正常、内圈、外圈、滚动体四种状态,还按 0 到 3 hp 的负载和 0.007/0.014/0.021 英寸三种故障直径做了组合,采样率有 12 kHz 和 48 kHz 两档。这意味着你在论文里看到的绝大多数诊断方法,都能在这份数据上先跑通。本文会按实际动手的顺序,把数据格式解析、Python 读取、特征提取、分类建模和常见坑完整走一遍,新手可以照着复现,熟手也能在参数和边界上找到对照。

2. 数据格式与文件命名:先弄懂 .mat 里的编号和采样参数再动手

CWRU 数据以 .mat 格式提供,每个文件是一段实验记录。很多初学者上来就直接loadmat一把梭,结果发现变量名对不上、数据维度看不懂,问题基本都出在没先搞清楚文件命名规则和信号的物理含义上。

2.1 文件编号:数字里藏着负载和故障类型

文件名里的数字不是乱编的,常见整理版本里编号与工况存在固定对应。以驱动端加速度计数据为例,不同负载对应同一类故障的连续编号段,故障直径决定编号区间。整段数据里能提取到的核心映射如下:

文件编号段故障类型故障直径(英寸)负载(hp)
97–100正常无0–3
105–108内圈0.0070–3
118–121滚动体0.0070–3
130–133外圈(6 点钟方向)0.0070–3
169–172内圈0.0140–3
197–200外圈(6 点钟方向)0.0140–3
209–212内圈0.0210–3

每个故障直径对应 4 个连续编号,最后一个数字表示负载档位:0 代表 0 hp(空载),1 代表 1 hp,2 代表 2 hp,3 代表 3 hp。所以你看到105.mat时,第一反应应该是「内圈 0.007 英寸、0 hp 负载」。

需要强调,不同来源打包的编号可能存在差异,以上映射以官网整理版本和多数公开教程的共识为准。拿到数据后第一件事是用代码把 .mat 内部变量名校验一遍,确认它和文件名编号描述一致,再进入后续处理。

2.2 采样参数:12 kHz 与 48 kHz 怎么选

原始实验里有两套采样配置:驱动端和风扇端加速度计通常以 12 kHz 采样,部分驱动端实验用 48 kHz。这个参数直接决定你后续算特征频率时的分辨率和计算量,选错会得出完全错误的频谱峰值位置。

负载和转速的关系也是固定的,电机实际转速随负载增加而下降:0 hp 时约 1797 rpm,1 hp 时约 1772 rpm,2 hp 时约 1750 rpm,3 hp 时约 1730 rpm。换算成转频就是除以 60:空载约 29.95 Hz,满载约 28.83 Hz。后面做包络谱找故障特征频率时,这些数值是判断峰值是否匹配的基准。

还有一点,整个官网数据量不小,单份 .mat 文件在 12 kHz 下大约包含 48 万个采样点(约 10 秒),48 kHz 下更是超过 120 万点。做滑窗分段时,这个长度决定了你能切出多少训练样本,也决定了特征提取的耗时量级。建议先用 12 kHz 的驱动端数据跑通流程,再扩展其他工况。

3. Python 读取与可视化:从 .mat 到 numpy 数组的完整代码

这一章解决最实际的问题:把 .mat 文件变成能直接运算的 numpy 数组,并画出时域波形和包络谱。代码用 Python 3 配合 scipy、numpy、matplotlib 三件套,实验环境用 Jupyter 或 VS Code 都行。

3.1 加载代码与结构体解包

CWRU 的 .mat 文件结构并不统一:有的文件直接存变量,有的套了一层结构体数组,变量名格式通常是X105_DE_time、X105_FE_time、X105_BA_time这类,分别代表驱动端、风扇端和基座加速度计信号。写一个通用的解包函数:

import scipy.io as sio import numpy as np def load_cwru_mat(path): """读取 CWRU 的 .mat 文件,兼容直接数组和结构体数组两种格式""" raw = sio.loadmat(path) # 过滤掉 __header__、__version__、__globals__ 等元数据键 keys = [k for k in raw.keys() if not k.startswith('__')] out = {} for k in keys: data = raw[k] # 判断是否为结构体数组(dtype.names 非空表示带字段) if isinstance(data, np.ndarray) and data.dtype.names: for name in data.dtype.names: arr = data[name][0, 0] out[name] = np.squeeze(arr) else: out[k] = np.squeeze(data) return out data = load_cwru_mat('105.mat') de_signal = data['X105_DE_time'] print(de_signal.shape, de_signal.dtype)

这段代码先尝试直接取变量,如果发现是结构体数组就遍历字段名,把内部数组取出来做squeeze降维。这样无论官方原版还是网友二次整理的打包方式,都能稳定拿到一维信号。

3.2 时域波形:先看幅值和冲击

拿到信号后别急着算特征,先画波形,眼睛能看出很多问题。比如内圈故障的波形里会出现周期性冲击,正常信号的幅值则平稳得多。

import matplotlib.pyplot as plt fs = 12000 # 采样率,确认该文件是 12 kHz 实验 t = np.arange(len(de_signal)) / fs plt.figure(figsize=(12, 4)) plt.plot(t[:12000], de_signal[:12000]) # 只画前 1 秒 plt.xlabel('Time (s)') plt.ylabel('Amplitude (g)') plt.title('DE signal, 1s slice') plt.grid(True) plt.show()

注意这里只取前 12000 个点画图,也就是 1 秒窗口。全画出来 48 万点会糊成一片,看不出冲击特征。波形图上如果能看到间隔均匀的尖峰,先粗算一下尖峰间隔,后面用包络谱验证。

3.3 包络谱:故障特征频率的计算与验证

时域波形只能定性看,定量判断靠包络谱。原理是对信号先带通滤波,再用 Hilbert 变换取包络,最后对包络做 FFT。这样能把高频冲击解调成低频的故障特征频率。

from scipy import signal def envelope_spectrum(x, fs, bp_low=500, bp_high=3500): """带通滤波 + Hilbert 包络 + FFT,返回频率轴和包络谱""" # 带通滤波可以滤掉低频干扰和高频噪声,突出冲击成分 b, a = signal.butter(4, [bp_low / (fs / 2), bp_high / (fs / 2)], 'bandpass') x_f = signal.filtfilt(b, a, x) # Hilbert 变换求包络 env = np.abs(signal.hilbert(x_f)) # 对包络做 FFT freq = np.fft.rfftfreq(len(env), 1 / fs) spec = np.abs(np.fft.rfft(env)) return freq, spec freq, spec = envelope_spectrum(de_signal, fs) plt.figure(figsize=(12, 4)) plt.plot(freq[:2000], spec[:2000]) # 只画 0-2000 Hz 区间 plt.xlabel('Frequency (Hz)') plt.ylabel('Amplitude') plt.title('Envelope Spectrum') plt.grid(True) plt.show()

判断峰值时要对照轴承参数。CWRU 用的 SKF 6205 深沟球轴承,常见特征频率系数如下:

故障位置特征频率系数(乘以转频)
内圈故障(BPFI)约 5.42
外圈故障(BPFO)约 3.57
滚动体故障(BSF)约 2.38

以 3 hp 负载约 28.83 Hz 转频计算,内圈故障基频约 156 Hz,外圈故障基频约 103 Hz。在包络谱的对应位置找到明显峰值,才能判断这个文件确实是内圈故障,而不是文件名标错了。

4. 特征提取与多分类:构建一个能跑的故障诊断基线

波形和频谱都能看了,接下来解决工程里真正的问题:把一段信号变成一条特征向量,然后交给分类器输出故障类型。这一章会给出一个能直接跑出结果的完整流程,特征不用多高级,但切分和评估方式必须正确。

4.1 滑窗分段:把长信号切成训练样本

一条 CWRU 信号有几十万点,不能整条丢进模型。常见做法是滑窗分段,窗口长度要覆盖至少几个故障冲击周期,通常取 2048 到 4096 点。12 kHz 下 2048 点约 0.17 秒,足够包含多次冲击。

def make_samples(signal, label, win=2048, step=1024): """滑窗切分,返回样本矩阵和标签数组""" samples, labels = [], [] for start in range(0, len(signal) - win, step): samples.append(signal[start:start + win]) labels.append(label) return np.vstack(samples), np.array(labels) # 示例:0=正常, 1=内圈, 2=滚动体, 3=外圈 normal = load_cwru_mat('97.mat')['X097_DE_time'] inner = load_cwru_mat('105.mat')['X105_DE_time'] X0, y0 = make_samples(normal, 0) X1, y1 = make_samples(inner, 1) print(X0.shape, X1.shape)

步长设成窗口的一半(1024)时,相邻窗口有 50% 重叠,样本量翻倍但对特征区分度提升有限,反而可能因为重叠导致数据泄漏。如果只是做分类基线,窗口设 2048、步长也设 2048 不重叠,样本量也够用。

4.2 时域与频域特征:一组能区分四种状态的特征

每个窗口都要转成固定长度的特征向量。工程里常用的特征组合是时域统计量加频带能量,计算成本低,效果稳定。

from scipy import stats def extract_features(x, fs=12000): """从一段波形中提取时域和频域特征""" feats = {} feats['mean'] = np.mean(x) feats['std'] = np.std(x) feats['peak'] = np.max(np.abs(x)) feats['rms'] = np.sqrt(np.mean(x**2)) feats['kurtosis'] = stats.kurtosis(x) # 峭度:对冲击敏感 feats['skewness'] = stats.skew(x) fetures = stats.skew(x) feats['skewness'] = fetures # 频带能量:把功率谱分成 4 段,每段能量占比 freq, psd = signal.welch(x, fs, nperseg=256) total = np.sum(psd) + 1e-12 for i, band in enumerate(np.array_split(psd, 4)): feats[f'band_{i}'] = np.sum(band) / total return feats # 把每个窗口转成特征向量 def build_feature_matrix(samples): rows = [] for s in samples: rows.append(list(extract_features(s).values())) return np.array(rows) F0 = build_feature_matrix(X0) F1 = build_feature_matrix(X1) print(F0.shape, F1.shape)

峭度对轴承冲击非常敏感,正常信号的峭度接近 3,出现故障后明显增大。频带能量则刻画了频谱形态,不同故障的能量分布差异明显。这两个特征是区分度最高的,建议在分析结果里重点看它们的取值。

4.3 训练与评估:随机森林做多分类,切分方式决定结果可信度

特征矩阵准备好后,用随机森林做多分类是最稳的基线方案。但这里有一个关键陷阱:不能把窗口随机打乱后切训练集和测试集,因为同一个 .mat 文件里相邻窗口高度相关,随机切分会导致测试集里混着训练集的近邻样本,准确率虚高到 99% 以上,换到新数据立刻崩掉。

from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report from sklearn.model_selection import GroupShuffleSplit # 假设 all_X 是所有样本特征,all_y 是对应标签,groups 是文件编号 groups = np.concatenate([ np.full(len(F0), 0), # 来自 97.mat np.full(len(F1), 1), # 来自 105.mat ]) all_X = np.vstack([F0, F1]) all_y = np.concatenate([y0, y1]) # 按文件分组切分:同一个文件的所有窗口只能出现在同一侧 gss = GroupShuffleSplit(n_splits=1, test_size=0.3, random_state=42) train_idx, test_idx = next(gss.split(all_X, all_y, groups)) clf = RandomForestClassifier(n_estimators=200, random_state=42) clf.fit(all_X[train_idx], all_y[train_idx]) pred = clf.predict(all_X[test_idx]) print(classification_report(all_y[test_idx], pred))

GroupShuffleSplit保证同一个文件的所有窗口不会被拆到两侧。判断一个模型靠不靠谱,看它按文件切分后的测试准确率,而不是随机切分的结果。如果训练集和测试集来自不同负载,比如用 0 hp 训练、3 hp 测试,准确率会明显下降,这才是真实场景下的诊断性能。

5. 避坑与常见问题:五个让新手翻车的真实场景

CWRU 数据集看着简单,实际跑起来翻车点非常多。下面五条是我在复现和教学过程中遇到频率最高的,每一条都按现象、原因、解决三个层次说明。

5.1 读取报错:scipy.io.loadmat 提示不支持文件格式

现象:loadmat直接抛异常,提示KeyError或无法解析,但文件确认是 .mat 后缀。原因:这份 .mat 是 MATLAB v7.3 格式,内部用 HDF5 存储,scipy 的loadmat只支持 v5 及更早版本。解决:用h5py读取,HDF5 格式的变量结构是引用关系,需要额外处理:

import h5py def load_mat_v73(path): """读取 v7.3 格式的 CWRU mat 文件""" with h5py.File(path, 'r') as f: for key in f.keys(): # v7.3 里数据以引用形式存储,需要取出再转置 data = np.array(f[key]) # CWRU 信号通常是 1 x N,转置成一维 if data.ndim == 2 and data.shape[0] == 1: data = data.squeeze() print(key, data.shape)

判断文件属于哪种格式最快的方法:用文本编辑器打开 .mat 文件,开头出现HDF5字样就是 v7.3,出现MATLAB 5.0就是老格式。

5.2 变量名对不上:不同打包版本的字段名不一致

现象:代码按X105_DE_time取变量,结果KeyError。原因:网上流传的 CWRU 数据有多个整理版本,有的变量名去掉了time后缀,有的把DE改成了d,甚至同一文件内同时存在X105_DE_time和X105_DE两个数组。解决:先打印load_cwru_mat返回的字典键名,确认真实字段后再写死变量名,或者写一个按关键词匹配的兜底逻辑,比如字段名包含DE就认为是驱动端信号。

5.3 外圈故障的负载方向标记

现象:有人发现130.mat和某个外圈故障样本的频谱峰值位置不一致,怀疑数据有问题。原因:CWRU 的外圈故障数据里标注了故障位置相对于负载区的角度,常见的是 6 点钟、3 点钟和 12 点钟方向。不同角度下冲击的传递路径不同,包络谱幅值有差异,但特征频率不变。解决:外圈故障文件先确认对照表里标注的方向,做分类时把不同方向当成同一类没问题,但做迁移学习时要注意训练集和测试集的方向分布是否一致。

5.4 数据泄漏:热切分准确率虚高

现象:随机切分后准确率 99.8%,但换成按负载跨域测试后掉到 85%,怀疑模型不行。原因:滑窗重叠加上随机切分,训练和测试样本高度相关,模型记忆了窗口邻域的形态而不是真正的故障模式。解决:按 4.3 的方法用GroupShuffleSplit按 .mat 文件分组切分,更严格的做法是按负载切分,用 0 hp 数据训练、1–3 hp 数据测试,检验模型在不同工况下的泛化能力。

5.5 采样率用错:特征频率漂移

现象:包络谱峰值出现在 70 Hz,对照特征频率计算却对不上。原因:把 48 kHz 采集的文件用 12 kHz 的采样率去做频率轴,所有频率都缩小到真实值的四分之一,峰值位置完全错位。解决:每个 .mat 文件在读取时把采样率作为元数据一起维护,不要全局写死。加载后先检查信号长度,48 kHz 的文件 10 秒信号长度约 48 万点,12 kHz 约 12 万点,长度也能反推采样率。

6. 进阶验证:把 CWRU 流程迁移到自己的实验台

CWRU 数据跑通后,真正的考验是把这套流程迁移到自己的实验台或现场设备数据上。我常用的做法是先做一次「跨域盲测」:用 CWRU 全部数据按负载分组训练模型,然后把你自采的一段已知故障信号丢进去。自采信号和 CWRU 的采样率、传感器安装位置都不一样,如果直接分类准确率很低,这不代表流程错了,而是说明特征分布发生了偏移。

一个实用技巧是先从包络谱入手做迁移适配。自采数据先算包络谱,找到故障特征频率的峰值位置,确认它和 SKF 6205(或你实际轴承型号)的理论特征频率对得上,再去做特征提取和分类。这一步能过滤掉一大半传感器松动、转速波动导致的问题。如果包络谱里根本找不到对应峰值,先回头检查轴承参数是否填错,或者故障是否已经严重到信号形态发生变化。

另一个值得做的验证是对比实验:同一组样本,一路直接提取时域特征分类,另一路先做包络谱再取频谱峰值附近的能量特征,对比两组准确率。实际测试中,包络谱预处理在低信噪比数据上的优势很明显,这是因为包络谱把故障信息集中到了低频段,削弱了转速波动和背景噪声的影响。从那以后我每次拿到新的采集数据,都强制先跑一遍包络谱对照特征频率,再谈建模,这个习惯帮我少走了很多弯路,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询