CLIP零样本分类实战:提示词、线性探针与落地避坑

发布时间:2026/10/31 18:21:28
CLIP零样本分类实战:提示词、线性探针与落地避坑 1. 从固定标签到用一句话定义类别CLIP改的到底是什么做图像分类的人都熟悉那种憋屈感模型训好了上线了业务方第二天说再加一个新品类。你回去重新标注、重新训练、重新调参、重新评测一套流程走下来两周没了。如果这个品类还特别长尾样本只有几十张那基本就是白干。CLIP这类视觉语言大模型最颠覆的地方不是它精度比ResNet高多少而是它把分类这件事从训练一个固定输出的分类头变成了算图像向量和一段文字向量的相似度。类别不再写死在网络结构里而是变成了你可以随时改的一行字符串。这就是万物分类这个说法的来源——你不需要为每一个新类别准备训练数据你需要的是能准确描述这个类别的一句话。我第一次在项目里用CLIP是为了处理一个电商素材库的粗分类任务主图、细节图、场景图、模特图、尺寸表截图、包装图六个类别历史数据混杂标注质量参差不齐。当时团队已经在ResNet上折腾了快三周准确率卡在七十几上不去主要原因是场景图和模特图的边界在标注规范里本身就模糊。换成CLIP做零样本之后一小时跑完全量数据准确率直接到八十四虽然没有达到可以完全替代人工的程度但足够把人工复核量砍掉七成。从那天起我对这类模型的定位就很清楚了它不是用来刷榜的是用来快速把一条业务链路跑通、把人力成本降下来的。1.1 传统分类器的天花板其实不在模型容量很多人把传统分类效果不好归因于模型不够大这是误判。以那个素材库任务为例用更大的骨干网络、加更多数据增强收益非常有限因为瓶颈根本不在表征能力而在标签定义本身。当场景图和模特图这两个类在人类标注员之间的一致性只有八成的时候你让模型去拟合一个本身就有噪声的决策边界它学到的其实是标注员的偏好而不是真实的视觉规律。传统分类头的另一个硬约束是输出维度固定。你想加一个类就得改最后一层改完就得重新训练哪怕只加一类。工程上更麻烦的是版本管理线上跑的是v1模型v2加了新类你得同时维护两套预处理、两套推理服务、两套灰度策略。做过这类迭代的人都知道真正耗时间的不是训练是这些配套工作。CLIP绕开了这两件事。它的视觉编码器输出一个固定维度ViT-B/32是512维ViT-L/14是768维的向量文本编码器也输出同样维度的向量两者在同一个空间里可以直接做余弦相似度。分类的过程就是把你要分的K个类别写成K句话编码成K个向量然后看图像向量跟哪一个最接近。加一个类别就是往列表里多加一句话模型一秒钟都不用重训。1.2 双塔对比学习图像和文本被强行拉到同一个坐标系理解CLIP的关键在于理解它的训练目标。假设一个批次里有N对图文数据图像塔和文本塔分别把它们编码成N个图像向量和N个文本向量归一化之后算一个N×N的相似度矩阵。对角线上的N个元素是真实的配对应该相似度高其余N²-N个元素是错配的应该相似度低。损失函数就是让这个矩阵尽量接近单位矩阵——这个叫对称的InfoNCE损失图像到文本、文本到图像两个方向各算一次。这里有个容易被忽略的工程细节负样本完全来自同一个批次内的其他样本。也就是说批次越大负样本越充分学到的表征越好。这也是为什么这类模型训练时动辄要用几千的批大小配合梯度累积本质是在构造足够难的负样本池。你在下游做微调时如果批大小开得很小会明显感觉到对比学习的收益不如预期原因就在这里。另一个细节是温度系数。相似度矩阵除以一个可学习的温度τ初值约0.07再送进softmax。温度越小softmax越尖锐模型越自信。这个参数是在训练中一起学的推理时官方实现里用model.logit_scale.exp()把它取出来乘到logits上。我在代码审查里见过不止一次有人直接softmax(相似度矩阵)而不乘这个系数结果概率分布被压得极平最高分常常只有0.2出头完全没法用来做阈值判断。1.3 零样本推理的完整链路以及每一行为什么这么写光说原理没用直接上能跑的代码。下面这段是我自己在项目里用的最小可运行版本用的是公开的CLIP权重import torch import clip from PIL import Image device cuda if torch.cuda.is_available() else cpu model, preprocess clip.load(ViT-B/32, devicedevice) model.eval() # 类别描述注意这里是自然语言不是标签名 labels [ a photo of a main product image on white background, a close-up detail shot of a product, a photo of a model wearing the product, a screenshot of a size chart, a photo of product packaging, ] image preprocess(Image.open(sample.jpg)).unsqueeze(0).to(device) text clip.tokenize(labels, truncateTrue).to(device) with torch.no_grad(): image_features model.encode_image(image) text_features model.encode_text(text) # 一定要归一化否则相似度不等于余弦相似度 image_features image_features / image_features.norm(dim-1, keepdimTrue) text_features text_features / text_features.norm(dim-1, keepdimTrue) # 乘上温度系数让概率分布有区分度 logit_scale model.logit_scale.exp() logits logit_scale * image_features text_features.t() probs logits.softmax(dim-1) print(probs)这段代码里有四处是新手最容易写错的preprocess必须用clip.load返回的那个不能自己拿torchvision拼一个encode_image和encode_text的输出必须手动归一化官方API不会帮你做logit_scale.exp()必须乘上clip.tokenize的上下文长度是77个token超过会被截断写长句的时候要留意。preprocess这个东西尤其值得展开说。CLIP用的归一化参数是mean[0.48145466, 0.4578275, 0.40821073]、std[0.26862954, 0.26130258, 0.27577711]不是ImageNet那套[0.485, 0.456, 0.406]。这两个值差得不多所以错了也不会报错只会让你的准确率莫名其妙掉几个点而且掉得很隐蔽你很难第一时间怀疑到预处理上。我见过一个项目组在这个问题上排查了整整两天最后是对比官方issue才发现的。2. 提示词不是玄学让零样本结果从及格线往上走零样本分类的准确率对你怎么写那句话极其敏感。同一批数据类别写成cat和写成a photo of a cat结果可能差三到五个点写a photo of a cat, a type of pet和写a blurry photo of a cat方向又不一样。很多人第一次用CLIP的时候拿裸标签词去试效果不理想就下结论说这模型不行其实是没有把文本侧当成一个需要调的东西来对待。我的经验是把文本提示词当成超参数来管理跟学习率、批大小一个待遇。给每个类别准备一组模板跑一遍验证集选最好的那个。这件事的投入产出比高得离谱通常半小时的工作能换来三到八个点的提升而且完全不需要训练。2.1 那个a photo of a {}到底为什么有用官方论文里提到用a photo of a {label}这种模板比直接用{label}平均能提升一点几个点。原因不难理解CLIP的训练数据是从互联网上爬的图文对图下面的说明文字绝大多数是完整的自然语言句子不是孤立的标签词。你喂给它一个裸词它在训练时见到的这种分布很少编码出来的向量位置就不是最优的。加上a photo of之后文本的语法结构和训练分布对齐了编码质量自然更好。这个逻辑可以继续往下推如果你的目标图像有很明确的风格特征把这个特征写进模板里会有额外收益。比如做卫星图像分类用a satellite photo of a {label}明显好于通用的a photo of a {label}做医学影像用a radiograph showing {label}这类模板。本质上你是在用文字告诉模型我这类图长什么样帮它把文本向量往对应子空间里挪一挪。还有一个更狠的做法是模板集成为每个类别准备多个模板分别编码然后把向量平均或者先把概率平均。官方实现里给ImageNet准备的模板有八十个集成之后比单模板提升明显。实操中不需要那么多挑五到十个语义上有差异的模板就够比如模板类型示例适用场景基础句式a photo of a {label}通用兜底带冠词变体a picture of the {label}类别名是集合名词时风格限定a close-up photo of a {label}目标多为特写场景限定a photo of a {label} in the wild自然场景数据模糊限定a low quality photo of a {label}数据质量差时复数形式a photo of many {label}s目标常成群出现做集成的时候有个取舍模板越多单张图要编码的文本越多但因为文本编码很便宜几十个token而已实际开销可以忽略。真正的问题是会稀释掉真正有效的那个模板。我的做法是先单独评测每个模板只保留top 3到5个再集成而不是一股脑全上。2.2 类别名本身比模板更值得琢磨模板是锦上添花类别名才是决定性的。我踩过最典型的一个坑给一个工业质检场景做缺陷分类类别之一是划痕我直接写了scratch。结果模型把大量文字印刷和细线条纹理的图也判成了划痕因为scratch这个词在英文语料里跟线条、纹理的关联太强了。后来改成a deep linear scratch mark on a metal surface误判率直接降了一半多。这里的经验是类别名要往视觉上能看见的东西靠而不是往业务概念靠。业务上的不良品在视觉上可能表现为边缘有毛刺、表面有凹陷、颜色不均匀你得把这些视觉表现写出来模型才认。同理把电子产品这种上位概念直接丢给CLIP效果一定差因为它在视觉上太发散了拆成a smartphone、a laptop、a pair of headphones就准得多。同义词的处理也值得说一句。有些类别在不同地区、不同人群里叫法不一样比如沙发可以叫sofa也可以叫couch卡车可以叫truck也可以叫lorry。稳妥的做法是给一个类别准备多个同义表述把它们视为同一个类的多个文本向量取其中的最大相似度或者把它们的向量平均后再算。# 同义词合并的写法 synonyms { sofa: [a photo of a sofa, a photo of a couch, a photo of a settee], chair: [a photo of a chair, a photo of an armchair], } class_features {} with torch.no_grad(): for cls_name, prompts in synonyms.items(): feats model.encode_text(clip.tokenize(prompts, truncateTrue).to(device)) feats feats / feats.norm(dim-1, keepdimTrue) class_features[cls_name] (feats.mean(dim0, keepdimTrue))注意这里是先各自归一化再平均平均完最好再归一化一次。顺序不能反先平均再归一化会让长度不一致的向量互相干扰。2.3 归一化和温度系数两个必须显式处理的量前面提过一次这里展开讲清楚为什么必须做。CLIP输出的原始向量长度不是1不同图像、不同文本的输出向量的模长差异还不小。如果你直接做点积而不归一化得到的结果里就混进了模长信息相似度的物理意义就没了。归一化之后点积严格等于余弦相似度取值范围锁在[-1, 1]你好设阈值也好做跨模态检索。温度系数的问题更隐蔽。model.logit_scale存的是log值约2.659exp之后约14.3也就是温度约0.07。乘上这个系数之后相似度差异被放大14倍softmax出来的分布才有区分度。不乘的话假设两个类别的余弦相似度是0.28和0.26直接softmax几乎是0.505对0.495你根本没法判断模型到底有没有把握。顺带说一个实用的点因为有了这个放大余弦相似度的绝对值本身没太大参考意义重要的是相对差距。两个类的分数如果是0.31对0.12那基本可以放心如果是0.28对0.27那就是模型在犹豫这种样本应该进人工复核队列。我在实际项目里就是靠这个Top1与Top2的分差来做置信度筛选的比用softmax概率还稳因为它不受类别数量的影响。3. 三条落地路线纯零样本、线性探针、微调怎么选CLIP能用在项目里但用在哪个层次上是有讲究的。我把它归纳成三条路线完全不动模型参数的纯零样本、冻结图像塔只训一个轻量分类头的线性探针、以及在图像塔上挂低秩适配器的微调。这三条路线的成本、效果、维护难度差了一个数量级选错了要么浪费算力要么效果上不去。判断标准其实很简单看两个量每个类别的可用标注样本数以及类别之间的视觉区分度。区分度高比如猫和汽车、样本数少几十张以内纯零样本就是最优解别折腾。区分度低但有一定样本每类几百到几千线性探针的性价比最高。区分度极低且样本充足才值得上微调。3.1 纯零样本的适用边界在哪里零样本真正能打的场景有几个共同特征类别语义清晰、类别之间差异是概念级而不是细节级、图像内容相对干净。内容审核里的违规类型粗筛、素材库的素材类型分类、相册的自动归类、电商主图的场景判断这些都属于零样本的舒适区。反过来零样本最容易翻车的场景也很集中。第一是细粒度分类比如区分某种花的两个亚种或者区分两个外观极其相似的工业零件型号这类任务需要的判别信息在CLIP的表征里根本没被强调零样本准确率经常掉到随机猜测附近。第二是需要计数的任务问图里有三只鸟还是四只鸟CLIP基本无能为力它的训练目标里没有数量这个维度。第三是空间关系判断比如杯子在桌子左边和杯子在桌子右边这类任务上CLIP的表现接近瞎猜因为对比学习学的是整体的语义对齐不是结构化的空间推理。还有一个容易被忽视的限制文本编码器的输入长度是77个token。你想用一段长描述来精确定义一个复杂的类别很可能被截断后半段根本没进模型。这种情况下不如拆成多个短模板分别编码效果反而更好。3.2 线性探针小样本场景下最被低估的方案线性探针的做法是把图像塔完全冻结用训练集过一遍把每张图的特征向量存下来然后在这个特征矩阵上训一个逻辑回归或者单层全连接。因为特征已经提好了训练这件事在CPU上几秒就能完成成本几乎为零。这个方案的收益经常被低估。在一个每类只有两三百张图的任务上我实测零样本大概七十出头线性探针能到八十八左右差距非常大。原因很直接零样本依赖的是文本侧对类别的通用理解而线性探针直接在特征空间里找最优的分割超平面它是针对你的具体数据分布做优化的。只要特征本身够好CLIP的特征确实好一个线性层就能榨出大部分价值。import numpy as np from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import normalize def extract_features(model, preprocess, image_paths, device, batch_size64): feats [] for i in range(0, len(image_paths), batch_size): batch torch.stack([ preprocess(Image.open(p).convert(RGB)) for p in image_paths[i:ibatch_size] ]).to(device) with torch.no_grad(): f model.encode_image(batch) feats.append(f.cpu().numpy()) feats np.concatenate(feats, axis0) return normalize(feats) # L2 归一化后再送进线性分类器 X_train extract_features(model, preprocess, train_paths, device) X_val extract_features(model, preprocess, val_paths, device) clf LogisticRegression(max_iter2000, C1.0, class_weightbalanced) clf.fit(X_train, train_labels) print(val acc:, clf.score(X_val, val_labels))这里有几个实操要点。C这个正则化强度在小样本下影响很大样本少的时候往小调0.01到0.1样本多的时候可以放大到1以上建议做个网格搜索。class_weightbalanced在类别不均衡时几乎是必须的不然少数类会被完全压掉。归一化那一步不能省逻辑回归对特征尺度敏感不归一化会让某些维度主导决策。还有个进阶技巧把零样本的文本特征也塞进训练数据里。具体做法是把每个类别的文本向量当作该类的一个虚拟样本跟真实图像特征一起训线性层。这在样本极少每类不到二十张的时候能显著稳定训练相当于给模型一个先验锚点。3.3 微调与图文检索什么时候值得动参数如果你要做的是图文检索以文搜图、以图搜图那路线就不一样了因为你需要的不是分类而是一个高质量的共享向量空间。这种情况下通常不需要微调直接拿CLIP的特征建向量索引就行——图像特征入库查询时把文本编码成向量去检索因为两者本来就在同一个空间里。工程上要注意的是索引规模和召回率。特征维度512或768用精确检索在几十万量级还能接受上到百万级就必须上近似最近邻索引了。我一般用内积索引配合归一化特征等价于余弦相似度索引参数里把构建时的搜索深度调高一点召回率能上到95%以上查询延迟在毫秒级。真正需要微调的场景是零样本和线性探针都顶不住的时候比如细粒度分类、专业领域图像工业缺陷、医疗影像。这时候我的建议是不要全量微调用低秩适配的方式在注意力层上加小矩阵可训练参数只有原来的百分之一不到显存占用小过拟合风险也低。全量微调在这个量级的数据上几乎必然过拟合而且会把CLIP原来学到的通用语义能力破坏掉得不偿失。4. 我在实际项目里踩过的坑以及怎么绕开这一节讲的东西官方文档里基本不会写但每一个都能让你的项目多花两三天。4.1 预处理不一致是最难查的一类问题前面说过归一化参数的问题这里补充一个更隐蔽的缩放和裁剪的顺序。CLIP官方的预处理是先按短边缩放到224用的是bicubic插值再中心裁剪224。如果你图省事用了Resize((224, 224))直接拉伸宽高比就变了模型的输入分布跟训练时不一致准确率会掉。还有一个坑是图片模式。有些PNG带alpha通道有些是灰度图有些是CMYK。CLIP的预处理期望三通道RGB遇到四通道图会直接报错遇到灰度图会静默产生错误结果。统一在读取时加一句.convert(RGB)能省掉大量莫名其妙的bug。我曾经遇到过一个特别诡异的情况同一个模型、同一批数据两次跑出来的结果差了六个点。排查了一整天最后发现是两次用的图片读取库不同一个用PIL一个用OpenCV两个库在JPEG解码时的色度上采样实现有细微差异导致像素值在小数点后有偏差经过归一化放大之后影响了最终结果。这件事之后我定了个规矩特征提取管线和线上推理管线必须共用同一份预处理代码且必须有单元测试保证同一张图两次处理结果完全一致。4.2 细粒度、计数、空间关系这三个是硬短板不要试图用提示词工程去弥补模型能力上的结构性缺陷。我见过有人为了让CLIP数清楚图里有几个人写了一堆a photo of exactly two people、a photo of exactly three people的模板结果该错的还是错。原因在于CLIP的训练目标里根本没有数量这个监督信号你怎么写提示词都变不出来。正确的做法是换工具不是换提示词。计数任务上目标检测模型加后处理空间关系任务上要么上用视觉问答模型要么用检测框的坐标关系做规则判断细粒度分类上老老实实标注数据做微调。把CLIP放在它擅长的位置上——做概念级的粗分类、做召回的第一道筛子、做跨模态检索的底座——而不是指望它什么都能干。4.3 中文场景下文本编码器的选择很关键原版CLIP的文本塔主要在英文语料上训练直接拿中文类别名去编码效果会有明显衰减。我在一个中文场景的项目里做过对比同一批图用英文类别名零样本能到八十二用中文类别名只有六十多差距相当大。解决办法有三条一是直接用英文写类别名虽然别扭但效果最好二是换用支持中文的双语模型权重社区里有不少在多语言数据上训练的变体中文效果明显改善三是用翻译模型把中文类别名译成英文再编码工程上最简单但要小心翻译歧义比如苹果到底是水果还是品牌这种情况下得靠上下文提示来消歧。我的默认选择是第二条如果找不到合适的中文权重就退回到第一条。第三条我一般不用因为多了一个翻译环节就多了一个不确定因素出了问题很难定位是翻译错了还是模型判错了。4.4 显存和吞吐别只看单张延迟CLIP的推理成本跟输入分辨率强相关。ViT-B/32是224输入、patch大小32序列长度只有49跑起来非常轻ViT-L/14-336是336输入、patch大小14序列长度576显存占用和计算量差了一个数量级。做选型的时候一定要按实际吞吐需求算不要看着大模型精度高就上。模型规格输入分辨率向量维度相对计算量适用场景ViT-B/322245121x高吞吐粗筛、边缘设备ViT-B/16224512约4x通用场景性价比最高ViT-L/14224768约12x精度要求高的离线任务ViT-L/14-336336768约35x细粒度、离线批处理实践中我发现一个反直觉的点批大小不是越大越好。在单卡上把批大小从32加到128吞吐确实上去了但如果你的数据里长宽比差异很大大批次统一resize再裁剪会造成更多的信息损失。我的做法是分批处理时先按宽高比排序把比例接近的图放在同一批减少裁剪带来的信息损失这个改动通常能带来一两个点的提升而且不花钱。5. 怎么证明你的方案真的有效评测环节的实操细节模型跑通了不等于方案有效。我见过太多项目在demo阶段看着漂亮一上真实数据就崩原因基本都是评测环节做得太草率——验证集是从训练分布里随机抽的跟线上真实分布差了十万八千里。5.1 验证集必须模拟真实分布如果你要做的是零样本分类验证集的构造尤其关键。不要用那种精心挑选的、每类数量完全均衡的样本因为线上数据永远是不均衡的。我通常会把验证集分成三块一块是随机采样的、反映真实分布的一块是困难样本也就是人工标注时都犹豫的那些一块是长尾类别的补充样本。三块分别看指标才能知道方案在什么条件下会失效。指标上除了整体的Top-1准确率一定要看每类的召回率和混淆矩阵。整体准确率在类别不均衡时极具欺骗性一个把所有样本都判成多数类的模型在九比一的数据上也能有九十的准确率但完全没用。看混淆矩阵能立刻发现问题比如我之前那个素材库任务混淆矩阵上场景图和模特图之间有一大块互相错判这就是提示词需要针对性调整的信号——后来我把模板改成a photo of a product placed in a real-life scene without a person这块错判明显收敛。5.2 从混淆矩阵里读出语义冲突混淆矩阵不只是看指标的工具它其实能告诉你模型的文本表征里哪些概念被混在一起了。如果A类和B类大量互错大概率是这两类的文本向量在空间里离得太近而原因往往是你的类别描述里有共同的词或者共同的语义倾向。处理方式有两种。一种是改描述把区分性的视觉特征显式写进去让两个向量拉开距离。另一种是引入负向描述也就是在算相似度的时候不仅看跟目标类的相似度还要减去跟易混类的相似度。这个技巧在类别少的时候特别有效# 简单版负向提示拉大易混类之间的距离 target_logits image_features text_features[target_idx].t() confuser_logits image_features text_features[confuser_idx].t() adjusted target_logits - 0.5 * confuser_logits系数0.5是我试出来的经验值太大会把真正属于目标类的样本也压下去太小没效果建议在验证集上扫一遍。还有一点值得强调评测要固定随机种子并重复多次。零样本分类本身是确定性的但如果你在验证集上做模板选择就是在做超参数搜索这时候必须留一个独立的测试集否则你选出来的模板只是在这批数据上过拟合了。这个道理很简单但实际项目里因为赶进度而忽略的人不在少数最后上线效果跟验证效果对不上又得回头重来一遍。我在几个项目里反复验证过一件事CLIP这类模型最大的价值不在于替代精调模型而在于把验证一个想法是否可行的周期从两周压缩到两小时。你可以在一个下午之内试完十种不同的分类体系看看哪种在视觉上最可分然后再决定往哪个方向投入标注资源。这个能力本身就值回票价至于最终上线用零样本还是微调那是后面才需要纠结的事。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询