简介:这份数据集面向从事电机故障诊断的工程师、设备维护人员及高校研究人员,系统整理了转子偏心、轴弯曲、转子条断裂、电机缺相、轴承故障、匝间短路、转子不平衡等七类典型故障的频谱特征,可作为振动分析入门与现场排查的对照参考。资源包内含1个docx文档,约608KB,以表格与文字结合的方式归纳各类故障的特征频率、边带规律及诊断建议,便于快速查阅与比对。目前已有947人学习下载,说明其在电机状态监测领域具有一定实用价值。读者可从中获取各故障对应的频谱判据,例如两倍行频、极通过频率边带、1X主导成分等关键指标,并了解匝间短路需借助电路分析而非振动信号监测的差异,从而提升故障定位效率,也可作为开发智能诊断系统的数据素材。
1. 故障电机典型频谱特征整理数据集:从振动信号到可训练样本的落地路径
一台 7.5kW 三相异步电机在试验台上跑到 70% 额定负载时,加速度传感器采到的时域波形看不出任何异常,但把同一段信号做 FFT 之后,在 2 倍电源频率附近冒出一根明显抬升的边带——这类"时域平静、频域露馅"的场景,正是故障电机典型频谱特征整理数据集要解决的核心问题。它本质上是一份把电机常见故障(轴承内圈/外圈剥落、转子断条、定子绕组匝间短路、气隙偏心、不对中)对应的振动频谱特征,按统一采样规范、统一标签体系整理成结构化样本的数据集,让做设备预测性维护的工程师能直接拿去训练分类模型或做规则阈值标定。适合两类人:一是手上只有原始振动采集、缺标注的运维算法工程师;二是想验证自己特征提取流程是否靠谱的故障诊断方向开发者。数据集不是万能的,它最大的价值在于把"频谱长什么样算哪种故障"这件事从老师傅的经验里抠出来,变成可复现、可对比的数值。
2. 频谱特征到底怎么变成一条样本:采集、变换与标签设计
2.1 从时域到频域:为什么故障特征藏在频谱里
电机故障的物理机理决定了它的振动能量会集中在特定频率上。轴承外圈剥落时,滚珠每次碾过缺陷点会产生一次冲击,冲击的重复频率就是外圈故障特征频率 BPFO;转子断条会让转子回路不对称,在电源频率两侧调制出 (1±2s)f₁ 的边带;气隙偏心则会在 2 倍电源频率处产生异常分量。这些频率成分在时域里被噪声淹没,只有通过频谱分析才能把能量重新分配到频率轴上,让特征"显形"。
所以整理数据集的第一步不是采集,而是想清楚:我要保留哪些频率分辨率、哪些频段。常见做法是采样率取 10~20 kHz,覆盖到 5 倍以上最高关注频率,避免混叠;频率分辨率 Δf = fs/N,N 取 8192 或 16384,对应 Δf 约 1.2 Hz 或 0.6 Hz,足够分辨边带间隔。如果分辨率太粗,断条边带和主频会糊在一起,模型学到的就是噪声。
2.2 一条样本的字段设计:我一般会保留这 6 个维度
数据集不是只存一张频谱图就完事。为了让下游既能做深度学习也能做传统特征工程,每条样本我建议保留以下字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| sample_id | string | 唯一编号,建议含工况信息 |
| raw_signal | float32[] | 原始时域波形,长度 8192/16384 |
| spectrum | float32[] | 单边幅值谱,长度 N/2 |
| fault_label | int | 故障类别编码,0 为健康 |
| severity | float | 故障严重程度,0~1 归一化 |
| rpm | float | 采集时转速,用于频率归一化 |
其中 severity 这个字段很多人会忽略,但它是区分"早期微弱故障"和"晚期明显故障"的关键。没有它,模型会把所有同类故障当成一个模式,实际部署时早期故障根本检不出来。
2.3 标签体系怎么定:别把多故障混采当成单标签
真实电机很少只坏一处。轴承故障往往伴随不对中,断条可能引发偏心。如果数据集强行给每条样本打单一标签,模型上线后遇到复合故障就会乱判。我的做法是采用多热编码(multi-hot),同时保留一个主故障标签用于分层采样。下面是一段把原始采集目录整理成标准样本的 Python 脚本骨架:
import numpy as np import os import json from scipy.fft import rfft, rfftfreq FS = 12000 # 采样率 Hz N = 8192 # 每段点数 FAULT_MAP = { # 故障类别编码 "healthy": 0, "bearing_inner": 1, "bearing_outer": 2, "rotor_broken": 3, "stator_short": 4, "eccentricity": 5, "misalignment": 6 } def load_segment(path): """读取一段时域信号,要求单列 float 文本或 npy""" if path.endswith(".npy"): sig = np.load(path).astype(np.float32) else: sig = np.loadtxt(path, dtype=np.float32) if len(sig) < N: raise ValueError(f"{path} 长度不足 {N}") return sig[:N] def to_spectrum(sig): """单边幅值谱,做汉宁窗抑制泄漏""" win = np.hanning(len(sig)) spec = np.abs(rfft(sig * win)) / (len(sig) / 2) return spec.astype(np.float32) def build_sample(sig, label_name, severity, rpm): spec = to_spectrum(sig) return { "sample_id": f"{label_name}_{rpm:.0f}_{np.random.randint(1e6):06d}", "raw_signal": sig.tolist(), "spectrum": spec.tolist(), "fault_label": FAULT_MAP[label_name], "severity": float(severity), "rpm": float(rpm) } if __name__ == "__main__": root = "./raw_data" samples = [] for fault in os.listdir(root): for f in os.listdir(os.path.join(root, fault)): sig = load_segment(os.path.join(root, fault, f)) # severity 和 rpm 建议从文件名或旁路元数据读取 samples.append(build_sample(sig, fault, severity=0.5, rpm=1480)) with open("dataset.json", "w") as fp: json.dump(samples, fp) print(f"共生成 {len(samples)} 条样本")这段脚本的逻辑很直白:统一截断到固定长度、加汉宁窗做 FFT、把时域和频域都存下来。参数上,FS必须和实际采集卡一致,写错会导致所有特征频率整体偏移;N决定频率分辨率,8192 在 12 kHz 采样下给出约 1.46 Hz 分辨率,对 50 Hz 电源系统的边带分析够用。np.hanning窗会略微展宽主瓣,但能压住频谱泄漏,避免边带被主频旁瓣淹没——这是很多人第一次做频谱整理时翻车的地方。
2.4 频率归一化:让不同转速的样本能放在一起训
同一台电机在 900 rpm 和 1480 rpm 下采到的故障特征频率完全不同。如果直接拿原始频率轴去训练,模型会先学转速、再学故障,泛化很差。常见做法是把频率轴除以转频,转成"阶次"(order),这样轴承故障特征就固定在 BPFO/转频 这个比值上。整理数据集时,我一般会额外存一列 order_spectrum,或者至少把 rpm 字段留好,让下游自己转。这一步不做,数据集在变转速工况下基本废掉一半。
3. 用整理好的数据集跑通一个基线分类器:从加载到评估
3.1 数据加载与划分:按工况分层,别随机切
拿到 dataset.json 之后,第一件事是划分训练/验证/测试集。如果直接train_test_split(random_state=42),同一段连续采集被切到两边,验证集准确率会虚高到 99%,上线就崩。正确做法是按采集批次或按转速工况做分组划分。下面这段代码演示按 rpm 分层:
import json import numpy as np from sklearn.model_selection import GroupShuffleSplit with open("dataset.json") as fp: data = json.load(fp) X = np.array([d["spectrum"] for d in data], dtype=np.float32) y = np.array([d["fault_label"] for d in data]) groups = np.array([round(d["rpm"] / 100) for d in data]) # 按转速分桶 gss = GroupShuffleSplit(n_splits=1, test_size=0.3, random_state=0) train_idx, test_idx = next(gss.split(X, y, groups)) X_train, X_test = X[train_idx], X[test_idx] y_train, y_test = y[train_idx], y[test_idx] print(f"训练集 {len(X_train)} 条,测试集 {len(X_test)} 条")GroupShuffleSplit保证同一转速桶的样本不会同时出现在训练和测试里,这样评估出来的准确率才接近真实换工况场景。test_size=0.3是经验值,样本量少于 2000 条时可以放到 0.2,但别低于 0.15,否则测试集统计波动太大。
3.2 基线模型:先上逻辑回归,别一上来就堆 CNN
很多人拿到频谱数据第一反应是搭一维卷积网络。我的血泪经验是:先用逻辑回归或 SVM 跑一个基线,确认特征本身可分,再决定要不要上深度模型。如果线性模型都能到 90%,说明频谱特征很干净,深度模型提升空间有限;如果线性模型只有 60%,再去看是特征没对齐还是标签有问题。
from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from sklearn.metrics import classification_report clf = make_pipeline( StandardScaler(), LogisticRegression(max_iter=2000, C=1.0, multi_class="multinomial") ) clf.fit(X_train, y_train) pred = clf.predict(X_test) print(classification_report(y_test, pred, digits=3))StandardScaler对频谱幅值做零均值单位方差,避免高频段小幅值被低频段大幅值压制。C=1.0是正则强度,频谱维度高(4096 维)时容易过拟合,可以降到 0.1 试试。max_iter调到 2000 是因为高维数据下默认 100 次往往不收敛,会报 ConvergenceWarning——这不是模型坏了,是迭代不够。
3.3 看混淆矩阵:故障类别之间的混淆才是真问题
准确率只是一个数,真正有价值的是看哪两类在互相混。轴承内圈和外圈故障在包络谱上区别明显,但在原始幅值谱上可能因为边带重叠而混淆;转子断条和偏心都表现为电源频率附近的调制,也容易混。整理数据集时如果发现这两类混淆严重,说明要么频率分辨率不够,要么需要补包络谱特征。下面这段代码把混淆矩阵打出来:
from sklearn.metrics import confusion_matrix import numpy as np cm = confusion_matrix(y_test, pred) labels = ["healthy", "inner", "outer", "rotor", "stator", "ecc", "misalign"] print(" " + " ".join(f"{l:>8}" for l in labels)) for i, row in enumerate(cm): print(f"{labels[i]:>8} " + " ".join(f"{v:>8d}" for v in row))如果发现 inner 和 outer 互相混,优先检查 BPFO 和 BPFI 是否在频率轴上分得开——这直接回到 2.1 节的频率分辨率问题。数据集整理阶段埋的坑,模型阶段一定会暴露。
4. 避坑与排查:整理频谱数据集时最容易翻车的 5 件事
4.1 现象:所有样本频谱在 50 Hz 处都有一根巨峰,模型只学这一根
原因:采集时没有做去趋势或没有滤掉工频干扰,50 Hz 电源频率及其谐波能量远大于故障特征,标准化之后仍然主导方差。
解决:在 FFT 之前加一个 50 Hz 陷波滤波器,或者在做完频谱后把 50 Hz 及其 2、3 次谐波附近 ±2 Hz 的谱线置零。注意别把边带一起抹掉,断条特征就在 50 Hz 两侧。
4.2 现象:训练集准确率 99%,测试集 60%
原因:按随机方式切分,同一段连续采集的相邻样本同时进了训练和测试,模型记住了这段信号的噪声指纹而不是故障特征。
解决:按采集批次或转速分组切分,参考 3.1 节的 GroupShuffleSplit。如果数据本身就是一段长信号切出来的,至少保证切分边界有重叠丢弃。
4.3 现象:severity 字段全是 0.5,模型分不清早期和晚期故障
原因:整理时图省事,没有从元数据里读取故障注入程度,统一填了默认值。
解决:如果原始采集没有 severity 记录,可以用故障特征频率处的幅值相对于健康基线的抬升倍数做代理指标,归一化到 0~1。这个代理不完美,但比常数强。
4.4 现象:不同采集批次的频谱幅值量纲差十倍
原因:传感器灵敏度、放大器增益、采集卡量程在不同批次间不一致,整理时没有做幅值归一化。
解决:每条样本的频谱除以自身总能量(L2 范数)或除以健康基线在相同工况下的频谱,做相对幅值。绝对幅值只在同一批次内可比。
4.5 现象:数据集里健康样本远多于故障样本,模型全预测健康也有 85% 准确率
原因:故障电机采集成本高,健康数据容易攒,类别不平衡是常态。
解决:训练时用 class_weight='balanced',评估时看宏平均 F1 而不是准确率。整理数据集时也可以在文档里明确标注类别分布,让下游心里有数。
5. 进阶技巧:用阶次谱和包络谱把弱故障从噪声里捞出来
整理到这一步,基础数据集已经能跑通分类。但如果你面对的是早期微弱故障——特征频率处幅值只比噪声底高两三个 dB——单靠原始幅值谱还是不够。我一般会补两个衍生特征:阶次谱和包络谱。
阶次谱的做法是把频率轴除以转频,得到无量纲的阶次轴。这样同一故障在不同转速下的特征会对齐到同一阶次位置,模型不用再学转速。实现上,先对时域信号做角域重采样(需要转速脉冲或转速估计),再 FFT。如果没有转速脉冲,可以用谱峭度法估计转频,但精度会打折。
包络谱更适合轴承故障。轴承冲击是高频调制信号,直接看幅值谱会被其他低频成分淹没。做法是先带通滤波到轴承共振频带(通常 2~5 kHz),再做希尔伯特变换取包络,最后对包络做 FFT。包络谱上 BPFO、BPFI 及其谐波会非常干净地冒出来。下面是一段包络谱提取的代码:
from scipy.signal import butter, filtfilt, hilbert from scipy.fft import rfft, rfftfreq def envelope_spectrum(sig, fs, band=(2000, 5000)): """带通滤波 + 希尔伯特包络 + FFT""" b, a = butter(4, [band[0]/(fs/2), band[1]/(fs/2)], btype="band") filtered = filtfilt(b, a, sig) env = np.abs(hilbert(filtered)) env = env - np.mean(env) # 去直流 spec = np.abs(rfft(env)) / (len(env) / 2) freqs = rfftfreq(len(env), 1/fs) return freqs, spec.astype(np.float32)band的选择是这套方法里最玄学的一步。选窄了,共振频带没覆盖全,包络里冲击成分弱;选宽了,噪声进来把包络淹没。我一般会先对健康信号做谱峭度图,找峭度最大的频带作为带通范围。butter的阶数取 4 是折中,阶数太高会引入相位失真,阶数太低阻带衰减不够。filtfilt做零相位滤波,避免包络时间偏移。
把包络谱和阶次谱都整理进数据集之后,你会发现原本混在一起的内圈/外圈故障在包络谱上分得很开,因为 BPFO 和 BPFI 的数值差异在包络域被放大了。代价是数据集体积会涨——每条样本多存两列频谱。我的习惯是原始幅值谱、包络谱、阶次谱各存一份,让下游按任务选,而不是替他们做决定。
最后说一个我踩过的坑:包络谱的幅值量纲和原始谱完全不同,如果混在一起做标准化,包络谱会被原始谱的大幅值压成一条平线。正确做法是分别标准化,或者干脆分成两个输入通道。这个细节不写进数据集文档里,下游十有八九会翻车。希望帮到你。
本文还有配套的精品资源,点击获取