news 2026/10/28 10:43:30

齿轮箱故障数据.zip:从振动信号到包络谱诊断与分组验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
齿轮箱故障数据.zip:从振动信号到包络谱诊断与分组验证

简介:面向机械故障诊断与工业信号分析学习者的齿轮箱振动数据集,覆盖无故障、断齿、三齿磨损三类全负载工况实测记录,可用于机器学习建模、故障特征提取与预测性维护实验。压缩包共15个文件、约5.91MB,以mat格式原始振动信号为主体,配套6张png时域波形与频谱图、2个Python数据处理与振动分析脚本、1份记录齿数与啮合频率偏差的csv、pdf实验数据集说明文档及txt解读材料,构成从原始采集到分析出图的完整链路。已有846人学习下载。读者可借助脚本完成傅里叶变换等频域特征提取,对照图像比较不同故障模式的频率成分差异,并据此搭建分类模型验证诊断效果,适合课程实验与入门级故障诊断项目参考。

1. 齿轮箱故障数据.zip 里到底是什么:从压缩包到可用振动信号

齿轮箱故障数据.zip 这类压缩包,多半来自数据集合集、课程资料或设备厂商的示例包:解开后是按故障类型命名的文件夹,里面以 csv、mat 或 txt 保存数据,每条记录是加速度传感器采到的一段电压序列,转速、负载、采样率要么写在文件名里,要么只能靠信号本身反推。它解决的核心问题只有一个:没有台架、没有拆机权限,也能拿到「已知故障类型 + 已知工况」的振动波形,把诊断算法跑到能得出混淆矩阵。适合做旋转机械状态监测的算法工程师、要跑出课程结果的学生,以及验证边缘盒子推理链路的嵌入式开发。它不是开箱即用的训练集:从压缩包到能喂进模型的特征矩阵,中间隔着采样率核对、特征频率计算、切窗和防泄漏划分四道工序,哪一步糊弄,后面的准确率都是假的。

2. 拆包先看骨架:文件命名、采样率与特征频率

压缩包解开之后第一件事不是写模型,而是把「目录结构 + 文件名 + 采样参数」这三样东西弄清楚。经验上,一个齿轮箱故障数据集的信息密度最高的地方就是文件名——作者往往把故障类型、转速、负载、通道号、序号全塞进了一串下划线里。你把这串东西解析成一张表,后面所有的过滤、分组、画图都是在这张表上做;解析不了,就只能靠肉眼一个个点开文件看波形,几十个文件还能忍,几百个就是纯体力活。

第二件事是算特征频率。齿轮箱故障诊断之所以比一般的异常检测更「可解释」,就是因为齿轮和轴承的故障频率是可以从几何参数和转速算出来的:齿面点蚀、断齿会在啮合频率两侧长出一圈边带,轴承内圈、外圈的剥落会在包络谱上留下固定间隔的谱线。这些频率你不算,就只能看模型输出的概率值,出了错连往哪查都不知道;算了,哪怕模型翻车,你也能回到谱上找到原因。

第三件事是核对数据本身完不完整。公开数据集或示例包里出现采样中断、量程削顶、通道接反的情况并不罕见,这些缺陷在特征表上是看不出来的,只有回到原始波形才暴露。

2.1 目录树和文件名里藏着的标签

常见的组织方式是「一级目录按故障类型分,二级目录按工况分,文件名里带通道和序号」,大致长这样:

gearbox_fault/ ├── health/ │ ├── health_1500rpm_0Nm_ch1_001.csv │ └── health_1500rpm_0Nm_ch1_002.csv ├── gear_missing_tooth/ │ ├── gms_1500rpm_0Nm_ch1_001.csv │ └── gms_1200rpm_5Nm_ch1_001.csv ├── gear_wear/ └── bearing_inner/

这种结构最省事,因为标签同时存在于目录名和文件名里,两边可以互相校验。也有把标签只写在目录名、文件名纯序号的,那就先按目录归类再编号。下面这段脚本把整棵树扫成一张索引表:

import re from pathlib import Path import pandas as pd ROOT = Path("./gearbox_fault") # 解压后的根目录 # 文件名示例:gms_1500rpm_0Nm_ch1_001.csv PAT = re.compile( r"(?P<tag>[a-z]+)_" r"(?P<rpm>\d+)rpm_" r"(?P<load>\d+)Nm_" r"(?P<ch>ch\d+)_" r"(?P<idx>\d+)", re.I, ) rows = [] for p in ROOT.rglob("*"): if p.suffix.lower() not in {".csv", ".mat", ".txt"}: continue m = PAT.search(p.stem) if m is None: # 解析失败不要丢,单独标记后人工看,命名不规范的文件往往正是额外工况 rows.append({"path": str(p), "tag": None, "rpm": None, "load": None, "ch": None, "idx": None}) continue rows.append({"path": str(p), **m.groupdict()}) idx = pd.DataFrame(rows) idx["label"] = idx["path"].str.split("/").str[-2] # 用上级目录名兜底做标签 print(idx["tag"].value_counts(dropna=False)) print(idx["label"].value_counts()) idx.to_csv("index.csv", index=False)

