面向读者:长期从事传统工业软件(PLC/SCADA/DCS)的工程师,希望理解AI模型在工业场景中的实际意义和应用方式。

阅读收获:理解Transformer模型的核心原理,能读懂、运行、修改一段完整的PyTorch故障检测代码。

环境要求:Python 3.8+,PyTorch 2.0+,Matplotlib。不需要GPU,普通笔记本即可运行。


一、写给传统工程师的AI入门

1.1 你已经懂了50%的AI

如果你做过以下任何一件事情,你已经理解了AI的核心思想:

  • 在PLC里写 IF 传感器电压 > 4.0V THEN 触发报警
  • 在SCADA趋势图上画一条红线,超出就报警
  • 用振动分析软件设定阈值,超限就停机

这些做法的本质完全一样:输入数据 → 按规则判断 → 输出结论

AI做的事情也一样。唯一的区别是:规则怎么来

方式规则来源举例
传统方法工程师根据自己的经验手动编写if voltage > 4.0: alarm()
统计方法从历史数据中计算统计量if value > mean + 3*sigma: alarm()
AI方法机器从大量标注数据中自动学习模型看了9000个案例后自己总结出判别规则

1.2 一个类比帮你快速理解

传统工程师像一位经验丰富的老师傅:他见过几百台设备的运行数据,知道"电压超过4V大概率是故障",于是把这个经验写成规则。

AI模型像一个勤奋的新手徒弟:他一开始什么都不懂(参数全是随机数),但他愿意反复看9000个历史案例(训练数据),每次做错就记下教训(反向传播),看多了就总结出了自己的判别规则。

徒弟和老师傅的关键区别:

  • 老师傅的规则是显式的:你可以直接看代码理解为什么报故障
  • 徒弟的规则是隐式的:判断依据分散在17000多个参数中,无法直接阅读

但徒弟有一个优势:他能发现老师傅看不到的复杂模式——比如"第5个点和第12个点同时偏高,即使都没超过4V,也可能是故障"。这种多变量关联模式,人工规则极难穷举。

1.3 本文要做什么

我们将用一段纯PyTorch代码,从零构建一个基于Transformer的传感器故障检测模型。代码覆盖完整流程:

合成传感器数据

构建Transformer模型

训练 + 早停

测试集评估

交互式推理

不需要GPU,不需要云服务器,不需要任何深度学习框架以外的依赖。 你的办公笔记本就能跑通全部代码。


二、代码要解决什么问题

2.1 工业场景

一台工业电机上安装了电压传感器,每秒采集16个连续读数,形成一个时序序列

[1.8V, 2.1V, 2.5V, 1.9V, 2.2V, 1.5V, 2.8V, 2.3V,
 1.9V, 2.0V, 1.4V, 2.6V, 1.7V, 2.2V, 1.3V, 2.4V]

需要判断:设备运行正常(标签0)还是存在故障异常(标签1)

2.2 传统方法的困境

固定阈值法 if voltage > 4.0: alarm() 有三个致命缺陷:

  1. 无法识别渐变故障:如果电压从2V缓慢升到3.8V(绝缘老化),每个时刻都没超阈值,但整体趋势已经异常
  2. 误报临界工况:设备满载运行时电压3.5V是正常的,但接近阈值容易误报
  3. 忽略时序关联:只看单个数值,不考虑"哪些位置同时异常"

统计方法(均值±3倍标准差)有改进,但仍假设数据服从高斯分布,且只能检测"偏离统计分布"的单点异常,无法捕获多点协同的时序模式

2.3 AI的思路

不写规则,直接给模型看大量已标注的历史案例。模型从案例中自动学习判别模式:

  • 不仅看单点数值大小,还看16个时间步之间的关联关系
  • 能识别"第5位突增+第12位突增"这种需要多变量协同才能发现的故障模式
  • 能识别"后半段持续升高"这种渐变趋势,无需任何人工阈值

三、数据从哪来——模拟真实传感器信号

3.1 真实传感器信号的构成

真实工业传感器的读数不是纯随机数,而是多个物理因素叠加的结果:

信号 = 基线电压 + 设备老化漂移 + 工况周期波动 + 电磁随机噪声

我们的代码用数学公式模拟了这四个成分:

成分代码实现物理含义
基线电压baseline = 2.0 + 0.4 * randn()不同设备个体的基准差异
线性漂移trend = 0.3 * (t - 0.5)设备缓慢老化导致的趋势变化
周期波动periodic = 0.25 * sin(2π*t*2)电机旋转等周期性工况
高斯噪声noise = randn() * 0.12电磁干扰等随机误差

