基于CLIP的零样本异常检测:无需训练,让AI看图说话识别缺陷 📅 发布时间:2026/8/25 10:45:09 👁 浏览次数: 1. 项目概述当AI学会“看图说话”来发现异常最近在工业质检和运维监控的圈子里一个老生常谈但又始终在演进的话题就是“异常检测”。传统的路子无论是基于统计模型还是早期的机器学习都高度依赖大量标注好的“异常样本”来训练模型。但现实很骨感——异常之所以叫异常就是因为它稀少且形态各异想收集一个完备的异常样本库成本高得吓人很多时候甚至不可能。这就让很多异常检测项目卡在了数据准备的起跑线上。所以当“无监督”或“少样本”异常检测的概念出来时大家眼睛都亮了。而最近随着多模态大模型我们常说的视觉-语言基础模型比如CLIP的爆火一种全新的思路开始浮现能不能让AI像人一样通过“看”视觉和“理解描述”语言的先天能力直接判断一个东西是否正常完全不用针对特定场景进行训练这就是“LogSAD”这个项目标题背后让我兴奋的核心点。它直指一个前沿方向基于视觉和语言基础模型的无训练异常检测。简单来说LogSAD试图利用像CLIP这类已经在大规模图文对上预训练好的模型。这类模型有一个神奇的特性它能把一张图片和一个文本描述映射到同一个语义空间里并计算它们的相似度。那么一个很自然的想法就产生了如果我给一个待检测的物体比如一个工业零件拍张照然后我用文本描述一下这个物体“正常”时应该是什么样子例如“一个表面光滑、无裂纹的金属齿轮”再描述一下“异常”可能是什么样子例如“一个带有裂纹或锈蚀的金属齿轮”。接着我让CLIP模型分别计算图片与“正常”文本、图片与“异常”文本的相似度。如果图片更像“正常”描述则判为正常如果更像“异常”描述则判为异常。整个过程CLIP模型本身的参数是冻结的不需要用任何目标场景的数据去训练或微调它——这就是“无训练”的精髓。这听起来有点“玄学”但背后的逻辑其实很符合人的直觉。我们人类判断一个东西是否异常不就是基于已有的常识相当于大模型的海量预训练知识和对“正常”与“异常”的语言理解吗LogSAD正是将这种直觉过程形式化、可计算化了。它特别适合那些缺乏异常样本、但可以对“正常”状态进行相对清晰描述的领域比如工业外观检测、医疗影像筛查、网络流量模式识别甚至是一些创意内容审核。2. 核心思路拆解如何让通用大模型“零样本”上岗LogSAD的核心在我看来是把一个复杂的检测问题转化为了一个在统一语义空间内的“相似度比较”问题。它的成功与否高度依赖于两个支点一是所选用的视觉-语言基础模型本身的能力二是如何构建有效的文本提示Prompt来引导模型做出我们想要的判断。2.1 视觉-语言基础模型的选择与考量目前CLIPContrastive Language-Image Pre-training系列模型是这方面事实上的标准。它由OpenAI提出通过在数亿计的“图片-文本对”上进行对比学习训练学会了将图像和文本编码到同一个高维向量空间中使得语义相近的图文对在空间中的距离更近。对于LogSAD项目选择CLIP模型时有几个关键考量点模型变体与规模CLIP有不同规模的版本如RN50基于ResNet50、ViT-B/32、ViT-L/14等。越大的模型通常具有更强的泛化能力和语义理解深度但计算开销也越大。对于实时性要求高的工业检测可能需要在精度和速度间权衡ViT-B/32是一个不错的起点。如果对精度要求极致且算力充足ViT-L/14或更新更大的模型是更好的选择。特征提取层面CLIP的视觉编码器通常是Vision Transformer会输出多个层次的特征。浅层特征包含更多细节和纹理信息适合检测划痕、污点深层特征包含更多高级语义信息适合判断结构缺失、装配错误。在LogSAD中可能需要实验对比不同层次特征的效果或者尝试融合多尺度特征来应对不同类型的缺陷。模型固有偏差CLIP是在非常广泛的互联网数据上训练的它可能对某些领域如常见物体、自然场景的理解远超对高度专业化工业零件的理解。这会导致模型对专业领域描述的敏感性不足。虽然我们追求“无训练”但有时需要对文本提示词进行非常精细的设计来弥补这种偏差。注意直接使用开源的CLIP预训练模型时务必了解其训练数据的时间截止点。它可能不认识训练数据截止日后出现的新概念或特定术语这时需要在提示词中采用更通用、更贴近模型“知识库”的描述方式。2.2 提示词工程定义“正常”与“异常”的艺术这是LogSAD项目中最具“手艺活”特性的部分也是效果提升的关键杠杆。模型本身是固定的我们能操作的主要就是输入给它的文本提示。这里的核心是构建一个有效的“正常提示”和一系列覆盖性的“异常提示”。1. 正常提示的构建正常提示的目标是精准捕捉目标物体在完好状态下的所有关键视觉属性和上下文。不能太笼统也不能陷入无关细节。基础模板“一张高质量、光线均匀的照片展示了一个[物体类别]处于完好无损的状态。”属性细化加入关键属性如材质金属、塑料、颜色银色、黑色、表面质感光滑、磨砂、关键部件带有齿牙、中心有孔。上下文引入有时需要环境信息例如“在整洁的白色检测台上”这有助于模型排除背景干扰。示例对于一个金属螺栓正常提示可以是“一张特写照片展示了一个崭新、银色、螺纹清晰、无任何磨损或划痕的六角金属螺栓放置在灰色工业背景上。”2. 异常提示的构建异常提示需要尽可能覆盖可能出现的缺陷类型。由于缺陷的多样性通常需要构建一个“异常提示集合”。分类别描述将异常分为几大类每类用一句或几句提示描述。结构类“一个带有裂纹或断裂的[物体类别]。”“一个发生形变或弯曲的[物体类别]。”表面类“一个表面有深划痕、刮伤或凹痕的[物体类别]。”“一个带有锈蚀、污渍或腐蚀斑点的[物体类别]。”装配/缺失类“一个缺少了关键部件的[物体类别]。”“一个沾染了油污或异物的[物体类别]。”通用异常提示也可以尝试一些更通用的描述如“一张有缺陷的、损坏的[物体类别]的照片。”或“一个低质量的、需要被拒收的[物体类别]。”。这类提示的泛化能力有时会出人意料。组合策略最终我们会有一个正常提示T_normal和一个包含K个描述的异常提示集合{T_abnormal_1, T_abnormal_2, ..., T_abnormal_K}。在推理时需要设计一个策略来综合这些异常提示的得分。2.3 相似度计算与决策逻辑流程可以概括为编码 - 计算 - 聚合 - 决策。编码将待检测图像I输入CLIP的视觉编码器得到图像特征向量v_img。将正常提示T_normal和所有异常提示{T_abnormal_i}分别输入CLIP的文本编码器得到对应的文本特征向量v_text_normal和{v_text_abnormal_i}。计算相似度计算图像特征与每个文本特征的余弦相似度Cosine Similarity。这是CLIP空间中最自然的度量方式。S_normal cosine_sim(v_img, v_text_normal)S_abnormal_i cosine_sim(v_img, v_text_abnormal_i) 其中 i 1...K异常得分聚合如何将K个异常提示的相似度合并成一个总的“异常倾向”得分S_abnormal常见方法有最大值法S_abnormal max(S_abnormal_1, ..., S_abnormal_K)。认为只要图像与任何一种异常描述高度相似就是异常。这种方法对各类缺陷敏感但可能因某个异常提示过于宽泛而误报。平均值法S_abnormal mean(S_abnormal_1, ..., S_abnormal_K)。更稳健但可能稀释了强相关异常信号。加权平均法根据先验知识或验证集表现为不同异常提示分配权重。决策最终我们得到两个分数S_normal和S_abnormal。最简单的决策规则是如果S_normal S_abnormal则判定为正常。如果S_normal S_abnormal则判定为异常。 更复杂的可以设置一个阈值delta当S_abnormal - S_normal delta时才判定为异常以控制误报率。3. 实操搭建与核心环节实现理论说再多不如动手跑一遍。下面我将以一个具体的例子——“金属表面划痕检测”来演示如何从零搭建一个简易的LogSAD检测流程。我们假设场景是检测金属板材表面的划痕、凹痕等缺陷。3.1 环境准备与依赖安装首先需要一个Python环境3.8以上。核心库是openai-clip官方实现或transformers库Hugging Face提供的CLIP集成。这里我推荐使用transformers因为它更新维护更活跃且集成度高。# 创建虚拟环境可选但推荐 python -m venv logsad_env source logsad_env/bin/activate # Linux/Mac # logsad_env\Scripts\activate # Windows # 安装核心依赖 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 根据你的CUDA版本选择这里以CPU为例 pip install transformers pillow pandas numpy pip install opencv-python # 用于图像读取和处理可选PIL也可3.2 模型加载与初始化我们使用transformers加载预训练的CLIP模型和处理器。处理器负责将图像和文本转换为模型所需的格式。from transformers import CLIPProcessor, CLIPModel import torch from PIL import Image # 选择模型这里使用 OpenAI 的 CLIP ViT-B/32 model_name openai/clip-vit-base-patch32 model CLIPModel.from_pretrained(model_name) processor CLIPProcessor.from_pretrained(model_name) # 将模型设置为评估模式 model.eval() # 如果有GPU可以转移到GPU上 device cuda if torch.cuda.is_available() else cpu model.to(device) print(fModel loaded on {device})3.3 构建提示词与图像预处理根据我们的金属板材场景设计提示词。图像需要被预处理成模型接受的格式。# 1. 定义提示词集合 normal_prompt a high-resolution photo of a clean, smooth, flawless metal surface under even lighting. abnormal_prompts [ a photo of a metal surface with deep scratches or scoring marks., a metal surface containing dents or deformation., a corroded or rusted metal surface., a metal surface with visible stains or contamination., a defective, damaged metal surface. ] # 将所有提示词放入一个列表方便批量处理 all_prompts [normal_prompt] abnormal_prompts # 2. 加载并预处理待检测图像 image_path path/to/your/test_metal_surface.jpg image Image.open(image_path).convert(RGB) # 确保是RGB格式 # 使用处理器处理图像和文本 inputs processor(textall_prompts, imagesimage, return_tensorspt, paddingTrue) # 将输入数据转移到与模型相同的设备上 inputs {k: v.to(device) for k, v in inputs.items()}3.4 前向推理与相似度计算将处理好的输入喂给模型得到图像和文本的特征然后计算余弦相似度。# 禁用梯度计算加快推理速度 with torch.no_grad(): outputs model(**inputs) # 提取图像特征和文本特征 # outputs.image_embeds 形状: [1, feature_dim] # outputs.text_embeds 形状: [len(all_prompts), feature_dim] image_features outputs.image_embeds text_features outputs.text_embeds # 计算余弦相似度 # 首先对特征进行L2归一化这是计算余弦相似度的标准步骤 image_features image_features / image_features.norm(dim-1, keepdimTrue) text_features text_features / text_features.norm(dim-1, keepdimTrue) # 计算相似度矩阵logit_scale是CLIP模型学习到的温度参数用于缩放相似度 logit_scale model.logit_scale.exp() similarities logit_scale * image_features text_features.T # 形状: [1, len(all_prompts)] # 将相似度从Tensor转换为数值 similarities similarities.cpu().numpy().flatten() normal_score similarities[0] # 第一个是正常提示的相似度 abnormal_scores similarities[1:] # 后面的是各个异常提示的相似度 print(f与正常描述的相似度: {normal_score:.4f}) for i, (prompt, score) in enumerate(zip(abnormal_prompts, abnormal_scores)): print(f与异常描述{i1} ({prompt[:50]}...) 的相似度: {score:.4f})3.5 决策逻辑实现采用最大值法聚合异常得分并进行最终判断。# 聚合异常得分取最大值认为只要像任何一种异常描述风险就高 aggregated_abnormal_score abnormal_scores.max() # 决策 threshold 0.0 # 基础阈值可以调整。这里简单认为异常分超过正常分即为异常。 if normal_score aggregated_abnormal_score threshold: decision 正常 confidence normal_score - aggregated_abnormal_score else: decision 异常 confidence aggregated_abnormal_score - normal_score print(f\n决策结果: {decision}) print(f置信度差值: {confidence:.4f} (正常分-异常分)) # 可以进一步设置一个置信度阈值例如 confidence 0.1 才最终确定低于此值则视为“不确定”3.6 封装与批量处理将上述步骤封装成函数或类便于对大量图像进行循环检测并输出结构化的结果如CSV文件。import os import pandas as pd class LogSADDetector: def __init__(self, model_nameopenai/clip-vit-base-patch32): self.model CLIPModel.from_pretrained(model_name) self.processor CLIPProcessor.from_pretrained(model_name) self.device cuda if torch.cuda.is_available() else cpu self.model.to(self.device) self.model.eval() # 定义提示词可根据不同检测任务修改 self.normal_prompt a high-resolution photo of a clean, smooth, flawless metal surface under even lighting. self.abnormal_prompts [...] # 同上文 self.all_prompts [self.normal_prompt] self.abnormal_prompts def predict(self, image_path): # 图像加载与预处理 image Image.open(image_path).convert(RGB) inputs self.processor(textself.all_prompts, imagesimage, return_tensorspt, paddingTrue) inputs {k: v.to(self.device) for k, v in inputs.items()} # 推理 with torch.no_grad(): outputs self.model(**inputs) image_features outputs.image_embeds text_features outputs.text_embeds image_features image_features / image_features.norm(dim-1, keepdimTrue) text_features text_features / text_features.norm(dim-1, keepdimTrue) logit_scale self.model.logit_scale.exp() similarities (logit_scale * image_features text_features.T).cpu().numpy().flatten() normal_score similarities[0] abnormal_scores similarities[1:] aggregated_abnormal_score abnormal_scores.max() max_abnormal_idx abnormal_scores.argmax() decision 正常 if normal_score aggregated_abnormal_score else 异常 confidence abs(normal_score - aggregated_abnormal_score) most_similar_abnormal self.abnormal_prompts[max_abnormal_idx][:100] # 截取部分描述 return { image_path: image_path, normal_score: normal_score, abnormal_score: aggregated_abnormal_score, decision: decision, confidence: confidence, most_similar_defect: most_similar_abnormal } # 批量检测示例 detector LogSADDetector() image_dir path/to/your/test_images results [] for img_name in os.listdir(image_dir): if img_name.lower().endswith((.png, .jpg, .jpeg)): img_path os.path.join(image_dir, img_name) result detector.predict(img_path) results.append(result) # 保存结果 df pd.DataFrame(results) df.to_csv(detection_results.csv, indexFalse) print(批量检测完成结果已保存至 detection_results.csv)4. 效果调优与高级技巧实录搭起来容易调好却需要功夫。在实际应用中原始的LogSAD流程可能面临灵敏度不足、误报率高、对复杂背景鲁棒性差等问题。下面分享几个我实践中总结的调优技巧。4.1 提示词工程的进阶玩法基础提示词可能不够“给力”需要一些策略来增强其表达能力。多角度正常描述不要只用一个“正常”提示。可以构建一组从不同角度描述正常的提示然后取它们与图像相似度的平均值或最小值作为最终的S_normal。例如“A flawless metal surface.”“A perfectly manufactured metal part without defects.”“An ideal sample of a metal sheet.”使用最小值最严格的正常标准可以降低漏报将异常判为正常但可能增加误报。异常提示的细化与分级对于已知的特定缺陷描述要尽可能具体。例如针对划痕可以描述“thin, straight, shiny lines on the surface”针对凹痕描述“localized depression with blurred edges”。甚至可以构建分级提示如“minor scratch”, “severe scratch”观察模型对不同严重程度的响应。使用否定和对比在提示词中明确排除正常情况。例如异常提示可以是“a metal surface that is not smooth and has imperfections.”或者“a photo of a metal surface, different from a perfect one.”这种方法有时能更好地激活模型对“异常”这个抽象概念的理解。模板集成与自动提示生成可以设计一些模板如“a photo of [object] with [defect]”然后枚举不同的缺陷填入。更前沿的做法是使用大语言模型LLM根据任务描述自动生成一组丰富多样的提示词。4.2 特征层面的操作超越CLS Token默认情况下我们使用的是CLIP视觉编码器输出的全局特征通常是[CLS]token对应的特征。但对于一些局部缺陷全局特征可能会被大量正常区域的信息所“淹没”。多尺度特征融合提取Vision Transformer中间层的特征图而非仅仅最后一层的CLS token。这些特征图保留了空间信息。可以对特征图进行全局平均池化GAP或最大池化GMP得到多个特征向量然后分别与文本特征计算相似度最后综合这些相似度得分。这能让模型同时关注全局外观和局部细节。区域提议与局部匹配这是一个更复杂的思路。先用简单的边缘检测或显著性检测方法找出图像中可能“有问题”的局部区域Region of Interest, ROI然后分别将这些局部区域裁剪出来送入CLIP计算与异常提示的相似度。如果某个局部区域与异常提示的相似度异常高即使全局图像与正常提示相似度也不低也可以判定为异常。这种方法对小型、局部缺陷非常有效。4.3 决策逻辑的优化阈值与后处理直接比较S_normal和S_abnormal的原始分数可能不稳定因为不同图像、不同提示词的绝对分数范围可能有差异。分数标准化对一个验证集包含少量已知正常和异常样本计算所有样本的S_normal和S_abnormal分数。然后计算整个验证集上这两个分数的均值和标准差对待检测样本的分数进行Z-score标准化。score_norm (score_raw - mean) / std。在标准化后的分数空间里设置阈值会更鲁棒。动态阈值阈值不应该是固定的。可以根据历史检测结果的分数分布动态调整阈值。例如维护一个正常样本分数的滑动窗口以窗口内正常分数均值的某个倍数如均值减去3倍标准差作为异常判定的动态阈值。集成多个模型或提示集使用不同CLIP变体如RN50和ViT或不同的提示词集合分别进行检测然后对它们的决策进行投票如多数决。集成学习能有效提升系统的稳定性和泛化能力。5. 常见问题、局限性与实战避坑指南尽管LogSAD思路新颖但在实际落地中会遇到不少挑战。下面是我在几个项目中踩过的坑和对应的思考。5.1 典型问题与排查问题现象可能原因排查与解决思路对所有图像都判为“正常”1. 异常提示词太弱或太具体与真实缺陷不匹配。2. 正常提示词过于宽泛或强大得分总是最高。3. 图像背景复杂干扰了主体特征。1.检查提示词用一些明显的缺陷图片测试看异常提示的得分是否显著上升。尝试更通用、更强烈的异常描述词如“damaged”, “broken”, “flawed”。2.削弱正常提示将正常提示从“完美”降级为“标准”或“合格”例如从“flawless”改为“acceptable”。3.预处理图像尝试对图像进行裁剪、分割只保留ROI区域进行检测。误报率极高1. 正常提示词描述力不足无法覆盖正常样本的合理波动如光照变化、角度变化。2. 异常提示词过于宽泛将一些正常的纹理或变化也囊括进去了。3. 决策阈值设置得太低。1.增强正常提示使用多角度正常描述集合并用其平均分或最低分作为正常分。2.细化异常提示避免使用“different”, “strange”这类模糊词。将异常具体化到已知缺陷类型。3.调整阈值收集一批确认正常的样本观察其S_abnormal - S_normal的分布将阈值设置在该分布的较高百分位如95%。对某些缺陷不敏感1. 该缺陷类型未包含在异常提示集合中。2. CLIP模型在预训练时未充分学习此类缺陷的视觉-语言关联。3. 缺陷尺寸太小在全局特征中被稀释。1.扩充异常提示分析漏检的缺陷用语言精确描述它加入提示集。2.尝试局部分析采用4.2节提到的区域提议方法聚焦于图像局部。3.考虑模型局限性承认CLIP的边界。对于极其专业、罕见的缺陷纯无训练方法可能力有不逮需要引入少量样本进行提示学习Prompt Learning或模型微调。推理速度慢1. 使用的CLIP模型过大如ViT-L/14。2. 提示词数量过多导致文本编码计算量大。3. 未启用GPU或批处理。1.模型选型在精度可接受的前提下换用更小的模型如ViT-B/32或RN50。2.精简提示词通过实验筛选出最有效的少数几个提示词去除冗余。3.优化代码确保使用GPU并对批量图像进行一次性编码注意文本提示需相同利用transformers库的批处理能力。5.2 方法的核心局限性依赖模型的先验知识效果完全建立在CLIP等基础模型的海量预训练知识上。如果待检测的异常是模型在预训练中极少“见过”或“理解”的例如某种极其特殊的工业缺陷效果会大打折扣。语言描述的模糊性与偏差如何用语言精准描述“正常”和“异常”本身就是一个挑战。描述偏差会直接导致检测偏差。不同的人可能会写出不同的提示词从而得到不同的结果可重复性面临挑战。对复杂背景和多个对象的处理能力弱当前方法通常假设图像主体清晰、背景干净。如果待检物体位于杂乱背景中或画面中有多个物体模型可能会被干扰因为它缺乏对图像的空间结构理解。定量评估困难由于是无训练方法很难像监督学习那样在测试集上优化出一个“最佳”模型。其性能评估更依赖于在特定数据集上的零样本Zero-Shot测试结果调优过程更像是一种“提示词工程”和“启发式参数搜索”。5.3 实战避坑心得起步阶段用ViT-B/32足矣不要一开始就追求最大的模型。ViT-B/32在速度和精度上取得了很好的平衡是快速验证想法和搭建原型的首选。在明确效果瓶颈是模型能力后再考虑升级。构建一个小的“验证集”至关重要即使没有大量标注数据也尽量收集几十张涵盖正常、典型异常、以及容易混淆的“困难样本”图片。这个微型验证集是你调试提示词、观察分数分布、设定阈值的唯一依据。没有它调优就是盲人摸象。可视化相似度热图这是一个非常有效的调试工具。利用Grad-CAM或类似技术生成图像区域与某个文本提示如“scratch”的相似度热图。这能直观地告诉你模型到底是看到了划痕才给高分还是因为其他无关区域。很多问题通过热图一看便知。LogSAD更适合作为“初筛”或“辅助”工具鉴于其可能存在的误报和漏报在要求极高的场景如安全关键领域不要完全依赖它做最终判决。可以将其作为第一道快速筛查关卡筛出疑似异常再由更精确的传统算法或人工进行复核能极大提升整体效率。拥抱“少样本”微调当发现纯零样本效果遇到天花板时不要死磕。可以考虑收集目标场景下的少量样本比如每个缺陷类型5-10张对CLIP的文本编码器进行轻量级的提示学习如CoOp, CLIP-Adapter或者只微调一个额外的适配器层。这能在保留大模型通用知识的前提下快速适配特定领域效果提升往往非常显著。这可以看作是LogSAD从“无训练”到“极简训练”的自然演进。从我自己的项目经验来看LogSAD代表的是一种范式转变的尝试——从“训练一个专用模型”到“调用一个通用模型的能力”。它的最大魅力在于其灵活性和快速启动能力。对于数据匮乏、需求变化快的长尾检测场景它提供了一个极具吸引力的新起点。虽然目前还不是银弹但沿着这个方向结合提示工程、特征工程和少量数据微调已经能够解决很多实际问题。