几个地方值得说明。rglob("*")是递归遍历,比手写os.walk干净;后缀过滤一定要做,因为解压包里常混着.png、README.txt、.DS_Store这类文件,不滤掉后面读文件会直接抛异常。正则用了命名分组,好处是m.groupdict()直接变成字典,塞进 DataFrame 时列名天然对齐,不用再手动拼。

关键的一点是解析失败时保留行、把字段置空,而不是continue跳过。踩过的坑是:某一批文件命名时漏了转速字段,名字成了gms_0Nm_ch1_007.csv,如果按「解析失败就丢」的逻辑,这批文件会静默消失,而它们恰好是唯一一组变转速工况——模型在你的测试集上表现很好,一上真实设备就崩。留一个tag is None的子集,跑完统计一眼就能看见。

最后加一句:标签别只信目录名。有些包会把bearing_inner和bearing_outer的文件夹放反,或者在同一个目录里混装两种故障。用聚类或者简单的时域统计粗看一遍每类的分布,比无条件相信目录结构稳妥。

2.2 采样率、转速、齿数:算清楚该看哪根谱线

齿轮箱的频谱主要由三类频率构成:轴转频及其谐波、齿轮啮合频率及其边带、轴承故障特征频率及其谐波。它们的算法都是初等代数,但要先把几何参数凑齐。

转频与啮合频率:

fr = rpm / 60 # 单根轴的转频,单位 Hz GMF = z * fr # 啮合频率,z 为该对齿轮中主动轮的齿数 边带 = GMF ± k * fr (k = 1, 2, 3…) # 故障调制出来的边带族

滚动轴承的四个特征频率(外圈 BPFO、内圈 BPFI、滚动体 BSF、保持架 FTF),设滚动体数 N、滚动体直径 d、节圆直径 D、接触角 α:

BPFO = (N/2) * fr * (1 - (d/D) * cos α) BPFI = (N/2) * fr * (1 + (d/D) * cos α) BSF = (D/(2d)) * fr * (1 - ((d/D) * cos α)**2) FTF = (fr/2) * (1 - (d/D) * cos α)

拿一组示例参数走一遍,转速 1500 rpm 即 fr1 = 25 Hz,一级减速主动轮 29 齿、从动轮 100 齿:

参数取值推导
输入轴转频 fr125.0 Hz1500 / 60
减速比3.45100 / 29
输出轴转频 fr27.25 Hz25 × 29 / 100
啮合频率 GMF725 Hz29 × 25
GMF 二次、三次谐波1450 / 2175 Hz需要 fs > 4.4 kHz
轴承外圈特征频率 BPFO≈ 89 Hz取 N=9, d/D=0.207, α=0
轴承内圈特征频率 BPFI≈ 136 Hz同上

这几个数一出来,后面所有决策都有了依据。窗长要多长?至少要覆盖几个 fr2 周期,否则 7.25 Hz 的边带根本分辨不出来。带通滤波选多宽?轴承故障激起的共振频带通常在 2 kHz 以上,如果你的采样率只有 4 kHz,那就别指望做包络分析。滤波器阶数怎么定?先用 4 阶 Butterworth 打底,过渡带够用、相位失真可控。

提示:齿数和轴承几何参数如果压缩包里没给,不要自己猜了填表。宁可只用不依赖几何参数的指标(RMS、峭度、谱峭度、频带能量占比),也不要用一个错的特征频率去解释包络谱——那是最容易被「看起来能对上」的巧合骗到的环节。

2.3 用一段脚本把每个文件体检一遍

索引表建好之后,写一个体检脚本,对每个文件输出长度、时长、缺失值、削顶比例和 RMS,五列就够:

import numpy as np import pandas as pd def health_check(path, fs=20000, clip_thr=0.98): x = pd.read_csv(path, header=None).iloc[:, 0].to_numpy(dtype=float) peak = np.abs(x).max() + 1e-12 return { "n": len(x), "dur_s": round(len(x) / fs, 3), "nan": int(np.isnan(x).sum()), "clip_ratio": float((np.abs(x) > clip_thr * peak).mean()), "rms": float(np.sqrt(np.mean(x ** 2))), } idx = pd.read_csv("index.csv") report = pd.DataFrame([health_check(p) for p in idx["path"]]) summary = pd.concat([idx[["label", "rpm", "load"]], report], axis=1) print(summary.groupby(["label", "rpm"])[["dur_s", "rms", "clip_ratio"]].describe())