关键设计:用高斯噪声(randn)而非均匀噪声(uniform,因为真实传感器的随机误差更接近正态分布。

3.2 六类工况波形

代码生成6类波形(3类正常+3类故障),覆盖多种实际工况:

波形类型标签对应真实工况设计目的
标准正常0设备正常运行基准参照
临界高值正常0满载高负载运行让模型不把"高值"等同于"故障"
低幅波动正常0低功耗待机模式让模型适应不同工况的基线差异
标准故障1瞬时短路/电磁冲击正常波形+随机3个点位突发尖峰
弱故障1早期异常征兆仅1个点位轻微超标,增加检测难度
渐变故障1设备过热/绝缘老化后半段数值持续升高,无法用单点阈值检测

每类波形对应的核心代码:

# 标准正常:基线 + 趋势 + 周期 + 噪声
baseline = 2.0 + 0.4 * torch.randn(1).item()          # 设备个体差异
trend = 0.3 * (t - 0.5)                                # 缓慢漂移
periodic = 0.25 * torch.sin(2 * math.pi * t * 2)      # 工况周期
noise = torch.randn(SEQ_LEN) * 0.12                     # 电磁干扰
normal_sequence = baseline + trend + periodic + noise

# 标准故障:在正常波形基础上,随机3个位置叠加突发尖峰
fault_pos = torch.randint(0, SEQ_LEN, (3,))
fault_seq[fault_pos] += torch.FloatTensor(3).uniform_(2.5, 4.0)

# 渐变故障:后半段叠加线性上升
gradual_fault[SEQ_LEN // 2:] += torch.linspace(1.0, 2.5, SEQ_LEN // 2)

3.3 波形可视化

下面是代码自动生成的6类波形示意图:

在这里插入图片描述

可以看到:正常波形(青色)在阈值线以下波动,故障波形(红色)出现了明显的尖峰或持续上升趋势。但临界高值正常虽然整体偏高却没有超过4V——这正是模型需要学习的"边界判别能力"。

3.4 数据集生成结果

运行代码后,控制台输出:

【数据集初始化中...】基础样本组数 1500 组(每组生成6条样本)
数据集生成进度:已完成 100/1500 组基础样本配套波形
...
数据集生成进度:已完成 1500/1500 组基础样本配套波形
【数据集生成完成】总样本数量:9000 条
包含6类波形:标准正常、标准故障、弱故障、渐变故障、临界高值正常、低幅波动正常
信号构成:基线偏移 + 线性趋势 + 周期正弦波 + 高斯随机噪声

四、Transformer模型——把电压变成"判断"

这是本文的核心章节。Transformer模型把16个浮点电压值,经过5层变换,最终输出"正常/故障"的判定。

4.1 整体数据流

输入层
16个电压值
形状: [batch, 16]

线性投影
nn.Linear(1→32)
形状: [batch, 16, 32]

位置编码
PositionalEncoding
形状: [batch, 16, 32]

Transformer编码器
2层 × 4头注意力
形状: [batch, 16, 32]

平均池化
torch.mean
形状: [batch, 32]

分类输出
nn.Linear(32→2)
形状: [batch, 2]

4.2 第1步:线性投影——为什么不用Embedding?

self.input_proj = nn.Linear(1, EMBED_DIM)  # 将每个标量映射为32维向量

问题:PyTorch处理文本时通常用 nn.Embedding,把每个词当作一个离散ID查表。如果对传感器数值也这样做——把2.3V查表为ID=23,把2.4V查表为ID=24——这两个几乎等价的电压值会被当作两个完全不同的"单词"

解决方案:用 nn.Linear(1, 32) 做线性投影。Linear是连续映射——输入值相近,输出向量也相近。2.3V和2.4V的投影向量会非常相似,这符合传感器数据的物理本质。

类比:

  • Embedding 像查字典——离散查表,2.3和2.4是两个完全不同的词条
  • Linear 像温度计刻度——连续映射,30°C和31°C的读数非常接近

代码中通过 unsqueeze(-1) 把一维标量扩展为特征维度,再送入Linear:

x = x.unsqueeze(-1)       # [batch, 16] → [batch, 16, 1]
x = self.input_proj(x)    # [batch, 16, 1] → [batch, 16, 32]

4.3 第2步:位置编码——为什么必须告诉模型"时间"?

Transformer的自注意力机制有一个关键特性:位置无关。把16个电压值打乱顺序再输入,输出不会变。但传感器数据的核心价值恰恰在于时序顺序——第3个采样点异常和第13个采样点异常,含义完全不同。

正弦位置编码为每个时间步注入一个唯一的"位置指纹":

pe[:, 0::2] = torch.sin(position * div_term)   # 偶数维度用sin
pe[:, 1::2] = torch.cos(position * div_term)   # 奇数维度用cos

数学直觉:不同频率的三角函数组合,使得每个位置都有一个唯一的编码向量。而且三角函数的相位差天然编码了"距离信息"——模型能隐式学到"第5步和第8步相隔3步"这个相对位置关系。

# pe形状 [1, max_len, d_model] 与 x [batch, seq_len, d_model] 相加
# 通过Broadcasting自动扩展到batch_size
x = x + self.pe[:, :x.size(1), :]

4.4 第3步:Transformer编码器——模型"在看什么"

encoder_layer = nn.TransformerEncoderLayer(
    d_model=EMBED_DIM, nhead=N_HEAD,
    dim_feedforward=FFN_HIDDEN,
    batch_first=True, dropout=DROPOUT
)
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=LAYERS)

这是模型最核心的部分。多头自注意力的直观理解:

想象16个传感器读数排成一排,每个读数都要"看"其他所有读数,判断"谁和自己有关"。注意力机制为每一对位置计算一个相关性权重

4个注意力头可以同时关注不同的模式维度:

关注的模式类比
头1局部突变——哪个位置突然偏离了周围值“有没有突然跳一下?”
头2全局趋势——整体是在上升还是下降“整体趋势正常吗?”
头3周期性异常——周期波动是否被打破“工况循环有干扰吗?”
头4多点关联——多个异常位置是否同时出现“好几个位置都不对劲?”

"故障共识"机制:当输入是故障样本时(比如第5、9、13位有尖峰),注意力矩阵会呈现这样的模式——异常位置之间互相建立强注意力连接。模型学到的规则是:单个高值可能是噪声,但多个高值同时出现极大概率是故障

每层Transformer内部还包含一个前馈网络(FFN):对注意力提取的特征做非线性变换,把线性不可分的模式映射到线性可分空间——相当于在特征空间中"扭曲"数据分布,让分类边界更容易划定。

2层堆叠的设计:第一层提取局部模式(单点突变),第二层在第一层的基础上提取更全局的模式(多点关联)。

4.4+ 模型内部状态可视化

下面这张图展示了模型收敛后,面对故障样本正常样本时,内部各层的状态差异:

在这里插入图片描述

故障样本(左列)

  • 输入层:第5、9、13位出现明显尖峰(红色柱)
  • 注意力层:异常位置之间形成强注意力连接(深紫色区域),实现"故障共识"
  • 特征激活:"高电压检测"和"突变检测"维度被显著激活(>0.6)
  • 分类输出:故障得分2.87远高于正常得分0.23 → 判定为故障

正常样本(右列)

  • 输入层:数值平稳波动,无突变
  • 注意力层:注意力均匀分布,无明显聚焦点
  • 特征激活:"中电压基准"和"周期性波动"维度主导
  • 分类输出:正常得分高于故障得分 → 判定为正常

4.5 第4步+第5步:平均池化 + 分类输出

# 平均池化:将16个时间步的特征取平均,得到全局向量
global_feature = torch.mean(seq_feature, dim=1)  # [batch, 16, 32] → [batch, 32]

# 分类输出:32维全局向量 → 正常/故障两个得分
self.classify_head = nn.Sequential(
    nn.Dropout(p=DROPOUT),
    nn.Linear(EMBED_DIM, 2)
)

平均池化的设计动机:不依赖特定位置。无论故障发生在序列的开头、中间还是结尾,都能被等权重地捕捉到。

输出2维得分(logits),通过 torch.argmax 取最高分索引作为预测标签。

重要细节:模型输出的是原始得分(logits),不需要手动加softmax。因为 CrossEntropyLoss 内部已经自动包含了 softmax + 取对数操作。

4.6 模型初始化输出

【AI模型网络组件初始化】
组件1:线性投影层 Linear(1→32) 初始化完成
组件2:正弦位置编码 PositionalEncoding 初始化完成
组件3:2层Transformer编码器(4头注意力,FFN=64)初始化完成
组件4:二分类输出全连接层(含Dropout)初始化完成
【AI模型所有组件组装完毕】
模型总可训练参数量:17,218 个

4.7 数据集三分集划分

代码将9000条数据按 70/15/15 划分为训练集、验证集、测试集:

全部数据
9000条

训练集 70%
6300条
更新模型参数

验证集 15%
1350条
早停判断 + 调参

测试集 15%
1350条
最终评估(全程封印)

为什么不直接用二分集(训练+验证)? 因为验证集既用于早停又用于最终评估时,模型已经"看过"验证集的表现并据此做了决策——这就引入了信息泄露。独立的测试集给出的是模型在完全未见数据上的真实泛化能力。


五、训练——模型如何从"什么都不懂"到"判断准确"

5.1 初始状态:17,218个随机数

模型创建时,PyTorch用随机数填充全部17,218个参数。此时的模型对"正常"和"故障"没有任何概念,输出接近随机猜测——准确率约50%(二分类瞎猜的理论值)。

5.2 标准训练五步流程

每轮训练中,6300条训练数据被分成262个批次(每批24条),每个批次执行5步标准流程:

1. 清零梯度
zero_grad

2. 前向传播
model(x)

3. 计算损失
CrossEntropyLoss

4. 反向传播
loss.backward()

5. 更新参数
optimizer.step()

每一步的含义:

optimizer.zero_grad()                      # 1. 清零上一批次残留的梯度
batch_pred_logits = model(batch_seq)        # 2. 24条样本送入模型,输出24×2得分
batch_loss = loss_function(...)            # 3. 对比预测和真实标签,计算差距
batch_loss.backward()                       # 4. 从输出层往输入层逐层求导
optimizer.step()                            # 5. 按梯度方向微调17,218个参数

损失值(loss)的含义:CrossEntropyLoss计算预测与真实的差距。随机猜测的理论损失约 ln(2) ≈ 0.693,损失越小表示预测越准。

关键:model.train()model.eval()

model.train()   # 训练模式:启用Dropout随机屏蔽,保证正则效果
# ... 训练代码 ...

model.eval()    # 评估模式:关闭Dropout,保证推理结果确定性
with torch.no_grad():  # 关闭梯度追踪,节省显存,加速约30%~50%
    # ... 验证代码 ...

5.2+ 训练循环全景图

下面这张图展示了从随机初始化收敛保存的完整训练流程:

在这里插入图片描述

外层循环(Epoch):每轮遍历全部6300条训练数据,分为262个批次。

内层循环(Batch):每个批次执行5步标准训练流程,更新17,218个参数。

验证阶段:每轮结束后用验证集评估,计算Accuracy/Precision/Recall/F1四项指标。

早停决策:验证集连续5轮无提升则终止训练,保留历史上表现最好的模型权重。

5.3 验证集评估——四项指标

每轮训练结束后,用验证集(模型没见过的数据)计算完整评估指标:

指标公式含义工业关注点
Accuracy(TP+TN)/总数整体正确率总体概览
PrecisionTP/(TP+FP)预测为故障中真正故障的比例关注误报率——频繁误报导致"狼来了"
RecallTP/(TP+FN)真实故障中被正确检出的比例关注漏报率——漏报可能导致安全事故
F12×P×R/(P+R)Precision和Recall的调和平均综合评价

混淆矩阵四个基础量:

TP(真阳性):预测故障,实际故障 —— 正确检出
FP(假阳性):预测故障,实际正常 —— 误报
FN(假阴性):预测正常,实际故障 —— 漏报
TN(真阴性):预测正常,实际正常 —— 正确判定

工业场景中Recall尤其重要:漏报一个故障可能导致设备损坏甚至安全事故。

5.4 早停策略——防止过拟合的"刹车"

每轮结束
计算验证集准确率

准确率 > 历史最佳?

更新最佳记录
重置计数器=0
保存模型快照

计数器+1

计数器 ≥ 5?

触发早停
停止训练
使用最佳模型

类比:做模拟试卷时,如果连续5次成绩没有进步,说明你已经在"刷题但没学到新东西"了——此时应该停止刷题,回顾之前状态最好的时候。

5.5 执行日志解读

以下是程序的实际运行输出(为节约篇幅,每轮只展示关键批次):

第1轮:模型从随机状态开始学习

===== 进入第 1/40 轮完整训练 =====
[轮次1 | 批次 0/262] 批次损失=0.7123 | 批次准确率=41.67%
[轮次1 | 批次30/262] 批次损失=0.6021 | 批次准确率=70.83%
[轮次1 | 批次90/262] 批次损失=0.2107 | 批次准确率=91.67%
[轮次1 | 批次120/262] 批次损失=0.0404 | 批次准确率=100.00%
[轮次1 | 批次240/262] 批次损失=0.0094 | 批次准确率=100.00%
★ 验证集准确率创新高!当前最佳:100.00%(已保存模型快照)
---------- 第1轮 训练+验证完整汇总报告 ----------
【训练集】平均损失:0.2080 | 准确率:90.10%
【验证集】平均损失:0.0019 | 准确率:100.00% | Precision:100.00% | Recall:100.00% | F1:100.00%
【混淆矩阵】TP=685 FP=0 FN=0 TN=665

观察关键变化:

  • 批次0:损失0.7123(接近随机),准确率41.67%——模型还没学到东西
  • 批次30:损失降到0.6,准确率跳到70.83%——模型开始学到"高值=故障"这个粗略模式
  • 批次90:准确率91.67%——模型开始区分"临界正常"和"真实故障"
  • 批次120以后:准确率稳定在100%——模型已经掌握了主要判别模式

第2~6轮:模型持续优化,最终触发早停

===== 进入第 2/40 轮完整训练 =====
[轮次2 | 批次 0/262] 批次损失=0.0062 | 批次准确率=100.00%
验证集无提升,早停等待计数:1/5
---------- 第2轮汇总 ----------
【训练集】平均损失:0.0166 | 准确率:99.59%
【验证集】平均损失:0.0008 | 准确率:100.00%

...(第3、4、5轮类似,验证集持续100%但无提升)...

===== 进入第 6/40 轮完整训练 =====
验证集无提升,早停等待计数:5/5
===== 触发早停!验证集连续5轮无提升,停止训练 =====
【训练流程全部结束】
全程最佳验证集准确率:100.00%

5.6 防过拟合策略总结

代码在多个层面构建了完整的防过拟合防线:

层面策略作用
数据层6类波形+高斯噪声让模型学判别规则,而非记忆固定数字
数据层训练/验证/测试三分集测试集全程封印,保证评估无偏
模型层Dropout 0.25训练时随机屏蔽25%神经元
模型层L2权重衰减约束参数不要过大
训练层早停(5轮)验证集无提升时提前终止

六、评估与推理

6.1 测试集最终结果

训练结束后,加载最佳模型权重,在全程完全没见过的1350条测试数据上评估:

========== 测试集最终评估(模型全程未见过的数据) ==========
【测试集最终结果】
平均损失:0.0001
准确率 Accuracy:100.00%
精确率 Precision:100.00%(预测故障中真正故障的比例)
召回率 Recall:100.00%(真实故障中被正确检出的比例)
F1 分数:100.00%(Precision和Recall的调和平均)
混淆矩阵:TP=673 FP=0 FN=0 TN=677

测试集与验证集的区别:验证集参与了早停决策(模型"间接看过"),测试集全程封印。测试集的100%准确率才是模型真实泛化能力的无偏估计。

6.2 训练曲线可视化

代码自动生成4张训练曲线图,直观展示模型的学习过程:

在这里插入图片描述

图表包含4个子图:

子图横轴纵轴看什么
左上:损失曲线训练轮次损失值训练损失↓验证损失↓ = 正常收敛;训练↓验证↑ = 过拟合
右上:准确率曲线训练轮次准确率两者差距越大 = 过拟合越严重
左下:P/R/F1曲线训练轮次分数观察精确率和召回率的权衡变化
右下:混淆矩阵类别样本数FP多=误报多,FN多=漏报多

6.3 交互式推理演示

训练完成后进入交互模式,输入16个逗号分隔的电压值即可实时检测:

请输入16个传感器电压数值:1.8,2.1,2.5,1.9,2.2,1.5,2.8,2.3,1.9,2.0,1.4,2.6,1.7,2.2,1.3,2.4
------------ AI故障检测详细报告 ------------
输入传感器完整序列:[1.8, 2.1, 2.5, 1.9, 2.2, 1.5, 2.8, 2.3, 1.9, 2.0, 1.4, 2.6, 1.7, 2.2, 1.3, 2.4]
AI对【设备正常】打分:3.2105
AI对【设备故障】打分:-3.0762
最终判定:【设备运行状态良好,各项指标正常】
--------------------------------------------
请输入16个传感器电压数值:1.1,2.3,5.2,2.0,1.8,4.6,2.1,2.5,1.7,2.2,1.5,2.7,4.9,2.3,1.6,2.1
------------ AI故障检测详细报告 ------------
输入传感器完整序列:[1.1, 2.3, 5.2, 2.0, 1.8, 4.6, 2.1, 2.5, 1.7, 2.2, 1.5, 2.7, 4.9, 2.3, 1.6, 2.1]
AI对【设备正常】打分:-8.5921
AI对【设备故障】打分:10.2910
最终判定:【设备存在异常,判定为故障,建议停机检修!】
--------------------------------------------

七、AI vs 传统方法:深度对比

7.1 三种方法的核心差异

在这里插入图片描述

三种工业故障检测方案在五个维度上的直观对比:

维度固定阈值法统计方法(3-sigma)Transformer AI
决策依据单点数值是否超限单点是否偏离统计分布16个时间步的全局关联模式
时序建模多头自注意力捕获任意两点间关系
模式类型仅"数值过大"“统计异常”尖峰、渐变、周期性异常、复合模式
渐变故障无法检测缓慢漂移可检序列趋势建模
可解释性极高(一条规则)高(均值和标准差)中(注意力权重可部分解释)
工况适应需人工重写规则需重新计算统计量用新数据重新训练即可
维护成本规则频繁调整窗口参数需调需标注数据,但一劳永逸

7.2 什么时候用传统方法就够了

  • 规则极简:简单的过温保护,一个 if 语句就够
  • 工况固定:设备永远在相同条件下运行,数据分布不随时间变化
  • 成本敏感:没有算力资源,也不需要处理复杂模式

7.3 什么时候必须上AI

  • 故障模式复杂:渐变老化、多点关联、工况切换等无法用单一阈值描述
  • 工况多变:设备在不同负载、不同季节下运行,基线持续漂移
  • 需要提前预警:想在故障发生前就识别"异常趋势",而不是等超限了才报警

7.4 给传统工程师的转型建议

  1. 不需要从数学博士开始。先理解"输入→模型→输出"这个黑盒,把Transformer当作一个"更聪明的规则引擎"
  2. 用合成数据验证想法。先用代码模拟数据跑通全流程,再接入真实传感器数据
  3. 从"规则辅助AI"过渡到"AI替代规则"。先让AI和传统规则并行运行,对比两者结果,逐步建立信任

八、距离工业部署还有多远

8.1 当前代码的定位

技术可行性验证 + 团队学习参考

它能证明"用Transformer做传感器故障检测是可行的",也能帮助团队理解自注意力在时序数据上的工作原理。但不是可以直接上产线的工业系统。

8.2 缺少的工业级能力

维度当前状态工业要求
数据来源纯代码合成,6类固定模式真实产线传感器采集,含未知异常
输入规模固定16个时间步每秒数百到数千采样点,连续流式输入
模型部署Python交互式命令行模型服务化(REST/gRPC)、边缘设备推理
实时性无延迟要求毫秒级响应,确定性延迟上限
可靠性训练完跑一次7×24小时持续运行,故障自愈
数据漂移无监测自动检测分布漂移,触发重训练
可解释性仅输出分数需知道"为什么报故障"(注意力可视化、SHAP值)

8.3 从原型到产线的改进路径

阶段1
数据真实化
接入真实传感器数据

阶段2
工程化
ONNX导出 + 模型服务化

阶段3
系统可靠性
版本管理 + A/B测试

阶段4
持续运维
漂移监测 + 自动重训练


附录A:环境配置

# 1. 创建虚拟环境(推荐)
python -m venv venv

# 2. 激活虚拟环境
# Windows:
venv\Scripts\activate
# Linux/Mac:
source venv/bin/activate

# 3. 安装依赖
pip install torch matplotlib

# 4. 运行
python main.py

硬件要求:普通办公笔记本即可,不需要GPU。在CPU上完整训练约需1~3分钟。


附录B:超参数配置清单

参数含义
SEQ_LEN16每次采集16个传感器读数
EMBED_DIM32投影向量维度
N_HEAD4多头注意力头数
FFN_HIDDEN64前馈网络隐藏层大小
LAYERS2Transformer编码器层数
BATCH_SIZE24每批训练样本数
EPOCHS40最大训练轮数
LR0.0005学习率
DROPOUT0.25Dropout概率
SAMPLE_NUM_BASE1500基础样本组数(总计9000条)
PATIENT_EARLY_STOP5早停耐心值

附录C:完整代码

代码约700行,带详细中文注释。

# ===================== 程序功能说明 =====================
# 场景:工业设备传感器时序故障检测AI
# 输入:连续16个设备电压传感器数值序列
# 输出:0=设备运行正常  1=设备存在故障异常
# 模型:2层Transformer + 正弦位置编码 + 线性投影输入层
# 核心设计:
#   1. 用nn.Linear处理连续浮点数值,保留数值的大小关系语义
#   2. 正弦位置编码为序列注入时序位置信息,让模型区分"开头异常"和"尾部异常"
#   3. 合成数据模拟真实传感器特征:基线漂移、周期性波动、线性趋势、高斯噪声
#   4. 训练/验证/测试 三分集划分,测试集全程不参与训练和调参,保证评估无偏
#   5. 早停策略防止过拟合,Dropout + L2权重衰减双重正则约束
#   6. Precision / Recall / F1 / 混淆矩阵 全维度评估指标
#   7. 模型自动保存最佳权重,训练曲线可视化
# 依赖:PyTorch + Matplotlib
# ========================================================

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader, random_split
import math
# import os  # 当前版本未使用,预留用于后续模型路径管理、环境变量读取等扩展

# ===================== 1. 硬件设备与全局超参数配置区 =====================
# 自动判断运行硬件:有NVIDIA显卡用GPU加速,无显卡自动切换CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

print("=" * 80)
print("【设备故障检测AI程序 - 启动自检日志】")
print(f"程序运行硬件设备:{device}")
print(f"当前PyTorch框架版本:{torch.__version__}")
if torch.cuda.is_available():
    print(f"检测到NVIDIA显卡,显卡型号:{torch.cuda.get_device_name(0)}")
else:
    print("未检测到NVIDIA独立显卡,本次全程使用CPU训练模型")
print("=" * 80)

# ---------------------- 模型超参数(集中顶部,方便调参) ----------------------
SEQ_LEN = 16            # 一次采集16个数据点,对应16个连续时间步的传感器读数
EMBED_DIM = 32          # 投影向量维度:每个传感器数值被映射为32维特征向量
N_HEAD = 4              # 多头注意力头数:4个头可同时关注不同尺度的时间模式
FFN_HIDDEN = 64         # 前馈网络隐藏层神经元数,负责非线性特征变换
LAYERS = 2              # Transformer编码器层数,2层可提取更抽象的时序特征
BATCH_SIZE = 24         # 每次训练送入模型的样本批次数
EPOCHS = 40             # 最大训练轮次上限,早停机制通常会在之前终止
LR = 0.0005             # 学习率:控制每步参数更新的幅度,偏小使训练更稳定
SAMPLE_NUM_BASE = 1500  # 基础样本组数(每组生成6条样本,总计9000条)
PATIENT_EARLY_STOP = 5  # 早停耐心值:验证集连续5轮无提升则提前终止训练
DROPOUT = 0.25          # Dropout概率:训练时随机屏蔽25%的神经元,防止过拟合
MODEL_SAVE_PATH = "sensor_model_best.pth"  # 训练好的模型权重保存路径

# 打印全部超参数配置,方便对照和复现
print("\n【模型全部超参数配置清单】")
print(f"传感器序列固定长度 SEQ_LEN = {SEQ_LEN}")
print(f"投影向量维度 EMBED_DIM = {EMBED_DIM}")
print(f"多头注意力头数 N_HEAD = {N_HEAD}")
print(f"前馈网络隐藏层 FFN_HIDDEN = {FFN_HIDDEN}")
print(f"Transformer编码器层数 LAYERS = {LAYERS}")
print(f"单次训练批次大小 BATCH_SIZE = {BATCH_SIZE}")
print(f"最大训练轮数 EPOCHS = {EPOCHS}")
print(f"基础样本组数 SAMPLE_NUM_BASE = {SAMPLE_NUM_BASE}")
print(f"优化器学习率 LR = {LR}")
print(f"早停等待轮数 PATIENT_EARLY_STOP = {PATIENT_EARLY_STOP}")
print(f"Dropout概率 = {DROPOUT}")
print(f"模型保存路径 = {MODEL_SAVE_PATH}")
print("=" * 80)

# ===================== 2. 传感器数据集:模拟真实工业传感器信号 =====================
class SensorDataset(Dataset):
    """
    工业传感器时序数据集生成器
    核心思路:真实传感器信号不是纯随机数,而是由多种物理因素叠加而成:
      信号 = 基线电压 + 设备老化漂移 + 工况周期波动 + 电磁随机噪声

    生成6类样本(3类正常 + 3类故障),模拟多种实际工况:
      正常类:
        1. 标准正常:2V基准附近波动,叠加微弱趋势和周期性(设备正常运行)
        2. 临界高值正常:3.2~3.95V,高负载但未超限(满载运行)
        3. 低幅波动正常:1.2V附近平稳波动(低功耗待机模式)
      故障类:
        4. 标准故障:正常波形上随机3个点位突发尖峰(瞬时短路/冲击)
        5. 弱故障:仅1个点位轻微超标(早期异常,值得关注)
        6. 渐变故障:后半段数值持续升高(设备过热/绝缘老化)
    """
    def __init__(self, sample_num=800):
        print(f"\n【数据集初始化中...】基础样本组数 {sample_num} 组(每组生成6条样本)")
        self.data = []
        self.label = []

        for sample_idx in range(sample_num):
            # 生成归一化时间轴 [0, 1],用于构造趋势和周期波形
            t = torch.linspace(0, 1, SEQ_LEN)

            # ---------------- 类型1:标准正常 — 带基线漂移 + 周期性波动 + 噪声 ----------------
            baseline = 2.0 + 0.4 * torch.randn(1).item()        # 随机基线偏移,模拟设备个体差异
            trend = 0.3 * (t - 0.5)                              # 微弱线性趋势,模拟缓慢漂移
            periodic = 0.25 * torch.sin(2 * math.pi * t * 2)    # 周期性正弦波,模拟工况循环
            noise = torch.randn(SEQ_LEN) * 0.12                   # 高斯噪声,模拟电磁干扰
            normal_sequence = baseline + trend + periodic + noise
            normal_sequence = torch.clamp(normal_sequence, min=0.1, max=6.0)
            self.data.append(normal_sequence)
            self.label.append(0)

            # ---------------- 类型2:标准故障 — 正常波形 + 多点突发尖峰 ----------------
            baseline2 = 2.0 + 0.3 * torch.randn(1).item()
            trend2 = 0.2 * (t - 0.5)
            periodic2 = 0.2 * torch.sin(2 * math.pi * t * 2)
            fault_seq = baseline2 + trend2 + periodic2
            # 随机选3个位置叠加突发尖峰,模拟瞬时短路/电磁冲击
            fault_pos = torch.randint(0, SEQ_LEN, (3,))
            fault_seq[fault_pos] = fault_seq[fault_pos] + torch.FloatTensor(3).uniform_(2.5, 4.0)
            fault_noise = torch.randn(SEQ_LEN) * 0.12
            fault_seq = torch.clamp(fault_seq + fault_noise, min=0.1, max=6.0)
            self.data.append(fault_seq)
            self.label.append(1)

            # ---------------- 类型3:弱故障 — 仅1个轻微超标点位 ----------------
            baseline3 = 2.0 + 0.3 * torch.randn(1).item()
            periodic3 = 0.15 * torch.sin(2 * math.pi * t * 3)
            weak_fault = baseline3 + periodic3
            # 仅1个位置出现轻微异常,模拟早期故障征兆
            single_pos = torch.randint(0, SEQ_LEN, (1,))
            weak_fault[single_pos] = weak_fault[single_pos] + torch.FloatTensor(1).uniform_(2.5, 3.8)
            weak_fault = torch.clamp(weak_fault + torch.randn(SEQ_LEN) * 0.12, min=0.1, max=6.0)
            self.data.append(weak_fault)
            self.label.append(1)

            # ---------------- 类型4:渐变故障 — 后半段数值持续升高 ----------------
            baseline4 = 2.0 + 0.2 * torch.randn(1).item()
            gradual_fault = baseline4 + 0.15 * torch.randn(SEQ_LEN)
            # 后半段叠加线性上升,模拟设备持续升温或绝缘逐渐老化
            gradual_fault[SEQ_LEN // 2:] = gradual_fault[SEQ_LEN // 2:] + torch.linspace(1.0, 2.5, SEQ_LEN // 2)
            gradual_fault = torch.clamp(gradual_fault, min=0.1, max=6.0)
            self.data.append(gradual_fault)
            self.label.append(1)

            # ---------------- 类型5:临界高值正常 — 高负载运行但未超限 ----------------
            edge_normal = 3.2 + 0.3 * torch.randn(SEQ_LEN) * 0.5
            edge_normal = edge_normal + 0.15 * torch.sin(2 * math.pi * t * 2)
            # 强制钳位到3.95以下,属于正常工况中的满载运行
            edge_normal = torch.clamp(edge_normal, min=0.1, max=3.95)
            self.data.append(edge_normal)
            self.label.append(0)

            # ---------------- 类型6:低幅波动正常 — 低功耗待机模式 ----------------
            low_normal = 1.2 + 0.3 * torch.randn(1).item()
            low_normal_seq = low_normal + 0.2 * torch.sin(2 * math.pi * t * 3) + torch.randn(SEQ_LEN) * 0.1
            low_normal_seq = torch.clamp(low_normal_seq, min=0.1, max=2.5)
            self.data.append(low_normal_seq)
            self.label.append(0)

            if (sample_idx + 1) % 100 == 0:
                print(f"数据集生成进度:已完成 {sample_idx + 1}/{sample_num} 组基础样本配套波形")

        print(f"【数据集生成完成】总样本数量:{len(self.data)} 条")
        print(f"包含6类波形:标准正常、标准故障、弱故障、渐变故障、临界高值正常、低幅波动正常")
        print("信号构成:基线偏移 + 线性趋势 + 周期正弦波 + 高斯随机噪声")
        print("=" * 80)

    def __len__(self):
        """返回数据集总样本数。PyTorch的DataLoader靠它知道有多少样本可遍历。"""
        return len(self.data)

    def __getitem__(self, idx):
        """根据索引取出单条样本。
        DataLoader通过它按批次抽取数据,返回 (样本张量, 标签张量) 的元组。
        这是PyTorch Dataset抽象接口的标准约定,必须实现。
        """
        # 返回原始浮点数序列,不做离散化
        # 模型入口使用 nn.Linear 投影层,能正确处理连续数值的大小关系
        raw_float_seq = self.data[idx]
        raw_label = self.label[idx]
        float_tensor = raw_float_seq.float()
        label_tensor = torch.tensor(raw_label, dtype=torch.long)
        return float_tensor, label_tensor


# ===================== 3. 正弦位置编码 =====================
class PositionalEncoding(nn.Module):
    """
    正弦位置编码(Sinusoidal Positional Encoding)

    为什么需要位置编码?
      Transformer的自注意力机制是"位置无关"的——打乱输入序列的顺序,输出不变。
      但传感器数据的核心价值在于时序顺序:第3个采样点和第13个采样点含义完全不同。
      因此必须显式告诉模型"当前是第几个时间步"。

    原理:
      使用不同频率的正弦/余弦函数为每个位置生成唯一编码向量。
      公式:PE(pos, 2i)   = sin(pos / 10000^(2i/d_model))
            PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
      其中 pos 是位置索引,i 是维度索引。

      三角函数的数学性质使得模型能隐式学到相对位置关系
      (相位差天然编码了两个位置之间的距离),这对时序建模非常有价值。
    """
    def __init__(self, d_model, max_len=100, dropout=0.1):
        super().__init__()
        self.dropout = nn.Dropout(p=dropout)

        # 预计算所有位置的位置编码矩阵
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)  # [max_len, 1]
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))

        pe[:, 0::2] = torch.sin(position * div_term)   # 偶数维度:sin编码
        pe[:, 1::2] = torch.cos(position * div_term)   # 奇数维度:cos编码
        pe = pe.unsqueeze(0)  # 扩展为 [1, max_len, d_model],方便广播

        # register_buffer:标记为非参数张量,不参与梯度更新但会随模型一起保存/加载
        self.register_buffer('pe', pe)

    def forward(self, x):
        """
        :param x: [batch_size, seq_len, d_model] 输入特征
        :return: x + 对应位置的位置编码
        """
        # self.pe 形状为 [1, max_len, d_model],x 形状为 [batch, seq_len, d_model]
        # 切片 [:, :seq_len, :] 取前 seq_len 个位置,然后利用 Broadcasting 自动扩展到 batch_size
        x = x + self.pe[:, :x.size(1), :]
        return self.dropout(x)


