☰
CLIP双塔架构与对比学习原理:零样本分类实践踩坑复盘
2026/10/20 4:46:08 网站建设 项目流程

“第三周(1)CLIP的学习”这个标题写在我学习笔记顶端时,我其实有点心虚。前两周我把CLIP相关的论文、解析、别人跑通的demo都过了一遍,可真轮到自己用CLIP做点事,它就变成一个“背过书但不会做题”的学生:让它找“一只戴帽子的狗”,它记住了狗却没看见帽子;让它区分“货车”和“卡车”,它反问我要分类提示;甚至同一张图换一次写法,结果都能大变。这不是CLIP随机,是我根本没理解它内部到底在做什么。

这篇文章不是论文复述,而是我的学习周记加踩坑复盘。如果你在做多模态方向,或者想用CLIP做图文检索、零样本分类、特征抽取,又或者和我一样卡在“懂概念但不会用”的阶段,这篇记录应该能帮你节约不少试错时间。我第三周的目标很简单:不再刷新论文,而是把一个能用的CLIP从前到后彻底拆开,搞清楚数据是怎么流的、训练是怎么对齐的、推理时每一步为什么要那样写。

1. 为什么学了三周,我才敢说大致看懂了CLIP

1.1 前两周的四个“糊涂账”

回头整理前两周的学习笔记,我发现自己的问题非常集中,基本可以概括成四个点。

  • CLIP到底是分类模型还是特征提取模型?论文里那个表格看起来像是分类精度,可demo里它又能抽特征,这两者的关系我没想明白。
  • 训练目标为什么选对比学习,而不是让模型直接预测“这张图和这句话是不是一对”?直觉上预测式任务更直接,为什么要绕一圈。
  • 零样本分类究竟是怎么做到的?模型没有见过目标类别的训练数据,凭什么知道“狗”这个字对应像素上的什么形状。
  • 推理相似度计算时,为什么要除以一个温度系数?我试过不除或者乱填一个数,结果都崩了,但没人告诉我这个数的物理意义。

这四个问题堆在一起,导致我那两周的代码全是照着别人拼的,自己完全没法变通。

1.2 数据流视角:从像素和文字到同一个向量空间

第三周我换了个思路,不先看公式,而是把CLIP当成一条流水线来看。图像从一边进,文字从另一边进,中间经历三步:编码、投影、对齐。

第一步,图像编码器把一张图片的像素矩阵压成一个向量,这个向量保留的是图片的视觉内容,比如“有一条狗、背景是草地、光线偏暖”。第二步,文本编码器把一句话压缩成另一个向量,保留的是语义内容,比如“这一帧画面描述了一条在草地上的狗”。第三步,也是CLIP真正花力气练的一步,是把这两个向量映射到同一个向量空间里,让“一条在草地上的狗”这么一句话对应的文本向量,靠近“画面里真的有草和狗”那张图的图像向量。

这个流程一旦在脑子里立起来,我之前的第一个困惑就散了:CLIP既不是分类模型也不是纯粹的特征模型,它是一个双塔对齐模型。分类和检索都是对齐这个核心能力在外面套了一层壳而已。

1.3 CLIP真正解决的问题是“对齐”

我再打一个比方。银行柜台有十个窗口,每天来一批办业务的客户。我们不知道客户具体要办什么,但我们可以拿一句话播报:“请A001号客户前往2号窗口”。CLIP的训练过程,就是让系统学会“哪个客户该去哪个窗口”,而不是学会“这位客户是VIP还是普通客户”。

传统分类模型学的是后者:给定一张图,判断它属于哪个预定义类别。CLIP学的是前者:给定一张图,判断它和哪句话最匹配。类别只是把“寻找匹配这句话”的任务包装了一下,换成“和类别名对应的那句话”。一旦理解了这点,后面看零样本分类、图文检索、特征抽取,其实都是同一套逻辑在不同任务上的表现。

2. 双塔架构和对比学习:CLIP的核心机制拆解

2.1 两个编码器各管各的,最后才汇合

CLIP的双塔,一个塔负责图像,一个塔负责文字。图像塔常用的是卷积网络或者视觉Transformer,把224×224或者更大分辨率的图片变成图像特征向量;文本塔一般用Transformer,把句子切分成子词后编码成一个文本特征向量。两个塔的结构可以完全不同、训练时可以部分冻结、升级时可以各换各的骨干网络,因为CLIP在意的不是塔内部长什么样,而是它们在共享的嵌入空间里能不能对上。

