
近年来视觉 TransformerViT在图像分类、目标检测、语义分割等任务上表现非常强势但一个一直被讨论的问题是当输入样本来自训练分布之外Out-of-DistributionOOD时模型依然会给出高置信度的预测结果。这个问题在自动驾驶、医疗影像、工业质检等场景中很致命。这篇文章要聊的论文就是围绕 ViT 的 OOD 检测提出了一种基于 SVD 的典型性图Typicality Maps方法核心思路不是重新训练一个分类头而是在预训练 ViT 的特征空间中做数学变换用 SVD 分解构建一个能反映“样本是否典型”的空间图从而把分布内样本和分布外样本区分开。这个方法的切入点很有意思。它没有像很多 OOD 检测工作那样依赖额外数据集、重训练或修改损失函数而是回到特征本身用奇异值分解把 token 特征映射到一个低维典型性子空间里再可视化成一张图。从这张图上可以直接观察样本的“典型程度”。对于分布内样本典型性图表现出清晰、结构化的模式对于分布外样本图会显得混乱、分散。这个思路既适合做学术复现也适合工程中作为模型上线前的一个鲁棒性检查手段。先看这个项目的核心能力速览。1. 核心能力速览能力项说明项目类型OOD 检测 / ViT 可解释性分析 / 特征空间建模核心方法对 ViT 中间层特征做 SVD 分解构建 Typicality Maps主要功能区分分布内样本与分布外样本、可视化样本典型程度基础模型适用于各种 ViT 变体需按模型结构调整细节是否需要训练不需要重新训练分类头在推理时对特征做后处理显存需求取决于 ViT 模型规模特征后处理阶段开销很小支持平台Python 环境PyTorch / TensorFlow 均可实现是否支持 API不涉及 Web 服务可按需封装成接口是否支持批量支持本质上是矩阵运算可批量处理特征矩阵适合人群算法研究员、模型鲁棒性工程师、CV 方向学生从架构设计上看这个方法的计算主体分为两步第一步是提取 ViT 中间层的 token 特征第二步是对特征矩阵做 SVD 分解并计算典型性图。整个流程不需要额外的可学习参数因此推理开销相对可控。2. 问题背景ViT 在 OOD 检测上为什么难传统卷积神经网络在 OOD 检测上已经有较多研究比如基于 logits 的后验概率校准、基于距离的最近邻方法、基于能量函数的分数计算等。但 ViT 的结构特点导致这些方法不能完全直接迁移。ViT 把图像切成 patch映射成 token 序列然后通过多头自注意力机制捕捉全局关系。问题在于自注意力天然会把注意力权重分散到所有 token 上即使某个 token 对应的是“分布外”的纹理它也可能被其他 token 的上下文“平滑”掉。最后分类 token 输出的 logits 置信度往往偏高无法直接作为 OOD 判断依据。另一个难点是特征分布的高维性。ViT 的中间层特征维度通常很高比如 DeiT-S 的每个 token 特征可能是 384 维经过多层后特征空间非常复杂。直接用欧氏距离或余弦相似度计算样本邻近性结果不稳定直接对特征做密度估计计算开销和存储开销也很大。很多已有方法尝试在 logits 或 softmax 概率上动手但研究表明这并不可靠。真正有效的信息通常藏在特征空间中。SVD 在这里的优势在于它能把高维 token 特征矩阵分解成若干奇异值和对应的奇异向量而这些奇异值向量天然就能拆解出“主要模式”和“噪声模式”。典型性图本质上就是在利用这种模式差异。从材料的表述来看这篇论文选择在 ViT 上使用 SVD 和 Typicality Maps核心逻辑是通过矩阵分解来量化一个样本的特征是否服从训练集的主要结构规律。如果一个样本的内部特征规律性强、能被少数几个奇异模式解释那它就是典型的反之如果特征模式松散、需要很多奇异值才能解释那它就可能是 OOD。3. 方法框架SVD 与 Typicality Maps 如何结合3.1 SVD 的基本形式对于 ViT 某一层输出的特征矩阵 F形状假设为[N, D]其中 N 是 token 数量D 是每 token 的特征维度。对 F 做奇异值分解[ F U \Sigma V^T ]其中 U 是[N, N]左奇异向量矩阵Σ 是[N, D]对角奇异值矩阵V 是[D, D]右奇异向量矩阵。从信号处理角度看奇异值从小到大排列大奇异值对应主要结构小奇异值对应细节或噪声。ViT 的 token 特征矩阵经过 SVD 后分布内样本往往表现出奇异值快速衰减的规律即少数几个奇异值就能解释大部分能量而分布外样本的奇异值衰减更慢能量分散说明其内容模式不符合训练集的主要结构。3.2 Typicality Maps 是怎么构建的Typicality Maps 的核心操作是把 SVD 得到的右奇异向量和特征矩阵做投影得到一个与 token 空间对齐的“典型性分数图”。典型性图生成的基本流程可以拆成下面几步第一步特征提取。从 ViT 的选定层取出所有 token 的特征不含 class token 或含 class token都需实验确定。这个特征矩阵记录了每个 patch 位置的表征。第二步中心化处理。对特征矩阵按 token 维做均值中心化消除整体偏移对 SVD 结果的影响。第三步SVD 分解。对中心化后的特征矩阵做 SVD得到奇异值和奇异向量。第四步投影构建典型性图。选择前 k 个主奇异向量将原始特征映射到这组向量张成的子空间。计算每个 token 在典型子空间内的投影能量占总能量的比例作为该位置的典型性分数。将所有 token 的分数按原始图像 patch 的位置重新排列就得到一张二维的典型性图。第五步全局分数计算。对整张典型性图做聚合例如统计均值、方差或熵得到一个标量分数。分数高于阈值就判断为分布内样本低于阈值为分布外样本。从方法论角度看典型性图有两个作用一是提供像素级别的可解释性直接定位图像中哪些区域是模型“熟悉”的二是全局分数可以作为 OOD 检测的判据。3.3 为什么奇异值分解能区分分布内外分布内样本和分布外样本在特征空间中的本质区别在于“内在维度”。训练集图像经过 ViT 提取特征后大部分样本的 token 特征存在显著相关性。相关性高意味着矩阵的秩较低SVD 后有效奇异值集中在前面几个。分布外样本由于内容与训练分布不一致token 之间的相关性弱矩阵的有效秩更高奇异值谱更平坦。所以Typicality Maps 本质上并不是一个黑盒分数而是一种特征结构诊断。它把“样本是否是 OOD”转化为“样本特征矩阵是否具有低秩结构”的判断。这也是它区别于直接使用 softmax 置信度的核心逻辑。4. 算法流程与伪代码实现下面给出一个可复现的实验框架参考。实际运行需要把model换成具体的 ViT 实现并指定提取哪个 Transformer 层的输出。import torch import torch.nn.functional as F import numpy as np def extract_features(model, x, layer_index-1): 提取 ViT 指定层的 token 特征。 这里以 timm 或 huggingface transformers 的 ViT 为例 需要根据实际后端调整获取中间层输出的方式。 # x: [B, C, H, W] with torch.no_grad(): outputs model.forward_features(x) # 说明具体方法取决于模型实现 # 取 layer_index 对应的 token 特征 features outputs # [B, N, D] return features def compute_typicality_map(features, top_k10): 输入: features [N, D] 单个样本的 token 特征 输出: typicality_map [H_map, W_map], global_score N, D features.shape # 1. 中心化 centered features - features.mean(dim0, keepdimTrue) # 2. SVD 分解 U, S, Vt torch.linalg.svd(centered, full_matricesFalse) # U: [N, min(N,D)], S: [min(N,D)], Vt: [min(N,D), D] # 3. 主成分子空间投影 Vk Vt[:top_k, :] # [top_k, D] projection centered Vk.T # [N, top_k] # 4. 计算每个 token 在典型子空间内的能量占比 total_energy torch.sum(S**2) 1e-8 # 投影部分能量可以直接用前 top_k 奇异值平方和 typical_energy torch.sum(S[:top_k]**2) per_token_typicality torch.norm(projection, dim1, p2) ** 2 per_token_typicality per_token_typicality / (total_energy / N) # 5. 重排成二维图需要知道 patch 网格布局 # 假设图像被切成 14x14 的 patch224/16 H_map W_map int(np.sqrt(N)) typicality_map per_token_typicality.reshape(H_map, W_map).cpu().numpy() # 6. 全局分数可以使用熵或均值这里示例使用标准差 global_score float(typicality_map.std()) return typicality_map, global_score需要指出的是这只是论文思路的一种可能实现不同 ViT 变体对“最优层”的敏感度不同实际使用时建议在验证集上调layer_index和top_k。另外上述代码中forward_features的具体名称和返回格式需要以你实际加载的预训练模型库为准。例如 Hugging Face 的 ViT 模型可能需要启用output_hidden_statesTrue并通过hidden_states[layer_index]获取特征timm 的模型则可能是另外一种接口。5. 实验设计与评估方法如果要验证这个方法的有效性需要构建一套标准评估流程。从方法论上讲OOD 检测评估通常包含以下几个维度。5.1 数据集划分分布内数据集In-DistributionID比如 CIFAR-10、CIFAR-100、ImageNet-1K 的验证集或测试集。分布外数据集OOD常用 CIFAR-100相对于 CIFAR-10、SVHN、Texture、Place365、LSUN 等。也可以在实际业务中选择与 ID 差异较大的自有数据集。近 OOD 和远 OOD 需要分别评估。例如 CIFAR-10 vs SVHN 属于远 OODCIFAR-10 vs CIFAR-100 属于近 OOD。近 OOD 的难度更大也更能反映方法的真实能力。5.2 核心指标AUROCArea Under the ROC Curve衡量阈值无关的判别能力越接近 1 越好。AUPRArea Under the Precision-Recall Curve正类为 OOD 时这个指标更关注对 OOD 的召回能力。FPR95当 TPR 达到 95% 时OOD 样本的误检率。这个工程上很常用越低越好。ID 准确率使用该方法后不能显著降低原始模型在分布内数据上的分类准确率。5.3 基线对比建议与以下通用基线做对比MSPMaximum Softmax Probability最简单的 baseline使用 softmax 最大值作为置信度。ODIN基于温度缩放和输入预处理的改进方法。Mahalanobis Distance基于特征分布的类条件高斯距离。Energy Score用能量函数替代 softmax 的 OOD 分数。ViMVirtual Logit Matching专门针对 ViT 提出的方法和 SVD 方向有相似之处。从搜索材料的信息看SVD 和 PCA 在数学上经常被放在一起讨论但两者侧重点不同。PCA 关注的是降维后的方差最大化SVD 是数值上更稳定的矩阵分解方式。在 OOD 检测这个场景里SVD 的优势是对非方阵、非满秩矩阵处理更自然而且能直接给出奇异值谱这在数值分析和特征诊断上更有利。5.4 消融实验设计不同层的影响分别取 ViT 的浅层、中层、深层观察 Typicality Maps 的判别能力变化。top_k 值的影响从 1 到 D 遍历观察 AUROC 变化。通常太高会把噪声引入太低会丢掉有效结构信息。是否中心化的影响对比中心化和未中心化的 SVD 结果。使用哪种距离度量基于欧氏距离还是余弦相似度构建典型性分数。6. 代码实战基于预训练 ViT 的 OOD 检测完整示例为了让读者能直接跑通这里给一个结合 Hugging Facetransformers库的示例。假设已经安装了torch、transformers、torchvision。pip install torch torchvision transformers使用vit-base-patch16-224作为预训练模型提取倒数第二层的 hidden state。import torch import torch.nn.functional as F import numpy as np from transformers import ViTModel, ViTImageProcessor from PIL import Image model_name google/vit-base-patch16-224 device torch.device(cuda if torch.cuda.is_available() else cpu) processor ViTImageProcessor.from_pretrained(model_name) model ViTModel.from_pretrained(model_name, output_hidden_statesTrue).to(device).eval() def load_image(path): image Image.open(path).convert(RGB) inputs processor(imagesimage, return_tensorspt).to(device) return inputs def get_layer_features(inputs, layer_index-2): with torch.no_grad(): outputs model(**inputs, output_hidden_statesTrue) hidden_states outputs.hidden_states features hidden_states[layer_index] # [1, 197, 768] # 去掉 class token只看 patch token patch_features features[:, 1:, :] # [1, 196, 768] return patch_features[0] # [196, 768] def svd_typicality_score(features, top_k16): features features - features.mean(dim0, keepdimTrue) U, S, Vt torch.linalg.svd(features, full_matricesFalse) total_energy torch.sum(S**2) 1e-8 topk_energy torch.sum(S[:top_k]**2) energy_ratio topk_energy / total_energy # 用能量比作为全局分数的一种可选形式 return energy_ratio.item() def compute_ood_score(image_path): inputs load_image(image_path) features get_layer_features(inputs) score svd_typicality_score(features) return score # 使用示例 score_normal compute_ood_score(normal_sample.jpg) score_ood compute_ood_score(ood_sample.jpg) print(fID sample energy ratio: {score_normal:.4f}) print(fOOD sample energy ratio: {score_ood:.4f})示例中使用的energy_ratio是前 top_k 个奇异值能量占全体奇异值能量的比例。分布内样本的这个比例通常更高因为特征更集中。具体阈值需要在自己的数据集上确定。7. 资源占用与性能观察虽然这不是一个训练任务但在实际推理中引入 SVD 会带来额外的计算开销。需要明确几点7.1 显存占用显存开销主要来自 ViT 前向传播。添加 SVD 后只需要在原有特征矩阵上做矩阵分解不产生额外的大张量显存增量一般在可接受范围。具体数值取决于 ViT 的规模例如 ViT-Base 的中间特征大约为[196, 768]这个尺寸的 SVD 分解在 CPU 上也非常快。如果提取多层特征或使用更大模型ViT-Large、ViT-Huge显存和计算开销会相应增加。从实践角度判断部署时优先关注原始模型前向传播的显存占用。如果原始 ViT-Base 在 16GB 显存上能跑 batch size 64引入 SVD 后建议先降为 batch size 32 或 16再做性能对比。7.2 推理耗时SVD 分解复杂度约为 (O(\min(N,D) \cdot N \cdot D))。对于 N196、D768 的矩阵单次 SVD 只需毫秒级。所以总体推理耗时由 ViT 的 forward 主导。7.3 如何观察性能批量测试时可以通过以下方式记录耗时import time start time.time() score compute_ood_score(sample.jpg) end time.time() print(fInference time: {end - start:.3f}s)测试环境下建议分别统计纯模型 forward 时间和 SVD 后处理时间确认后处理是否是瓶颈。如果后处理占比过高可以降低 top_k或者使用截断 SVD 近似。8. 常见问题与排查方法问题现象可能原因排查方式解决方案SVD 结果出现 NaN 或异常大值特征矩阵包含 NaN或者输入图像异常检查输入张量打印特征矩阵统计量对输入做归一化检查图像解码逻辑OOD 分数区分度不明显选错特征层或 top_k 过大/过小打印多层的能量比分布在验证集上搜索 layer_index 和 top_k显存不足ViT 模型前向传播消耗过大使用nvidia-smi监控显存减小 batch size切分特征提取不同数据集上表现差异大分布内数据与 OOD 数据过于相似检查近 OOD 指标结合 logits 分数做二次筛选运行时加载模型缓慢预训练模型首次下载或本地缓存未命中检查网络状态或模型缓存路径提前离线下载或设置HF_HOME环境变量还有一个常见问题token 数量不是完全平方数。ViT 的 patch 网格通常是 1:1但有些模型使用非方形输入。生成 Typicality Map 时reshape 前或插入位置编码时需要注意映射关系。9. 使用建议与边界讨论从这篇论文的方法论本身出发有几个工程使用建议第一不要只依赖单一分数。Typicality Map 的全局分数适合做第一层粗筛对于不确定的样本可以结合原始 softmax 置信度、特征距离等方法做二次判断。这会显著降低误报率。第二选择合适层是关键。从对 Transformer 结构的普遍认知来看中间层的特征既包含局部结构又包含全局语义而 logits 附近的高层特征更偏向类别区分。OOD 检测一般建议从倒数第二层或中间层提取特征然后通过实验结果确定最优层。不同数据集上的最优层可能不同工程上需要对特定业务数据做小规模验证。第三阈值校准需要在真实数据上完成。典型性分数不是天然的概率值不能用固定阈值直接套用到所有数据集。部署前建议收集一批典型的分布内样本和可能出现的分布外样本用验证集画出 ROC 曲线确定适合业务风险偏好的阈值。第四注意类别的多样性。如果训练集类别很多、图像内容差异很大分布内样本的典型性分数方差也会很大。可以考虑按类别或按 cluster 分别计算典型性基线再做比较。第五SVD 在其他方向的热度也为这个思路提供了参考。搜索材料中提到的 svd 与 lora、svd in pca、svd 气象分析本质上都利用了 SVD 的矩阵低秩分解能力。LoRA 使用 SVD 思想构造低秩增量矩阵PCA 中常用 SVD 做数值稳定求解气象分析中 SVD 用于提取时空模态。这篇论文在 OOD 检测里使用 SVD背后的数学思想是相通的把复杂的特征矩阵分解成少数主模式再判断样本与主模式的匹配程度。理解这一点后也能更容易迁移到其他模态或模型结构上。10. 总结与下一步这个项目最值得尝试的点是它不需要重新训练模型只对已有的 ViT 代表特征做矩阵分解就能获得带有可解释性的 OOD 判别依据。Typicality Map 的另一个好处是可以直接可视化这在模型调试和对外解释时有很大帮助。如果要在自己的实验里验证建议先跑通最小的实验加载一个预训练 ViT准备一批分布内图像和一批分布外图像提取倒数第二层 patch token 特征做 SVD计算前 16 个奇异值的能量占比然后画出两类样本的分布直方图。这一步只需要几十行代码投入很低但能快速判断这个方向对当前业务是否有效。最容易踩的坑有两个一是使用最高层特征时可能会丢失太多结构信息导致 SVD 能量比区分度不高二是忽略中心化导致奇异值被整体均值偏移主导典型性图出现伪结构。建议在实现时把这两点纳入实验对比。后续可以扩展的方向包括把 Typicality Maps 和多层特征融合构造多尺度分数结合其他 OOD 基线方法构建集成模型在语义分割模型的中间特征上做密度估计用于开放集识别任务中拒绝未知类别甚至可以把 SVD 的右奇异向量作为一组“原型过滤器”在少数样本场景下增强可解释性。这篇论文提供了一种从特征矩阵内在结构出发的 OOD 检测思路比起直接看分类置信度它的优势在于对分布偏移更敏感、结果可解释。如果你正在做 ViT 相关应用的可靠性评估建议把 SVD 典型性图加入你的工具箱作为模型上线前的一道检查手段。