ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

CLIP模型双塔架构与零样本图像分类实战解析

CLIP模型双塔架构与零样本图像分类实战解析

如果你做过图像分类,一定对“先收集数据、再定义类别、再训练模型”这套流程非常熟悉。它的问题也很明显:新类别出现时,往往要重新标注一批样本重新训练,标签体系的每一次调整都意味着不小的成本。CLIP模型的做法则完全不一样——它没有传统意义上的分类层,而是把图片和文字映射到同一个语义空间,用匹配关系代替固定标签来完成识别。这篇文章从CLIP模型的架构与特点出发,聊聊双塔结构是怎么回事、预训练阶段做了哪些关键取舍,以及它在实际项目中哪些能力好用、哪些坑容易踩。

1. 被标签锁死的分类模型:CLIP出现前的困境

1.1 固定类别集合的三个天花板

传统的图像分类模型,本质上都是在学“图像特征到固定标签索引”的映射。训练集里出现哪些类别,模型就只能识别哪些类别,而且通常每个类别都要准备足够多的正负样本。这个设计有三个绕不开的天花板。

第一,类别集合要提前定死。产品上线前需求方列出十类,模型就按十类训练;等运营说“我们下周要加一个品类”,整个流程又得重新走一遍。

第二,每个类别都要有样本。哪怕新类别在语义上和旧类别高度相关,比如已经有“猫”“狗”,现在要加“狐狸”,模型也不会自动学会识别狐狸,需要单独收集狐狸的图片并标注。

第三,是类别内部的细粒度问题。一个“车”的类别,下面可能有轿车、卡车、SUV。如果训练数据里只有“车”这个标签,模型很难区分这些子类,因为它根本没学过对应的语义边界。

这些困境并不是某个项目的问题,而是固定标签范式本身的局限。标签体系一旦确定,模型的表达能力就被限制在这个体系里,业务需求一扩展,代价就被放大。

1.2 把分类任务改写成“图文匹配”的核心思路

CLIP模型没有在最末端接一个线性分类层,而是换了一个思路:让图片和文字共享一个向量空间。给定一张图,用图像编码器得到一个向量;给定一句文字描述,用文本编码器得到另一个向量;两者做点积相似度,相似度越高,说明图与文越匹配。

这样一来,“识别一只猫”不再是“在固定类别集合里找一个索引”,而是变成“判断‘a photo of a cat’这句描述和当前图片是不是匹配”。描述是无限可构造的,所以模型天然不再受固定类别集合的限制。

这个转变看起来只是把分类层换成了文本匹配,但它实际上改变了模型的学习目标:模型要学的不是“这张图属于哪一类”,而是“什么样的文字能描述这张图”。自然语言的开放性,让模型有机会接触到远多于标签词表的概念。

1.3 为什么过去没有大规模实现这个思路

图文匹配这个想法并不算新奇,早期很多跨模态检索工作都用过类似框架。但没有大规模成功,关键在于两个前提条件没成熟。

一是数据。传统图像分类数据集靠人工标注,成本极高,能达到百万量级已经很不容易。而对比学习需要海量的图文配对数据,最好是“图片+自然语言描述”这种弱监督形式。真正让这条路跑通的,是互联网上的亿级图文配对资源。

二是算力和工程。大Batch训练、大规模分布式同步、几十亿参数规模的编码器训练,都需要成熟的工程基础。数据、算力、训练技巧三者齐备之后,这种“用自然语言做监督信号”的方案才终于从实验变成可用的预训练模型。

2. 双塔架构拆解:图像编码器与文本编码器怎样汇合

2.1 图像侧:CNN主干与ViT主干各自承担什么角色

CLIP的图像编码器有两类主流选型:一类是ResNet风格的CNN主干,一类是ViT风格的主干。两者在结构上的目标一致——把一张任意尺寸的图片编码成一个固定维度的特征向量。

CNN方案的做法是延续经典的卷积堆叠思路,但针对大型预训练做了细节调整。常见配置会使用更大输入分辨率(比如288或448),并把全局平均池化之前的特征图做进一步聚合。CNN的优势是空间局部性好,对纹理和边缘的捕捉比较稳定。

