news 2026/9/17 7:58:53

Anomalib 中 EfficientAd 模型全解析:Student-Teacher 蒸馏架构、参数详解与训练实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Anomalib 中 EfficientAd 模型全解析:Student-Teacher 蒸馏架构、参数详解与训练实战

Anomalib 中 EfficientAd 模型全解析:Student-Teacher 蒸馏架构、参数详解与训练实战

【免费下载链接】anomalibAn anomaly detection library comprising state-of-the-art algorithms and features such as experiment management, hyper-parameter optimization, and edge inference.项目地址: https://gitcode.com/GitHub_Trending/an/anomalib

EfficientAd 是 anomalib 内置的毫秒级低延迟异常检测算法,基于「预训练教师网络 + 轻量学生网络 + 自编码器」的三分支 Student-Teacher 蒸馏架构。本文以 anomalib 官方模型文档页(自动抽取自 EfficientAd 与 EfficientAdModel 两个类的完整文档字符串)为主体,逐参数解读 Lightning 封装层的配置项、PyTorch 网络结构、训练期的三项损失与推理期的分位数归一化机制,并结合仓库中的示例脚本与配置文件给出可直接复制的训练命令与 API 用法。读完后你可以独立完成 EfficientAd 在 MVTecAD 数据集上的训练、调参与推理流程。

一、算法定位与核心思想

EfficientAd 实现自论文《EfficientAd: Accurate Visual Anomaly Detection at Millisecond-Level Latencies》(见 lightning_model.py 的模块 docstring,其 Paper 字段即指向该论文),模型类型为像素级分割(Segmentation),即同时输出逐像素异常图与图像级异常分数。

从 模块文档 与 torch_model.py 的描述看,整个模型由三部分构成:

  1. 预训练教师网络(Teacher):一个预训练后冻结的 Patch Description Network(PDN),负责提取「正常外观」的参考特征;
  2. 学生网络(Student):与教师同构的轻量网络,通过知识蒸馏逼近教师的输出;
  3. 自编码器(AutoEncoder):由 6 层卷积编码器 + 8 层卷积解码器组成,额外提供一条重建通路。

异常判定基于两组输出差异:

  • 局部异常:教师与学生的特征图差异(distance_st),捕捉局部纹理/结构偏移;
  • 全局异常:学生与自编码器的特征图差异(distance_stae),捕捉全局语义偏移。

推理时最终异常图正是这两路信号的等权混合(见下文compute_mapsforward的代码走读)。

二、三分支网络架构的源码级拆解

2.1 教师/学生:Small 与 Medium 两档 PDN

EfficientAdModelSize 是一个字符串枚举,仅有两个取值:S = "small"M = "medium"。在 EfficientAdModel.init中,两个尺寸对应不同的 PDN 变体:

  • S→ 教师与学生均为 SmallPatchDescriptionNetwork:4 层卷积(3→128→256→256→out_channels,核大小 4/4/3/4)+ 2 次平均池化下采样;
  • M→ 教师与学生均为 MediumPatchDescriptionNetwork:6 层卷积(3→256→512→512(1x1)→512→out_channels→out_channels(1x1))+ 2 次平均池化。

一个容易忽视但很关键的细节是师生输出通道数的不对称

# src/anomalib/models/image/efficient_ad/torch_model.py L483-L489 self.teacher = MediumPatchDescriptionNetwork(out_channels=teacher_out_channels, padding=padding).eval() self.student = MediumPatchDescriptionNetwork(out_channels=teacher_out_channels * 2, padding=padding) # 或 Small 档 self.teacher = SmallPatchDescriptionNetwork(out_channels=teacher_out_channels, padding=padding).eval() self.student = SmallPatchDescriptionNetwork(out_channels=teacher_out_channels * 2, padding=padding)

学生网络的输出通道数是教师的2 倍,其中前teacher_out_channels个通道用于模仿教师(学生-教师一致性),后半段通道则专门留给自编码器特征的一致性(后文loss_stae使用student_output[:, self.teacher_out_channels:])。教师构造后立即进入eval()且推理/训练全程在torch.no_grad()下运行,属于冻结参考模型。

2.2 自编码器:Encoder + Decoder