怎么读这张表。dur_s在同一工况下应该高度一致,出现明显偏短的,多半是采集中断或者文件被截断,这类样本切窗时会切出半截。clip_ratio大于 0.001 就要警惕,说明信号在传感器量程附近反复触顶,削顶后的波形是平的,峭度会被人为拉低,包络谱上还会冒出一堆和故障无关的高次谐波——这是典型的「数据缺陷伪装成故障特征」。rms在同一类、同一转速下应该在同一个量级上,某一类整体高出 5 倍以上,先怀疑通道接错或者单位不对(有的是 g,有的是 mV),而不是先怀疑故障。

真实项目里这五列救过命:某次一批bearing_outer样本的rms全都比health低一个数量级,一开始以为是早期故障幅值小,后来发现是那批文件的信号列在第 3 列,脚本按第 1 列取到了时间戳。这种错误在特征表上完全看不出来,只有回到原始文件才现形。

3. 从原始波形到样本矩阵:读取、切窗、标注与划分

数据体检通过之后,才轮到构造样本。这一步的产出是一张三要素对齐的表:特征矩阵 X、标签 y、分组 id g。第三列最容易被忽略,但它决定了后面的评估结果可不可信。整个流程里,读取环节负责「别取错列」,切窗环节负责「别切太短」,划分环节负责「别让同一段信号同时出现在训练和测试里」。

很多人在这三步上都做对了单个动作,但顺序错了:先切窗再划分,于是同一段连续信号切出来的相邻窗,一半在训练集、一半在测试集。因为相邻窗高度相关,模型只要记住这段波形就能拿高分,准确率 99% 而换一台设备就掉到 60%。这类翻车是静默的,没有任何报错,唯一能发现它的办法就是在切窗之前就把分组 id 打上。

3.1 三种格式的读取差异与通道对齐

csv 和 txt 相对简单,麻烦的是分隔符和表头。有的包用逗号,有的用制表符,有的第一行是列名、有的直接是数据。稳妥写法是先读前两行看看,再决定header和sep:

def sniff_csv(path): head = pd.read_csv(path, header=None, nrows=3) # 第一行如果全是字符串,基本可以判定是表头 has_header = all(isinstance(v, str) for v in head.iloc[0].tolist()) sep = "\t" if head.shape[1] == 1 and "\t" in open(path).readline() else "," return {"header": 0 if has_header else None, "sep": sep}

.mat 的情况更复杂一些。scipy.io.loadmat返回的是一个字典,里面除了信号还有__header__、__version__、__globals__三个元数据键,直接对返回值做np.array()会得到一个 dtype=object 的怪东西,后面所有运算全报错。加上squeeze_me=True可以避免读出来是(1, 1, 10000)的三维数组,但真正的坑是「键名不固定」——不同批次的导出脚本可能把通道命名为acc1、CH1、X1_DE_time,写死键名一定会翻车。

import numpy as np import scipy.io as sio def load_mat_signal(path, prefer=("acc", "ch", "x")): raw = sio.loadmat(path, squeeze_me=True, struct_as_record=False) keys = [k for k in raw if not k.startswith("__")] # 优先按通道名匹配,匹配不到再退回"最长的一维数值数组" cand = [] for k in keys: v = raw[k] if np.ndim(v) == 1 and any(p in k.lower() for p in prefer): cand.append(k) if not cand: cand = [k for k in keys if np.ndim(raw[k]) == 1 and np.size(raw[k]) > 1000] if not cand: raise KeyError(f"{path} 无可用通道,实际键:{keys}") key = max(cand, key=lambda k: np.size(raw[k])) x = np.asarray(raw[key], dtype=float).ravel() return x, key

逻辑上分三层:先按名字匹配,名字匹配不到就退回「长度超过 1000 的一维数组」,再不行就抛异常并把实际的键打印出来。最后这一步很重要,报错信息里带上keys,排查时间能从半小时缩到十秒。ravel()兜底处理个别文件被存成(n, 1)列向量的情况。

通道对齐的原则是:永远按名字取,不按列索引取。同一个数据集里,不同批次的列顺序可能不一样,某批是「时间、通道1、通道2」,另一批是「通道1、时间、通道2」,按索引取第 0 列,第一批拿到时间戳(单调递增,看起来还挺像信号),第二批拿到真信号。这种错误的特征是模型在第一批上表现极差、在第二批上正常,很容易被误判成工况差异。

3.2 窗长用转频算,不要用习惯值

「切 2048 点」几乎是所有人上手时的默认选择,因为 FFT 方便、显存友好、代码短。但如果采样率是 20 kHz,2048 点只有 0.1 秒,频率分辨率是 9.77 Hz。而前面算出来输出轴转频是 7.25 Hz,边带间隔就是 7.25 Hz —— 分辨率比间隔还宽,边带在谱上会糊成一根,包络解调看到的东西全是混叠出来的。这不是模型的问题,是窗长的问题,而且它无法靠增加训练轮数补救。