ViT方案则是把图片切分成固定大小的patch,例如14x14像素一块。每个patch被展平成向量,经过线性投影后加入位置编码,再送进Transformer编码器。ViT的优势在于全局建模能力更强,大规模数据下扩展性更好,训练过程也往往更平稳。我自己在实际使用时,更青睐带高分辨率输入的ViT变体,因为小目标细节明显保留得更好;代价是计算量上涨明显,线上推理之前需要仔细评估延迟预算。

2.2 文本侧:为什么用Transformer把一句话压成一个向量

文本编码器的结构相对固定:输入是一段分词后的token序列,每个token经过嵌入层得到向量,再叠加位置编码。多层的双向自注意力让每个token都能充分感知上下文,最后把整段序列的表示压缩成一个句子向量。

这里有两个容易被忽略的细节值得展开。

第一,文本长度不是任意长的。预训练时句子通常会被截断到固定上限,很多实现里是77个token。原因在于互联网图文配对里的描述普遍偏短,长文本不仅有截断损失,还会带来多余的计算负担。如果你在下游任务里用到特别长的描述,直接照搬默认长度并不合适,需要根据文本统计重新选。

第二,为什么用Transformer而不是简单的词向量平均。因为词序在很多描述里是信息关键:同样是“狗追猫”和“猫追狗”,词袋平均以后得到的句子向量几乎没有差异,而Transformer能利用自注意力建模词与词的依赖关系,保留顺序信息。这也是“这只狗在追猫”和“猫被这只狗追”在向量空间里能被区分开的原因。

2.3 投影与相似度计算:两个特征空间怎么对齐

双塔各自编码之后,不能直接把两个原始特征向量拿来做相似度计算。图像的原始特征和文本的原始特征来自完全不同的网络和模态,维度、数值分布、语义密度都不一样,直接计算余弦相似度往往效果很差。

所以两座塔的末端各带一个投影层,把特征映射到一个共享的嵌入空间。常见做法是投影到同一固定维度,比如512维或768维,并且在训练和推理时都做L2归一化。归一化之后,点积和余弦相似度等价,所有向量的模长变成1,数值范围被限制在[-1,1]之间,方便后续配合温度参数计算。

推理时的相似度计算流程可以简化为下面这段伪代码逻辑:

# CLIP推理阶段的相似度计算简化示意 def clip_infer(image_feat, text_feat, temperature=0.07): # 归一化,使点积等价于余弦相似度 image_feat = image_feat / image_feat.norm(dim=-1, keepdim=True) text_feat = text_feat / text_feat.norm(dim=-1, keepdim=True) # 计算图与文本的相似度矩阵,并做温度缩放 logits = image_feat @ text_feat.T / temperature return logits.softmax(dim=-1)

温度参数在这里的作用是把相似度分布的锐度控制在一个合理范围。温度越小,softmax结果越尖锐,模型对负样本的惩罚越强;温度太大,所有分数都被拉平,训练信号变弱。这也是为什么很多实现里温度值初始化为0.07左右,而不是直接用1。

3. 预训练配方里的三个关键选择:数据、对比损失与温度系数

3.1 从互联网抓取的图文配对数据:规模与质量如何取舍

CLIP的预训练使用了约4亿对“图像-文本”数据,文本大多来自网页上图片的alt属性、标题或周边描述。这类数据的最大优势是便宜且规模巨大,不需要任何人工标注;代价是噪声非常高。

实践中清洗这类数据需要做不少工作:过滤过短文本,去除纯数字或乱码内容,排除与图片内容完全不相关的句子。即使做了这些处理,剩下的描述也大多只是“图片的相关题注”,而不是严格意义上的语义标注。模型学到的匹配关系更偏向“相关”而不是“精细一致”。

这个特点带来的直接影响很明显:模型在常见粗粒度类别上表现不错,遇到专业性很强的细粒度描述时,准确率波动会非常大。比如“一只橘色的猫坐在沙发上”这类描述一般匹配良好,但“一种名为某某的植物病害叶片图像”这种带专业术语的描述,匹配效果就会很不稳定。

