ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

帕德博恩轴承数据集全解析:命名规则、故障类型与避坑指南

帕德博恩轴承数据集全解析:命名规则、故障类型与避坑指南 简介面向轴承故障诊断研究者的帕德博恩数据集解读文档系统梳理健康K0##、外圈KA##、内外圈复合KB##、内圈KI##四类数据的分组与命名规则并结合实例拆解 N09_M07_F10_K001_1 等文件名的六段含义。文档同时对比电火花、钻孔、电动雕刻等人工损伤与加速寿命实验真实损伤两类故障来源说明各自对应组别与适用场景。针对 MATLAB 读取需求重点介绍 Info、X、Y、Description 四部分结构明确 Y 中电流、转速、温度、扭矩、振动等信号的存放位置便于直接提取与建模。资源为单个 docx 文件大小 1.59MB内容紧凑但覆盖要点完整已有 3961 人学习下载适合刚接触该数据集、需要快速理清数据组织与字段语义的初学者也可作为故障诊断实验前的数据预处理参考。1. 帕德博恩轴承数据集故障诊断绕不开的多模态数据源头做轴承故障诊断的人绕不开帕德博恩数据集。这份多模态数据集把健康、外圈故障、内圈故障和内外圈复合故障四种状态都打包好了每个文件里能同时拿出电流、振动、转速、扭矩、温度等多路信号在公开资源里相当难得。我最早拿它练手时光弄懂文件名和 MATLAB 结构体就折腾了半天后来发现不少论文里对它的使用方式其实都有隐患。这篇笔记把命名规则、故障类型、字段结构和踩过的坑一次讲清适合准备拿它做实验、写论文或搭故障诊断 Demo 的从业者。2. 命名规则这样读N/M 编码不是日期是转速与负载2.1 文件名五段拆解帕德博恩数据集的文件名长这样N09_M07_F10_K001_1。第一次看到它的人十有八九会把它当成“2009 年 7 月采集”——网上某个中文解读确实这么写但实际对照数据列出来看这个说法站不住脚。整个文件名用下划线分隔一共五段N09转速编码。900 转/分对应 N091500 转/分对应 N15规律是转速除以 100 后取两位。M07负载编码。负载 0.7 对应 M07负载 0.1 对应 M01基本是负载值乘以 10 后补零。F10机器类型标识整份数据集统一用 F10。K001数据组编号K 开头后接三位组号。末尾数字 1该组内的文件编号范围 1~20。所以N09_M07_F10_K001_1的真实含义是900 转/分、负载 0.7、径向力 1000 N 条件下K001 健康轴承的第 1 个记录文件。N15_M01_F10_K001_1则是 1500 转/分、负载 0.1 下的同组第 1 个文件。之所以有人说它“六部分”是把 N09 和 M07 再拆成转速、负载两个语义字段。实际文件名就是五段写解析脚本时按五段处理即可。2.2 四类前缀对应四类状态前缀决定了轴承状态整份数据集一共四类前缀含义数据组数具体组号K0健康轴承6K001~K006KA外圈故障12KA01、KA03~KA09、KA15、KA16、KA22、KA30KB内外圈复合故障3KB23、KB24、KB27KI内圈故障11KI01、KI03~KI05、KI07、KI08、KI14、KI16~KI18、KI21注意 KA 和 KI 的组号不是连续的中间缺了很多号比如 KA02 和 KA10~KA14 不存在。缺失原因一般是采集过程中某些样本被剔除或者编号留给了其他实验。写论文列数据分组时别想当然写“KA01~KA12”直接抄上表的组号最稳。每组数据下都有两种工况的文件。以 K001 为例N09_M07_F10_K001_1~_20共 20 个文件N15_M01_F10_K001_1~_20也有 20 个加在一起 40 个 .mat 文件其他组同理。2.3 工况参数只有两套别自己脑补整份数据集在文件名里出现的负载/转速组合只有两套工况代号转速负载径向力N09_M07900 转/分0.71000 NN15_M011500 转/分0.11000 N这意味着想研究“高转速高负载”或者“中转速中负载”这些组合时这份数据集给不了你只能换数据源。它的设计意图是让每个数据组都在两种工况下各采 20 段方便做跨工况泛化实验而不是覆盖全工况平面。提示个别二手资料把 N/M 解释成日期和月份属于以讹传讹。判断依据很简单——如果 N 是年份整个数据集只会有 2009 和 2015 两个年份且恰好和 900/1500 两种转速一一对应这种巧合在实际采集里几乎不可能发生。2.4 用脚本批量解析文件名手工看几十个文件还行一旦要用全部 1200 多个文件就得写脚本。最通用的做法是用正则把文件名拆成结构化字段import re from pathlib import Path pattern re.compile( r^(N\d)_(M\d)_(F\d)_([A-Z]\d)_(\d)$ ) def parse_filename(fname: str) - dict: m pattern.match(Path(fname).stem) if not m: raise ValueError(f无法解析文件名: {fname}) n_code, m_code, machine, group, idx m.groups() return { speed_code: n_code, load_code: m_code, machine: machine, group: group, file_idx: int(idx), speed_rpm: int(n_code[1:]) * 100, # N09 - 900 load: int(m_code[1:]) / 10, # M07 - 0.7 fault_type: group[:2], # K0 / KA / KB / KI }解析逻辑说明正则里N\d抓住 N09、N15M\d抓住 M07、M01[A-Z]\d负责 K001、KA01 这类混合编号。最后通过n_code[1:]取数字部分转成转速m_code[1:]转成负载。这样后面做数据清单时可以直接得到一个带fault_type、speed_rpm、load的表格按字段筛选、分组都很方便。参数说明正则要求最后一位文件编号必须是纯数字如果你的数据里有_1.mat和_1_re.mat这种二次采集文件正则要放宽成(\d[A-Za-z]*)。还有一点Path(fname).stem会去掉.mat后缀避免点号干扰匹配。2.5 先列清单再动手比什么都重要我处理这一类公开数据集有个习惯第一步永远是跑一遍文件名清单把所有组号、文件数、工况分布先打印出来确认和文档对得上再往下走。这一步能提前挡掉很多后面才爆的雷。root Path(data/Paderborn) # 按实际路径修改 rows [] for f in root.rglob(*.mat): rows.append(parse_filename(f.name)) import pandas as pd df pd.DataFrame(rows) print(df.groupby([fault_type, speed_rpm, load]).size())这段代码会把整份数据的分布按“故障类型 × 转速 × 负载”列出来一眼就能看出 KA、KI 各有多少文件、N09 和 N15 是否成对。如果某组只有一种工况说明文件拷贝不全趁早处理别等训练时报错才回头看。3. 故障类型与损伤工艺人工损伤和加速寿命实验决定了标签怎么打3.1 Table4人工制造损伤的三种工艺帕德博恩数据集里的人工损伤用的是三种明确标注的工艺EDM电火花损伤、Drilling钻孔、Electric engraver电动雕刻。三种方式模拟的是轴承运行中不同形态的表面缺陷损伤程度分成 1 级和 2 级。数据说明文档里给的两个例子很能说明问题KA01外圈故障OR损伤程度 1 级由电火花人工模拟损伤EDM。KI08内圈故障IR损伤程度 2 级由电动雕刻人工模拟损伤。这两个例子透露了一个关键信息同一类故障前缀下不同组之间的损伤工艺可能完全不同损伤位置和扩展形态也不一样。拿 KI08 训练出来的模型直接拿去预测另一个工艺做的 KI03效果很可能会掉一截。3.2 Table5加速寿命实验生成的真实损伤加速寿命实验是另一条损伤来源。这部分样本不是人工在表面刻缺陷而是让轴承在超负荷条件下连续跑直到出现真实裂纹、剥落和磨损。和人工损伤相比真实损伤的故障特征更接近现场采集的轴承振动早期微弱、逐渐扩展、伴随非平稳成分。从组号分布看前缀总组数人工损伤组数加速寿命组数K06--KA1275KB303KI1156也就是说KB 复合故障的三组KB23、KB24、KB27全部来自加速寿命实验没有人工损伤版本。如果想单独研究“真实损伤下的复合故障”KB 是唯一选择千万别在这三个组上套用人工损伤的标签描述。3.3 标签体系怎么设计分清人工损伤和真实损伤直接决定你怎么写标签文件。最省事的做法是只按“健康 / 外圈 / 内圈 / 复合”做四分类。这种情况K0 是健康样本KA 是外圈KI 是内圈KB 是复合标签直接用前缀映射。稍微细一点的实验会按“损伤工艺 位置”打标签。比如同样标注为 KA 外圈故障EDM 人工损伤和加速寿命真实损伤在频谱能量分布上差很多混在一起打同一个标签会拉低模型上限。建议至少给训练数据加一列damage_source能确认人工的写artificial确认加速寿命的写run_to_failure再用fault_type damage_source做分层抽样。3.4 批量生成标签表用第 2 章的解析函数可以直接把全数据集的标签表生成出来fault_map { K0: healthy, KA: outer, KI: inner, KB: combined, } df[label] df[fault_type].map(fault_map) print(df.groupby([label, group]).size())这段代码只做两步先按前缀映射出四类标签再按组号校验数据量。映射关系放在fault_map字典里想扩展五分类、六分类标签时只改字典即可。label列生成后建议直接to_csv(labels.csv)存一份后面所有训练脚本统一读它不要在每个脚本里各写一套标签逻辑。人工/加速寿命的区分不能靠文件名猜因为两种来源的文件命名格式完全一样。可以先建立一个确认清单# 已确认的人工损伤组号来自 Table4 manual_groups {KA01, KI08} # KA01EDM外圈KI08电动雕刻内圈 df[damage_source] np.where( df[group].isin(manual_groups), artificial, unknown, ) print(df.groupby([fault_type, damage_source]).size())输出里能看到人工组和未知组各占多少剩下unknown的部分就是对照 Table5 逐个补充的。这个过程没有捷径必须查文档。3.5 损伤程度是隐藏信息别浪费数据说明文档只明确写了损伤程度 1 级、2 级但没有对所有组逐一标注。如果你的实验目标不是简单分类而是“故障严重程度评估”那这个字段就是关键特征。建议在建数据清单时把damage_level单独拉成一列能查到就填查不到就留空。注意损伤程度字段在部分资料里是缺失的用之前一定要逐组核对说明文档盲填会导致训练标签错误而且这类错误很隐蔽模型 loss 照样下降但结果完全不可信。4. MATLAB 文件内部结构先从 Y 结构体里取回多模态信号与工况参数4.1 Info、X、Y、Description 的分工每个 .mat 文件打开后命名空间里通常有四个变量Info、X、Y、Description。Info记录文件级别的元信息比如采样配置、文件版本一类。在多数分析流程里用不上。X在部分文件中是空变量或者仅作辅助。数据说明文档强调“所有数据均存储在 Y 这个结构体下从 Y 下取数据即可”所以别在 X 上浪费时间。Y真正的核心结构体下面挂着一堆信号字段。Description文本描述写论文时需要确认字段含义时回头看它。一句话总结分析前直接盯住 Y其他变量最多用来交叉验证。4.2 Y 字段逐项说明Y 结构体下包含的字段按类型和作用分成三组字段名含义备注force负载和文件名里的 M 编码对应phase_current_1电机电流数据 1第一组电流信号phase_current_2电机电流数据 2第二组电流信号speed转速采集时的转速torque扭矩扭矩信号temp温度数据大小 1×4四个温度测点vibration_1s振动数据1 秒长的振动段这套字段组合就是典型的“多模态”有振动、电流两个主要信号通道还有转速、扭矩、温度、负载这些工况参数。做故障诊断时最常见的组合是把vibration_1s和phase_current_1/2一起作为特征输入转速扭矩温度当辅助变量。4.3 用 MATLAB 读取 Y 里的信号自己写 MATLAB 脚本加载时重点是把 Y 解出来data load(N09_M07_F10_K001_1.mat); disp(fieldnames(data)); % 确认顶层变量名 info data.Info; desc data.Description; y data.Y; % 所有信号都在 Y 下 vib y.vibration_1s; cur1 y.phase_current_1; cur2 y.phase_current_2; spd y.speed; trq y.torque; tmp y.temp; frc y.force; disp(size(vib)); % 确认振动数据维度这段代码先把Y单独拿出来再按字段名取各个信号。data.Y是结构体直接y.vibration_1s就能取到对应数组。size(vib)这一步必须做因为不同条件下的振动数据行数和列数可能不一样先确认维度再写后续处理能少踩好几个坑。4.4 用 Python 读取注意结构体解包很多新项目直接用 Python 处理读取工具首选scipy.io.loadmat。但 MATLAB 结构体在 Python 里不会自动变成字典而是变成 NumPy 的 structured array取数逻辑跟 MATLAB 里不太一样import scipy.io as sio import numpy as np mat sio.loadmat(N09_M07_F10_K001_1.mat) print([k for k in mat.keys() if not k.startswith(__)]) y_raw mat[Y] # shape 通常是 (1, 1) vib y_raw[vibration_1s][0, 0] # 先解一层结构体 vib np.squeeze(vib) # 去掉多余的 1 维 cur1 y_raw[phase_current_1][0, 0] cur1 np.squeeze(cur1) print(vibration:, vib.shape, current1:, cur1.shape)逻辑说明mat[Y]返回的是 shape 为(1, 1)的结构化数组字段访问要写y_raw[vibration_1s]取出来还是个二维数组所以后面要跟[0, 0]才能拿到真正的信号数组。np.squeeze只是兜底把可能残留的 shape(1, n)拉直成一维。参数说明vibration_1s字段名末尾带_1s意思是这段振动是按 1 秒时长切出来的段。做深度学习输入时直接把这个一维数组当序列用即可想要二维时频图再对这段数据做短时傅里叶变换。4.5 多模态信号先对齐再拼接把vibration_1s和phase_current_1拼成多通道输入前先检查长度print(vib len:, vib.size, cur len:, cur1.size)振动和电流可能采样率不同、长度不同。如果长度不一致常见做法是截短到公共长度或者分别做特征提取再融合而不是硬性np.stack。这一条在帕德博恩数据集上特别容易翻车因为文件名里没有直接给出采样率不看数据就默认等长十有八九要报错。5. 加载实操与避坑MATLAB 到 Python 迁移中的五个常见问题5.1 把 N09_M07 当成采集日期工况直接选错现象按照某摘要里的说法把N09_M07_F10_K001_1理解成“2009 年 7 月采集”于是写论文时把 N09 当成时间变量工况分析全乱。原因这份数据集的 N/M 编码被二手资料错误转述成日期。实际上对照数据可知N09 对应 900 转/分N15 对应 1500 转/分M07 对应负载 0.7M01 对应负载 0.1。两者组合成唯一的两种工况。解决以原始文件列表和说明文档为准。拿任一组数据看N09_M07和N15_M01两种前缀在所有组里成对出现而“年份”不可能只出现两个值。解析文件名时直接映射转速和负载不要保留“日期”这个错误字段。5.2 scipy 读出来的 Y 不是字典取数路径绕晕人现象mat[Y]打印出来是一个ndarray以为数据存的是字典直接用mat[Y][vibration_1s]取数结果拿到一个 shape 为(1, 1)的嵌套对象再取一次才见真身。原因MATLAB 的 struct 被 scipy 转成了 structured ndarray字段访问和索引要交错使用很多人第一次接触就卡在这。解决统一封装一个取数函数内部把[0, 0]和squeeze处理掉业务代码里只调函数不裸写索引import numpy as np def load_paderborn_field(mat_file: str, field: str) - np.ndarray: mat sio.loadmat(mat_file) y_raw mat[Y] arr y_raw[field][0, 0] return np.squeeze(arr)调用时vib load_paderborn_field(xxx.mat, vibration_1s)一处封装到处复用能少写一大段重复的[0, 0]。5.3 振动和电流长度对不齐拼接直接报错现象np.stack([vib, cur1], axis1)报 shape 不匹配。或者不报错但特征矩阵里的行语义错位模型结果不稳定。原因不同信号采样率不同文件之间的振动段和电流段长度也不固定。文件名里没有直接给出采样率不能默认等长。解决先打印 shapes长度不一致时按短边截断min_len min(vib.size, cur1.size) vib vib[:min_len] cur1 cur1[:min_len]然后拼成双通道矩阵。更稳的做法是每段单独做时域/频域特征再把特征拼成向量绕开长度匹配问题。5.4 训练测试集切分时把同一组文件拆到两边准确率虚高现象训练集和测试集同时包含 K001 组的数据测试准确率 99%换到现场数据直接崩。原因同一个文件组如 K001下的 40 个文件来自同一轴承的连续采集时间相关性极强。随机切分会把高度相似的数据分到两边模型记住的是文件级噪声而不是故障模式。解决按组切分同一组的 40 个文件必须全部落在同一边。用GroupShuffleSplit或手动按组号划分保证训练时的组完全不出现在测试集from sklearn.model_selection import GroupShuffleSplit gss GroupShuffleSplit(n_splits1, test_size0.25, random_state42) train_idx, test_idx next(iter(gss.split(df, groupsdf[group])))这段代码的关键是把groupsdf[group]传给GroupShuffleSplit分出来的两份数据在组号上互不重叠。test_size0.25表示留 25% 的组做测试也可以按实际组数改成 0.3。想同时保证四类故障都在训练集里出现可以再结合stratify按label做分层组与组之间仍然不混切。5.5 不检查字段存在性加载到一半才报错现象循环处理几百个文件时中途抛 KeyError前面跑完的都白跑。原因不同批次的 .mat 文件字段可能不完全一致有的文件 Y 下没有某个字段直接按固定字段名取数就崩。解决在取数函数里先判断字段是否存在def get_field_safe(y_raw, field): if field in y_raw.dtype.names: arr y_raw[field][0, 0] return np.squeeze(arr) return Noney_raw.dtype.names能列出结构化数组的字段名列表这个判断成本极低。返回None表示该字段缺失调用方自行处理。数据工程先做防御再谈效率这种细节能省下不少半夜调 bug 的时间。6. 验证技巧按工况组划分数据集让准确率不虚高帕德博恩数据集的验证策略核心就一条按“组”切而不是按“行”切。K001 有 40 个文件N09 和 N15 各 20 个这 40 个文件必须整体进入训练集或测试集。前面给过GroupShuffleSplit的代码这里补一个实际效果对比。下面数字是示例具体取决于特征和模型。划分方式测试准确率示例泛化风险完全随机切分约 99%高同组数据泄漏按组切分同工况约 90% ~ 95%中工况内泛化按组切分跨工况约 80% ~ 90%低接近现场场景面子上随便写 99% 没有意义跨工况验证才能说明模型真的学到了故障模式而不是记住了采集批次。具体操作上我一般会为这份数据做三折验证第一折用N09_M07工况的全部文件训练N15_M01的全部文件测试第二折反过来第三折把两种工况混合但按组不重叠切分。三折的准确率取平均作为最终报告值。这么做的好处是能明确看到模型在两种工况下的表现差异如果跨工况准确率掉得厉害说明特征里混进了工况相关信息应该加工况自适应或者改用受转速影响更小的频带特征。最终训练脚本里建议把划分索引保存成文件比如split_train.npy、split_test.npy保证实验可复现。数据清洗和划分属于“没有后悔药”的环节——模型可以重训划分错了整个实验结论都要推翻。从那以后我每次拿到一份陌生数据集都强制先跑一遍文件名解析、字段检查和工况分布清单确认数据本身的逻辑闭环了才写训练代码再没在数据准备阶段翻过车。希望这份解读能帮你少走同样弯路。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

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