简介凯斯西储大学CWRU轴承故障数据集信号时频分析文档面向旋转机械故障诊断与状态监测研究者以经典CWRU数据为对象系统说明实验台构成、驱动端/风扇端/基座三类振动信号的获取与含义并示范短时傅里叶变换STFT和连续小波变换CWT在非平稳故障特征提取中的用法。文档覆盖正常信号与0.021英寸内圈、滚珠、外圈故障信号的对比分析对STFT不同尺度16、32、64以及CWT不同小波函数morl、cmor1.5-2、cgau8等进行可视化比较解释重叠比例0.5与尺度序列参数设置给出频率/时间分辨率权衡后的选参结论并附Python代码片段方便读者复现时频图并迁移到其他机械故障案例。资源包共1个文件docx格式约1.05MB正文结构清晰既有理论说明也有结果图解读适合故障诊断初学者入门和科研人员的快速参考。已有1582人浏览学习在轴承故障诊断与信号处理方向具有一定参考价值。1. 凯斯西储大学轴承故障数据集为什么信号时频分析是绕不过去的第一关做设备故障诊断的工程师手里如果没有一套公认的基准数据集几乎没法向别人证明自己的算法到底行不行。凯斯西储大学CWRU的轴承故障数据集是行业内用了二十多年的公开数据凡是做滚动轴承故障诊断、剩余寿命预测、振动信号处理的人基本都拿它当过第一块试金石。这个数据集的价值在于它把三类最典型的轴承故障滚动体、内圈、外圈在不同损伤尺寸、不同载荷工况下的振动信号完整录了下来而且采样频率覆盖 12kHz 和 48kHz信号干净、工况标注清晰非常适合用来验证时频分析方法的真实效果。如果你正在搭建一套轴承故障诊断流程那么先在这个数据集上把信号时频分析的参数调明白再去碰现场采集的复杂数据是一条最稳妥的路径。时频分析之所以在这个数据集上是“绕不过去”的是因为轴承故障信号本质上是非平稳的——故障点每转一圈就冲击一次这种周期性的瞬态冲击在频谱上表现为一系列边频带单靠 FFT 很难把早期微弱故障从背景噪声里分离出来。凯斯西储大学数据集的采样率高、故障特征清晰恰恰是把短时傅里叶变换STFT、连续小波变换CWT、Hilbert 包络谱这些时频分析手段跑通的最佳试验场。下面我会从数据落地、方法选型、特征提取到踩坑排查把整个流程完整讲一遍。2. 从官网下载到本地落地CWRU 数据集的采集工况与文件组织2.1 实验台构成与信号通道先搞清楚你手里拿的是什么凯斯西储大学轴承数据中心使用的是电机驱动系统实验台由 2HP 的电机、扭矩传感器、测功机和被测轴承组成。加速度计分别安装在电机驱动端DE和风扇端FE的壳体上同时记录基座BA的振动信号。核心被测轴承是 SKF 6205-2RS 深沟球轴承这个型号后面会反复出现在你的代码里先记住。每一次实验对应一个.mat文件里面通常包含三到四个通道的时域信号常见的是DE、FE、BA和转速信息。文件名的数字编号对应不同的故障类型、故障直径和工况组合——但具体映射关系不写在文件名里而是记录在数据中心的文档中。所以在写代码批量加载数据之前先花十分钟把官方文档里的编号说明整理成一个 CSV 索引表这个动作能让你后面少踩很多雷。2.2 最小加载示例用 Python 读取 .mat 文件并拆分训练样本采集工况方面驱动端加速度信号的采样频率通常有两种12kHz 和 48kHz后者专门用于外圈故障的细化分析。负载分为 0、1、2、3HP 四档对应电机转速大约 1797、1772、1750、1730 转/分注意 0HP 不是零转速是空载。故障损伤直径有 0.007、0.014、0.021 英寸三档外圈故障还细分了故障点在负载区6点钟方向、正交区3点钟方向和对侧区12点钟方向三种安装位置。from scipy.io import loadmat import numpy as np import pandas as pd # 读取单个文件注意mat文件里是结构体需要逐层解包 mat loadmat(data/CWRU/0HP/97.mat) print(mat.keys()) # 查看文件里有哪些键常见的有DE,FE,BA,RPM # 提取驱动端加速度信号 de_signal np.ravel(mat[DE]) # (int32) 避免维度问题 fs 12000 # 12kHz采样率 duration len(de_signal) / fs print(f信号时长: {duration:.2f}s, 样本点数: {len(de_signal)}) # 按固定窗长切分样本每个样本代表一次“观测” window_len 2048 # 约0.17s覆盖至少4个转频周期 stride 512 # 相邻样本重叠增强数据多样性 samples [] for start in range(0, len(de_signal) - window_len, stride): samples.append(de_signal[start:start window_len]) samples np.array(samples) print(f切分得到 {len(samples)} 个样本)这段代码的逻辑是把一整段长信号按固定长度切窗每个窗口作为一个独立样本。这里window_len取 2048 个点在 12kHz 采样率下约合 0.17 秒按转速 1797rpm 计算每个样本覆盖至少 5 个旋转周期这样样本内包含足够多的冲击事件时频分析才有意义。stride取 512 是让相邻窗有 75% 的重叠做训练集时能有效扩充样本量。如果你后面要用深度学习做分类样本量不够时首先调这个参数而不是去改采样率。2.3 文件索引表把实验编号翻译成可读标签我一般会建立一张简单的元数据表把文件名映射到(故障类型, 故障直径, 负载, 转速, 采样率)五个字段。这个表就是后续所有实验的“字典”无论是做分类还是做回归只需要从这个表里取配置和标签代码里的魔法数字会少很多。常见做法是在加载数据时逐个解析结构体的键把DE之外的字段也读出来特别是RPM因为在做阶比跟踪和频谱重采样时准确的转速值直接决定转频位置的精度。3. 三种时频分析方法的选型与参数STFT、连续小波与 Hilbert 包络谱3.1 短时傅里叶变换窗口长度决定了你看到的是“变频”还是“冲击”STFT 是进入时频分析最直接的一步原理是把信号分段乘窗后做 FFT得到的是一张以时间为横轴、频率为纵轴的热力图。在这张图上内圈故障的特征表现为以转频及其倍频为中心的边频带而且边频带在时间方向上呈现出周期性的能量波动。STFT 最关键的参数是窗函数、窗长和重叠率。import numpy as np from scipy.signal import stft import matplotlib.pyplot as plt fs 12000 f, t, Zxx stft( de_signal, fsfs, windowhann, nperseg256, # 窗长256点频率分辨率约46.9Hz noverlap128, # 50%重叠 detrendFalse, return_onesidedTrue ) # 只看0-2000Hz范围冲击能量通常集中在这里 mask f 2000 plt.pcolormesh(t, f[mask], np.abs(Zxx[mask]), shadinggouraud, vmin0, vmax0.5) plt.ylabel(Frequency [Hz]) plt.xlabel(Time [sec]) plt.title(STFT of DE signal (0HP, Inner Race Fault)) plt.colorbar(labelAmplitude)窗长nperseg的选择本质上是时间和频率分辨率之间的权衡窗越长频率分辨率越高但时间分辨率越差冲击的起止时刻会被抹平窗越短时间定位越准但频率上看到的是模糊的宽带。对 CWRU 这种转速相对稳定的数据256 点是比较常用的起点频谱分辨率约 46.9Hz足够分辨 1 倍频约 30Hz和 2 倍频但要看清 0.007 英寸早期故障的微弱边频带建议把窗长放大到 1024 点代价是你会损失冲击的时间定位精度。如果发现频谱是平的先检查noverlap是不是太小重叠率低于 50% 时图像会出现明显的网格状横向条纹。3.2 连续小波变换尺度参数才是决定成败的旋钮连续小波变换CWT在处理瞬态冲击方面比 STFT 自然得多因为它用可变尺度的母小波去“匹配”信号里的瞬态成分低频看趋势、高频看细节。对于轴承故障最常用的是复 Morlet 小波因为它同时提供幅值和相位信息而带宽参数和中心频率直接控制时频分辨率。import pywt scales np.logspace(np.log2(2), np.log2(64), 64, base2) coefs, freqs pywt.cwt(de_signal, scales, cmor1.5-1.0, sampling_period1/fs) # cmor1.5-1.0: 带宽参数1.5中心频率1.0Hz归一化 # 只看低频段 freq_limit 2000 mask freqs freq_limit plt.imshow(np.abs(coefs[mask]), aspectauto, extent[0, len(de_signal)/fs, freqs[mask][-1], freqs[mask][0]]) plt.colorbar(labelMagnitude) plt.ylabel(Frequency [Hz]) plt.xlabel(Time [sec]) plt.title(CWT scalogram (0HP, Outer Race Fault))常见的坑是用默认的scales等间隔取值这样低频段尺度稀疏高频段尺度密集图像会严重失真。我用np.logspace做对数均匀取尺度让每个倍频程内的尺度数量一致小波图才好看。cmor1.5-1.0中的 1.5 是带宽参数越大频率分辨率越高但时间分辨率越差中心频率 1.0 决定小波的振荡频率对冲击型信号这个值偏低会让尺度图中的冲击显得很宽偏高则容易看到伪振荡建议在 0.5 到 2.0 之间扫一遍再定。小波分析的一个隐藏价值是它能直接输出“尺度—频率”映射关系这在做变转速信号的阶比跟踪时非常有用。3.3 Hilbert 包络谱先把高频冲击解调出来再谈时频包络分析是轴承故障诊断里最经典的手法思路是用 Hilbert 变换构造解析信号取模得到包络对包络再做 FFT 从而在低频段观察故障特征频率。时频分析在这里的体现是你可以对包络信号再做 STFT观察特征频率的幅值随时间的变化这在变负载工况下特别有用。from scipy.signal import hilbert analytic_signal hilbert(de_signal) envelope np.abs(analytic_signal) # 对包络做FFT获取包络谱 env_fft np.fft.rfft(envelope - np.mean(envelope)) freqs_env np.fft.rfftfreq(len(envelope), 1/fs) # 内圈故障特征频率BPFI≈162.2Hz (0HP工况) plt.figure() plt.plot(freqs_env[:500], np.abs(env_fft[:500])) plt.axvline(162.2, colorr, linestyle--, labelBPFI) plt.xlabel(Frequency [Hz]) plt.ylabel(Envelope Amplitude) plt.legend() plt.title(Envelope Spectrum - Inner Race Fault)Hilbert 包络谱的精髓在于先包络再分析而不是直接看高频段。轴承故障的冲击会激起轴承座和结构的固有频率通常在 2kHz 以上直接看原始信号的频谱很难看到故障频率因为能量重心在高频区。通过包络解调冲击的信息被搬移到了低频段特征频率处会出现明显的峰值及其谐波。做这一步时要注意包络谱之前一定要去除直流分量否则 0Hz 的巨大能量会把后面的谱线全部压扁这就是np.mean那行代码的用途。另外如果峰值出现在特征频率的分数倍处先检查转速是否稳定转速波动会让特征频率展宽成一个频带。4. 把时频分析结果变成能训练的特征从热力图到谱熵的落地路径4.1 图像化特征时频图转灰度图喂给 CNN 的三个注意点用深度学习做故障诊断最常见的套路是把时频图当作图像输入卷积神经网络。这条路本身没问题但很多人直接拿pcolormesh导出的彩色图喂给模型结果颜色映射的差异导致同一故障在不同设备上表现出不同颜色分布模型的泛化性直接崩掉。常见做法是保存灰度图并且在保存时把每个样本的幅值归一化到 [0, 255]。import cv2 def scalogram_to_grayscale(coefs): # 取模并做对数压缩让微弱冲击可见 magnitude np.abs(coefs) log_mag 10 * np.log10(magnitude 1e-12) # 归一化到0-255并转uint8 norm (log_mag - log_mag.min()) / (log_mag.max() - log_mag.min() 1e-8) gray (norm * 255).astype(np.uint8) return cv2.resize(gray, (224, 224), interpolationcv2.INTER_LINEAR)对数压缩这一步非常关键。原始小波系数的幅值跨度可以达到几个数量级直接线性归一化会让小冲击的特征消失在量化噪声里。取 10 倍以 10 为底的对数后动态范围被压缩到合理的尺度微弱故障才能显现。另外所有样本必须统一时频图的尺寸我用cv2.resize拉到 224×224 是方便直接套用 ImageNet 预训练模型的结构。尺寸改变会让频率轴和时间轴的物理含义发生畸变因为缩放是纯像素级的、不感知物理频率所以如果模型在测试集上表现忽高忽低先查这一步是不是用的固定尺寸。4.2 数值化特征谱熵与频带能量比图像化特征适合深度学习但如果你用的是传统机器学习模型随机森林、XGBoost需要的是低维数值特征。谱熵是衡量频率分布“混乱程度”的指标轴承从健康到故障振动能量会从分散的广谱逐渐集中到特征频率及其谐波上所以谱熵通常会下降。频带能量比则是把频谱划分为若干个频带计算每个频带能量占总能量的比例故障发生时高频带的能量占比显著上升。from scipy.stats import entropy def spectral_features(signal, fs, bands[(0,1000),(1000,2000),(2000,4000),(4000,8000)]): fft_vals np.abs(np.fft.rfft(signal)) freqs np.fft.rfftfreq(len(signal), 1/fs) power fft_vals ** 2 total_energy np.sum(power) 1e-12 # 谱熵 prob power / total_energy spec_entropy entropy(prob) # 频带能量比 band_ratios [] for lower, upper in bands: mask (freqs lower) (freqs upper) band_energy np.sum(power[mask]) band_ratios.append(band_energy / total_energy) return np.array([spec_entropy] band_ratios) # 生成一个样本的特征向量 feat spectral_features(de_signal[:2048], fs) print(feat)频带划分的边界要参考实际信号中结构固有频率的位置来调整。CWRU 数据的驱动端结构固有频率大约在 3kHz 附近所以我在 2000-4000Hz 这个频带单独切了一段这段能量对冲击最为敏感。如果换数据集比如把这套流程迁移到帕德劳恩轴承故障数据集上采样率变成 64kHz结构固有频率位置完全不同频带边界必须重新用频谱图看一眼再定不能照抄 CWRU 的经验值。这些特征组合起来虽然维数很低但在故障分类任务上常常能达到 95% 以上的准确率优点是训练极快、可解释性强。4.3 数据划分先切时间窗再随机打乱无论你用图像还是数值特征数据划分的方式直接决定你的结果可不可信。正确做法是在每个.mat文件的完整时间序列上先切窗然后把所有窗打乱分配训练集和测试集。但很多人的做法是先打乱全部原始信号点再切窗这就等于把同一个时间窗里的点拆到了训练集和测试集两边导致信息泄漏——模型在测试集上的准确率高得离谱到了现场数据上一败涂地。记住一句话切窗的单位永远是“物理时间段”不是“单个信号点”。5. 时频分析翻车排查这 5 个坑我基本每次都踩5.1 0HP 工况的转速值被误当成 0现象用 0HP 数据做频谱分析0Hz 附近出现巨大能量特征频率完全被淹没。原因0HP 是电机空载工况转速约 1797rpm不是停机状态。有人把 0HP 理解成电机不转直接对整段数据做 FFT频谱上低频部分堆积了大量转频和结构低频成分。解决做任何频谱分析之前先查看.mat文件里的RPM字段或者用转速表单独确认。特征频率计算时按 1797rpm 折算转频约 29.95HzBPFO、BPFI 在这个转速基础上计算。5.2 外圈故障的故障点位置混用现象外圈故障数据在相同条件下训练准确率只有 70%而同参数的内圈故障准确率 98%。原因外圈故障点设置在负载区6点钟方向、正交区3点钟方向和对侧区12点钟方向三种位置。负载区故障点直接承受滚珠冲击特征最明显对侧区的冲击需要经过轴承座的传导衰减信号能量弱很多。把三种位置的样本混在一起当同一类做分类模型会学得“稀里糊涂”。解决如果做三分类滚动体/内圈/外圈外圈故障只取负载区样本如果做细粒度分类把三个位置单独作为三个类别。我一般建议后者因为现场外圈故障的方位信息本身就是有价值的诊断线索。5.3 STFT 窗长不同得到矛盾结论现象同一段内圈故障信号用 256 点窗做 STFT 能看到明显的边频带换 1024 点窗后边频带消失频谱变成一片平滑。原因窗长增加后频率分辨率提高但时间分辨率下降内圈故障的冲击间隔约 10.3ms窗长 1024 点约 85ms一个窗内包含约 8 个冲击多个冲击相互叠加反而在频率上形成了梳状谱把原本清晰的边频带“抹匀”了。解决在分析冲击型故障时先用包络谱定位特征频率再用 STFT 验证时变特性。STFT 的窗长应当小于故障冲击间隔的一半这样每个窗内至多包含一个冲击事件时频图才有物理意义。具体就是算好故障特征频率后用freq倒推最大窗长。5.4 CWT 尺度范围取错导致图像全是噪点现象小波时频图上全是密密麻麻的亮斑看不到任何周期性结构。原因scales取值范围太大高频段尺度取值过密而低频段过疏或者cmor带宽参数设得太大小波的频率响应过宽把噪声一并放大。解决先粗略估计信号的主要频段一般 0-5000Hz再按对数均匀取 30-60 个尺度。cmor的带宽参数取 1.0-1.5中心频率取 1.0这两个值是绝大多数工程案例的默认起点。调参时看一行水平的切片确认小波频率响应落在目标频段内。5.5 训练集随机打乱后准确率虚高到 99%现象模型在测试集上准确率 99%但拿到现场数据只有 60% 不到。原因数据划分时先对整个时间序列做了点级打乱再切窗或者用train_test_split时没有设置shuffleFalse加上分组约束导致同一个时间窗的数据部分进了训练集、部分进了测试集模型实际上记住了时间窗内的噪声模式而不是故障的本质特征。解决按时间顺序切窗后以“文件”为单位做分组划分同一个.mat文件的全部窗只能出现在训练集或测试集不能两边都出现。用sklearn.model_selection.GroupShuffleSplit可以比较干净地实现。6. 进阶用帕德劳恩数据集检验你的时频分析流程是否真的可靠当你在 CWRU 数据集上把一套时频分析流程跑通后最容易产生的错觉是“我的方法已经成熟了”。但要验证流程的泛化性最直接的做法是拿德国帕德劳恩Paderborn轴承故障数据集做一次迁移测试。帕德劳恩数据集的独特之处在于它同时包含人工植入故障和加速寿命试验自然产生的故障后者在真实工况下更常见信号特征更微弱也更复杂。另外它的采样率是 64kHz转速范围在 900-1500rpm 之间和 CWRU 的 12kHz、1797rpm 有显著差异同样一套时频分析参数直接套用大概率会出问题。我一般会把第 4 章的特征提取流程做成一个配置化的函数参数包括采样率、频带边界、STFT 窗长、小波尺度范围。迁移到帕德劳恩数据时先对一段健康信号和一段故障信号分别做频谱图确定新的频带边界和特征频率范围再运行同样的特征提取脚本。如果谱熵和频带能量比的特征分布能够在两类数据上呈现出类似的区分度那说明这套时频分析流程不只是在对 CWRU 的“背答案”。如果分布发生严重重叠优先检查是不是转速变化导致特征频率漂移出了原本的频带然后再检查是否需要对信号做阶比重采样来消除转速波动的影响。这里还值得多提一个在诊断现场很有用的做法的变体——只计算频带能量比中的高频频带和低频频带之比用这个比值画一条随时间的趋势线。帕德劳恩数据里自然磨损的轴承在故障恶化过程中这个比值会呈现明显的上升趋势这比训练一个分类器更能给现场工程师提供直观的判据。说实话我在实际项目里很少只用单一模型做诊断大多数时候是先用时频分析看趋势、定位异常时间段再针对性地做精细化分析这套工作流里时频分析的参数选择才是决定诊断质量的核心。如果你准备认真做这个方向我建议你保留实验中的每一次参数调整记录。我自己就曾经因为没记录某次 STFT 窗长参数导致花了整整两天时间重新调参那种感觉就是明明做出来的图像比之前好看却再也复现不了当时的结果。把窗长、重叠率、尺度范围、频带边界这些参数做成配置文件并纳入版本管理是比任何花哨算法都重要的习惯。希望这个流程能帮你在自己的项目里少走一段弯路。本文还有配套的精品资源点击获取