接着,两个向量都要过一个线性投影层,把维度统一到多模态嵌入空间的同一个维度,最后做L2归一化。归一化这一步很多人忽略,但实际上它决定了后续相似度计算用什么度量——CLIP用的是余弦相似度,而归一化之后,余弦相似度和内积完全等价。换句话说,做矩阵乘法求相似度的时候,其实就是在算两张特征谁和谁的方向更一致。

我刚开始会想当然地把图像特征和文本特征直接比较,结果发现两个塔的输出维度根本不一样,也谈不上相似度计算。看完架构图才意识到,维度统一是交给投影层和归一化完成的,不是编码器本身的责任。

2.2 对比学习到底在对比什么:正负样本的设定

训练一个batch里通常有N个图文对,也就是N张图和N句对应的描述。把它们全部送进双塔之后,我们会得到一个N×N的相似度矩阵。

这个矩阵的对角线是“这张图和自己那句描述”的相似度,也就是正样本;矩阵里其余位置都是负样本,比如第一张图和第二句描述、第二张图和第五句描述,它们本来就不该匹配。

对比学习要做的事情特别纯粹:让对角线上的相似度尽量高,让对角线外的相似度尽量低。听上去和普通分类很像,但有一个本质区别——分类需要提前定义好“类别”这个中间变量,对比学习完全不需要预定义类别,它只是朴素地认为“配对的正例应该靠近”。CLIP能在零样本场景下泛化,根源就在这里:它学到的不是某个固定类别的判别边界,而是视觉和语言之间通用的对应规律。

2.3 InfoNCE损失的直观理解,以及为什么非要用大batch

CLIP的训练损失是从InfoNCE变过来的,通常写成对称的交叉熵形式。简单说,它把每张图在相似度矩阵的那一行变成一次分类任务:在N个文本候选中,模型要找出哪个才是正确的那一个。同理,每个文本在那一列也要从N个图像候选中认出自己的另一半。

这里有一个绕不开的参数:温度系数。假设某一行里有好几个候选文本的相似度都差不多,模型就分不清哪个是正确的;温度系数的作用是把相似度的差距放大,逼模型做出更明确的区分。数值越小,对比越尖锐;数值越大,分布越平缓。论文里的初始值大约是0.07,并且把它设计成可学习的参数,训练到后期它可以自己调成更适合数据分布的值。

那为什么CLIP训练时要用非常大的batch?原因很简单:N越大,每一行参与对比的负样本就越多,模型学到的东西越扎实。只有两三个负样本时,它很容易偷懒,随便找出一个不太像的就能过关;当负样本数量达到数万级别时,模型被迫认真区分每一对细微差异。这也是为什么个人电脑很难从头训练一个完整CLIP,显存和算力都卡在batch大小上。

3. 零样本分类到底是怎么跑起来的:从理论到代码

3.1 推理流程:一张图和一列句子比赛

零样本分类的推理核心可以压缩成四行伪代码。第一步,输入一张图片,经过图像塔得到一个归一化后的图像特征;第二步,把每个候选类别名拼接进模板句,比如“a photo of a {}”,变成一句描述;第三步,所有描述经过文本塔,得到一组归一化后的文本特征;第四步,用图像特征和所有文本特征做矩阵乘法,得到相似度,除以温度系数,再经过softmax输出每个类别的概率。

import torch # image_features: (1, dim) # text_features: (num_classes, dim) image_features = image_encoder(processed_img) text_features = text_encoder(processed_texts) image_features = l2_normalize(image_features, dim=-1) text_features = l2_normalize(text_features, dim=-1) logits = image_features @ text_features.T / temperature probs = torch.softmax(logits, dim=-1)

我第三周第一次手写这套流程时,最大的感受是:原来零样本分类没有任何“识别新类别”的魔法,它只是把分类任务转化成了一次检索任务。模型手里拿着一张图,在候选的类别描述里找最匹配的那一句。它没见过“金毛犬”的训练图,但只要它理解语言中的“金毛犬”对应视觉上的金色、长毛、大型犬这些概念,它就能把图匹配对。

3.2 为什么模板词能提升精度:一个朴素的分布对齐问题

刚开始我很不理解,为什么直接写类别名“dog”效果差,改成“a photo of a dog”效果就好了。后来我用一句话说服了自己:训练时CLIP看到的句子绝大多数是自然的完整描述,比如“一个人骑着自行车在街上”,而不是孤零零的“自行车”。推理时如果你只丢一个光秃秃的名词给它,文本端的分布和训练时的分布就错位了。

