第3章

第3章:视觉-语言对齐 (CLIP)

从零实现 CLIP 双编码器架构,掌握对比学习(InfoNCE/SigLIP)、Zero-shot 迁移和图文检索。

CLIP对比学习InfoNCESigLIPZero-shot

从视觉到多模态的关键一步


第一部分:为什么需要视觉-语言对齐?

1.1 从"看"到"理解"

第2章的 ViT、MAE、DINO 解决了"看"的问题——它们能把图像编码为高质量的特征向量。但这些特征向量是"无语义的":它们知道图像包含什么,但不知道这些东西叫什么名字,更不知道如何与人类的语言指令对应起来。

对于 VLA 自动驾驶,这是一个致命问题:

- 指令是语言的("在下一个路口右转")

- 感知是视觉的(摄像头图像)

- 规划需要连接两者

CLIP 解决了这个 gap:将视觉和语言映射到同一个语义空间。

1.2 CLIP 的核心贡献

CLIP (Radford et al., 2021) 的核心思想极其简单但强大:

1. 收集 4 亿对(图像,文本)数据

2. 训练两个编码器分别处理图像和文本

3. 让匹配的图文对的嵌入向量尽量接近,不匹配的尽量远离

就这么简单。但效果惊人:训练好的模型可以直接用于分类、检索、检测等任务,完全不需要微调。

1.3 在 VLA 学习路线中的位置


第1章: Transformer + GPT(语言能力)
第2章: ViT + MAE + DINO(视觉能力)
第3章: CLIP + SigLIP(视觉-语言对齐)← 当前
第4章: LLaVA(多模态对话)
第14章: VLA(视觉-语言-行动)

本章是连接"纯视觉"和"多模态"的关键桥梁。


第二部分:双编码器架构

2.1 架构总览


图像 (224×224)              文本 "a cat sitting on..."
     │                              │
  ViT Encoder                  Text Transformer
  (12 layers)                  (12 layers, causal)
     │                              │
  [CLS] token                 [EOS] token
     │                              │
  Linear Projection           Linear Projection
     │                              │
  L2 Normalize                L2 Normalize
     │                              │
  image_features (512-d)      text_features (512-d)
     │                              │
     └──────────┬───────────────────┘
                │
         Cosine Similarity
         × exp(logit_scale)
                │
         Similarity Matrix (B×B)

2.2 视觉编码器:ViT

CLIP 的视觉编码器与标准 ViT 几乎相同,但有两点关键差异:

1. 更大的 patch size:ViT-B/32 使用 32×32 的 patch(而非 16×16),为了处理 4 亿张图像时的训练效率

2. 输出是归一化嵌入:最后有一个投影层将 [CLS] token 映射到共享空间并 L2 归一化


class CLIPVisionEncoder(nn.Module):
    def forward(self, x):
        x = self.patch_embed(x)        # (B, 3, H, W) → (B, N, D)
        x = x + self.pos_embed
        for block in self.blocks:
            x = block(x)
        x = x[:, 0]                      # [CLS] token
        x = self.visual_projection(x)    # → projection_dim
        x = F.normalize(x, dim=-1)       # L2 normalize
        return x

2.3 文本编码器:Causal Transformer

CLIP 的文本编码器是一个 GPT-2 风格的因果 Transformer。关键设计选择:

- 使用因果掩码:虽然不需要生成文本,但因果注意力保留了文本的序列结构

- 取 EOS token 的输出:EOS token 能"看到"所有前文,其输出是整个文本的摘要

- max_seq_len=77:这个数字来自对 4 亿条文本的统计

2.4 投影到共享空间

两个编码器的输出维度可能不同(视觉 768,文本 512),需要通过线性投影映射到同一个维度(通常 512 或 768):


self.visual_projection = nn.Linear(vision_dim, projection_dim, bias=False)
self.text_projection = nn.Linear(text_dim, projection_dim, bias=False)

投影之后再 L2 归一化,确保所有嵌入都在单位超球面上。

2.5 为什么不共享编码器?

为什么不训练一个统一的编码器同时处理图像和文本?因为:

- 图像和文本的数据模态完全不同(像素 vs 离散 token)

- 需要不同的处理方式(卷积/patch vs embedding lookup)

- 双编码器更灵活:可以离线预计算图像库的嵌入


第三部分:对比学习 — InfoNCE Loss

3.1 核心思想

给定一个 batch 的 B 对(图像,文本),构建 B×B 的相似度矩阵:


      文本1  文本2  文本3  文本4
图像1  ✓      ✗      ✗      ✗     ← 图像1 应该最匹配 文本1
图像2  ✗      ✓      ✗      ✗
图像3  ✗      ✗      ✓      ✗
图像4  ✗      ✗      ✗      ✓

- 对角线(i=j)是正样本对:应该最大化相似度

- 非对角线(i≠j)是负样本对:应该最小化相似度

3.2 数学公式

对于图像 i,它与文本 j 的匹配概率为:

$$P(j|i) = \frac{\exp(S_{ij}/\tau)}{\sum_k \exp(S_{ik}/\tau)}$$

