简介:这份资源是面向机械故障诊断与信号处理方向的科研人员和学生准备的CWRU轴承振动信号导入包,用于快速加载并分析凯斯西储大学经典轴承数据集,支撑故障检测与健康监测实验。压缩包共6个文件,约6KB,以Python脚本为主,辅以打包配置、许可协议和说明文档,其中核心模块提供多变量振动数据的读取与组织方式,便于直接接入后续的特征提取与分类流程。已有260人学习下载,适合刚接触CWRU数据集、希望省去数据解析环节的研究者。借助该包,读者可以跳过繁琐的数据格式整理,把精力集中在滤波降噪、谱分析、峭度等特征计算以及异常检测与故障识别算法的验证上,也能以此为基线复现和对比不同诊断方法,对工业设备维护与健康管理研究具有实用参考价值。
1. 拿到 CWRU 轴承振动信号导入包,先搞清楚它到底解决什么问题
很多人第一次接触 CWRU 轴承数据集,卡住的地方不是算法,而是数据读不进来。凯斯西储大学轴承数据中心的原始文件是 .mat 格式,里面嵌套着 DE、FE、BA 多通道结构体,采样频率还分 12kHz 和 48kHz 两套,故障直径从 0.007 到 0.040 英寸不等。你兴冲冲下载完,打开 MATLAB 一看,变量名一堆乱码,直接懵了。所谓 CWRU 轴承振动信号导入包,本质上就是把这堆原始 .mat 文件规范化成统一数组或 DataFrame 的中间层工具,让你不用每次写重复的解析逻辑。它适合三类人:刚入门做轴承故障诊断的学生、需要快速搭 baseline 的算法工程师、以及想把 CWRU 当验证集但不想被数据格式拖住的研究者。这一章先把导入包要解决的边界讲清楚,后面再动手。
2. CWRU 数据集的目录结构与导入包的设计取舍
2.1 原始 .mat 文件里到底存了什么
CWRU 的每个 .mat 文件通常包含几个关键变量:X 开头的编号变量(如 X105_DE_time)、采样频率标记、转速信息。以 12kHz 驱动端数据为例,正常基线文件里会有 X097_DE_time、X097_FE_time、X097_BA_time 三条时间序列,分别对应驱动端加速度计、风扇端加速度计和基座加速度计。故障文件则按故障类型和直径命名,比如内圈故障 0.007 英寸对应 IR007_0,外圈故障 0.014 英寸对应 OR014@6。这些命名规则不统一,有的带 @ 符号表示故障相对载荷角位置,有的直接数字编号,这就是为什么裸读 .mat 经常翻车。
导入包要做的第一件事,就是把这些命名差异吃掉。常见做法是维护一张映射表,把文件名前缀映射到标签和通道信息。我一般会在导入包里放一个 config 字典,键是文件名模式,值是 (故障类型, 故障直径, 通道) 三元组。这样新增文件时只改配置,不动解析逻辑。
2.2 导入包该暴露什么接口
一个能用的 CWRU 导入包,接口不需要多,但必须稳定。我倾向于暴露三个函数:load_single_file(path)返回单文件的时间序列和元信息,load_by_label(fault_type, diameter)返回匹配的所有样本,load_dataset(split_ratio)返回训练测试划分好的数组。参数上,采样频率和通道选择必须可配,因为 48kHz 数据和 12kHz 数据的后续处理逻辑不同,混用会导致频谱分析时频率轴对不上。
下面是一个最小可用的导入实现,用 scipy 读 .mat,用 numpy 做规范化:
import scipy.io as sio import numpy as np from pathlib import Path # 文件名到标签的映射,按实际下载的文件名调整 LABEL_MAP = { "Normal": ("Normal", 0.0), "IR007": ("InnerRace", 0.007), "IR014": ("InnerRace", 0.014), "IR021": ("InnerRace", 0.021), "OR007": ("OuterRace", 0.007), "OR014": ("OuterRace", 0.014), "OR021": ("OuterRace", 0.021), "B007": ("Ball", 0.007), "B014": ("Ball", 0.014), "B021": ("Ball", 0.021), } def load_single_file(mat_path, channel="DE"): """读取单个 CWRU .mat 文件,返回 (signal, meta)""" mat_path = Path(mat_path) data = sio.loadmat(str(mat_path)) # 找到包含 _DE_time / _FE_time / _BA_time 的键 key = None for k in data.keys(): if k.endswith(f"_{channel}_time"): key = k break if key is None: raise KeyError(f"未找到通道 {channel} 的数据,可用键:{list(data.keys())}") signal = data[key].ravel().astype(np.float32) # 从文件名推断标签 stem = mat_path.stem label, diameter = "Unknown", -1.0 for prefix, (l, d) in LABEL_MAP.items(): if prefix in stem: label, diameter = l, d break meta = {"file": stem, "channel": channel, "label": label, "diameter": diameter} return signal, meta这段代码的逻辑很直白:先遍历 .mat 的键找通道后缀,再按文件名前缀匹配标签。参数channel默认 DE,因为驱动端信号信噪比最好,做故障诊断时最常用。ravel()把列向量压平,避免后续 reshape 时维度对不上。astype(np.float32)是习惯性操作,CWRU 原始数据是 double,转 float32 能省一半内存,对深度学习训练没影响。
2.3 采样频率与转速信息的处理
CWRU 数据分 12kHz 和 48kHz 两套采样率,转速有 1730、1750、1772、1797 rpm 四种。导入包如果不记录这些元信息,后面做阶次分析或角域重采样时就得回去翻原始文档。我的做法是在 meta 里加fs和rpm字段,从文件名或目录名解析。比如目录结构是12k_Drive_End/就设 fs=12000,48k_Drive_End/就设 fs=48000。转速信息通常在文件名里没有,需要根据载荷推断:0 马力对应 1797 rpm,1 马力对应 1772 rpm,2 马力对应 1750 rpm,3 马力对应 1730 rpm。这个映射关系写死在配置里,比每次手动查表靠谱。
提示:如果你下载的数据集目录结构和本文不一致,先别急着改代码,用
sio.loadmat打印一下 keys,确认变量命名规律再动手。
3. 从 .mat 到训练张量:导入包的完整落地流程
3.1 批量加载与标签编码
单文件读取跑通后,下一步是批量加载整个数据集。这里有个容易忽略的点:CWRU 各类故障的样本数量不均衡,正常样本和故障样本比例大概 1:3,如果直接按文件切分训练测试集,会出现某类故障在测试集里一个样本都没有的情况。我一般用分层抽样,按 (label, diameter) 组合分层,保证每类在训练和测试里都有代表。
from sklearn.model_selection import train_test_split import numpy as np def load_dataset(root_dir, channel="DE", test_size=0.3, random_state=42): """批量加载 CWRU 数据集,返回分层划分后的数组""" root = Path(root_dir) signals, labels, metas = [], [], [] for mat_file in root.rglob("*.mat"): try: sig, meta = load_single_file(mat_file, channel=channel) signals.append(sig) labels.append(f"{meta['label']}_{meta['diameter']}") metas.append(meta) except KeyError: continue # 跳过不含目标通道的文件 # 按最短长度截断,保证能堆成矩阵 min_len = min(len(s) for s in signals) X = np.stack([s[:min_len] for s in signals]) # 标签编码 unique_labels = sorted(set(labels)) label_to_idx = {l: i for i, l in enumerate(unique_labels)} y = np.array([label_to_idx[l] for l in labels]) # 分层划分 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=test_size, random_state=random_state, stratify=y ) return X_train, X_test, y_train, y_test, label_to_idxmin_len截断是无奈之举,因为不同文件的采样点数可能差几个点,不截断np.stack会报错。stratify=y保证分层,random_state固定后结果可复现。label_to_idx返回出去,后面画混淆矩阵时要用。
3.2 信号预处理:归一化与分段
原始振动信号幅值范围在 ±5g 左右,直接送进网络收敛慢。导入包可以顺带做 z-score 归一化,按通道全局减均值除标准差。另一个常见操作是分段,把长信号切成 1024 或 2048 点的窗口,重叠率 50%。分段函数我一般放在导入包里,因为窗口长度和重叠率是实验超参,放在数据层比放在模型层更灵活。
def segment_signal(signal, window_size=1024, overlap=0.5): """将长信号切分为重叠窗口""" step = int(window_size * (1 - overlap)) segments = [] for start in range(0, len(signal) - window_size + 1, step): segments.append(signal[start:start + window_size]) return np.stack(segments) def normalize_signal(signal): """z-score 归一化""" return (signal - signal.mean()) / (signal.std() + 1e-8)1e-8是防止除零,虽然 CWRU 信号不会全零,但养成习惯没坏处。分段后的形状是 (num_segments, window_size),可以直接喂给 1D-CNN 或 LSTM。
3.3 与 PyTorch Dataset 对接
如果你用 PyTorch,导入包最后一步是包一个 Dataset 类。这样 DataLoader 的多进程加载和 shuffle 都能直接用。
import torch from torch.utils.data import Dataset class CWRUDataset(Dataset): def __init__(self, X, y): self.X = torch.tensor(X, dtype=torch.float32).unsqueeze(1) # 加通道维 self.y = torch.tensor(y, dtype=torch.long) def __len__(self): return len(self.y) def __getitem__(self, idx): return self.X[idx], self.y[idx]unsqueeze(1)把 (N, L) 变成 (N, 1, L),符合 Conv1d 的输入要求。这一步如果忘了,训练时第一个报错就是维度不匹配,血泪经验。
4. 导入 CWRU 数据时最容易踩的五个坑
4.1 现象:读取 .mat 报 “Unknown mat file type”
原因:CWRU 官网下载的某些文件是 MATLAB v7.3 格式,底层是 HDF5,scipy.io.loadmat 不支持。解决:用h5py读,或者用mat73库。我一般先试 scipy,捕获异常后切 h5py。
try: data = sio.loadmat(path) except NotImplementedError: import h5py with h5py.File(path, "r") as f: data = {k: np.array(f[k]) for k in f.keys()}4.2 现象:通道键名找不到,比如没有 X105_DE_time
原因:不同批次的 CWRU 文件变量命名不一致,有的用 “DE_time” 后缀,有的用 “_DE_time”,还有的用 “X105_DE_time”。解决:不要硬编码键名,用后缀匹配。上面load_single_file里的k.endswith(f"_{channel}_time")就是干这个的。如果还找不到,打印所有 keys 人工确认。
4.3 现象:训练集准确率 99%,测试集 60%
原因:分段时把同一段信号的不同窗口分到了训练和测试集,造成数据泄漏。解决:分段要在划分之后做,或者按文件划分而不是按窗口划分。我一般先按文件分 train/test,再各自分段。
4.4 现象:48kHz 数据做 FFT 后频率轴对不上
原因:导入时没记录采样频率,默认按 12kHz 算。解决:meta 里必须带 fs,画频谱时用np.fft.rfftfreq(len(signal), 1/fs)。
4.5 现象:内存爆了,加载 48kHz 全量数据直接 OOM
原因:48kHz 文件单个就有几十万点,全量加载成 float64 矩阵轻松超过 8GB。解决:用 float32,或者用生成器逐文件加载,不要一次性 stack。如果做实验,先拿 12kHz 子集跑通再上全量。
注意:CWRU 数据集里正常样本的文件名有的叫 “Normal”,有的叫 “NORMAL”,大小写敏感会导致标签匹配失败。统一转小写再匹配。
5. 用导入包做一次可复现的基线实验
5.1 最小验证:加载 + 分段 + 训练一个 1D-CNN
导入包写完后,必须用一个端到端实验验证它没毛病。我一般用 12kHz 驱动端数据,取 1024 点窗口,50% 重叠,搭一个三层 1D-CNN,跑 20 个 epoch,看测试集准确率能不能到 95% 以上。如果低于 90%,先查数据泄漏和标签对齐,再调模型。
import torch.nn as nn class BaselineCNN(nn.Module): def __init__(self, num_classes): super().__init__() self.net = nn.Sequential( nn.Conv1d(1, 16, kernel_size=15, stride=2, padding=7), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(16, 32, kernel_size=7, stride=2, padding=3), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(32, 64, kernel_size=3, stride=2, padding=1), nn.ReLU(), nn.AdaptiveAvgPool1d(1), nn.Flatten(), nn.Linear(64, num_classes), ) def forward(self, x): return self.net(x)AdaptiveAvgPool1d(1)把任意长度压成 1,这样换窗口大小不用改网络结构。kernel_size=15是第一层大感受野,对振动信号里的冲击成分敏感。
5.2 验证导入包正确性的三个检查点
第一,标签分布检查:打印每个类别的样本数,确认没有某类为 0。第二,信号可视化:随机抽三类信号画时域波形,正常信号幅值均匀,故障信号有明显周期性冲击。第三,频谱检查:对正常和故障信号各做 FFT,故障信号在轴承特征频率处应有峰值。这三个检查过了,导入包基本可信。
5.3 一个具体技巧:用阶次分析替代 FFT
CWRU 数据转速有波动,直接 FFT 频谱会 smear。我习惯在导入包里加一个角域重采样函数,用转速信息把时域信号转成角域,再做 FFT 得到阶次谱。这样故障特征阶次固定,不受转速影响。实现上需要先估计转速,可以用希尔伯特变换提取包络,再做 FFT 找转频。这个技巧在变转速工况下特别管用,但代码量比普通 FFT 多,建议先把基础导入跑通再上。
我自己做 CWRU 相关实验时,导入包改过至少五版,每次都是因为遇到新的文件命名或格式问题。后来学乖了,所有解析逻辑都写成配置驱动,新增数据只改配置不改代码。如果你打算长期用 CWRU 做实验,建议把导入包当基础设施维护,别每次临时写脚本。希望帮到你。
本文还有配套的精品资源,点击获取