# ===================== 4. Transformer 故障检测模型 =====================
class SensorTransformer(nn.Module):
    """
    时序故障检测 Transformer 模型

    完整数据流:
      浮点数值 [batch, 16]
        → 线性投影 [batch, 16, 32]    将每个标量数值映射为32维特征向量
        → 位置编码 [batch, 16, 32]    注入时序位置信息
        → Transformer编码器 [batch, 16, 32]  多头自注意力提取全局时序特征
        → 平均池化 [batch, 32]         将16个时间步的特征压缩为1个全局向量
        → 分类输出 [batch, 2]          输出正常/故障两个类别的得分

    为什么用 Linear 投影而不是 Embedding?
      Embedding 把每个数值当作离散ID处理,2.3V和2.4V会被当作两个完全不同的"单词"。
      但在传感器场景中,2.3V和2.4V在语义上几乎等价(都是正常电压)。
      Linear 是连续映射,数值相近的输入会映射到相近的向量,保留了数值的大小关系语义。
    """
    def __init__(self):
        super().__init__()
        print("\n【AI模型网络组件初始化】")

        # 组件1:线性投影层,将标量数值映射为高维特征向量
        self.input_proj = nn.Linear(1, EMBED_DIM)
        print(f"组件1:线性投影层 Linear(1→{EMBED_DIM}) 初始化完成")

        # 组件2:正弦位置编码,注入时序位置信息
        self.pos_encoder = PositionalEncoding(d_model=EMBED_DIM, max_len=SEQ_LEN, dropout=DROPOUT)
        print(f"组件2:正弦位置编码 PositionalEncoding 初始化完成")

        # 组件3:Transformer编码器,多头自注意力提取全局时序特征
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=EMBED_DIM,
            nhead=N_HEAD,
            dim_feedforward=FFN_HIDDEN,
            batch_first=True,
            dropout=DROPOUT
        )
        self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=LAYERS)
        print(f"组件3:{LAYERS}层Transformer编码器({N_HEAD}头注意力,FFN={FFN_HIDDEN})初始化完成")

        # 组件4:分类输出头,带Dropout正则
        self.classify_head = nn.Sequential(
            nn.Dropout(p=DROPOUT),
            nn.Linear(EMBED_DIM, 2)
        )
        print("组件4:二分类输出全连接层(含Dropout)初始化完成")
        print("【AI模型所有组件组装完毕】")
        print("=" * 80)

    def forward(self, x):
        """
        :param x: [batch_size, SEQ_LEN] 传感器浮点数值序列
        :return logits: [batch_size, 2] 正常/故障两个类别的原始得分
        """
        # unsqueeze(-1) 在最后一个维度插入大小为1的新维度
        # 原因:nn.Linear 期望输入的最后一维是特征数,这里每个时间步只有一个标量值
        # [batch, seq_len] → [batch, seq_len, 1] 才能适配 Linear(1, 32)
        x = x.unsqueeze(-1)
        x = self.input_proj(x)

        # 注入时序位置信息
        x = self.pos_encoder(x)

        # 多头自注意力编码
        seq_feature = self.encoder(x)

        # 平均池化:将16个时间步的特征向量取平均,得到一个全局特征向量
        # 这样做的好处是不依赖特定位置,无论故障发生在序列开头还是结尾都能被捕捉
        global_feature = torch.mean(seq_feature, dim=1)

        # 输出分类得分
        logits = self.classify_head(global_feature)
        return logits


