1. 从零开始:认识CWRU数据集与轴承故障诊断
如果你是第一次接触工业设备的故障诊断,可能会觉得这玩意儿特别“玄学”——老师傅听听声音、摸摸温度就能判断机器好坏,但轮到我们这些搞技术的,总得有点数据支撑才踏实。轴承故障诊断,就是这么一个典型的领域。而CWRU数据集,可以说是这个领域的“MNIST”,是每个想入门或深耕设备预测性维护的工程师和研究者都绕不开的宝藏。
简单来说,CWRU数据集就是美国凯斯西储大学(Case Western Reserve University)通过一个精心设计的实验台,采集了大量轴承在各种“健康状况”下的振动信号。这个实验台模拟了真实电机的工作场景,然后研究人员“狠心”地在轴承上人为制造了各种“伤口”——也就是故障,比如在内圈、外圈或者滚动体上用电火花加工出不同大小的小坑。接着,用高精度的加速度计,像听诊器一样,贴在电机外壳上,记录下轴承在不同负载下运转时产生的振动声音。这些声音数据,就是我们进行故障诊断的原始材料。
我刚开始接触这个数据集时,觉得它最大的价值在于“真实”和“系统”。它不像一些仿真数据那么完美,包含了真实的噪声和工况变化;同时,它的故障类型、尺寸、负载条件都被严格标定,形成了一个多维度的、可控的“故障字典”。这意味着,你可以用它来验证你的算法:不仅能判断“轴承有没有病”,还能进一步诊断“病在哪里(内圈还是外圈)”、“病得多重(故障尺寸多大)”,甚至考虑“病人在什么状态下发病的(负载条件)”。对于想从理论走向实践的我们来说,这简直是一个完美的沙盒。
那么,这个数据集具体能帮你做什么呢?如果你是学生或研究者,可以用它来复现经典论文,验证新的特征提取方法或深度学习模型。如果你是在工厂里搞设备运维的工程师,通过学习如何分析这个数据集,你能掌握从振动信号中揪出早期故障征兆的核心技能,这对于避免非计划停机、实现预测性维护至关重要。接下来,我就带你一步步拆解这个数据集,并手把手展示如何从一堆看似杂乱无章的振动波形中,提取出有价值的故障特征,最终实现自动诊断。
2. 深入拆解:CWRU数据集的结构与内涵
拿到一个数据集,最忌讳的就是一头扎进代码里。先花点时间把它的“家底”摸清楚,后续处理数据时才能心中有数,避免很多低级错误。CWRU数据集的结构,可以用“一个核心,四个维度”来概括。
一个核心,就是振动信号的时间序列。数据以.mat文件格式存储,这是MATLAB的默认格式,用Python的scipy.io库可以轻松读取。每个文件里通常包含一个或多个通道的振动数据,采样频率常见的有12 kHz和48 kHz两种。比如12 kHz采样率,意味着一秒钟采集12000个数据点,足以捕捉轴承旋转引发的高频振动成分。原始信号就是一连串随着时间变化的加速度值,单位通常是重力加速度g。
四个维度,则构成了数据集的丰富标签体系,这也是它比许多简单数据集更强大的地方:
第一个维度:健康状态。这是最基础的分类。
- 正常状态:轴承完好无损时的振动信号。这是我们的“基线”,所有故障信号都要与之对比。正常信号通常振幅较小,有一定的随机噪声,但周期性成分不明显。
- 故障状态:这是我们的重点研究对象。又细分为三种:
- 内圈故障:故障点位于轴承的内圈上。内圈是安装在旋转轴上的,所以故障点会随着轴一起转动。
- 外圈故障:故障点位于轴承的外圈上。外圈通常是固定在轴承座里的,所以故障点的位置相对电机壳体是静止的。
- 滚动体故障:故障点位于滚珠或滚子上。滚动体在内外圈之间既公转又自转,运动轨迹更复杂。
第二个维度:故障尺寸。他们用故障坑的直径来表示严重程度,共有四档:0.007英寸、0.014英寸、0.021英寸和0.028英寸。你可以理解为“小伤”、“中伤”、“重伤”和“危重”。故障尺寸越大,产生的冲击能量越强,在振动信号中通常表现为更明显的周期性冲击脉冲。这个维度对于评估故障的严重性、预测剩余使用寿命非常关键。
第三个维度:故障位置(特指外圈故障)。这是CWRU数据集一个非常细致的设定。对于外圈故障,他们还记录了故障点相对于负载区域的位置,分为3点钟、6点钟和12点钟方向。
- 3点钟方向:位于负载区域中心。轴承转动时,这个位置承受的力最大,故障冲击信号最强烈。
- 6点钟方向:位于负载区域边缘。冲击强度次之。
- 12点钟方向:位于非负载区域。这里的故障信号可能最弱,也最难检测。 这个设定模拟了现实中故障可能发生在轴承圈上任何位置的情况,极大地考验了诊断算法对信号强弱变化的鲁棒性。
第四个维度:负载条件。这指的是数据采集时电机输出的功率(马力),分为0马力(空载)、1马力、2马力和3马力四档。负载不同,电机的转速会略有变化,轴承承受的力也不同,这会直接改变振动信号的“背景环境”。我实测下来发现,负载增大时,整体振动幅度通常会升高,但故障特征频率可能会被更强的背景噪声或非线性振动所调制,有时反而增加了识别难度。忽略负载条件训练的模型,换到另一个负载工况下,性能可能会严重下降。
为了更直观地理解这四个维度如何交织在一起,我们可以看下面这个简化的数据索引表:
| 文件名示例 (部分) | 健康状态 | 故障类型 | 故障尺寸 (英寸) | 负载 (马力) | 备注 |
|---|---|---|---|---|---|
97.mat | 正常 | 无 | 无 | 0 | 基线数据 |
105.mat | 故障 | 内圈 | 0.007 | 0 | 早期轻微故障 |
130.mat | 故障 | 外圈 (6点) | 0.021 | 2 | 负载下的大尺寸外圈故障 |
209.mat | 故障 | 滚动体 | 0.014 | 3 | 重载下的滚动体故障 |
理解了这个结构,你就知道在调用数据时,你需要根据文件名或配套的文档来定位你需要的“实验条件组合”。很多开源代码里会提供一个映射字典,把文件名和这些标签对应起来,这是使用数据集的第一步。
3. 实战第一步:数据获取与预处理
理论懂了,手就痒了。咱们直接进入实战环节。第一步,就是把数据弄到本地,并把它处理成算法能“舒服”消化的样子。
3.1 数据获取与读取
CWRU数据集是公开的,你可以从他们的官网或一些开源仓库(比如GitHub上的一些镜像)下载。我通常喜欢用Python的pooch或wget库写个小脚本自动下载,避免手动点来点去。数据下载后是一堆.mat文件,命名可能像97.mat, 105.mat这样。
用Python读取非常简单:
import numpy as np
import scipy.io as sio
# 读取一个.mat文件
file_path = ‘path/to/your/105.mat‘
data = sio.loadmat(file_path)
# 查看文件中有哪些变量
print(data.keys())
# 通常振动数据在 ‘X‘ 或 ‘DE‘ (驱动端) 这样的键下
# 例如,CWRU数据常用 ‘DE‘ 代表驱动端加速度信号
vibration_signal = data[‘DE‘].flatten() # 将其展平为一维数组
fs = 12000 # 采样频率,根据实际数据设置,可能是12000或48000
print(f“信号长度:{len(vibration_signal)}, 采样率:{fs} Hz“)
读出来的vibration_signal就是一长串数字,这就是原始的时域波形。你可以先简单画图看一眼:
import matplotlib.pyplot as plt
time = np.arange(len(vibration_signal)) / fs
plt.figure(figsize=(12, 4))
plt.plot(time, vibration_signal)
plt.xlabel(‘时间 (秒)‘)
plt.ylabel(‘加速度‘)
plt.title(‘原始振动信号时域图‘)
plt.grid(True)
plt.show()
这时你看到的可能是一条上下波动、看起来没什么规律的曲线。别急,玄机都藏在里面。
3.2 数据预处理三板斧
原始信号不能直接喂给模型,我们需要进行清洗和标准化。
第一斧:去趋势与去直流分量。传感器采集的信号可能包含缓慢变化的趋势项或固定的直流偏移,这不利于后续分析。一个简单有效的方法是用信号减去其均值或进行一阶差分。
# 去除直流分量(减去均值)
signal_detrended = vibration_signal - np.mean(vibration_signal)
第二斧:滤波降噪。现场环境复杂,信号里混有大量高频噪声。对于轴承故障诊断,我们更关心的是与轴承几何尺寸和转速相关的特定频率成分(通常在中高频段)。一个常用的方法是使用带通滤波器,保留我们感兴趣的频带。比如,我们可以设计一个高通滤波器滤除低频的结构振动,再用一个低通滤波器滤除无关的高频电子噪声。
from scipy import signal
# 设计一个巴特沃斯带通滤波器,例如保留 100 Hz 到 2000 Hz 的成分
lowcut = 100.0
highcut = 2000.0
nyquist = 0.5 * fs
low = lowcut / nyquist
high = highcut / nyquist
b, a = signal.butter(4, [low, high], btype=‘band‘)
signal_filtered = signal.filtfilt(b, a, signal_detrended) # 使用filtfilt实现零相位滤波
注意:滤波器的参数需要根据你关心的故障特征频率范围来调整,这需要对轴承的故障特征频率有所了解,我们下一章会讲。
第三斧:数据切片与标准化。一条数据可能很长(几十万点),我们通常将其切分成一个个固定长度的样本(比如每个样本4096个点),用于训练或测试。切分后,要对每个样本进行标准化,使其均值为0,标准差为1。这一步对于很多机器学习模型(特别是深度学习模型)的稳定收敛至关重要。
def segment_signal(signal, window_size, step_size):
"""将长信号切分为重叠的窗口"""
segments = []
for start in range(0, len(signal) - window_size + 1, step_size):
segment = signal[start:start + window_size]
segments.append(segment)
return np.array(segments)
window_size = 2048 # 每个样本的长度
step_size = 512 # 滑动步长,可以重叠以增加数据量
segments = segment_signal(signal_filtered, window_size, step_size)
# 对每个样本进行标准化
segments_normalized = (segments - np.mean(segments, axis=1, keepdims=True)) / (np.std(segments, axis=1, keepdims=True) + 1e-8)
预处理完成后,我们就得到了一批干净、规整的样本数据,可以送入特征提取或模型训练的流水线了。
4. 特征提取:从波形中“听”出故障的密码
预处理后的信号,对我们人眼来说依然像天书。特征提取,就是翻译这道天书的过程,把一长串波形数据,提炼成几个能代表其本质的数学指标。这是故障诊断中最核心、也最体现经验的部分。我把它分为时域特征、频域特征和时频域特征三大类。
4.1 时域特征:最直观的统计量
时域特征直接从信号的振幅随时间变化中计算。它们计算简单,物理意义明确,对于明显的冲击类故障有时很有效。
- 有量纲指标:比如均方根值,它反映了振动的平均能量,故障严重时通常会增大。峰值和峰峰值,能捕捉到瞬时的剧烈冲击。
- 无量纲指标:这类指标对负载、转速变化不敏感,更稳定。比如峭度,它描述信号分布尖锐还是平坦。健康的轴承振动信号近似高斯分布,峭度接近3。一旦出现早期故障,会产生瞬态冲击,信号分布出现“尖峰厚尾”,峭度值会显著增大,对早期故障非常敏感,是我必看的指标之一。还有波形因子、脉冲因子、裕度因子等,它们从不同角度刻画波形的形状。
计算示例:
def extract_time_features(segment):
"""提取一个信号片段的时域特征"""
rms = np.sqrt(np.mean(segment**2)) # 均方根值
peak = np.max(np.abs(segment)) # 峰值
kurtosis = np.mean((segment - np.mean(segment))**4) / (np.std(segment)**4) # 峭度
skewness = np.mean((segment - np.mean(segment))**3) / (np.std(segment)**3) # 偏度
return [rms, peak, kurtosis, skewness]
# 对每个样本计算特征
time_features = np.array([extract_time_features(seg) for seg in segments_normalized])
4.2 频域特征:找到故障的“主旋律”
时域特征看整体,频域特征则像做“频谱分析”,把信号分解成不同频率的正弦波,看看能量集中在哪些频率上。轴承发生故障时,会产生周期性的冲击,这些冲击会激发系统的固有频率,并在频谱上形成特定的故障特征频率及其倍频(谐波)。
计算频域特征,首先要进行快速傅里叶变换:
from scipy.fft import fft
def extract_freq_features(segment, fs):
"""提取频域特征"""
n = len(segment)
fft_vals = np.abs(fft(segment))[:n//2] # 取单边谱
freqs = np.linspace(0, fs/2, n//2)
# 1. 重心频率:频谱能量分布的中心
fc = np.sum(freqs * fft_vals) / np.sum(fft_vals)
# 2. 均方频率:反映频率分布的方差
msf = np.sum((freqs**2) * fft_vals) / np.sum(fft_vals)
# 3. 频率方差
vf = np.sum(((freqs - fc)**2) * fft_vals) / np.sum(fft_vals)
# 更重要的是,找到故障特征频率对应的幅值
# 假设我们已经计算出了轴承的故障特征频率(如BPFI, BPFO, BSF, FTF)
# bpfi_amp = np.max(fft_vals[np.where(np.isclose(freqs, bpfi_freq, atol=1))])
return [fc, msf, vf] # 返回基础特征,实际中会加入特征频率幅值
这里的关键是故障特征频率的计算。它取决于轴承的几何参数(滚珠数、直径等)和轴的转速。公式网上有很多,比如内圈故障频率、外圈故障频率等。你需要根据CWRU数据集中电机的实际转速(不同负载下略有不同,1797, 1772, 1750, 1730 RPM)来计算。然后,在频谱图上观察这些频率点及其谐波处是否有明显的峰值,这是诊断故障类型的直接证据。
4.3 时频域特征:捕捉动态变化
轴承故障的冲击信号可能是非平稳的,特别是变转速工况下。这时,单纯的频域分析就力不从心了。时频分析能同时看到频率成分随时间的变化,就像音乐的“钢琴卷帘谱”。最常用的方法是短时傅里叶变换和小波变换。
以小波变换为例,它能用不同尺度的“小波”去匹配信号的不同成分,特别适合提取瞬态冲击特征。我们可以计算小波系数矩阵的能量或熵作为特征。
import pywt
def extract_wavelet_features(segment, wavelet=‘db4‘, level=3):
"""提取小波包能量特征"""
# 进行小波包分解
wp = pywt.WaveletPacket(data=segment, wavelet=wavelet, mode=‘symmetric‘, maxlevel=level)
# 获取最后一层所有节点的能量
nodes = [node.path for node in wp.get_level(level, ‘natural‘)]
energies = []
for node_path in nodes:
node = wp[node_path]
energies.append(np.sum(node.data**2))
# 将能量归一化,形成能量分布向量
total_energy = np.sum(energies)
energy_features = [e / total_energy for e in energies]
return energy_features
小波特征向量能很好地表征信号的局部细节,对早期微弱故障的检测能力很强,但维度较高。在实际项目中,我通常会混合使用时域、频域和时频域特征,形成一个高维特征向量,然后利用特征选择方法(如基于树模型的重要性排序、递归特征消除等)来筛选出最有效的特征子集,避免“维度灾难”。
5. 构建诊断模型:从特征到决策
特征准备好了,就像厨师备好了菜,接下来就是“炒菜”——构建诊断模型。根据你的目标(是二分类“健康/故障”,还是多分类“故障类型”,甚至是回归“故障尺寸”),可以选择不同的“菜谱”。
5.1 传统机器学习模型
如果你的特征提取做得足够好,传统机器学习模型往往能取得又快又好的效果,而且可解释性强。
- 支持向量机:在小样本、高维特征上表现优异。对于像CWRU这样类别清晰的数据,用RBF核的SVC通常效果就不错。关键是要做好特征的标准化和超参数(如C和gamma)的调优。
- 随机森林:这是我非常喜欢用的一个模型,因为它能天然地评估特征重要性,对异常值和过拟合不太敏感,而且训练速度快。你可以轻松地看到是峭度、RMS还是某个频带能量对分类贡献最大。
- 梯度提升树:如XGBoost、LightGBM,性能通常比随机森林更强,但需要更多的调参技巧。
一个基于随机森林的简单示例:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix
# 假设 X 是所有样本的特征矩阵,y 是对应的标签(0:正常,1:内圈,2:外圈...)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
# 创建并训练随机森林模型
rf_clf = RandomForestClassifier(n_estimators=100, max_depth=10, random_state=42)
rf_clf.fit(X_train, y_train)
# 预测与评估
y_pred = rf_clf.predict(X_test)
print(classification_report(y_test, y_pred))
print(“特征重要性:“, rf_clf.feature_importances_)
5.2 深度学习模型:端到端的魅力
深度学习模型,特别是卷积神经网络和一维CNN,可以直接吃进原始的振动信号或简单的频谱图,自动学习特征,实现“端到端”诊断。这省去了复杂的人工特征工程,对于海量数据或复杂故障模式很有优势。
对于CWRU数据,一个经典的做法是将一维振动信号通过STFT转换成二维的时频谱图,然后当作图像输入到2D CNN中。或者,更直接地,使用1D CNN处理一维信号序列。
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
def build_1d_cnn(input_shape, num_classes):
model = keras.Sequential([
layers.Input(shape=input_shape),
layers.Conv1D(filters=64, kernel_size=3, activation=‘relu‘, padding=‘same‘),
layers.BatchNormalization(),
layers.MaxPooling1D(pool_size=2),
layers.Conv1D(filters=128, kernel_size=3, activation=‘relu‘, padding=‘same‘),
layers.BatchNormalization(),
layers.GlobalAveragePooling1D(), # 全局池化替代Flatten+Dense,参数更少
layers.Dense(128, activation=‘relu‘),
layers.Dropout(0.3),
layers.Dense(num_classes, activation=‘softmax‘)
])
model.compile(optimizer=‘adam‘,
loss=‘sparse_categorical_crossentropy‘,
metrics=[‘accuracy‘])
return model
# 假设 segments_normalized 是形状为 (样本数, 2048) 的输入
input_shape = (2048, 1) # 需要增加一个通道维度
X_cnn = segments_normalized[..., np.newaxis] # 增加通道维度
y = ... # 标签
# 划分数据集,训练模型
model = build_1d_cnn(input_shape, num_classes=len(np.unique(y)))
history = model.fit(X_train, y_train, validation_split=0.2, epochs=50, batch_size=32, verbose=1)
使用深度学习模型时,数据量是关键。CWRU数据集样本数量有限,需要充分利用数据增强技术,比如对信号片段进行随机缩放、平移、添加噪声等,来扩充训练集,防止模型过拟合。
5.3 模型评估与选择
无论用哪种模型,都不能只看训练集上的准确率。一定要用独立的测试集来评估泛化能力。对于CWRU数据集,我建议按负载条件来划分训练集和测试集。例如,用0、1、2马力的数据训练,用3马力的数据测试。这样可以检验模型在未见过的工况下的表现,更符合实际应用场景。
评估指标除了准确率,还要看精确率、召回率、F1-score,特别是对于类别不平衡的数据(正常样本可能远多于某种特定故障样本)。混淆矩阵能帮你清晰看到模型具体在哪些类别上容易混淆,比如是不是总把内圈故障和外圈故障搞混。
在我自己的实验中,对于CWRU这种中等规模、标签清晰的数据集,精心设计特征+随机森林/XGBoost的组合,与1D CNN模型相比,在准确率上往往相差无几,有时传统方法甚至更优,而且训练和推理速度快,解释性好。深度学习的优势在于,当你没有足够深的领域知识来设计特征时,它提供了一个强大的自动特征学习工具。我的建议是,先从传统方法做起,建立一个性能基线,再尝试深度学习模型,对比两者的效果、复杂度和可解释性,选择最适合你当前需求的方案。
946




被折叠的 条评论
为什么被折叠?



