ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ViT部署必备:基于SVD的OOD典型性地图检测原理与实现

ViT部署必备:基于SVD的OOD典型性地图检测原理与实现 把 ViT 模型部署到真实项目里的第一周你大概率会遇到一个测试报告完全没暴露的问题模型对“它没见过的输入”依然会自信地给出分类结果。在工业质检场景里一批完全不该出现在训练类别中的样本被送进来模型的 softmax 输出写着“该样本属于类别 A 的概率为 0.96”。模型没有报错没有犹豫而是非常自信地犯错。这不是模型精度的问题而是模型的“认知边界”问题。技术上它叫 Out-of-Distribution Detection也就是分布外检测。近几年OOD 检测从论文里的一个评估指标变成了工程团队上线模型前必须回答的关键问题。而 SVD-Based Typicality Maps 这类方法之所以值得关注不是因为它又多了一个评分公式而是它把“这个样本是不是分布外的”这个二元判断升级成了一张可以定位、可以解释、可以拼回原图上的典型性地图。它真正改变的不是数值而是人对模型行为进行诊断的方式。1. 模型“自信地犯错”比精度下降更危险1.1 一个真实到不能再真实的生产场景假设你负责的是一条基于视觉模型的质量检查流水线。测试集是经过筛选的标准样本模型在测试集上的准确率很高。一旦进入生产环境情况就变了摄像头换了角度、光照条件变了、出现了训练数据里完全没有的新缺陷类型。这时候模型会做什么它不会说“我不知道”。它只会把新的输入映射到它学过的某一个类别上然后给一个看起来很高的概率。最麻烦的是在训练分布内部这个概率可能是合理的但在分布外部这个概率没有语义意义。也就是说模型的输出格式没有变错的却是内容。下游自动化系统不会知道这个的概率是从一个它没见过的世界里算出来的它只会继续执行。这就是 OOD 检测要解决的第一个问题在模型产生不可信输出之前先把输入挡住。1.2 OOD 检测和普通分类任务不是同一件事普通分类任务是一个闭集问题。我们有固定的 k 个类别每个样本必须属于其中之一。模型要做的是学会 k 个类别之间的决策边界这个边界是有监督信号直接标注出来的。OOD 检测不一样。它是一个开集问题部署时输入空间是开放的而训练时我们手里只有分布内数据。你没法给模型准备一份“所有可能出错样本”的清单因为在真实世界里不典型的情况几乎是无限的。这更像是在描述“正常是什么样”而不是在划分“正常和异常之间的边界”。所以 OOD 检测本质上是一个单类问题我们需要用一堆正样本学出一个紧凑的“正常模式”然后拿它去衡量新样本偏离了多少。这个角度看问题就能理解为什么单纯靠分类器输出不好使。1.3 softmax 概率不能当作 OOD 信号很多人第一反应是直接用 softmax 的输出概率当置信度不行吗问题在于softmax 只是在 k 个类别之间做归一化它根本不知道外部世界的存在。早期很多研究已经证实神经网络在某些 OOD 样本上给出的置信度甚至高于分布内样本。温度缩放只能调整概率分布的锐度无法改变特征空间里 OOD 样本离正常数据很远这个事实。因此业界发展出了各种“事后评分”方法基于 logits 的 energy score、特征空间里的马氏距离、以及各类基于内部表征的检测器。SVD-Based Typicality Maps 属于最后这一类它不依赖最后一层 logits而是深入 ViT 的内部表征去问一个问题——这张图里的每个局部区域到底有多像模型在训练时见过的世界。2. ViT 让 OOD 检测的难度升了一个级别2.1 卷积网络的局部归纳偏置其实帮了 OOD 检测的忙CNN 的强项是局部性。卷积核只关心局部感受野层级结构从边缘、纹理逐步组合到对象部件。这个归纳偏置让 CNN 的特征在空间上是相对容易预测的一个小区域的分布变化往往只影响附近位置的特征统计。很多经典 OOD 方法比如基于特征空间马氏距离的方法之所以在 CNN 上表现不错正是因为 CNN 的特征空间有比较清晰的结构。每一层的均值、协方差、通道统计都能比较稳定地反映数据分布。可以说结构本身的约束替我们做了很多工作。2.2 ViT 的全局注意力信息更丰富但“正常模式”更难定义Vision Transformer 从第一层开始就是全局自注意力。patch 和 patch 之间没有距离概念任何位置的 token 都可以直接关联到任何其他位置的 token。这种设计的表达能力更强但也带来一个副作用一个异常的局部区域可以通过注意力把影响传播到整张特征图。这给 OOD 检测带来两个麻烦。第一局部异常信号被全局传播稀释了你很难从某个通道的统计量里准确捕捉到它。第二ViT 的归纳偏置更少特征是高度分布、互相纠缠的不像 CNN 那样有相对规则的通道-空间结构。结果就是“正常数据的特征应该长什么样”这个问题在 ViT 上更难回答。2.3 patch token 结构难点也是机会ViT 有一个 CNN 没有的天然优势它把图像切成固定大小的 patch每个 patch 对应一个 token。这意味着在任意一层我们都能拿到一组和输入图像空间位置严格对齐的 token 特征向量。如果我们能对每个 token 算出一个“典型性分数”那这些分数天然可以重排成一张和原图对齐的二维地图。而 CNN 的特征图虽然也有空间维度但感受野高度重叠空间对应关系更模糊。ViT 的 token-grid 结构反而让“逐区域诊断”这件事变得干净利落。Typicality Maps 这类方法本质上就是在利用这个结构红利。维度CNNViT局部先验强卷积核天然限定感受野弱第一层起就是全局注意力特征空间结构相对规则通道统计容易解释高度分布token 之间互相纠缠空间对应感受野重叠对应较模糊天然 patch 网格与输入严格对齐传统 OOD 方法适配度较好经典方法直接可用需要新的结构感知评分构建典型性地图可以做但语义比较粗糙自然对齐解释性强3. SVD 是一种被低估的“典型性探测器”3.1 SVD 到底在做什么从 PCA、LoRA 到气象分析奇异值分解SVD是线性代数里最通用的工具之一。任何一个 m×n 的实矩阵 M都可以分解成 UΣVᵀ 的形式其中 Σ 对角线上的奇异值按从大到小排列。前 k 个奇异向量张成的子空间是在能量保持意义下对该矩阵最好的 k 维近似。这个性质被用在了各种看似不相关的领域。PCA 其实就是对中心化数据做 SVD主成分就是右奇异向量LoRA 的低秩适应思想本质上也是在和一个低秩分解打交道压缩出来的更新矩阵天然带有 SVD 的痕迹气象分析里研究者用 SVD 从海温和降水两个场里提取耦合模态找的也是“主导结构”。这些应用指向同一件事SVD 擅长从一堆高维观测里抽出少数几个真正稳定的方向。而这个能力恰恰是“定义典型性”最需要的。3.2 主结构、残差结构以及“典型性”的定义假设我们用训练集里的一批正常图片喂给一个训练好的 ViT取出某一层所有 patch token 的特征。把这些特征向量堆成一个矩阵然后做 SVD。会发现一个规律大部分能量集中在少数几个奇异值上。这说明正常图片在这一层的特征基本上落在一个低维子空间里。一个 patch 是“典型”的意思是它的特征向量能被这个低维子空间很好得重构反之如果某个 patch 含有未知物体、异常纹理或者它和周边 token 的关系不太对劲它的特征向量在投影回去之后会留下一个很大的残差。于是我们可以给“典型性”下一个可操作的定义一个 patch 的典型性等于它的特征拟合分布内主导结构的好坏程度。正常结构就是那个低秩近似异常就是残差。这个思路和 PCA 的原理完全同构只不过对象从图像本身换成了 ViT 内部的特征空间。3.3 一个跨领域类比图书馆推荐系统和典型性地图可以把 SVD 理解成图书馆的图书分类系统。一个图书馆有几百万本书但大部分书会聚在少数几个主题方向上。如果对书目做 SVD那几个主导方向就是主要的主题簇。现在来了一本新书。如果它的内容和已有主题簇高度吻合那它就是“典型”的书如果它把好几个冷门领域混在一起或者用了非常少见的结构那它在现有分类系统里就会留下很大的偏差。图书馆不需要事先枚举所有“不典型”的书它只需要一份对典型图书的紧凑描述然后衡量新书和这份描述的差距。OOD 检测也是同样的道理。我们不可能枚举所有可能的分布外样本但我们可以用 SVD 把“正常”压缩成一小撮主方向。新输入的偏差大小就是它的 OOD 程度。4. SVD-Based Typicality Maps 的核心设计拆解4.1 整体流程四步走从方法名称和 SVD 的常见用法来看这类方法的核心流程可以归纳成四步让图像通过 ViT 前向传播在选定的若干层取出 patch token 特征。把 token 特征整理成矩阵做 SVD。利用奇异结构和重建残差算出每个 token 的典型性分数。把分数重排成和原图对齐的二维地图再把多层地图聚合成一个图像级 OOD 分数。下面是一个通用流程的伪代码只是为了展示结构。具体实现、层数选择和公式细节要以论文原稿和官方代码为准。# 伪代码SVD Typicality Map 的通用结构 # 具体公式和细节以论文原稿为准 def build_typicality_map(model, image, layer_ids, energy_threshold0.9): # 1. 取多个 transformer 层的 patch token 特征 # 每层特征形状约为 [H/16, W/16, D] token_feats extract_patch_features(model, image, layer_ids) maps [] for feat in token_feats: # 2. 转成 [D, P] 矩阵P 为 patch 数量再做 SVD M feat.reshape(-1, feat.shape[-1]).T U, S, Vt np.linalg.svd(M, full_matricesFalse) # 按能量占比自动选择保留秩 k energy np.cumsum(S ** 2) / np.sum(S ** 2) k int(np.searchsorted(energy, energy_threshold)) 1 # 3. 用前 k 个方向重建计算每个 token 的残差 M_approx U[:, :k] np.diag(S[:k]) Vt[:k, :] residual np.linalg.norm(M - M_approx, axis0) # 残差越大越不典型取反并归一化到 0~1 typicality 1.0 - (residual - residual.min()) / (residual.max() - residual.min() 1e-8) maps.append(typicality.reshape(grid_h, grid_w)) # 4. 融合多层地图并聚合成图像级分数 fused_map np.mean(maps, axis0) image_score np.percentile(fused_map, 95) return fused_map, image_score4.2 第一步特征从哪来层数怎么选ViT 的不同层学到的是不同抽象层次的语义。浅层偏纹理和边缘中层偏对象部件深层偏语义类别。对 OOD 检测来说没有任何一层是绝对最优的。浅层对纹理变化敏感但容易把正常的风格差异误判为异常深层对语义偏移敏感但可能漏掉小范围的局部异常。一个稳妥的做法是先拿一个小验证集把每一层的 AUROC 单独算一遍再选出 2 到 4 层做融合。另外要注意 [CLS] token 的处理。CLS 是全局表征信息量大但它没有空间位置不能直接进地图。常见设计是用 patch token 构建空间地图然后在最终图像级分数里再考虑是否加入 CLS 的信息。如果用的是 Swin 这类层级化 Transformer不同层的特征图分辨率是不一样的。这时候需要把地图插值到同一分辨率否则没法直接融合。4.3 第二步SVD 怎么作用于特征矩阵根据我的理解这个方向主要有两种 SVD 用法各有各的侧重点。第一种是逐输入 SVD。对当前这张输入图像把它的 patch token 特征矩阵直接做 SVD。这样刻画的是这张图片内部的 token 相关结构。OOD 图像的内部相关结构和正常图像往往差别很大比如它的奇异值能量分布更分散或者某些 patch 的重建残差特别大。这种做法的好处是速度快不需要额外的参考数据但缺点是比较依赖“当前输入内部的一致性”。第二种是参考子空间 SVD。在训练集上先取一批正常图像的特征算出参考子空间然后把新输入的 token 特征投影到这个参考子空间上用投影残差作为不典型程度。这种做法更接近马氏距离的思考方式但用低秩子空间替代了完整协方差在高维小样本场景下更稳定。不管用哪种特征矩阵的构成都很关键。如果直接把原始的 patch 特征丢进 SVD通道尺度的差异会让大数值通道主导整个分解。通常需要先对特征做逐层标准化再进 SVD。4.4 第三步怎样把奇异值信息变成逐 patch 的典型性分数算完 SVD 之后把奇异值信息变成一个可用的逐 patch 分数有几种常见设计。第一种是局部重建误差。每个 token 用前 k 个奇异方向重建残差大的就是异常区域。这是最直观、最稳定的设计对局部异常敏感。第二种是子空间对齐度。算每个 token 的特征向量和 top-k 奇异向量张成子空间的余弦相似度。相似度高代表典型低代表不典型。它比重建误差更平滑但可能对某些特殊类型的高残差样本不敏感。第三种是能量集中度。看整张图的奇异值谱衰减得快不快。如果前几个奇异值占了绝大部分能量说明图像内部结构非常规则如果能量分散在很多方向上说明它的内部结构很混乱更像分布外数据。还有一个必须考虑的细节保留秩 k 的选择。它决定了“正常结构”取到哪个粒度。一个非常容易踩的坑保留秩 k 不是一个需要调得越准越好的参数而是一个需要和评估流程一起固定的超参数。今天为了某个 OOD 数据集把 k 调到最优下次换一个数据集就可能完全失效。正确的做法是先定 k再用完整的验证协议评测。4.5 第四步地图聚合与图像级 OOD 分数拿到空间地图之后还要回答一个问题怎么从地图算出整张图像的 OOD 分数这里有几个选择。max 响应最敏感适合局部异常但容易受单个噪声 patch 干扰mean 响应最稳定但会把一个明显的局部异常稀释掉百分位响应比如取地图的 95 分位是一个折中方案既保留局部信息又抑制极端噪声。多层地图的融合也有讲究。简单平均是常用做法但如果各层分辨率不同要先插值到统一分辨率。也可以给每一层学一个权重让它自己决定哪些层对 OOD 检测更重要。不过这么做需要更多的验证数据否则容易过拟合到某几个 OOD 集合上。评估之前先跑通一个最小的单张图片演示。确认在 ID 图像上地图整体偏亮在 OOD 图像上地图整体偏暗再去跑完整 benchmark。跳过这一步后面所有的调参都是盲调。5. 自己复现和验证时按这个路线走5.1 先搭一个最小验证流程如果你想在自己的项目里验证这类方法的价值不需要从零训练模型。直接用现成的预训练 ViT 和公开 OOD 基准就能搭建最小验证流程。# 示例安装常用依赖 pip install torch torchvision timm scikit-learn numpy建议组合骨干网络timm 里的vit_base_patch16_224或者其他你项目实际在用的 ViT。分布内数据CIFAR-10 验证集或者 ImageNet 的一个子集。分布外数据SVHN、Texture、iNaturalist 等常见 OOD benchmark 集合。实现的路径就是上一节的四步流程。先实现地图可视化和图像级分数再和几个常见基线对比。方法特征来源是否需要训练数据输出粒度Softmax 置信度logits否标量Energy Scorelogits否标量Mahalanobis 距离中间层特征需要估计均值/协方差标量SVD Typicality Map多层 patch token视设计可能需要参考子空间标量 空间图5.2 评估指标怎么读AUROC 和 FPR95OOD 检测最常见的两个指标是 AUROC 和 FPR95。AUROC 的含义是随机抽一个分布内样本和一个分布外样本模型把分布内样本排在分布外样本前面的概率。1.0 是完美0.5 等于随机猜测。它不依赖具体阈值适合做方法对比。FPR95 的含义是在“95% 的分布内样本被正确保留”这个阈值下有多少比例的分布外样本被误判成分布内。这个指标更贴合实际部署因为很多场景明确要求误杀率不能超过 5%。实操上两个提醒。第一阈值的选择必须放在独立的验证集上不能用测试集反复调。第二报告结果时要给出多次运行或多条数据集的均值和方差。单个 OOD 数据集上的亮眼数字说明不了问题。5.3 排查链路结果不对时先查哪几层如果跑出来的 AUROC 接近 0.5或者地图看起来像随机噪声不要急着调换 SVD 的公式。按这个顺序排查查预处理。图像尺寸、归一化均值方差、插值方式是否和模型训练时一致。很多人在这里翻车。查特征提取。取的层名是否正确token 顺序是不是被重构打乱了有没有把 [CLS] token 混进 patch token 里一起去重建地图。查矩阵方向。SVD 返回的 U、S、Vt 三个矩阵的维度对没对上重建时有没有搞错行列顺序。查归一化。特征通道尺度差异是不是太大有没有做逐层标准化。查 OOD 数据集本身。有些 OOD 数据集和你的分布内数据在内容上高度重叠比如 ImageNet 的裁剪版本和原版的关系。这种情况下分数接近随机不是方法的问题而是任务定义的问题。6. 适用边界、坑点以及它真正改变了什么6.1 适合什么场景不适合什么场景任何方法都有边界。SVD Typicality Map 最适合的场景是已经使用了 ViT并且希望不仅知道“这个样本可疑”还想知道“可疑区域在哪里”。场景适合程度原因医学影像的异常筛查较适合需要空间定位patch 级解释有临床决策价值工业质检中的未知缺陷较适合缺陷位置本身就是核心信息自动驾驶开放道路异常部分适合patch 级解释有价值但延迟和算力需要权衡边缘设备上的在线首级筛选不太适合SVD 和多次前向会增加延迟像素级异常分割不太适合只能提供 patch 级精度达不到像素级大规模在线服务的首级过滤不太适合需要更轻量的标量方法6.2 最常见的几个误判第一个误判是把 OOD 检测当成万能安全网。模型本身的分类错误、标签噪声、领域漂移OOD 检测都管不了。它只负责一件事输入是不是来自训练分布。别指望一个异常检测器能修好模型的所有问题。第二个误判是在一个 OOD 集合上效果好就觉得大功告成。near-OOD 和 far-OOD 的难度差异非常大
返回列表