# ===================== 5. 数据集划分:训练 / 验证 / 测试 三分集 =====================
#
# 三分集各自的角色和隔离原则:
#   训练集(70%):直接用于更新模型参数,模型唯一的"学习材料"
#   验证集(15%):不参与梯度更新,用于早停判断和超参数调优
#   测试集(15%):训练和调参期间完全不可见,最终评估时才揭晓,保证泛化性能评估无偏
#
# 为什么不直接用二分集(训练+验证)?
#   如果验证集既用于早停又用于最终评估,模型在训练过程中已经"看过"验证集的表现
#   并据此做了决策(何时停止训练),这就引入了信息泄露,导致评估指标虚高。
#   独立的测试集给出的是模型在"完全未见数据"上的真实泛化能力。
# =========================================================================

full_dataset = SensorDataset(sample_num=SAMPLE_NUM_BASE)

total_size = len(full_dataset)
train_size = int(0.7 * total_size)
val_size = int(0.15 * total_size)
test_size = total_size - train_size - val_size

train_dataset, val_dataset, test_dataset = random_split(
    full_dataset, [train_size, val_size, test_size]
)

train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)

total_batch_per_epoch = len(train_dataset) // BATCH_SIZE
print(f"\n【数据集三分集划分结果】")
print(f"训练集:{train_size} 条(70%)| 验证集:{val_size} 条(15%)| 测试集:{test_size} 条(15%)")
print(f"每完整一轮训练处理批次总数:{total_batch_per_epoch} 批")
print("=" * 80)