窗长有两个约束:

时域约束:N ≥ cycles / fr_min * fs # 至少覆盖 cycles 个最低转频周期 频域约束:N ≥ fs / (df_ratio * fr_min) # 分辨率 Δf = fs/N 要小于 df_ratio * fr_min
import numpy as np def pick_window(fs=20000, fr_min=7.25, cycles=5, df_ratio=0.2): n_time = int(np.ceil(cycles / fr_min * fs)) # 13793 n_freq = int(np.ceil(fs / (df_ratio * fr_min))) # 13793 n = max(n_time, n_freq) # 向上取到 2 的幂,FFT 更快,也方便后面统一 batch return int(2 ** np.ceil(np.log2(n))) N = pick_window() print(N, N / 20000, 20000 / N) # 16384 0.819 s 1.22 Hz

参数含义:cycles是窗内包含的最低转频周期数,取 5 意味着最慢那根轴的波形至少走完 5 圈,转频调制的信息才不会丢;df_ratio是允许的频率分辨率相对最低转频的比例,取 0.2 表示分辨率要比边带间隔细 5 倍。按 fs=20 kHz、fr_min=7.25 Hz 算出来是 16384 点、0.82 秒、分辨率 1.22 Hz,边带能干净地分开。

实际切窗时叠 50% 重叠做样本增广,重叠比例别超过 75%,否则相邻样本几乎一样,等于给训练集注水。滑窗代码:

def sliding_window(x, N, hop=None): hop = hop or N // 2 if len(x) < N: return np.empty((0, N)) k = 1 + (len(x) - N) // hop idx = np.arange(N)[None, :] + hop * np.arange(k)[:, None] return x[idx]

用np.arange花式索引一次性取出所有窗,比 Python 循环快一到两个数量级;len(x) < N时返回空数组而不是报错,让上层决定是丢弃还是零填充。

注意:不同转速下算出来的窗长不一样。如果一个数据集里混着 1500 rpm 和 600 rpm,要么统一按最低转速算一个偏长的窗,要么按转速分组各自切窗,然后重采样到同一长度。前一种做法简单,后一种做法保真,选哪个取决于你要不要做变工况迁移。

3.3 标签生成与划分:按工况分组,别按样本随机

标签生成本身不难,难的是划分。train_test_split的默认行为是随机打乱,对这份数据来说是错的,因为同一个文件切出来的几十个窗高度相关。正确的做法是按「文件」或者按「工况(rpm × 负载)」分组,用GroupKFold:

import numpy as np from sklearn.model_selection import GroupKFold # groups 的每一个元素对应一个窗,值是该窗来源文件(或工况)的 id groups = np.repeat(file_ids, windows_per_file) gkf = GroupKFold(n_splits=5) for tr, te in gkf.split(X, y, groups=groups): assert set(groups[tr]).isdisjoint(set(groups[te])) # 自检:分组必须不相交 ...

assert那一行不是装饰,是后悔药。任何一次改动切窗逻辑之后,先跑这一行,确认分组确实不相交,再谈模型效果。血泪经验是:把切窗函数从「按文件切」改成「拼起来统一切」之后,分组 id 忘了同步更新,结果训练集和测试集共享了文件,跑了三天实验才发现,前面的结论全部作废。

如果目标是评估「换工况还能不能用」,那分组粒度要提到工况一级:留一工况交叉验证,五折就是每次留一个转速-负载组合不参与训练。这个设置下的准确率通常比随机划分低 10~30 个百分点,但那个数字才是现场能指望的。

3.4 归一化的三种做法与各自的代价

归一化看着是标准操作,实际是个选择题,因为振动信号的幅值本身就携带故障信息——齿面剥落的冲击会让 RMS 显著升高,如果归一化把这个差异抹掉,等于把最直白的线索删了。

做法统计量来源优点代价
全局 z-score全体样本合并实现最简单把类间的幅值差异也压平,弱故障更难分
逐样本 z-score每个窗自己消除传感器灵敏度、增益差异幅值类特征全废,只剩波形形状
逐文件/逐工况 z-score同一文件或同一工况消除通道差异,保留类间幅值差需要文件级分组信息,冷启动时拿不到

我一般选第三种,代码上就是把统计量按file_id分组算:

def norm_by_group(X, gids): X = X.copy() for g in np.unique(gids): m = gids == g mu = X[m].mean(axis=0) sd = X[m].std(axis=0) + 1e-8 X[m] = (X[m] - mu) / sd return X

