CLIP 与跨模态对齐
上一节我们让文字住进了一个向量空间,同一个空间里"报销"和"费用申请"离得很近。这一节要干一件听起来不太可能的事:让一张照片和一句话住进同一个空间,近到可以直接量它们之间的夹角。做成这件事的模型叫 CLIP,它没有学过一道标准的分类题,却在 ImageNet 上零样本达到了原始 ResNet-50 的水平——而后者要靠 128 万张人工标注图片才能训出来。这一节把 CLIP 从数据、损失函数、温度参数、两条编码器路线一路拆到能跑的代码,同时诚实地告诉你它连手写数字都认不太准。所有论文数字都标明出处,凡是我没核实到的具体数值一律留白不写。
公司年会玩一个游戏。主持人收了 32 位同事的童年照贴在左边墙上,又收了这 32 个人各写的一句童年自述贴在右边墙上,两边都打乱了顺序。任务是:把左右两边正确连起来。
你会怎么做?你不会去研究"照片"和"文字"这两种东西本质上有什么区别——那没用。你会给每张照片提炼几个特征(男孩、短头发、在海边、抱着一只狗),给每句话也提炼几个特征,然后比哪一对的特征最贴。
关键在于游戏的记分方式。你手上一共有 32 × 32 = 1024 种可能的连法,其中只有 32 条是对的,剩下 992 条全是错的。主持人的记分规则是:连对的每条加分,连错的每条扣分。于是为了拿高分,你被迫做两件事——把对的那 32 对往一起靠,把错的那 992 对往两边推。
玩上十把,你会自己摸索出一套"该看哪些特征"的经验。这套经验就是 CLIP 训练出来的东西。只不过 CLIP 玩的不是 32 张照片,是 4 亿组;也不是玩十把,是玩到 592 张显卡连转 18 天。
先把"跨模态对齐"这四个字拆开
跨模态对齐(Cross-modal Alignment)这个词听着玄,其实就是让两种完全不同的东西用上同一套坐标。
"模态"这个词 §9.1 讲过,说白了就是"信息的种类"——图像是一种模态,文字是一种模态,声音是一种模态。"跨模态"就是横跨两种以上的种类。那"对齐"呢?对齐说白了就是:让两套本来各说各话的编号系统,变成同一套编号系统。
为什么这件事以前做不到?因为图像模型和文本模型是各自单独训练的,各自都会输出一串向量,但那两串向量的"坐标含义"毫无关系。相当于两个部门各自做了一套货架编号:仓库那边把货位叫"A3-12",门店那边把同一批货叫"东侧第二排"。两套编号都自成体系,都很好用,但你没法把 A3-12 直接跟"东侧第二排"比大小。要打通,只能人工做一张对照表。
CLIP 做的事就是让两个部门从第一天起就用同一套编号规则去培训员工。训练完成后,图像编码器输出的 512 个数和文本编码器输出的 512 个数是可以直接算余弦相似度的,因为它们指的是同一个空间里的方向。这一步一旦打通,"用文字搜图片"就退化成了上一节讲过的"找最近的向量"——一个已经解决了的问题。
把术语链条串一遍,你就知道这一节在整章里的位置:§9.1 说清了什么叫模态,§9.2 说清了什么叫向量空间,本节说清怎么把不同模态塞进同一个向量空间,§9.4 再讲塞进去之后模型怎么真正读懂一张图。
CLIP 的准确档案:一条最容易搞混的时间线
先把可核实的事实钉死,因为 CLIP 是被转述得最多、也被写错得最多的论文之一。
- 论文标题《Learning Transferable Visual Models From Natural Language Supervision》(从自然语言监督中学习可迁移的视觉模型)
- 编号与日期arXiv 2103.00020,2021 年 2 月 26 日
- 机构OpenAI
- 作者共 12 位。Alec Radford 与 Jong Wook Kim 共同第一作者,末位作者是 Ilya Sutskever
- 正式发表ICML 2021(PMLR 第 139 卷)
- ★ 最容易搞混的一条OpenAI 官方博客在 2021 年 1 月 5 日就公开发布了 CLIP,比 arXiv 上那篇论文(2 月 26 日)早了将近两个月
最后那条值得单独强调,因为网上大量文章写"CLIP 发布于 2021 年 2 月"或"CLIP 是 2021 年 3 月的工作",也有文章写"2021 年 1 月",两派互相说对方错。事实是两个日期都真实存在,只是指的是两件不同的事:1 月 5 日是官方博客公布模型与权重,2 月 26 日是 arXiv 上传预印本。
这个先博客后论文的顺序不是失误,而是那几年的常态。研究机构先发博客抢时间、把权重放出去让社区玩起来,正式论文随后补上。相当于一家餐厅先在门口挂出"新菜上市"的牌子让客人尝,配料表和工艺说明书过两个月才印进正式菜单。写技术文档时如果只认论文日期,你会把整条时间线往后挪两个月,进而误判它和同期其他工作的先后关系。
顺便记一个作者层面的细节:Alec Radford 也是 GPT-2 论文的第一作者。这解释了后面一个看起来奇怪的设计——CLIP 的文本编码器直接沿用了 GPT-2 的架构改动,因为写它的就是同一批人。
训练数据 WIT:4 亿组图文对是怎么攒出来的
CLIP 的效果里有很大一块要归功于数据。它没用现成的公开数据集,而是自建了一个,名字叫 WIT(WebImageText,"网络图文数据"),规模是 4 亿(400M)组图文对。
把 4 亿这个数换算成可感知的量:假设你一秒钟看一组图配一句话,不吃不喝不睡地看下去,看完 4 亿组需要 12 年半。这不是"很多数据",这是一个人一辈子清醒时间的相当一部分。
更值得学的是它怎么攒的。随便爬 4 亿张图会得到什么?大量重复的表情包、大量猫狗、大量美女图——数据会严重偏斜,某些概念多到爆,某些概念一张都没有。论文里的做法是先造一张"查询词表",再按词逐个去搜,最后对每个词限量。
WIT 数据集构建流程(按论文描述)
第一步:造一张约 50 万条的查询词表,来源有四类
① 英文维基百科里出现次数 ≥ 100 次的所有词
② 高 PMI 的二元词组(PMI = 点互信息,
用来找"这两个词经常一起出现且不是巧合"的搭配)
③ 搜索量高的维基百科条目名
④ 剩下的所有 WordNet synset(同义词集合)
第二步:拿每条查询去检索图文对
第三步:★ 每条查询最多只留 20000 组
—— 做近似的类别平衡,防止热门词淹没冷门词
结果:4 亿组图文对
总词量级与训练 GPT-2 用的 WebText 相当
第三步那个"每个查询最多 2 万组"的限额是整套设计里最聪明的一笔。换成大白话:这是在给数据集做配额制。相当于食堂打菜规定"每人最多打两勺红烧肉"——不是嫌红烧肉不好,而是不限量的话前面几个人就把肉端走了,后面的人只剩青菜。限了额之后,"猫"这种烂大街的词最多贡献 2 万组,而"星盘""涡轮增压器"这类冷门词也能凑够自己的份额,模型才见得到长尾概念。
词表来源里的第一类和第四类也有讲究。"维基百科里出现 ≥100 次的词"保证覆盖的是人类真的在用的概念;"剩余的 WordNet synset"(synset 说白了就是"一组意思相同的词",比如 car / auto / automobile 算一个 synset)是兜底,把前面几类没扫到的字典词全补进来。一头保证常用,一头保证不漏,这是造词表的通用套路。
最后那句"总词量级与 WebText 相当"是个很有用的锚点。WebText 是训练 GPT-2 用的文本语料。也就是说,CLIP 读到的文字总量,和 GPT-2 读到的文字总量在同一个量级——只不过 CLIP 的每一段文字后面都拴着一张图。它不是"文本模型加了个视觉插件",它是从头就把文字当成图像的监督信号在读。
对比学习:CLIP 到底在优化什么
现在讲最核心的机制。开篇那个年会连线游戏已经把直觉给你了,这里把它翻译成论文的原话。
论文的描述是这样的:给定一个批次(batch)里的 N 组(图像,文本)对,CLIP 要预测的是——在 N × N 种可能的配对中,哪些是真实发生过的。做法是联合训练图像编码器和文本编码器,最大化那 N 组真实配对的图文 embedding 余弦相似度,同时最小化剩下 N² − N 组错误配对的余弦相似度,并在这一批相似度分数上优化一个对称交叉熵损失(symmetric cross entropy loss)。
"批次"这个词先解释:batch 说白了就是"一次同时喂进去的一小撮样本"。训练时不会一张一张喂,而是几百几千张一起喂,因为显卡擅长并行。
一个 batch 内的 N × N 相似度矩阵(这里画 N = 4)
文本1 文本2 文本3 文本4
图像1 [ ✔真 ✘假 ✘假 ✘假 ]
图像2 [ ✘假 ✔真 ✘假 ✘假 ]
图像3 [ ✘假 ✘假 ✔真 ✘假 ]
图像4 [ ✘假 ✘假 ✘假 ✔真 ]
对角线上 N 个 = 真实配对 → 相似度要往 +1 推
对角线外 N²−N 个 = 错误配对 → 相似度要往下压
N = 4 时:4 个正例,12 个负例
N = 32768 时:32768 个正例,约 10.7 亿个负例
(同一个 batch 里白拿的负样本)
这张矩阵图值得多看两眼,因为它藏着对比学习最划算的一笔生意。你只提供了 N 组"这张图配这句话"的正确答案,却顺手得到了 N² − N 组"这张图不配那句话"的错误答案——负样本是白送的,batch 越大送得越多,而且完全不用额外标注。
换成大白话:正样本要花钱买,负样本可以从正样本堆里现场配出来。相当于你买了 32 双鞋,就自动知道了上千种"左脚配错右脚"的搭配是不对的——这些反面知识一分钱没多花。这就是为什么对比学习被叫做自监督学习:监督信号不是人写的标签,而是数据自己的结构。
那"对称"两个字是什么意思?因为这道题可以从两头问:给定一张图,在 N 句话里挑对的那一句(图 → 文);给定一句话,在 N 张图里挑对的那一张(文 → 图)。CLIP 两个方向都算一遍交叉熵,加起来取平均。相当于年会连线游戏既要求"从照片找自述"也要求"从自述找照片",两个方向都得对——只练一个方向的话,模型可能学出偏科的表征。
★ 术语严谨性:CLIP 原文里并没有"InfoNCE"这个词
这一条是本节最值得较真的地方,也是判断一篇 CLIP 科普文靠不靠谱的试纸。
你看到的绝大多数介绍会说"CLIP 用的是 InfoNCE 损失"。这个说法在数学形式上没错,但在文献学上是不准确的——CLIP 论文原文从头到尾没有使用"InfoNCE"这个词,它用的表述是"对称交叉熵损失"。
所以严谨的写法是这样一句:CLIP 用的是对称的 InfoNCE 式对比损失,论文原文称之为对称交叉熵损失。这句话既让懂行的人立刻知道你在说哪一类损失函数,又没有把一个作者没用的词硬塞到人家论文里。
论文自己交代的溯源反而更清楚,而且比"InfoNCE"这条线更具体:
| 环节 | 出处 | 贡献 |
|---|---|---|
| batch 构造方式与目标函数的最早提出 | Sohn (2016) | 作为 multi-class N-pair loss(多类 N 对损失)提出 |
| 适配到图文表征学习 | Zhang et al. (2020),即 ConVIRT | 把这套目标用在医学影像的图文对比学习上 |
| CLIP 自己的定位 | CLIP 论文自述 | "ConVIRT 的简化版本,从零开始训练" |
注意最后一行。CLIP 自己承认它不是方法上的原创,而是把一个已有方法简化、然后灌进大得离谱的数据量和算力。这是深度学习史上反复出现的剧本:方法在小圈子里被发明出来(医学影像),几年后有人用一百倍的数据把它做爆,于是所有人都以为方法是后者发明的。
为什么要特意讲这个?因为它反映了一个通用的读文献习惯:看到"某模型开创了某方法"这种句子,先去原论文的 Related Work 那一节看看作者自己怎么说。作者通常比转述者诚实得多——CLIP 论文明明白白写着自己是简化版 ConVIRT,是二手转述把它写成了原创。相当于菜市场里那句"祖传秘方",回头翻翻这家人自己的族谱,可能写的是"配方买自隔壁镇某厨子"。
InfoNCE 的真正出处:2018 年的 CPC
既然说 CLIP 用的是"InfoNCE 式"损失,那 InfoNCE 这个名字到底出自哪里?答案很明确,值得单独记住,因为它是 CLIP 和 SimCLR 的共同祖先。
- 论文标题《Representation Learning with Contrastive Predictive Coding》(用对比预测编码做表征学习),简称 CPC
- 作者van den Oord, Li, Vinyals
- 编号与时间arXiv 1807.03748,2018 年 7 月
- 机构DeepMind
- 名字来历NCE = Noise-Contrastive Estimation(噪声对比估计)。"InfoNCE"这个名字就是这篇论文起的
CPC 的核心思路可以用一句大白话说清:让模型在"压缩后的空间"里预测接下来会发生什么,而不是在原始数据上预测。论文的做法是用一个自回归模型在潜空间(latent space,说白了就是"压缩过的特征空间")里预测未来,并用一个概率型的对比损失来训练——这个损失可以证明是在最大化互信息的一个下界。
"互信息"这个词也得翻译一下:互信息说白了就是"知道了 A 之后,你对 B 的不确定性减少了多少"。看到乌云会让你对"要不要带伞"的判断确定不少,这两件事的互信息就高;看到今天几号对要不要带伞几乎没影响,互信息就接近零。
这里有一个工程上的关键点:互信息本身极难直接算,尤其是高维数据。CPC 的贡献之一就是——靠负采样把这个不可算的量变成了可优化的目标。所谓负采样,说白了就是"随手抓几个不相关的样本当反面例子",用"能不能从一堆假的里挑出真的"这个可以算分的任务,去替代那个算不出来的互信息。相当于你没法直接测一个人的厨艺有多高,但你可以让他和五个路人一起做同一道菜,看评委能不能一口尝出哪份是他做的——把不可测量的东西变成可打分的比赛,这是科研里非常常见的一招。
CPC 的另一个值得记的点是它的验证范围:论文在语音、图像、文本、以及 3D 环境下的强化学习这四个领域上都做了验证。一个方法在四个完全不同的领域都有效,这是"通用方法"的强信号——它说明这套思路抓住的不是某种数据的特性,而是表征学习本身的某种结构。CLIP 三年后把它用在图文上,SimCLR 同期把它用在纯图像上,都是这条线的延续。
一家公司有两个仓库:A 仓存衣服,B 仓存鞋子。两个仓库各自用了几十年的老货位号。A 仓的号是"三楼-东区-7 排-2 层",B 仓的号是"仓 2-通道 C-货架 14"。
两套号在各自仓库里都好使——A 仓的老员工听到"三楼东区 7 排"立刻知道往哪走。但麻烦出在一个新需求上:老板要做"衣服配鞋"的搭配推荐,需要知道"这件外套该配哪双鞋"。这时候两套货位号就完全帮不上忙了,因为它们描述的是"东西放在哪",不是"东西是什么样"。
公司想了个办法:重新设计一套编号规则,这套规则不描述位置,只描述风格。比如用四个数字表示:正式度、季节、色系冷暖、价位档。于是那件深灰羊毛外套编成 [0.9, 0.2, 0.3, 0.8],那双黑色牛津皮鞋编成 [0.9, 0.3, 0.3, 0.7]——两个编号很接近,一比就知道能搭。而一双荧光色跑鞋编成 [0.1, 0.8, 0.9, 0.3],跟外套差得远,一比就知道不搭。
这套新编号就是共享向量空间。请注意它做到了什么:衣服和鞋子是完全不同的东西,但它们现在住在同一套坐标里,可以直接互相比较了。这就是"跨模态对齐"这四个字的全部含义。
那么这四个维度是谁定的?关键就在这里——不是人定的,是训练出来的。公司的做法是:翻出过去十年四亿张顾客买单小票,每张小票上都记着"这个人同时买了这件衣服和这双鞋"。已经被同一个人买走的组合就是"配对成功"的正例;随机拿一件衣服配一双别人买的鞋,就是负例。然后调整编号规则,直到"真实成交的组合编号接近、随机瞎配的组合编号远离"。调完之后,那四个数字自动就带上了"正式度""季节"这类含义——虽然没人给它们起过名字。
CLIP 做的一模一样。它的"小票"是 4 亿组从网上抓来的图文对(图片和它旁边的说明文字,天然就是"配对成功"的记录);它的编号不是 4 个数而是 512 个数;它的"调整规则"就是梯度下降。训练完成后,那 512 个维度谁也说不清每一维是什么意思,但它们确实把"图片内容"和"文字含义"编到了同一套坐标里。
temperature:那个藏在损失函数里的旋钮
CLIP 的损失函数里有一个不起眼但很关键的参数,叫 temperature(温度),符号写成 τ(希腊字母 tau,读"陶")。
你在 §8.4 见过这个词——那里讲的是生成文字时调温度,温度高输出更随机,温度低更保守。这里的 temperature 是同一个数学机制,但用途完全不同。论文对它的定义很朴素:τ 用来控制 softmax 里 logits 的取值范围。
两个词得先翻译。logits(读"洛基茨")说白了就是"还没归一化的原始打分"——一堆可以是任意大小的数。softmax 说白了就是"把一堆打分变成加起来等于 1 的概率"的那个函数。而 τ 干的活儿,相当于在把打分交给 softmax 之前,先统一乘或除一个倍数,把分数之间的差距拉开或压扁。
temperature 对 softmax 的影响(一组打分:3, 1, 0)
τ 小(相当于把差距放大):
3/0.2=15, 1/0.2=5, 0/0.2=0
softmax → [0.9999, 0.0001, 0.0000]
结果:几乎全部概率压在最高分那一项上(很"尖")
τ 大(相当于把差距压扁):
3/5=0.6, 1/5=0.2, 0/5=0
softmax → [0.43, 0.29, 0.28]
结果:三项概率接近,模型显得"犹豫"(很"平")
CLIP 的实现细节(论文伪代码):
logits = np.dot(I_e, T_e.T) * np.exp(t)
↑ 图文向量点积 ↑ 温度以 exp(t) 形式乘进来
相当于调收音机的选台旋钮灵敏度:灵敏度调高(τ 小),指针稍微一动电台就完全切换,非常果断但容易过头;灵敏度调低(τ 大),指针转半圈还在两个台之间含混不清。τ 决定的是"模型对相似度差异有多敏感"。
现在讲 CLIP 在这件事上的一个漂亮设计。按常规做法,τ 是一个超参数——所谓超参数,说白了就是"训练前得由人拍一个数、训练过程中不变的设置",比如学习率、batch 大小。调超参数是件苦活,得试很多次。
CLIP 的做法是:不把 τ 当超参数,而是把它当成一个可学习的参数,让它自己在训练里被优化。具体是以 log 参数化的形式作为一个乘性标量直接参与训练——就是伪代码里 np.exp(t) 的意思:真正被优化的是 t,实际乘进 logits 的是 exp(t)。
为什么要绕这一层 exp?因为温度必须是正数,而梯度下降不管你三七二十一,可能把参数优化成负数。套一层 exp 之后,无论 t 被优化到多小(哪怕是 −5),exp(t) 永远是正的。相当于给水龙头加了个限位卡:你可以往关的方向使劲拧,但拧不出"负水流"这种物理上不存在的东西。
论文自己说明这么做的动机就一句话:为了避免把它当超参数来调。这是一个非常实用的工程思路——凡是又重要又难调的超参数,想办法把它变成可学习参数,让优化器替你调。
最后补一个你实际用 CLIP 时一定会碰到的细节:CLIP 的推理接口返回的 logits,数值上就是"余弦相似度 × 100"。所以你看到 logits_per_image 里出现 25.3 这样的数字,不要以为出了 bug——把它除以 100 就是那个熟悉的、范围在 −1 到 +1 之间的余弦相似度。
两条图像编码器路线:8 个模型的完整清单
CLIP 不是一个模型,是一组 8 个模型。它的图像编码器走了两条完全不同的技术路线,各训了几个规模。
路线一:修改版 ResNet。ResNet(Residual Network,"残差网络")是 2015 年之后卷积神经网络的标准架构,§5 讲过卷积的基本原理。CLIP 基于 ResNet-50 做了三处改动:
- 改动一加入 ResNet-D 改进(He et al. 2019)
- 改动二加入 antialiased rect-2 blur pooling(抗锯齿的模糊池化,Zhang 2019)
- 改动三 ★ 最关键把全局平均池化替换成 attention pooling(注意力池化)——一层 transformer 式的 multi-head QKV 注意力,其中 query 以图像的全局平均池化表征为条件
第三条得翻译成大白话,它是理解 CLIP 图像侧的关键。池化(pooling)说白了就是"把一片区域的很多个数汇总成一个数"。全局平均池化就是最简单的那种汇总:全部取平均。
相当于你要给一个班的整体水平打一个分。全局平均池化的做法是:把全班五十个人的分数加起来除以五十。简单、公平,但它有个毛病——它认为每个人的重要性完全一样。
而 attention pooling 的做法是:先决定"该重点看谁",再按不同权重加权汇总。那个"该重点看谁"的判断,来自 query(查询向量)。相当于校长评估班级水平时先问一句"我们这次关心的是理科能力",然后理科好的同学权重高、其他人权重低。图像上的意义就是:模型可以自己决定"这张图里哪些区域重要",而不是把背景和主体一样看待。
路线二:ViT(Vision Transformer,视觉 Transformer)。CLIP 说自己"紧随 ViT 的原实现",只做了两处微调:
- 微调一把 patch embedding 与 position embedding 相加后、送进 transformer 之前,额外加了一层 LayerNorm(层归一化)
- 微调二初始化方案略有不同
LayerNorm 说白了就是"把一组数字统一调整到均值 0、方差 1 的标准刻度上"。相当于几个不同学校的成绩要放在一起比,先各自换算成"距本校平均分几个标准差"再比——不然一个学校平均 90 分、另一个平均 60 分,原始分没法比。这一层的作用是让训练更稳,是深度网络里的常规操作。
现在给出 8 个模型的完整清单:
| 模型名 | 路线 | 规模说明 |
|---|---|---|
RN50 | 修改版 ResNet | 基准(ResNet-50 规模) |
RN101 | 修改版 ResNet | 更深(ResNet-101 规模) |
RN50x4 | 修改版 ResNet | 按 EfficientNet 风格缩放到约 4 倍计算量 |
RN50x16 | 修改版 ResNet | 缩放到约 16 倍计算量 |
RN50x64 | 修改版 ResNet | 缩放到约 64 倍计算量(ResNet 系里最大) |
ViT-B/32 | ViT | Base 规模,patch 边长 32 像素 |
ViT-B/16 | ViT | Base 规模,patch 边长 16 像素(切得更细) |
ViT-L/14 | ViT | Large 规模,patch 边长 14 像素;额外在 336 像素分辨率下多训了一个 epoch(FixRes 风格) |
表里三个术语要解释。EfficientNet 风格缩放说白了就是"不只加深,也同时加宽、加大输入分辨率,三者按比例一起长"——相当于房子要扩建,不是只往上加层,而是长宽高一起放大,比例才协调。epoch(读"埃波克")说白了就是"把整个训练集完整过一遍"。patch 边长越小,一张图被切出的块越多、看得越细,但计算量也越大——ViT-B/32 把 224×224 的图切成 49 块,ViT-B/16 切成 196 块,差了四倍。
最后那个 ViT-L/14 的"336 像素多训一个 epoch"值得单独说。它解决的是一个很实际的错配问题:训练时用小图(224),实际用的时候常给大图。直接换分辨率会掉点,所以在训练末尾用目标分辨率再过一遍。这就是 ViT-L/14@336px 这个型号名里 @336px 的来历,它是 CLIP 家族里效果最好的一个。
文本编码器:一个 6300 万参数的 GPT-2 亲戚
图像侧讲完,文本侧其实更简单,但有几个设计细节非常值得学。
- 基础架构Transformer(Vaswani et al. 2017),并采用了 GPT-2(Radford et al. 2019)的那套架构修改
- 基础规模12 层 · 宽度 512 · 8 个注意力头 · 约 6300 万参数
- 输入处理小写化的 BPE(Byte Pair Encoding,字节对编码——§8.1 讲过的那种分词方式)
- 序列格式用
[SOS]和[EOS]两个特殊标记把文本包起来(Start / End Of Sequence,序列开始 / 结束) - ★ 怎么取出句子向量取最高层
[EOS]位置上的激活值,经 LayerNorm 后线性投影到多模态 embedding 空间 - 注意力类型掩码自注意力(masked self-attention)
那个"取 [EOS] 位置的激活"是最容易被忽略、也最有意思的一条。你可能会想:一句话有十几个 token,每个 token 都有一个向量,凭什么只拿最后那个特殊标记的向量代表整句话?
答案在"掩码自注意力"上。掩码说白了就是"挡住后面的内容,只让每个词看到自己前面的词"。于是在这种结构里,只有最后一个位置能看到整句话的全部内容——前面的位置都是残缺的。
相当于开一场接力式的会议:规定每个人只能听到自己之前发言者说的话。那么会议记录该找谁总结?只有最后一个发言的人听全了所有人的意见,只有他有资格总结。[EOS] 就是那个最后发言的人。
接着说"掩码"的另一层用意,论文明确交代了:用掩码自注意力是为了保留未来添加语言建模辅助目标的可能性。换成大白话:如果哪天想让这个文本编码器顺便也学会"预测下一个词"(也就是 GPT 那套本事),现在这个结构可以直接支持,不用改架构。相当于装修时先在墙里埋好了一根空管,眼下没用,但以后想穿网线不用砸墙。这是一个非常成熟的工程习惯——为还没确定的未来需求留一个成本极低的接口。
最后是缩放策略,这一条藏着一个反直觉的结论:CLIP 在放大模型时,文本编码器只按 ResNet 的宽度同比加宽,完全不增加深度。为什么?因为作者发现 CLIP 的性能对文本编码器的容量并不敏感。
这个发现很值得琢磨。它说明在图文对齐这个任务里,瓶颈在视觉侧,不在语言侧——图片里的信息量远大于一句短说明文字,所以给文本编码器加料的边际收益很低。相当于你要把一头大象装进冰箱,瓶颈是冰箱门的宽度,把大象的鼻子练得再灵活也没用。把参数预算花在真正的瓶颈上,这是模型设计里最基本的取舍原则。
把 6300 万参数换算成可感知的量:GPT-2 最小版本约 1.24 亿参数,也就是说 CLIP 的文本编码器还不到最小号 GPT-2 的一半。这个规模在今天看简直是个小玩具——但它撑起了整个"用文字搜图"的能力。
还有一个反直觉的缩放决定:只加宽,不加深
论文里有个容易被忽略但很有启发的工程选择:当 CLIP 把模型往大做的时候,文本编码器只按 ResNet 的宽度增幅同比放宽,完全不增加层数。
为什么?因为作者发现 CLIP 的整体性能对文本编码器的容量并不敏感。换成大白话:这套系统的瓶颈在"看"那一侧,不在"读"那一侧。你把读文字的那半边做得再强,整体也涨不了多少;钱应该花在图像编码器上。
打个比方:这好比开一家翻译公司,业务是把中文菜谱翻成英文。你发现瓶颈是"看不懂手写菜谱",那就该多招几个擅长辨认字迹的人,而不是给英文很好的译员再报个英文进修班。知道系统的瓶颈在哪,比知道每个部件怎么优化更重要。
zero-shot 的真实成绩:它到底有多能打
现在到了最关键的问题:这套训练方式换来了什么?答案是 zero-shot(零样本)分类能力——不给任何标注样本,直接用文字描述就能分类。
论文里最有名的那个结论是:CLIP 在 ImageNet 上 zero-shot 的准确率,达到了原始 ResNet-50 的水平——而后者是用 128 万张人工标注的图片训练出来的。
请把这句话的分量掂清楚。ResNet-50 那 128 万张图,是人一张一张标出来的;而 CLIP 一张 ImageNet 的标注图都没看过,成绩打平。这不是准确率上的胜利,这是"根本不需要标注"这件事本身的胜利。
为了让你感受到这一步跨越有多大,把它放进历史坐标里看:
| 方法 | ImageNet zero-shot 准确率 | 说明 |
|---|---|---|
| Li et al. (2017) 的前人 SOTA | 11.5% | CLIP 之前 zero-shot 的最好水平 |
| CLIP | 达到原始 ResNet-50 的水平 | 不用任何 ImageNet 标注样本 |
| 当时的全监督 SOTA(Xie et al. 2020) | 88.4% | 用了全部标注数据,是天花板参照 |
从 11.5% 到追平 ResNet-50,这是量级上的跨越,不是百分点上的改进。顺便说一句为什么这里我不给 CLIP 的具体百分数:网上流传最广的那个数字(76.2%)我没能在论文正文的表格里核实到,只在二手来源见过。科普写作里,一个查不到出处的精确数字,不如一句查得到出处的定性描述。"达到原始 ResNet-50 的水平"这句话是论文和官方博客都反复写过的,可以放心引用。
但 CLIP 有个尴尬的短板:它不太会认手写数字
上面全是好话,现在说说它的怪毛病——这部分往往比优点更能帮你理解它的原理。
CLIP 在 MNIST 手写数字上的 zero-shot 准确率只有 88%,而人类是 99.75%。MNIST 是什么难度?它是深度学习领域的"你好世界",一个几十行代码的简单卷积网络就能轻松做到 99% 以上。一个能在 ImageNet 上追平 ResNet-50 的模型,却在最简单的入门任务上被一个新手模型碾压。
这看起来很荒谬,但原因非常合理:CLIP 的知识全部来自互联网上的图文对,而互联网上几乎没有人会拍一张手写数字的照片,然后配上文字说"这是数字 7"。
换成大白话:CLIP 是个博览群书但从没上过学的自学者。它读遍了互联网上的图配文,所以认得出埃菲尔铁塔、认得出柯基犬、认得出赛博朋克风格;但小学一年级课堂上那种"照着描红本认阿拉伯数字"的训练,它压根没经历过。它的知识边界,就是互联网图文的分布边界。
再举一个同源的例子:CLIP 的名人识别能力在 100 个候选里 top-1 是 59.2%,扩到 1000 个候选就降到 43.3%。这同样是分布问题——互联网上有大量名人照片配名字,所以它会;但候选一多,细分辨别力就跟不上了。
任何模型的能力边界,都由它训练数据的分布决定,而不是由任务的"客观难度"决定。MNIST 对人类来说比 ImageNet 简单得多,但对 CLIP 来说反而更难——因为难易的标尺不在任务本身,而在"训练数据里有没有这类东西"。
这条道理可以直接用在你评估任何 AI 工具上:当它在某个看起来很简单的任务上表现离谱,先别急着说它笨,去想想它的训练数据里有没有这种东西。
训练成本:这件事当年有多贵
最后交代一下代价。CLIP 不是能在自家显卡上复现的东西:
- 最大的 ResNet(RN50x64)592 张 V100 显卡训练 18 天
- 最大的 ViT256 张 V100 显卡训练 12 天
592 张卡跑 18 天是什么概念:换算成单卡时间是约 592 × 18 = 10656 卡天,也就是一张 V100 要不停跑将近 29 年。这就是 2021 年做一个 CLIP 的门票价格。
不过论文里也给了两个非常实用的效率结论,说明选对方法比堆卡更值钱:
- 换目标函数从"预测图片的完整文字描述"改成"对比式地判断配对",zero-shot ImageNet 的效率提升 4 倍
- 换图像骨架从标准 ResNet 换成 ViT,再获得 3 倍计算效率提升
4 倍乘 3 倍,就是 12 倍——这两个决定省下的算力,比买十倍的卡更管用。这也解释了为什么 CLIP 最终最好的模型是 ViT 版本而不是 ResNet 版本。
跨模态对齐的目标只有一句话:让"猫的照片"和"猫"这两个字,在同一个向量空间里落到相近的位置。做到这件事,图搜文、文搜图、zero-shot 分类就都自然成立了。
CLIP 的做法是对比学习。一个 batch 里 N 个图文对,构成 N×N 种可能配对,最大化 N 个真实对的余弦相似度、最小化 N²−N 个错误配对的相似度,在相似度矩阵上优化对称交叉熵损失。数据是自建的 WIT,4 亿组图文对。
术语要严谨:CLIP 原文并没有用"InfoNCE"这个词,它写的是对称交叉熵损失。InfoNCE 这个名字出自 2018 年 DeepMind 的 CPC 论文(arXiv 1807.03748),是 CLIP、SimCLR 共同的祖先。CLIP 也明确溯源过:这种 batch 构造最早是 Sohn (2016) 的 multi-class N-pair loss,后由 ConVIRT 用于医学影像,CLIP 自述是"ConVIRT 的简化版"。
temperature 在 CLIP 里不是超参数,而是可学习参数——以 log 参数化的乘性标量直接参与训练,省掉了手工调它的麻烦。
成绩与短板要一起看。zero-shot ImageNet 达到原始 ResNet-50 水平(而对手用了 128 万张标注图),前人 SOTA 只有 11.5%;但 MNIST 只有 88%,名人识别 1000 候选降到 43.3%。能力边界由训练数据分布决定,不由任务的客观难度决定——这是这一节最值得带走的一条判断力。