# ===================== 6. 初始化训练三件套:损失函数 + 优化器 + 学习率调度 =====================
model = SensorTransformer().to(device)

# 交叉熵损失:二分类标准损失函数,衡量预测概率分布与真实标签的差距
# 注意:CrossEntropyLoss 内部会自动对输入做 softmax + 取对数
# 因此模型 forward 返回的 logits(原始得分)直接传入即可,不需要手动加 softmax
loss_function = nn.CrossEntropyLoss()

# Adam优化器:自适应学习率,配合L2权重衰减(weight_decay)约束模型参数不要过大
# L2正则通过在损失函数中添加参数平方和的惩罚项,抑制模型对训练数据的过度拟合
optimizer = optim.Adam(model.parameters(), lr=LR, weight_decay=1e-4)

total_trainable_params = sum(p.numel() for p in model.parameters())
print("\n【训练工具初始化信息】")
print(f"模型总可训练参数量:{total_trainable_params:,} 个")
print(f"损失函数:CrossEntropyLoss 二分类交叉熵")
print(f"优化器:Adam,学习率={LR},L2权重衰减 weight_decay=1e-4")
print("=" * 80)
print("========== 正式开始AI模型训练流程(训练+验证+早停) ==========")
print("=" * 80)


# ===================== 7. 评估指标计算函数 =====================
def compute_metrics(preds, labels):
    """
    计算分类任务的完整评估指标

    四个基础量(混淆矩阵):
      TP(真阳性):预测为故障,实际也是故障 —— 正确检出的故障
      FP(假阳性):预测为故障,实际是正常   —— 误报
      FN(假阴性):预测为正常,实际是故障   —— 漏报
      TN(真阴性):预测为正常,实际也是正常 —— 正确判定的正常

    衍生指标:
      Accuracy  = (TP+TN) / 总数           整体正确率
      Precision = TP / (TP+FP)              预测为故障中,真正故障的比例(关注误报率)
      Recall    = TP / (TP+FN)              真实故障中,被正确检出的比例(关注漏报率)
      F1        = 2*P*R / (P+R)             Precision和Recall的调和平均(综合评价)

    工业场景中 Recall 尤其重要:漏报一个故障可能导致安全事故。
    """
    preds = torch.tensor(preds)
    labels = torch.tensor(labels)

    tp = ((preds == 1) & (labels == 1)).sum().item()
    fp = ((preds == 1) & (labels == 0)).sum().item()
    fn = ((preds == 0) & (labels == 1)).sum().item()
    tn = ((preds == 0) & (labels == 0)).sum().item()

    accuracy = (tp + tn) / max(tp + fp + fn + tn, 1)
    precision = tp / max(tp + fp, 1)
    recall = tp / max(tp + fn, 1)
    f1 = 2 * precision * recall / max(precision + recall, 1e-8)

    return {
        "accuracy": accuracy,
        "precision": precision,
        "recall": recall,
        "f1": f1,
        "tp": tp, "fp": fp, "fn": fn, "tn": tn
    }


# ===================== 8. 完整训练循环 =====================
#
# 训练流程:
#   每轮训练包含两个阶段:
#     阶段1 - 训练:用训练集数据更新模型参数(标准五步流程:清零→前向→算损失→反向→更新)
#     阶段2 - 验证:用验证集评估当前模型(不更新参数),计算完整评估指标
#
# 早停策略:
#   如果验证集准确率连续 PATIENT_EARLY_STOP 轮没有提升,说明模型已经收敛或开始过拟合,
#   提前终止训练,避免浪费算力并保留最佳状态的模型权重。
# =========================================================================

best_val_acc = 0.0           # 记录历史最高验证集准确率
early_stop_count = 0          # 连续无提升的轮次计数器
best_model_state = None      # 保存最佳模型参数的快照

# 记录每轮训练指标,用于绘制训练曲线
history = {
    "train_loss": [], "train_acc": [],
    "val_loss": [], "val_acc": [],
    "val_precision": [], "val_recall": [], "val_f1": []
}

