齿轮箱故障诊断:从zip数据解压到特征工程避坑指南
简介:面向齿轮箱故障诊断与预测性维护场景的开放数据集,共十五个文件、约5.91MB,适合设备健康管理、机器学习建模和故障特征提取方向的研究者与工程师使用。包内以mat格式保存振动原始数据,涵盖正常、单齿崩齿、三齿磨损三种典型工况;配套png频谱图/时域图、Python分析脚本、csv参数表、txt数据解读和PDF文献,形成从原始信号、特征工程到模型验证的完整链条。csv参数表记录了转速1420rpm、主动轴15齿、从动轮110齿等关键工况信息,PDF文献可帮助理解实验台架与采集方案。已有843人浏览学习,可直接用于训练SVM、随机森林、LSTM等故障预测模型,结合傅里叶变换、小波分析以及温度、扭矩等监测思路进行异常检测、剩余寿命评估,对工业现场降低设备停机成本具有实用价值。无论是学术研究还是工程验证,均能提供可复现的故障诊断实验基准。
1. 齿轮箱故障数据.zip:一份数据包背后要解决什么
一个名为“齿轮箱故障数据.zip”的文件,在设备预测性维护项目里几乎就是“全部家当”。它通常装着齿轮箱在健康与不同失效状态下的振动信号、转速/负载工况和故障标签,目标是支撑起一套滚动的故障诊断模型——你拿它训练分类器,去识别齿面点蚀、断齿、轴承剥落这些典型失效。适合谁读?刚接手振动数据建模的工程师,或者正要把自己采集的数据整理成可交付数据集的从业者。我的第一建议可能反直觉:拿到zip先别急着解压,先问清楚包里的信号是从哪台实验台来的、采样频率是多少、工况是否被记录。否则后面所有特征工程和模型精度都会建立在一个黑匣子上面。
2. 打开之前先看包结构:故障数据该有哪些东西
2.1 一份可复用的故障数据包必须包括的五个部分
很多“齿轮箱故障数据.zip”看起来文件不少,但真正要建模时会发现少这少那。我整理数据包时的底线是五个部分都要有:原始振动信号、工况参数、故障标签、元数据、数据说明。原始振动信号是核心资产,不能只留特征或频谱,否则后续想换一种采样方式或重做预处理就只能重做实验。工况参数至少包括输入轴转速和负载,齿轮箱振动对转速和负载极其敏感,缺失工况的数据只能做定性分析,没法做跨工况模型。故障标签要和文件名、信号一一对应,常见标签有健康、齿面磨损、齿根裂纹、断齿、轴承外圈损伤、内圈损伤等;标签命名要统一,别一会儿用“fault1”,一会儿用“broken”。元数据是采样率和传感器量程,这个最关键,没有采样率你连FFT横轴都画不出来。数据说明可以是一个README,记录台架布置、测点位置、采集仪型号、滤波设置,这些信息在模型部署阶段比模型本身还要宝贵。
为了直观,我会在解压后先看一眼目录。下面是一个常见做法下的目录结构,用来存放同一批实验台在多种转速负载下的信号:
gearbox_fault_dataset/ ├── README.md ├── metadata.json ├── raw/ │ ├── healthy_0_1800rpm_0Nm.csv │ ├── tooth_chipped_1_1800rpm_0Nm.csv │ ├── tooth_broken_2_1800rpm_0Nm.csv │ ├── bearing_outer_3_1800rpm_0Nm.csv │ └── bearing_inner_4_1800rpm_0Nm.csv ├── processed/ │ └── features.parquet └── labels.csv这里的命名规则是“故障类型_样本编号_转速_负载”,一眼就能看出实验条件,比“data1.csv”这种命名有效得多。raw目录里放原始加速度波形,processed放之后计算的特征表,labels.csv单独维护每个文件对应的故障标签。metadata.json集中放采样率和台架参数,让目录干干净净。这种结构在多人协作和后续扩展时特别舒服,不用每次靠猜。
2.2 命名与工况组织:按故障类型嵌套,尽量少用模糊名
命名控制是数据包治理的第一件事。我见过太多zip包解压后是“新建文件夹/data/2019-05-06/001.txt”,等你一个月后再取数据,已经完全想不起001对应的是断齿还是健康的工况。所以命名要带语义,但也不能带中文或特殊空格,因为在跨平台解压时很容易出现乱码。按故障类型嵌套目录是一个稳妥方案:顶层分healthy、cracked_tooth、broken_gear、bearing_outer等;每个故障目录内再按转速负载分子目录,比如1800rpm_10Nm。这样做的好处是后续写数据加载脚本时,可以直接从路径中解析标签和工况,减少人工标注的错误。
另外,metadata.json里面要写清楚传感器的安装位置、方向、单位。常见测点是齿轮箱壳体垂直方向和水平方向,同一个故障在两个方向的能量分布完全不同。如果采集时用了抗混叠滤波器,也在元数据里写明白;很多实验台采集卡默认做了滤波,但使用者并不知道,导致特征频率范围被人为截断。metadata内容可以做成这样,方便程序一次性读取:
{ "sampling_rate_hz": 25600, "sensor_type": "piezoelectric_accelerometer", "sensor_unit": "m/s^2", "position": "gearbox_housing_vertical", "shaft_config": { "input_shaft_rpm": [900, 1200, 1800], "load_nm": [0, 10, 20] }, "fault_labels": [ "healthy", "tooth_chipped", "tooth_broken", "bearing_outer", "bearing_inner" ], "acquisition": { "anti_alias_filter_hz": 10000, "signal_length_s": 10, "adc_bits": 24 } }这里面每个字段都有实际用途。比如信号长度10秒,采样率25600Hz,每个文件就是256000点,约4MB浮点数组,这个长度足够覆盖多次齿轮转周期和故障冲击的重复事件;ADC位数决定了信号最低可分辨的幅值;抗混叠截止频率10kHz说明后面的频谱分析最高不要超过10kHz。有了这些信息,你才能放心地写程序做批量读取。
3. 用Python正确读zip:从完整性校验到信号切割
3.1 完整性与EOCD:先修包,再谈数据
把zip文件当作普通文件直接解压是很多人的第一反应,但恰恰在这步会翻车。压缩包在传输过程中可能缺失结尾的EOCD(End of Central Directory),表现为解压时直接报错“could not find end of central directory record”(找不到中央目录结尾)。还存在一种伪加密情况:文件头把加密标志位置1,但内部数据流实际没有加密,强行要求密码导致后续处理中断。为了不让数据读取被这些琐碎问题卡住,我习惯先用Python把zip的完整性和成员列表检查一遍,代码加注释之后可以直接抄:
import zipfile ZIP_PATH = "gearbox_fault_data.zip" # 先用快速判断确认是不是有效zip,避免乱传文件浪费后面时间 if not zipfile.is_zipfile(ZIP_PATH): raise RuntimeError("文件不是标准zip格式") with zipfile.ZipFile(ZIP_PATH) as zf: # testzip()会逐个文件校验CRC,返回第一个损坏文件名 bad_file = zf.testzip() if bad_file is not None: raise RuntimeError(f"zip内文件校验失败: {bad_file}") # 打印成员列表,确认目录结构符合预期 names = zf.namelist() print(f"共 {len(names)} 个文件") for name in names[:20]: print(name)代码逻辑分三步:先is_zipfile做快速甄别,再用testzip校验每个文件的CRC,最后通过namelist确认包内路径。testzip返回None表示全部完整,返回字符串则是第一个出问题的成员名。这一步看起来简单,实际能避开后续大量莫名其妙的解析错误。
如果testzip检查不过,或者解压时提示找不到EOCD,常见做法是先用zip的修复工具重建索引,再重新验证:
zip -FF corrupted_input.zip --out repaired_output.zip unzip -t repaired_output.zip第一行会在文件中搜索EOCD记录并尝试重建中央目录,第二行检查修复结果。注意zip -FF对完全截断的文件不一定有效,它依赖比较完整的文件尾部。我在实际项目里遇到过下载了一半的zip包,修复后虽然能列出文件,但中间某个大文件CRC校验还是失败,那种情况只能回到源头重新获取,不要硬扛。
处理完完整性,接下来是伪加密。zipfile在遇到加密位时会要求密码,但有些打包工具生成的zip,只设置了bit 0明文标志却没有真正加密。这种情况下,尝试用空密码bytes去读取往往反而能成功:
with zipfile.ZipFile(ZIP_PATH) as zf: sample_name = names[1] # 文件列表中任取一个 info = zf.getinfo(sample_name) try: data = zf.read(info) except RuntimeError as exc: if "encrypted" in str(exc): # 伪加密套路:头标记与数据流不一致,用空密码绕过标志位 data = zf.read(info, pwd=b"")用pwd=b""的意思是告诉zipfile“我有密码,密码是空”,从而跳过它读取数据。如果数据真加密,空密码会继续报错,那就说明打包者确实设置了密码。很多“zip密码移除”工具本质也是在改这个标志位,并不适合未知密码的暴力破解场景,合法使用场景应该是数据提供方明确告诉你密码但系统属性错乱。我自己遇到伪加密的频次不高,但一遇到就是当天最大的阻碍,所以值得写进读取流程里。
3.2 读取信号文件:按块读、滑动窗口切样本
zip验证通过后,下一步是把csv或mat文件中的波形读成可计算的数组。齿轮箱振动信号往往很大,10秒25600Hz的加速度数据是25.6万点,多个工况叠加后内存占用轻松上GB。我一般不会一次性把所有文件全部加载到内存,而是先读取一个文件的元信息和长度,再用滑动窗口切样本,边切边决定是否保留。下面这段代码把“按文件读取+滑窗切分”做成一个小模块:
import os import numpy as np import pandas as pd def load_signal_from_csv(path): # 假设CSV有两列:时间戳、加速度;也可能只有一列直接是波形 df = pd.read_csv(path, header=None) if df.shape[1] == 2: return df.iloc[:, 1].to_numpy(dtype=np.float64) return df.iloc[:, 0].to_numpy(dtype=np.float64) def make_windows(signal, window_points=1024, stride_points=512): total = len(signal) if stride_points <= 0: raise ValueError("stride_points必须大于0") if window_points > total: raise ValueError("窗口长度超过信号长度") windows = [] # 从0开始,以stride递增,留下尾部不足一个窗口的数据 for start in range(0, total - window_points + 1, stride_points): windows.append(signal[start:start + window_points]) return np.asarray(windows)这里window_points是窗口内的样本点数,stride_points是相邻窗口的起点间距。比如25600Hz采样率下,一个窗口1024点等于40毫秒,能够覆盖齿轮轴一个完整旋转周期的一小部分;如果想覆盖整转甚至多转,窗口就要扩大到2048或4096。stride决定重叠率:1024窗口配512步长,重叠50%;重叠越高,训练样本数越多,但相邻样本之间相似度越高,后面如果不按信号片段划分数据集,就会引出一节要说的“数据泄漏”问题,所以窗口和步长不是越大越好,要配套存好每个窗口在原始信号中的起始位置,方便追溯。
读取并切出窗口后,我的习惯是每个窗口的样本做一个轻量的“是否有效”判断,比如方差是不是接近零,或者是常数。齿轮箱不会输出完全平的信号,出现平信号大概率是传感器断线或采集卡丢数,直接丢掉这一窗口比留着干扰模型更合适。写一个简化版判断,根据自己的采样率调整阈值:
def drop_flat_windows(windows, min_variance=1e-6): valid = [] for w in windows: if np.var(w) >= min_variance: valid.append(w) return np.asarray(valid)参数min_variance建议用标定过的量程估算,一般加速度信号有振动时方差远大于1e-6,如果设置为0.0001也合理。关键是不要用零均值之类的固定假设,因为有些低转速工况的振动幅值本来就很低,一刀切会误删。
4. 从原始信号到故障特征:时域频域参数怎么选
4.1 时域统计量:峭度能反应什么,不能反应什么
把原始波形直接怼进神经网络,不是不能做,但可解释性和训练稳定性都差;工业场景我更倾向先压缩成有物理意义的特征。时域统计量是第一步,典型指标包括均方根值RMS、峭度、波形因子和峰值因子。峭度是一个很敏感的高斯分布偏离指标,越是冲击型故障,越会拉出大量尖峰,峭度值升得很明显。但它只能告诉你有冲击,不能告诉你冲击来自哪个部位——齿面点蚀和轴承外圈剥落都会产生振动冲击,峭度相同也可能对应完全不同的故障齿轮。所以严格做法是把时域统计量作为辅助特征,配合频域分析来定位。
举个例子,一个断齿故障,齿轮每转一圈都会撞击一次,RPM=1800时轴频为30Hz,那么冲击周期约为33毫秒;而轴承外圈剥落冲击频率可能是轴频的若干倍。如果只看峭度,两个信号都可能超过5,但分类完全无法区分。所以我在做故障诊断时会给每个窗口计算一组时域参数,记录成一个向量,但最终不单独依赖它们。计算这些统计量的代码非常标准,注意标定好自己的单位:
def time_domain_features(x): rms_val = np.sqrt(np.mean(x**2)) peak_val = np.max(np.abs(x)) mean_val = np.mean(x) variance_val = np.var(x) std_val = np.std(x) # 峭度:四阶矩/方差平方;减3是让健康信号接近0 kurt_val = np.mean((x - mean_val)**4) / (std_val**4 + 1e-12) peak_factor = peak_val / (rms_val + 1e-12) waveform_factor = rms_val / (np.mean(np.abs(x)) + 1e-12) return np.array([rms_val, peak_val, variance_val, kurt_val, peak_factor, waveform_factor])计算里加1e-12是为了防止信号值全为0时除零。峭度减3是让标准正态分布峭度为0,方便对比;有些行业习惯不减3,会直接叫“峭度系数”,记录时要写清楚,否则不同同事间的特征数据会差一个常数。
4.2 频域包络谱:把故障特征频率从调制中解出来
齿轮箱的故障信号常常是调制的:齿轮啮合产生高频载波,故障冲击在载波上留下幅值调制边带。直接对原始信号做FFT,你能看到啮合频率和边带,但边带幅度小,很难识别;因此我会先对带通滤波后的信号做Hilbert变换得到包络,再对包络做FFT,得到的是故障冲击的包络谱,特征频率就非常突出。这个解调流程是齿轮箱故障诊断里最经典也最实用的做法。代码如下:
from scipy.signal import hilbert, butter, filtfilt from numpy.fft import rfft, rfftfreq def envelope_spectrum(x, fs, band_low, band_high): b, a = butter(4, [band_low, band_high], fs=fs, btype='band') filtered = filtfilt(b, a, x) # Hilbert得到解析信号,模长就是包络 env = np.abs(hilbert(filtered)) # 去直流,减小升降趋势干扰 env = env - np.mean(env) spec = np.abs(rfft(env)) freqs = rfftfreq(len(env), 1/fs) return freqs, spec带通滤波器选择是这里的第一个玄学。band_low通常设在500到1000Hz,避开低频基座晃动和电机50Hz干扰;band_high要低于采样率一半,并在抗混叠截止频率以内,例如10kHz。你可以在包络谱上找齿轮啮合频率GMF乘以转频后的边带,也可以找轴承特征频率BPFO、BPFI。这些频率先用转速算出来,再回包络谱上核对。比如一个齿数为32的齿轮在1800rpm下,啮合频率是32×30=960Hz,如果你的观测边带偏移正好等于30Hz,就能初步判断是轴频相关的故障。这种先验频率核对远比拿到谱图“看峰值”可靠。
4.3 把特征写成结构化数组:标签与样本对齐
特征计算完面临最后一步:如何把特征和对应的故障标签、工况、所属文件编号保存下来。这里最容易出现标签错位,常见翻车是用Python内置zip()直接同时迭代特征数组和类别列表,但某个文件没读出来导致样本数和标签数对不上。为了避免这个问题,我在保存特征时会把“来源文件”“窗口起始位置”“故障标签”“转速负载”都放在同一个结构化记录里,这样后面无论怎么划分训练集验证集,都不会破坏样本身份。保存成npz的示意代码如下:
def save_features(feature_matrix, labels, win_starts, filenames, out_path): np.savez_compressed( out_path, features=feature_matrix, labels=np.asarray(labels), win_start=np.asarray(win_starts), source_file=np.asarray(filenames) )npz是numpy自带格式,压缩率不错;如果不喜欢npz,也可以用parquet。关键是source_file和win_start这两个字段,别忽略。它们能在模型表现异常时帮你定位到一个具体信号片段,也是后面做分组交叉验证的基础。保存之后,最好写一个反向读取并打印形状的验证脚本,确保features的行数等于labels的长度。这一步很琐碎,但不做的话后面所有统计脚本都处于“大概对”的状态。
5. 故障诊断落地中的常见坑:从样本不均衡到数据泄漏
5.1 重叠窗口随机划分导致“数据泄漏”,测试集虚高
现象:训练集和测试集用同一个zip里的窗口随机划分,模型在测试集上准确率95%以上,可到了现场另一台齿轮箱上,准确率掉到50%,像是换了个世界。
原因:我们在滑窗时如果stride小于窗口长度,相邻窗口大量重叠,同一个连续信号片段会同时出现在训练集和测试集中。模型相当于在考试时见过题目原题,考试分数当然好。齿轮箱信号的连续性好,相邻窗口在波形和特征上高度相似,这种泄漏比想象中更严重。
解决:划分数据集时按“信号文件”或“连续采集段”分组,而不是按窗口。sklearn里提供GroupShuffleSplit,分之前把source_file字段当作组别传入:
from sklearn.model_selection import GroupShuffleSplit # groups数组长度等于样本数,内容为该样本属于哪个文件 groups = np.asarray(source_files) gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42) train_idx, test_idx = next(gss.split(features, labels, groups))这样划分后,同一个原始信号文件的窗口不会横跨训练和测试集。如果想更严格,可以按设备或实验批次分组,但至少不要让文件内窗口泄漏。现场部署时如果有多台齿轮箱数据,建议按设备分组验证,得到的准确率才接近真实能力。
5.2 类别不平衡让少数故障被忽略
现象:故障数据zip里,健康样本占70%,断齿样本只占2%。模型训练完,断齿这一类F1分数很低,或者模型把几乎全部样本都判成健康,整体准确率却很高。
原因:分类器倾向于多数类,因为把少数类错判为多数类不会提高损失函数的惩罚。故障诊断场景中,最危险的就是把严重故障漏判成健康,这类代价远高于误报。
解决:第一优先是不要做普通过采样复制少数类,复制只是让模型背下来,不能提升泛化。时域里做轻量增强可以考虑对少数类原窗口做小幅平移、白噪声扰动,样本总量控制在一个合理范围;或者使用带类别权重的分类器,比如在RandomForest里设置class_weight='balanced'。下面是一个在训练前检查类别分布的常规做法:
from collections import Counter label_counts = Counter(labels) total = len(labels) for label, cnt in label_counts.items(): print(f"{label}: {cnt} ({cnt / total:.2%})")如果发现少数类只有几十个样本,即使加权,模型也学不到稳定的规律。更务实的做法是先做异常检测二分类——把少数故障和所有其他状态区分,而不是一开始就做多分类。诊断边界放得越细,对样本量的要求越高。等你积累了更多数据再扩大分类标签,这比硬撑多分类模型更可控。
5.3 zip解压遇到“could not find eocd”或“伪加密”
现象:解压时提示“could not find end of central directory record”或“file is encrypted, but password not given”,但当前文件明明没有密码保护。
原因:前者是zip的EOCD索引丢失或文件不完整;后者是zip头部的加密标志位与真实内容不一致,属于伪加密。热词“zip密码移除”常出现在这类坑里,很多人去搜加密工具,实际上问题根本不在密码,而在文件格式标志错乱。
解决:先按第3章的zipfile.testzip检查完整成员,确定具体是哪个文件损坏。再用zip -FF修复EOCD,最后用伪加密方案空密码绕过。如果修复后仍打不开关键文件,不要在这个包上反复消耗时间,回到来源重新下载或让数据提供方重新打包,这是最有效率的止损方式。
5.4 转速与采样率没对齐,故障频率永远算不准
现象:你在包络谱上看到一个峰值,按轴承特征频率公式怎么都对应不上;换了个转速文件,同样的峰值又跑到了另一个位置。
原因:采集振动信号时没有同步记录转速,或者记录的转速表是实验前的目标转速,不是实时值。齿轮箱带负载后会有转差,实际轴频和目标设定可能有差异,差别超过1%,在长时FFT中就会造成特征频率漂移。
解决:在设计数据包时尽量记录转速通道,哪怕只是一路键相脉冲也比没有强。如果历史数据里没有转速通道,我会手动从振动信号中提取“轴转频”,找频谱里接近理论轴频的最大峰值,然后用这个轴频去计算啮合频率和轴承特征频率。提取轴频要注意谱线分辨率,如果窗口只有0.1秒,频率分辨率只有10Hz,转速3500rpm都标不精确;把窗口加长到1秒以上,频率分辨率到1Hz,才比较可靠。特征频率算不准的时候,先怀疑转速,再怀疑滤波器,最后才怀疑算法。
6. 让数据集更值钱:验证集设计与小样本迁移技巧
一套齿轮箱故障诊断数据能不能被长期复用,取决于验证方案是否真正反映了现场泛化能力。我的习惯是除了按文件分组,还要做“跨工况验证”:用900rpm训练,用1800rpm测试。这个验证结果才是你部署到新转速时的心理底线。如果跨工况掉点严重,就先做特征缩放或者域对齐,而不是急着改网络结构。
小样本迁移上,一个轻量但实用的技巧是从源域特征统计迁移:把健康样本的均值和标准差对齐到目标域,再做线性变换。下面的代码表达这个思路,输入是源域特征矩阵X_src和目标域特征矩阵X_tgt,输出对齐后的源域特征:
def coral_features(X_src, X_tgt): mu_s = X_src.mean(axis=0) std_s = X_src.std(axis=0) + 1e-8 mu_t = X_tgt.mean(axis=0) std_t = X_tgt.std(axis=0) + 1e-8 return (X_src - mu_s) / std_s * std_t + mu_t这个CORAL变换不要求你有任何目标域标签,只要目标域能采集到少量无标注振动信号,就能把特征分布拉近。在实际项目中,这个方法比直接微调深度学习模型简单得多,也更容易向现场同事解释:相当于把源域的“说话口音”标准化成目标域的口音,再让分类器去识别。然后再用目标域里极少量有标签样本做一次逻辑回归或随机森林微调,往往能补回跨工况的大部分掉点。
我踩过最重的一次坑是把重叠窗口随机划分当成了默认操作,测试集准确率98%,现场demo却完全翻车。复盘时才发现数据的source_file字段没有保留,无法做分组验证,整个训练流程都得重跑一遍。从那以后,我把“必须能解释每个样本来自哪段原始信号”写进了数据处理规范里。数据泄漏这类问题,靠理论很难察觉,但通过在验证集上记录清楚来源就能提前堵住。
如果你手头也有一份“齿轮箱故障数据.zip”,建议今天先别急着训模型,先花半小时做三件事:确认EOCD完整、检查每个文件是否有转速记录、按文件分组做一次基线验证。这三步做完,数据包到底能不能用,你的心里就有数了。希望帮到你。
本文还有配套的精品资源,点击获取