其中 $S_{ij} = \text{cosine\_sim}(I_i, T_j)$,$\tau$ 是温度参数。

InfoNCE 损失是图像→文本 和 文本→图像 两个方向的交叉熵:

$$\mathcal{L} = \frac{1}{2B}\sum_i \left[-\log P(i|i) - \log P(i|i)_{\text{txt}\to\text{img}}\right]$$

3.3 为什么是对称损失?

直觉上,好的对齐应该是对称的:

- 给出图像,能准确找到对应的文本

- 给出文本,也能准确找到对应的图像

对称损失强制模型在两个方向上都做好。

3.4 代码实现


def infonce_loss(image_features, text_features, logit_scale):
    # 相似度矩阵
    logits = logit_scale * image_features @ text_features.t()

    # 标签:对角线位置
    labels = torch.arange(B)

    # 双向损失
    loss_img = F.cross_entropy(logits, labels)      # 图像→文本
    loss_txt = F.cross_entropy(logits.t(), labels)   # 文本→图像

    return (loss_img + loss_txt) / 2

3.5 温度参数的作用

温度 $\tau = 1/\text{logit\_scale}$ 控制 softmax 的"锐度":

- 低温(τ→0):softmax 变成 one-hot → 模型只关注最相似的样本

- 高温(τ→1):softmax 变平 → 所有样本获得相似的梯度

CLIP 将 logit_scale 设为可学习参数,初始值对应 τ=0.07(经验最优值)。


第四部分:SigLIP — Sigmoid Loss

4.1 InfoNCE 的一个问题

InfoNCE 是 B-way 的 softmax 分类,需要 batch 内有足够的负样本。当 B 较小时(比如训练资源有限),效果会显著下降。

OpenAI 的训练用了 batch_size=32768,这对大多数团队来说完全不可行。

4.2 SigLIP 的解决方案

SigLIP (Zhai et al., 2023) 将问题重新定义为 B² 个独立的二分类问题:

对于每个 (i, j) 对:

- 如果 i=j(匹配对),标签 y=1

- 如果 i≠j(不匹配对),标签 y=-1

损失函数:

$$\mathcal{L} = -\frac{1}{B^2}\sum_{i,j} \log\sigma(y_{ij} \cdot (S_{ij} + b))$$

其中 σ 是 sigmoid 函数,b 是可学习的偏置。

4.3 SigLIP 的优势

特性 InfoNCE SigLIP
损失形式 B-way softmax B² 个二分类
batch size 依赖 强(需要大 B)
训练稳定性 中等
计算复杂度 O(B²D) O(B²D)
额外参数 logit_scale logit_scale + logit_bias
VLA 中的使用 早期模型 当前主流(OpenVLA 等)

4.4 代码实现


def siglip_loss(image_features, text_features, logit_scale, logit_bias):
    # 相似度矩阵
    logits = logit_scale * image_features @ text_features.t() + logit_bias

    # 标签:1(匹配)或 -1(不匹配)
    labels = 2 * torch.eye(B) - 1

    # Sigmoid loss
    loss = -F.logsigmoid(labels * logits).mean()
    return loss

4.5 为什么 SigLIP 不依赖大 batch?

在 InfoNCE 中,负样本通过 softmax 分母中的 Σ 竞争。如果负样本太少,这个竞争不充分。

在 SigLIP 中,每个 (i, j) 对是独立判断的,不需要与其他对的比较。因此 batch size 小也不影响单个对的判定质量。


第五部分:CLIP 训练

5.1 数据需求

CLIP 的训练数据规模是前所未有的:

- 4 亿(图像,文本)对

- 从互联网收集(alt-text、社交媒体等)

- 包含极其多样的视觉概念

对于自动驾驶场景的 CLIP 微调:

- 可以使用驾驶视频 + 对应的语音/文字指令

- 数据量级远小于 OpenAI,但可以用 SigLIP 弥补

5.2 训练技巧

大 batch size

- 对比学习天然需要负样本多样性

- 如果 batch size 不够 → 用梯度累积或 SigLIP

参数分组 weight decay


# bias 和 norm 参数不做 decay
decay_params = [p for n, p in model.named_parameters()
                if "bias" not in n and "norm" not in n.lower()]
no_decay_params = [p for n, p in model.named_parameters()
                   if "bias" in n or "norm" in n.lower()]
optimizer = AdamW([{"params": decay_params, "weight_decay": 0.2},
                   {"params": no_decay_params, "weight_decay": 0.0}])

学习率调度

- Cosine annealing 从初始 lr 到 0

- 必要时加 warmup(前几百步线性增长)

5.3 训练监控

训练中需要关注的指标:

- Loss:InfoNCE 应该从 ~ln(B) ≈ 4-5 开始,逐渐下降

- Image Accuracy:图像→文本的 top-1 匹配率(in-batch)

- Text Accuracy:文本→图像的 top-1 匹配率

- Temperature:logit_scale 的倒数,通常在训练中逐渐减小


第六部分:图文检索

6.1 检索作为 CLIP 的核心应用

训练好的 CLIP 模型天然支持双向检索:

文本→图像:"找所有包含行人的场景"


