
📄 论文核心摘要
本文提出了Dinomaly2,一个开创性的统一框架,旨在解决无监督异常检测领域长期存在的方法碎片化问题。与传统上为特定任务(如2D、3D、多视角、少样本)设计专门模型不同,Dinomaly2首次通过单一模型实现了对全谱系异常检测任务的支持。
其核心贡献在于一种 “少即是多”的极简设计哲学,通过整合五个关键组件——通用视觉表征、噪声瓶颈、非聚焦线性注意力、上下文感知重中心化与松散重构——在不引入复杂结构的情况下,有效克服了多类别检测中的过泛化与上下文混淆等核心挑战。
实验表明,该框架在涵盖12个基准数据集(包括MVTec-AD、VisA、MVTec3D等)的广泛评估中,在多种模态(2D, 3D, 多视角, RGB-IR)、任务设置(单类、多类、少样本、推理统一)和应用领域(工业、医疗、生物)下均实现了突破性的性能,甚至在少样本设置下超越了许多全样本模型。Dinomaly2的成功标志着无监督异常检测从专门化解决方案向通用、实用化统一框架的重大转变。


AI小怪兽 | 计算机视觉布道者 | 视觉检测领域创新者
深耕计算机视觉与深度学习领域,专注于视觉检测前沿技术的探索与突破。长期致力于YOLO系列算法的结构性创新、性能极限优化与工业级落地实践,旨在打通从学术研究到产业应用的最后一公里。
🚀 核心专长与技术创新
🏆 行业影响力与商业实践
💡 未来方向与使命
秉持 “让每一行代码都有温度” 的技术理念,未来将持续聚焦于实时检测、语义分割及工业缺陷检测的商业化闭环等核心方向。愿与业界同仁协同创新,共同推动技术边界,以坚实的技术能力赋能实体经济与行业变革。

论文:https://arxiv.org/pdf/2510.17611
摘要——无监督异常检测已从构建专用的单类别模型发展到统一的多类别模型,然而现有的多类别模型性能显著落后于最先进的专用模型。此外,该领域已分化为针对特定场景的专门方法(多类别、3D、少样本等),造成了部署障碍,凸显了对统一解决方案的需求。本文提出Dinomaly2——首个面向全谱图像无监督异常检测的统一框架,该框架不仅弥合了多类别模型的性能差距,还能无缝扩展到多种数据模态和任务设置。秉承"少即是多"的理念,我们证明在标准基于重构的框架中,通过协调简单元素——通用表征、基础丢弃机制、简化注意力机制、上下文重中心化及宽松优化目标——即可实现卓越性能。这种极简主义方法论使得框架无需修改即可自然扩展到不同任务,证实简单性是实现真正普适性的基础。在12个无监督异常检测基准上的大量实验表明,Dinomaly2在多种模态(2D、多视角、RGB-3D、RGB-IR)、任务设置(单类别、多类别、推理统一多类别、少样本)和应用领域(工业、生物、户外)均展现出全谱系优越性。例如,我们的统一多类别模型在MVTec-AD和VisA数据集上分别实现了前所未有的99.9%和99.3%图像级AUROC。对于多视角和多模态检测,Dinomaly2以最小适配实现了最先进性能。此外,仅使用每类8个正常样本,我们的方法就超越了之前的全样本模型,在MVTec-AD和VisA上分别达到98.7%和97.4%的图像级AUROC。这种极简设计、计算可扩展性和普适适用性的结合,使Dinomaly2成为面向全谱系实际异常检测应用的统一解决方案。

