☰
帕德博恩轴承数据集实战:从MATLAB结构体到故障分类全流程
2026/10/14 2:29:14 网站建设 项目流程

简介:帕德博恩数据集详细解读文档面向轴承故障诊断领域的研究者与学习者,旨在帮助快速理解该经典多模态数据集的数据命名、故障类别与数据组织方式。资源共1个docx文件,压缩包1.59MB,内容以清晰的表格与示例展开,从K0##、KA##、KB##、KI##四类轴承状态入手,逐一说明健康轴承、外圈故障、内外圈复合故障和内圈故障的组别与文件命名规则,并划分人工损伤与加速寿命实验真实损伤两大类故障来源。同时详解每个文件内Info、X、Y、Description四个数据集,重点说明Y中所含电流、转速、温度、扭矩与振动等多模态信号字段。已有3961人学习下载,适合刚接触帕德博恩数据集、需要快速上手并使用MATLAB读取数据的诊断算法初学者。对照示例文件N09_M07_F10_K001_1,可清楚知道负载、转速等工况信息的编码位置,减少因数据名误解导致的建模错误,帮助顺利开展轴承故障识别与剩余寿命预测实验。

1. 帕德博恩数据集是什么:为什么它是轴承故障诊断绕不开的公开集

在故障诊断这个方向,公开数据集不算多,能拿来做基线、写论文、跑方案验证的更少,帕德博恩数据集就是多数人绕不开的那一个。它的特点是信号来自真实试验台而非纯仿真,轴承分成健康、人工加工损伤和真实损伤三类,标签、工况、采样频率都写在总表里,省去了大量整理数据的时间。想做设备预测性维护、旋转机械故障诊断方案验证,或者课题需要一份相对可信的评估数据,都能靠它快速跑通全流程。

但这份数据“好上手”不等于没有坑:信号是 MATLAB 结构体,标签分布在另一张总表里,不同流传版本的字段命名还不统一,很多人的第一步就卡在读文件和对应标签上。下面按实际用下来的完整路径讲一遍,从文件格式、可视化、数据划分到建模和避坑,目标是让新手照着能做出来,让熟手看到边界和参数。

2. 文件构成与标签体系:从 MATLAB 结构体到自己的故障清单

2.1 先摸清数据的组织方式:mat 文件、总表、工况三类信息

帕德博恩数据集最常见的发布形态是一个压缩包,里面有按轴承实例组织的一系列 .mat 文件,另外配一张总表。每一个 mat 文件对应一个轴承的测试信号,时长通常覆盖多段稳态工况;总表负责给出这个轴承属于健康、人工加工损伤还是真实损伤,损坏位置在内圈、外圈还是滚动体,以及对应的工况条件,比如转速和负载。

我拿到一份新版本的数据集时,习惯先做三件事而不是直接开读:解压后列目录,看文件名是否按规则命名;打开总表,看列名和类别取值;随机打开一个 mat 文件打印结构体字段。这三件事加起来五分钟,但能避免后面所有“字段对不上”的问题。因为帕德博恩数据集在流传中有多个整理版本,字段命名、文件组织、备注语言都可能不一样,网上很多教程里的字段名到你手上未必存在,最重要的不是死记字段,而是学会从文件本身识别结构。

还有一个容易忽略的点:总表里通常有故障类型和损坏位置的编码,不同整理者给的编码风格不一致。有的用数字,有的用英文缩写如 IR、OR、B 表示内圈、外圈、滚动体,还有的版本直接用中文备注。所以拿到数据第一步一定是把总表里这一列的唯一值打印出来,亲自确认一遍,再开始做标签映射,不要相信“所有版本都长一样”的说法。健康轴承的损伤位置列通常是空值,也需要单独处理。

2.2 用 Python 读取 MATLAB 结构体:从 loadmat 到 numpy 数组

