ARTICLE DETAIL

资讯详情

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

DANN迁移学习轴承故障诊断:跨工况模型翻车解法与Python实践

DANN迁移学习轴承故障诊断:跨工况模型翻车解法与Python实践 简介一份基于迁移学习和域对抗网络的轴承故障诊断源码项目包面向计算机相关专业的学生、教师及企业工程技术人员针对不同工况下的故障识别场景适用于毕业设计、课程设计、作业或项目初期演示。资源共三十五个文件以十八个程序源码文件为主体覆盖模型训练、测试和数据读取等核心逻辑另有说明文档、配置文件和项目工程文件分别用于使用指导、环境配置与工程管理压缩包整体大小为二十三KB目录结构清晰便于分模块查阅。项目代码已测试运行成功答辩评审平均分达九十六分配套的数据集与模型可直接复现实验支持在现有域对抗网络框架上修改以适配其他数据或故障类型。已有一百四十七人学习下载适合初学者进阶也为课程答辩和方案验证提供完整参考。1. 基于迁移学习DANN模型的不同工况轴承故障诊断为什么换个工况模型就翻车在产线上训练好的轴承故障诊断模型换到另一台转速或负载不同的设备上准确率从95%掉到60%多这是很多做预测性维护的工程师都撞见过的事。原因不是模型过拟合而是工况一变振动信号的数据分布整体偏移模型之前在训练工况下学到的判别边界直接失效。基于迁移学习DANN模型的这套Python源码核心就是把这个跨工况泛化问题按住用领域对抗训练让特征提取器学到对工况不敏感、对故障敏感的特征把源域训练工况的能力平移到目标域实际工况。适合旋转机械PHM、故障诊断算法和工业智能运维方向的工程师和研究生使用源码里自带数据划分、模型定义、训练循环和文档说明整套流程可以照着改成你自己的工况对。下面我会按照源码的组织顺序把模型原理、数据加载、训练循环和几个容易踩坑的点完整拆一遍。2. DANN模型原理与迁移学习选型为什么领域对抗能压制工况差异2.1 直推式迁移学习工况差异为什么是典型的直推式问题先看一个典型场景在25600Hz采样、负载固定时采集了一批轴承振动数据标注了正常、内圈故障、外圈故障、滚子故障四类在这个数据集上训练一个卷积分类器测试准确率能到98%。模型部署到现场后负载提高、转速变化故障特征不仅幅值变了频带分布也整体偏移分类器把大量正常样本误判成故障准确率雪崩。这个问题的本质是训练集和测试集的数据分布不一致。在迁移学习的分类框架里有一个说法叫直推式迁移学习transductive transfer learning源域source domain有大量带标签数据目标域target domain有数据但基本没有标签两个域的任务相同、标签空间一致只是数据分布不同。轴承跨工况诊断正好满足这三个条件源域是实验室或历史工况目标域是现场新工况源域打好了标签目标域你通常拿不到足够多的真实标注任务还是那几类故障模式。因此直推式迁移学习是最贴切的设定它不要求目标域带标签也不需要目标域数据参与分类损失的计算——这是它和微调fine-tuning这类归纳式迁移学习的根本区别。这一设定决定了DANNDomain-Adversarial Neural Network这类无监督领域自适应方法天然适配训练时源域样本参与故障分类损失目标域样本只参与领域判别损失。故障分类器在源域上学到判别知识领域判别器负责度量两个域的特征分布差异特征提取器则在对抗过程中学会剔除工况敏感信息只保留与故障模式相关的特征。注意如果目标域本身能拿到大量真实标签直接微调往往更快且精度更高DANN更适合现场没有标注或标注成本极高的跨工况迁移场景。2.2 DANN的三网络结构与梯度反转层模型选型的核心机制DANN最早是Ganin等人在2016年发表的JMLR论文里提出的核心思路非常直接整个网络里同时做两件事——一个故障分类器负责识别故障类别一个领域判别器负责判断特征来自源域还是目标域再引入一个梯度反转层Gradient Reversal LayerGRL让特征提取器的训练方向与领域判别器相反。特征提取器提取的特征进入两个分支。故障分类分支计算分类损失这是常规的监督学习领域判别分支计算域分类损失判别器试图区分样本来自哪个域。反向传播时领域判别分支的梯度经过GRL被取反并乘以一个缩放系数λ再传给特征提取器。此时特征提取器被迫往让领域判别器分不清的方向更新——学到对工况差异不敏感、但对故障类别敏感的特征。损失函数的组合思路可以写成L L_class(x_s, y_s) - λ · L_domain(x_s, x_t)其中x_s是源域样本x_t是目标域样本y_s是源域标签。前一项保证故障分类精度后一项用负号惩罚领域可区分性。两项不断对抗最终达到一个平衡特征提取器输出的特征分布对齐了两个域同时保留了故障判别信息。从实现层面看GRL本身不改变推理过程只改变反向传播的梯度走向。我在理解这个机制时觉得最关键的体会是DANN不是在特征层面做显式的分布距离计算而是用判别器在高维特征空间里隐式地度量分布差异。这样比用MMD距离算两个域的均值差更灵活但代价是训练过程对超参数非常敏感λ调度、学习率、batch大小任何一个不对训练曲线就会乱跳。2.3 不同迁移方案对比微调、CORAL、DAN与DANN的取舍在轴承故障诊断里可选的迁移学习方案远不止DANN一种我在实际项目里至少用过四种。把选择和取舍说清楚后面对源码里参数设置的意图才能看懂。微调是最常见的基线先用源域训练一个CNN然后冻结前面几层卷积用少量目标域带标签数据微调后面层。前提是目标域必须有部分标签而且目标域标签越少过拟合风险越高。CORAL相关对齐的思路是在特征层面把源域和目标域的二阶统计量对齐说白了就是让两个域的协方差矩阵尽量一致。实现简单但它是线性对齐面对振动信号这种非线性的工况偏移效果有限。DAN深度适应网络用MK-MMD多核最大均值差异作为分布差异度量在特征空间中最小化两个域的MMD距离。优点是收敛稳定缺点是MMD只能刻画有限阶的统计信息对复杂工况变化的表达能力不如对抗方式充分。DANN用对抗训练逼近分布差异不预设统计量模式理论上能处理更复杂的域偏移但训练不稳定对λ调度非常敏感这两个问题会在第5章展开。方案是否需要目标域标签分布对齐方式训练稳定性跨工况适配性微调是少量即可参数重用监督微调依赖标签质量目标域可用但域差异大时易过拟合CORAL否协方差对齐二阶统计量稳定适合线性偏移非线性偏移不足DAN否MK-MMD距离最小化较稳定适合中等偏移工况DANN否领域判别器对抗需仔细调参适合复杂非线性偏移但参数敏感2.4 使用对抗迁移的边界什么情况下不要硬上DANNDANN不是万能的有两类场景我会直接放弃它。第一类是域差异过大比如源域是滚动轴承、目标域是滑动轴承故障机理完全不同特征空间几乎没有交集这时判别器很容易分辨两个域对抗训练会让特征提取器学出一个奇怪的中间分布两个域的故障分类都会变差。第二类是源域和目标域的标签空间不重合比如源域只有正常和早期故障两类目标域出现了一种全新的故障模式DANN无处对齐这种问题应该先从数据采集和标签体系入手。还有一个实践层面的边界当源域数据量很少比如一个工况只有几千个样本DANN会过拟合领域判别器特征提取器学不到足够丰富的故障特征。这时候用数据增强把源域样本扩起来或者退回使用CORAL这类简单方法往往效果更稳。3. Python环境与源码结构从依赖安装到PHM2012数据集加载3.1 Python环境安装与依赖锁定在Linux上跑通DANN的最小配置这套基于迁移学习DANN的源码用Python编写训练框架选的是PyTorch。先说环境我自己一般锁定Python 3.8或3.93.12以上的新版本在部分旧版依赖上会有编译兼容问题纯属给自己添堵。PyTorch用1.10到2.x之间都可以GRL的自定义autograd.Function实现不依赖框架版本特性所以不用纠结版本差异。最小依赖文件requirements.txt长这样python3.8.10 numpy1.24.3 pandas2.0.3 scipy1.10.1 scikit-learn1.3.0 matplotlib3.7.2 torch2.0.1 tqdm4.65.0安装命令就一行pip install -r requirements.txt。有GPU的机器建议额外安装对应cuda版本的torch没有GPU也用不着慌PHM2012的样本总量不算大纯CPU跑完整套DANN训练加测试最多也就是几十分钟的事。参数说明一下这些库的用途numpy和pandas负责读取和整理原始振动信号scipy做FFT变换和加窗处理scikit-learn用来计算混淆矩阵和评估指标matplotlib出T-SNE图和损失曲线torch是模型主体tqdm只是显示训练进度。熟悉的这些依赖各自的职责后面调试时定位问题会快很多。3.2 源码目录与文档说明训练入口、配置与模型保存拿到源码包之后第一件事不是急着跑train.py而是先把目录结构看清楚。一套典型的DANN轴承诊断源码目录我一般会这样组织dann_bearing/ ├── data/ │ └── phm2012/ # PHM2012原始csv与工况划分脚本 ├── docs/ │ ├── data_prep.md # 数据预处理说明 │ ├── train_config.md # 超参数配置说明 │ └── eval_result.md # 结果评估说明 ├── models/ │ ├── feature_extractor.py │ ├── classifier.py │ ├── domain_discriminator.py │ └── grl.py ├── utils/ │ ├── dataset.py # 数据读取与Dataset封装 │ ├── preprocess.py # 滑动窗口切片与FFT特征 │ └── visualization.py # T-SNE与混淆矩阵可视化 ├── train.py # 训练入口 ├── evaluate.py # 评估入口 └── requirements.txt文档说明一般会包含三个脚本的用法train.py负责加载数据和训练启动命令通常是python train.py --source_workload 420 --target_workload 600evaluate.py负责加载模型权重并输出目标域准确率train_config.md里写清每个超参数的默认值和调参范围。我强烈建议在启动训练之前把docs目录里的train_config.md读一遍尤其是source和target的指定方式、数据划分的模式、以及checkpoint保存路径。这些信息如果你不先搞清楚后面跑出来的实验对比会很混乱。3.3 PHM2012数据集与预处理滑动窗口切片和频域特征PHM2012是IEEE PHM Challenge 2012发布的轴承故障数据集包含三组不同工况下的轴承振动数据每组工况的负载、转速和采样时长不同。和CWRU西储大学数据集相比CWRU更偏故障尺寸的稳态分类PHM2012则包含从健康到故障的全生命周期数据更贴近实际设备的退化过程所以跨工况迁移实验里用的频次更高。跨工况诊断实验一般从PHM2012挑两个工况做域迁移比如把工况11800rpm作为源域工况21650rpm作为目标域故障类别按健康、内圈故障、外圈故障、滚子故障来划分。原始数据是csv格式每一行包含水平和垂直两轴的振动采样值读取方式import pandas as pd import numpy as np # PHM2012单个csv文件读取 df pd.read_csv(data/phm2012/工况1_轴承1.csv, headerNone) vib_x df.iloc[:, 0].values # 水平方向振动 fs 25600 # PHM2012统一采样率 25.6kHz参数说明PHM2012所有数据文件的采样率都是25600Hz这是这个数据集的公共设定后面算窗长、做频域特征时都得按这个基准来。我一般只保留水平方向振动的单通道因为轴承外圈故障在水平方向的激励更明显信噪比更高如果要保留双通道就在网络输入层把通道数改成2其它逻辑不用动。原始振动信号很长不能直接整段丢进网络要用滑动窗口切片。窗口长度建议取1024或2048个采样点对应40ms和80ms时长尽量覆盖轴承转频的整数个周期这样模型能看到完整的故障冲击模式。def sliding_window_slice(signal, window_size1024, stride512): 滑动窗口切片把一维振动信号切成样本矩阵 samples [] n len(signal) for start in range(0, n - window_size 1, stride): seg signal[start:start window_size] samples.append(seg) return np.array(samples) # shape: (num_samples, window_size) # 取工况1前4秒数据切成一批样本 raw vib_x[:4 * fs] samples sliding_window_slice(raw, window_size1024, stride512) print(samples.shape) # 大约 (192, 1024)逻辑说明从信号起点开始每隔stride个采样点取一段长度为window_size的数据最后得到一个二维数组每一行就是一个独立样本。stride小于window_size时相邻样本有重叠相当于数据增强样本少的时候非常管用数据量充足时可以直接让stride等于window_size来减少冗余。窗口切完之后还有一道选择题直接用时域波形作为网络输入还是先做FFT转成频域特征。我通常两个都实现用CNN做特征提取器时直接吃时域波形让卷积自己学频带特征如果用全连接网络就先把样本转成幅值谱再喂进去收敛会快很多。频域特征提取代码from scipy.fft import rfft, rfftfreq def compute_fft_spec(segment, fs25600): 对单个样本做实数FFT返回幅度谱和对应频率 n len(segment) win np.hanning(n) # 加窗抑制频谱泄漏 spectrum np.abs(rfft(segment * win)) # 幅度谱 freqs rfftfreq(n, d1/fs) return spectrum, freqs spec, freqs compute_fft_spec(samples[0]) print(spec.shape) # 1024点输入输出513维幅度谱参数和逻辑说明Hanning窗是振动信号处理的基本操作能减少非整周期截取造成的频谱泄漏rfft只输出正频率部分1024点输入对应513点幅值谱FFT之后一般不取相位因为故障诊断更关注故障特征在哪些频带上以多大能量出现相位对工况变化过于敏感、容易引入噪声。预处理完成后把源域样本和目标域样本分别封装成PyTorch的Dataset对象源域带标签目标域不带标签。注意目标域数据不要参与归一化参数的计算统一用源域统计量做标准化防止泄漏。4. 模型训练核心代码解读特征提取器、领域判别器与梯度反转4.1 特征提取器与故障分类器PyTorch网络定义DANN的骨干网络我用的是三层卷积加全连接的结构。对时域波形输入第一层卷积相当于学习一组带通滤波器第二层捕捉故障冲击的周期第三层进一步抽象高频细节。卷积核尺寸在振动信号里要比图像大一些取64、32、16这种较大的奇数这样单次卷积能覆盖一个完整的瞬时冲击宽度。import torch import torch.nn as nn class FeatureExtractor(nn.Module): 特征提取器输入时域波形输出128维特征向量 def __init__(self, input_len1024, feature_dim128): super().__init__() self.conv nn.Sequential( nn.Conv1d(1, 32, kernel_size64, stride4, padding32), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(4), nn.Conv1d(32, 64, kernel_size32, stride2, padding16), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(4), nn.Conv1d(64, 128, kernel_size16, stride2, padding8), nn.BatchNorm1d(128), nn.ReLU(), nn.AdaptiveAvgPool1d(1), ) self.fc nn.Linear(128, feature_dim) def forward(self, x): # x: (batch, 1, input_len) feat self.conv(x).squeeze(-1) # (batch, 128) return self.fc(feat) class FaultClassifier(nn.Module): 故障分类器从特征向量预测4类故障 def __init__(self, feature_dim128, num_classes4): super().__init__() self.fc nn.Sequential( nn.Linear(feature_dim, 64), nn.BatchNorm1d(64), nn.ReLU(), nn.Dropout(0.5), nn.Linear(64, num_classes) ) def forward(self, feature): return self.fc(feature)参数说明input_len1024对应前面滑动窗口设置的窗口长度feature_dim128是送入判别器和分类器之前的特征维度这个值调大能提升表征能力但也会加重对抗训练的不稳定性建议先保持128卷积层的padding和stride必须和kernel_size配合好让每一层输出尺寸能被后续池化整除否则会报尺寸不匹配的错误分类器里加了一个dropout0.5目的是在源域上控制过拟合因为DANN的特征提取器要同时服务两个域分类器太强势反而会破坏迁移效果。注意BatchNorm在DANN里是双刃剑。它能加速共享特征空间的收敛但跨域batch的统计量不一致时BN的running mean会漂移。训练时batch size不要太小至少32不然域判别器的输入统计噪声太大。4.2 领域判别器与梯度反转层对抗训练的核心实现梯度反转层是DANN最核心的代码分两步先定义一个继承autograd.Function的自定义函数forward原样返回输入backward把梯度乘负的缩放系数。class GradientReversal(Function): 梯度反转层前向恒等反向梯度取反乘lambda staticmethod def forward(ctx, x, lambda_val): ctx.lambda_val lambda_val return x.clone() staticmethod def backward(ctx, grad_output): return -ctx.lambda_val * grad_output, None def grad_reverse(x, lambda_val): return GradientReversal.apply(x, lambda_val) class DomainDiscriminator(nn.Module): 领域判别器判断特征来自源域还是目标域二分类 def __init__(self, feature_dim128): super().__init__() self.fc nn.Sequential( nn.Linear(feature_dim, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 2) # 0源域, 1目标域 ) def forward(self, feature): return self.fc(feature)逻辑说明GradientReversal.forward中把输入x克隆返回不修改数值backward中把上游传来的梯度grad_output乘以-λ之后返回给上一层。这样领域判别器接收的梯度是正常的特征提取器接收的梯度被反转歧义就出现在这两个网络之间。DomainDiscriminator输出2分类用交叉熵损失训练源域样本标为0目标域样本标为1。如果判别器很容易就分清楚两个域的样本说明特征提取器还没对齐当判别器准确率降到接近50%附近时说明特征分布已经分不开了。4.3 训练循环与关键超参数epoch、batch大小、学习率与lambda调度DANN训练最关键的是三个超参数的配合学习率、batch大小、梯度反转权重λ的调度。λ不能一开始就设大否则特征提取器早早就被领域对抗项带偏故障分类精度会崩。常见做法是让λ从0随训练进度线性增长到最大值1。import torch.optim as optim def train_dann(source_loader, target_loader, epochs50, lr1e-3, lambda_max1.0): feature_ext FeatureExtractor() classifier FaultClassifier() domain_disc DomainDiscriminator() opt_e optim.Adam(feature_ext.parameters(), lrlr) opt_c optim.Adam(classifier.parameters(), lrlr) opt_d optim.Adam(domain_disc.parameters(), lrlr) loss_cls nn.CrossEntropyLoss() loss_dom nn.CrossEntropyLoss() for epoch in range(epochs): lam lambda_max * (epoch / epochs) # 从0线性增长到最大值 feature_ext.train(); classifier.train(); domain_disc.train() for (src_x, src_y), (tgt_x, _) in zip(source_loader, target_loader): src_feat feature_ext(src_x) tgt_feat feature_ext(tgt_x) # 1) 故障分类损失只作用于源域 cls_out classifier(src_feat) loss_cls_val loss_cls(cls_out, src_y) # 2) 领域对抗损失源域目标域拼接后过梯度反转层 feat_all torch.cat([src_feat, tgt_feat], dim0) dom_labels torch.cat([ torch.zeros(src_x.size(0), dtypetorch.long), torch.ones(tgt_x.size(0), dtypetorch.long) ]) rev_feat grad_reverse(feat_all, lam) dom_out domain_disc(rev_feat) loss_dom_val loss_dom(dom_out, dom_labels) total loss_cls_val loss_dom_val opt_e.zero_grad(); opt_c.zero_grad(); opt_d.zero_grad() total.backward() opt_e.step(); opt_c.step(); opt_d.step() print(fepoch{epoch1}, lambda{lam:.2f}, cls_loss{loss_cls_val.item():.4f}, dom_loss{loss_dom_val.item():.4f})这段代码的要点说明源域batch的类别标签参与分类损失目标域batch完全没有类别标签参与。这就是第2章说过的直推式设定在代码里的具体形态。领域损失用的是拼接后的源域加目标域特征输入判别器。反向传播时判别器自身梯度正常更新相当于它不断努力区分两个域而特征提取器收到的梯度经过GRL后取反被迫逆向努力——让判别器区分失败两股力量互相拉扯最后稳定在特征分布对齐的位置。lam从0线性增长。epoch是50时前10个epoch域对抗很弱模型主要在做源域分类先建立基本的故障判别能力后面λ变大再逐步强化域对齐。这种调度比λ一开始就是1要稳得多。学习率lr我建议用1e-3或5e-4曾经用1e-2直接训练发散batch size建议64太小了BN统计量和领域判别器的噪声都会变大。训练结束后保存模型torch.save({ feature_extractor: feature_ext.state_dict(), classifier: classifier.state_dict(), domain_disc: domain_disc.state_dict(), config: {input_len: 1024, feature_dim: 128, num_classes: 4} }, checkpoints/dann_phm2012_source420_target600.pth)保存时把配置参数和模型权重放同一个字典里后面加载模型时不用手动猜参数这个习惯能省不少返工。5. 跨工况DANN训练的避坑清单数据泄漏、lambda调度、类别不平衡5.1 数据泄漏源域和目标域混在一个DataLoader里现象训练过程损失正常下降目标域测试时准确率忽然掉到和随机差不多或者反过来训练时测试精度虚高一换数据集就崩。原因做滑动窗口切片时如果一整段振动信号切成了很多短样本再按比例随机分给源域和目标域两个域的样本可能来自同一个轴承的同一运行时段前后序列高度重叠。模型记住的是样本编号规律而不是故障特征迁移效果其实是记忆泄漏。解决源域和目标域的划分必须在工况或轴承级别完成要么整批数据全进源域要么全进目标域绝不在同一段连续信号内部随机混分。划分逻辑写在数据读取阶段不写在Dataset构造之后。5.2 lambda调度和学习率配错训练直接发散现象前十几个epoch分类损失稳步下降到了第25个epoch左右准确率突然暴跌后面怎么调都回不来。原因λ随epoch线性增大但学习率没有同步调整。λ大了之后领域对抗项梯度占主导特征提取器不断改变特征分布分类器前面学到的边界作废对抗失衡了。解决把λ的增长改成前慢后快或者在λ增大到0.5之后把学习率降一个量级。我试过比较稳的组合是epoch50λmax1.0学习率1e-3起步到了epoch25降到5e-4训练曲线明显平滑很多。如果还发散检查batch里目标域样本是否太少目标域和源域batch数量不平衡时先按批次对齐。5.3 目标域类别不平衡故障样本被整体淹没现象最终目标域整体准确率还可以但看混淆矩阵某种故障类别几乎全被预测成正常。原因PHM2012里早期故障样本多外圈故障在目标域里占比小。DANN的领域对抗只对齐特征分布不保证类别分布对齐。目标域又没有标签没法做监督式纠正故障类的特征在高维空间里占的体积小分类边界自然偏向大头类别。解决在源域构造batch时做类别重采样保证每个batch里四类故障数量一致。如果目标域能拿到几十个带标签样本把它们放进去做半监督微调类别平衡会立刻改善。5.4 训练和测试阶段归一化参数不一致现象模型在源域验证集上表现很好但目标域准确率忽高忽低同一个checkpoint换一次随机种子就差十个点。原因用z-score归一化时源域和目标域振动幅值不一样。如果各自独立算均值和方差目标域样本的尺度被人为拉到和源域一致真实域偏移被预处理抹掉模型没学到应该学的东西。反之统一用源域统计量去标准化目标域又开始于幅值差距太大而失真。解决统计量一律从源域训练数据里计算得出目标域在推理时直接套用同一组scale和shift不做目标域自身的二次标准化。这样域偏移是真实暴露给对抗训练去对齐的而不是在预处理阶段被悄悄掩盖。5.5 没有设置不迁移的基线无法判断DANN的真实收益现象换一种网络结构或换一批超参数之后DANN跑得比普通CNN还差但又说不清是方法问题还是参数问题。原因实际工作中最常见的原因是对照缺失。工况差异大时DANN的提升未必在每个故障类别上都是正的没有source-only基线作为reference你看到的准确率可能只是源域分类器在目标域上的泛化残余根本没法定位是方法贡献还是运气。解决在同样的数据划分和预处理流程下先训练一个不做领域对抗的同一特征提取器加分类器得到source-only基线再用DANN跑同一批划分。最终对比基线、DANN在目标域的准确率和每个故障类别的F1分数迁移增益一目了然。这套流程我建议写成一个shell脚本一键跑完别每次手动开两个配置文件来回试。6. 用T-SNE与混淆矩阵做最终验收交叉工况验证可视化方法DANN训练完不能只看一个目标域总准确率数字。我的验收流程分三步先看目标域整体准确率再出混淆矩阵排查具体故障类别的偏向最后把特征提取器输出的高维特征降维到二维用T-SNE图直观确认两个域的特征分布是否真的对齐了。T-SNE可视化代码import matplotlib.pyplot as plt import numpy as np from sklearn.manifold import TSNE tsne TSNE(n_components2, perplexity30, random_state42) feat_2d tsne.fit_transform(all_target_feats) plt.figure(figsize(8, 6)) for cls in range(4): idx np.array(all_target_labels) cls plt.scatter(feat_2d[idx, 0], feat_2d[idx, 1], s5, labelffault_{cls}) plt.legend() plt.title(Target domain features after DANN (t-SNE)) plt.savefig(results/target_tsne.png, dpi200)其中all_target_feats是目标域所有样本过特征提取器后拼接得到的特征矩阵all_target_labels是对应的故障类别标签这两个变量在evaluate.py里会先计算好。如果DANN对齐效果好图中会看到四个明显的颜色簇如果四个簇混成一团说明域分布没拉开优先回头查第5章的λ调度、学习率和归一化设置。perplexity这个参数在样本量只有几百时建议调到15到20样本量上千再设30否则小样本的分布图会失真。混淆矩阵用来排查类别偏向from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay cm confusion_matrix(all_target_labels, all_target_preds) ConfusionMatrixDisplay(cm, display_labels[normal, inner, outer, roller]).plot() plt.savefig(results/target_confusion.png, dpi200)如果对角线上某个值明显偏低比如外圈故障大量被预测成正常说明这个类别在目标域里样本占比太少回到第5章的类别重采样去做源域数据补充。最后一步是交叉工况验证把PHM2012三个工况两两组合做三个方向的迁移实验工况1到工况2、工况1到工况3、工况2到工况3三个方向的准确率取均值和标准差作为最终指标而不是只报最好的一次迁移结果。DANN对方向非常敏感不同工况对的难度差异很大单一方向的结果很容易被数据本身特征误导三个方向平均才有说服力。我自己做轴承跨工况诊断时最深的体会是DANN不是装上就能跑的模型它对数据划分、λ调度、学习率、归一化的一致性都极其敏感。同一套代码换了滑动窗口长度和预处理方式结果可能完全两极分化。这也是为什么第5章花了这么大篇幅写避坑——那些坑基本都是我自己踩过的翻过车之后才明白迁移学习的迁移两个字远远不只是换数据集再训练一次那么简单。希望你拿到这套源码后先跑通基线再上DANN用数据说服自己也说服现场的工程师。希望帮到你。本文还有配套的精品资源点击获取
返回列表