text_features = clip.encode_text("a street with pedestrians")
similarities = text_features @ image_database.t()
top_matches = similarities.topk(k=5)

图像→文本:给定一张图像,找最匹配的描述


image_features = clip.encode_image(query_image)
similarities = image_features @ text_database.t()
best_caption = text_database[similarities.argmax()]

6.2 自动驾驶中的检索应用

场景挖掘


"a rainy night with heavy traffic and pedestrians crossing"
→ 找到所有类似的 corner case 场景

异常检测


"a normal highway in clear weather"
→ 匹配度低于阈值?可能是异常场景

数据整理


为每条驾驶指令自动匹配最相关的图像帧
→ 减少人工标注工作量

训练数据构建


从海量驾驶日志中筛选出符合特定模式(变道、转弯、跟车)的片段

6.3 大规模检索优化

当图像数据库很大(百万级)时,暴力计算所有相似度不可行。实际使用:

- FAISS(Facebook AI Similarity Search):高效的向量检索

- 分层索引:粗筛 → 精排

- 离线预计算:图像嵌入提前算好存储


第七部分:Zero-shot 分类

7.1 CLIP 最令人惊叹的能力

不需要任何训练数据,不需要微调,直接用文本描述来做图像分类。


输入图像 → 编码为嵌入向量
类别列表 → 编码为嵌入向量
计算相似度 → 最高分的类别就是预测

7.2 工作原理


def zero_shot_classify(images, class_names, clip_model, tokenizer):
    # 1. 将类别名转为 CLIP 风格的 prompt
    class_texts = [f"a photo of a {name}" for name in class_names]

    # 2. 编码图像和文本
    image_features = clip_model.encode_image(images)
    text_features = clip_model.encode_text(tokenizer(class_texts))

    # 3. 计算相似度并 softmax
    logits = image_features @ text_features.t()
    probs = softmax(logits)

    return probs

7.3 Prompt Engineering

CLIP 对 prompt 很敏感。同样的类别,不同的 prompt 可以带来显著的准确率提升:

Prompt ImageNet 准确率
"cat" ~55%
"a photo of a cat" ~63%
"a photo of a cat, a type of animal" ~65%

这就是为什么 CLIP 论文中使用 80 个不同的 prompt 模板做集成。

7.4 自动驾驶 Zero-shot

CLIP zero-shot 在驾驶场景中的应用:

- 未见过的交通标志:只要有文本描述就能识别

- 异常天气分类:"heavy snow","dense fog"

- 驾驶场景分类:"urban intersection","highway merge"

- 危险情况检测:"pedestrian running across street","vehicle running red light"


第八部分:CLIP 的局限与演进

8.1 CLIP 的局限

1. 缺乏细粒度理解:能判断"这是猫",但不能数"图里有几只猫"

2. 空间关系弱:不能区分"猫在桌上"和"桌在猫上"

3. 幻觉:会在完全无关的图像中"看到"提到的物体

4. 文本能力有限:文本编码器很小,不能进行复杂推理

8.2 从 CLIP 到 BLIP-2 / LLaVA

CLIP 是一个"看+读"的模型,但不能"说"。后续工作将 CLIP 的视觉编码器接入 LLM:

- BLIP-2:用 Q-Former 桥接 CLIP 视觉编码器和 LLM

- LLaVA:直接用线性投影连接 CLIP 视觉编码器和 LLM

- MiniGPT-4:类似 BLIP-2,但训练更简单

这就是第4章的内容。

8.3 CLIP 在 VLA 中的角色

现代 VLA 模型的典型架构:


图像 → CLIP/SigLIP 视觉编码器 → 视觉 tokens
                                   ↓
                              视觉-语言投影
                                   ↓
语言指令 → Tokenizer → ────→ LLM → 行动 token

CLIP 的视觉编码器(特别是 SigLIP 版本)已经经过大规模图文对齐训练,提供的视觉特征天然适合与语言指令融合。

OpenVLA、RT-2 等主流 VLA 模型都使用 SigLIP 作为视觉 backbone。


总结

本章建立的核心能力:

1. ✅ 双编码器架构:视觉+文本独立编码,投影到共享空间

2. ✅ InfoNCE 损失:B-way 对比分类,对角线是正样本

3. ✅ SigLIP 损失:B² 个独立二分类,不依赖大 batch

4. ✅ 可学习温度:logit_scale 自适应调整 softmax 锐度

5. ✅ 图文检索:双向 Top-K 最近邻搜索

6. ✅ Zero-shot 分类:文本描述 → 图像分类,无需微调

7. ✅ CLIP 训练器:参数分组 decay、余弦调度、梯度裁剪


练习建议

1. 运行 01_clip_model.py,观察相似度矩阵和 loss 值

2. 运行 02_training_retrieval.py,理解训练循环和检索流程

3. 尝试修改 logit_scale 的初始值,观察对 loss 的影响

4. 比较 InfoNCE 和 SigLIP 在不同 batch size 下的表现差异

5. 思考:如果要为自动驾驶构建 CLIP 训练数据,你会如何收集?


下一章:多模态大语言模型 (LLaVA) — 让模型"看图说话"