ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CWRU轴承数据集实战指南:从文件解析到机器学习

CWRU轴承数据集实战指南:从文件解析到机器学习 简介凯斯西储大学(CWRU)轴承数据集是旋转机械故障诊断领域广泛使用的公开基准数据。这个数据包将原始mat数据、整理好的Python封装程序及使用说明整合在一起解决大家重复下载、格式杂乱、预处理繁琐的痛点。使用者可调用cwru.CWRU接口选择12kHz驱动端故障数据按0~3hp负载对应1797/1772/1750/1730 rpm转速以及300~700之间的信号长度参数一键划分训练集与测试集获得0~15共16类故障标签并直接映射到滚动体、内圈、外圈等具体故障名称及多种故障直径非常适合故障诊断、机器学习分类研究的初学者与工程师。整个压缩包共172个文件以165个.mat数据文件为核心配合3个Python脚本、2个.pyc与2个txt说明文档整体大小约240.31MB目录结构清晰离线可用。目前已有1064人学习浏览是一份开箱即用的高价值参考资料。1. CWRU轴承数据集是故障诊断绕不开的“标准答案”先看清它再动手CWRU轴承数据集是故障诊断圈子里绕不开的“标准答案”论文按它出结果竞赛用它当基准新手入门也几乎都是从它开始。它提供的是真实电机轴承在电火花加工出的单点故障下的振动信号不是合成数据采样率、故障直径、负载工况都标得清清楚楚。这份资源在原始采集数据之外多配了一套Python整理程序和一份能直接照着跑的使用说明等于把官网那堆让人头大的.mat文件掰开揉碎摆平了。适合刚接触振动诊断的工程师、要做故障分类方向的学生前提是你愿意先把工况表读明白再往上堆模型。2. 数据包结构与工况表先看懂.mat里存的是什么再做整理2.1 实验台与故障注入方式这套数据来自凯斯西储大学的电机轴承实验台电机驱动端装的是SKF 6205深沟球轴承风扇端是SKF 6203。故障不是自然磨损出来的而是用电火花在轴承零件上直接打出单点凹坑位置分为内圈、外圈、滚动体三类故障直径又分0.007、0.014、0.021英寸三档。电机负载有0、1、2、3 HP四挡转速对应从1797 RPM逐渐降到1720 RPM左右。对这个数据集来说故障注入方式和负载配置就是标签体系的底层来源。分类任务里常见的inner、outer、ball、normal这四个类别其实就是在这个实验条件下定义的。常有人忽略“负载”这个维度直接拿所有负载的数据混在一起切训练集和测试集这种做法在刷准确率时没问题一旦你把模型部署到现场换个负载立刻打回原形。所以整理数据的第一步不是急着读信号而是把工况维度先梳理清楚。2.2 通道变量与采样率DE、FE、BA到底指什么每个.mat文件里存的不是一个数组而是好几个同长度的通道变量。文件名形如X097_DE_time、X097_FE_time、X097_BA_time命名的规律是X 文件编号 通道名 _time。通道名采集位置常见用途DE驱动端加速度传感器驱动端轴承故障诊断的主信号FE风扇端加速度传感器风扇端故障或做对照实验BA电机基座加速度传感器结构传递路径分析采样率方面常见套餐是12 kHz驱动端数据和48 kHz驱动端数据。12 kHz数据里DE、FE、BA三通道齐全48 kHz数据采样点数更密适合做高频包络分析但文件体积也大得多。基座信号BA在部分场景下反而是绕过电机自身振动干扰的好选择做故障特征频率提取时值得留一份备用。还有一个容易漏掉的通道X097_RPM。它记录了这段信号对应的实时转速。官网说明里给出的转速是额定值但实际每次录信号转速有小幅波动整理程序里保留了RPM字段后面做故障特征频率计算时用的是实际转速而不是额定转速这点对新手很关键。2.3 编号与工况的对应关系不能凭文件名猜官网下载页面附带了一张Excel表里面把每个文件编号对应的故障位置、故障直径、负载都列清楚了。例如97号到100号对应正常工况的0到3 HP负载105号附近是内圈0.007英寸故障的一组数据。这张表是整套数据的“身份证”整理程序里把它的核心字段抽出来维护成了一张META.csv这样后续所有脚本都能通过文件编号反查工况。你可能会想文件名里不是已经写了故障类型吗其实并没有。.mat变量名只有X105_DE_time这样的格式不写故障位置也不写直径。如果不拿编号去对照工况表你看到的只是一堆数字序列根本分不清谁是内圈谁是外圈。这也是数组整理程序的第一个价值把编号自动翻译成可读的工况标签。3. Python整理程序解读从.mat到规整DataFrame的三步流水线3.1 第一步用scipy.io读取.mat文件配套程序的核心读盘函数用scipy.io.loadmat实现逐通道提取数据。这里有一个细节loadmat返回的是字典键名就是.mat里的变量名但不同版本的MATLAB文件可能带__header__等附加键必须用正则把真正的信号通道筛出来。from pathlib import Path import scipy.io as sio import re import numpy as np def read_cwru_mat(mat_path): 读取单个 CWRU .mat 文件返回 DE/FE/BA 通道和转速 mat sio.loadmat(str(mat_path)) channels {} for key in mat.keys(): # 变量名形如 X097_DE_time / X097_FE_time / X097_BA_time if re.fullmatch(rX\d_(DE|FE|BA)_time, key): channel_name key.split(_)[1] channels[channel_name] mat[key].flatten() rpm None for key in mat.keys(): # 转速变量形如 X097_RPM if re.fullmatch(rX\d_RPM, key): rpm float(mat[key].flatten()[0]) break return channels, rpm这段代码做了三件事遍历字典里的所有键用re.fullmatch精确匹配通道名格式把匹配到的数据从二维列向量拉平成一维数组最后单独抽出转速。之所以用flatten()而不是reshape(-1)是因为loadmat读出来的数组默认是N x 1的列向量直接拿去切窗会出现意外的二维形状后面拼接特征矩阵时容易翻车。3.2 第二步文件编号反查工况表读出来只是拿到了一堆数组还缺标签。第二步是用文件名里的编号去查META.csv把故障位置、直径、负载这些元信息拼进来。import pandas as pd META_PATH Path(programs/META.csv) def load_meta(): # META.csv 列file_id, fault, diameter, load, position return pd.read_csv(META_PATH) def parse_file_id(name): 从 X105_DE_time 这类变量名里取出文件编号 105 m re.search(rX(\d)_, name) return int(m.group(1)) if m else None这里要注意的是position列。外圈故障的数据里故障点相对载荷区的方向不同官网用“中心”“正交”“反对”来描述.mat变量名里也能看到OR相关的方向后缀。整理程序把它单独存成一列而不是简单归到outer一个类别里就是为了保留这个维度的信息。你也可以在后续建模时决定是丢弃它还是把它当作特征。3.3 第三步切窗、构造标签、落盘原始信号每条长达十几万点直接整条喂给分类器不现实常规做法是滑窗切片。程序里默认window_len2048点、stride1024点12 kHz采样率下每个窗口约0.17秒既够算包络谱又保证样本量充足。标签由fault和diameter组合生成正常为0内圈0.007英寸为1内圈0.014英寸为2其余依此类推。def build_dataset(root_dir, window_len2048, stride1024): rows [] meta load_meta() mat_files sorted(Path(root_dir).rglob(*.mat)) for mat_path in mat_files: file_id parse_file_id(mat_path.name) if file_id is None: continue row meta[meta[file_id] file_id] if row.empty: continue # 工况表里没有对应记录跳过 channels, rpm read_cwru_mat(mat_path) if DE not in channels: continue sig channels[DE] for start in range(0, len(sig) - window_len, stride): window sig[start:start window_len] rows.append({ file_id: file_id, fault: row[fault].iloc[0], diameter: row[diameter].iloc[0], load: row[load].iloc[0], rpm: rpm, signal: window, label: make_label(row[fault].iloc[0], row[diameter].iloc[0]), }) return pd.DataFrame(rows)切窗时range的上界是len(sig) - window_len这是为了避免最后一个不完整窗口被硬塞进来。窗口长度2048点不是拍脑袋定的它刚好是2的11次方做FFT和WELCH功率谱密度估计时不需要额外补零频率分辨率约5.86 Hz对轴承故障特征频率来说完全够用。如果你要识别更低频的保持架故障FTF可以把window_len加到4096甚至8192。落盘环节程序提供了两个选项一是把DataFrame整体存成parquet格式保留signal整列二是把每段窗口单独np.save成.npy文件后续训练时用np.load流式读取。我一般推荐前者因为parquet列式存储对signal这种定长数组支持不错而且能和pandas无缝衔接。4. 特征提取与实验划分把振动信号变成能进模型的特征矩阵4.1 时域统计特征峭度、峰值因子、RMS的取舍原始振动信号直接进线性模型效果有限常规做法是先算一组时域统计特征再拼成特征矩阵。最常用的四个特征均方根值RMS反映能量大小峰值因子反映冲击特性峭度反映信号分布尾部厚度波形因子用来区分平稳振动和冲击振动。import numpy as np from scipy import stats def time_domain_features(x): rms float(np.sqrt(np.mean(x ** 2))) peak float(np.max(np.abs(x))) rms rms if rms 1e-12 else 1e-12 # 除零保护 return { rms: rms, peak: peak, crest_factor: peak / rms, # 峰值因子 kurtosis: float(stats.kurtosis(x)), # 峭度Fisher 定义 waveform_factor: rms / float(np.mean(np.abs(x)) 1e-12), }峭度这里用的是scipy.stats.kurtosis的Fisher定义即正态分布峭度为0而不是经典教材里正态分布峭度为3的Pearson定义。你在对比别人论文里的峭度数值前先确认他用的哪种定义否则对不上是正常的。RMS出现极小时加一个1e-12的epsilon保护不然峰值因子会除零报错。这套特征对正常轴承和点蚀故障的区分非常灵敏内圈故障信号里冲击成分多峭度通常明显偏高。4.2 频域特征包络谱与故障特征频率对照表时域特征只看统计量分不清到底是内圈还是外圈故障这时候要用频域信息。轴承故障信号的特点是高频共振被低频的故障脉冲调制直接对原始信号做FFT看不出明显峰值必须先做希尔伯特变换取包络再对包络做功率谱这就是包络谱分析。from scipy.signal import hilbert, welch def envelope_spectrum(x, fs12000): analytic hilbert(x) envelope np.abs(analytic) freq, psd welch(envelope, fsfs, npersegmin(len(envelope), 1024)) return freq, psdhilbert返回的是解析信号实部是原信号、虚部是希尔伯特变换结果取绝对值就得到了包络。welch用平均周期图法估计功率谱nperseg默认取窗口长度和1024的较小值这样短窗口信号不会报错。计算完成后在频谱里找峰值位置对照理论故障特征频率就能判定故障位置。以SKF 6205为例滚动体数9个节圆直径39.04 mm滚动体直径7.94 mm接触角近似0度。设转频fr RPM / 60各故障的特征频率按下表估算故障位置特征频率近似系数×fr对应约30Hz转频的值外圈 BPFO0.5×n×(1 - d/D×cosα)×fr3.58约107 Hz内圈 BPFI0.5×n×(1 d/D×cosα)×fr5.42约162 Hz滚动体 BSFD/(2d)×(1-(d/D)²)×fr2.36约71 Hz保持架 FTF0.5×(1 - d/D×cosα)×fr0.40约12 Hz实际频谱里看到的峰值不会精确等于理论值因为转速有波动、接触角不严格为零、轴承加工有公差通常允许±1%到±2%的偏差。如果你发现峰值偏差超过5%先查转速是不是用的额定值而不是实测RPM通道值。4.3 训练集与测试集划分按文件分组而不是按窗口随机切很多人在CWRU上翻车就翻在数据划分。如果直接把所有窗口合在一起随机切训练集和测试集同一个文件切出来的相邻窗口会同时出现在两边模型等于先背了答案测试准确率虚高到0.99以上换个负载立刻崩盘。正确做法是按file_id分组整个文件的窗口要么全在训练集、要么全在测试集。from sklearn.model_selection import GroupShuffleSplit def split_by_file(X, y, groups, train_size0.7, random_state42): gss GroupShuffleSplit(n_splits1, train_sizetrain_size, random_staterandom_state) train_idx, test_idx next(gss.split(X, y, groupsgroups)) return train_idx, test_idxGroupShuffleSplit的第三参数groups传file_id数组它保证同一个组的数据不会被拆散。random_state固定成42不是为了玄学而是为了让实验可复现。如果你想验证模型对不同负载的泛化能力还可以用“留一负载法”拿0、1、2 HP的数据训练3 HP的数据测试这种做法在故障诊断论文里很常见也更贴近现场工况迁移的真实场景。5. 常见问题与排查五个翻车现场和处理办法翻车现场一训练集准确率99%换负载后准确率掉到70%。现象同一个模型随机划分时测试集准确率极高一旦按负载划分立刻大幅下降。原因随机划分把同一文件的相邻窗口同时分到训练和测试集数据严重泄漏模型学的是窗口间的局部噪声而非真正的故障特征。解决一律按file_id分组用GroupShuffleSplit或直接按负载划分。做完划分后检查一下训练集和测试集有没有重复的file_id这一步值得每次跑实验前都验证一遍。翻车现场二把12 kHz和48 kHz的数据混在同一个数据集里训练直接报维度错误。现象报错信息通常是ValueError: operands could not be broadcast或者特征矩阵行数对不上。原因12 kHz和48 kHz的同一段信号长度差4倍切窗后每个窗口的样本点数完全不同特征维度自然不一致。解决要么只用12 kHz子集做全套实验要么把48 kHz数据重采样到12 kHz再合并。重采样用scipy.signal.resample注意先滤波再抽点避免混叠。翻车现场三外圈故障的标签错乱同一类故障准确率特别低。现象outer类别被分成了好几簇混淆矩阵里outer互相混。原因外圈故障有“中心”“正交”“反对”三种位置故障点相对载荷区的角度不同振动传递路径不同包络谱特征差异明显。把它们当成同一个类特征分布变成多峰模型自然学不好。解决整理阶段保留position字段建模时要么把位置作为独立类别要么固定只用某一个位置的样本做训练。我见过一些论文把外圈三个位置当作三个类别准确率反而更高因为物理上它们确实是三种不同工况。翻车现场四包络谱里找不到理论故障频率的峰值。现象明明用了hilbert和welch频谱里却找不到对应107 Hz或162 Hz的尖峰。原因转速用的额定值而不是实测值转频偏差导致特征频率偏移另一个常见原因是窗口太短频率分辨率不足相邻频率分量糊在一起。解决先用RPM通道算出实际转频再代入公式窗口长度至少设1024点做低速轴承分析时建议4096点起步。另外原始信号里有直流偏置先做x - np.mean(x)去均值再算包络否则零点附近的低频分量会盖住故障峰值。翻车现场五loadmat直接报错提示不是有效的MAT文件。现象scipy.io.loadmat抛出NotImplementedError或ValueError但文件在MATLAB里能正常打开。原因CWRU官网部分文件用MATLAB 7.3及以上版本保存用的是HDF5格式loadmat老版本不支持。解决用h5py读取这类文件或者先确认文件头是MATLAB 5.0 MAT-file还是HDF5。整理程序里我加了一个格式探测分支按文件头自动选择loadmat还是h5py你在本地复现时如果报错优先检查这一层。6. 进阶把整理好的数据接到机器学习分类流程整理程序输出的parquet文件已经是一张标准的宽表每行一个窗口样本含signal数组、工况标签和特征列。进阶用法是跳过手工特征直接把signal数组当输入交给随机森林或一维卷积网络做端到端分类。这里用一个随机森林跑通基线作为示范。import pandas as pd import numpy as np from sklearn.ensemble import RandomForestClassifier from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler df pd.read_parquet(cwru_processed.parquet) X np.stack(df[signal].values) # 形状(样本数, 2048) y df[label].values train_idx, test_idx split_by_file( X, y, groupsdf[file_id].values, train_size0.7) model make_pipeline( StandardScaler(), RandomForestClassifier(n_estimators200, random_state42) ) model.fit(X[train_idx], y[train_idx]) print(Test Accuracy:, model.score(X[test_idx], y[test_idx]))np.stack把signal列里的多个一维数组堆叠成二维矩阵这是把窗口数据送入sklearn的标准姿势。StandardScaler对每个特征维度做零均值单位方差归一化对树模型影响不大但如果你后面换用SVM或KNN就有必要。随机森林的优势在于不需要调参就能拿到一个可靠的基线准确率200棵树在这个数据规模下训练很快。验证环节我习惯跑三件事第一打印分类报告看在normal和inner之间有没有误判第二把模型对某个外圈故障窗口的预测概率画出来确认不是靠类别不平衡硬猜第三用留一负载法重新训练一版记录准确率下降幅度。这三步走完对模型到底学到什么才算心里有底。CWRU这套数据最大的优点也是最大的坑它太干净了故障是打出来的标准坑不存在实际工况里的变转速、变负载和噪声干扰。用它可以验证算法流程但别把它的准确率当成现场部署的预期值。从那以后我每次拿CWRU做基准实验都强制走一遍按文件分组划分、检查重复file_id的小脚本再顺手看一眼测试集里每个类别的样本数是否均衡。这套习惯帮我挡掉了很多次“高准确率”的假象希望帮到你。本文还有配套的精品资源点击获取
返回列表