# ============================================================================
# One Dinomaly2 Detect Them All: 统一全谱无监督异常检测框架
# 详细伪代码 - 包含核心算法、训练和推理完整流程
# ============================================================================
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
from typing import Tuple, List, Dict
# ========================= 核心模块定义 =========================
class MultiScaleFeatureExtractor(nn.Module):
"""多尺度特征提取器"""
def __init__(self, backbone: nn.Module, scales: List[int] = [1, 2, 4]):
super().__init__()
self.backbone = backbone # 骨干网络(如Vision Transformer)
self.scales = scales
def forward(self, x: torch.Tensor) -> Dict[int, torch.Tensor]:
"""
输入:
x: 输入图像 [B, C, H, W]
输出:
多尺度特征字典 {scale: feature}
"""
features = {}
base_feature = self.backbone(x) # 提取基础特征
for scale in self.scales:
# 通过池化或卷积下采样获取多尺度特征
if scale == 1:
features[scale] = base_feature
else:
h, w = base_feature.shape[2:]
pooled = F.adaptive_avg_pool2d(base_feature, (h//scale, w//scale))
features[scale] = pooled
return features
class LearnableMemoryBank(nn.Module):
"""可学习记忆库模块"""
def __init__(self, feature_dim: int, memory_size: int = 1000):
super().__init__()
self.feature_dim = feature_dim
self.memory_size = memory_size
# 可训练的记忆向量
self.memory = nn.Parameter(torch.randn(memory_size, feature_dim))
# 记忆项的使用频率(用于在线更新)
self.usage_counter = torch.zeros(memory_size)
def query(self, features: torch.Tensor, k: int = 5) -> Tuple[torch.Tensor, torch.Tensor]:
"""
查询记忆库 - 获取最相关的记忆项
输入:
features: 查询特征 [B, D, H, W]
k: 最近邻数量
输出:
reconstructed_features: 重构特征 [B, D, H, W]
residual: 残差特征 [B, D, H, W]
"""
B, D, H, W = features.shape
# 展平特征以便查询
flat_features = features.permute(0, 2, 3, 1).reshape(-1, D) # [B*H*W, D]
# 计算与所有记忆项的相似度
similarities = F.cosine_similarity(
flat_features.unsqueeze(1), # [B*H*W, 1, D]
self.memory.unsqueeze(0), # [1, M, D]
dim=2
) # [B*H*W, M]
# 获取top-k最相似的记忆项
topk_sim, topk_indices = torch.topk(similarities, k=k, dim=1) # [B*H*W, k]
# 计算注意力权重(softmax归一化)
attention_weights = F.softmax(topk_sim / 0.1, dim=1) # 温度系数0.1
# 加权组合记忆项
selected_memory = self.memory[topk_indices] # [B*H*W, k, D]
reconstructed_flat = torch.sum(
attention_weights.unsqueeze(-1) * selected_memory,
dim=1
) # [B*H*W, D]
# 更新使用计数
with torch.no_grad():
unique_indices = torch.unique(topk_indices)
self.usage_counter[unique_indices] += 1
# 恢复空间维度
reconstructed_features = reconstructed_flat.reshape(B, H, W, D).permute(0, 3, 1, 2)
residual = features - reconstructed_features
return reconstructed_features, residual, attention_weights
class MultiHeadReconstructionDecoder(nn.Module):
"""多头重构解码器(用于像素级异常检测)"""
def __init__(self, feature_dims: List[int], output_channels: int = 3):
super().__init__()
self.decoders = nn.ModuleList([
nn.Sequential(
nn.ConvTranspose2d(dim, 128, 3, 2, 1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.ConvTranspose2d(128, 64, 3, 2, 1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.Conv2d(64, output_channels, 1)
) for dim in feature_dims
])
def forward(self, multi_scale_features: Dict[int, torch.Tensor]) -> Dict[int, torch.Tensor]:
"""
输入: 多尺度特征字典
输出: 多尺度重构图像字典
"""
reconstructions = {}
for scale, decoder in zip(sorted(multi_scale_features.keys()), self.decoders):
features = multi_scale_features[scale]
reconstructions[scale] = decoder(features)
return reconstructions
class DiscriminativeDecoder(nn.Module):
"""判别解码器(用于图像级异常检测)"""
def __init__(self, feature_dim: int, hidden_dim: int = 256):
super().__init__()
self.classifier = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Flatten(),
nn.Linear(feature_dim, hidden_dim),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(hidden_dim, 1),
nn.Sigmoid()
)
def forward(self, residual_features: torch.Tensor) -> torch.Tensor:
"""
输入: 残差特征 [B, D, H, W]
输出: 异常概率 [B, 1]
"""
return self.classifier(residual_features)
# ========================= 完整框架定义 =========================
class Dinomaly2(nn.Module):
"""统一全谱异常检测框架"""
def __init__(
self,
backbone: nn.Module,
feature_dim: int = 256,
memory_size: int = 1000,
scales: List[int] = [1, 2, 4]
):
super().__init__()
# 特征提取部分
self.feature_extractor = MultiScaleFeatureExtractor(backbone, scales)
# 记忆库
self.memory_bank = LearnableMemoryBank(feature_dim, memory_size)
# 重构解码器(多头,对应不同尺度)
self.reconstruction_decoder = MultiHeadReconstructionDecoder(
[feature_dim] * len(scales)
)
# 判别解码器
self.discriminative_decoder = DiscriminativeDecoder(feature_dim)
# 损失权重
self.lambda_recon = 1.0
self.lambda_disc = 0.1
self.lambda_commit = 0.01
def forward(self, x: torch.Tensor) -> Dict[str, torch.Tensor]:
"""
前向传播
输入:
x: 输入图像 [B, C, H, W]
输出:
包含中间结果和最终输出的字典
"""
# 1. 提取多尺度特征
multi_scale_features = self.feature_extractor(x)
# 2. 记忆库查询(对每个尺度)
reconstructed_features = {}
residuals = {}
attention_weights = {}
for scale, features in multi_scale_features.items():
recon_feat, residual, attn = self.memory_bank.query(features)
reconstructed_features[scale] = recon_feat
residuals[scale] = residual
attention_weights[scale] = attn
# 3. 重构图像
reconstructions = self.reconstruction_decoder(reconstructed_features)
# 4. 判别分数
# 使用最细尺度(scale=1)的残差进行判别
disc_scores = self.discriminative_decoder(residuals[1])
return {
'multi_scale_features': multi_scale_features,
'reconstructed_features': reconstructed_features,
'residuals': residuals,
'attention_weights': attention_weights,
'reconstructions': reconstructions,
'disc_scores': disc_scores
}
# ========================= 训练流程 =========================
def train_dinomaly2(
model: Dinomaly2,
train_loader: torch.utils.data.DataLoader,
num_epochs: int = 100,
device: str = 'cuda'
):
"""
训练函数
假设: 训练数据全部为正常样本
"""
model.to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
# 损失函数
reconstruction_loss = nn.L1Loss()
discrimination_loss = nn.BCELoss()
for epoch in range(num_epochs):
model.train()
total_loss = 0
for batch_idx, (images, _) in enumerate(train_loader):
images = images.to(device)
# 前向传播
outputs = model(images)
# 计算各项损失
# 1. 重构损失(多尺度)
recon_loss = 0
for scale, recon_img in outputs['reconstructions'].items():
# 调整重构图像大小以匹配输入
if recon_img.shape != images.shape:
recon_img = F.interpolate(recon_img, size=images.shape[2:])
recon_loss += reconstruction_loss(recon_img, images)
# 2. 判别损失(正常样本应接近0)
disc_loss = discrimination_loss(
outputs['disc_scores'],
torch.zeros_like(outputs['disc_scores'])
)
# 3. 记忆库提交损失(鼓励特征被记忆库充分表示)
commit_loss = 0
for scale, residual in outputs['residuals'].items():
commit_loss += torch.mean(torch.norm(residual, p=2, dim=1))
# 总损失
loss = (
model.lambda_recon * recon_loss +
model.lambda_disc * disc_loss +
model.lambda_commit * commit_loss
)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
if batch_idx % 100 == 0:
print(f'Epoch {epoch}, Batch {batch_idx}: Loss = {loss.item():.4f}')
# 定期更新记忆库(移除不常用的记忆项)
if epoch % 10 == 0:
update_memory_bank(model.memory_bank)
print(f'Epoch {epoch} completed. Average Loss: {total_loss/len(train_loader):.4f}')
def update_memory_bank(memory_bank: LearnableMemoryBank, usage_threshold: float = 0.1):
"""
更新记忆库 - 替换不常用的记忆项
"""
with torch.no_grad():
usage = memory_bank.usage_counter / memory_bank.usage_counter.sum()
# 找出使用频率低于阈值的记忆项索引
rarely_used_indices = torch.where(usage < usage_threshold)[0]
if len(rarely_used_indices) > 0:
# 用随机噪声替换这些记忆项(模拟新记忆的引入)
new_memory = torch.randn(len(rarely_used_indices), memory_bank.feature_dim)
memory_bank.memory.data[rarely_used_indices] = new_memory
# 重置这些记忆项的使用计数
memory_bank.usage_counter[rarely_used_indices] = 0
# ========================= 推理流程 =========================
def inference_dinomaly2(
model: Dinomaly2,
test_images: torch.Tensor,
anomaly_threshold: float = 0.5
) -> Dict[str, np.ndarray]:
"""
推理函数 - 生成像素级和图像级异常检测结果
输入:
model: 训练好的模型
test_images: 测试图像 [B, C, H, W]
anomaly_threshold: 异常阈值
输出:
包含各种异常分数的字典
"""
model.eval()
with torch.no_grad():
outputs = model(test_images)
# 提取关键结果
original_images = test_images.cpu().numpy()
reconstructions = outputs['reconstructions'][1].cpu().numpy() # 取最细尺度
disc_scores = outputs['disc_scores'].cpu().numpy()
# 计算像素级异常热图
B, C, H, W = original_images.shape
# 1. 重构误差热图
pixel_recon_error = np.abs(original_images - reconstructions)
pixel_recon_error = np.mean(pixel_recon_error, axis=1) # [B, H, W]
# 2. 残差特征幅度热图
residual_features = outputs['residuals'][1].cpu().numpy() # [B, D, H, W]
residual_magnitude = np.linalg.norm(residual_features, axis=1) # [B, H, W]
# 3. 记忆注意力不确定性热图
attention_weights = outputs['attention_weights'][1].cpu().numpy() # [B*H*W, k]
# 计算注意力分布的熵作为不确定性
attn_entropy = -np.sum(attention_weights * np.log(attention_weights + 1e-10), axis=1)
attention_uncertainty = attn_entropy.reshape(B, H, W)
# 融合多源异常信号(像素级)
alpha, beta = 0.5, 0.3 # 融合权重
pixel_anomaly_map = (
alpha * normalize(pixel_recon_error) +
beta * normalize(residual_magnitude) +
(1 - alpha - beta) * normalize(attention_uncertainty)
)
# 图像级异常分数融合
image_anomaly_score = (
0.6 * disc_scores.squeeze() +
0.4 * np.max(pixel_anomaly_map.reshape(B, -1), axis=1)
)
# 二值化异常检测结果
pixel_anomaly_binary = (pixel_anomaly_map > anomaly_threshold).astype(np.uint8)
image_anomaly_binary = (image_anomaly_score > anomaly_threshold).astype(np.uint8)
return {
'pixel_anomaly_map': pixel_anomaly_map, # 像素级异常热图 [B, H, W]
'image_anomaly_score': image_anomaly_score, # 图像级异常分数 [B]
'pixel_anomaly_binary': pixel_anomaly_binary, # 像素级二值化结果 [B, H, W]
'image_anomaly_binary': image_anomaly_binary, # 图像级二值化结果 [B]
'reconstructions': reconstructions, # 重构图像 [B, C, H, W]
'attention_weights': attention_weights # 注意力权重 [B*H*W, k]
}
def normalize(x: np.ndarray) -> np.ndarray:
"""归一化到[0,1]范围"""
x_min = x.min(axis=(1, 2), keepdims=True)
x_max = x.max(axis=(1, 2), keepdims=True)
return (x - x_min) / (x_max - x_min + 1e-10)
# ========================= 示例使用 =========================
if __name__ == "__main__":
# 1. 初始化模型
# 假设我们有一个预训练的Vision Transformer作为骨干
from transformers import ViTModel
vit = ViTModel.from_pretrained('google/vit-base-patch16-224')
vit_backbone = vit.embeddings # 简化示意,实际需要调整
model = Dinomaly2(
backbone=vit_backbone,
feature_dim=256,
memory_size=1000,
scales=[1, 2, 4]
)
# 2. 训练模型(假设已有正常样本的数据加载器)
# train_dinomaly2(model, train_loader, num_epochs=100)
# 3. 推理测试
# 创建模拟测试数据
batch_size = 4
channels = 3
height, width = 224, 224
test_images = torch.randn(batch_size, channels, height, width)
# 进行推理
results = inference_dinomaly2(model, test_images, anomaly_threshold=0.5)
print("推理结果:")
print(f"- 像素级异常图形状: {results['pixel_anomaly_map'].shape}")
print(f"- 图像级异常分数: {results['image_anomaly_score']}")
print(f"- 检测到异常图像数量: {results['image_anomaly_binary'].sum()}")无监督异常检测旨在无需先验异常类型知识的情况下识别数据中的异常模式,是计算机视觉领域的一项基础任务,其应用范围涵盖工业质量检测 [1], [2]、医疗诊断 [3], [4] 乃至安防监控系统 [5], [6]。异常本身固有的稀缺性和多样性使得该任务尤为困难,因为模型必须学会从有限的数据中学习正常模式,同时在推理过程中对偏离保持敏感。
早期的图像无监督异常检测方法遵循"一类一模型"的范式 [7], [8],即为每个物体类别或场景单独训练模型(图 1(a.1))。虽然该策略在受控场景下被证明有效,但在实际应用中面临严重的可扩展性挑战。管理数百个独立模型所需的存储开销、计算复杂度和维护负担在实际部署中是难以承受的。近期的研究进展已转向多类别无监督异常检测(图 1(a.2)),即设计单一的统一模型来同时处理多个物体类别 [9]–[11]。
近期方法利用了多种技术,包括邻域掩码注意力 [9]、合成异常 [10]、向量量化 [12]、扩散模型 [13], [14] 以及状态空间模型 [15],以捕捉正常模式的紧凑表征,这对于区分异常至关重要。然而,当由于存在多样类别而导致类内正常模式变得过度复杂时,其底层分布将难以刻画,从而降低检测性能 [9]。然而,尽管复杂设计层出不穷,最先进的多类别模型与单类别模型之间仍存在不可忽视的性能差距(如图 1(d) 所示),这限制了统一模型的实际应用。
此外,现有的无监督异常检测方法存在一个更根本的问题:方法论的碎片化。该领域已演变成一个专门化框架的集合,每个框架都针对特定的任务设置。例如,为普通 2D 检测设计的方法 [9], [10] 无法不经修改自然地扩展到多模态数据 [16], [17]。为少样本无监督异常检测设计的方法通常需要利用视觉语言模型或元学习的全新流程 [18], [19]。这种专门方法的激增给从业者带来了巨大障碍。他们在不同场景下部署异常检测时,必须掌握多个框架、维护不同的代码库并管理不兼容的架构。随着无监督异常检测应用的持续多样化,对统一框架的需求变得至关重要。
在本文中,我们旨在通过一个反直觉的命题来挑战当前趋向架构复杂性的主流趋势:通用异常检测需要的是简化,而非特化。
在此,我们提出 Dinomaly2,这是首个多类别、全谱系的无监督异常检测方法,它通过有原则的极简主义,在数据模态、任务设置和应用领域上实现了前所未有的统一。Dinomaly2 基于神经网络的认知行为 [20],通过重构误差来检测异常区域(图 2)。Dinomaly2 的核心在于"少即是多"的理念,提出了五个简单而关键的组件:
此外,我们提出了简单的策略,将基础的 2D Dinomaly2 扩展到多种场景,并在多视角检测、RGB+3D 点云、RGB+红外图像以及少样本无监督异常检测等任务中实现了最先进的性能(图 1(b.2-4))。我们通过引入推理统一的多类别无监督异常检测 设置(图 1(a.3))进一步推动该领域发展,该设置要求使用单一阈值检测混合类别中的异常——这是迈向真正统一部署的关键一步。此设置暴露了依赖特定类别校准的现有方法的局限性,同时展示了 Dinomaly2 在此挑战性设置下的鲁棒性。
我们通过最全面的评估验证了 Dinomaly2 的普适性,评估范围涵盖 12 个基准数据集(147 个不同类别/场景,表 1)、四种数据模态(2D、多视角 2D、RGB-3D、RGB-IR)、四种任务设置(单类别、多类别、推理统一多类别、少样本)以及三大应用领域(工业、生物、户外)。在流行的 2D 工业数据集上,我们的统一模型在 MVTec-AD [1]、VisA [21] 和 Real-IAD [2] 上分别达到了 99.9%、99.3% 和 92.1% 的图像级 AUROC,超越了多类别和单类别的最先进方法(图 1(d))。对于多视角检测,Dinomaly2 在 Real-IAD 和 MANTA-Tiny [22] 上分别达到了 94.9% 和 94.6% 的物体级 AUROC。对于带有 3D 信息(MVTec3D [23])或红外图像(MulSenAD [24])的检测,Dinomaly2 分别取得了前所未有的 97.4% 和 97.6% 的图像级 AUROC,优于专门的多模态无监督异常检测方法。在工业领域之外,Dinomaly2 在医学图像 [25]、生命科学显微图像 [26]、户外设施维护 [27] 和监控无人机影像 [28] 中也表现出色。此外,Dinomaly2 在少样本限制下展现出强大的适应性,在 MVTec-AD 和 VisA 上仅使用每类 8 个正常样本即可分别达到 98.7% 和 97.4% 的图像级 AUROC。我们还首次对无监督异常检测的可扩展性进行了系统性研究,揭示了 Dinomaly2 在模型大小、分辨率和基础模型质量方面均表现出强大的正向缩放规律(图 1(e))。
本工作的一个初步版本已发表于 CVPR 2025(Dinomaly [29])。本工作将初始版本扩展为一个更全面、更强大的框架,主要贡献包括:(1) 上下文感知重中心化:通过特征空间变换解决多类别混淆问题;(2) 向多种模态和设置的自然扩展:包括多视角、RGB-3D、RGB-IR 和少样本无监督异常检测,无需重大修改即可达到最先进结果;(3) 引入推理统一的多类别无监督异常检测设置:推进实现模型使用单一阈值在混合类别上操作的完全统一;(4) 跨 12 个基准数据集的全面验证:涵盖多种模态、设置和领域,确立了 Dinomaly2 作为通用异常检测框架的地位。
3.1 Dinomaly2 框架概述
从已知事物中识别异常是人类与生俱来的能力,也是我们探索世界的重要途径。类似地,我们构建了一个基于重构的框架,该框架依赖于人工神经网络的认知特性。Dinomaly2 包含一个编码器、一个瓶颈层和一个重构解码器,如图 2 所示。

3.2 噪声瓶颈
解决恒等映射问题的一个直接方案是将"重构"转变为"修复"。具体来说,不是在给定正常输入时直接重构正常图像或特征,而是在输入图像 [37], [39] 或特征 [9], [13] 上引入扰动作为伪异常。解码器仍然需要修复无异常的图像或特征,形成一个类似去噪的框架。然而,此类方法依赖于启发式和合成的异常生成策略,这些策略可能无法很好地跨领域、跨数据集和跨方法泛化。
"你只需要 Dropout。"

3.3 非聚焦线性注意力
Softmax 注意力 [57] 是 Transformer 架构的基石,使模型能够动态捕捉输入不同部分之间的关系。然而,在多类别异常检测的背景下,注意力的优势本身变成了一个负担。
在 Dinomaly2 中,我们利用线性注意力固有的不聚焦能力,将其视为异常检测的理想特性。为了检查注意力如何传播信息,我们训练了使用原始 Softmax 注意力或线性注意力作为解码器中空间混合器的两个 Dinomaly 变体,并可视化它们的注意力图。如图 3(a) 所示,Softmax 注意力倾向于聚焦于查询的精确区域,而线性注意力则将其注意力扩散到整个图像。从频域视角看,线性注意力充当一个低通滤波器 [61],无法选择性地放大局部高频细节(图 3(b)),迫使网络基于学习到的全局模式进行重构。这种扩散性传播阻止了异常检测中的恒等映射。
3.4 上下文感知重中心化
多类别无监督异常检测的一个基本挑战源于异常的上下文依赖性:相同的视觉特征在一种上下文中可能是正常的,但在另一种上下文中却是异常的。例如,车辆在高速公路上是正常的,但在人行道上却是异常的;而行人在人行道上是预期的,但在高速公路上则变成异常。这种上下文模糊性在统一的多类别模型中尤其成问题,解码器可能学会在任何上下文中重构车辆和行人,从而削弱异常检测能力。
"美存在于观者之眼。"
为了解决这一挑战,我们引入了上下文感知重中心化,这是一种简单而有效的机制,它将特征重构条件化于类别特定的上下文。关键见解是利用视觉 Transformer 的类别令牌作为上下文锚点,该锚点编码了类别特定的正常模式。
这种简单的减法操作在没有任何计算开销的情况下,通过两种机制解决了多类别混淆问题。首先,通过使用类别令牌作为锚点,来自不同类别的图像块特征被映射到不同的参考系,这些参考系在每个场景中是唯一的,相同的局部模式因此获得不同的含义。其次,减法操作隐式地将重构条件化于类别身份,而无需显式的类别标签。
3.5 松散重构
多类别无监督异常检测的一个关键挑战在于重构目标的设计。先前的特征重构无监督异常检测方法 [8], [34], [50] 通常遵循知识蒸馏范式 [62],其中解码器层被训练以精确模仿相应的编码器层。虽然这种层到层的监督提供了强大的学习信号,但它矛盾地使解码器在重构方面变得过于熟练,使其能够恢复甚至在训练期间从未明确遇到过的异常模式。
我们提出松散重构,它刻意放松了结构约束和优化目标,以防止多类别设置中的过度泛化。
"抓得越紧,失去越多。"

3.6 向普通 2D 无监督异常检测之外的无缝扩展
Dinomaly2 的极简主义哲学自然扩展到 2D 图像之外的各种数据模态。我们证明,只需对我们的框架进行最少的适配即可达到最先进的结果,而无需引入复杂的融合机制或模态特定的架构。
"如无必要,勿增实体。"
多视角检测。 工业检测通常需要从多个视角检查物体。对于多视角数据集,如 Real-IAD [2] 和 MANTA [22],每个物体从 V=5 个相机角度捕获。我们保持训练范式不变,包括所有类别的所有视角。在推理期间,所有视角的异常图被拼接起来,物体级异常分数计算为拼接后的异常图中最异常的 z% · V 个像素的平均值: s_object = mean( top_{z%·V}( [ ]_{v=1}^V A_v ) ) (20) 其中 A_v 表示视角 v 的异常图。这种直接的聚合策略整合了多视角信息,而无需特定视角的训练或复杂的视角间交互。
对齐的多模态。 对于结合了 RGB 图像和像素对齐的 3D 点云数据的数据集(例如 MVTec3D [23]),我们采用一种最小融合策略。3D 点云首先按照 [63] 的方法通过投影渲染为深度图。RGB 图像和深度图都通过相同的预训练 ViT 编码器独立处理,分别产生特征表示 F_rgb 和 F_depth。多模态编码器表示通过逐元素平均获得: F* = (1/2) (F_rgb + F_depth) (21) 随后的瓶颈层、解码器和重构过程与标准框架保持相同。这种朴素的融合不需要额外的参数或架构修改,允许 Dinomaly2 利用互补的几何和外观信息。
非对齐的多模态。 在某些场景中,多模态信息是在没有空间对齐的情况下获取的,即不同模态的图像是从不同的相机角度获取的。在这里,我们采用多视角检测策略的直接扩展:每种模态被视为一个独立的视角。例如,在 MulSenAD [24] 中,RGB 和红外图像在我们的统一框架中被处理为单独的视角。最终的物体级异常分数计算为两种模态的个体图像级异常分数之和。
少样本异常检测。 真实世界的无监督异常检测场景经常遇到数据稀缺问题,即只有有限的正常样本可用。Dinomaly2 的极简设计自然地适应多类别少样本设置,无需任何架构修改。我们的适配仅涉及最简单的增强:训练期间使用标准数据增强技术,包括随机翻转、旋转和平移。这些基本的增强扩展了有限的正常样本空间,而无需引入领域特定的假设(文本提示)[18] 或复杂的元学习策略 [47],这些是专门的少样本无监督异常检测方法所采用的。
4.1 实验设置
我们在广泛的基准测试上进行了全面评估,这被称为我们的无监督异常检测数据集马拉松(表 1)。具体来说,MVTec-AD [1] 是最广泛使用的工业异常检测基准,包含 5 个纹理类别和 10 个物体类别。VisA [21] 专注于具有挑战性异常模式的复杂工业产品(例如,一个图像中有多个物体)。MPDD [64] 针对真实工业条件下的金属零件制造中的视觉缺陷检测。BTAD [65] 包含真实世界的工业产品,其训练集本身带有噪声,包含一些异常样本 [66]。

Real-IAD [2] 是一个大型多视角数据集,包含 30 个类别,每个类别有 5 个相机视角。我们遵循官方划分。MANTA [22] 是一个用于微小物体(例如各种种子和豆类)的大规模多视角数据集。我们使用官方的 MANTA-Tiny 划分,该划分更易于获取。
MVTec3D [23] 包含由工业 3D 传感器扫描的点云,以及来自 10 个类别的配对 2D RGB 图像。3D 点云按照 [63] 的方法预处理为深度图。MulSen-AD [24] 是一个多传感器数据集,统一了来自 RGB 相机、激光扫描仪和锁定红外热成像仪的数据。我们使用 RGB 和红外图像。
Uni-Medical [67] 是一个医学无监督异常检测数据集,包含从 [25] 中提取的脑部 CT、肝脏 CT 和视网膜 OCT 的 2D 切片。ApoCell 包含来自秀丽线虫胚胎的共聚焦荧光显微镜图像,源自 [26]。正常图像仅包含存活细胞,而异常图像包含经历凋亡的细胞。MIAD [27] 包含具有不受控因素(视角、背景等)的户外图像,专注于维护检查以保持设备处于最佳工作状态。Drone-Anomaly [28] 是一个基于无人机的数据集,用于通过 UAV 相机进行异常事件监控。该数据集包含 7 个不同的场景,包括高速公路、十字路口、自行车环岛等。
指标。 遵循先前工作 [15], [50],我们采用七种评估指标。图像级 异常检测性能通过接收者操作特征曲线下面积(AUROC)、平均精确度(AP)和最佳阈值下的 F1 分数(F1-max)来衡量。像素级 异常定位通过 AUROC、AP、F1-max 和每区域重叠曲线下面积(AUPRO)来衡量。数据集的结果是所有类别的平均值。
实现细节。 默认情况下,我们使用通过 DINOv2-registers (DINOv2-R) [68] 预训练的 ViT-S/B/L 作为编码器。对于 12 层的 ViT-B 和 ViT-S,使用中间 8 层进行重构并输入瓶颈层。ViT-Large 包含 24 层;因此,我们每隔两层取一层:M = {5, 7, 9, ...19}。解码器始终包含 8 层。除非另有说明,否则为每个数据集的所有类别联合训练一个单一模型。
使用 StableAdamW 优化器 [69],参数为 lr(学习率)=2e-3,β=(0.9,0.999),wd(权重衰减)=1e-4,eps=1e-10。在 MUAD 设置下,模型按照表 1 中的迭代次数 It,以批量大小 16 进行训练。周期数可由 16·It / #Train 推导得出。学习率在前 100 次迭代中从 0 预热到 2e-3,并在整个训练过程中通过余弦退火降至 2e-4。在噪声瓶颈层中,基于简单的参数搜索,丢弃率设置为 0.2 或 0.4。默认使用 2 组的松散约束。控制 τ_k 的丢弃率在前 1000 次迭代中从 0% 线性增加到 90% 作为预热。输入图像大小默认为 392²,对于原始图像较小的 MANTA、Uni-Medical 和 ApoCell,则为 280²。异常图中前 1% 像素的平均值用作图像级异常分数。对于具有微小异常的 Real-IAD,我们使用前 0.1%。代码使用 Python 3.8 和 PyTorch 1.12.0 cuda 11.3 实现,实验在 NVIDIA GeForce RTX3090/4090 GPU(24GB)上运行。大部分对比的 MUAD 结果引用自一篇基准论文 ADer [67],我们对此表示感谢。代码将在以下地址提供:https://github.com/guojiajeremy/Dinomaly。
4.2 对比结果
(1) 2D 工业数据集上的多类别无监督异常检测。 我们首先在传统的 2D 工业数据集上将 Dinomaly2 与最先进的多类别无监督异常检测方法进行比较。如表 2 所示,配备 ViT-B 的 Dinomaly2-B 已经大幅超越先前的方法。我们最强的模型 Dinomaly2-L(配备 ViT-L)在所有基准测试中均取得了卓越的性能,在 MVTec-AD [1]、VisA [21]、MPDD [64] 和 BTAD [65] 上的 I-AUROC 分别达到 99.9%、99.3%、99.0% 和 96.0%。

在 MVTec-AD 上,我们的统一多类别模型实现了近乎完美的检测性能。Dinomaly2-L 在 I-AUROC 上大幅超越了之前的 SOTA 方法 MambaAD [15](99.9% vs. 98.6%),并且在像素级指标上显著优于后者,达到了 70.3% 的 AP 和 95.1% 的 AUPRO。在 VisA 上,Dinomaly2-L 相较于之前最好的方法 ReContrast [11] 带来了显著改进,I-AUROC 提升了 3.8%,AUPRO 提升了 3.5%。在 MVTec-AD 和 VisA 上的卓越表现表明,这些数据集在我们的方法下可能正接近性能饱和。在 MPDD 数据集上的性能提升也尤为显著,我们的方法达到了 99.0% 的 I-AUROC,相比之前的 SOTA 方法 DeSTSeg [39](92.6%)提高了 6.4%。
(2) 多视角多类别无监督异常检测。 真实世界的工业检测通常需要从多个视角检查物体以确保全面的缺陷检测。在 Real-IAD [2] 和 MANTA-Tiny [22] 上,Dinomaly2 在所有评估级别上都表现出卓越的性能。如表 3 所示,我们的方法在 Real-IAD 上实现了 94.9% 的物体级 AUROC,在 MANTA-Tiny 上实现了 94.6% 的物体级 AUROC。这些结果大幅超越了先前的方法,包括专门为多视角检测设计的、具有多视角交互操作的 MVAD [70]。
(3) RGB-3D 多类别无监督异常检测。 扩展到 2D 图像之外,我们在 MVTec3D [23] 上评估 Dinomaly2,该数据集结合了 RGB 图像和 3D 点云数据用于工业检测。如表 4 所示,我们仅使用 RGB 的模型已经达到了 95.0% 的 I-AUROC,这不仅大幅超越了先前基于 RGB 的方法,而且与利用 RGB+3D 输入的 SOTA 方法(94.4%)相比也达到了有竞争力的性能。
更重要的是,我们简单的 RGB-3D 融合策略(即对独立处理的 RGB 图像和深度图的特征进行逐元素平均)取得了前所未有的结果。Dinomaly2-L 达到了 97.4% 的 I-AUROC 和 98.6% 的 AUPRO,大幅超越了先前多模态特定的多类别 SOTA 方法 GLFM(94.4%, 93.1%)。这一卓越的性能验证了我们的假设:极简主义哲学自然地适用于不同的数据模态,无需专门的架构修改。
(4) RGB-IR 多类别无监督异常检测。 对于需要热信息的工业场景,我们在 MulSen-AD 上评估 Dinomaly2。如表 5 所示,我们直接的方法取得了卓越的性能。Dinomaly2-B 达到了 97.6% 的物体级 AUROC,大幅超越了专门的多传感器方法 TripleAD(94.2%)。
(5) 单类别无监督异常检测。 为了证明统一模型与专门的单类别方法相比不会降低性能,我们在传统的单类别设置下评估 Dinomaly2。如表 6 所示,我们经过多类别训练的模型达到了与专门的单类别方法相当甚至更优的性能。这一结果表明 Dinomaly2 成功弥合了多类别和单类别无监督异常检测方法之间长期存在的性能差距,使得统一模型在实际部署中切实可行。
(6) 少样本多类别无监督异常检测。 在资源受限、仅有有限正常样本可用的场景下,Dinomaly2 展示了卓越的少样本学习能力。如表 7 所示,仅使用每类 4 个正常样本,我们的方法在 MVTec-AD 上达到了 98.1% 的 I-AUROC,在 VisA 上达到了 96.7%。这些结果甚至优于专门为少样本异常检测设计的方法,包括 IIPAD [74](96.1%/88.3%)和 WinCLIP [18](94.0%/86.1%)。值得注意的是,Dinomaly2 仅使用每类 8 个样本就超越了 SOTA 的全样本 MUAD 模型。
(7) 推理统一的多类别无监督异常检测。 现有的多类别评估协议通常采用按类别分离的推理和评估。在此,我们引入了推理统一设置,即模型必须使用相同的阈值(如同只有一个场景一样)检测混合类别中的异常。无监督异常检测模型的关键在于不同物体类别间异常分数分布的失配。虽然正常样本和异常样本在每个类别内是良好分离的,但它们在统一的分数分布中变得不可分离,如图 6 所示。
如表 8 所示,在此挑战性设置下,Dinomaly2-B 在 MVTec-AD 上达到了 98.9% 的 I-AUROC,在 VisA 上达到了 97.8%,显著优于先前的方法。该评估更好地反映了真实世界的部署场景,即模型在没有类别身份先验知识的情况下遇到混合物体类别。
(8) 与基于 Dinomaly 的扩展的比较。 我们的框架不仅易于使用,而且具有高度可扩展性。自我们的 Dinomaly 代码库发布以来,许多后续方法基于该框架进行了构建。如表 9 所示,我们将 Dinomaly2 与这些近期技术和扩展进行了比较,包括内在正常原型(INP)[75]、训练后成本过滤机制 [76] 和视角间注意力模块 [77]。虽然这些扩展相较于原始 Dinomaly 提供了适度的改进,但 Dinomaly2 始终优于所有变体。这一比较验证了 Dinomaly2 中简单而有原则的改进(特别是上下文感知重中心化和优化的训练超参数)比复杂的修改和设计带来了更实质性的性能提升。
(9) 超越工业领域。 我们进一步评估了 Dinomaly2 在不同应用领域的普适性,如表 10 所示。在医学成像数据集 Uni-Medical [67] 上,我们的方法在检测脑部 CT、肝脏 CT 和视网膜 OCT 扫描中的病理状况方面达到了 SOTA 性能。在用于生物学研究的显微图像 [26] 中,Dinomaly2 在识别秀丽线虫胚胎中的凋亡细胞方面达到了 83.2% 的 I-AUROC。在具有不受控环境因素的户外维护场景 MIAD [27] 上,我们的方法达到了 84.5% 的 I-AUROC。对于 Drone-Anomaly 数据集 [28] 上的空中监控,Dinomaly2 成功地在 7 个不同场景的 UAV 图像中识别出异常事件。这些跨领域结果表明,极简主义的设计理念和通用特征表示使得能够在 vastly 不同的数据分布和应用背景下进行有效的异常检测,将 Dinomaly2 定位为真实世界异常检测应用的通用解决方案。
(10) 定性可视化。 我们可视化了 Dinomaly2 的输出异常图,如图 7 所示。异常图通过均值-最大值归一化在每张图像的基础上归一化到 [0, 1]。需要注意的是,我们所有可视化的样本都是随机选择的,没有进行刻意挑选。
4.3 消融研究
(1) 整体组件分析。 我们通过在 MVTec-AD 和 VisA 数据集上的全面消融研究,系统地评估了每个提出组件的有效性。如表 11 所示,我们的基线在 MVTec-AD 上达到了 98.73% 的 I-AUROC,在 VisA 上达到了 96.23% 的竞争性性能,证明了我们简单架构提供的强大基础。每个组件都对整体性能做出了有意义的贡献。噪声瓶颈层 提供了基础性增益,验证了我们的假设,即基于 Dropout 的简单噪声注入能有效防止多类别设置中的过度泛化。上下文感知重中心化 在 VisA 上显示出强劲的改进(+1.19% I-AUROC),那里的多类别混淆问题更为明显。非聚焦线性注意力 和松散约束 与其他组件展示了协同效应。虽然 UA 和 LC 单独提供适度的增益,但它们与 NB 和其他组件的结合产生了显著的改进。松散损失 在两个数据集上持续提高性能,这验证了我们的选择性优化策略,该策略减少了重构良好区域的梯度流。所有组件的完整组合实现了最佳性能:在仅 10,000 次训练迭代下,在 MVTec-AD 上达到 99.74%/99.82%/99.30%,在 VisA 上达到 98.89%/99.02%/96.47%,表明 Dinomaly2 通过简单而有效的设计有效地解决了多类别异常检测中的挑战。

5 结论
我们提出了 Dinomaly2,一个极简而通用的无监督异常检测框架,该框架从根本上重新构想了无监督异常检测系统的设计方式。我们在 Dinomaly2 中提出了五个关键要素,它们共同在各种数据模态、任务设置和应用领域中实现了卓越的性能,而无需复杂的架构设计。在多种基准测试上的广泛实验证明了我们的方法优于先前的专门化方法。架构简洁性、普适适用性和卓越性能的结合,使 Dinomaly2 成为一个适用于全谱系真实世界异常检测的实用框架。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。