AutoEncoder 的输出目标是与教师特征对齐的 384 通道特征图,而非重建 RGB 图像:

  • Encoder:6 层卷积将(N,3,256,256)压缩到(N,64,1,1),前 5 层 stride=2 逐层减半空间尺寸,最后一层 8x8 卷积直接压到 1x1;
  • Decoder:8 层卷积 + 双线性上采样 + 6 个 Dropout(p=0.2),从(N,64,H/64,W/64)一路恢复出(N,384,64,64)级别的特征图(对 256x256 输入)。

2.3 ImageNet 统计量归一化的隐藏前提

imagenet_norm_batch 使用标准 ImageNet 均值[0.485, 0.456, 0.406]、标准差[0.229, 0.224, 0.225]对输入做通道级归一化。这个归一化在网络内部(forward第一步)完成,而不是放在数据变换里——configure_pre_processor的 docstring 明确写着 "ImageNet normalization is applied in the forward pass, not here"(见 lightning_model.py)。因此用户自定义的pre_processor不应包含Normalize变换,否则会被训练启动检查拦截(见 4.1 节)。

三、EfficientAd Lightning 类:完整参数说明

文档页 efficient_ad.md 的automodule指令渲染的就是 EfficientAd 类的完整 Args 文档。该继承自AnomalibModule的 Lightning 封装类全部构造参数如下:

参数类型默认值说明
imagenet_dirPath \| str"./datasets/imagenette"ImageNet 风格数据集目录;不存在时自动下载 ImageNette2(见 3.1 节)
teacher_out_channelsint384教师网络(及 AE)卷积输出通道数;学生网络自动取 2 倍
model_sizeEfficientAdModelSize \| strEfficientAdModelSize.S模型档位,"small""medium";字符串会自动转为枚举(L144-L146)
lrfloat0.0001Adam 优化器学习率
weight_decayfloat0.00001优化器权重衰减
paddingboolFalsePDN/AE 卷积层是否启用 padding;影响特征图空间尺寸
pad_mapsboolTrue仅当padding=False时生效:把输出异常图 pad 到与padding=True时一致的尺寸,再插值回原图尺寸(见 compute_maps)
pre_processorPreProcessor \| boolTrue输入预处理器;默认只做Resize((256,256), antialias=True)
post_processorPostProcessor \| boolTrue预测后处理器
evaluatorEvaluator \| boolTrue验证/测试指标计算器
visualizerVisualizer \| boolTrue可视化器

此外有两个隐式固定值

  • self.batch_size = 1(L153),注释标明 "imagenet dataloader batch_size is 1 according to the paper",即 ImageNette 分支始终按 batch=1 取图;
  • self.model_size统一归一化为EfficientAdModelSize枚举实例(L146)。

最小可用示例(来自类 docstring):

>>> from anomalib.models import EfficientAd >>> model = EfficientAd( ... imagenet_dir="./datasets/imagenette", ... model_size="s", ... lr=1e-4 ... )

3.1 预训练权重与 ImageNette 的自动下载

prepare_pretrained_model 在训练启动时检查get_pretrained_weights_dir()下是否存在efficientad_pretrained_weights/目录,缺失时按 WEIGHTS_DOWNLOAD_INFO 下载并解压efficientad_pretrained_weights.zip(带 hashsum 校验),随后按档位加载pretrained_teacher_{small|medium}.pth到冻结的教师网络。

prepare_imagenette_data 则在imagenet_dir不存在时按 IMAGENETTE_DOWNLOAD_INFO 自动下载imagenette2.tgz,并用如下变换管线喂给ImageFolder(注意先放大 2 倍再中心裁剪,属于论文里的标准增强):

Compose([ Resize((image_size[0] * 2, image_size[1] * 2)), RandomGrayscale(p=0.3), CenterCrop((image_size[0], image_size[1])), ToTensor(), ])

ImageNette 图在损失函数中的角色是惩罚项:防止学生在异常区域也去模仿教师的输出(模型 README 的 "To hinder the student from imitating the teacher on anomalies, Imagenet images are used in the loss function"),因此实际训练环境需要能访问外网完成这两次一次性下载,或在本地提前放置好这两个资源目录。

四、训练流程深度走读

4.1 训练启动的硬性约束(on_train_start)

