简介:基于Python的滚动轴承智能故障诊断系统开发资料,面向机械故障诊断、深度学习和毕业设计场景,解决轴承正常、内圈、外圈及滚动体故障的分类识别问题。压缩包共50个文件,体积39.63MB,以30个mat格式的CWRU振动数据集为核心,覆盖不同转速与故障尺寸,配合4个py脚本(含DNN、CNN及CNN+GRU混合模型实现)、8个zbak备份文件、7个md说明文档和1个zip附包,结构清晰、适合从零复现。已有42人参与学习,可作为课程设计、论文实验的参考范本。通过该资源可获得整套信号处理与模型训练流程:数字滤波消除干扰、幅值归一化、时频特征提取、小批量梯度下降训练、早停策略防过拟合、贝叶斯优化调参,以及故障类别与严重程度评估,并生成频谱对比、演化趋势图表和标准化诊断报告,能帮助完整复现轴承智能故障诊断项目。
1. 为什么说滚动轴承故障诊断是深度学习最容易见效的落地场景
车间里的老师傅靠耳朵听轴承,能分辨内圈还是外圈故障;可老师傅会退休,24 小时无人产线也不会自己“听”。滚动轴承智能故障诊断系统,做的就是把这双耳朵换成一套基于 Python 的程序:从振动传感器拿信号,用深度学习算法自动识别正常、内圈、外圈和滚动体故障状态。这个方向最反直觉的一点是:真正卡住项目进度的从来不是模型结构,而是数据集怎么切、标签有没有对齐、滑窗采样有没有造成信息泄漏。《基于Python的滚动轴承智能故障诊断系统开发(深度学习算法实现与完整数据集)》拆开来就是三件事——数据、模型、部署,而“完整数据集”恰恰是让这个项目能落地、能复现的第一桶金。这篇文章适合机械方向想转深度学习的同学,也适合做设备预测性维护的工程师,照着步骤能把整套流程从原始振动波形一路跑到可部署的模型文件。
2. 完整数据集不会用等于白拿:振动数据加载、滑窗采样与划分顺序
标题里“完整数据集”五个字才是这个项目的发动机。深度学习模型需要样本量大、类别均衡、工况多样;如果你手里的数据集只有十几个文件,每个文件几十万个采样点,直接把整条信号喂给模型,样本数远远不够。所以第一步永远是把原始振动波形切成“窗口样本”,同时保证训练、验证、测试三份数据在时间上不重叠。这里最容易翻车,也最值得先讲。
2.1 公开数据集与自采信号的取舍
滚动轴承故障诊断领域最常被拿来做基准的公开数据集是凯斯西储大学(CWRU)的滚动轴承数据集,它包含正常、内圈故障、外圈故障、滚动体故障四类状态,故障直径还有 0.007、0.014、0.021 英寸等几种等级,采样率常见 12kHz 和 48kHz。优点是标签干净、类别完整,做算法验证和模型选型非常方便,你可以在国内学术平台或 GitHub 镜像找到打包好的版本。缺点是工况单一,转速负载恒定,和真实产线差距大。我的建议是:先用公开数据集把整个训练、验证、部署流程跑通,再去现场用自采数据做迁移学习与微调。
如果是自己采集数据,传感器最好吸附在轴承座正上方或承载区附近,避免信号经过长路径衰减;采集卡至少 16bit,采样率不低于 10kHz。这里有一个常被忽略的点:必须同步记录转速、负载、温度等工况参数,否则后面算故障特征频率时没有参照系,模型跨工况测试也做不了。另外,现场数据往往混着工频干扰、齿轮啮合噪声和不对中成分,信号链问题导致数据“变脏”的情况很常见,后面避坑章会展开。
2.2 加载振动信号:从 mat、csv、npy 到 numpy
公开数据集常见的封装格式是 .mat,有的项目打包成 .csv 或 .npy。加载这一步看起来很基础,但字段名采错、维度没展开、数据是 HDF5 格式读不了,这三种问题我都在第一次写代码时遇到过。以一个 CWRU 风格的 .mat 文件为例:
import numpy as np import scipy.io as sio def load_bearing_signal(path, key='DE_time'): """加载滚动轴承振动信号,返回一维 float64 数组""" if path.endswith('.mat'): mat = sio.loadmat(path) # CWRU 文件中 DE_time 是驱动端加速度,FE_time 是风扇端加速度 # 部分版本字段叫 signal 或 d_data,先打印 keys() 确认 raw = mat[key].ravel().astype(np.float64) elif path.endswith('.npy'): raw = np.load(path).ravel().astype(np.float64) else: raw = np.loadtxt(path, dtype=np.float64) return raw def inspect(raw): print('shape:', raw.shape) print('采样点数:', len(raw)) print('峰值: {:.4f}'.format(np.abs(raw).max())) print('均值/标准差: {:.4f}/{:.4f}'.format(raw.mean(), raw.std()))这里.ravel()的作用是把 mat 里可能的 (N,1) 或 (1,N) 维度展平成一维;.astype(np.float64)保证后续滤波和 FFT 不因为整数类型丢失精度。DE_time是 CWRU 最常用的驱动端加速度字段,但不同打包版本字段名不固定,所以第一次加载时一定先打印mat.keys()确认,不要凭记忆猜键名。
如果你的 .mat 文件是 v7.3 格式(HDF5),scipy.io.loadmat会直接报错,因为底层存储格式变了。解决办法是换 h5py:
import h5py def load_mat73(path, key='DE_time'): with h5py.File(path, 'r') as f: raw = np.array(f[key]).ravel().astype(np.float64) return rawh5py 读出来的矩阵维度顺序可能和 matlab 里相反,遇到(n,1)变(1,n)的情况就再.T一下。这不是玄学,是 HDF5 按行主序存储导致的常见差异。
2.3 窗口该开多大:转速、故障节拍与 2048 点的关系
窗口长度不是拍脑袋定的,它由转速和故障特征频率决定。轴承故障冲击在每转的固定角度位置出现,比如外圈故障的频率通常是转频的 3 到 5 倍。以 1797rpm 为例,转频 29.95Hz,一个周期约 33ms,在 12kHz 采样下大约 400 个采样点。你希望一个窗口至少包含 3 到 5 个故障周期,所以 1024 到 2048 点是合理范围。我一般默认 2048 点,重叠率 25% 到 50%。
fs = 12000 rpm = 1797 fr = rpm / 60 rev_samples = int(fs / fr) print('每转采样点数:', rev_samples) # 约 400 点 print('2048 点覆盖转数:', 2048 / rev_samples) # 约 5 转窗口太短,一个样本里包含不到完整的冲击周期,模型学不到周期性;窗口太长,计算量变大,而且滑窗后样本数变少,某些类别可能不够用。建议在 512 到 4096 点之间做一组小消融实验,用验证集 F1 选最优,而不是凭感觉定。
滑窗采样函数是每个诊断项目的地基,直接贴一个可用版本:
def make_samples(raw, win_len=2048, hop=1024): """对一维信号滑窗切分,返回 (n_samples, win_len) 数组""" if len(raw) < win_len: raise ValueError('窗口大于信号长度,请减小 win_len') n = (len(raw) - win_len) // hop + 1 X = np.zeros((n, win_len), dtype=np.float32) for i in range(n): start = i * hop X[i] = raw[start:start + win_len] return Xhop是滑窗步长,hop=win_len表示窗口不重叠,hop=win_len // 2表示 50% 重叠。重叠率高会让训练样本数量膨胀,但也让相邻窗口共享了大量数据点,后面避坑章会专门讲这个问题。
2.4 划分顺序:先按文件切,再滑窗
这是整个数据准备阶段最重要的一条规则:划分训练集、验证集、测试集必须在滑窗之前完成,而且要以“文件”或“连续时间段”为最小单位,不能把滑窗后的所有样本混在一起随机打散。原因是相邻窗口之间有大量重叠采样点,如果它们同时出现在训练集和验证集,相当于开卷考试,验证集准确率虚高。正确做法是把每个文件当作一个独立 session,先在文件层面切分,再对每个文件内部滑窗。
# 1) 按文件划分 train / val / test def train_val_test_files(file_list, ratio=(0.7, 0.15, 0.15), seed=42): n = len(file_list) n_tr = int(n * ratio[0]) n_va = int(n * ratio[1]) rng = np.random.RandomState(seed) idx = rng.permutation(n) return ([file_list[i] for i in idx[:n_tr]], [file_list[i] for i in idx[n_tr:n_tr + n_va]], [file_list[i] for i in idx[n_tr + n_va:]]) # 2) 各自滑窗,测试文件在滑窗前完全不参与训练 train_raw = np.concatenate([load_bearing_signal(f) for f in train_files]) val_raw = np.concatenate([load_bearing_signal(f) for f in val_files]) test_raw = np.concatenate([load_bearing_signal(f) for f in test_files]) X_train = make_samples(train_raw, win_len=2048, hop=1024) X_val = make_samples(val_raw, win_len=2048, hop=1024) X_test = make_samples(test_raw, win_len=2048, hop=2048) # 测试集不重叠代码里测试集滑窗步长特意设成 2048(不重叠),这是为了让测试样本彼此独立,评估结果更可信。验证集可以保留少量重叠,但测试集尽量不要重叠。对于自采数据,更稳妥的划分方法是按时间切:设备运行第 1 小时做训练、第 2 小时做验证、第 3 小时做测试,而不是从不同时间段随机抽取样本,因为相邻时段的工况变化往往比随机抽取更能反映真实部署时的分布漂移。
3. 从时域到频域再到时频图:故障特征怎么在数据里显形
深度学习号称端到端,但在公开小数据集上,纯原始 2048 点波形直接喂网络,往往不如“先做一点变换”的版本好用。常见做法有两种:一种是保留原始波形,配合数据增强做判别;另一种是把振动信号转成频谱或时频图,让 CNN 当图像分类。两条路都走得通,但前提是你得先理解轴承故障特征频率,否则后面对模型输出做验证时完全抓瞎。
3.1 轴承特征频率公式:诊断的“标尺”
滚动轴承四个典型故障对应四个特征频率,这些频率等于“故障的身份证号”。已知滚动体个数 Z、转速 fr、节圆直径 D、滚动体直径 d、接触角 α,近似公式如下:
- 外圈故障频率 BPFO = Z/2 × fr × (1 - d/D × cosα)
- 内圈故障频率 BPFI = Z/2 × fr × (1 + d/D × cosα)
- 滚动体故障频率 BSF = D/(2d) × fr × (1 - (d/D × cosα)²)
- 保持架故障频率 FTF = fr/2 × (1 - d/D × cosα)
CWRU 数据集的 6205-2RS 轴承常见参数约 Z=9、d=8mm、D=52mm、接触角近似 0。计算脚本如下:
z = 9 d = 8.0 D = 52.0 alpha = 0.0 fr = 1797 / 60 bpfo = z / 2 * fr * (1 - d / D) bpfi = z / 2 * fr * (1 + d / D) bsf = D / (2 * d) * fr * (1 - (d / D) ** 2) ftf = fr / 2 * (1 - d / D) print({'BPFO': round(bpfo, 2), 'BPFI': round(bpfi, 2), 'BSF': round(bsf, 2), 'FTF': round(ftf, 2)})算出来转频约 29.95Hz 时,BPFO 约 107Hz、BPFI 约 162Hz。注意实际频谱上看到的不是一根干净的谱线,而是故障特征频率及其倍频、边频带组成的谱峰簇,因为负载波动和转频调制会让能量分散。读频谱时先找基频,再验证倍频是否对齐,比单纯找最高峰靠谱得多。
3.2 包络谱:用 Hilbert 解调出故障冲击节拍
早期轴承故障的表现形式是周期性冲击,冲击能量集中在轴承座或传感器的高频共振频带,而故障重复频率本身是低频的。直接对原始信号做 FFT,高频共振成分可能被工频和低频趋势盖住,看不出来。经典解法是先带通滤波,再做希尔伯特包络解调,最后对包络做 FFT,得到包络谱。
from scipy.signal import butter, filtfilt, hilbert def envelope_spectrum(x, fs=12000, low=500, high=4000): """带通滤波 + Hilbert 包络解调,返回频率轴和包络谱""" # 1. 带通滤波:去掉工频趋势,保留故障冲击所在的高频共振带 b, a = butter(4, [low, high], btype='bandpass', fs=fs) y = filtfilt(b, a, x) # 2. Hilbert 变换求包络 analytic = hilbert(y) env = np.abs(analytic) # 3. 对包络做 FFT,故障重复频率出现在低频段 spec = np.abs(np.fft.rfft(env)) freq = np.fft.rfftfreq(len(x), d=1 / fs) return freq, spec关键参数是带通范围 low 和 high。12kHz 采样率下 high 不要超过 6000,否则超过采样定理;low 一般取 500Hz 以上,避开转频和齿轮啮合频率的干扰。这里用filtfilt而不是lfilter,因为filtfilt是零相位滤波,不会让冲击时刻在时间轴上发生偏移,包络谱的峰位置才准。调 low/high 时观察包络谱峰是否和 BPFO/BPFI 计算值对齐,对上就说明故障特征确实被解调出来了。
3.3 时频图:给 CNN 当图像输入的另一种选择
如果想让模型像图像分类一样工作,可以把短时傅里叶变换得到的时频图作为二维输入。这种做法在强噪声、非平稳工况下比纯频域更稳,因为时频图同时保留频率和时间分辨率,能捕捉故障冲击的时变特性。
from scipy.signal import stft def stft_to_image(x, fs=12000, nperseg=256, noverlap=128): """STFT 时频图,对数压缩 + min-max 归一化,输出 0~1 浮点数组""" f, t, Zxx = stft(x, fs=fs, nperseg=nperseg, noverlap=noverlap) spec = np.abs(Zxx) # (freq_bins, time_frames) log_spec = np.log1p(spec) # 对数压缩,防止动态范围过大 eps = 1e-6 img = (log_spec - log_spec.min()) / (log_spec.max() - log_spec.min() + eps) return img.astype(np.float32), f, t img, f, t = stft_to_image(raw[:2048]) print('时频图 shape:', img.shape) # 约 (129, 15)np.log1p相当于log(1+x),避免信号为 0 时取对数出错。nperseg=256在 12kHz 下频率分辨率约 45Hz,对区分 BPFO 和 BPFI 够用;如果噪声大,可以把nperseg提到 1024,频率分辨率更细,但时间分辨率会下降。生成的时频图样本可以直接作为二维输入训练 CNN,也可以缩放成固定尺寸后保存为 npy 文件。还有一种常见的数据增强做法:对原始信号做随机裁剪和加噪后再生成时频图,相当于把训练样本数量翻几倍,后面避坑章会说这个方法的边界。
4. 搭建一维残差网络:让模型从 2048 点信号里自己“听”出故障
模型选型这件事,不要一上来就上 Transformer。振动信号本质是时域序列,有很强的局部相关性,故障冲击的形态“短促冲击后跟随衰减震荡”是典型的局部模式,一维 CNN 的卷积核天然适合提取这种模式。LSTM 和 Transformer 的优势在长程依赖,但在公开轴承数据集上很难体现,反而因为参数多、数据少更容易过拟合。所以默认方案是一维 CNN 加残差连接,数据量到了几万窗口、窗口长度上万点再考虑换 Transformer。
4.1 模型结构:小网络优先,别一上来就堆层数
下面的模型是残差结构的一维 CNN,输入是(batch, 1, 2048)的振动窗口,输出 4 类 logits。stem 用大步长卷积快速降采样,两个残差 block 提取特征,最后全局平均池化接全连接层。结构简单、参数量小,在公开小数据上不容易过拟合。
import torch import torch.nn as nn class ResidualBlock1d(nn.Module): def __init__(self, in_ch, out_ch, stride=1): super().__init__() self.conv1 = nn.Conv1d(in_ch, out_ch, kernel_size=7, stride=stride, padding=3, bias=False) self.bn1 = nn.BatchNorm1d(out_ch) self.conv2 = nn.Conv1d(out_ch, out_ch, kernel_size=7, padding=3, bias=False) self.bn2 = nn.BatchNorm1d(out_ch) self.relu = nn.ReLU(inplace=True) self.shortcut = nn.Sequential() if stride != 1 or in_ch != out_ch: self.shortcut = nn.Sequential( nn.Conv1d(in_ch, out_ch, kernel_size=1, stride=stride, bias=False), nn.BatchNorm1d(out_ch) ) def forward(self, x): identity = x out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) out += self.shortcut(identity) out = self.relu(out) return out class BearingResNet(nn.Module): def __init__(self, num_classes=4, in_channels=1): super().__init__() self.stem = nn.Sequential( nn.Conv1d(in_channels, 32, kernel_size=64, stride=8, padding=28, bias=False), nn.BatchNorm1d(32), nn.ReLU(inplace=True), nn.MaxPool1d(kernel_size=4, stride=4) ) self.layer1 = self._make_layer(32, 64, blocks=2, stride=1) self.layer2 = self._make_layer(64, 128, blocks=2, stride=2) self.gap = nn.AdaptiveAvgPool1d(1) self.fc = nn.Linear(128, num_classes) def _make_layer(self, in_ch, out_ch, blocks, stride): layers = [ResidualBlock1d(in_ch, out_ch, stride)] for _ in range(1, blocks): layers.append(ResidualBlock1d(out_ch, out_ch, stride=1)) return nn.Sequential(*layers) def forward(self, x): x = self.stem(x) x = self.layer1(x) x = self.layer2(x) x = self.gap(x).squeeze(-1) return self.fc(x)输入(B, 1, 2048),经过 stem 后变成(B, 32, 64),layer1 保持长度不变,layer2 步长 2 把长度压到(B, 128, 32),最后全局平均池化成(B, 128)。kernel_size=64 的大卷积核是为了让 stem 有足够大的感受野看整体波形的形态;残差连接解决的是层数加深后的退化问题,这里只用了 2 个 block,但保留残差能给后续扩大模型留余地。
4.2 损失函数、优化器与类别权重
故障诊断数据几乎必然存在类别不平衡:正常样本占大多数,故障样本尤其是早期轻微故障样本很少。直接拿原始数据训练,模型学会“全预测为正常”就能拿到很高的准确率,但这个模型在生产环境毫无价值。损失函数上要显式处理不平衡:
import torch import torch.nn as nn # 假设每类样本量为 n_class_0 ... n_class_3 class_counts = torch.tensor([5000, 1200, 800, 300], dtype=torch.float32) class_weight = 1.0 / class_counts class_weight = class_weight / class_weight.sum() * len(class_counts) criterion = nn.CrossEntropyLoss(weight=class_weight) optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)class_weight让少数类样本在计算 loss 时获得更高权重,相当于把每个少数类样本复制了若干份。weight_decay=1e-4对一维信号模型是合适的起点,太大容易欠拟合,太小起不到正则作用。学习率 0.001 配合 CosineAnnealingLR,比固定学习率更容易收敛到平坦区域,batch size 一般取 64,显存不够就降到 32,尽量不要动学习率去迁就 batch size。
4.3 训练循环与验证:用宏平均 F1 而不是准确率
训练循环本身不复杂,但有一个细节值得注意:数据增强要在训练集上做,验证集和测试集只能做确定性变换。下面给出一个完整的训练循环和一个按类别平均 F1 的验证函数:
def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss = 0.0 n_total = 0 for xb, yb in loader: xb = xb.to(device).unsqueeze(1) # (B, 1, 2048) yb = yb.to(device) optimizer.zero_grad() out = model(xb) loss = criterion(out, yb) loss.backward() optimizer.step() total_loss += loss.item() * len(xb) n_total += len(xb) return total_loss / n_total def evaluate_macro_f1(model, loader, device): model.eval() y_true, y_pred = [], [] with torch.no_grad(): for xb, yb in loader: xb = xb.to(device).unsqueeze(1) out = model(xb) y_pred.extend(out.argmax(dim=1).cpu().numpy()) y_true.extend(yb.numpy()) return f1_score(y_true, y_pred, average='macro'), y_true, y_predunsqueeze(1)是把(B, 2048)变成(B, 1, 2048),因为 Conv1d 要求输入带通道维。验证指标这里选average='macro',它先计算每个类别的 F1 再取平均,类别不平衡时比准确率诚实得多。训练过程中保存验证集 macro-F1 最高的权重,而不是最后一个 epoch 的权重,这是最常见也最有效的“后悔药”。
5. 避坑:数据泄漏、类别失衡、跨工况翻车的 5 个血泪案例
这个方向的项目失败,十个里有八个不是因为模型不够先进,而是死在数据和评估方式上。以下是我反复踩过的五个坑,按发生频率排序,每一条都按“现象 → 原因 → 解决”写清楚。
5.1 数据泄漏:测试集准确率 99%,现场一测就崩
现象:训练时验证集准确率达到 99%,模型在公开测试集上也很漂亮,但拿去现场测真实采集的数据,准确率掉到 70% 以下。原因几乎都是数据泄漏,最常见的两种动作是:先对全部数据做归一化再划分数据集,导致测试集的均值和标准差信息混进了训练过程;或者先滑窗再随机划分样本,使同一段信号的相邻窗口同时出现在训练集和验证集。解决方法是严格按文件或连续时间段划分,归一化参数只从训练集统计,测试集用训练集的均值和标准差做变换。用 scikit-learn 的话,就是只对X_train调fit,再对X_val和X_test调transform,不要用fit_transform一把梭。
5.2 滑窗重叠率过高:验证集在“开卷考试”
现象:50% 重叠率下验证集宏平均 F1 到了 0.95,把重叠率降到 25% 后掉到 0.88,模型结构一点没改。原因在于重叠窗口共享了大量数据点,相邻窗口几乎可以互相推出标签,相当于验证集里混进了训练集的“答案”。解决方法是训练集重叠率控制在 50% 以内,验证集用 25% 或完全不重叠,测试集固定不重叠。判断自己在不在坑里的方法很粗暴:把测试集重叠率改成 0,再跑一次评估,如果 F1 明显下降,说明之前的结果偏乐观,重叠率可能已经太高了。
5.3 类别不平衡:模型学会了躺赢
现象:整体准确率 92%,但故障类召回率只有 40%,内圈故障和滚动体故障几乎全被预测成正常类。原因是故障样本占比太低,交叉熵损失被多数类主导,模型发现预测成“正常”就能把 loss 压得很低。解决方法是组合拳:损失函数加class_weight,少数类做过采样,数据增强只针对少数类样本做随机裁剪和加噪,评估指标只看宏平均 F1 和各类别的 precision/recall,不要只看总体准确率。如果过采样之后模型在小类别上表现上来了,再检查一下有没有过拟合——少数类样本被重复生成,模型可能记住的是增强样本里的噪声模式。
5.4 跨工况泛化:一换转速就翻车
现象:在 1797rpm 数据上训练,测试集也在同转速,F1 0.98;换到 1772rpm 或 1750rpm 的测试文件,F1 直接掉到 0.6 甚至更低。原因是转速改变后,故障特征频率跟着变了,模型学到的是“这个转速下的振动形态”,而不是“这个故障类别的本质特征”。这一点是致命的,因为真实产线转速和负载天天在变。解决方法是训练数据里混入多种转速和负载的样本,并按转速分层做验证;如果现场工况变化太大,考虑域自适应方法,或者至少在模型上线前做一次跨工况交叉验证,心里有底。
5.5 标签错位:一个丢帧毁掉整个训练集
现象:训练 loss 怎么都不降,模型在训练集上的准确率也只有 70%,换网络结构、调学习率都没用。最后发现数据采集阶段某段时间信号流丢了几十个帧,导致信号和标签整体错位,模型看到的是“标签说内圈故障、信号却是外圈故障”的乱配。解决方法是训练前做对齐校验:检查每个数据文件的采样点数是否符合预期,计算实际采样时长和预期时长的误差;如果有速度传感器,可以对比转频的 FFT 峰值位置来验证通道的时间轴是否一致。这个坑在公开数据集上基本不会出现,但自采数据几乎必踩,务必在数据加载阶段加一道检查。
6. 进阶:ONNX 导出在线监测与跨工况验证的最后一课
模型训练完只是开始,真正让诊断系统产生价值的是把它部署到产线上去。这里有一个很实用的技巧:把 PyTorch 模型导出成 ONNX,用 ONNX Runtime 做推理,一窗 2048 点的推理时间可以压到几毫秒,完全不依赖 PyTorch 环境,在工控机上也好装。
import torch model = BearingResNet(num_classes=4) model.load_state_dict(torch.load('best_model.pt', map_location='cpu')) model.eval() dummy = torch.randn(1, 1, 2048) torch.onnx.export( model, dummy, 'bearing_net.onnx', input_names=['signal'], output_names=['logits'], opset_version=13 )导出后可以用 ONNX Runtime 验证一遍输出是否和 PyTorch 一致,这是部署前的最后一道保险。在线监测场景里,滑窗步长可以按采集周期来定,比如每 2048 点推一次,也可以 1024 点推一次做重叠推理,然后对连续几次结果做平滑投票,能明显减少偶发误报。
关于验证,我现在的习惯是任何诊断模型交付前,必须输出一份按转速、负载拆分的混淆矩阵,而不是只报一个总体准确率。只报一个数字太容易骗人,按工况拆开看才能发现模型到底在哪些条件下会失效。比如内圈和外圈故障特征频率相差不大,混淆矩阵里这两类很容易互相串,看到这种结果就知道还需要在信号预处理上加带通滤波,或者增加对应工况的训练数据。准确率好看只是第一步,现场不掉链子才算数;这种“分层验证”的习惯,比多调几个超参数更能救项目。希望帮到你。
本文还有配套的精品资源,点击获取