for current_epoch in range(EPOCHS):
    model.train()  # 切换到训练模式:启用Dropout随机屏蔽,保证训练时的正则效果
    epoch_total_loss = 0.0
    epoch_correct_count = 0
    epoch_total_samples = 0

    print(f"\n===== 进入第 {current_epoch + 1}/{EPOCHS} 轮完整训练 =====")

    # ---------------------- 阶段1:训练集参数更新 ----------------------
    for batch_index, (batch_seq, batch_label) in enumerate(train_loader):
        batch_seq = batch_seq.to(device)
        batch_label = batch_label.to(device)

        # 标准训练五步流程
        optimizer.zero_grad()                      # 1. 清零梯度
        batch_pred_logits = model(batch_seq)        # 2. 前向传播
        batch_loss = loss_function(batch_pred_logits, batch_label)  # 3. 计算损失
        batch_loss.backward()                        # 4. 反向传播求梯度
        optimizer.step()                             # 5. 更新参数

        # 统计批次准确率
        batch_pred_class = torch.argmax(batch_pred_logits, dim=1)
        batch_correct_num = (batch_pred_class == batch_label).sum().item()
        batch_sample_count = batch_seq.shape[0]

        epoch_total_loss += batch_loss.item()
        epoch_correct_count += batch_correct_num
        epoch_total_samples += batch_sample_count

        # 每30批次打印实时训练日志
        if batch_index % 30 == 0:
            print(f"[轮次{current_epoch + 1} | 批次{batch_index:2d}/{total_batch_per_epoch}] "
                  f"批次损失={batch_loss.item():.4f} | "
                  f"批次准确率={batch_correct_num / batch_sample_count:.2%}")

    # 训练集本轮汇总
    epoch_avg_loss = epoch_total_loss / total_batch_per_epoch
    epoch_acc = epoch_correct_count / epoch_total_samples

    # ---------------------- 阶段2:验证集评估(不更新参数) ----------------------
    model.eval()   # 切换到评估模式:关闭Dropout,使用全局统计量,保证推理结果确定性
    val_total_loss = 0.0
    val_all_preds = []
    val_all_labels = []
    with torch.no_grad():
        # 推理阶段只进行前向传播,不需要反向传播求梯度
        # 关闭梯度追踪可大幅节省显存,并将推理速度提升约30%~50%
        for val_seq, val_lab in val_loader:
            val_seq = val_seq.to(device)
            val_lab = val_lab.to(device)
            val_logits = model(val_seq)
            val_loss = loss_function(val_logits, val_lab)
            val_pred = torch.argmax(val_logits, dim=1)
            val_total_loss += val_loss.item()
            val_all_preds.extend(val_pred.cpu().tolist())
            val_all_labels.extend(val_lab.cpu().tolist())

    val_avg_loss = val_total_loss / len(val_loader)
    val_metrics = compute_metrics(val_all_preds, val_all_labels)

    # 记录本轮历史数据
    history["train_loss"].append(epoch_avg_loss)
    history["train_acc"].append(epoch_acc)
    history["val_loss"].append(val_avg_loss)
    history["val_acc"].append(val_metrics["accuracy"])
    history["val_precision"].append(val_metrics["precision"])
    history["val_recall"].append(val_metrics["recall"])
    history["val_f1"].append(val_metrics["f1"])

    # ---------------------- 早停逻辑 ----------------------
    if val_metrics["accuracy"] > best_val_acc:
        best_val_acc = val_metrics["accuracy"]
        early_stop_count = 0
        best_model_state = model.state_dict()  # 保存当前最佳模型参数快照
        print(f"★ 验证集准确率创新高!当前最佳:{best_val_acc:.2%}(已保存模型快照)")
    else:
        early_stop_count += 1
        print(f"验证集无提升,早停等待计数:{early_stop_count}/{PATIENT_EARLY_STOP}")

    # 打印本轮完整汇总报告
    print(f"\n---------- 第{current_epoch + 1}轮 训练+验证完整汇总报告 ----------")
    print(f"【训练集】平均损失:{epoch_avg_loss:.4f} | 准确率:{epoch_acc:.2%}")
    print(f"【验证集】平均损失:{val_avg_loss:.4f} | 准确率:{val_metrics['accuracy']:.2%} | "
          f"Precision:{val_metrics['precision']:.2%} | Recall:{val_metrics['recall']:.2%} | "
          f"F1:{val_metrics['f1']:.2%}")
    print(f"【混淆矩阵】TP={val_metrics['tp']} FP={val_metrics['fp']} FN={val_metrics['fn']} TN={val_metrics['tn']}")
    print("-" * 70)

    # 触发早停则终止训练
    if early_stop_count >= PATIENT_EARLY_STOP:
        print(f"\n===== 触发早停!验证集连续{PATIENT_EARLY_STOP}轮无提升,停止训练 =====")
        break

# 训练结束收尾
print("=" * 80)
print("【训练流程全部结束】")
print(f"全程最佳验证集准确率:{best_val_acc:.2%}")
print("=" * 80)


# ===================== 9. 保存最佳模型权重 =====================
# 保存内容包含:模型参数 + 模型配置 + 最佳验证准确率
# 这样加载时可以自动校验架构是否匹配,避免加载不兼容的权重
if best_model_state is not None:
    torch.save({
        "model_state_dict": best_model_state,
        "config": {
            "seq_len": SEQ_LEN,
            "embed_dim": EMBED_DIM,
            "n_head": N_HEAD,
            "ffn_hidden": FFN_HIDDEN,
            "layers": LAYERS,
            "dropout": DROPOUT,
        },
        "best_val_acc": best_val_acc,
    }, MODEL_SAVE_PATH)
    print(f"\n【模型已保存】最佳模型权重 → {MODEL_SAVE_PATH}")
    print("=" * 80)


# ===================== 10. 测试集最终评估 =====================
# 测试集在训练全程中完全不可见,用于给出模型真实泛化能力的无偏估计
print("\n========== 测试集最终评估(模型全程未见过的数据) ==========")

if best_model_state is not None:
    model.load_state_dict(best_model_state)

model.eval()
test_all_preds = []
test_all_labels = []
test_total_loss = 0.0

with torch.no_grad():
    for test_seq, test_lab in test_loader:
        test_seq = test_seq.to(device)
        test_lab = test_lab.to(device)
        test_logits = model(test_seq)
        test_loss = loss_function(test_logits, test_lab)
        test_pred = torch.argmax(test_logits, dim=1)
        test_total_loss += test_loss.item()
        test_all_preds.extend(test_pred.cpu().tolist())
        test_all_labels.extend(test_lab.cpu().tolist())

test_metrics = compute_metrics(test_all_preds, test_all_labels)
test_avg_loss = test_total_loss / len(test_loader)

print(f"【测试集最终结果】")
print(f"平均损失:{test_avg_loss:.4f}")
print(f"准确率 Accuracy:{test_metrics['accuracy']:.2%}")
print(f"精确率 Precision:{test_metrics['precision']:.2%}(预测故障中真正故障的比例)")
print(f"召回率 Recall:{test_metrics['recall']:.2%}(真实故障中被正确检出的比例)")
print(f"F1 分数:{test_metrics['f1']:.2%}(Precision和Recall的调和平均)")
print(f"混淆矩阵:TP={test_metrics['tp']} FP={test_metrics['fp']} FN={test_metrics['fn']} TN={test_metrics['tn']}")
print("=" * 80)


# ===================== 11. 训练过程可视化 =====================
# 生成4张子图,直观展示模型的训练状态:
#   左上:损失曲线(训练 vs 验证)—— 两者差距大则过拟合
#   右上:准确率曲线(训练 vs 验证)—— 验证准确率停滞则模型已收敛
#   左下:P/R/F1 曲线 —— 观察精确率和召回率的权衡变化
#   右下:混淆矩阵柱状图 —— 一眼看出误报和漏报的数量分布
try:
    import matplotlib
    matplotlib.use("Agg")  # 非交互式后端,适合服务器环境
    import matplotlib.pyplot as plt

    # 设置中文字体,避免图表中的中文显示为方块
    plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "WenQuanYi Micro Hei"]
    plt.rcParams["axes.unicode_minus"] = False

    epochs_range = range(1, len(history["train_loss"]) + 1)

    fig, axes = plt.subplots(2, 2, figsize=(14, 10))

    # 子图1:损失曲线
    axes[0, 0].plot(epochs_range, history["train_loss"], "b-o", markersize=3, label="训练集损失")
    axes[0, 0].plot(epochs_range, history["val_loss"], "r-o", markersize=3, label="验证集损失")
    axes[0, 0].set_xlabel("训练轮次")
    axes[0, 0].set_ylabel("损失值")
    axes[0, 0].set_title("训练集 vs 验证集 损失曲线")
    axes[0, 0].legend()
    axes[0, 0].grid(True, alpha=0.3)

    # 子图2:准确率曲线
    axes[0, 1].plot(epochs_range, history["train_acc"], "b-o", markersize=3, label="训练集准确率")
    axes[0, 1].plot(epochs_range, history["val_acc"], "r-o", markersize=3, label="验证集准确率")
    axes[0, 1].set_xlabel("训练轮次")
    axes[0, 1].set_ylabel("准确率")
    axes[0, 1].set_title("训练集 vs 验证集 准确率曲线")
    axes[0, 1].legend()
    axes[0, 1].grid(True, alpha=0.3)

    # 子图3:Precision / Recall / F1 曲线
    axes[1, 0].plot(epochs_range, history["val_precision"], "g-o", markersize=3, label="Precision")
    axes[1, 0].plot(epochs_range, history["val_recall"], "m-o", markersize=3, label="Recall")
    axes[1, 0].plot(epochs_range, history["val_f1"], "c-o", markersize=3, label="F1")
    axes[1, 0].set_xlabel("训练轮次")
    axes[1, 0].set_ylabel("分数")
    axes[1, 0].set_title("验证集 Precision / Recall / F1 曲线")
    axes[1, 0].legend()
    axes[1, 0].grid(True, alpha=0.3)

    # 子图4:混淆矩阵柱状图
    cm_labels = ["TP(真阳性)", "FP(假阳性)", "FN(假阴性)", "TN(真阴性)"]
    cm_values = [test_metrics["tp"], test_metrics["fp"], test_metrics["fn"], test_metrics["tn"]]
    cm_colors = ["#4CAF50", "#FF9800", "#F44336", "#2196F3"]
    bars = axes[1, 1].bar(cm_labels, cm_values, color=cm_colors, edgecolor="black")
    axes[1, 1].set_ylabel("样本数量")
    axes[1, 1].set_title("测试集混淆矩阵")
    for bar, val in zip(bars, cm_values):
        axes[1, 1].text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 5,
                         str(val), ha="center", va="bottom")

    plt.tight_layout()
    plt.savefig("training_curves.png", dpi=150)
    print(f"\n【训练曲线图已保存】→ training_curves.png")
    print(f"图表包含4个子图:损失曲线、准确率曲线、P/R/F1曲线、混淆矩阵")

