简介:基于深度学习的1DCNN轴承故障诊断源码包,面向机械故障诊断、工业预测性维护领域的工程师与研究人员,提供从振动信号预处理、1DCNN模型构建、训练优化到故障分类的完整实现方案。资源共50个文件,包体仅3.64MB,以Python源码(.py)为主,涵盖数据读取与预处理、网络结构定义、训练测试以及t-SNE特征可视化;另有17张PNG结果图、配置XML、说明TXT和混淆矩阵Excel表格,便于对照实验结果与评估指标。源码基于TensorFlow/Keras编写,支持CWRU数据集,通过滑动窗口采样、归一化等操作对原始振动信号进行预处理,可识别滚动体缺陷、内圈故障、外圈故障等典型模式;同时提供多种优化器、损失函数与评估指标配置,并包含模型保存与加载功能。目录包含data、models、utils等模块,结构清晰,适合入门学习和在此基础上进行二次开发。已有424人学习下载,内容覆盖从数据准备到结果分析的完整链路,是快速掌握深度学习驱动轴承故障诊断实践流程的实用资料。
1. 基于深度学习1DCNN轴承故障诊断:为什么直接把振动信号送进卷积网络
遇到轴承故障诊断,很多人的第一反应是FFT频谱、小波包分解或者时频图,先做特征工程再训练分类器。但在公开的CWRU轴承数据集上跑过几组实验后会发现,这些预处理环节引入的参数比网络本身还多:窗函数选什么、频带切多宽、小波基用哪个,每换一个数据集就得重新调一轮。这套基于深度学习1DCNN轴承故障诊断源码走的是另一条路:把传感器采集到的原始振动加速度信号直接当作一维时间序列,通过一维卷积网络自动提取冲击特征并完成故障分类。它解决的是预测性维护里最核心的问题——只有振动数据时,如何区分正常、内圈故障、外圈故障和滚动体故障。适合正在复现论文、做设备健康管理平台,或者在边缘设备上跑推理的工程师阅读,从数据切分到网络落地都有可直接复用的代码。
2. 数据准备:CWRU数据加载、滑窗切分与防泄漏划分
2.1 CWRU数据集的类别定义与文件组织
CWRU是轴承故障诊断领域使用最广泛的公开数据集,用SKF6205轴承在驱动端做故障注入采集,采样率常用12kHz。故障由电火花加工产生,分为内圈IR、外圈OR、滚动体B三种位置,每种位置有0.007、0.014、0.021英寸三个损伤尺寸,加上正常状态,一共构成10个分类。这是绝大多数1DCNN复现实验采用的类别口径,源码里也建议按10类做。实验台还带0到3马力四档负载,对应转速从1797r/min降为1730r/min,这个转速信息在后面验证跨工况泛化时很有用。
数据以mat格式存储,文件名中包含了故障位置和尺寸信息,常见的读取方式是通过字段名过滤出驱动端振动信号。下面这个函数把mat文件读成一维数组并做基本的均值方差标准化:
import numpy as np from scipy.io import loadmat def load_cwru_signal(mat_path, sensor="DE"): mat = loadmat(mat_path) key = [k for k in mat.keys() if k.endswith(f"_{sensor}_time")][0] sig = mat[key].flatten().astype(np.float32) # 按整个文件做z-score,消除不同实验间的基线漂移 sig = (sig - sig.mean()) / (sig.std() + 1e-8) return sig这里用endswith(f"_{sensor}_time")匹配字段名,而不是写死完整字段名,是因为大多数CWRU版本的mat文件字段形如X097_DE_time,但不同下载源头存在小差异。按归一化处理说明:对整个连续信号计算均值和标准差,相当于把每个实验的直流分量和幅值差异消掉。如果后续按滑动窗口切分且没有重叠,这个操作不会引入标签泄漏;但如果做的是窗口内归一化,则下面一节还要再处理。
2.2 滑窗切分:窗口长度与重叠率怎么定
连续振动信号不能整段塞进网络,需要滑窗切成固定长度样本。窗口长度直接影响模型能观察到的物理范围。12kHz采样率下,主轴转一圈大约400个采样点,窗口取2048相当于连续观察约5个旋转周期,足以覆盖一次故障冲击从发生到衰减的完整序列。窗口过短抓不全周期冲击,过长则单个样本包含太多转速波动和无关噪声,同时样本数量变少。所以2048是这个任务里比较稳妥的起点。
重叠率解决的是样本量问题。0%重叠时每个文件只能切出几十个样本,10类数据全部算上也不过几千条,训练一个带BatchNorm的CNN显得捉襟见肘。50%重叠可以把样本量提升近一倍,同时相邻窗口的相关性还处在可控范围内;75%重叠会带来严重的样本冗余,训练速度变慢且测试集容易出现虚高。以下函数实现滑窗切分,返回一个二维数组,每一行是一个窗口:
def sliding_window(sig, win_len=2048, overlap=0.5): step = int(win_len * (1 - overlap)) if len(sig) < win_len: return np.empty((0, win_len), dtype=np.float32) starts = np.arange(0, len(sig) - win_len + 1, step) return np.stack([sig[s:s + win_len] for s in starts])参数说明:win_len控制网络输入的序列长度,overlap=0.5表示相邻窗口重叠一半,实际步长为1024个采样点。切分完成后,一个长度约50万点的原始文件可以产出约490个窗口,10类数据全量处理后在2万到4万样本量级,训练一个1DCNN模型在普通GPU上几分钟就能跑完一个epoch。切分时注意保留每个样本对应的原始文件ID,后续划分训练集和测试集时要用。
2.3 归一化与防数据泄漏的数据集划分
这一步是决定最终准确率是否可信的关键。两种常见做法有本质区别:按窗口做归一化,每个样本减去自身均值再除以自身标准差;按全局统计归一化,用整个文件或整个训练集的均值和标准差统一处理。推荐前者,因为现场采集的振动信号经常存在基线漂移和幅值波动,窗口内归一化可以消除这些设备相关变量,让模型更关注冲击波形本身。
def normalize_window(win): return (win - win.mean()) / (win.std() + 1e-8)直接调用train_test_split随机打乱所有窗口是一个隐蔽但高频出现的数据泄漏问题。由于相邻窗口重叠且来自同一段连续记录,随机划分后训练集里某几个窗口和测试集里另几个窗口在时间上几乎重合,模型相当于“见过”测试数据的相邻片段,测试准确率会虚高到很有误导性。正确做法是按原始文件分组,保证同一段连续记录的全部窗口只出现在训练集或只出现在测试集中。一个更严格的验证方式是跨负载划分,用0、1、2马力数据训练,3马力数据测试,此时转速和载荷都变了,得到的准确率才是模型真实泛化能力的体现。源码里建议至少保留一组这样的划分结果。
3. 1DCNN网络结构设计:卷积核、批量归一化与参数推算
3.1 为什么一维卷积适合振动信号:从滤波器组到感受野
从频域视角看,一维卷积核本质上是一组可学习的带通滤波器,卷积过程就是在原始信号上滑动计算局部加权和。轴承故障的特征频率如内圈BPFI、外圈BPFO在时域表现为周期性冲击,这些冲击在卷积层看来就是局部波形的重复模式,符合卷积权值共享的特性。相比把信号转成时频图再用二维CNN处理,1DCNN省去了短时傅里叶变换的窗长、重叠和频带选择参数,端到端直接从原始信号学习,推理时也更适合部署在资源受限的采集设备上。
卷积核尺寸选择是1DCNN设计里最重要的决定。第一层卷积感受野太小,看到的只是几个采样点的瞬时幅值,无法捕捉一个完整的冲击波形;太大又增加参数和计算量。常见的做法是第一层用大卷积核配合大步长,快速压缩序列长度,后面几层换回小卷积核提炼局部细节。典型配置是第一层kernel为64、stride为2,后面几层kernel为5、stride为2。设置第一层kernel大小为64,对应约5.3毫秒的时间窗,覆盖的物理范围足够辨认一次冲击。
3.2 网络结构与PyTorch源码实现
下面是一份可以直接用的PyTorch实现,4层一维卷积加自适应池化,最后接两层全连接分类器。输入张量形状为(batch_size, 1, 2048),输出形状为(batch_size, 10)。
import torch import torch.nn as nn class Bearing1DCNN(nn.Module): def __init__(self, num_classes=10): super().__init__() self.features = nn.Sequential( nn.Conv1d(1, 32, kernel_size=64, stride=2, padding=32), nn.BatchNorm1d(32), nn.ReLU(inplace=True), nn.Conv1d(32, 64, kernel_size=5, stride=2, padding=2), nn.BatchNorm1d(64), nn.ReLU(inplace=True), nn.Conv1d(64, 128, kernel_size=5, stride=2, padding=2), nn.BatchNorm1d(128), nn.ReLU(inplace=True), nn.Conv1d(128, 128, kernel_size=5, stride=2, padding=2), nn.BatchNorm1d(128), nn.ReLU(inplace=True), nn.AdaptiveAvgPool1d(1), ) self.classifier = nn.Sequential( nn.Dropout(0.5), nn.Linear(128, 64), nn.ReLU(inplace=True), nn.Linear(64, num_classes), ) def forward(self, x): h = self.features(x).squeeze(-1) return self.classifier(h)3.2.1 参数量与输出维度推算
各层输出序列长度变化如下表,计算公式为L_out = floor((L_in + 2 * padding - kernel_size) / stride + 1)。
| 层 | 操作 | kernel | stride | 输入长度 | 输出长度 |
|---|---|---|---|---|---|
| conv1 | 1→32通道 | 64 | 2 | 2048 | 1025 |
| conv2 | 32→64通道 | 5 | 2 | 1025 | 513 |
| conv3 | 64→128通道 | 5 | 2 | 513 | 257 |
| conv4 | 128→128通道 | 5 | 2 | 257 | 129 |
| pool | AdaptiveAvgPool1d | - | - | 129 | 1 |
选第4层卷积输出为128维特征,是因为这个维度对10类故障已经足够表达,继续加宽到256收益有限且全连接层参数翻倍。全连接部分先压到64维再加Dropout,最后映射到10个类别,num_classes参数可以改成4做正常加三种故障的粗分,也可以改成6不区分故障尺寸,改的时候只需要换标签映射,网络主体不用动。
3.2.2 可选:给特征层加SE通道注意力模块
如果还想在准确率上再挤一点,可以在每层卷积和BN之后插入一个SE模块,用通道维度的注意力重新加权各个特征图。实现只有十行左右,参数增加很少,但对小样本故障特征有正向作用:
class SEBlock(nn.Module): def __init__(self, channels, reduction=16): super().__init__() self.pool = nn.AdaptiveAvgPool1d(1) self.fc = nn.Sequential( nn.Conv1d(channels, channels // reduction, 1), nn.ReLU(inplace=True), nn.Conv1d(channels // reduction, channels, 1), nn.Sigmoid(), ) def forward(self, x): return x * self.fc(self.pool(x))使用方式是在每个ReLU后插入x = se_block(x),通道数减半时SE的reduction注意不要整除出错,比如32通道时reduction=16会得到2个中间通道,仍然合法。加了这个模块后模型参数会增加约几千个,训练时间影响忽略不计,推荐在最终实验版本里保留。
4. 训练与调参:损失函数、优化器、数据增强与过拟合防治
4.1 损失函数、优化器与学习率策略
10分类问题直接使用nn.CrossEntropyLoss(),它内部已经把LogSoftmax和NLLLoss合并,所以模型最后一层不需要手动加Softmax。训练时如果某个类别样本特别少,比如现场只采集到正常数据和内圈故障数据,可以给CrossEntropyLoss传weight参数,权重与样本数成反比,避免模型把所有样本都判成多数类。
优化器选Adam,学习率初始化为0.001,设置weight_decay=1e-4做L2正则。对这个小规模分类任务,Adam比SGD收敛快,对学习率的敏感度低,更适合快速复现和调参。训练后期常见的做法是切换学习率调度器,这里用ReduceLROnPlateau,当验证集loss连续5个epoch不下降时把学习率乘0.5:
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4) criterion = nn.CrossEntropyLoss() scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, mode="min", factor=0.5, patience=5)学习率过大表现为loss剧烈震荡不收敛,过小则loss下降非常缓慢。确认优化器和调度器配置后,建议记录的指标是训练loss、验证loss和验证准确率,其中验证loss是调度器判断的依据,不能用训练loss替代。
4.2 训练循环、检查点保存与早停
训练循环的要点是每一轮结束时跑一次验证集,同时把验证loss最小的权重单独保存。只保存state_dict而不是整个模型,因为模型结构可能调整,但权重文件在相同结构下不会失效。完整训练流程如下:
def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total = 0.0, 0, 0 for x, y in loader: x, y = x.to(device), y.to(device) optimizer.zero_grad() out = model(x) loss = criterion(out, y) loss.backward() optimizer.step() total_loss += loss.item() * y.size(0) correct += (out.argmax(1) == y).sum().item() total += y.size(0) return total_loss / total, correct / total @torch.no_grad() def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total = 0.0, 0, 0 for x, y in loader: x, y = x.to(device), y.to(device) out = model(x) loss = criterion(out, y) total_loss += loss.item() * y.size(0) correct += (out.argmax(1) == y).sum().item() total += y.size(0) return total_loss / total, correct / total训练主循环里记住三点:每个epoch结束调用scheduler.step(val_loss);验证准确率不再提升时保存检查点;跑够预定epoch数或连续15个epoch验证loss没刷新纪录就提前停止。检查点里同时保存epoch、模型权重、优化器状态,便于中断后恢复训练:
best_loss = float("inf") for epoch in range(50): train_loss, train_acc = train_one_epoch( model, train_loader, optimizer, criterion, device) val_loss, val_acc = evaluate(model, val_loader, criterion, device) scheduler.step(val_loss) if val_loss < best_loss: best_loss = val_loss torch.save({"epoch": epoch, "model_state": model.state_dict(), "optimizer_state": optimizer.state_dict()}, "best_ckpt.pt")注意模型在训练和验证之间的状态切换。model.eval()必须调用,否则BatchNorm仍在使用训练批次统计量,验证输出会不准。训练完成后加载检查点再评估一次,确认保存的权重复现了验证准确率。
4.3 数据增强、Dropout与常见调参坑
CWRU数据干净且类别均衡,模型可能很快在训练集上拿到99%以上准确率,但真实振动信号含有噪声和转速波动。给训练集加高斯噪声是最贴近物理现实的数据增强手段,按信噪比加噪比直接加随机扰动更容易控制强度。常见配置是信噪比20dB到10dB,数值越小噪声越强,默认取20dB:
def add_noise(x, snr_db=20.0): # x为窗口内信号, 先计算原始信号功率再生成对应噪声 sig_power = x.pow(2).mean() noise_power = sig_power / (10 ** (snr_db / 10.0)) noise = torch.randn_like(x) * torch.sqrt(noise_power) return x + noise另一个有效增强是随机时间平移。故障冲击的起始位置在每个窗口内不一定对齐,用torch.roll把信号左右平移几十个采样点,可以模拟触发位置偏移。注意验证集和测试集不要加任何增强,只在训练循环内对输入张量调用。
常见调参问题集中在三个现象。第一,训练准确率接近100%但验证集只有七八成,属于过拟合,优先加大Dropout到0.6、增强噪声强度、把batch size从64降到32。第二,loss完全不下降,检查数据是否做了窗口内归一化、标签是否从0开始连续编码、学习率是否设置在合理区间。第三,训练集和测试集同时出现某个类别准确率特别低,通常是对应故障尺寸的数据量少或者和其他类别在波形上相似,比如滚动体故障的冲击信号容易被噪声掩盖,此时单独增加该类别的滑窗重叠率比改网络更有效。
5. 从源码到现场:混淆矩阵、TorchScript导出与跨负载验证
5.1 用混淆矩阵与t-SNE验证诊断效果
准确率只能说明整体水平,工程上更关注哪些类别互相混淆。内圈故障和外圈故障在载荷变化时容易混淆,滚动体故障误判成正常的比例通常最高,这个现象需要在验证阶段用混淆矩阵确认。
from sklearn.metrics import confusion_matrix @torch.no_grad() def predict_labels(model, loader, device): model.eval() preds, gts = [], [] for x, y in loader: x = x.to(device) preds.extend(model(x).argmax(1).cpu().tolist()) gts.extend(y.tolist()) return confusion_matrix(gts, preds)t-SNE可以直观展示倒数第二层特征是否按类别聚拢。把model.features的输出作为特征向量输入T-SNE,perplexity取30,看10个类别在二维平面上是否明显分簇。如果正常类和某个故障类重叠,说明该故障冲击微弱,优先考虑数据增强而不是加深网络。
5.2 跨负载验证:从CWRU到现场数据的一道坎
模型在同一负载内部划分训练集和测试集时准确率通常很高,但用0马力数据训练、3马力数据测试时,转速和载荷变化会让准确率明显下降,这是1DCNN轴承故障诊断从论文走向落地时最大的问题。缓解的通用做法是在目标工况收集少量数据,冻结前两层卷积,只微调后面特征层和分类器。冻结参数数量少,几百条样本就能做,效果比重新训练整个网络稳定得多。
导出推理模型用TorchScript,方便脱离Python环境部署。注意导出前确定输入尺寸是固定的(1, 1, 2048),否则trace会失败:
model.eval() example_input = torch.randn(1, 1, 2048) scripted = torch.jit.trace(model, example_input) scripted.save("bearing_1dcnn.pt")现场推理时预处理必须和训练完全一致,特别是窗口内归一化里的均值方差要从当前窗口独立计算,不能拿训练集的统计量套用。把滑窗参数、归一化逻辑、类别字典和模型文件一起打包,现场换人也能直接接手。
本文还有配套的精品资源,点击获取