on_train_start 是理解 EfficientAd 工程约束的最佳入口,它依次执行:

  1. 强制train_batch_size == 1,否则抛出ValueError("train_batch_size for EfficientAd should be 1.")。这一点也被集成测试明确遵循——tests/integration/model/test_models.py 中专门为 efficient_ad 设置train_batch_size=1,并注释说明 "EfficientAd requires that the batch size be lesser than the number of images in the dataset"(否则 LR 调度的 step_size 会退化为 0);
  2. 禁止数据变换中含Normalize(因为归一化在网络内部做);
  3. 从训练集首个 batch 探测真实image_size,据此准备预训练教师与 ImageNette loader;
  4. 若模型未保存过统计量,则调用 teacher_channel_mean_std 遍历整个训练集,用 Welford 式的求和累加计算教师激活的逐通道均值与标准差(输出形状(1, C, 1, 1)),存入self.model.mean_stdParameterDict 供前向归一化教师输出使用(compute_student_teacher_distance 中的if self.is_set(self.mean_std)分支)。

4.2 优化器与 LR 调度

configure_optimizers 值得细看:

  • 优化器为torch.optim.Adam只优化学生 + 自编码器的参数(教师冻结);
  • max_epochsmax_steps均未定义则直接抛错("A finite number of steps or epochs must be defined");
  • 由于 Lightning 在任一上限先触达时停止训练,代码会手动推算实际总步数num_steps(取max_stepsmax_epochs * 每 epoch 步数的较小者),再配置StepLR(optimizer, step_size=int(0.95 * num_steps), gamma=0.1)——即在训练进度 95% 处把学习率衰减 10 倍

4.3 三个损失项的构成

training_step 中,每个训练步会同时取一个业务图像 batch 与一个 ImageNette 图像(无限迭代器,StopIteration时自动重置),调用 compute_losses,返回三项损失后相加:

损失计算方式作用
loss_stdistance_st(归一化教师特征 − 学生前半段特征)的平方,经 reduce_tensor_elems 压到至多2**24个元素(规避torch.quantile的上限)后,取 0.999 分位d_hard,对distance_st >= d_hard的「困难样本」取均值(loss_hard),加上学生处理 ImageNette 图像时前半段特征幅值的均方loss_penalty蒸馏教师 + 抑制学生对异常/未知区域的过拟合
loss_ae对随机增广图像(choose_random_aug_image 以 U(0.8,1.2) 系数随机做亮度/对比度/饱和度之一)取归一化教师特征与 AE 输出之差的平方均值让自编码器逼近教师的「正常」表征
loss_stae同一增广图上 AE 输出与学生后半段特征之差的平方均值把学生后半段特征与 AE 表征对齐,为全局异常判定服务

三项分别以train_st/train_ae/train_stae记入日志,train_loss为总和。注意增广仅作用于 AE 分支,学生-教师分支用的是原始业务图像。

五、验证、推理与异常图归一化

5.1 分位数校准(on_validation_start)

on_validation_start 会调用 map_norm_quantiles:遍历验证集,只取gt_label == 0的正常样本,用get_maps(normalize=False)计算两路原始异常图,分别求90%(qa)与 99.5%(qb)分位数(_get_quantiles_of_maps,元素数超过16777216时随机子采样),写入self.model.quantiles

从源码结构看,这是把「正常外观的异常图分布」固化为两个校准锚点,使得推理期异常图被映射到一个近似[0, 1]的可比尺度。这也解释了trainer_arguments为何返回{"num_sanity_val_steps": 0}(L452-L460)——sanity check 阶段无法提供带标签的正常验证样本,分位数校准被推迟到真正的验证钩子中完成。

5.2 异常图合成与最终分数

推理路径为forwardself.training == False分支,torch_model.py L574-L580)→ compute_maps:

map_st = torch.mean(distance_st, dim=1, keepdim=True) # 教师-学生 map_stae = torch.mean((ae_output - student_output[:, teacher_out_channels:]) ** 2, dim=1, keepdim=True) # pad_maps=True 且 padding=False 时,先各补 4 像素再双线性插值回原图尺寸 # 已校准时用分位数归一化: map_st = 0.1 * (map_st - qa_st) / (qb_st - qa_st) map_stae = 0.1 * (map_stae - qa_ae) / (qb_ae - qa_ae) anomaly_map = 0.5 * map_st + 0.5 * map_stae # 两路等权融合 pred_score = torch.amax(anomaly_map, dim=(-2, -1)) # 图像级分数 = 全图最大值

