深度学习论文: A Zero-/Few-Shot Anomaly Classification and Segmentation Method for CVPR 2023 VAND Workshop
深度学习论文: A Zero- and Few-Shot Anomaly Detection and Segmentation Method for CVPR 2023 VAND Workshop Challenge, Tracks 1 and 2: Secured First Place in Zero-shot Anomaly Detection and Fourth Place in Few-shot Anomaly Detection
PDF: https://arxiv.org/pdf/2305.17382.pdf
PyTorch代码: https://github.com/shanglianlm0525/CvPytorch
PyTorch代码: https://github.com/shanglianlm0525/PyTorch-Networks
1 概述
为了应对工业视觉检测中产品类型繁多的问题,在实际应用中我们构建了一个独特而高效的新模型——它既能适应多种类别又无需或仅需少量正常参考图像——这一创新方法显著提升了工业视觉检测的技术方案。同时针对2023年VAND挑战开发了一种基于零样本和少量样本跟踪的新方案
1)在零样本任务中,在CLIP模型上增加了额外的一层线性变换层(linear transformation layer),实现了将图像特征映射至联合嵌入空间的能力,并在此基础上与文本特征进行对比以生成异常图谱(anomaly maps)。
2)当存在参考图像时(few-shot),本方案通过多个memory bank存储了参考图像的特征表示,并在测试阶段将查询图像与其进行对比。
在这个挑战中, 我们的算法在Zero-Shot任务中获得第一名, 同时在图像分割任务上展现出色表现; 在少样本学习场景下(Few-Shot), 我们的方法在全球排名中位列第四, 在分类任务中的F1得分为全球最优.
核心要点:
通过整合状态信息和模板规则的提示机制来生成文本提示。在CLIP编码器提取图像特征求索后引入了一种额外的线性变换层将其映射到与文本特征求索相关的空间中。通过计算映射后的图像特征求索与文本特征求索之间的相似度得分可以生成相应的异常分布图。在few-shot学习框架中零样本阶段仍然保留了额外的一层线性变换并且保持其参数值不变。在测试过程中系统首先通过CLIP编码器提取参考样本的关键字描述子并将这些描述子存储于记忆库中随后通过比较测试样本与记忆库中的描述子来进行分类判断。在多级别特征融合策略下系统能够同时捕获图像中的低层次细节以及高层次抽象信息从而实现更全面的信息表达能力。
2 Methodology
基于CLIP整体架构的基础上开展零样本分类研究,并通过状态与模板集合相结合的方式构建出相应的文本提示信息