加模板本质上是把推理时的文本拉回训练时的语言分布区间。“a photo of a {类别}”是最常用也最有效的模板之一。如果你想进一步提升精度,还可以准备一组模板,比如把“a photo of a {}”“a bad photo of a {}”“a sculpture of a {}”全部各过一遍,把每个类别的多个匹配分数合并后取平均或取最大,这样能进一步压低偶然波动。论文里在公开基准上就是这样做的,几十条模板平均下来,效果往往比单模板高出几个点。

3.3 数据预处理和超参数:被忽略的决胜细节

跑CLIP推理时,真正坑我的往往是预处理,不是模型结构。

图像端要做的流程一般是调整大小、中心裁剪、转成Tensor、按CLIP专用的均值和方差做归一化。CLIP的归一化参数和常见分类模型不一样,我用过一次ImageNet那套标准参数,结果精度掉得很明显,后来才改成CLIP自带的统计值。文本端则要注意,句子要做子词切分,序列长度通常要截断到一个固定上限,比如77个token,超过就截断,不足则补占位符。

mean = [0.48145466, 0.4578275, 0.40821073] std = [0.26862954, 0.26130258, 0.27577711]

我建议把预处理封装成一个固定的处理函数,每次换数据集都复用同一条链路,不要每次重写。因为这一步一旦出错,后面调什么参数都救不回来。

4. 复现实验中的意外情况:那些教学文档不会写的坑

4.1 温度系数不是摆设:一次对照实验让我长了记性

我做过一次很草率的对照实验,把推理时的温度系数从0.07改成0.1,其他条件完全不变,结果top-1精度大概掉了一个多点。当时我以为是随机波动,反复跑了几次才发现不是。温度变大等于把相似度分布变平,模型在两个接近的候选项面前会变得犹豫,最终导致错误。

温度系数还会放大过拟合。如果用很小的温度,模型的输出分布会非常尖锐,看起来“信心十足”,但一旦遇到分布外数据,这种信心就是虚假的。做实际项目时,如果某个类别概率突然接近1,不要急着相信,先检查温度系数设置是否合理。

4.2 类别名写法和模板措辞:同一张图,差出两成都有过

我试过一个实验:用三组文本去分类同一张猫的图片,第一组直接用“cat”,第二组用“a cat”,第三组用“a photo of a cat”。前两种写法在相似度排序上确实不如第三种稳定,但如果把模板换成完全不搭的表达式,比如“a drawing of a {}”,精度还会继续掉。更麻烦的是,类别名的大小写也会影响结果,因为文本编码器对大小写不是完全无感的。

所以在业务场景里,我养成了一个习惯:给每类准备5到10个模板,做一次小规模验证集评测,选出在该领域表现最好的模板组合。这比盲目相信论文里给的通用模板要靠谱得多。

我踩过的另一个细节是类别列表的顺序。因为softmax是对所有候选类别的分数做归一化,如果一个关键类别被放到列表末尾,而前面类别又很多,分数偶尔会被稀释掉。虽然理论上不影响结果,但实际浮点计算会带来微小波动。为了保险,我尽量让候选类别数量保持在几十个以内,类别太多时建议分层筛选。

4.3 显存不够时的操作顺序:先提特征,再做运算

CLIP的编码器本身不算特别大,但如果你在同一个batch里既跑权重又存所有中间特征,显存很快就撑不住。第三周我拿一台普通GPU做实验,一次性输入200张图加200句文本,直接爆显存。后来我把流程拆成三步:先把所有图像单独过图像塔,把图像特征存到磁盘或者内存里;再把所有文本单独过文本塔,把文本特征也存起来;最后在CPU上做矩阵乘法和softmax。

这样操作有几个额外的好处。特征可以复用:同一批图像特征,如果只是想换几个候选类别重新分类,不需要重新过模型;特征还可以缓存:做大规模检索时,先建好图像特征库,后面来一个查询文本,只需要算一次文本特征和库里的特征做匹配,速度会快很多。内存占用从“图片数量乘以整张图的中间特征”变成了“图片数量乘以一个向量”,完全不是一个量级。

4.4 论文里的分数好看,到了自己数据上却“翻车”:分布偏差

