机械设备故障诊断这个领域,做久了都会碰到同一个头疼事:现场采集的数据量倒是不少,但真正带标签的数据少得可怜。每次项目进场,光是筛出有效标签就得耗掉大量时间,而轴承、齿轮箱这类旋转机械一旦出问题,往往是非线性的微弱信号,纯靠人工特征工程和传统机器学习,不仅费劲,还容易漏掉早期故障的蛛丝马迹。我自己在多个工业现场项目里反复折腾这些数据之后,慢慢摸索出一套结合标签传播策略(Label Propagation Strategy,LPS)与动态图注意力网络(Dynamic Graph Attention Transformer,DGAT)的半监督故障诊断方法,实测下来既不用手动标注海量样本,又能从无标签数据里挖出不少隐藏规律。这篇内容就把这套方法的思路、实现细节、参数选择和踩过的坑完整整理出来,供做故障诊断、工业AI落地的朋友参考。
1. 方法设计的整体思路拆解
1.1 从有标签数据稀缺这个真实痛点说起
工业现场的故障诊断任务,和实验室理想条件差别很大。实验室里可以反复模拟故障、精确标注,但到了实际产线,故障样本往往是零星的、偶然的,而且不同工况、不同负载下的信号形态差异非常大。我接触过的几个项目里,有标签的轴承故障样本可能只占全部采集数据的百分之五不到,剩下全是正常运行的连续监测数据。
这种情况下,有两个常见选择:一是用传统无监督方法做异常检测,简单直接但难以区分具体故障类型;二是硬着头皮做人工标注,找老师傅逐段看波形,效率低不说,不同人的标注标准还不一致。半监督学习的价值就在这个位置体现出来了:利用一小部分高质量标签,配合大量无标签数据,让模型同时学习“形状特征”和“分布规律”,最终在故障分类上的表现可以接近甚至超过全监督方案。
我选择半监督路线还有一层考虑。工业数据里很多故障是渐进式发展的,比如轴承磨损,初期信号特征非常微弱,单纯靠人工去看频谱图很难捕捉。无标签数据恰好提供了更丰富的工况覆盖,模型通过一致性约束、伪标签迭代等手段,可以在特征空间里自动发现这些微妙的迁移模式。
1.2 LPS和DGAT分别解决什么问题
这套方法名字里的两个关键词,各自承担不同职责。
LPS是一套标签传播策略。它的核心思路是:在特征空间中,距离近的样本大概率属于同一类别。算法迭代地从少量有标签样本出发,利用相似度矩阵把标签逐步传播到邻近的无标签样本上,筛选出高置信度的伪标签,再让模型去学习这些伪标签。整个过程类似“滚雪球”,每轮都扩大有效训练集。
DGAT则是特征提取和分类的主干网络。和传统CNN不同,DGAT将传感器通道、时间窗口处理成图结构,用图注意力机制动态建模通道之间的关联关系。我之所以说“动态”,是因为不同故障状态下,通道间相关性结构会发生变化。例如正常状态时电流与振动的相关性较弱,而出现轴承故障时两者可能出现明显的耦合特征。静态图构造无法捕捉这种变化,动态图结构则可以在训练过程中学习更新节点间连边的权重。
从总体结构上看,LPS负责解决“标签不够”的问题,DGAT负责解决“特征表达不充分”的问题。两者串起来的效果是:少量标签启动模型训练,模型提取高质量特征,LPS在特征空间里传播标签,反过来生成更多伪标签,再喂给DGAT继续训练。每一轮迭代,模型的特征表达和标签质量都在提升。
1.3 为什么不用全监督CNN或普通图神经网络
在选定这套方案之前,我对比过几种常见路线。纯CNN如果数据量足够、标签充足,表现确实不错,但它在工业小样本场景下很容易过拟合。尤其当故障类型十几种、每种样本只有几十个时,CNN学到的更多是噪声而不是本质特征。普通图神经网络(GNN)虽然可以利用拓扑结构,但绝大多数实现用的是预定义邻接矩阵,一旦图结构构造不准确,反而会引入误导信息。
DGAT的“动态”特性恰好补上了这两个短板。一方面,注意力机制让模型自己学习哪些通道、哪些时间窗口之间的关联是重要的,不需要人工设计复杂的邻接矩阵;另一方面,半监督框架下模型可以利用无标签样本不断调整注意力权重,特征空间的流形结构也被纳入学习过程,减少了过拟合风险。
2. 核心模块解析与实操要点
2.1 数据预处理和图的构造方式
很多人一上来就直接把一维振动信号送进模型,这个做法我试过,效果很一般。工业监测数据往往包含多个传感器通道,比如加速度计、速度传感器、电流信号,每个通道的特性不同。直接拼接不仅维度爆炸,而且丢失了通道间的相关结构。我采用的预处理流程是:
- 等长分窗:以每1024个采样点为窗口,覆盖大约一个完整旋转周期的时长,不留重叠或者少量重叠。
- 多通道对齐:将同一时间段的振动、电流、温度等通道对齐,形成矩阵。
- 时频切片:对每个通道做短时傅里叶变换(STFT),保留幅值谱,形成时间×频率的二维表达。
- 图节点定义:将每个窗口内的时间片段视为图节点,或者将传感器通道视为图节点,视具体任务而定。我更推荐后者,因为不同传感器本身就是某种意义上的“视图”,注意力机制可以在节点间学习跨通道融合方式。
- 边的初始化:初始邻接矩阵用通道间皮尔逊相关系数填充,作为先验,后续由注意力机制动态更新。
需要特别注意,图结构的构造方式直接影响模型下限。如果初始化边权过于随意(比如全连接),模型注意力学习压力会非常大;如果过于稀疏,又可能丢失有效连接。我通常的做法是保留相关性最强的Top-5连接,其余置零,再让注意力机制在稀疏图上学习变权。
2.2 DGAT中的动态注意力机制
DGAT的动态性,关键在于它不只是计算一次静态注意力,而是在每个图卷积层都更新节点特征,同时根据节点特征重估边的重要性。这个机制类似于人在看图时会先粗扫一遍,再聚焦关键区域,经过多层迭代后,模型对“哪些通道组合最具有判别力”会有更精准的判断。
具体实现上,我使用了多头注意力,头数设为4。每一层的计算过程是:先计算每个节点与邻居节点的注意力系数,然后对邻居特征做加权聚合,再经过一层前馈网络增加非线性表达。和原始Transformer不同,这里没有位置编码,因为图结构本身已经包含了相对位置关系。
训练时有两个细节容易踩坑。第一,注意力计算时的温度参数对结果影响很大。温度太高,注意力分布趋于均匀,失去“聚焦”作用;温度太低,训练初期梯度不稳定。我在代码里把它初始化为1.0,并在前20个epoch内逐步调整到0.8。第二,动态图更新不能太频繁,否则模型会陷入震荡。我一般每5个epoch更新一次邻接矩阵,而不是每个batch都更新。
2.3 LPS标签传播策略的迭代细节
LPS是整个方法中最需要“手感”的部分,因为它直接控制伪标签的质量,而伪标签错了,后面就是错误累积。我的LPS实现分为三个核心步骤:
第一步,自监督特征提取。用当前DGAT提取所有样本的特征向量,然后做L2归一化,消除幅值差异。
第二步,相似度传播。计算无标签样本与所有有标签样本之间的余弦相似度,选择相似度最高的有标签类别作为候选标签,同时记录置信度分数。
第三步,伪标签筛选。不是所有高相似度样本都进入训练集,我设置了双重门槛:置信度阈值和传播轮数。以置信度阈值为0.85作为初始门槛,每轮传播后下降0.02,最低到0.75;同时限制每轮新增伪标签数量不超过总无标签样本的10%,保证模型的更新不会因为单轮噪声过大而跳动。
如果某类故障样本始终没有高置信度邻居,说明特征空间里这类样本本身就比较孤立,强行传播反而有害。我的处理方式是保留这类样本为“不确定集”,等后续模型效果提升后再回扫一遍。实际操作中,这个方法稳定性很好,能明显减少伪标签噪声。
2.4 损失函数和训练策略
半监督故障诊断的损失函数不能简单用交叉熵。我使用的是三部分损失组合:
- 有标签样本的监督损失:标准交叉熵,但做了类别加权。因为故障类型往往不平衡,轴承正常样本可能占60%,而外圈故障只占5%。
- 无标签样本的一致性损失:对无标签样本做轻微数据增强(加噪声、时间偏移),要求模型对增强前后的预测分布保持一致。这是从半监督图像分类迁移过来的思想。
- 伪标签损失:对LPS产生的高置信度伪标签样本,使用带权重的交叉熵,权重与该样本的置信度成正比。
训练策略上,我推荐两阶段法。第一阶段只用有标签样本预训练DGAT,大约20个epoch,目标是把特征空间拉开,让同类样本先聚拢。第二阶段开启LPS和一致性损失,冻结图更新参数10个epoch,再解冻联调。这样能显著改善模型初期的稳定性。
3. 实操过程与核心环节实现
3.1 数据准备与实验配置
我拿一个模拟实验数据来说明完整流程,用的是某型号齿轮箱的振动加速度数据,采样频率12.8kHz,一共8个通道,包含正常、齿轮磨损、轴承内圈故障、轴承外圈故障四种状态。数据总量约10万秒,其中有标签数据只有8000秒,占比8%,剩下全是无标签数据。
窗口长度取1024点,对应0.08秒,每个状态样本约2.3万条。训练集、验证集、测试集的划分严格按照时间顺序,避免同一连续时间段既进训练又进测试,防止信息泄露。无标签数据参与训练,测试集只用来评估最终效果。
预处理后,每个样本的形状是8通道×32×32的时频图,之所以选32×32,是兼顾计算效率和频域分辨率。太小会丢失低频故障特征,太大会增加注意力计算开销。这个尺寸在RTX级别显卡上的训练时间比较合适。
3.2 模型搭建关键代码片段
下面这段是DGAT核心模块的简版实现,融合了动态邻接矩阵更新和注意力聚合逻辑:
import torch import torch.nn as nn import torch.nn.functional as F class DynamicGraphAttentionLayer(nn.Module): def __init__(self, in_features, out_features, n_heads=4): super().__init__() self.n_heads = n_heads self.W = nn.Linear(in_features, out_features * n_heads) self.a = nn.Parameter(torch.zeros(n_heads, out_features * 2)) nn.init.xavier_uniform_(self.a) def forward(self, x, adj): # x: [B, N, F],B是batch,N是节点数,F是特征维度 h = self.W(x) # [B, N, heads*out] h = h.view(h.size(0), h.size(1), self.n_heads, -1) h = h.transpose(1, 2) # [B, heads, N, out] B, heads, N, out = h.shape # 计算注意力系数 h_i = h.unsqueeze(3).expand(B, heads, N, N, out) h_j = h.unsqueeze(2).expand(B, heads, N, N, out) attn_input = torch.cat([h_i, h_j], dim=-1) attn_score = torch.einsum('bhijk,k->bhij', attn_input, self.a) # 简化的注意力打分 attn_score = F.leaky_relu(attn_score, negative_slope=0.2) # 动态掩码:结合初始邻接矩阵,更新边权重 mask = (adj > 0).unsqueeze(1).unsqueeze(1) # [B, 1, N, N] attn_score = attn_score.masked_fill(~mask, float('-1e9')) attn_weight = F.softmax(attn_score, dim=-1) out = torch.einsum('bhij,bhjk->bhi', attn_weight, h.contiguous()) out = out.transpose(1, 2).contiguous().view(B, N, -1) return out注意代码里的mask操作,它的作用是把初始邻接矩阵为0的位置强制掩蔽,模型只能在初始化连通的通道组合中重新分配注意力权重。如果你希望模型学习完全新的连边,可以放宽这个约束,比如设置50%的概率允许新建边,但实验证明完全自由的情况下模型更容易过拟合。
3.3 LPS伪标签迭代实现
LPS这一步相比模型搭建,更需要注意工程细节。我实现的核心算法流程如下:
def label_propagation(features, labeled_mask, label_matrix, threshold=0.85, min_confidence=0.75): """ features: 归一化后的特征向量 [N, D] labeled_mask: [N] bool,标记有标签样本 label_matrix: [N, C] one-hot标签 """ # 计算余弦相似度矩阵 sim_matrix = torch.mm(features, features.T) # 已归一化,等价于余弦相似度 # 只保留到有标签样本的相似度 labeled_idx = torch.nonzero(labeled_mask).squeeze() if labeled_idx.dim() == 0: labeled_idx = labeled_idx.unsqueeze(0) for unlabeled_idx in torch.nonzero(~labeled_mask).squeeze(1): sim_row = sim_matrix[unlabeled_idx, labeled_idx] max_sim, idx = sim_row.max(dim=0) if max_sim < threshold: continue pred_label = label_matrix[labeled_idx[idx]] confidence = float(max_sim) # 这里可以加入置信度衰减逻辑,每轮迭代降低灵敏度 if confidence >= min_confidence: pseudo_label_matrix[unlabeled_idx] = pred_label pseudo_conf[unlabeled_idx] = confidence pseudo_mask[unlabeled_idx] = True return pseudo_mask, pseudo_label_matrix, pseudo_conf这段代码只是个基础版,实际使用时我还会加上一条限制:如果间接邻居中有超过三个高置信度同类样本,才能确认该样本的伪标签。这样能进一步减少边界样本误标。
关于置信度阈值,我做了多轮实验。阈值定在0.85,准确率最高但召回率偏低;降到0.75,召回率提升但准确率出现了明显下滑,噪声样本增多导致模型F1分数反而下降。最后折中到0.80,配合每10轮提升一个点的调整策略,效果最优。这个参数跟你数据的信噪比强相关,建议小批量预实验时多跑几个候选值。
3.4 训练参数和效果对比
完整的训练参考配置如下:
| 参数 | 数值 | 说明 |
|---|---|---|
| 优化器 | AdamW | 权重衰减设为1e-4 |
| 初始学习率 | 1e-3 | 每20个epoch衰减50% |
| Batch Size | 64 | 其中至少含8个有标签样本 |
| 图注意力头数 | 4 | 输出维度拼接后为256 |
| 图卷积层数 | 3 | 再加1层全连接分类头 |
| 一致性损失系数 | 0.5 | 随训练线性增加到1.0 |
| 伪标签损失系数 | 0.3 | 固定比例 |
| 训练总epoch | 80 | 早停耐心值15 |
在这个模拟数据上,最终测试集准确率约为91.5%,F1加权分数约为90.2%。作为对比,只用有标签样本训练的纯监督CNN模型,同样条件下准确率只有78.3%,甚至训练集上还出现了明显过拟合。单纯把CNN换成GAT但不用LPS,准确率提升到83.1%,证明结构上的收益是真实的。加上LPS之后,准确率再跳到91.5%,说明标签传播策略确实把无标签数据的价值挖掘出来了。
4. 常见问题与排查技巧实录
4.1 伪标签噪声不断累积导致模型漂移
这是半监督方法最经典的问题,也是我在实验中最头疼的。表现是训练初期指标上升正常,到了后期无标签样本加入越多,反而准确率开始下滑。究其原因,有些样本被赋予了错误伪标签,模型坚持学习这些噪声,渐渐把决策边界带偏。
排查思路不复杂,最直接的办法是在训练过程中把伪标签准确率记录下来,按轮次画曲线。如果发现准确率曲线在第某个epoch后开始下降,说明伪标签质量在恶化。我的处理手段有三个:一是强制加入“丢弃策略”,即每隔几个epoch随机丢弃置信度排名靠后的伪标签样本,让模型重新消化;二是增加一种“伪标签修正”逻辑,即模型对伪标签样本的预测出现变化时,以模型当前预测为准,重新评估该样本是否保留;三是对训练损失做熵正则化,抑制模型对低置信度样本的过度自信。
在这个问题上,我踩过最深的坑是一味扩大伪标签规模。有段时间为了让训练集变大,我把置信度阈值降到0.6,结果模型准确率从88%一路掉到75%。教训是:宁可少加,不要乱加。伪标签的质量远比数量重要。
4.2 动态图结构更新导致训练震荡
动态图的核心卖点是自适应学习通道关联,但在实际训练中,如果边权更新太激进,损失曲线会出现明显波动,甚至不收敛。我遇到过一次异常情况,损失值从0.2直接跳到2.5,排查发现是注意力权重过度集中在某些边,其他节点梯度被淹没。
解决办法是给图更新增加约束:一是引入边权更新的L2惩罚项,让邻接矩阵的变化幅度不要太大;二是对相邻两次更新求余弦相似度,如果相似度低于某个阈值,说明结构跳跃过大,这种情况下可以跳过本次更新,保留之前的图结构。我一般用这两条叠加之后,训练过程平稳了很多。
另外我建议图更新频率与学习率联动。学习率大时,特征变化剧烈,此时更新图结构容易引入无效信息。把图更新频率放在学习率衰减到峰值之后,再每10个epoch更新一次,稳定性明显改善。
4.3 类别不平衡导致的标签传播偏向多数类
工业数据里,正常样本数量远大于故障样本,这是常态。但LPS基于相似度传播,本质上更容易找出与多数类相似的样本,结果就是伪标签严重偏向正常类,少数类故障几乎拿不到新样本。这个问题如果不处理,模型最后的分类结果会是“偏科生”,绝大多数类准确率高,少数类一塌糊涂。
我尝试过几种解决手段。最有效的是对特征空间做类中心对齐:每次迭代时,计算各类有标签样本的特征中心,然后对无标签样本的特征做吸引/排斥约束,推动少数类样本的特征向同类中心靠拢,同时远离多数类中心。这种做法相当于在LPS前面加了一个“形状修正”环节。
另一个很实用的技巧是类别感知采样:构造每个训练batch时,强制包含一定比例少数类样本,哪怕这些样本是伪标签或不确定样本。这样模型不会因为多数类的梯度占比太大而忽视了少数类的学习。
4.4 跨工况数据分布漂移问题
工业现场的工况经常变化,不同转速、不同负载下的信号分布差异很大。模型在A工况下训练,直接用到B工况时准确率往往下降明显。这在半监督框架里更容易被忽略,因为没有标签校验,模型可能在新工况数据上学到的是与任务无关的分布特征。
我建议引入领域自适应里常用的思路,在DGAT的特征提取端加上域判别器,不过不用对抗那么复杂,更简单的方法是加一层批归一化统计量修正:先统计源域(比如正常工况)的特征均值和方差,对目标域(新工况)特征做适配。结合LPS的迭代,模型可以在特征空间里逐步对齐两种工况的分布。
这个方法我在另一个项目上做过验证,跨工况测试准确率能从65%提升到82%,虽然还没达到源域内测试的水平,但已经能够满足故障预警场景的需求了。后续如果再结合一些多源域信息,比如不同设备型号的监测数据,效果还能进一步优化。
5. 效果评估与工程化落地的进一步思考
5.1 比准确率更重要的几个评估维度
故障诊断场景下,单纯盯着准确率很容易跑偏。比如一个故障占比5%的数据集,模型全部预测正常也能有95%准确率,但这个模型没有任何实用价值。我评估这套方法时,重点看三个指标:各类别的召回率、误报率、以及早期故障的检出延迟。
召回率方面,重点观察少数类故障的召回是否随伪标签迭代逐步提升。我记录过一套完整数据:训练前外圈故障召回率是71%,加入LPS五个迭代后提升到84%,说明方法确实帮到了少数类。误报率则是看正常样本被误判为故障的比例,实际应用中误报太多会导致维护团队“狼来了”效应,慢慢就不再信任系统了。早期故障检出延迟更实际,我在轴承渐进退化数据集上测试,模型能够比人工巡检提前约2~3天判断出异常趋势。
还有一个容易忽略的维度:解释性。工业现场的老师傅不会轻易相信一个黑盒模型的判断。我基于DGAT的注意力权重做了一定的可视化,提取出“模型主要关注的是哪几个传感器通道以及哪个频段的特征”,再结合故障机理去验证。比如内圈故障时注意力主要集中振动通道的某个高频段,这跟理论上的故障特征频率是对得上的,就能大大增加一线人员对模型结果的信任度。
5.2 模型压缩与部署硬件适配
这套模型初始版本参数量比较大,如果直接部署到现场边缘设备并不现实。我一步步做剪枝和量化,把模型参数量降低到原来的约四分之一。具体操作是:对多头注意力输出做通道剪枝,去掉对最终分类贡献度最低的20%通道;然后对激活函数做8位整型量化;最后把图更新部分的算子替换为深度可分离卷积实现。
部署硬件我用过工控机和轻量级边缘盒子,前向推理时间可以控制在20毫秒以内,满足实时监测需求。模型文件压缩到约15MB,能够内嵌到现有的PLC数据采集系统中。不过这里要提醒,伪标签迭代和训练图更新的代码不要直接带上线设备,因为那是训练阶段的工作,部署模型只需要前向推理图已经固定的DGAT。
5.3 如何迁移到不同的故障诊断场景
这套方法的可迁移性是我选择它的另一个原因。换一种设备、换一种故障类型,不需要从头设计算法,只需要替换预处理和目标类别即可。我迁移过齿轮箱点蚀、电机转子偏心、液压泵磨损等场景,整体流程基本不动。
迁移过程中的主要调整是图节点的定义和第二阶段的预训练策略。比如齿轮箱的传感器点位相对固定,可以沿用通道间图结构;但液压泵的传感器布置差异较大,需要重新计算初始邻接矩阵。无标签数据的比例对最终效果影响也很大,我做过一个对比实验:标签占比从2%到20%变化时,模型准确率从69%提升到91%,其中标签占比5%以上时准确率提升速度最快,这是LPS进入良性循环的阈值线。
5.4 数据闭环与模型持续更新
最后想讨论的是模型上线之后的持续学习问题。故障诊断模型不是在实验室训练完就能一劳永逸的。新工况、新设备型号、新的故障模式都会让模型逐渐失效。我在方案里增加了一个简单的数据闭环机制:边缘端持续采样并保存特征,周期性上传到中心服务器,服务器用LPS对新增无标签数据做伪标签扩展,然后触发小幅度增量训练,更新后的模型再下发到端侧。
这个闭环跑了一段时候后发现,伪标签筛选门槛可以在线自适应:当某段时间内伪标签准确率持续走高时,就稍微扩大筛选范围;反之则收紧。这样既避免了人工定期调参,又保持了模型对新工况的适应力。这套方法的完整链路也逐步沉淀成了一套内部工具,后续凡是遇到类似的半监督故障诊断项目,我都能直接复用这套流程,节省大量重复开发的时间。