4.2 CLIP 双塔架构深度解析 CLIP(Contrastive Language–Image Pre-training)是 OpenAI 在 2021 年发布的多模态对齐模型,它把第4.1节的对比学习方法论完整落地,成为多模态大模型时代的奠基之作。本节深度拆解 CLIP 的架构、训练流程与零样本能力。 一、CLIP 解决的核心问题 在 CLIP 之前,视觉-语言模型(如 ViLBERT、LXMERT)通常这样工作: 这种「早期融合」结构有一个工程瓶颈——图像与文本必须同时进入同一个编码器,无法分别预计算。
CLIP(Contrastive Language–Image Pre-training)是 OpenAI 在 2021 年发布的多模态对齐模型,它把第4.1节的对比学习方法论完整落地,成为多模态大模型时代的奠基之作。本节深度拆解 CLIP 的架构、训练流程与零样本能力。
在 CLIP 之前,视觉-语言模型(如 ViLBERT、LXMERT)通常这样工作:
[图像] ──┐ ├→ [融合编码器] → 输出 [文本] ──┘
这种「早期融合」结构有一个工程瓶颈——图像与文本必须同时进入同一个编码器,无法分别预计算。这导致:
CLIP 的革命性在于换成双塔结构:
[图像] → [视觉塔] → 图像向量 I ↓ 余弦相似度(点积) ↑ [文本] → [文本塔] → 文本向量 T
两个塔完全独立,可以预先离线编码所有图像与文本,构建索引。检索时只需在线计算查询向量与索引向量的相似度——速度极快。
下面这张 SVG 刻画了 CLIP 的双塔结构与对比训练流程:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 480" font-family="sans-serif" font-size="13"> <!-- 标题 --> <text x="450" y="25" text-anchor="middle" font-weight="bold" font-size="16">CLIP 双塔对比学习架构</text> <!-- 视觉塔 --> <g transform="translate(60,80)"> <rect x="0" y="0" width="180" height="220" fill="#e1f5ff" stroke="#333" rx="10"/> <text x="90" y="25" text-anchor="middle" font-weight="bold">视觉塔</text> <rect x="20" y="50" width="140" height="35" fill="#fff" stroke="#666" rx="4"/> <text x="90" y="72" text-anchor="middle">图像 224×224</text> <rect x="20" y="100" width="140" height="35" fill="#fff4e1" stroke="#666" rx="4"/> <text x="90" y="122" text-anchor="middle">ViT-L/14 或 ResNet</text> <rect x="20" y="150" width="140" height="35" fill="#fff" stroke="#666" rx="4"/> <text x="90" y="172" text-anchor="middle">L2 归一化</text> <text x="90" y="205" text-anchor="middle" font-weight="bold">图像向量 I ∈ R^d</text> </g> <!-- 文本塔 --> <g transform="translate(660,80)"> <rect x="0" y="0" width="180" height="220" fill="#fff4e1" stroke="#333" rx="10"/> <text x="90" y="25" text-anchor="middle" font-weight="bold">文本塔</text> <rect x="20" y="50" width="140" height="35" fill="#fff" stroke="#666" rx="4"/> <text x="90" y="72" text-anchor="middle">文本 token 序列</text> <rect x="20" y="100" width="140" height="35" fill="#e1f5ff" stroke="#666" rx="4"/> <text x="90" y="122" text-anchor="middle">Transformer Encoder</text> <rect x="20" y="150" width="140" height="35" fill="#fff" stroke="#666" rx="4"/> <text x="90" y="172" text-anchor="middle">L2 归一化</text> <text x="90" y="205" text-anchor="middle" font-weight="bold">文本向量 T ∈ R^d</text> </g> <!-- 相似度矩阵 --> <g transform="translate(330,180)"> <rect x="0" y="0" width="240" height="120" fill="#f5e1ff" stroke="#333" rx="8"/> <text x="120" y="22" text-anchor="middle" font-weight="bold">相似度矩阵 I·T^T</text> <g font-size="11"> <rect x="30" y="40" width="30" height="20" fill="#e8f5e1" stroke="#333"/> <text x="45" y="54" text-anchor="middle">0.9</text> <rect x="60" y="40" width="30" height="20" fill="#ffe1e1" stroke="#333"/> <text x="75" y="54" text-anchor="middle">0.1</text> <rect x="90" y="40" width="30" height="20" fill="#ffe1e1" stroke="#333"/> <text x="105" y="54" text-anchor="middle">0.2</text> <rect x="120" y="40" width="30" height="20" fill="#ffe1e1" stroke="#333"/> <text x="135" y="54" text-anchor="middle">0.1</text> <rect x="150" y="40" width="30" height="20" fill="#ffe1e1" stroke="#333"/> <rect x="30" y="65" width="30" height="20" fill="#ffe1e1" stroke="#333"/> <rect x="60" y="65" width="30" height="20" fill="#e8f5e1" stroke="#333"/> <text x="75" y="79" text-anchor="middle">0.8</text> <rect x="90" y="65" width="30" height="20" fill="#ffe1e1" stroke="#333"/> <rect x="120" y="65" width="30" height="20" fill="#ffe1e1" stroke="#333"/> <rect x="150" y="65" width="30" height="20" fill="#ffe1e1" stroke="#333"/> <rect x="30" y="90" width="30" height="20" fill="#ffe1e1" stroke="#333"/> <rect x="60" y="90" width="30" height="20" fill="#ffe1e1" stroke="#333"/> <rect x="90" y="90" width="30" height="20" fill="#e8f5e1" stroke="#333"/> <text x="105" y="104" text-anchor="middle">0.9</text> <rect x="120" y="90" width="30" height="20" fill="#ffe1e1" stroke="#333"/> <rect x="150" y="90" width="30" height="20" fill="#ffe1e1" stroke="#333"/> </g> <text x="120" y="135" text-anchor="middle">对角线 = 正样本(要最大)</text> </g> <!-- InfoNCE 损失 --> <g transform="translate(330,360)"> <rect x="0" y="0" width="240" height="60" fill="#ffe1e1" stroke="#333" rx="8"/> <text x="120" y="25" text-anchor="middle" font-weight="bold">对称 InfoNCE 损失</text> <text x="120" y="45" text-anchor="middle">L = (L_{I→T} + L_{T→I}) / 2</text> </g> <!-- 箭头:视觉塔 → 相似度 --> <line x1="245" y1="190" x2="325" y2="220" stroke="#333" stroke-width="2" marker-end="url(#arr3)"/> <!-- 箭头:文本塔 → 相似度 --> <line x1="655" y1="190" x2="575" y2="220" stroke="#333" stroke-width="2" marker-end="url(#arr3)"/> <!-- 箭头:相似度 → 损失 --> <line x1="450" y1="305" x2="450" y2="355" stroke="#333" stroke-width="2" marker-end="url(#arr3)"/> <defs> <marker id="arr3" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"> <path d="M0,0 L10,5 L0,10 z" fill="#333"/> </marker> </defs> </svg>
下面逐一拆解每个组件。
CLIP 的视觉塔有两种实现:
CLIP 原论文发布了基于 ResNet-50、ResNet-101、RN-50x4、RN-50x16、RN-50x64 等多个版本。这些 ResNet 经过修改:
更常用的实现是 ViT:
ViT-L/14 是后续 LLaVA、BLIP-2、Stable Diffusion 等大量工作的默认选择。
无论用哪种,视觉塔的输出处理方式相同:
输出 I \in \mathbb{R}^d,其中 d 是隐藏维度(ViT-L 是 768)。
CLIP 的文本塔是一个标准的 Transformer Encoder:
输入处理流程:
为什么取 [EOS]?这与 ViT 用 CLS token 的逻辑一致——[EOS] 是序列末尾的特殊 token,能聚合整段文本的信息。
两个塔输出向量 I \in \mathbb{R}^{d}、T \in \mathbb{R}^{d},由于都已归一化,相似度就是点积 I \cdot T。
在一个 batch 内(假设 batch size = B),可以一次计算所有图-文对的相似度,得到一个 B \times B 矩阵:
对角线 S_{ii} 是正样本相似度(要大),其他位置是负样本相似度(要小)。
对称 InfoNCE 损失:
注意分母中的求和方向:\mathcal{L}_{I \to T} 沿「行」归一化(每张图找对应文本),\mathcal{L}_{T \to I} 沿「列」归一化(每段文本找对应图)。
CLIP 的另一个突破是数据规模。OpenAI 从互联网抓取了 4 亿对图文(WIT,Web Image Text),覆盖极其广泛的语义。
这跟 ImageNet 的 100 万张、1000 类完全不在一个量级——CLIP 见过的图文组合远超任何监督数据集,因此学到的对齐空间泛化能力极强。
后续开源复现(如 OpenCLIP、LAION)用 LAION-5B(50 亿对图文)做训练,进一步证明数据规模是 CLIP 类模型性能的关键驱动力。
CLIP 训练的几个关键超参:
| 参数 | 取值 | 说明 |
|---|---|---|
| Batch size | 32768 | 极大,依赖硬件优化 |
| 温度 \tau | 0.01(可学习) | 后续 SigLIP 等也用可学习温度 |
| 训练步数 | 32 epoch | 在 4 亿对上 |
| 优化器 | Adam | β1=0.9, β2=0.98 |
| 学习率 | 5e-4 + 余弦退火 | 大 batch 配套 |
batch size 32768 是 CLIP 的标志性数字——它对应了 4.1 节讲的「负样本越多越好」。但这也意味着训练需要极强的工程:原始 CLIP 用了 256 张 V100 训练 12 天,后续 OpenCLIP 用 400 张 A100 训练 LAION-2B 用了约 18 天。
CLIP 训练完后最惊人的能力是零样本图像分类——不需要为任何类别训练分类头,直接用以下流程做分类:
这个流程在 ImageNet 上达到了 76.2% 的零样本 top-1 准确率——与有监督的 ResNet-50 持平,但完全不需要 ImageNet 训练数据。
CLIP 论文发现 prompt 的写法对零样本精度影响很大:
这就是所谓的 prompt engineering for CLIP——后续工作如 CoOp、CoCoOp 进一步探索如何把这些 prompt 也变成可学习的。
零样本分类的副作用是跨模态检索——以图搜文、以文搜图。
[一张狗的图] → 视觉塔 → 图像向量 ↓ 与百万张文本向量比对(预先编码建索引) ↓ 返回最相似的文本描述
这种能力在图文搜索引擎、内容推荐、版权检测等场景有巨大商业价值。CLIP 之前,跨模态检索需要专门训练,CLIP 让它变成「免费」。
CLIP 的影响远超它自己的应用——它成了多模态大模型时代的通用视觉-语言接口:
| 下游模型 | 如何使用 CLIP |
|---|---|
| LLaVA、MiniGPT-4 | 用 CLIP ViT-L/14 作为视觉编码器 |
| BLIP-2 | 用 EVA-CLIP(CLIP 增强版)作为视觉编码器 |
| Stable Diffusion v1/v2 | 用 OpenCLIP 作为文本编码器 |
| DALL-E 2 | 用 CLIP 作为图像-文本桥接 |
| Flamingo | 用 NFNet(CLIP 风格训练)作为视觉编码器 |
可以说,几乎所有 2022 年后的多模态大模型都直接或间接依赖 CLIP 的视觉塔或对齐思想。这是 CLIP 最重要的历史贡献。
CLIP 也有几个公认局限,催生了后续大量改进:
CLIP 只学了一个图像向量与一个文本向量的对齐,无法对应「图中右上角的猫」与「猫这个短语」这种区域-短语级对齐。后续工作如 GLIP、RegionCLIP 试图改进这一点。
CLIP 经常分不清「两只猫」和「三只猫」,也搞不清「左边的猫」与「右边的猫」。这是对比学习目标的固有局限——它只学整体匹配,不学计数与空间。
softmax 形式的 InfoNCE 要求大 batch,小实验室难以训练。SigLIP 后来用 sigmoid 解决了这一点。
CLIP 训练在 224 或 336 分辨率,处理高清图、长文档效果差。NaViT、SigLIP-2 后来用多分辨率训练解决。
CLIP 用双塔结构 + 对称 InfoNCE + 4 亿对图文,把图像向量与文本向量拉到同一语义空间,开创了多模态对齐的新范式。它的零样本分类、跨模态检索能力震惊了整个研究界,并成为后续多模态大模型的通用视觉-语言接口。
下一节(4.3)我们看 CLIP 训练中的几个关键工程策略:对称损失、归一化、温度系数、硬负样本挖掘。