返回类型为 InferenceBatch(pred_score+ 全分辨率anomaly_map),并注册为 One-Class 学习类型(learning_type属性返回LearningType.ONE_CLASS,L462-L469),即训练只需要正常样本。

六、配置与运行方式

6.1 YAML 配置

仓库提供了完整参考配置 examples/configs/model/efficient_ad.yaml:

model: class_path: anomalib.models.EfficientAd init_args: teacher_out_channels: 384 model_size: small lr: 0.0001 weight_decay: 1.0e-05 padding: false pad_maps: true trainer: max_epochs: 1000 max_steps: 70000

注意trainer.max_steps: 70000max_epochs: 1000同时出现,实际步数由二者先触达者决定(4.2 节的num_steps推算逻辑正是为此设计)。集成测试里同样采用max_steps=70000(tests/integration/model/test_models.py)。

6.2 CLI 训练

模型自带 README(src/anomalib/models/image/efficient_ad/README.md)给出的官方命令行:

anomalib train --model EfficientAd --data anomalib.data.MVTecAD --data.category <category> --data.train_batch_size 1

其中--data.train_batch_size 1不是可选项,而是on_train_start的硬校验要求。仓库示例脚本 examples/cli/03_models/efficient_ad.sh 还覆盖了自定义参数与超参搜索的用法:

# 自定义模型参数 anomalib train \ --model efficient_ad \ --model.teacher_out_channels 384 \ --model.model_size m \ --model.lr 1e-4 # 完整训练流水线 anomalib train \ --model efficient_ad \ --data.category bottle \ --trainer.max_epochs 20 \ --trainer.accelerator auto \ --trainer.devices 1 \ --trainer.default_root_dir results/efficient_ad

6.3 Python API 训练

examples/api/03_models/efficient_ad.py 展示了等价的 API 用法,并演示了自定义预处理器:

from torchvision.transforms.v2 import Compose, Resize from anomalib.data import MVTecAD from anomalib.engine import Engine from anomalib.models import EfficientAd from anomalib.pre_processing import PreProcessor # 可选:自定义输入尺寸(默认预处理器只做 Resize((256,256))) transform = Compose([Resize(size=(512, 512))]) pre_processor = PreProcessor(transform=transform) model = EfficientAd( teacher_out_channels=384, model_size="medium", lr=1e-4, pre_processor=pre_processor, ) datamodule = MVTecAD( root="./datasets/MVTecAD", category="bottle", train_batch_size=1, # EfficientAd 硬性要求 ) engine = Engine(max_epochs=20, accelerator="auto", devices=1) engine.fit(model=model, datamodule=datamodule)

模型可通过get_model工厂按名称实例化,tests/unit/models/test_model_utils.py 中有get_model("EfficientAd")的实例类型断言。

七、官方基准结果(MVTec AD,seed=42)

模型 README 中给出 EfficientAd-S / EfficientAd-M 在 MVTec AD 全部 15 个类别上的基准,均为 seed42下测得。

7.1 Image-Level AUC

AvgCarpetGridLeatherTileWoodBottleCableCapsuleHazelnutMetal NutPillScrewToothbrushTransistorZipper
EfficientAd-S0.9820.9821.0000.9971.0000.9861.0000.9520.9500.9520.9790.9870.9600.9970.9990.994
EfficientAd-M0.9750.9720.9981.0000.9990.9840.9910.9450.9570.9480.9890.9260.9751.0000.9650.971

7.2 Image F1 Score

AvgCarpetGridLeatherTileWoodBottleCableCapsuleHazelnutMetal NutPillScrewToothbrushTransistorZipper
EfficientAd-S0.9700.9661.0000.9951.0000.9751.0000.9070.9560.8970.9780.9820.9440.9840.9880.983
EfficientAd-M0.9660.9770.9911.0000.9940.9670.9840.9220.9690.8840.9840.9520.9551.0000.9290.979