except ImportError:
    print("\n【提示】未安装 Matplotlib,跳过训练曲线可视化。")
    print("如需可视化,请运行:pip install matplotlib")
print("=" * 80)


# ===================== 12. 推理预测函数 =====================
def detect_equipment_fault(input_float_sequence):
    """
    推理预测:接收一组传感器数值,返回AI的故障判定结果和置信度

    :param input_float_sequence: list,长度固定SEQ_LEN个浮点数
    :return: (pred_result, score_normal, score_fault)
             pred_result: 0=正常, 1=故障
             score_normal: 模型对"正常"类别的打分
             score_fault:  模型对"故障"类别的打分
    """
    model.eval()  # 推理前必须切到评估模式,确保 Dropout 不随机屏蔽神经元
    with torch.no_grad():
        input_tensor = torch.FloatTensor(input_float_sequence).unsqueeze(0).to(device)
        output_scores = model(input_tensor)
        score_normal = output_scores[0][0].item()
        score_fault = output_scores[0][1].item()
        pred_result = torch.argmax(output_scores, dim=1).item()
    return pred_result, score_normal, score_fault


# ===================== 13. 交互式控制台测试面板 =====================
# 训练完成后可在此手动输入传感器数据进行实时故障检测
print("\n===================== 设备故障检测交互测试面板 =====================")
print("使用说明:")
print(f"1. 输入{SEQ_LEN}个数字,英文逗号 , 分隔,代表{SEQ_LEN}个连续传感器电压值")
print("2. 输入 exit 即可退出检测程序")
print(f"3. 正常波形示例:1.8,2.1,2.5,1.9,2.2,1.5,2.8,2.3,1.9,2.0,1.4,2.6,1.7,2.2,1.3,2.4")
print(f"4. 故障波形示例:1.1,2.3,5.2,2.0,1.8,4.6,2.1,2.5,1.7,2.2,1.5,2.7,4.9,2.3,1.6,2.1")
print("====================================================================")

while True:
    user_input_text = input(f"\n请输入{SEQ_LEN}个传感器电压数值:")
    user_command = user_input_text.strip().lower()
    if user_command == "exit":
        print("\n收到退出指令,故障检测程序结束!")
        break

    try:
        num_value_list = [float(num_str.strip()) for num_str in user_input_text.split(",")]
        if len(num_value_list) != SEQ_LEN:
            print(f"【输入错误】必须输入恰好{SEQ_LEN}个数值,你输入了{len(num_value_list)}个,请重新输入!")
            continue

        predict_label, score_norm, score_fault = detect_equipment_fault(num_value_list)
        print("------------ AI故障检测详细报告 ------------")
        print(f"输入传感器完整序列:{[round(v, 2) for v in num_value_list]}")
        print(f"AI对【设备正常】打分:{score_norm:.4f}")
        print(f"AI对【设备故障】打分:{score_fault:.4f}")
        if predict_label == 1:
            print("最终判定:【设备存在异常,判定为故障,建议停机检修!】")
        else:
            print("最终判定:【设备运行状态良好,各项指标正常】")
        print("--------------------------------------------")

    except Exception as error_info:
        print(f"【输入格式解析失败】请输入纯数字并用英文逗号分隔,错误详情:{error_info}")

你也可以直接复制本文涉及的核心代码片段到本地 .py 文件中运行。
一次运行日志:

C:\Users\lenovo\PycharmProjects\pythonProject_AI\venv\Scripts\python.exe C:\Users\lenovo\PycharmProjects\pythonProject_AI\main.py 
================================================================================
【设备故障检测AI程序 - 启动自检日志】
程序运行硬件设备:cpu
当前PyTorch框架版本:2.13.0+cpu
未检测到NVIDIA独立显卡,本次全程使用CPU训练模型
================================================================================

【模型全部超参数配置清单】
传感器序列固定长度 SEQ_LEN = 16
投影向量维度 EMBED_DIM = 32
多头注意力头数 N_HEAD = 4
前馈网络隐藏层 FFN_HIDDEN = 64
Transformer编码器层数 LAYERS = 2
单次训练批次大小 BATCH_SIZE = 24
最大训练轮数 EPOCHS = 40
基础样本组数 SAMPLE_NUM_BASE = 1500
优化器学习率 LR = 0.0005
早停等待轮数 PATIENT_EARLY_STOP = 5
Dropout概率 = 0.25
模型保存路径 = sensor_model_best.pth
================================================================================

【数据集初始化中...】基础样本组数 1500 组(每组生成6条样本)
数据集生成进度:已完成 100/1500 组基础样本配套波形
数据集生成进度:已完成 200/1500 组基础样本配套波形
数据集生成进度:已完成 300/1500 组基础样本配套波形
数据集生成进度:已完成 400/1500 组基础样本配套波形
数据集生成进度:已完成 500/1500 组基础样本配套波形
数据集生成进度:已完成 600/1500 组基础样本配套波形
数据集生成进度:已完成 700/1500 组基础样本配套波形
数据集生成进度:已完成 800/1500 组基础样本配套波形
数据集生成进度:已完成 900/1500 组基础样本配套波形
数据集生成进度:已完成 1000/1500 组基础样本配套波形
数据集生成进度:已完成 1100/1500 组基础样本配套波形
数据集生成进度:已完成 1200/1500 组基础样本配套波形
数据集生成进度:已完成 1300/1500 组基础样本配套波形
数据集生成进度:已完成 1400/1500 组基础样本配套波形
数据集生成进度:已完成 1500/1500 组基础样本配套波形
【数据集生成完成】总样本数量:9000 条
包含6类波形:标准正常、标准故障、弱故障、渐变故障、临界高值正常、低幅波动正常
信号构成:基线偏移 + 线性趋势 + 周期正弦波 + 高斯随机噪声
================================================================================

