医学图像异常检测这个方向做过几年的人都有个共同的体感工业界那套在 MVTec AD 上跑得飞起的方法搬到 X 光、脑 MRI、眼底图上精度经常一下掉十几二十个点。到底是方法不行还是评估口径压根不统一这个问题困扰了很多人。大家各用各的数据划分、各挑各的指标论文里报出来的数字看着都挺漂亮可你真去复现发现根本不是那么回事。MedIAnomaly 这篇比较研究的意义就在这里它把医学图像异常检测这件散装的事尽可能塞进一个统一的框架里去量。它属于无监督异常检测在医学影像上的系统性基准工作目标是回答现有的主流方法迁移到医学图像到底行不行、哪一类更抗打、评估该怎么定。适合刚入坑异常检测的算法同学、做医学影像产品落地的工程师以及正在挑 baseline 准备写论文的研究生。下面我按自己的理解把这篇工作的设计逻辑、实现细节和坑掰开揉碎讲一遍。1. 医学图像异常检测为什么需要一把统一的尺子1.1 工业异常检测和医学影像根本不是同一个任务先得把背景说清楚不然后面全是空谈。工业异常检测有个标杆数据集叫 MVTec AD它定义了这样一个设定给你一堆正常的工业产品图像模型要学会判断测试图里有没有缺陷并且把缺陷区域圈出来。这个设定之所以能成立是因为工业场景有一堆隐含前提——相机位置固定、光照稳定、产品形态高度一致缺陷主要是划痕、脏污、缺料、变形这几类形态相对可枚举。医学图像把这些前提全打破了。同一个器官在不同人身上形状、大小、纹理都不一样正常本身的方差就极大。一个肺部结节是异常一块炎症渗出也是异常一个陈旧瘢痕严格说也能算异常但它们在同一张胸片上可能长得完全不像。更麻烦的是医学异常的边界往往模糊小病灶可能就几个像素还容易和血管横断面、组织褶皱混淆。所以你拿工业上除正常以外皆为异常的思路硬套医学会立刻遇到两个问题正常样本的分布太宽异常样本的形态太杂模型很难学到一条清晰的分界线。这就是为什么医学图像异常检测不能简单照搬工业方案也是 MedIAnomaly 这类基准要做医学专用评估的根本原因。1.2 这篇工作想回答的三个核心问题我读完这篇工作觉得它其实就是在替整个社区问三句话。第一句现成的异常检测方法尤其是那些在工业数据集上拿到 SOTA 的直接迁移到医学图像上性能会掉多少这不是一句会掉就能打发的得用统一协议量出具体差距。第二句把大家放在同一个训练/测试协议、同一套指标下方法之间的真实排名还和原论文一致吗很多论文报的漂亮数字是依赖了自己精心调过的预处理和划分一旦统一可能就露馅了。第三句医学场景里重构类、嵌入类、合成异常类这几大流派到底谁更合适尤其合成异常这条路在工业上很吃香因为可以人为造缺陷可医学异常你能随便贴吗贴出来像不像真的这些都得验证。这三个问题看似朴素但恰恰是落地时最要命的。你选 baseline、选方案其实就是在回答它们。1.3 评估维度拆开看才知道差在哪MedIAnomaly 的评估不是只报一个 AUROC 就完事它把维度拆得比较细。首先是图像级和像素级两个层次图像级判断这张图有没有病像素级判断病在哪、圈得准不准。这两件事在实际产品里对应完全不同的功能图像级相当于筛查报警像素级相当于辅助定位不能混为一谈。其次是单类别和多类别两种设定。单类别就是一个模型只学一种正常模式比如专门做脑 MRI 的多类别是一个模型同时覆盖多个器官/多种模态。多类别更接近真实临床医院设备型号多、检查项目杂但难度也陡增因为不同模态的正常分布差异巨大。第三是预训练权重的影响用不用 ImageNet 预训练结果可能天差地别。这三个维度叠在一起才构成一个相对完整的评估画像。只看单一指标的论文其实信息量是很有限的。这里给个实操提醒如果你要复现任何异常检测工作第一件事就是确认它的数据划分和指标口径别急着跑代码。口径不对跑出来的数字没有可比性。2. MedIAnomaly 的整体设计思路与数据组织2.1 数据集的选择逻辑覆盖多模态、多器官、多任务一个基准好不好用一半看数据集选得全不全。医学图像异常检测不能只用胸片一种因为不同成像模态的异常表现差异太大你只用一种数据得出的结论推广性很差。从我了解到的这份基准设计看它大致覆盖了几大类胸部 X 光如 RSNA 相关数据、VinDr-CXR 这类、脑部 MRIBraTS 系列、眼底/视网膜图像OCT、糖网眼底数据、乳腺钼靶等。选择标准基本围绕三点有公开可获取的标注、异常种类有代表性、数据量足以支撑统计意义上的比较。下面这个表是我按常见实践整理的帮助理解不同模态对异常检测的挑战点在哪具体数据集归属以论文原文为准成像模态典型异常对检测的主要挑战像素标注难度胸部 X 光结节、渗出、气胸、积液正常变异大、病灶与组织重叠高边界模糊脑部 MRI肿瘤、水肿、出血多序列、病灶形态不规则中肿瘤边界相对清视网膜 OCT黄斑水肿、视网膜病变层状结构、细线条异常高层间微小改变眼底彩照出血点、渗出、微血管瘤小目标密集、背景复杂高点状病灶极小乳腺钼靶肿块、钙化低对比、钙化点极微小极高微钙化难标从这张表能看出来医学异常检测的难点是分层级的既有病灶太小学不到的问题也有边界模糊标不准的问题。数据集选得越杂越能暴露方法的短板这也是为什么这份基准强调覆盖面而不是单一刷分。2.2 统一训练与测试协议公平比较的前提比较研究最核心的功夫其实不在算法而在协议统一。MedIAnomaly 在这块做了几件事。第一固定数据划分。它把每个数据集按统一标准切成训练/验证/测试通常是正常样本进训练集异常样本只在测试阶段出现有的设定验证集也含异常用于早停。划分一旦固定并公开别人复现就不会因为你切得巧而偏差。第二固定输入尺寸与预处理。医学图像原始分辨率动辄 2000 像素以上直接喂网络显存扛不住。统一缩放到一个固定尺寸比如 256 或 512同时明确归一化方式。这一步看着小其实影响很大——缩得太狠小病灶直接消失缩得不够显存爆炸。第三固定随机种子和评估脚本。异常检测里随机性来源多初始化、采样、合成异常的随机贴图不固定种子你每次跑的数字都能差出好几个点。我踩过的一个坑早期复现某方法时没固定合成异常的随机种子同配置连跑三次图像级 AUROC 波动快到 2 个点一度以为代码写错了。后来把所有随机源都固定住结果才稳定。2.3 方法分类重构、嵌入、合成异常三大流派把方法归类是为了看清每一类的适用边界。这份基准里的方法大致能分成几大流派。重构类用自编码器、VAE、GAN 或者 Transformer 去重建正常图像重建误差大的地方认为是异常。代表有 AE、VAE、GANomaly、RD4AD、UniAD 这些。逻辑是模型只见过正常所以重建异常会失真。嵌入类不重建像素而是用预训练网络抽特征再对特征分布做建模看测试特征离正常特征分布有多远。典型是 SPADE、PaDiM、PatchCore。这类方法在工业上非常强因为预训练特征本身就带强大语义。合成异常类人为在正常图上贴、混、生成假的异常把它变成监督问题来训练。代表有 DRAEM、SimpleNet 这种。工业上贴个划痕、脏污很逼真医学上你贴块病灶很可能一眼假。除此之外还有归一化流类、知识蒸馏类。分类的意义在于你能根据自己场景的数据特点去选流派而不是盲目追 SOTA。比如你的异常形态很接近训练分布嵌入类可能最稳如果你有大量正常数据但异常极少重构类可能更好上手。3. 核心方法与实操要点3.1 预处理决定成败缩放、裁剪与归一化我先说一个很多新手忽视的点医学图像异常检测里预处理对结果的影响可能比换模型还大。原因很直接——小病灶对分辨率极其敏感。一张眼底图上的微血管瘤可能就十来个像素你缩放到 256 后它可能就剩两三个像素甚至直接被双线性插值抹平。这时候模型性能再强也无能为力。实操上我一般会做几件事。第一评估目标病灶的最小尺寸反推可接受的最小分辨率。如果最小病灶在原图约 15 像素缩放后至少要保留 5-6 像素那缩放因子就不能超过 3 倍。第二考虑分块patch而不是整图缩放尤其对超高分辨率眼底图切块能保分辨率代价是要处理块间拼接。第三归一化按模态区分CT 要用窗宽窗位比如肺窗、纵隔窗不能全局一刀切。CT 的 HU 值范围差异极大不做窗处理模型学到的大概率是伪影而不是病灶。# CT 窗宽窗位处理的常见做法 def apply_window(image, window_center, window_width): low window_center - window_width / 2 high window_center window_width / 2 image np.clip(image, low, high) image (image - low) / (high - low) # 归一化到 [0,1] return image # 肺窗示例 lung apply_window(ct_slice, window_center-600, window_width1500)3.2 三大流派的实现要点别只抄超参重构类的核心在重建目标和损失设计。你要是重建原始像素模型很容易把异常也一起重建出来导致漏检。常见改进是用特征级重建在预训练特征空间算误差或者加注意力约束让模型聚焦正常区域。RD4AD 这类教师-学生结构本质上是让预训练教师网络教一个学生网络在正常图上对齐特征测试时学生无法复现异常区域的特征误差就出来了。实现上要注意教师网络必须冻结学生网络容量别太大不然它会连异常都学过去。嵌入类的关键在记忆库怎么建。PatchCore 的思路是把正常训练样本的特征块存进一个记忆库测试时找最近的正常特征块距离大就判异常。这里有个大坑记忆库太大推理慢到无法接受太小覆盖不全导致误报。实践中要做 coreset 采样把记忆库压到一个合理规模。SPADE、PaDiM 则是假设特征服从多维高斯分布用均值协方差来建模计算量小但假设较强。合成异常类最需要警惕。工业上贴缺陷之所以有效是因为缺陷形态单一、背景干净。医学上你贴的假病灶要同时满足形态像真病灶位置合理纹理与周围组织协调三个条件非常难。这份基准的一个有价值发现就是合成异常方法在医学图像上普遍表现不如工业。所以如果你的场景是医学图像我不建议一上来就把合成异常当主力更稳的做法是把它当作数据增广的辅助手段。3.3 评估指标怎么选AUROC、AP、F1、PRO指标这块必须讲透否则你连自己模型好坏都判断不了。图像级常用的是 AUROC它衡量的是随机取一个正样本和一个负样本模型给正样本更高分的概率。AUROC 好用的地方在于不受阈值影响坏处是在类别极不平衡时它会显得过于乐观。举个例子如果 100 张图里只有 1 张异常一个把所有图都判正常的模型 AUROC 是 0.5但换成 precision/recall 就极难看。所以像这类基准通常会同时报 AP平均精度或 F1。AP 对正类更敏感能反映在异常样本极少的情况下模型到底抓到了多少。像素级定位则常用 PROPer-Region Overlap它对小病灶更友好不会因为大片正常区域被正确排除就冲淡了针对病灶的评估。我的经验是筛查场景重点看图像级 F1 和 recall宁可误报也别漏辅助定位场景重点看像素级 PRO。别只盯一个 AUROC 就下结论。指标衡量层次适用场景注意点AUROC图像级综合排序能力评估类别不平衡时偏乐观AP图像级异常样本极少时对正类更敏感F1图像级需要固定阈值时依赖阈值选择像素 AUROC像素级分割定位质量对面积不敏感PRO像素级小病灶定位更能反映小目标表现3.4 多类别设定的陷阱统一模型不等于更好多类别设定是这份基准很值得关注的部分。直觉上一个模型学多个器官的正常模式数据量更大应该更强。但实测往往相反。不同模态的像素分布差异巨大模型容量被稀释反而每个类别都做不好更糟的是某些类别的正常模式会被模型误当成别的类别的异常导致跨类干扰。我自己的处理经验是如果类别间差异极大比如脑 MRI 和眼底彩照放一起优先用独立模型或者加模态/类别条件如果类别间差异小同一器官不同扫描协议可以考虑共享特征提取、分开建模正常分布。多类别不是不能做而是要想清楚共享什么、隔离什么不能简单地把所有数据混到一个 batch 里就完事。4. 跑通一次 MedIAnomaly 风格的评测流程4.1 环境准备与依赖要把这类基准跑起来环境不用太花哨关键是要能稳定复现。# 典型的依赖栈 pip install torch torchvision # 主干 pip install scikit-learn # AUROC/AP 计算 pip install opencv-python # 图像预处理 pip install pandas numpy tqdm # 数据处理 pip install timm # 预训练骨干网络GPU 显存方面如果你用 512 分辨率加 PatchCore 的记忆库8G 显存有点紧建议 12G 以上。跑合成异常方法时注意数据加载器别开太多 worker医学图像单张读取慢IO 容易成为瓶颈。提示一定要在开始前把随机种子、cuDNN 的确定性开关设置好torch.backends.cudnn.deterministic True虽然会慢一点但结果可复现。4.2 配置文件与训练脚本的组织比较研究的工程重点是能批量跑、能对齐口径。我习惯把每个数据集、每个方法拆成配置用统一入口跑。# 简化版配置结构示意 config { dataset: brain_mri, method: patchcore, image_size: 256, backbone: resnet18, pretrained: True, # 是否用 ImageNet 预训练 coreset_ratio: 0.1, # 记忆库采样比例 seed: 42, metrics: [auroc, ap, pro], }关键在于训练脚本和评估脚本要解耦。训练只管产出正常性分数图评估脚本读分数图统一算指标。这样你换数据、换方法评估环节完全不用动避免了换数据集时手滑改了指标算法的低级错误。4.3 评估执行与结果记录评估阶段最容易出的问题是分数图的尺寸和标注掩码对不上。预处理做了缩放掩码也得同步缩放而且要用最近邻插值不能用双线性否则边界会糊掉。记录结果时我强烈建议把每个类别、每个指标、每次运行的曲线都落盘别只存一个最终数字。因为当你发现某个方法异常时需要回过头看它是图像级掉了还是像素级掉了曲线能帮你快速定位。流程大致是先加载测试集逐张过模型得到分数图然后对所有分数图和掩码做上采样对齐最后批量算指标。整个过程要保证同一批测试样本、同一顺序喂给所有方法否则排名都不可比。4.4 从结果里读出的三个结论从这类基准的普遍结论看有几条是反复被验证的。第一绝大多数工业 SOTA 迁移到医学图像后图像级 AUROC 会明显下降像素级定位下降更剧烈。这说明病在哪比有没有病难得多。第二预训练特征极其关键用不用 ImageNet 预训练某些方法差距能到十几个点。第三合成异常方法在医学场景整体偏弱不如嵌入类稳定。这些结论不是拍脑袋而是统一协议量出来的参考价值比单篇论文的自我报告高得多。5. 常见问题与排查技巧实录5.1 指标虚高八成是数据泄漏异常检测里最隐蔽的坑是数据泄漏。典型表现是你啥都没改指标突然特别高高到不真实比如图像级 AUROC 0.99。这时候先别高兴去查两件事。一是训练集里是不是混进了异常样本比如按文件名划分时把同一次检查的切片分散到了训练和测试集造成同源泄漏。二是预处理时是不是用了全局统计量比如全数据集的均值方差把测试集信息泄露到了训练。正确做法是按受试者或检查级别划分预处理统计量只用训练集算。5.2 合成异常学不动形态和真实严重不匹配前面提过医学合成异常容易一眼假。具体怎么排查你可以把合成的异常图拿出来肉眼看看假病灶是不是边缘太规整、颜色和周围组织反差太大、位置出现在不可能的解剖区域。如果模型在训练集上对这些假异常分类很准一到真实异常就崩基本就是合成分布和真实分布脱节。解决办法是降低合成权重或者用真实异常做少量微调但要小心重新引入泄漏或者干脆换嵌入类方法。5.3 小病灶漏检先怀疑分辨率如果你的模型大病灶都抓到了就是小病灶漏第一反应应该是分辨率问题而不是模型问题。排查方法是把分数图放大看原图上明明有病灶的地方分数为什么低。很多时候是因为预处理缩得太狠病灶在特征图上已经没了。解决要么提高输入分辨率要么分块处理要么在评估时对小目标单独统计。别急着换更复杂的模型先把这个最可能的原因排除掉。5.4 排查速查表现象最可能原因排查方向处理建议指标异常高数据泄漏检查划分粒度、预处理统计量按受试者划分统计量只用训练集小病灶全漏分辨率过低放大分数图核对病灶位置提高分辨率或分块处理合成异常失效假异常不真实肉眼检查合成图质量换方法或降权重多类别掉点跨类干扰检查各类别混淆矩阵加类别条件或独立建模结果不稳定随机源未固定检查种子、合成随机性全流程固定种子像素级很差掩码未对齐核对分数图与掩码尺寸掩码用最近邻插值同步缩放5.5 几个独家避坑技巧再分享几个我实际踩出来的经验。第一评估一定要做阈值敏感性分析别只报一个最佳 F1因为部署时你得自己定阈值只看最优阈值会给你虚假信心。第二跨模态比较时先单独跑每个模态再看多类别否则你分不清掉点是跨类干扰还是某个模态本身太难。第三文档里写清楚你用的预训练权重来源和版本换一个 timm 版本特征分布可能就变了别人复现不出来会浪费大量时间。这些细节论文不会写但决定你的工作能不能被别人用起来。医学图像异常检测这条路选对评估口径比选对模型更重要。我个人的体会是先老老实实把数据划分、指标、预处理固定死再谈方法对比你会少走很多弯路。如果你想继续深入接下来可以沿着小病灶像素级定位跨模态统一建模有标注数据下的半监督几个方向扩展这些都是这份基准明确指向但还没完全解决的硬骨头。