从这张表可以看到 S 档平均略优于 M 档,且两类指标上 S 档在小类别(Toothbrush、Transistor 等)上更稳;实际选型时可以按 6.2 节的超参搜索方式对model_size做小规模对比验证。

八、工程要点与常见坑位速查

综合上文源码证据,落地 EfficientAd 时需要记住的要点:

  1. train_batch_size必须为 1on_train_start会直接抛错;集成测试注释还补充了第二个隐含约束——batch size 需小于数据集图像数,否则 LR 调度 step 退化为 0(tests/integration/model/test_models.py);
  2. 预处理器中不要放Normalize:归一化在网络内部以 ImageNet 统计量完成,重复归一化会触发启动校验失败;
  3. 一次性外网下载:训练会自动拉取教师权重(efficientad_pretrained_weights.zip)与 ImageNette(imagenette2.tgz,约数百 MB,均有 hashsum 校验);离线环境需按 lightning_model.py 中的DownloadInfo提前在imagenet_dirget_pretrained_weights_dir()下放好对应文件;
  4. 验证集必须含标签:分位数校准只使用gt_label == 0的验证样本;缺少带标签验证集时quantiles不会被写入,推理期compute_maps的归一化分支(is_set(self.quantiles)判断)将不生效,异常图尺度不再经过 90/99.5 分位校准;
  5. 序列化安全__getstate__会剔除不可 pickle 的imagenet_iterator(L384-L392),保存/检查点模型时不会因此报错;
  6. 训练上限必须定义max_epochsmax_steps至少给一个,否则configure_optimizersValueError

相关延伸阅读:模型参考文档页 efficient_ad.md、配置说明 examples/configs/README.md、数据模块配置 examples/configs/data/mvtec.yaml,以及基准测试工具 tests/integration/pipelines/test_benchmark.py。

小结

EfficientAd 在 anomalib 中的实现把论文的三个关键设计全部落到了可复现的代码路径上:冻结教师 + 双通道学生的蒸馏、ImageNette 惩罚项防异常过拟合、以及基于验证集正常样本 90/99.5 分位数的异常图校准。配合batch_size=1train_batch_size=1max_steps上限这三条工程约束,以及 YAML/CLI/API 三种一致的配置入口(配置示例、CLI 示例、API 示例),它构成了 anomalib 中面向低延迟产线检测场景的一条完整可运行链路。

【免费下载链接】anomalibAn anomaly detection library comprising state-of-the-art algorithms and features such as experiment management, hyper-parameter optimization, and edge inference.项目地址: https://gitcode.com/GitHub_Trending/an/anomalib

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 7:57:52

银行排队叫号系统设计:核心表、状态机与队列实现

简介&#xff1a;基于Java与JSP技术的银行排队叫号系统毕业设计论文&#xff0c;面向计算机相关专业学生及Web应用开发人员&#xff0c;针对传统业务管理效率低、客户排队体验差等现实问题&#xff0c;完整呈现了从需求分析到系统实现的全过程。文档遵循软件工程常规流程&#…

作者头像 李华
网站建设 2026/9/17 7:57:20

2026年AI编程工具全景解析:五条主线与实战选型指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 7:55:57

6Valley 14.2多商户跨境电商PHP源码部署与二次开发实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 7:54:32

AI协作的三次范式跃迁与Harness工程实践

1. 从指令编写到系统整合的范式迁移去年夏天&#xff0c;当我第一次尝试用自然语言描述需求来生成代码时&#xff0c;需要反复调整七八次prompt才能得到可用的结果。而今天&#xff0c;我已经可以用一套标准化的工具链&#xff0c;将AI能力无缝嵌入到持续交付流程中——这个转变…

作者头像 李华
网站建设 2026/9/17 7:54:14

Vivado/Vitis 2024.2升级报错“找不到现有安装”:原因与解决指南

升级 Vivado/Vitis 2024.2 到 2024.2.1 的时候&#xff0c;安装器弹出“找不到现有安装”&#xff0c;踩过这个坑的人应该不少。更难受的是&#xff0c;这个提示往往不是出现在刚开始&#xff0c;而是在你等了几分钟安装器初始化之后才突然冒出来&#xff0c;让人很懵&#xff…

作者头像 李华