【数据集三分集划分结果】
训练集:6300 条(70%| 验证集:1350 条(15%| 测试集:1350 条(15%)
每完整一轮训练处理批次总数:262================================================================================

【AI模型网络组件初始化】
组件1:线性投影层 Linear(132) 初始化完成
组件2:正弦位置编码 PositionalEncoding 初始化完成
组件32层Transformer编码器(4头注意力,FFN=64)初始化完成
组件4:二分类输出全连接层(含Dropout)初始化完成
【AI模型所有组件组装完毕】
================================================================================

【训练工具初始化信息】
模型总可训练参数量:17,218 个
损失函数:CrossEntropyLoss 二分类交叉熵
优化器:Adam,学习率=0.0005,L2权重衰减 weight_decay=1e-4
================================================================================
========== 正式开始AI模型训练流程(训练+验证+早停) ==========
================================================================================

===== 进入第 1/40 轮完整训练 =====
[轮次1 | 批次 0/262] 批次损失=0.6848 | 批次准确率=50.00%
[轮次1 | 批次30/262] 批次损失=0.6382 | 批次准确率=58.33%
[轮次1 | 批次60/262] 批次损失=0.4756 | 批次准确率=79.17%
[轮次1 | 批次90/262] 批次损失=0.1328 | 批次准确率=100.00%
[轮次1 | 批次120/262] 批次损失=0.1899 | 批次准确率=95.83%
[轮次1 | 批次150/262] 批次损失=0.0996 | 批次准确率=95.83%
[轮次1 | 批次180/262] 批次损失=0.1348 | 批次准确率=95.83%
[轮次1 | 批次210/262] 批次损失=0.0711 | 批次准确率=95.83%
[轮次1 | 批次240/262] 批次损失=0.0306 | 批次准确率=100.00%
★ 验证集准确率创新高!当前最佳:100.00%(已保存模型快照)

----------1轮 训练+验证完整汇总报告 ----------
【训练集】平均损失:0.2382 | 准确率:89.14%
【验证集】平均损失:0.0026 | 准确率:100.00% | Precision:100.00% | Recall:100.00% | F1:100.00%
【混淆矩阵】TP=677 FP=0 FN=0 TN=673
----------------------------------------------------------------------

===== 进入第 2/40 轮完整训练 =====
[轮次2 | 批次 0/262] 批次损失=0.1066 | 批次准确率=95.83%
[轮次2 | 批次30/262] 批次损失=0.0096 | 批次准确率=100.00%
[轮次2 | 批次60/262] 批次损失=0.0330 | 批次准确率=100.00%
[轮次2 | 批次90/262] 批次损失=0.0081 | 批次准确率=100.00%
[轮次2 | 批次120/262] 批次损失=0.0072 | 批次准确率=100.00%
[轮次2 | 批次150/262] 批次损失=0.0058 | 批次准确率=100.00%
[轮次2 | 批次180/262] 批次损失=0.0099 | 批次准确率=100.00%
[轮次2 | 批次210/262] 批次损失=0.0049 | 批次准确率=100.00%
[轮次2 | 批次240/262] 批次损失=0.0046 | 批次准确率=100.00%
验证集无提升,早停等待计数:1/5

----------2轮 训练+验证完整汇总报告 ----------
【训练集】平均损失:0.0300 | 准确率:99.14%
【验证集】平均损失:0.0008 | 准确率:100.00% | Precision:100.00% | Recall:100.00% | F1:100.00%
【混淆矩阵】TP=677 FP=0 FN=0 TN=673
----------------------------------------------------------------------

===== 进入第 3/40 轮完整训练 =====
[轮次3 | 批次 0/262] 批次损失=0.0106 | 批次准确率=100.00%
[轮次3 | 批次30/262] 批次损失=0.0092 | 批次准确率=100.00%
[轮次3 | 批次60/262] 批次损失=0.0098 | 批次准确率=100.00%
[轮次3 | 批次90/262] 批次损失=0.0090 | 批次准确率=100.00%
[轮次3 | 批次120/262] 批次损失=0.0027 | 批次准确率=100.00%
[轮次3 | 批次150/262] 批次损失=0.1143 | 批次准确率=95.83%
[轮次3 | 批次180/262] 批次损失=0.0078 | 批次准确率=100.00%
[轮次3 | 批次210/262] 批次损失=0.0019 | 批次准确率=100.00%
[轮次3 | 批次240/262] 批次损失=0.0061 | 批次准确率=100.00%
验证集无提升,早停等待计数:2/5

----------3轮 训练+验证完整汇总报告 ----------
【训练集】平均损失:0.0208 | 准确率:99.44%
【验证集】平均损失:0.0005 | 准确率:100.00% | Precision:100.00% | Recall:100.00% | F1:100.00%
【混淆矩阵】TP=677 FP=0 FN=0 TN=673
----------------------------------------------------------------------

===== 进入第 4/40 轮完整训练 =====
[轮次4 | 批次 0/262] 批次损失=0.0038 | 批次准确率=100.00%
[轮次4 | 批次30/262] 批次损失=0.0036 | 批次准确率=100.00%
[轮次4 | 批次60/262] 批次损失=0.0030 | 批次准确率=100.00%
[轮次4 | 批次90/262] 批次损失=0.0041 | 批次准确率=100.00%
[轮次4 | 批次120/262] 批次损失=0.0014 | 批次准确率=100.00%
[轮次4 | 批次150/262] 批次损失=0.0092 | 批次准确率=100.00%
[轮次4 | 批次180/262] 批次损失=0.0733 | 批次准确率=95.83%
[轮次4 | 批次210/262] 批次损失=0.0020 | 批次准确率=100.00%
[轮次4 | 批次240/262] 批次损失=0.0018 | 批次准确率=100.00%
验证集无提升,早停等待计数:3/5

----------4轮 训练+验证完整汇总报告 ----------
【训练集】平均损失:0.0200 | 准确率:99.44%
【验证集】平均损失:0.0004 | 准确率:100.00% | Precision:100.00% | Recall:100.00% | F1:100.00%
【混淆矩阵】TP=677 FP=0 FN=0 TN=673
----------------------------------------------------------------------

===== 进入第 5/40 轮完整训练 =====
[轮次5 | 批次 0/262] 批次损失=0.0018 | 批次准确率=100.00%
[轮次5 | 批次30/262] 批次损失=0.0355 | 批次准确率=95.83%
[轮次5 | 批次60/262] 批次损失=0.0012 | 批次准确率=100.00%
[轮次5 | 批次90/262] 批次损失=0.0036 | 批次准确率=100.00%
[轮次5 | 批次120/262] 批次损失=0.0012 | 批次准确率=100.00%
[轮次5 | 批次150/262] 批次损失=0.0016 | 批次准确率=100.00%
[轮次5 | 批次180/262] 批次损失=0.0019 | 批次准确率=100.00%
[轮次5 | 批次210/262] 批次损失=0.0138 | 批次准确率=100.00%
[轮次5 | 批次240/262] 批次损失=0.0011 | 批次准确率=100.00%
验证集无提升,早停等待计数:4/5

----------5轮 训练+验证完整汇总报告 ----------
【训练集】平均损失:0.0092 | 准确率:99.79%
【验证集】平均损失:0.0002 | 准确率:100.00% | Precision:100.00% | Recall:100.00% | F1:100.00%
【混淆矩阵】TP=677 FP=0 FN=0 TN=673
----------------------------------------------------------------------

===== 进入第 6/40 轮完整训练 =====
[轮次6 | 批次 0/262] 批次损失=0.0036 | 批次准确率=100.00%
[轮次6 | 批次30/262] 批次损失=0.0163 | 批次准确率=100.00%
[轮次6 | 批次60/262] 批次损失=0.0020 | 批次准确率=100.00%
[轮次6 | 批次90/262] 批次损失=0.0019 | 批次准确率=100.00%
[轮次6 | 批次120/262] 批次损失=0.0014 | 批次准确率=100.00%
[轮次6 | 批次150/262] 批次损失=0.0008 | 批次准确率=100.00%
[轮次6 | 批次180/262] 批次损失=0.0015 | 批次准确率=100.00%
[轮次6 | 批次210/262] 批次损失=0.0080 | 批次准确率=100.00%
[轮次6 | 批次240/262] 批次损失=0.0019 | 批次准确率=100.00%
验证集无提升,早停等待计数:5/5

----------6轮 训练+验证完整汇总报告 ----------
【训练集】平均损失:0.0089 | 准确率:99.75%
【验证集】平均损失:0.0002 | 准确率:100.00% | Precision:100.00% | Recall:100.00% | F1:100.00%
【混淆矩阵】TP=677 FP=0 FN=0 TN=673
----------------------------------------------------------------------

===== 触发早停!验证集连续5轮无提升,停止训练 =====
================================================================================
【训练流程全部结束】
全程最佳验证集准确率:100.00%
================================================================================

【模型已保存】最佳模型权重 → sensor_model_best.pth
================================================================================

========== 测试集最终评估(模型全程未见过的数据) ==========
【测试集最终结果】
平均损失:0.0002
准确率 Accuracy:100.00%
精确率 Precision:100.00%(预测故障中真正故障的比例)
召回率 Recall:100.00%(真实故障中被正确检出的比例)
F1 分数:100.00%(Precision和Recall的调和平均)
混淆矩阵:TP=683 FP=0 FN=0 TN=667
================================================================================

【训练曲线图已保存】→ training_curves.png
图表包含4个子图:损失曲线、准确率曲线、P/R/F1曲线、混淆矩阵
================================================================================

===================== 设备故障检测交互测试面板 =====================
使用说明:
1. 输入16个数字,英文逗号 , 分隔,代表16个连续传感器电压值
2. 输入 exit 即可退出检测程序
3. 正常波形示例:1.8,2.1,2.5,1.9,2.2,1.5,2.8,2.3,1.9,2.0,1.4,2.6,1.7,2.2,1.3,2.4
4. 故障波形示例:1.1,2.3,5.2,2.0,1.8,4.6,2.1,2.5,1.7,2.2,1.5,2.7,4.9,2.3,1.6,2.1
====================================================================

请输入16个传感器电压数值:1.8,2.1,2.5,1.9,2.2,1.5,2.8,2.3,1.9,2.0,1.4,2.6,1.7,2.2,1.3,2.4
------------ AI故障检测详细报告 ------------
输入传感器完整序列:[1.8, 2.1, 2.5, 1.9, 2.2, 1.5, 2.8, 2.3, 1.9, 2.0, 1.4, 2.6, 1.7, 2.2, 1.3, 2.4]
AI对【设备正常】打分:4.2948
AI对【设备故障】打分:-3.9801
最终判定:【设备运行状态良好,各项指标正常】
--------------------------------------------

请输入16个传感器电压数值:1.1,2.3,5.2,2.0,1.8,4.6,2.1,2.5,1.7,2.2,1.5,2.7,4.9,2.3,1.6,2.1
------------ AI故障检测详细报告 ------------
输入传感器完整序列:[1.1, 2.3, 5.2, 2.0, 1.8, 4.6, 2.1, 2.5, 1.7, 2.2, 1.5, 2.7, 4.9, 2.3, 1.6, 2.1]
AI对【设备正常】打分:-4.5312
AI对【设备故障】打分:4.3450
最终判定:【设备存在异常,判定为故障,建议停机检修!】
--------------------------------------------

总结

本文从传统工业工程师的视角出发,用一段完整的PyTorch代码演示了Transformer在传感器故障检测中的应用。核心要点:

  1. AI不是魔法,是"从数据中学规则"——和传统阈值法的本质区别仅在于规则来源
  2. Transformer的核心优势是"看全局关联"——不是判断单个数值大小,而是理解16个时间步之间的协同模式
  3. 完整流程:数据→模型→训练→评估→推理——每一步都有明确的工程动机
  4. 当前代码是学习原型,不是工业系统——从原型到产线还需要数据真实化、工程化、可靠性三个层面的补充

希望这篇文章能帮助传统工业工程师建立对AI模型的直觉理解。理解"为什么"比记住"怎么做"重要得多。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