简介:这份齿轮箱故障数据集面向机械故障诊断与预测性维护方向的工程师、科研人员及数据科学学习者,可用于振动信号分析、故障模式识别及机器学习建模实践。压缩包共15个文件,大小约5.91MB,包含MAT格式原始信号数据、振动/频谱/时域波形PNG图、数据处理与振动分析Python脚本、CSV工况参数表、配套PDF文献及TXT数据解读说明,类型覆盖从原始数据到可视化结果与代码复现的完整链条。数据集涵盖无故障、齿面崩裂、三齿磨损等典型工况,并标注转速、齿数及啮合频率校准信息(如1420rpm主动轴15齿、从动轮110齿、实测啮合频率约365Hz),便于开展特征工程、时间序列分析与故障分类模型训练。已有843人学习本资源,适合用于课程设计、科研对比或工业设备健康管理方案验证。
1. 齿轮箱故障数据.zip:一次“能下载却跑不出结果”的落差
做设备故障诊断的人,几乎都经历过这个场景:在某个公开数据集页面找到“齿轮箱故障数据.zip”,下载、解压、看到一堆.txt或.dat文件,以为离训练模型只差一步。结果第一个脚本就把你卡住了——文件读出来长度对不上采样率,标签和文件名对不上,好不容易跑通一个模型,验证精度却低得让人怀疑数据本身有问题。这个 zip 不是稀缺资源,真正的门槛在于:数据盘面怎么解析、工况参数怎么归一化、故障标签怎么映射到模型输出。这篇笔记会把这个数据集从解压到训练的全流程拆开讲,适合正在做故障诊断入门、毕业设计或者设备健康管理预研的工程师和研究生。定位是“照着做能跑通”,不是泛泛介绍。
2. 数据盘面摸底:传感器通道、工况参数与标签映射
2.1 常见盘面结构:说明书、数据文件与属性表
公开的齿轮箱故障数据集(比如高校或国家实验室发布的版本)解压之后,常见盘面是一份description.txt或 PDF 说明、若干按工况或故障类型分组的原始数据文件、一个属性表或标注文件。不同发布方命名差异很大,但核心信息是固定的:采样率、传感器通道数、故障模式代号、工况参数(输入转速、负载扭矩)。先把这三样找齐,再动代码。
我一般会先做一次“只读摸底”——不开任何深度学习框架,用 Python 写一个 30 行的脚本把目录结构、文件大小、扩展名列出来。这一步不是为了写正式代码,而是为了确认两件事:第一,文件是纯文本还是二进制;第二,文件大小是否暗示了固定的数据长度。很多版本的数据文件是float64或float32的二进制裸数据,没有文件头,文件名本身就承载了全部元信息。
import os from pathlib import Path data_root = Path("./gearbox_data") for root, dirs, files in os.walk(data_root): for fname in sorted(files): full = Path(root) / fname size = full.stat().st_size print(f"{size:>12,} {full.relative_to(data_root)}")这段代码的作用是把整个数据集的“体积地图”打出来。如果同一组文件的字节数完全相同,说明每条样本是定长的二进制记录,直接fromfile就能解析;如果大小参差不齐,大概率是文本格式,每行代表一个采样时刻。拿到这张表之后,再和description.txt里的采样率做一次乘法验算:文件字节数 ÷(通道数 × 每样本字节数)÷ 采样率 = 秒数。算出来的秒数如果是整数,说明盘面干净;如果出现小数,说明文件尾部有额外字段或换行符混入,后面解析时要留个心眼。
2.2 工况参数为什么比故障模式更影响建模
很多初学者把注意力全放在“正常、齿根裂纹、齿面磨损、断齿”这类标签上,却忽略了同一故障在不同转速和负载下的振动特征差异巨大。公开数据集的常见工况有两组输入转速(比如 880 rpm 和 1750 rpm)和两组负载扭矩(0 Nm 和 12 Nm),组合出四到八个工况子集。同一个齿根裂纹样本,在 880 rpm 下转频约 14.7 Hz,啮合频率约 294 Hz(单级齿轮副 20 齿左右);在 1750 rpm 下转频约 29.2 Hz,啮合频率约 583 Hz。如果你把两个工况的数据直接混在一起训练,网络会去学转速差异,而不是故障差异——这是验证精度虚高的最常见原因之一。
这里的工程决策是:要么按工况分组分别训练模型,要么把转速值或转频作为额外输入特征喂给网络。分组训练的好处是简单可靠,坏处是数据量被切成几块,小样本问题更突出;带工况输入的方案能统一建模,但推理时你得知道设备当前转速,这在工业现场不一定拿得到。我做预研时会先跑“单工况小模型”确认数据本身可学,再做跨工况泛化实验。
另一个坑是负载扭矩。扭矩变化会改变齿轮啮合刚度激励,振动幅值和频谱分布都会变。很多数据集把扭矩作为文件名的一部分,比如880_0_Chipped这样的命名规则,含义是“转速 880、扭矩 0、故障类型 Chipped”。解析文件名时,直接按分隔符_拆字段就行,但一定要确认分隔符是下划线还是空格,不同发布方的习惯不一样。
3. 从 zip 到 numpy 数组:解析代码与第一个可用 batch
3.1 二进制裸数据读取:dtype 与长度校验
假设你手里的版本是二进制裸数据文件,每通道按固定采样率连续采集,采样率 20 kHz,通道数 4(驱动端、非驱动端加速度计加上转速计等)。读取的核心是numpy.fromfile,但 dtype 必须先从说明文档里确认。常见的是float64,也有float32;用错 dtype 读出来的数据会完全错乱,而且不会报错——这是最阴险的地方。
import numpy as np fs = 20000 # 采样率,单位 Hz,来自说明文档 num_channels = 4 # 传感器通道数,来自说明文档 dtype = np.float64 # 二进制格式,来自说明文档 raw = np.fromfile("./gearbox_data/880_0_Chipped.dat", dtype=dtype) total_samples = raw.size // num_channels raw = raw[:total_samples * num_channels].reshape(total_samples, num_channels) print(f"原始点数: {raw.shape[0]}, 时长: {raw.shape[0] / fs:.2f} 秒") print(f"通道数校验: {raw.shape[1] == num_channels}")这段代码做了两件事:先用整除把多余字节砍掉,再按通道数重组二维数组。逻辑说明:fromfile会把整个文件读成一维数组,文件大小不一定恰好是通道数的整数倍,因为有文件尾残留;先整除再切片,能避免reshape报错。参数说明:fs和num_channels必须来自数据说明,不要靠猜。如果打印出来的时长是个带多位小数的数,比如 4.983 秒,说明文件里有非数据内容混入,这时候要检查文件尾部还是头部是否有描述信息。
3.2 重采样与帧切分:让样本进入可训练形态
原始数据是连续长序列,不能直接喂给模型。常见做法是按固定帧长切分,比如每帧 1024 或 2048 点。帧长选择有一个权衡:帧太短(256 点),频率分辨率只有 78 Hz(20 kHz / 256),齿面磨损这类低频调制特征会被抹掉;帧太长(8192 点),样本数变少,且假设了片段内工况恒定。我一般从 2048 点起步,对应约 0.1 秒,频率分辨率约 9.8 Hz,能看清转频及其谐波,样本数量也够训练。
frame_len = 2048 stride = 1024 # 重叠率 50%,做数据增强 frames = [] for start in range(0, raw.shape[0] - frame_len + 1, stride): frames.append(raw[start:start + frame_len, :]) frames = np.stack(frames) print(f"帧形状: {frames.shape}, 样本数: {frames.shape[0]}")参数说明:stride = frame_len // 2是常见设置,让相邻帧有 50% 重叠,既增加样本量又不让帧之间完全独立。如果你的分类任务里样本数已经过万,可以不重叠直接切;如果只有几千帧,重叠是划算的数据增强。帧切完之后,每个样本的形状是(2048, 4),四列对应四个通道。有些模型只吃单通道或融合后的特征,这时候可以在帧切分之后做通道选择,而不是之前。
3.3 标签编码:从文件名到 one-hot 的映射逻辑
标签映射是整个流程里最容易出错的一环。数据集里的故障类型通常是字符串:Healthy、Chipped、Tooth_Broken、Worn,或者其中文拼音。你要做的不是直接给这些字符串编号,而是先确认故障类型和文件名的对应关系有没有二义性。比如同一个故障类型出现在多个工况子目录里,那么标签应该是“故障类型”还是“故障类型+工况”?如果是前者,两个不同工况下同一故障的样本会被归为一类,模型的判别难度更高,但也更接近工业现场的实际情况。
label_map = { "Healthy": 0, "Chipped": 1, "Tooth_Broken": 2, "Worn": 3, } def parse_label(fname: str) -> int: parts = fname.stem.split("_") # 文件名示例: 880_0_Chipped fault_name = parts[-1] if fault_name not in label_map: raise ValueError(f"未知故障类型: {fault_name}") return label_map[fault_name]这段脚本不作任何智能推断,只做显式映射——未知类型直接抛异常。逻辑说明:split("_")的取最后一段是假设故障类型总是最后一个字段,如果你的数据文件名是880_Chipped_0.dat,这段代码就会错。所以更稳妥的做法是先打印所有文件名的stem字段,人工确认结构再写解析函数。参数说明:label_map的字典顺序会影响 one-hot 编码的列顺序,训练和推理时要用同一份映射表,建议把它存成 JSON 文件,而不是每次运行重新定义。
4. 故障特征怎么用:时域统计量、边频带与“无名标签”处理
4.1 时域统计量:均值、峰值、峭度能告诉你什么
齿轮故障在时域上的表现很直白:齿面磨损会让振动幅值整体上升,齿根裂纹会在每次啮合时产生冲击脉冲,断齿的冲击更剧烈但可能更稀疏。常用的时域特征包括 RMS(均方根值)、峰值、峰值因子和峭度。峭度对冲击类故障最敏感,健康齿轮的振动接近高斯分布,峭度接近 3;出现局部冲击时峭度会显著升高。
def time_domain_features(x: np.ndarray) -> np.ndarray: x = x.astype(np.float64) rms = np.sqrt(np.mean(x ** 2, axis=-1)) peak = np.max(np.abs(x), axis=-1) crest = peak / (rms + 1e-12) kurt = np.mean((x - np.mean(x, axis=-1, keepdims=True)) ** 4, axis=-1) kurt = kurt / (np.std(x, axis=-1) ** 4 + 1e-12) - 3.0 return np.stack([rms, peak, crest, kurt], axis=-1)这段代码一次算出四个特征,输出维度是(样本数, 4),可以直接拼进后续分类器。注意eps=1e-12是防止 RMS 为零时除以零,实际齿轮箱振动数据不会出现零 RMS,但代码要守住边界。峭度的-3.0是让健康数据的峭度归零,这是行业惯例,便于对比。
特征维度远低于原始波形,训练速度会快两个数量级,但代价是丢掉频谱细节。时域特征适合做故障的初步筛选和报警阈值设定,不适合做细粒度故障类型识别。比如齿根裂纹和断齿都可能出现冲击脉冲,峭度都偏高,仅靠时域特征区分不了。
4.2 频域视角:FFT、边频带和啮合频率的关系
齿轮箱振动频谱的基础是啮合频率及其谐波。单级齿轮箱的啮合频率是转频乘以齿数,常见的是 20 齿上下,所以 1750 rpm 下啮合频率约 583 Hz。故障齿轮的频谱会在啮合频率两侧出现边频带,间隔等于转频——这是判断齿轮局部故障(比如裂纹、断齿)最经典的判据。齿面磨损这类分布型故障让啮合频率幅值整体抬升,但边频带不一定明显。
一段好的 FFT 分析不能只看幅值谱,要看边频带的调制结构。
from scipy.fft import rfft, rfftfreq def spectrum_features(x: np.ndarray, fs: int) -> np.ndarray: n = x.shape[-1] fft_vals = np.abs(rfft(x - np.mean(x, axis=-1, keepdims=True))) / n freqs = rfftfreq(n, d=1.0 / fs) return freqs, fft_vals freqs, mag = spectrum_features(frames[0, :, 0], fs) mesh_freq = 583.0 # 由转频与齿数计算 idx = np.argmin(np.abs(freqs - mesh_freq)) print(f"啮合频率附近幅值: {mag[max(0, idx-5):idx+6]}")这段代码先做去直流,再计算单边幅值谱。/ n归一化让幅值不随帧长漂移,不同长度帧算出来的幅值才有可比性。rfft只算正频率部分,采样率 20 kHz、帧长 2048 时输出 1025 个频点。打印啮合频率附近的 11 个频点,能直观看到幅值是否出现对称的边频带。如果你在mesh_freq ± 14.7 Hz处看到明显的成对谱峰,裂纹或断齿的概率很大;如果只是在啮合频率处整体抬升,更可能是磨损。
4.3 “无名标签”怎么处理:先分离,再定性
部分公开数据集里会出现一个很尴尬的文件组——标签是未知的,或者论文里没有明确描述。常见版本中有一个“无名故障”类别,文件命名可能是Unknown或Unidentified,实际是什么故障,发布方没有给出权威结论。处理这类数据的行业惯例是:先把它们单独拎出来,不要硬塞进四个已知类别里。
如果你把Unknown当成第五类训练,模型会学到一组“不属于其他四类”的特征,这个类别的内聚性差,推理时遇到新的未知故障大概率被分进来,反而让模型变得不可信。我一般这样处理:训练阶段只用四个已知标签,把Unknown样本留作异常检测的测试集。训练一个四分类模型后,用未知故障样本做前向推理,观察模型的 softmax 输出是否有低置信度的样本聚集现象。如果Unknown样本在四个类别上均匀低置信度,说明它真的是未知模式;如果集中被分到某个类别,说明它的特征和该故障类有重叠,值得回头细看频谱。
# 推理阶段:观察未知样本的置信度分布 probs = model.predict(unknown_frames) # 形状 (N, 4) max_probs = np.max(probs, axis=1) uncertain_mask = max_probs < 0.6 print(f"未知样本数: {len(max_probs)}") print(f"低置信度占比: {uncertain_mask.mean():.2%}")阈值0.6是经验值,不是硬标准。如果你的训练精度在 95% 以上,正常样本的置信度通常会超过 0.9,那 0.6 已经是很保守的分界线。如果正常测试样本的置信度也普遍低于 0.6,说明模型本身没训练好,这个阈值要重新校准。
5. 整理成训练集的常见问题:伪加密、中文编码与工况泄露
5.1 zip 解压失败:伪加密、EOCD 缺失与损坏包
解压 zip 是第一步,但这一步就能卡住很多人。最常见的情况是:文件下载到一半断了,zip 包没有结束标记(EOCD),解压工具报could not find EOCD;另一种是伪加密——文件头标记了加密位,但没有实际加密内容,Windows 自带解压会直接要求输密码,而命令行工具有时能直接解开。
伪加密的检测手段很简单:用 Python 的zipfile模块打开时传pwd=b""试一次,如果传空密码能读出文件列表,说明是伪加密。真正的解决方式是重新下载完整包,不要用“修复”工具硬修,修复出来的数据可能缺文件。如果下载源只有一份且反复损坏,检查一下是不是浏览器插件或下载工具在拦截.zip后缀,换一个下载工具或换个网络环境重下。
unzip -O GBK gearbox_data.zip -d ./gearbox_data-O GBK这个参数是处理 zip 内中文文件名乱码的关键。很多数据集是中文环境打包的,直接用unzip解压会得到乱码文件名,之后解析代码按文件名匹配标签时会全部失效。-O指定的是 zip 内部编码的字符集,如果你的系统默认 UTF-8,但包内是 GBK,就必须显式指定。注意这是 Linux 命令行的写法,Windows 下可以用 7-Zip 设置“文件名编码”为 GBK 后解压。
5.2 工况泄露:数据切分时最常见的“隐形错误”
把同一个连续采集文件切成帧后,直接随机划分训练集和测试集,这是最危险的错误。原因在于:相邻帧之间高度相关,时间上只隔了 0.05 秒,振动特征几乎没有变化。如果同一条连续记录的帧既出现在训练集又出现在测试集,验证精度会虚高,模型在跨工况或跨设备场景下会明显退化。
按设备或按文件划分是正确的做法。
import numpy as np # 假设 file_names 是所有原始文件的路径列表,每个文件对应一次独立采集 file_ids = np.array([i for i, _ in enumerate(file_names)]) train_files, test_files = train_test_split( file_ids, test_size=0.3, random_state=42, stratify=file_labels # 按故障标签分层,保证每类在测试集都有样本 ) train_mask = np.isin(file_id_per_frame, train_files) test_mask = np.isin(file_id_per_frame, test_files) X_train, y_train = frames[train_mask], labels[train_mask] X_test, y_test = frames[test_mask], labels[test_mask]逻辑说明:file_id_per_frame是每个帧所属的原始文件编号,切分依据是文件而不是帧。这样训练集和测试集之间没有任何时间重叠。stratify=file_labels保证每个故障类在训练集和测试集中的比例一致,避免某个类别全被分到测试集导致训练不足。随机种子42是惯例,但如果你要做多组对比实验,固定这个值才能保证结果可复现。
5.3 中文注释陷阱:Python 源码里的编码问题
写解析脚本时,注释和字符串里尽量别用中文——不是技术洁癖,而是 Windows 下 Python 的默认编码在不同版本间不一致,源码文件用 UTF-8 保存,但运行时如果PYTHONUTF8环境变量没设置,中文字符串比较可能出问题。文件名里的中文,比如正常.txt,用path.stem拿到的字符串和代码里写死的"正常"在编码正确时相等,但一旦解压时用了错误的编码,两边就不会匹配。
我踩过的坑是:解压时没有指定编码,文件名变成一串乱码姝g‘鍒?txt,我把这段乱码直接复制到代码里做匹配,运行是能跑的,但可读性极差,而且换一台机器后再解压一次,乱码可能变成另一串。正确做法是解压后立刻重命名文件为标准英文命名,从源头消灭乱码问题。
unzip -O GBK gearbox_data.zip -d ./tmp cd ./tmp for f in *.txt; do mv "$f" "$(echo "$f" | iconv -f GBK -t UTF-8)" done这段脚本用iconv把文件名从 GBK 转成 UTF-8,然后用mv重命名。for f in *.txt只处理当前目录的文本文件,如果你的数据是.dat或.csv,把通配符改掉。重命名之后,再写一个把文件名映射到标签的脚本,此时不需要依赖 Python 的编码处理能力,OS 层的文件名就是 UTF-8,所有工具都能正确识别。
6. 用短时傅里叶变换给数据做“体检”:一小时确认数据能不能用
拿到新解压的数据集,先别急着训练,先用短时傅里叶变换(STFT)做一次快速体检。为什么选 STFT 而不是直接看 FFT?因为齿轮箱的工况是非平稳的,转速波动、负载变化都会让频谱随时间漂移,STFT 能看到“频率成分随时间的变化”,一眼就能发现数据是否断流、通道是否损坏、冲击事件是否存在。
from scipy.signal import stft f, t, Zxx = stft( raw[:fs * 10, 0], # 取前 10 秒的驱动端信号 fs=fs, nperseg=1024, noverlap=512, window="hann" ) # 绘制二维频谱图,检查 0-2 kHz 范围内的能量分布 plt.figure(figsize=(12, 4)) plt.pcolormesh(t, f, np.abs(Zxx), shading="gouraud", cmap="viridis") plt.ylim(0, 2000) plt.xlabel("时间 (s)") plt.ylabel("频率 (Hz)") plt.colorbar(label="幅值") plt.show()参数说明:nperseg=1024对应 51.2 ms 的时间窗,频率分辨率约 19.5 Hz;noverlap=512是 50% 重叠,让时间轴更平滑。ylim(0, 2000)把视野放在主要故障特征所在的低频段,齿轮箱的啮合频率和边频带通常在 1 kHz 以内。如果图上出现横向的亮色条带,说明有持续的窄带振动;如果出现竖向的短线,说明是瞬态冲击。健康的时频谱图应该是均匀分布的底色,故障信号会在这层底色上叠加异常成分。
我习惯按“每个故障类型抽一个文件、画一张 STFT 图、肉眼扫一遍”的流程来做体检。正常类别的图如果出现明显的周期性冲击,说明这个“正常”样本可能有问题;断齿类别的图如果和健康类别看不出区别,说明要么标签打错了,要么这段数据的采集当时确实没捕捉到故障特征。这一步不需要任何模型,半小时到一小时就能做完,但它能帮你避开后面几天甚至一周的无效训练。
如果你发现 STFT 图里某个通道的信号一直是平的,也就是没有任何振动幅值,这个通道大概率是传感器故障或采集通道断开。此时不要直接把该通道剔除,先确认是“数据记录问题”还是“设备真实无振动”——齿轮箱只要有转动,驱动端的振动不可能是平的。遇到这种情况,按前面的帧切分逻辑不用改,但在特征提取阶段把该通道剔除,否则模型会学到“某通道恒为零”这种无意义模式。希望这篇笔记能帮你把 gearbox_data 这件事做顺,少走几次弯路。
本文还有配套的精品资源,点击获取