3.2 对比损失目标函数:为什么要同时算两个方向的交叉熵

CLIP训练时,一个Batch内假设有N个图像-文本配对。对每张图来说,自己的文本是正样本,Batch内其余N-1条文本都是负样本。目标是让模型给正样本的相似度远高于所有负样本。

关键点在于,损失由两个方向的交叉熵共同构成:图像向文本方向匹配,以及文本向图像方向匹配。两个方向相加,模型既要做到“给定图片找到对应文本”,也要做到“给定文本找到对应图片”。

为什么不能只算一个方向?如果只优化图像到文本的方向,模型可能只学会了从图像视角进行匹配,文本侧的特征分布却未必对齐;两个方向同时优化,能强迫所有特征在共享空间里形成更一致的排列。这也是对比学习在多模态场景里比单向检索更稳定的原因。

另外,Softmax里的温度系数同样不可小视。它直接影响负样本产生的梯度强度:温度过低时模型倾向于过分惩罚相似但困难的负样本,容易导致训练震荡;温度过高时梯度被稀释,训练收敛极慢。这里没有绝对最优的温度值,而是需要根据Batch大小和学习率做整体调节。

3.3 训练稳定性的另一些细节:大Batch为何是必需品

对比学习中,负样本只能来自Batch内部。如果Batch大小为256,那每个正样本只对应255个负样本;可选的负样本越少,模型越容易通过“偷懒的特征”走捷径,例如只靠颜色或背景就能区分图文对,而不是真正理解语义关系。

CLIP的预训练把Batch size做到数千甚至上万,目的就是让每一个正样本都有充足的负样本可供对比。负样本数量足够多,相似度分布才能被学到比较精细的结构,最后的嵌入空间才不至于塌缩。

工程上的直接后果是,你没法在单卡上复现这一套流程的完整效果。分布式多卡同步训练几乎不可避免,梯度同步误差、BatchNorm统计量同步策略,都会影响最终的loss曲线。我自己在尝试复现类似架构时,一个很深的体会是:过小的Batch不但涨点慢,而且容易出现“看似收敛、迁移效果却很一般”的假象。

4. 架构特点带来的四种能力:零样本、少样本、特征迁移与鲁棒性

4.1 零样本分类的落地姿势与Prompt模板

预训练完成后,模型可以完全不接触下游数据直接做分类。操作方法并不复杂:把候选类别写成文本,例如“a photo of a cat”,用文本编码器生成每个类别的向量;然后对一张图片取图像特征,计算它和所有类别文本向量的余弦相似度,相似度最高的那个类别就是预测结果。

这里最影响实际效果的因素,往往不是模型本身,而是你如何组织候选文本。直接使用单品名“cat”,效果通常不如完整的“a photo of a cat”。原因是预训练时模型看到的大多是完整句子,单个单词的文本分布在语义空间里可能偏到角落。

一个实用的技巧是使用多个Prompt模板做集成,比如同时构造“a photo of a {}”“a painting of a {}”“a blurry photo of a {}”,把所有模板下的文本特征都算出来再取平均。这样做通常可以稳定提升几个百分点的准确率,尤其在候选类别比较接近时效果更明显。

4.2 少样本与特征迁移:冻结特征还是微调

CLIP在迁移到下游任务时,最常用的有三种方式。先给个直观对比:

迁移方式适用数据量训练成本稳定性使用建议
零样本直接推理0无高先做基线或初筛
线性探测(冻结特征训分类头)每类几十到几百样本低高多数项目首选
全量微调每类大量样本高中低数据充足且分布差异大时才用

线性探测的原理很简单:把图像编码器产出的特征固定住,只训练一个线性层做分类。它训练成本极低,而且因为不破坏预训练学到的语义空间,所以在小样本下不容易过拟合。我在多个项目里的一致观察是:几百张数据量时,线性探测的效果经常优于全量微调。