+1e-8是防除零,某些通道在健康样本上可能几乎是常数(比如没接的通道全是 0),标准差算出来是 0。这种常量通道应该在归一化之前就识别出来删掉,不然它会在模型里变成一根永远不变、权重却是随机的输入。

4. 特征工程:时域、频域、包络谱三条线

到这一步,样本矩阵已经有了,接下来决定喂什么进去。齿轮箱诊断的经典做法是「人工特征 + 浅层分类器」,近几年流行「原始波形 + 一维卷积」。两条路都有人跑出好结果,但对这份数据我更倾向先做人工特征,理由不是效果,是可解释性:当模型说「这个是内圈故障」,你能指着包络谱上的 BPFI 及其谐波说「对,就是它」;换成端到端网络,你能拿出来的只有一张热力图和一串概率。

人工特征按三条线组织:时域指标反映冲击的强弱和波形的陡峭程度,频域指标反映能量在啮合频率及其边带上的分布,包络谱指标专门盯轴承的低频故障特征频率。三条线互补,单用任何一条都有明显盲区。

4.1 时域指标:谁对早期故障敏感,谁先失灵

常用的六个指标,公式和适用场景:

指标定义对什么敏感什么时候失灵
RMSsqrt(mean(x²))整体能量,故障后期涨得最明显早期故障能量占比小,几乎不动
峰值maxx
峭度E[(x-μ)⁴]/σ⁴冲击性,早期故障最敏感故障发展到后期,冲击密集,峭度反而回落
峰值因子峰值 / RMS冲击的相对强度负载变化时会跟着漂
裕度因子峰值 / (mean(sqrtx))²
波形因子RMS / meanx

峭度那条「先升后降」的曲线是最容易误判的地方。新上手时看到峭度从 3 涨到 8 就以为故障在恶化,实际上继续跑下去它可能回落到 4 —— 因为冲击从「偶发」变成「连续」,高斯性反而回来了。这时候该看的是包络谱上谐波的数量,而不是峭度。