2-1 Zero-shot AD
Anomaly Classification
基于WinCLIP 异常分类框架的设计下,本研究构建了一种改进型文本提示集成策略,相较于传统方法未采用复杂的多尺度窗口策略,显著提升了基准模型在异常分类任务中的性能表现。具体而言,该集成策略包含两个主要模块:
- 在state-level阶段,我们采用了通用性更强的文本描述方式来表征目标状态,例如将"无损"和"损坏"等抽象状态作为描述依据,避免了过于具体的"边缘和角落有 chip"这类细节描述;
- 在template-level阶段,我们通过CLIP模型对ImageNet进行了优化筛选,最终确定了85个与异常检测任务相关的模板,并剔除了那些与该检测任务无关的一类模板。
通过上述两种阶段的联合表征机制,最终提取出的任务相关文本特征表示为F_{t} \in R^{2 \times C}的形式。
def encode_text_with_prompt_ensemble(model, texts, device):
prompt_normal = ['{}', 'flawless {}', 'perfect {}', 'unblemished {}', '{} without flaw', '{} without defect', '{} without damage']
prompt_abnormal = ['damaged {}', 'broken {}', '{} with flaw', '{} with defect', '{} with damage']
prompt_state = [prompt_normal, prompt_abnormal]
prompt_templates = ['a bad photo of a {}.',
'a low resolution photo of the {}.',
'a bad photo of the {}.',
'a cropped photo of the {}.',
'a bright photo of a {}.',
'a dark photo of the {}.',
'a photo of my {}.',
'a photo of the cool {}.',
'a close-up photo of a {}.',
'a black and white photo of the {}.',
'a bright photo of the {}.',
'a cropped photo of a {}.',
'a jpeg corrupted photo of a {}.',
'a blurry photo of the {}.',
'a photo of the {}.',
'a good photo of the {}.',
'a photo of one {}.',
'a close-up photo of the {}.',
'a photo of a {}.',
'a low resolution photo of a {}.',
'a photo of a large {}.',
'a blurry photo of a {}.',
'a jpeg corrupted photo of the {}.',
'a good photo of a {}.',
'a photo of the small {}.',
'a photo of the large {}.',
'a black and white photo of a {}.',
'a dark photo of a {}.',
'a photo of a cool {}.',
'a photo of a small {}.',
'there is a {} in the scene.',
'there is the {} in the scene.',
'this is a {} in the scene.',
'this is the {} in the scene.',
'this is one {} in the scene.']
text_features = []
for i in range(len(prompt_state)):
prompted_state = [state.format(texts[0]) for state in prompt_state[i]]
prompted_sentence = []
for s in prompted_state: # [prompt_normal, prompt_abnormal]
for template in prompt_templates:
prompted_sentence.append(template.format(s))
prompted_sentence = tokenize(prompted_sentence).to(device)
class_embeddings = model.encode_text(prompted_sentence)
class_embeddings /= class_embeddings.norm(dim=-1, keepdim=True)
class_embedding = class_embeddings.mean(dim=0)
class_embedding /= class_embedding.norm()
text_features.append(class_embedding)
text_features = torch.stack(text_features, dim=1).to(device).t()
return text_features
代码解读
基于状态级和模板级的集成方案中, 通过CLIP模型提取文本编码信息, 分别计算正常样本集与异常样本集的均值向量. 最终, 将这两组均值向量与图像编码结果进行对比分析. 通过应用Softmax函数得到各类别概率作为分类依据. 最后识别结果由矩阵s的第二维向量决定. 数学表达式如下:
s = \text{softmax}(F_{c}F_{t}^{T})
text_probs = (100.0 * image_features @ text_features.T).softmax(dim=-1)
results['pr_sp'].append(text_probs[0][1].cpu().item())
代码解读
Anomaly Segmentation
patch_tokens = linearlayer(patch_tokens)
anomaly_maps = []
for layer in range(len(patch_tokens)):
patch_tokens[layer] /= patch_tokens[layer].norm(dim=-1, keepdim=True)
anomaly_map = (100.0 * patch_tokens[layer] @ text_features.T)
B, L, C = anomaly_map.shape
H = int(np.sqrt(L))
anomaly_map = F.interpolate(anomaly_map.permute(0, 2, 1).view(B, 2, H, H),
size=img_size, mode='bilinear', align_corners=True)
anomaly_map = torch.softmax(anomaly_map, dim=1)[:, 1, :, :]
anomaly_maps.append(anomaly_map.cpu().numpy())
anomaly_map = np.sum(anomaly_maps, axis=0)
代码解读
在CLIP部分参数被冻结的情况下,Linear Layer的训练过程采用了结合了focal loss与dice loss两种损失函数的设计。
2-2 Few-shot AD
异常分类任务
在few-shot场景中,图像中的异常检测结果可被划分为两个主要组成部分。其中一部分与zero-shot场景下的处理方式一致。剩余的部分则采用了广泛应用于异常检测领域的传统策略,并通过分析anomaly map中的最大值来提取关键特征。所提方法将这两个模块的结果进行综合汇总后确定最终的异常评分。
Anomaly Segmentation
基于记忆库的方法被采用,在图1所示的部分区域中可见。
具体而言,则是通过计算查询样本与记忆库内支持样本之间的余弦相似度矩阵,并经过重塑操作获得异常图谱;随后将该结果与零样本学习获得的异常图谱叠加以实现最终分割预测效果。
值得注意的是,在所述的少样本分割任务中,并未对原有的线性层进行微调优化;相反地,则采用了零样本学习阶段训练完成的有效权重参数进行直接应用。
3 Experiments

简单来说,在较为简单的图像中zero-shot和few-shot的表现相当接近,在更为复杂的场景中,则能见到few-shot带来的些许提升。