全量微调能够进一步适配特殊数据分布,但风险也很明确——它会让模型忘记一部分预训练学到的通用语义。尤其是目标域数据量不够大时,微调后模型在新数据上准确率提升,但泛化能力反而下降。要不要微调,不是看“效果有没有涨”,而是看“涨的点是不是你真正需要的业务场景”。

4.3 鲁棒性优势和效率边界:不能神化CLIP

CLIP一个常被提及的特点是分布偏移下的鲁棒性。相比传统的ImageNet预训练模型,CLIP在自然图像分布偏移场景下通常掉点更少。这背后是训练数据的功劳:模型见过数以亿计的互联网图文对,文本描述里包含了大量上下文信息(物品、场景、动作、材质、背景关系),这些语义关联让特征表达比单一标签训练更加丰富。

但CLIP并不擅长所有事情。它在精细目标计数上表现很差,让模型数图片里有几只鸟,经常数错;在抽象关系推理上也很弱,无法理解“A在B的左边,B在C的上面”这种复杂空间逻辑;对图片中的文字内容理解同样有限,看到标志牌上的文字不一定能准确读出来。

另外,一个容易被忽视的问题是模型的数据偏差。原研究团队收集互联网数据,天然无法覆盖所有领域。如果你把它用到卫星遥感、医疗影像、工业质检等与预训练分布差异极大的场景,零样本效果可能低到无法直接使用。正确处理方式是:先把它当特征提取器,而不是直接当最终分类器。

5. 把CLIP用在真实任务时的坑和对应思路

5.1 候选类别词的选择:笼统词和具体词结果天差地别

我在一个商品分类项目里用CLIP做初筛时踩过比较明显的坑:候选类别直接用“shoe”,效果一直不理想;后来改成“sneaker”,准确率明显上涨;继续改成“a pair of sports shoes”这样的完整描述,又涨了一截。原因很简单,预训练文本里“sneaker”和“a pair of sports shoes”的上下文,比“shoe”更接近电商场景中对商品的自然描述。

所以实际使用CLIP做零样本或少样本分类时,候选文本的构造要尽量参考训练数据里的表达习惯。如果项目已经有一些历史数据,统计高频描述短语再作为候选文本,会比拍脑袋写一个单词更可靠。类别之间语义越接近,候选文本的措辞就越需要精细化。

5.2 输入分辨率与模型规模:精度的代价藏在工程细节里

同一种模型架构下,使用更高输入分辨率的版本,通常能保留更多细节,尤其对细小目标有明显帮助。但高分辨率也意味着更高的计算量和延迟。

我自己的经验是不要简单“来个最好的版本直接上”。先评估业务场景里目标物体的尺度分布,再决定分辨率投入。线上服务如果对延迟敏感,一个实用做法是两段式:用低分辨率版本做候选召回,再用高分辨率版本做精排,既能控制延迟,又不牺牲最终精度。

5.3 轻量适配思路:既不破坏已有能力,又显著提升下游分数

完全冻结CLIP做线性探测是最稳妥的方案,但有些场景下线性探测的精度仍然不够。如果业务数据量有限,又不想全量微调导致遗忘,可以考虑做局部参数更新。

一种常见的做法是固定图像编码器不动,在文本侧加入少量可学习的向量参数,只更新这些参数和文本编码器的部分层,让文本特征更贴合下游任务。这样做的核心优势是:图像侧的大规模知识被完整保留下来,文本侧又被赋予了一定的任务适配能力,同时训练参数量很小,不容易过拟合。

我用这个方法在一些具体业务上做过验证,少样本情况下效果往往优于直接全量微调,而且训练速度快很多。这种思路也符合“预训练大模型少改动、多复用”的基本原则:能不动主干就不动主干,把有限的标注数据花在刀刃上。

如果让我用一句话概括,CLIP模型的架构特点在于去掉了分类头,用双塔对比学习把视觉与语言放进同一个语义空间,由此获得了可迁移、可零样本的能力;它的代价是训练成本高、细粒度推理弱、数据分布依赖明显。我现在的固定习惯是:无论接到什么多模态任务,都先用CLIP做一次零样本基线,再根据错误样本决定是补充数据、做线性探测,还是走轻量适配——这样既快,也不容易踩坑。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进