import numpy as np from scipy.stats import kurtosis def time_features(x): rms = np.sqrt(np.mean(x ** 2)) peak = np.abs(x).max() mean_abs = np.mean(np.abs(x)) sqrt_mean = np.mean(np.sqrt(np.abs(x))) ** 2 return { "rms": rms, "peak": peak, "kurt": kurtosis(x, fisher=False), # fisher=False 得到 3 为正常值的峭度 "crest": peak / (rms + 1e-12), "clearance": peak / (sqrt_mean + 1e-12), "shape": rms / (mean_abs + 1e-12), }

fisher=False是个必须记住的参数,默认的fisher=True给出的是超额峭度,健康信号的值是 0 而不是 3,跟教科书对不上,做论文图的时候很容易搞错。分母上的1e-12同样是防除零,某些被截断或者全零的窗会让mean_abs变成 0。

六个指标里有三个(峰值、峰值因子、裕度因子)本质都在描述同一件事:峰值相对其他统计量的比值。它们之间相关性极高,全塞进模型只会增加共线性,后面 4.3 会处理。

4.2 频域与包络解调:把调制边带从噪声里拔出来

频域分析分两层。第一层是直接对原始信号做 FFT,看啮合频率 GMF 及其谐波,以及它们两侧的边带。齿轮出现局部损伤时,每转一圈啮合一次刚度变化,会在 GMF 两侧生成间隔等于转频的边带族,边带的数量和幅值随损伤程度增加。第二层是包络解调,专门对付轴承:轴承故障的冲击激起的是高频共振,故障信息被调制在共振频带附近,直接看频谱只能看到一片 2~6 kHz 的噪声包,必须先把共振频带滤出来、取包络、再对包络做低通或 FFT,才能看到 BPFO / BPFI 那根低频谱线。

import numpy as np from scipy.signal import butter, sosfiltfilt, hilbert def envelope_spectrum(x, fs, band=(2000, 6000), nfft=None): nyq = fs / 2 lo, hi = band[0] / nyq, min(band[1], nyq * 0.98) / nyq sos = butter(4, [lo, hi], btype="bandpass", output="sos") xb = sosfiltfilt(sos, x) # 零相位带通,避免包络相位失真 env = np.abs(hilbert(xb)) # 希尔伯特解析信号的模即包络 env = env - env.mean() # 去直流,否则 0 Hz 掩盖低频峰 nfft = nfft or len(env) win = np.hanning(len(env)) S = np.abs(np.fft.rfft(env * win, n=nfft)) f = np.fft.rfftfreq(nfft, 1 / fs) return f, S

几个参数需要交代清楚。sosfiltfilt用的是二阶节形式的滤波器,数值稳定性比b, a形式好,高阶时不会出现极点漂移;filtfilt前后各滤一遍,相位为零,包络不会整体平移。band的选择是这里最玄学的一环——它应该对准结构的共振频带,而不是随便取。判断方法有两个:一是看原始频谱,找能量抬升明显的宽峰;二是用谱峭度(spectral kurtosis)扫一遍频带,峭度最大的那个子带就是冲击最强的地方。如果数据里有内圈故障样本,验证方法很直接:改band让 BPFI 的谐波在包络谱上最清晰,然后把这一组参数固化下来用到所有样本,不要逐样本调带通——那等于把标签信息泄漏进特征提取。

补一句工程细节:nfft取默认的len(env)时,包络谱的频率分辨率是fs / N,跟原始谱一样。如果窗长是 16384、采样率 20 kHz,分辨率是 1.22 Hz,BPFO 的 89 Hz 和它的一、二次谐波都能落在不同的 bin 上,够用。如果窗短到 2048 点,分辨率 9.77 Hz,89 Hz 和 97.7 Hz 会挤在一起,包络分析等于白做。

拿到包络谱之后,提取的特征一般是这样几类:BPFO / BPFI / BSF 及其 2×、3× 谐波处的幅值,这些谐波幅值的和,以及特征频率与转频的调制边带(比如 BPFI ± fr)的幅值。这些都可以用一个函数批量算:

def order_amplitude(f, S, f0, tol=2.0, harmonics=3): """在包络谱上取 f0 及其谐波附近的峰值幅值,tol 单位 Hz""" out = {} for k in range(1, harmonics + 1): target = f0 * k m = (f >= target - tol) & (f <= target + tol) out[f"h{k}"] = float(S[m].max()) if m.any() else 0.0 return out

tol取 2 Hz 是个折中:太窄,转速有 1% 波动就采不到峰;太宽,容易把邻近的干扰谱线算进来。真正稳妥的做法是用转速计信号做阶次跟踪,把时域信号重采样到等角度域,这样特征频率就固定在阶次上,不受转速波动影响。

4.3 特征筛选:30 维里有一半是重复的

把时域 6 个、频域 8 个、包络谱 12 个特征拼起来,大概 26~30 维。这 30 维里有大量共线性:峰值和峰值因子、裕度因子几乎是一条直线上的三个点;RMS 和各种频带能量之间也高度相关。全喂给逻辑回归会让系数不稳定,喂给树模型影响不大但会稀释特征重要性。

三步走。第一步砍高相关:算 Pearson 相关矩阵,|r| > 0.95的特征对里保留物理意义更明确的那一个,一般保留 RMS 而不是峰值(RMS 更稳)。

import numpy as np import pandas as pd def drop_collinear(df, thr=0.95): corr = df.corr().abs() upper = corr.where(np.triu(np.ones(corr.shape), k=1).astype(bool)) return [c for c in upper.columns if (upper[c] > thr).any()] X_keep = X_feat.drop(columns=drop_collinear(X_feat, 0.95))

np.triu(..., k=1)取上三角,避免把对角线上的 1 和重复的下三角算进去;where把下三角置为 NaN,any()就能正确判断。

第二步看方差膨胀因子(VIF),VIF > 10 的特征说明它能被其余特征线性组合出来,用线性模型时应该删。第三步用树模型的feature_importances_或者置换重要性排序,把贡献接近 0 的砍掉。

注意:PCA 在这类特征上要慎用。降维之后的成分没有物理含义,诊断报告写不出「因为它包络谱上 BPFI 幅值超标」,只写得出「因为第二主成分大于 2.3」——现场没人买账。真要降维,优先用有监督的线性判别(LDA)或者直接让模型学稀疏权重。

5. 齿轮箱故障诊断的避坑清单:五类翻车现场与排查顺序

前面四章讲的是「怎么做对」,这一章讲「哪里会错」。下面五条都是实际跑数据时撞出来的,按「数据 → 信号 → 评估」的顺序排,排查的时候也建议按这个顺序走,因为后面的问题常常是前面埋的雷。

5.1 数据侧:标签名和通道顺序

坑一:同一类故障被拆成了两类。

现象:value_counts()里出现了bearing_inner、Bearing_Inner、inner三个类别,每个类别的样本数都只有预期的一半左右。训练出来的混淆矩阵里,模型把inner判成bearing_inner的错误率很高,而人眼看着这两类一模一样。

原因:解压包合并了不同批次的导出结果,命名规范不统一,大小写、下划线、中英文混用。代码里直接groupby("label"),字符串不相等就是不同类。

解决:在索引表阶段做一次标签归一化,统一小写、去下划线、建立映射表,并且把映射表打印出来人工核对一遍。

CANON = { "health": "healthy", "normal": "healthy", "good": "healthy", "gms": "gear_missing", "missing": "gear_missing", "inner": "bearing_inner", "bpfi": "bearing_inner", "outer": "bearing_outer", "bpfo": "bearing_outer", } idx["label_raw"] = idx["label"] idx["label"] = idx["label_raw"].str.lower().str.replace("-", "_") idx["label"] = idx["label"].map(lambda s: CANON.get(s, s)) print(idx.groupby(["label_raw", "label"]).size()) # 核对映射

归一化之后一定要重新核对类别数,如果某一类的样本数和你预期对不上,说明映射表漏了。

坑二:按列索引取通道,特征全乱。

现象:模型在训练集上表现正常,换一个批次的文件测试,准确率接近随机猜测,且特征分布图和训练集完全对不上。

原因:第一批文件的列是「通道1, 通道2」,第二批是「时间戳, 通道1, 通道2」。代码里写的是df.iloc[:, 0],第一批拿到通道1,第二批拿到时间戳。时间戳单调递增,看起来也是一条曲线,不画图发现不了。

解决:永远只按列名取,取不到就报错;文件没有表头时,先读一行判类型,把单调递增且量级明显不同的列识别为时间戳并剔除。

5.2 信号侧:窗长与带通

坑三:窗太短,边带糊成一根。

现象:频谱图上 GMF 两侧本该有一圈边带,实际只看到 GMF 单峰或者一个宽包;包络谱上 BPFO 附近也是一片隆起,找不到尖锐谱线。分类器在健康 vs 故障上能到 95%,但分不清内圈和外圈。

原因:窗长 2048 点、采样率 12 kHz,频率分辨率 5.86 Hz,而输出轴转频是 7.25 Hz,边带间隔比分辨率还小。

解决:按 3.2 的两条约束重算窗长,往上取到 2 的幂,用 16384 点重切一遍。顺带检查一下 FFT 前有没有加窗——不加窗的泄漏同样会糊掉边带。

坑四:包络谱上找不到轴承特征频率。

现象:明明标签是内圈故障,包络谱上 BPFI 的 136 Hz 却是个小鼓包,还没有旁边的 50 Hz 工频干扰高。

原因可能有四个:带通频带选错了,没盖住共振峰;信号没去趋势,低频漂移把包络压住;原始信号已经削顶,冲击被削平;或者 NPZ 文件里存的是已经滤波过的信号,再做一次带通等于双重滤波。排查顺序建议从「画原始波形」开始,看得见冲击再往下走。

解决:先看原始波形的时域图,冲击是否清晰;再看原始频谱,找到共振峰的位置重新定band;确认信号没有被预处理过;最后检查clip_ratio。

坑五:随机划分造出 99% 的假准确率。

现象:五折交叉验证准确率 99.2%,混淆矩阵干净得像教科书;但换一个转速的样本测试,掉到 62%。

原因:切窗之后用train_test_split随机划分,同一个文件切出来的相邻窗一半在训练、一半在测试。相邻窗重叠 50%,波形几乎相同。

解决:改成GroupKFold,分组 id 用文件名;如果目标是评估跨工况泛化,分组 id 提到工况一级,做留一工况验证。改完之后准确率大概率会掉到 75%~88% 区间,那个数字才是真的。

5.3 评估侧:口径和指标

除了上面五条,还有一个隐性的口径问题:多类问题里各类样本数往往不均衡,健康样本通常远多于故障样本。这时候看总体准确率没有意义,要看每类的召回率和宏平均 F1,尤其是「健康被判成故障」的误报率和「故障被判成健康」的漏报率——后者的代价在现场高得多。评估代码里直接classification_report(y_true, y_pred, digits=3),把每一类都打出来,别只看一个总数字。

6. 让结果站得住:留一工况验证与一条看谱的习惯

模型跑出 95% 之后,我一般不看这个数字,先看另外两样东西。

第一样是留一工况验证的结果表。做法很直接:把工况(转速 × 负载)当成分组,每次留一组完全不参与训练,训完在这组上测,记录每一类的召回率。五折跑下来会得到一张 5 行 × K 列的表,每一行是一次「换工况」的表现。

import numpy as np from sklearn.model_selection import LeaveOneGroupOut logo = LeaveOneGroupOut() rows = [] for tr, te in logo.split(X, y, groups=cond_ids): clf.fit(X[tr], y[tr]) pred = clf.predict(X[te]) rows.append({ "held_out_cond": cond_ids[te][0], **{f"recall_{c}": r for c, r in zip(clf.classes_, recall_score(y[te], pred, average=None, labels=clf.classes_))}, })

这张表比单一准确率信息量大得多。常见的形态是:留出高转速工况时各类召回率都在 85% 以上,留出低转速工况时健康类召回率还行、但早期故障类的召回率掉到 40% 左右。原因不复杂——低转速下冲击能量小,时域指标被噪声淹掉,而训练集里低转速样本又少。看到这个形态,改进方向自然就出来了:要么做阶次跟踪把转速影响归一化,要么在训练集里对低转速做重采样。

第二样是混淆矩阵的方向性。多类问题里我只看两个格子:健康被判成故障的数量,和故障被判成健康的数量。前者高说明模型过于敏感,现场会频繁误停机;后者高说明模型漏检,后果更严重。这两个数字调平衡,比把总体准确率从 92% 推到 95% 有价值得多。很多时候调阈值就能解决,不需要换模型。

还有一个习惯值得单独说:先画谱,再决定要不要上模型。

某次拿到一批新数据,直接在旧流程上跑,准确率只有 68%,折腾了两天换模型、调超参、加特征,最高爬到 73%。后来随手画了一张包络谱,发现所有样本的谱线形状几乎一样,区别只在幅值——再一看原始波形,信号里叠了一个 50 Hz 的强干扰,把整个动态范围都占了,峭度、峰值因子全部失真。加一个 50 Hz 陷波器重跑,同一套模型直接到 89%。

这件事之后,我的顺序固定成:先看时域波形有没有冲击、再看原始频谱找共振峰、再看包络谱上特征频率在不在、最后才轮到模型。谱上看得见的东西,模型才学得到;谱上根本看不见,堆再深的网络也只是在拟合噪声。前面算的那些特征频率,最大的价值不是当特征,是当尺子——用它来判断「这个信号里到底有没有故障信息」,比任何模型评估都直接。

包络谱的带通参数也一样,别指望一次调对。我一般的做法是先在几个候选频带上各画一张包络谱,把 BPFO / BPFI 的谐波族标出来,选那张谐波最清晰的,然后把参数写进配置文件固化下来,后面所有实验都用同一组。这样即使模型翻车,你也知道是模型的问题,不是特征提取又改了口径。

希望这些内容能帮你在拿到一个齿轮箱故障数据压缩包的时候,少走几步弯路。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 10:43:26

数据库安全十大核心实践与防护策略

1. 数据库安全为何如此重要&#xff1f;上周处理的一个生产事故让我至今心有余悸——某客户的核心业务数据库因为弱密码被暴力破解&#xff0c;导致近三个月订单数据被恶意加密勒索。这不是什么好莱坞剧本&#xff0c;而是真实发生在2023年Q2的安全事件。根据Verizon《2023年数…

作者头像 李华
网站建设 2026/10/28 10:42:10

Oracle EBS R12 安装手册:从裸机到可登录环境的完整指南

简介&#xff1a;这份《Oracle EBS R12安装手册》面向需要在Linux x86平台上部署Oracle E-Business Suite R12的DBA、系统管理员及企业信息化实施人员&#xff0c;帮助解决从环境准备到安装落地的完整流程问题。资源为1个PDF文件&#xff0c;压缩包约2.13MB&#xff0c;内容按目…

作者头像 李华
网站建设 2026/10/28 10:42:09

C#嵌入式抓包开发:SharpPcap+PacketDotNet实战指南

简介&#xff1a;这是一份面向C#初学者与网络编程实践者的SharpPcap抓包工具学习资源&#xff0c;聚焦网络数据包捕获、解析与诊断场景&#xff0c;适用于课程设计、协议分析实验及运维排障辅助。压缩包共17个文件&#xff0c;含3个可直接运行的exe程序&#xff08;含图形界面抓…

作者头像 李华
网站建设 2026/10/28 10:42:08

IntelliJ IDEA高效开发环境配置与优化指南

1. IntelliJ IDEA 高效开发环境配置指南作为JetBrains旗下最强大的Java IDE&#xff0c;IntelliJ IDEA在代码智能提示、重构和调试方面表现出色。但要让这个工具真正发挥威力&#xff0c;合理的配置是关键。本文将分享几个提升开发效率的实用配置技巧&#xff0c;涵盖代码可读性…

作者头像 李华
网站建设 2026/10/28 10:41:29

VC++ GDI坐标系映射原理与MM_ANISOTROPIC实战

简介&#xff1a;本资源是一份面向VC初学者与图形编程实践者的坐标系自定义技术详解文档&#xff0c;聚焦解决MFC绘图中逻辑坐标到设备坐标的映射难题&#xff0c;尤其针对可变比例映射模式&#xff08;MM_ANISOTROPIC&#xff09;这一易混淆知识点提供实操级解析。文档以完整对…

作者头像 李华
网站建设 2026/10/28 10:41:06

混合模型VMD-EEMD与LSTM-LSSVM在风速预测中的应用

1. 项目概述&#xff1a;混合模型在风速预测中的创新应用风速预测一直是气象学和可再生能源领域的重要课题。传统单一模型在应对风速序列的非线性、非平稳特性时往往力不从心&#xff0c;这正是混合模型大显身手的地方。我最近完成的一个项目就采用了VMD&#xff08;变分模态分…

作者头像 李华