很多人问我,为什么 scipy.io 读出来的东西和 MATLAB 里看到的不一样。因为 MATLAB 结构体在 Python 里会变成 numpy 的 structured array,访问方式不是直接取一个字段名就完事,而是先取到结构体记录,再通过字段名访问。我一般先打印结构体里的字段再取值,避免对着不存在的字段名空报错:

import scipy.io as sio import numpy as np # 读取 mat 文件 mat = sio.loadmat('Bearing_00.mat') # 打印顶层字段,跳过 MATLAB 自动生成的 __...__ 键 for key in mat.keys(): if key.startswith('__'): continue print(key, type(mat[key]), mat[key].shape)

这一步会输出类似X (n_samples, n_channels)或vibration (1, 1)的结果。如果输出 shape 是(1,1),不要急着当标量处理,它往往是一个 numpy.void 结构体,里面还要再掏一层。继续做字段探测:

# 如果打印出来是 void 结构体,继续取内部字段 for key in mat.keys(): if key.startswith('__'): continue val = mat[key] # MATLAB struct 在 Python 里表现为 np.void if hasattr(val, 'dtype') and val.dtype.names is not None: print('结构体字段:', val.dtype.names) signal = val[0, 0]['X'] # 假设字段名是 X,以实际打印为准 print('信号 shape:', signal.shape)

这段代码的核心是确认两类信息:一是信号的维度排布,二是字段名。不同整理版本常见做法是把振动信号放在 X、Y 两个字段,电流信号放另一个字段;但有的版本把多路信号合并成一个二维数组,shape 为(通道数,样本点数)。如果你打印出来 shape 是 (n, 3) 或 (3, n),就要先想清楚轴向再切窗,否则后面所有切片都是乱的。我一般会用signal.shape和signal.dtype两个信息确认精度和通道数,避免读出来是复数或者带单位缩放的数据。

还要注意:部分版本为了减小体积,把信号做了归一化,幅值范围可能只有 -1 到 1,这本身不影响分类,但会影响对幅值类特征的物理理解。如果你要做包络谱诊断,尽量保留原始采样率信息,采样率通常在总表里或者文件说明里,读出来之后第一时间存在一个字典里,而不是靠猜。

2.3 把总表整理成一份干净的训练清单

总表里通常有几十行到上百行记录,每一行定义一个轴承实例或一个文件,但直接拿来做训练还缺两步:过滤出你关心的工况,以及把分类标签映射成清晰的类别名。我用 pandas 来做这一步,写成固定脚本,避免每次手工复制:

import pandas as pd df = pd.read_excel('数据集总表.xlsx') # 也常见是 csv print(df.columns.tolist()) # 先看列名 print(df['故障类型'].value_counts()) # 看类别分布 # 常见映射:人工损伤、真实损伤、健康 label_map = { 'healthy': '健康', 'artificial': '人工损伤', 'real': '真实损伤' } df['label'] = df['故障类型'].map(label_map) # 按你关心的工况过滤,比如只保留某转速 df_filtered = df[df['转速(rpm)'] == 1500].copy() print(df_filtered[['文件名', 'label', '损伤位置']].head())

这里我提醒一句:不要一次性把全部工况混在一起做分类。不同转速下的信号特征分布差异很大,混合工况虽然会让模型表现“看起来更好”,但实际部署时你并不知道新数据属于哪个转速段。更常见的做法是先把某个典型工况跑通,再逐步加入跨工况数据。损伤位置如果是内圈、外圈、滚动体三类,需要确认总表里是否每行都有值,健康轴承这一列通常为空,代码里要用fillna('无')处理,否则训练时标签列会有 NaN。

2.4 采样率与通道:这两个参数先记下来,后面全靠它们

帕德博恩数据集的信号不是单一通道,通常包含振动和电流两类物理量,振动信号又可能分径向和轴向。不同整理版本对通道的命名差异很大,但物理意义可以靠采样率和信号形态来判别。采样率决定了你能看到的最高频率,也决定了切窗时一个旋转周期包含多少个采样点。

我习惯把这些信息汇总成一个配置字典,所有后续脚本都从它读取:

config = { 'fs': 64000, # 采样率,单位 Hz,以总表为准 'channels': ['X', 'Y'], # 通道名,按实际结构体字段填 'win_cycles': 10, # 每个窗口包含的旋转周期数 }

采样率如果总表里没写,可以从频谱上反推:先画一段信号的幅值谱,看最高有效频率边界,再用奈奎斯特频率乘以二得到采样率。通道顺序如果和预期不一致,会导致后续特征提取全错,所以我会在读取后对每个通道分别画图,确认哪一路是振动、哪一路是电流,再进流水线。

3. 把信号变成训练样本:可视化、切窗与数据划分

3.1 先画波形再谈分析:时域图与频谱图怎么读

不画图的直接后果是后面特征错了都发现不了。拿到任意一个 mat 文件,第一件事永远是可视化。画时域波形看幅值范围和周期性,画幅值谱看主要频率成分和是否存在明显的故障特征频率带:

import matplotlib.pyplot as plt import numpy as np fs = config['fs'] # 采样率 signal = signal.flatten() # 先取单通道示例 duration = 1.0 # 可视化 1 秒 n = int(fs * duration) seg = signal[:n] t = np.arange(n) / fs fig, ax = plt.subplots(2, 1, figsize=(12, 6)) ax[0].plot(t, seg) ax[0].set_title('时域波形(前1秒)') ax[0].set_xlabel('时间 (s)') freq = np.fft.rfftfreq(n, 1/fs) mag = np.abs(np.fft.rfft(seg - seg.mean())) / n ax[1].plot(freq, mag) ax[1].set_xlim(0, 20000) ax[1].set_title('幅值谱') ax[1].set_xlabel('频率 (Hz)') plt.tight_layout() plt.show()

为什么取 1 秒?因为 1 秒对应采样率数值个采样点,频率分辨率正好是 1 Hz,能看清轴承故障特征频率附近的成分。为什么画频谱前要减去均值?为了去掉直流分量,否则零频处的尖峰会压扁整个频谱的纵轴范围。为什么用 rfft 而不是 fft?实信号频谱关于奈奎斯特频率对称,取正半轴就够了,计算量也减半。

读图时有几个判断标准:健康轴承的时域波形幅值平稳、无明显冲击;人工加工损伤通常有周期性冲击;真实损伤的冲击幅度可能更小、更随机。如果画出来某个文件波形异常,比如截断了、幅值突变、全是零,先怀疑文件本身或读取过程,不要带着坏数据进训练集。

3.2 切窗参数怎么定:窗口长度、重叠率与旋转周期

切窗是把这个数据集做成监督学习样本的核心操作。窗口太短,一个旋转周期都没覆盖完整,故障特征被截断;窗口太长,样本量变少,而且可能包含转速切换或非平稳段。我一般是按照旋转周期来选窗口长度,而不是拍脑袋定一个“0.5 秒”。

计算方式很直接:转频等于转速除以 60,每转采样点等于采样率除以转频。比如转速 1500 rpm 时转频 25 Hz,采样率 64000 Hz 时每转 2560 点;如果取 10 转,窗口长度就是 25600 点,约 0.4 秒。代码里这样实现:

def split_fixed_window(data, win_len, overlap=0.5): stride = int(win_len * (1 - overlap)) n = len(data) if n < win_len: return [] starts = range(0, n - win_len + 1, stride) return np.stack([data[i:i + win_len] for i in starts]) rpm = 1500 fs = config['fs'] win_len = int(fs * 60 / rpm * config['win_cycles']) # 10 转 windows = split_fixed_window(signal, win_len, overlap=0.5) print('窗口长度:', win_len, '窗口数量:', windows.shape[0])

win_len的计算里,fs * 60 / rpm是每转采样点数,乘以win_cycles得到总点数。overlap=0.5表示相邻窗口重叠 50%,这样能成倍增加样本量。重叠率越高,样本越多,但样本之间的相关性也越强,如果后面划分不当,训练集和验证集之间会泄露出相似的片段。

重叠率的选择要和划分策略配套。如果样本量本来就不够,先用 0.5 重叠;如果样本量充足,用 0 重叠更干净,每个窗口都来自信号的不同区间。不管重叠率取多少,划分训练集和测试集时都必须保证同一个轴承的所有窗口被分到同一侧,这个坑下一节细说。

3.3 数据划分:按轴承实例分组,而不是按信号段随机分

这是帕德博恩数据集使用中最容易翻车的地方。一个轴承的信号被切成几百个窗口,这些窗口高度相似,如果直接按窗口随机划分训练集和测试集,同一个轴承的相似片段会同时出现在两侧,模型等于提前见过答案,测试精度虚高。正确做法是按轴承实例作为分组单位划分,让同一个轴承的所有窗口都归入同一侧。

from sklearn.model_selection import GroupShuffleSplit # 假设 df_windows 每行是一个窗口样本 # 包含特征矩阵 X、标签 y、所属轴承实例ID group gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42) train_idx, test_idx = next( gss.split(df_windows.index, df_windows['label'], groups=df_windows['bearing_id']) ) train_df = df_windows.iloc[train_idx] test_df = df_windows.iloc[test_idx] print('训练集窗口数:', len(train_df), '测试集窗口数:', len(test_df)) print('训练集轴承数:', train_df['bearing_id'].nunique(), '测试集轴承数:', test_df['bearing_id'].nunique())

GroupShuffleSplit的参数groups传的是每个样本所属的轴承实例 ID,划分时保证同一 ID 的所有样本落在同一边。test_size=0.2是测试集比例,random_state=42固定随机种子保证结果可复现。

如果数据里包含多种工况,只按实例分组还不太够。同一个轴承在两种转速下的信号差异可能比不同轴承在相同转速下的差异还大,所以建议先按工况分层,再在各工况内部按实例分组。更严格的做法是训练集只放一种工况,测试集放另一种工况,这叫做跨工况验证,能真正反映模型泛化能力,后面专门讲。

4. 从特征到模型:跑通一个故障分类的最小方案

4.1 时域特征与频域特征:为什么选这些

拿到切好的窗口,下一步是特征提取。帕德博恩数据集做分类,常见做法是提取时域统计特征加频域特征,喂给随机森林或 XGBoost,就能得到不错的基线。先给出一个可直接用的特征函数:

from scipy import stats def extract_features(x, fs): feats = {} # 时域 feats['rms'] = np.sqrt(np.mean(x ** 2)) # 有效值:反映总体能量 feats['peak'] = np.max(np.abs(x)) # 峰值:反映冲击强度 feats['kurtosis'] = stats.kurtosis(x) # 峭度:对早期故障敏感 feats['crest_factor'] = feats['peak'] / (feats['rms'] + 1e-12) # 频域 spectrum = np.abs(np.fft.rfft(x - x.mean())) / len(x) freq = np.fft.rfftfreq(len(x), 1 / fs) feats['spec_centroid'] = np.sum(freq * spectrum) / (np.sum(spectrum) + 1e-12) feats['spec_energy'] = np.sum(spectrum ** 2) # 常见故障频带能量,范围需结合轴承特征频率估算 band = (freq > 500) & (freq < 8000) feats['band_energy'] = np.sum(spectrum[band] ** 2) return feats

峭度对轴承早期故障敏感,是因为故障冲击会让波形尾部变重,四阶矩比二阶矩放大得更快;有效值稳定反映整体能量,峰度因子则刻画冲击性。频域特征里,谱重心描述能量集中在高频还是低频,故障越严重,重心通常越高;特定频带能量则是直接度量故障特征频率附近有多大的调制边带。

特征计算的窗口长度保持一致很关键。不同长度窗口做出来的频谱分辨率不一样,特征值直接不可比。所以切窗参数一旦定了,全流程都不要改,否则特征分布会漂移。

4.2 随机森林基线:代码与参数选择

把所有窗口提取成特征矩阵后,用随机森林做基线模型。我一般先拿随机森林跑通,再决定要不要上深度学习,因为树模型训练快、可解释性强,还能从特征重要性里反向检查特征是否有效:

import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix def build_feature_matrix(window_list, fs): rows = [] for w in window_list: rows.append(extract_features(w, fs)) return pd.DataFrame(rows) X = build_feature_matrix(train_windows, fs) y = train_labels clf = RandomForestClassifier( n_estimators=300, max_depth=12, min_samples_leaf=2, random_state=42, class_weight='balanced', n_jobs=-1 ) clf.fit(X, y) print(classification_report(test_labels, clf.predict(test_X))) print(confusion_matrix(test_labels, clf.predict(test_X)))

这里几个参数是按经验定的:n_estimators=300足够稳定,再多收益不大还拖慢速度;max_depth=12限制单棵树深度,防止在小型数据集上过拟合;class_weight='balanced'是最关键的一项,因为人工损伤样本往往远多于真实损伤,不平衡会推着模型偏向多数类,必须用类别权重纠正。n_jobs=-1用满所有 CPU 核,数据量不大时几秒就能出结果。

跑完先看classification_report而不是准确率数组。重点对比“真实损伤”这类别的召回率,如果只有零点几,说明模型把真实损伤当成健康或人工损伤了,后面调阈值或加特征才有意义。

4.3 评估指标要分三类看:准确率、召回率与混淆矩阵

分类任务里我只把准确率当第一眼概览,真正决策看的是每类召回率和混淆矩阵。帕德博恩数据集里人工加工损伤的样本往往特征明显、数量较多,模型很容易把人工损伤学到满分,但实际现场更关心真实损伤能不能被拦住。漏掉一个真实损伤比多报一个假阳性代价更高。

我建议打印整个分类报告后,按三个角度检查:整体宏平均 F1 是否达到可以接受的水平;真实损伤这一类的召回率是否明显低于其他类;混淆矩阵里真实损伤被误判成了哪一类。如果被误判成健康,说明特征窗口里故障冲击太弱,没提取出来;如果被误判成人工损伤,说明两类故障在所选频带上的特征重叠严重,需要加入包络谱或时频图特征。

如果真实损伤的召回率不够,常见做法有两个:一是把分类阈值往真实损伤方向偏移,虽然会增加误报,但能压低漏检;二是单独训练一个“真实损伤 vs 非真实损伤”的二分类器,只负责筛选这一种故障。先跑通通用多分类,再针对关键类别单独建模,是我在这个数据集上做方案的固定顺序。

5. 帕德博恩数据集使用避坑:5 个典型问题与排查思路

5.1 读出来的 mat 文件拿不到信号数组,一直在报 KeyError

现象:loadmat 成功执行了,但访问某个字段时直接 KeyError,或者打印出来 shape 是 (1,1),取出[0,0]之后仍然不是能直接画图的数组。

原因:MATLAB 结构体在 Python 里被转换成 numpy 的 void 类型,字段不是顶层可直取的;另一类是版本整理方式不同,信号字段名根本不是网上教程里的那个。

解决:先打印顶层所有字段名,再打印结构体的 dtype.names,按实际名字取值。不要直接用代码里的mat['X'],改成mat[key]后逐步检查。任何一份教程里的字段名都可能和你的版本不一致,时间花在探测结构上最划算。

5.2 文件读取后报内存错误或警告异常信息

现象:用 scipy.io.loadmat 读某个文件时抛出异常,或者读出来的数组是空的,文件体积明显很大。

原因:部分版本的 mat 文件采用较新的 HDF5 格式保存,scipy 对其支持有限,需要换用其他方式读取。

解决:先看文件扩展名和体积,大体积文件优先怀疑是 HDF5 格式。读取时使用h5py这类库,读取后注意二维数组的维度是转置的,需要做一次转置再进入后续流程。我没有固定推荐某种处理方式,关键是识别格式后立刻换工具,不要在 scipy 上反复试不同的参数。

5.3 训练精度 99%,换到其他工况数据上一落千丈

现象:随机划分训练集测试集时精度很高,但把模型放到另一种转速、另一种负载的数据上,准确率明显下降。

原因:数据划分时没有按轴承实例分组,同一段信号的不同窗口同时进了训练和测试;或者把所有工况混在一起随机划分,模型学到的是工况特征而不是故障特征。

解决:先按轴承实例分组,再做跨工况验证,训练集只保留某一种转速,测试集用另一种转速。如果跨工况掉点严重,说明特征里混入了工况信息,需要做频带筛选或对信号做包络解调,削弱转速相关成分,突出故障冲击成分。

5.4 分类报告里真实损伤的召回率特别低

现象:健康类和人工损伤类的指标都很好看,但真实损伤类大量被预测成健康,召回率只有 30% 到 40%。

原因:真实损伤来自运行损耗,故障特征微弱,和健康信号更像;同时样本量少,在常规训练权重下被多数类淹没。

解决:打开混淆矩阵确认误判去向,然后做三件事:对训练样本做类别重加权,评估时单独输出真实损伤这一类的召回率,必要时单独训练一个真实损伤二分类器。调阈值时注意误报成本,不要为了把召回率拉上来把健康样本大量误报。

5.5 总表里的文件名和实际 mat 文件对不上,标签错位

现象:整理标签后训练,发现同一个文件的信号在重复出现,或者某些文件总表里根本没有。

原因:数据集在多次转手整理过程中,文件名编码被重复处理过,比如大小写变化、前缀变化、空格或特殊符号被清洗过,导致索引对不上。

解决:不要用文件名做连接键,用我在配置文件里维护的物理到逻辑映射,先对文件名做标准化,再去掉扩展名后对齐总表。对齐后立刻做一次完整性校验:总表里有的文件必须存在,目录里存在的文件总表里必须能查到,两边数量完全一致再进入训练流程。

6. 让结果更可信:跨工况验证与模型对比的进阶习惯

单一工况下跑出来的好精度,不代表方案能落地。帕德博恩数据集的价值恰恰在于它天然带有多工况信息,所以我会建议在跑通基线后,立刻把实验升级成跨工况验证。具体做法是训练集只用某个低转速工况的全部轴承,测试集用另一个高转速工况的全部轴承,中间不留任何重叠样本。这样得到的指标才是面对新工况时的真实水平。

我一般会做三组实验放在一起看:同工况随机划分、跨工况验证、跨工况加特征筛选后的结果。同一份数据,三个数字的差距能直观说明模型到底是在学故障特征还是在记转速特征。如果跨工况比同工况下降超过十个百分点,先回去看特征,而不是换模型。很多情况下,对信号做包络解调或 Hilbert 变换后取包络谱,把高频调制成分解出来,跨工况指标就能拉回来一部分。

模型对比时也要保持检测习惯。我会把特征基线、1D CNN 基线、时频图加 2D CNN 的结果放在同一套划分、同一个随机种子下跑,避免各自用不同的验证方式导致数字不可比。卷积模型在这个数据集上有潜力,但数据量不大,训练时要注意加早停和数据增强,比如对信号加轻微噪声、随机幅度缩放,提高鲁棒性。

还有一个容易被忽略的分层评估点:人工加工损伤和真实损伤不应该混在一栏里看平均指标。人工损伤是人为加工出来的,特征强烈,真实损伤更贴近现场实际退化,这两类对诊断难度的贡献完全不同。我现在拿到这类数据集,一定会把真实损伤的单类召回率单独标出来,作为方案是否值得投入的硬指标。这个习惯帮我过滤掉了很多纸上谈兵的方案。

跨工况验证和分层评估一起做,基本能看清这个模型离落地还有多远。我现在接手任何新数据集,第一件事仍然先打印字段、再画波形、再定划分,这个习惯让我少踩了无数个隐蔽的坑。希望帮到你。

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

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

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

立即咨询