- 解释 CLIP 的双塔架构和对比训练目标
- 使用预训练的 CLIP(或 SigLIP)做零样本分类,无需任何任务特定训练
- 从零实现零样本分类:编码类别 prompt、计算余弦相似度、取 argmax
- 区分 CLIP、SigLIP、OpenCLIP 与 LLaVA/LLaMA-vision 模型——各自在 2026 年的用途
问题
传统分类器是封闭词表的:一个 1000 类的 ImageNet 模型只能预测 1000 个标签。每增加一个新类别,都需要带标签数据并重新训练分类头。
CLIP(Radford 等,OpenAI 2021)表明,用从网络抓取的 4 亿个(图像,描述)对训练出的模型,在推理时可以按任意类别集合分类,类别纯用自然语言描述。写一句话,就给它加了一个新类别。
这种能力——零样本迁移——正是现代视觉系统都从 CLIP 家族 checkpoint 起步的原因。检测(Grounding DINO、OWL-ViT)、分割(CLIPSeg、SAM)、检索、内容审核、VLM 以及文生图,全都构建在 CLIP 风格的联合 embedding 之上。
概念
双塔
flowchart LR
IMG["Image"] --> IENC["Image encoder<br/>(ViT-L/14)"] --> IEMB["Image embedding<br/>(1024,)"]
TXT["Caption"] --> TENC["Text encoder<br/>(transformer)"] --> TEMB["Text embedding<br/>(1024,)"]
IEMB --> SIM["Cosine similarity"]
TEMB --> SIM
style IENC fill:#dbeafe,stroke:#2563eb
style TENC fill:#fef3c7,stroke:#d97706
style SIM fill:#dcfce7,stroke:#16a34a两个编码器最后都通过线性投影到相同的 embedding 维度(CLIP-B/32 为 512,CLIP-L/14 为 1024)。做 L2 归一化后计算余弦相似度。
目标函数
给定一个包含 N 个(图像,描述)对的 batch,构造 N \times N 的相似度矩阵。训练两个编码器,使对角线(匹配对)相似度高,非对角线(不匹配)相似度低。
sim_matrix = image_embeddings @ text_embeddings.T / tau
loss_i2t = cross_entropy(sim_matrix, targets=arange(N))
loss_t2i = cross_entropy(sim_matrix.T, targets=arange(N))
loss = (loss_i2t + loss_t2i) / 2之所以对称,是因为图像到文本和文本到图像两个方向的检索都应当可用。tau(温度)通常作为标量参数学习,初始化为 0.07。
SigLIP:更好的损失
SigLIP(Zhai 等,2023)用逐对 sigmoid 取代了 softmax:
逐对损失去掉了 CLIP 所需的 batch 级归一化。SigLIP 在小 batch 上训练更好,在同等数据下追平或超过 CLIP。
零样本分类
给定一个训练好的 CLIP:
- 对每个类别,拼一个 prompt:"a photo of a {class}"。
- 用文本编码器编码所有类别 prompt →
T,形状 (C, d)。 - 编码测试图像 →
I,形状 (1, d)。 - 相似度 =
I @ T.T,形状 (1, C)。 - 取 argmax → 预测类别。
prompt 工程很重要。OpenAI 为 ImageNet 发布了 80 个 prompt 模板("a photo of a {}"、"a blurry photo of a {}"、"a sketch of a {}"……)。对每个类别,把所有模板的 embedding 取平均,可以再提升 1-3% 的 top-1 准确率。
2026 年 CLIP 风格模型的用武之地
- 零样本分类——直接使用。
- 图像检索——所有图像只编码一次,推理时嵌入查询。
- 文本条件检测——Grounding DINO、OWL-ViT 把 CLIP 文本塔套在检测器上。
- 文本条件分割——CLIPSeg;SAM 通过 CLIP 接收文本提示输入。
- VLM——LLaVA、Qwen-VL、InternVL 把 CLIP 家族的视觉编码器接入 LLM。
- 文生图——Stable Diffusion、DALL-E 3 以 CLIP 文本 embedding 为条件。
一旦有了共享嵌入空间,每个视觉 + 语言任务都变成一次距离计算。
构建它
第 1 步:一个微型双塔模型
真正的 CLIP 是 ViT + Transformer。本课中,两座塔是在预提取特征上运行的小型 MLP,这样在 CPU 上也能看到训练信号。
pythonimport torch
import torch.nn as nn
import torch.nn.functional as F
class TwoTower(nn.Module):
def __init__(self, img_in=128, txt_in=64, emb=64):
super().__init__()
self.image_proj = nn.Sequential(nn.Linear(img_in, 128), nn.ReLU(), nn.Linear(128, emb))
self.text_proj = nn.Sequential(nn.Linear(txt_in, 128), nn.ReLU(), nn.Linear(128, emb))
self.logit_scale = nn.Parameter(torch.ones([]) * 2.6592) # ln(1/0.07)
def forward(self, img_feats, txt_feats):
i = F.normalize(self.image_proj(img_feats), dim=-1)
t = F.normalize(self.text_proj(txt_feats), dim=-1)
return i, t, self.logit_scale.exp()两个投影、共享维度输出、可学习温度。与真实 CLIP API 的形状一致。
第 2 步:对比损失
pythondef clip_loss(image_emb, text_emb, logit_scale):
N = image_emb.size(0)
sim = logit_scale * image_emb @ text_emb.T
targets = torch.arange(N, device=sim.device)
l_i = F.cross_entropy(sim, targets)
l_t = F.cross_entropy(sim.T, targets)
return (l_i + l_t) / 2对称。logit_scale 越大,softmax 越尖锐,置信度越高,但有不稳定风险。
第 3 步:零样本分类器
python@torch.no_grad()
def zero_shot_classify(model, image_feats, class_text_feats, class_names):
"""
image_feats: (N, img_in)
class_text_feats: (C, txt_in) one averaged embedding per class
"""
i = F.normalize(model.image_proj(image_feats), dim=-1)
t = F.normalize(model.text_proj(class_text_feats), dim=-1)
sim = i @ t.T
pred = sim.argmax(dim=-1)
return [class_names[p] for p in pred.tolist()]每步一行。这正是用生产级 CLIP checkpoint 做零样本分类的流程。
第 4 步:健全性检查
pythontorch.manual_seed(0)
model = TwoTower()
img = torch.randn(8, 128)
txt = torch.randn(8, 64)
i, t, scale = model(img, txt)
loss = clip_loss(i, t, scale)
print(f"batch size: {i.size(0)} loss: {loss.item():.3f}")随机初始化的模型,损失应接近 log(N) = log(8) = 2.08——这是尚未学到任何结构时对称交叉熵的目标值。
使用它
2026 年,OpenCLIP 是社区默认选择:
pythonimport open_clip
import torch
from PIL import Image
model, _, preprocess = open_clip.create_model_and_transforms("ViT-B-32", pretrained="laion2b_s34b_b79k")
tokenizer = open_clip.get_tokenizer("ViT-B-32")
image = preprocess(Image.open("dog.jpg")).unsqueeze(0)
text = tokenizer(["a photo of a dog", "a photo of a cat", "a photo of a car"])
with torch.no_grad():
image_features = model.encode_image(image)
text_features = model.encode_text(text)
image_features = image_features / image_features.norm(dim=-1, keepdim=True)
text_features = text_features / text_features.norm(dim=-1, keepdim=True)
probs = (100.0 * image_features @ text_features.T).softmax(dim=-1)
print(probs)SigLIP 更新,在小规模训练下表现更好,新项目更倾向于它:google/siglip-base-patch16-224。Hugging Face 两者都提供。
交付它
本节课产出:
outputs/prompt-zero-shot-class-picker.md—— 一个在给定类别列表和领域后,为零样本 CLIP 设计类别模板的 prompt。outputs/skill-image-text-retriever.md—— 一个用任意 CLIP checkpoint 构建图像 embedding 索引,支持按文本查询和按图像查询的技能。
练习
- (简单) 使用预训练的 OpenCLIP ViT-B/32,配合 80 个模板的 prompt 集,在 CIFAR-10 上做零样本分类。报告 top-1 准确率;应在 85-90% 左右。
- (中等) 在同一 CIFAR-10 任务上比较单模板("a photo of a {}")与 80 模板平均 embedding。量化差距并解释模板为何有帮助。
- (困难) 构建一个零样本图像检索索引:用 CLIP 嵌入 1000 张图像,建立 FAISS 索引,用自然语言描述查询。对你手写的 20 条留出查询报告 recall@5。
关键术语
| 术语 | 人们怎么说 | 实际含义 |
|---|---|---|
| Two-tower | 「双编码器」 | 独立的图像和文本编码器,最后接到共享维度的投影头 |
| Zero-shot | 「无需任务特定训练」 | 推理时分类到仅由文本描述的类别;不接触任何标签 |
| Temperature / logit_scale | 「tau」 | 在 softmax 之前缩放相似度矩阵的可学习标量 |
| Prompt template | 「A photo of a {}」 | 包在类别名外面的自然语言模板;平均多个模板能提升零样本准确率 |
| CLIP | 「图像+文本模型」 | 2021 年 OpenAI 的模型;2026 年该领域的通用词汇 |
| SigLIP | 「Sigmoid CLIP」 | 用逐对 sigmoid 取代 softmax;小 batch 下训练更好 |
| OpenCLIP | 「开放复现」 | 社区在 LAION 上训练的 CLIP 变体;开源 pipeline 的生产默认 |
| VLM | 「视觉-语言模型」 | CLIP 家族编码器加一个 LLM,训练来回答关于图像的问题 |
延伸阅读
- CLIP: Learning Transferable Visual Models from Natural Language Supervision (Radford et al., 2021)
- SigLIP: Sigmoid Loss for Language-Image Pre-Training (Zhai et al., 2023)
- OpenCLIP —— 社区代码库
- Oquab et al. (2023). DINOv2: Learning Robust Visual Features without Supervision —— 该论文,包含与 CLIP 风格和 MAE 风格模型的特性基准对比