ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于DANN迁移学习的变工况轴承故障诊断:原理、PyTorch实现与避坑指南

基于DANN迁移学习的变工况轴承故障诊断:原理、PyTorch实现与避坑指南 简介这份资源面向计算机相关专业学生、教师及企业员工提供基于迁移学习DANN模型实现不同工况轴承故障诊断的完整Python项目可用于毕业设计、课程设计、作业或项目初期演示也适合初学者进阶学习。压缩包共35个文件约23KB以18个py源码文件为核心涵盖数据加载、模型定义、训练与测试等模块另含zbak备份、xml配置、yml与md说明文档及license等辅助文件结构清晰便于二次修改。已有147人学习下载。项目代码均测试运行成功功能正常答辩评审平均分达96分读者可获取完整源码、文档说明、数据集与模型理解迁移学习在跨工况故障诊断中的落地思路并在此基础上扩展其他功能。下载后请先查看README.md资源仅供学习参考禁止用于商业用途。1. 轴承故障诊断遇上变工况DANN 迁移学习到底在解决什么产线上刚换了一批新轴承或者电机转速从 1500 rpm 调到 900 rpm原来在实验室数据集上跑到 99% 准确率的诊断模型现场准确率直接掉到 60% 出头。这不是模型写错了而是域偏移在作祟——训练数据的工况负载、转速、采样环境和测试工况对不上特征分布整体漂移分类边界自然失效。基于迁移学习 DANN 模型的不同工况轴承故障诊断要解决的就是这件事让模型在源工况有标签、数据充足上学到的故障特征能迁移到目标工况标签稀缺甚至没有上继续用。DANNDomain-Adversarial Neural Network域对抗神经网络的核心思路不复杂一边训练故障分类器一边用一个域判别器去区分样本来自源域还是目标域再通过梯度反转层让特征提取器骗过判别器最终学到的特征既分得清故障类型又分不清工况来源。这套方案适合手里有 CWRU、JNU、XJTU-SY 这类公开轴承数据集、想往实际产线迁移的工程师也适合做直推式迁移学习和域适应方向的研究者。Python 生态下 PyTorch 是最顺手的实现工具下面从原理到代码一步步拆开讲。2. DANN 的域对抗机制与轴承振动信号的适配逻辑2.1 梯度反转层为什么能让特征忘记工况普通迁移学习常用 MMD、CORAL 这类统计距离去对齐源域和目标域的特征分布但它们是硬对齐对非线性、多模态的振动信号效果有限。DANN 换了个思路不直接度量分布距离而是引入一个域判别器让它去判断特征来自哪个域。如果判别器分不出来说明特征已经域不变了。关键在于梯度反转层Gradient Reversal LayerGRL。前向传播时它是个恒等映射反向传播时把梯度取负再传回去。这样一来特征提取器收到的梯度方向是让判别器变差而判别器自己收到的是让自己变好。两者对抗最终特征提取器学到的表示就是域无关的。用公式描述总损失是L L_cls(源域) - λ * L_domain其中L_cls是源域上的故障分类损失L_domain是域判别损失λ是权衡系数。注意那个负号——它就是梯度反转在损失层面的体现。λ 不能一开始就设很大否则训练初期分类还没学好就被域对齐带偏了常见做法是让 λ 随训练轮次从 0 缓慢升到 1。2.2 轴承振动信号怎么变成 DANN 能吃的输入CWRU 数据集的原始格式是.mat文件里面是驱动端和风扇端的加速度时序信号采样率 12 kHz 或 48 kHz。DANN 不能直接吃原始时序得先做特征工程或转成时频图。我一般用两种输入形式第一种是连续采样切片 统计特征。把每段信号切成 1024 点的样本提取时域特征均方根、峭度、峰值因子和频域特征FFT 后的频带能量拼成一个特征向量。这种方式数据量小、训练快适合快速验证 DANN 是否有效。第二种是短时傅里叶变换STFT转时频图把一维信号变成二维图像再用 CNN 做特征提取。这种方式保留了更多信息但计算量大对显存有要求。两种方式我都试过快速验证阶段用第一种追求精度时用第二种。下面代码以第一种为主方便你在普通笔记本上跑通。2.3 源域和目标域怎么划分才符合不同工况CWRU 的工况通常按负载划分0 HP、1 HP、2 HP、3 HP 四种负载对应不同转速。做迁移学习时常见划分方式是迁移任务源域目标域难度任务 A0 HP1 HP较易任务 B0 HP2 HP中等任务 C0 HP3 HP较难任务 D1 HP3 HP较难负载差距越大域偏移越严重迁移难度越高。建议先从任务 A 跑通确认代码没问题后再挑战任务 C 或 D。如果任务 A 都跑不出效果大概率是数据预处理或超参设置有问题别急着换模型。注意源域和目标域的故障类别必须一致。如果源域有 10 类故障目标域只有 3 类那就不是标准 DANN 能处理的场景了需要考虑部分域适应方法。3. 用 PyTorch 搭一个能跑的 DANN从数据加载到训练循环3.1 数据预处理CWRU 的 .mat 文件读取与切片先装依赖。Python 环境建议 3.8 以上PyTorch 用 CPU 版就能跑通小规模实验pip install torch numpy scipy scikit-learn matplotlib读取 CWRU 的.mat文件并切片import scipy.io as sio import numpy as np def load_cwru_signal(mat_path, keyX097_DE_time): 读取 CWRU 数据集中的驱动端振动信号 mat_path: .mat 文件路径 key: 信号变量名不同文件可能不同需先查看文件结构 data sio.loadmat(mat_path) signal data[key].flatten() # 展平成一维 return signal def slice_signal(signal, window_size1024, overlap0.5): 将长信号切成固定长度的样本 window_size: 每个样本的点数 overlap: 相邻样本的重叠比例 step int(window_size * (1 - overlap)) samples [] for start in range(0, len(signal) - window_size, step): samples.append(signal[start:start window_size]) return np.array(samples) # 示例读取 0 HP 正常基线信号 signal load_cwru_signal(97.mat, keyX097_DE_time) samples slice_signal(signal, window_size1024, overlap0.5) print(f切出 {samples.shape[0]} 个样本每个 {samples.shape[1]} 点)这里有几个参数需要说明。window_size1024是轴承故障诊断里的常用值对应 12 kHz 采样率下约 85 ms 的信号足够覆盖一个冲击周期。overlap0.5表示相邻样本重叠一半目的是增加样本数量但注意重叠过高会导致训练集和测试集信息泄漏0.5 是个比较稳妥的值。读取.mat文件时key参数容易踩坑。不同 CWRU 文件的变量名不一样比如正常基线是X097_DE_time内圈故障可能是X105_DE_time。建议先用print(data.keys())看一下文件里到底有哪些变量别硬编码。3.2 特征提取与 Dataset 封装把切片后的信号转成特征向量from scipy.stats import kurtosis, skew from scipy.fft import fft def extract_features(sample, fs12000): 提取时域和频域特征 sample: 一维振动信号 fs: 采样率 features [] # 时域特征 features.append(np.sqrt(np.mean(sample ** 2))) # RMS features.append(kurtosis(sample)) # 峭度 features.append(skew(sample)) # 偏度 features.append(np.max(np.abs(sample))) # 峰值 features.append(np.std(sample)) # 标准差 # 频域特征FFT 后分 5 个频带取能量 spectrum np.abs(fft(sample))[:len(sample) // 2] bands np.array_split(spectrum, 5) for band in bands: features.append(np.sum(band ** 2)) return np.array(features, dtypenp.float32)时域特征里RMS 反映振动能量峭度对冲击成分敏感轴承故障早期冲击弱峭度会先变化峰值因子适合检测局部缺陷。频域分 5 个频带是经验做法轴承故障特征频率通常在几千赫兹范围内分频带能量能捕捉到这些成分。封装成 PyTorch Datasetimport torch from torch.utils.data import Dataset class BearingDataset(Dataset): def __init__(self, samples, labels): self.features np.array([extract_features(s) for s in samples]) self.labels np.array(labels, dtypenp.int64) def __len__(self): return len(self.labels) def __getitem__(self, idx): return (torch.tensor(self.features[idx]), torch.tensor(self.labels[idx]))这里把特征提取放在__init__里一次性算完避免每个 epoch 重复计算。如果数据量大可以先把特征存成.npy文件下次直接加载。3.3 DANN 网络结构特征提取器 分类器 域判别器import torch.nn as nn class GradientReversalLayer(torch.autograd.Function): staticmethod def forward(ctx, x, alpha): ctx.alpha alpha return x.view_as(x) staticmethod def backward(ctx, grad_output): return -ctx.alpha * grad_output, None class DANN(nn.Module): def __init__(self, input_dim10, num_classes10, num_domains2): super().__init__() # 特征提取器 self.feature_extractor nn.Sequential( nn.Linear(input_dim, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, 64), nn.ReLU(), ) # 故障分类器 self.classifier nn.Sequential( nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, num_classes), ) # 域判别器 self.domain_discriminator nn.Sequential( nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, num_domains), ) def forward(self, x, alpha1.0): features self.feature_extractor(x) class_out self.classifier(features) reverse_features GradientReversalLayer.apply(features, alpha) domain_out self.domain_discriminator(reverse_features) return class_out, domain_out网络结构不复杂三层全连接足够处理手工特征。Dropout(0.3)是为了防止过拟合如果数据量很大可以调低。alpha就是前面说的 λ训练时从 0 逐渐升到 1。3.4 训练循环分类损失和域判别损失怎么平衡def train_dann(model, source_loader, target_loader, epochs100, lr1e-3): optimizer torch.optim.Adam(model.parameters(), lrlr) cls_criterion nn.CrossEntropyLoss() domain_criterion nn.CrossEntropyLoss() for epoch in range(epochs): model.train() # alpha 从 0 线性升到 1 alpha min(1.0, epoch / (epochs * 0.5)) total_cls_loss 0.0 total_domain_loss 0.0 # 同时遍历源域和目标域 target_iter iter(target_loader) for src_x, src_y in source_loader: try: tgt_x, _ next(target_iter) except StopIteration: target_iter iter(target_loader) tgt_x, _ next(target_iter) # 源域分类损失 域判别损失标签为 0 src_class_out, src_domain_out model(src_x, alpha) cls_loss cls_criterion(src_class_out, src_y) src_domain_loss domain_criterion( src_domain_out, torch.zeros(src_x.size(0), dtypetorch.long) ) # 目标域只有域判别损失标签为 1 _, tgt_domain_out model(tgt_x, alpha) tgt_domain_loss domain_criterion( tgt_domain_out, torch.ones(tgt_x.size(0), dtypetorch.long) ) domain_loss src_domain_loss tgt_domain_loss loss cls_loss domain_loss optimizer.zero_grad() loss.backward() optimizer.step() total_cls_loss cls_loss.item() total_domain_loss domain_loss.item() if (epoch 1) % 10 0: print(fEpoch {epoch1}: cls_loss{total_cls_loss:.4f}, fdomain_loss{total_domain_loss:.4f}, alpha{alpha:.2f})训练循环里几个关键点。第一源域和目标域要同时喂给模型所以用了两个 DataLoader 交替取数据。第二域标签是人工构造的源域样本标 0目标域样本标 1。第三alpha从 0 升到 1 用了epochs * 0.5作为分母意思是前一半训练轮次完成升温后一半保持满强度对抗。这个节奏比一开始就满强度对抗稳定得多。如果训练时发现domain_loss一直降不下去判别器太强说明特征提取器没学到域不变特征可以试试降低判别器的学习率或者把判别器网络改浅一点。反过来如果domain_loss一直在 0.69 附近二分类的随机水平说明判别器太弱特征提取器轻松骗过它这时候要增强判别器。4. 不同工况迁移实验参数怎么调、结果怎么看4.1 源域和目标域的 DataLoader 构建from torch.utils.data import DataLoader # 假设已经准备好了源域和目标域的特征和标签 # source_features, source_labels 来自 0 HP # target_features, target_labels 来自 3 HP训练时标签不用测试时用 source_dataset BearingDataset(source_samples, source_labels) target_dataset BearingDataset(target_samples, target_labels) source_loader DataLoader(source_dataset, batch_size64, shuffleTrue) target_loader DataLoader(target_dataset, batch_size64, shuffleTrue) model DANN(input_dim10, num_classes10, num_domains2) train_dann(model, source_loader, target_loader, epochs100, lr1e-3)batch_size64是个折中值太小梯度噪声大太大泛化差。如果显存不够就降到 32。shuffleTrue对源域是必须的对目标域也建议开避免每次取到的目标域样本顺序固定。4.2 关键超参alpha 升温策略、学习率、Dropout参数推荐值作用调整方向alpha 升温前 50% epoch 线性升到 1控制域对抗强度训练不稳定就放慢升温学习率1e-3Adam 默认值损失震荡就降到 5e-4Dropout0.3防止过拟合源域小就调高到 0.5batch_size64梯度估计显存不够降到 32特征维度10时域 5 频域 5可加到 15 增加频带数学习率是最容易翻车的参数。DANN 有两个损失在对抗学习率太大会导致训练震荡domain_loss和cls_loss此起彼伏。我一般先用 1e-3 跑 20 个 epoch 看趋势如果两条损失曲线都在下降就继续如果一条降一条升就降到 5e-4。4.3 用目标域测试集验证迁移效果训练完后在目标域上评估def evaluate(model, test_loader): model.eval() correct 0 total 0 with torch.no_grad(): for x, y in test_loader: class_out, _ model(x, alpha0.0) # 测试时不需要域对抗 pred class_out.argmax(dim1) correct (pred y).sum().item() total y.size(0) return correct / total # 用目标域的有标签测试集评估 target_test_loader DataLoader(target_dataset, batch_size64, shuffleFalse) acc evaluate(model, target_test_loader) print(f目标域准确率: {acc:.4f})注意测试时alpha0.0因为推理阶段不需要域判别梯度反转也不起作用。如果测试时忘了设 alpha虽然结果一样因为torch.no_grad()下不计算梯度但代码语义上不清晰。对比实验建议做三组只用源域训练直接在目标域测试无迁移、源域目标域但不用 DANN普通微调、DANN。三组结果放一起才能说明 DANN 到底有没有用。我跑 CWRU 0 HP 到 3 HP 的经验是无迁移大概 60% 到 70%普通微调 75% 到 85%DANN 能到 85% 到 92%。如果 DANN 比无迁移还差那肯定是实现有问题。5. 避坑与排查DANN 轴承诊断最常见的 5 个翻车现场5.1 域判别器太强导致分类器崩溃现象训练几个 epoch 后cls_loss不降反升目标域准确率比无迁移还低。原因域判别器学习能力太强特征提取器被逼着把所有特征都变成域不变的结果连故障相关的判别信息也丢了。这是 DANN 最经典的失败模式。解决降低域判别器的学习率比如设为分类器的 0.1 倍或者把判别器网络改浅从两层降到一层。另一个办法是减小 alpha 的最大值不让它升到 1比如封顶在 0.5。5.2 源域和目标域样本数严重不平衡现象目标域准确率波动很大每次训练结果差很多。原因源域有几千个样本目标域只有几百个训练时目标域的域判别损失被源域淹没域对齐效果不稳定。解决对目标域做过采样或者用加权损失让目标域的域判别损失权重更高。也可以在 DataLoader 里设置drop_lastTrue保证每个 batch 的样本数一致。5.3 特征提取时用了目标域标签信息现象离线评估准确率很高但实际部署时效果差。原因做特征标准化时用了全部数据包括目标域测试集的均值和方差造成信息泄漏。这是时序信号处理里很隐蔽的坑。解决标准化参数只能用源域训练集计算然后应用到目标域。代码上就是先fit源域再transform目标域不能反过来。5.4 窗口重叠过高导致训练集测试集泄漏现象训练准确率 99%测试准确率 60%。原因切片时overlap0.9相邻样本几乎一样训练集和测试集里存在大量近似重复样本模型记住了训练样本而不是学到特征。解决重叠比例控制在 0.5 以下并且划分训练集和测试集时按时间段划分不要随机打乱后划分。比如前 70% 时间段的样本做训练后 30% 做测试。5.5 alpha 升温太快导致训练不收敛现象前几个 epoch 损失就爆炸梯度变成 NaN。原因alpha 一开始就很大域对抗损失主导了总损失分类器还没学好就被带偏。解决让 alpha 从 0 开始用 sigmoid 或线性升温前 10 个 epoch 保持 alpha 小于 0.1。如果还是不稳定就在域判别损失前加一个小的系数比如 0.1等训练稳定后再逐步加大。6. 把 DANN 用到实际产线几个能落地的进阶技巧实验室跑通只是第一步真正往产线迁移时有几个技巧能明显提升效果。第一用元学习初始化特征提取器。标准 DANN 从随机初始化开始训练如果目标域和源域差距很大前期对齐会很吃力。可以先用 MAML 或 Reptile 在多个源工况上做元训练得到一个对工况变化敏感的初始化参数再用 DANN 做域适应。这就是最近比较热的元学习 迁移学习组合思路实测在跨负载任务上能比纯 DANN 高 3 到 5 个百分点。第二目标域伪标签自训练。DANN 训练完后用模型对目标域无标签样本预测伪标签挑置信度高的样本加入训练集再跑一轮 DANN。这个做法相当于半监督学习能进一步利用目标域数据。注意伪标签阈值别设太低一般取预测概率大于 0.9 的样本。第三在线部署时做滑动窗口统计。产线上的振动信号是连续流不能像离线实验那样切好样本再推理。实际部署时用滑动窗口每 1024 点推理一次然后对最近 10 次推理结果做投票取多数类作为最终输出。这样能平滑掉单次推理的噪声。第四模型保存和加载要固定特征维度。训练时特征维度是 10部署时如果忘了同步特征提取代码输入维度对不上直接报错。建议把特征提取逻辑单独封装成一个模块训练和推理共用同一份代码。# 保存模型时同时保存特征提取配置 torch.save({ model_state_dict: model.state_dict(), input_dim: 10, num_classes: 10, window_size: 1024, fs: 12000, }, dann_bearing.pth) # 加载时先读配置再建模型 checkpoint torch.load(dann_bearing.pth) model DANN(input_dimcheckpoint[input_dim], num_classescheckpoint[num_classes]) model.load_state_dict(checkpoint[model_state_dict])最后说个我自己的习惯每次跑新工况的迁移任务先不急着调 DANN 的超参而是先用源域数据训练一个普通分类器在目标域上测一下基线准确率。如果基线已经有 85%那 DANN 的提升空间有限不如把精力花在特征工程上如果基线只有 50% 到 60%那 DANN 值得好好调。这个判断习惯帮我省了很多无效调参的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表