论文报告的分类精度可以达到百分之七八十,这个数字能给人很大信心,但直接迁移到自己数据上时往往会掉一截。原因在于预训练数据的分布和业务数据的分布不一致:预训练语料里大多是常见的互联网图片和自然描述,如果业务数据里全是监控视角、医学影像、或者垂直行业里的特殊风格,CLIP的零样本能力会大打折扣。

最稳妥的做法,是在项目最开始先拿一小批带标注的样本做快速验证,记录一个baseline。如果这个baseline距离业务要求太远,就不要硬调模板词了,那点提升杯水车薪。要么收集数据做微调,要么换一个更贴近场景的预训练模型。第三周我给自己定了一条规矩:任何新数据集必须先跑一次最简单的零样本实验,拿到数字后再决定后面的技术路线,不再凭论文分数拍脑袋。

5. CLIP的边界:它解决不了什么,比它擅长什么更重要

5.1 细粒度分类:鸵鸟和鸸鹋的教训

CLIP对常见概念的大类区分很强,比如“猫”“狗”“汽车”“房子”,但这些大概念一旦细下去,它就明显吃力。我做过一个实验:用“鸵鸟”和“鸸鹋”两个类别去分类一组鸟类照片,结果错误率高得离谱,因为它掌握的视觉样本里这两种鸟的区分度不够。

这个现象不奇怪。CLIP是在约4亿个图文对上训练的,网页图文对里关于“鸟类”的很多,但关于“鸵鸟和鸸鹋的细微区别”这种细分描述相对少。对比学习只能学它见过的差异,没见过的差异它就无能为力。因此,如果你的业务是区分多种相似SKU商品、几十种近似的零件、或者细粒度的植物病害,零样本CLIP只能作为初筛,不能直接交结果。

5.2 数量、顺序、空间关系:CLIP的“一瞥式”理解

更隐蔽的边界在于CLIP对组合性语义的理解。它擅长辨认“一张图里有什么”,但不擅长回答“图里有三只猫”“猫在桌子下面”“猫的左边是狗”这类需要精细定位和数量统计的问题。

我用一张同时包含两只狗和一只猫的图做过测试,文本写成“two dogs and a cat”时还算勉强正确,但换成“three dogs and a cat”时,因为视觉上狗的特征过于明显,模型居然也给出了较高分数。这说明CLIP的文本特征和图像特征对齐的是一种“整体氛围”,不是严格逻辑。遇到要理解数量、顺序、空间位置关系的任务,别指望零样本CLIP,该上检测模型、视觉问答模型或者专门预训练的多模态模型就上。

5.3 门槛测试:用几张图快速摸清CLIP的底

如果你想知道CLIP在具体任务上到底行不行,不用做大规模评测,先做一个最小测试。准备两类样本,一类是你要识别的正类,大概10张图,一类是容易混淆的负类,也准备10张图。用它们各自的类别名加模板跑一次零样本分类,输出混淆矩阵。

混淆矩阵能回答三个关键问题:正类样本的识别率是否稳定;负类样本是否被大量误判成正类;哪些类别在语义上距离太近,导致相似度明显偏高。我记得那次门槛测试里,两个视觉上非常接近的品类把分数输出得几乎一模一样,我当时就知道在纯零样本条件下不可能把它们分开。这个结论一天就能得到,比闷头调模板高效得多。

5.4 学完边界之后,我的调整思路

认清CLIP的边界不是什么坏事,反而让我后面的路线清晰了。如果任务在CLIP的能力范围内,直接用特征加分类器,或者零样本模板就够;如果任务落在细粒度或组合性语义上,那就老老实实收集数据做微调,或者换更专用的模型。

微调CLIP时,我给自己定了两个原则:第一,保留双塔对齐的训练方式,不要简单把图像塔后面接一个分类头,那样容易把预训练学到的跨模态对齐能力破坏掉;第二,微调数据至少要覆盖目标场景的典型分布,只拿几十张样例去微调,效果往往不如加几个好模板做零样本。

第三周的学习让我最大的变化是:不再把CLIP当成一个万能的模型去买,而是把它当成一对“能对齐图文语义的编码器”。遇到任务先判断边界,再决定用零样本、特征抽取还是微调,整个过程变得可控多了。

如果让我给刚开始学CLIP的人一个建议,我会说:先别看那么多解析,先去把推理流程跑通,再去读框架的源码细节,最后回头对比论文里的公式,你会发现自己读懂的深度完全不一样。第三周(1)只是这个学习计划里的一步,但这一步踩扎实了,后面再往多模态方向走,底气会足很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询