文章目录
-
- [01 本篇要解决什么问题](#01 本篇要解决什么问题)
- [02 背景:异常标签的含义由任务定义决定](#02 背景:异常标签的含义由任务定义决定)
- [03 实操:把训练、选模型与定阈值分开](#03 实操:把训练、选模型与定阈值分开)
- [04 一个具体例子:把一张新图变成异常判断](#04 一个具体例子:把一张新图变成异常判断)
- [05 常见问题与自测](#05 常见问题与自测)
- [06 本篇回顾与下一步](#06 本篇回顾与下一步)
01 本篇要解决什么问题
前面的监督任务通常给每个训练样本标明类别。如果手头主要有"正常样本",能否先学习正常图像,再把不熟悉的输入找出来?
本篇以Fashion-MNIST的一类服装为正常,其他类别为异常,用卷积自编码器学习重建正常图,再把每张图的重建误差作为异常分数。
- 本篇关键词:自编码器、瓶颈表示、正常类训练、异常分数、验证阈值、AUROC。
- 完成标志:只用正常类更新参数,完成验证选模型、阈值校准和固定阈值测试。
- 场景边界:这是类别外识别练习,不是带像素缺陷标注的工业异常检测基准。
02 背景:异常标签的含义由任务定义决定
Fashion-MNIST作者项目提供28×28灰度服装图,官方训练60000张、测试10000张。这里默认类别0,即T-shirt/top,作为正常;其余九类记为异常。
原始服装标签与新任务标签不同:原始类别用于查数据,新的异常标签固定为正常0、异常1。模型不再输出十类概率,而是先重建输入,再计算一个分数。
从官方训练集按类别划出10%验证数据,默认得到5400张正常训练图、6000张混合验证图;官方10000张测试图保留,其中正常1000、异常9000。混合验证集用于阈值校准,因此完整流程并不是完全不使用异常标签。
03 实操:把训练、选模型与定阈值分开
(1)只让正常图参与反向传播
powershell
python examples/download_data.py --dataset fashion
下载使用作者维护的HTTPS资源,Torchvision检查资源MD5。若前面第13篇的数据已经存在,可把--data指向相同根目录。
训练集合先从原始训练数据中划出,再筛出正常类;测试图不会被放回训练。完整的源索引与正常类别会保存在split_manifest.json,便于检查是否存在集合交叉。
(2)瓶颈结构让模型学习受限重建
编码器把28×28图像压到低维向量,解码器把它还原到原尺寸。默认通道16、潜变量32,输出Sigmoid限定到0~1。
python
import torch
from torch import nn
from torch.nn import functional as F
class Autoencoder(nn.Module):
def __init__(self,width=16,latent=32):
super().__init__()
self.encoder=nn.Sequential(nn.Conv2d(1,width,3,2,1),nn.ReLU(),
nn.Conv2d(width,width*2,3,2,1),nn.ReLU(),nn.Flatten(),
nn.Linear(width*2*7*7,latent))
self.fc=nn.Linear(latent,width*2*7*7)
self.decoder=nn.Sequential(nn.ConvTranspose2d(width*2,width,4,2,1),nn.ReLU(),
nn.ConvTranspose2d(width,1,4,2,1),nn.Sigmoid())
self.width=width
def forward(self,x):
z=self.fc(self.encoder(x)).reshape(-1,self.width*2,7,7)
return self.decoder(z)
这里没有从输入直接跨到输出的长跳跃连接,避免轻易复制全部输入。瓶颈只是建模选择,并不保证"只会重建正常图":网络仍可能把某些类别外样本重建得很好。
(3)重建损失与样本分数的归约不同
训练时:
python
reconstruction = model(normal_images)
loss = torch.nn.functional.mse_loss(reconstruction, normal_images)
推理时,需要为每个样本单独计算平均误差:
python
scores = (reconstruction - images).square().mean(dim=(1, 2, 3))
第二种写法保留batch维,输出[N]。如果直接对全部维度求平均,会把一批图压成一个数字,无法再逐图排序或判断。
分数越大越倾向异常。这个方向必须与AUROC、阈值判断和日志保持一致,不能一处取相反数,另一处仍按原方向解释。
(4)选模型只看正常验证重建误差
每轮在验证集推理,取其中正常样本的平均MSE,越低越好,保存为best.pt。这一步不以测试AUROC挑轮次,也不让异常验证图参与参数更新。
读取最优权重后,才在混合验证集上选择异常判别阈值。本版遍历验证分数对应的候选阈值,使异常类别F1最大;并列保留更大的阈值。
阈值规则固定为score >= threshold判异常,并写入检查点。测试阶段直接读取,不重新寻找一个使测试结果更漂亮的阈值。
(5)AUROC与固定阈值指标各有作用
AUROC接口说明对应的是连续分数的排序评价。本工程用平均秩处理相同分数,自行实现二分类AUROC;标签只有一类时返回null。
同时报告固定阈值下的Precision、Recall、F1与TP/FP/FN/TN。AUROC不能告诉我们某个阈值下会误报多少,F1又会受异常比例影响,二者应该结合使用。
小规模检查会让验证、测试的正常和异常各占一半,以保留两类;完整数据是1:9。因此小规模F1不能直接外推到完整数据或现实业务异常比例。

图:原创示意。正常训练、正常验证选权重、混合验证定阈值分别承担不同作用。
04 一个具体例子:把一张新图变成异常判断
小规模运行:
powershell
python examples/train_anomaly.py --epochs 2 --width 4 --latent 8 --limit-train 64 --limit-val 40 --limit-test 40
python examples/visualize_run.py --run artifacts/23_anomaly
完整教学配置的起点:
powershell
python examples/train_anomaly.py --epochs 20 --normal-class 0 --output artifacts/23_anomaly_full
数据不大,可以先用CPU;有GPU可追加--device cuda。主入口如下,阈值与评价实现全文见公共代码附录。
python
"""Chapter23: normal-only AE training, mixed-validation threshold calibration."""
from vision_train import parser_for,train_project
if __name__=='__main__':
train_project('anomaly',parser_for('anomaly','artifacts/23_anomaly').parse_args())
准备一张与Fashion-MNIST显示约定相近的服装图后:
powershell
python examples/predict_image.py --checkpoint artifacts/23_anomaly/best.pt --image data/my_fashion.png --output artifacts/23_predict
脚本转为灰度28×28,输出重建图、误差数组、score、threshold和is_anomaly。误差图可帮助查看重建差异,但这里没有像素级缺陷监督,不能把高误差区域自动解释为真实缺陷定位。
出现高误差时,还应检查背景、尺度、亮度与预处理是否偏离训练分布;它们同样会造成模型不熟悉的输入。改变正常类、图像范围或模型权重后,应重新在验证集校准阈值。
05 常见问题与自测
问题1:异常图片可以混进训练一起重建吗?
本篇的假设是正常类训练,混入异常会改变任务设置,需要另行说明。
问题2:MSE很大就一定有工业缺陷吗?
不能这样解释。这里的异常按服装类别定义,重建误差也可能受输入分布变化影响。
问题3:为什么不能在测试集上选阈值?
测试就参与了决策调节,结果不再反映固定模型与规则的独立评价。
问题4:AUROC高,固定阈值F1为什么可能低?
排序能力、阈值位置和类别比例是不同因素,需要分别分析。
问题5:这套流程完全没有用异常标签吗?
训练参数只用正常图,但混合验证阈值校准使用异常标签,应如实说明。
06 本篇回顾与下一步
异常检测不仅是"训练一个自编码器",还包括分数方向、阈值来源和集合边界。将三者写清楚,结果才有可解释的使用条件。
| 中文 | 常见英文或写法 |
|---|---|
| 自编码器 | Autoencoder / AE |
| 瓶颈表示 | Bottleneck Representation |
| 异常分数 | Anomaly Score |
| 阈值校准 | Threshold Calibration |
下一篇从重建已有输入,转向从随机潜变量生成新数字图像。