VL模型演进:从CLIP双塔到原生多模态统一架构
深度拆解Vision-Language模型的三代演进:双塔架构、交叉注意力、原生多模态统一
引言:跨越视觉与语言的鸿沟
问题的本质
人类天生就能理解图像和文字之间的关联:看到一张猫的照片,我们知道"猫"这个词指的就是它。但对机器来说,图像是像素矩阵,文字是token序列,两者存在于完全不同的"世界"。
VL模型要解决的核心问题:
如何让机器理解:
[一张猫的照片] ≈ "一只猫" ≈ "cat" ≈ "小猫咪"
更进一步:
[猫在沙发上的照片] ≈ "猫坐在沙发上"
但 ≠ "猫在地板上"
三代演进的本质
第一代(CLIP):
建立"翻译字典" - 图像和文本映射到同一空间
就像学会了"这个图等于那个词"
第二代(BLIP-2):
加入"理解对话" - 模态之间可以交互
就像不只是查字典,还能根据图片回答问题
第三代(LLaVA):
完全"融合思考" - 图像变成了语言的一部分
就像把图片当作特殊的"词",直接参与推理
演进的驱动力
| 代际 | 能做什么 | 不能做什么 | 突破点 |
|---|---|---|---|
| 第一代 | 判断图文是否匹配 | 不能生成文本、不能推理 | 对比学习 |
| 第二代 | 可以描述图片、回答问题 | 架构复杂、模态融合不够深 | 交叉注意力 |
| 第三代 | 深度推理、多轮对话 | 推理成本高 | 统一架构 |
第一代:双塔架构 - CLIP(2021)
1.1 核心思想:建立"翻译字典"
设计哲学
CLIP的本质是建立一个"图文翻译字典":让语义相近的图像和文本在向量空间中靠近,语义不同的远离。
类比理解:双语词典
想象你在学英语:
看到苹果图片 → 大脑记住 → "apple"这个词
看到狗的图片 → 大脑记住 → "dog"这个词
CLIP做的就是这件事:
训练时看4亿个图文对
学会:[猫的图片] 和 "cat" 应该很近
[猫的图片] 和 "dog" 应该很远
架构:两座独立的桥
图像世界 文本世界
↓ ↓
┌──────────┐ ┌──────────┐
│ 图像编码器 │ │ 文本编码器 │
│ (ViT) │ │ (GPT) │
└──────────┘ └──────────┘
↓ ↓
统一的语义空间(向量空间)
┌───────────────────────────┐
│ [猫图] 靠近 "cat" │
│ [狗图] 靠近 "dog" │
│ [车图] 靠近 "car" │
└───────────────────────────┘
关键设计决策
| 决策 | 为什么这样做 | 代价 |
|---|---|---|
| 双塔独立 | 图像和文本可以分别预计算,检索时只需计算相似度 | 无法深度理解图文细节关系 |
| 对比学习 | 不需要人工标注"这是猫/狗",只要知道图文是否匹配 | 需要海量数据(4亿对) |
| 大规模数据 | 见过足够多的图文对,才能泛化到新概念 | 训练成本极高 |
1.2 训练原理:对比学习
核心机制
训练目标:让匹配的图文对靠近,不匹配的远离
batch中有N个图文对:
正例:(图1, 文本1), (图2, 文本2), ... ← 应该相似
负例:(图1, 文本2), (图1, 文本3), ... ← 应该不相似
训练过程:
1. 分别编码图像和文本 → 得到向量
2. 计算所有图文对的相似度矩阵(N×N)
3. 让对角线(正例)的相似度高
4. 让非对角线(负例)的相似度低
结果:
语义相关的图文在向量空间中自然聚集
为什么叫"对比学习"?
类比:学习外语时
传统方法(监督学习):
老师指着猫说:"这是cat,记住!" ← 需要标注
对比学习:
给你100个物体和100个单词,告诉你哪个配哪个
你自己通过"对比"学会:
- "这个毛茸茸的" 对应 "cat"
- "这个汪汪叫的" 对应 "dog"
← 不需要标注具体特征
1.3 零样本能力:突破性创新
传统方法 vs CLIP
| 维度 | 传统分类模型 | CLIP |
|---|---|---|
| 训练时 | 必须定义类别(猫、狗、车...) | 只看图文对,不需要类别 |
| 预测时 | 只能预测训练过的类别 | 可以预测任何用文字描述的概念 |
| 扩展性 | 新类别 = 重新训练 | 新类别 = 换个文字描述 |
零样本的本质
传统模型:
学会了1000个固定类别的"标签"
像背单词表,只认识背过的
CLIP:
学会了"图像语义"和"文本语义"的通用映射
像理解了语言逻辑,可以推理新词
示例:
训练时从未见过"斑马"
但见过:
- [马的图片] + "horse"
- [有条纹的东西] + "striped"
测试时:
给出 "a striped horse-like animal"
CLIP能理解并匹配到斑马图片 ✓
1.4 CLIP的边界
能力边界
| 能做 | 不能做 | 本质原因 |
|---|---|---|
| ✅ 判断图文是否匹配 | ❌ 理解细粒度关系("左边"vs"右边") | 双塔独立,无交互 |
| ✅ 图文检索(以图搜文/以文搜图) | ❌ 生成文本描述 | 没有解码器 |
| ✅ 零样本分类 | ❌ 复杂推理 | 只是简单匹配 |
为什么会有这些局限?
双塔架构的本质:
就像两个人各自看图片和文本,然后汇报
"我看到的" vs "我读到的" → 比较是否一致
缺少的是:
两个人一边看图一边讨论
"你看这个是不是在左边?" → 细粒度交互
这些局限导致了第二代的诞生
第二代:交叉注意力 - Flamingo/BLIP系列(2022-2023)
2.1 动机:为什么需要模态交互?
CLIP的核心问题:独立编码导致信息损失
问题场景:图文细粒度理解
图片:[桌子上有苹果和香蕉,苹果在左边]
问题:"桌子上苹果在哪边?"
正确答案:"左边"
CLIP的处理:
图像塔:[桌子、苹果、香蕉、水果...] → 向量
文本塔:[桌子、苹果、位置、左边...] → 向量
问题:
- 图像编码时不知道要关注"位置关系"
- 文本编码时不知道图片中的具体布局
- 两个向量点积,无法捕捉细节
解决方案:让模态之间"对话"
交叉注意力机制:
图像特征 → Query: "苹果在哪里?"
↓ 交叉注意力
图像特征 ← 关注到苹果和位置信息
↓
生成答案:"左边"
2.2 架构演进:从双塔到交叉注意力
Flamingo (DeepMind, 2022)
┌─────────────────────────────────────────────────────┐
│ Flamingo: Few-shot Learning with Vision │
├─────────────────────────────────────────────────────┤
│ │
│ 图像输入 文本输入 │
│ [图片1, 图片2, ...] "描述这些图片" │
│ ↓ ↓ │
│ Vision Encoder Frozen LLM │
│ (预训练好的ViT) (Chinchilla 70B) │
│ ↓ ↓ │
│ Perceiver Resampler ┌─────────────────┐ │
│ (压缩视觉特征) │ Language Model │ │
│ ↓ │ │ │
│ 视觉tokens │ 每隔N层插入 │ │
│ └─────────────→ │ Cross-Attention │ │
│ │ │ │
│ │ 文本 ⟷ 视觉 │ │
│ └─────────────────┘ │
│ ↓ │
│ 生成文本输出 │
└─────────────────────────────────────────────────────┘
关键创新
-
Perceiver Resampler:压缩视觉特征
# 问题:ViT输出太多token(如256个) # 解决:用可学习的queries压缩到固定数量(如64个) visual_features = vision_encoder(image) # (256, D) # Perceiver: 用少量queries提取关键信息 queries = learnable_queries # (64, D) 可学习 compressed = cross_attention( query=queries, key=visual_features, value=visual_features ) # (64, D) -
交叉注意力插入LLM
# 每隔N层,在LLM中插入交叉注意力 for layer in language_model.layers: # 正常的自注意力(文本内部) x = self_attention(x, x, x) # 每隔N层:交叉注意力(文本关注视觉) if layer.id % N == 0: x = cross_attention( query=x, # 来自文本 key=visual_tokens, # 来自图像 value=visual_tokens ) x = feedforward(x)
BLIP-2 (Salesforce, 2023)
┌──────────────────────────────────────────────────┐
│ BLIP-2: Bootstrapping Language-Image Pretraining│
├──────────────────────────────────────────────────┤
│ │
│ 图像 Q-Former LLM │
│ ↓ ↓ ↓ │
│ ViT-L/14 可学习Queries Frozen │
│ ↓ ↓ OPT-6.7B │
│ [Frozen] 32个Queries ↓ │
│ ↓ ↓ ↓ │
│ 图像特征 → 交叉注意力 → 线性投影 → 文本生成 │
│ (257, D) (32, D) (32, D) ↓ │
│ 输出描述 │
└──────────────────────────────────────────────────┘
Q-Former核心思想
# Q-Former: 可学习的查询机制
class QFormer:
def __init__(self):
self.queries = nn.Parameter(torch.randn(32, 768)) # 32个可学习查询
self.cross_attn_layers = nn.ModuleList([...]) # 多层交叉注意力
self.self_attn_layers = nn.ModuleList([...]) # 自注意力
def forward(self, image_features):
# image_features: (257, 1408) 来自ViT
x = self.queries # (32, 768) 初始化
# 多层交互
for self_attn, cross_attn in zip(self.self_attn_layers, self.cross_attn_layers):
# 1. Query之间自注意力(提炼信息)
x = self_attn(x, x, x)
# 2. Query关注图像特征(提取视觉信息)
x = cross_attn(
query=x, # (32, 768)
key=image_features, # (257, 1408)
value=image_features
)
return x # (32, 768) 压缩后的视觉-语言特征
为什么Q-Former有效?
传统方法:
图像 → 257个patch tokens → 直接输入LLM
问题:token太多,计算量大
Q-Former:
图像 → 257个patch tokens
↓ 交叉注意力
32个可学习queries(相当于问32个问题)
↓
32个精炼的视觉特征 → 输入LLM
优势:
✓ 大幅减少token数量(257 → 32)
✓ 可学习的queries自动学会提取关键信息
✓ LLM计算量降低8倍
2.3 训练策略:两阶段预训练
BLIP-2的训练流程
┌─────────────────────────────────────────────────┐
│ Stage 1: 视觉-语言表示学习 │
├─────────────────────────────────────────────────┤
│ 目标:让Q-Former学会提取有用的视觉信息 │
│ │
│ 冻结:ViT图像编码器 │
│ 训练:Q-Former │
│ 数据:图文对(1.29亿对) │
│ │
│ 三个损失函数: │
│ 1. Image-Text Contrastive (ITC) │
│ → 图像和文本整体语义对齐 │
│ 2. Image-grounded Text Generation (ITG) │
│ → 给定图像,生成描述 │
│ 3. Image-Text Matching (ITM) │
│ → 判断图文是否匹配(二分类) │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Stage 2: 视觉到语言生成对齐 │
├─────────────────────────────────────────────────┤
│ 目标:让LLM理解Q-Former输出的视觉特征 │
│ │
│ 冻结:ViT + LLM │
│ 训练:Q-Former + 线性投影层 │
│ 数据:图像描述、VQA问答 │
│ │
│ 损失函数: │
│ - Language Modeling Loss │
│ → 给定图像特征,生成文本 │
└─────────────────────────────────────────────────┘
为什么要两阶段?
Stage 1:
让Q-Former学会"看懂"图片
- 哪些视觉特征重要?
- 如何压缩信息?
- 如何与文本对齐?
Stage 2:
让LLM学会"理解"Q-Former的输出
- 将视觉特征映射到LLM的语言空间
- 学会根据图像生成连贯文本
优势:
✓ 分阶段训练,每阶段目标明确
✓ 冻结大模型(ViT和LLM),降低计算成本
✓ 只训练Q-Former和投影层,参数少
2.4 效果提升
性能对比(Zero-shot Image Captioning)
| 模型 | COCO CIDEr | 参数量 | 训练数据 |
|---|---|---|---|
| CLIP | - | 428M | 4亿对 |
| Flamingo-9B | 84.3 | 9B | 未公开 |
| Flamingo-80B | 93.1 | 80B | 未公开 |
| BLIP-2 (OPT-2.7B) | 103.9 | 3.4B | 1.29亿对 |
| BLIP-2 (OPT-6.7B) | 113.2 | 7.3B | 1.29亿对 |
关键观察
BLIP-2的优势:
✓ 用更少的数据(1.29亿 vs 4亿+)
✓ 更小的模型(7B vs 80B)
✓ 达到更好的效果(113.2 vs 93.1)
原因:
1. Q-Former设计巧妙,高效压缩视觉信息
2. 两阶段训练,目标更清晰
3. 充分利用预训练的ViT和LLM
第二代总结
| 优势 | 局限 |
|---|---|
| ✅ 细粒度图文理解 | ❌ 仍需复杂的模块设计(Q-Former) |
| ✅ 可以生成文本 | ❌ 训练流程复杂(两阶段) |
| ✅ 训练成本降低 | ❌ 模态融合仍不够"原生" |
| ✅ 效果大幅提升 | ❌ 推理需要多个模块协作 |
第三代:原生多模态Transformer - LLaVA/GPT-4V(2023-至今)
3.1 核心理念:统一的多模态语言模型
问题:前两代都是"拼接"架构
第一代 CLIP:
图像塔 + 文本塔 → 两个独立模型拼接
第二代 BLIP-2:
ViT + Q-Former + LLM → 三个模块拼接
问题:
- 需要精心设计连接方式
- 训练复杂(分阶段、冻结/解冻)
- 推理需要多个模块协作
第三代思路:把图像当作"视觉tokens"
核心洞察:
文本是token序列:["我", "喜欢", "猫"]
图像也可以是token序列:[patch1, patch2, ..., patch256]
那么:
为什么不能让LLM直接处理图像tokens?
输入:[图像tokens] + [文本tokens]
输出:文本回答
→ 真正的"原生"多模态!
3.2 LLaVA: 视觉指令微调
架构设计(极致简洁)
┌────────────────────────────────────────────────┐
│ LLaVA: Large Language and Vision Assistant │
├────────────────────────────────────────────────┤
│ │
│ 图像输入 文本输入 │
│ ↓ ↓ │
│ ViT-L/14 Tokenizer │
│ ↓ ↓ │
│ 图像features 文本tokens │
│ (256, 1024) (L, 4096) │
│ ↓ ↓ │
│ Linear投影 │ │
│ (256, 4096) │ │
│ ↓ ↓ │
│ └────── Concat ────┘ │
│ ↓ │
│ ┌────────────────────┐ │
│ │ LLaMA-7B/13B │ │
│ │ (统一Transformer) │ │
│ └────────────────────┘ │
│ ↓ │
│ 文本输出 │
└────────────────────────────────────────────────┘
关键代码实现
class LLaVA(nn.Module):
def __init__(self):
# 1. 视觉编码器(冻结预训练ViT)
self.vision_encoder = CLIPVisionModel.from_pretrained("openai/clip-vit-large-patch14")
# 2. 语言模型(LLaMA)
self.language_model = LlamaForCausalLM.from_pretrained("meta-llama/Llama-2-7b")
# 3. 唯一新增:视觉投影层(将ViT特征投影到LLaMA空间)
self.vision_projection = nn.Linear(1024, 4096) # ViT维度 → LLaMA维度
def forward(self, images, input_text):
# 1. 编码图像
vision_features = self.vision_encoder(images) # (B, 256, 1024)
vision_tokens = self.vision_projection(vision_features) # (B, 256, 4096)
# 2. Tokenize文本
text_tokens = self.tokenizer(input_text) # (B, L)
text_embeddings = self.language_model.get_input_embeddings()(text_tokens) # (B, L, 4096)
# 3. 拼接视觉和文本tokens
# 格式:[图像tokens] [文本tokens]
inputs_embeds = torch.cat([vision_tokens, text_embeddings], dim=1) # (B, 256+L, 4096)
# 4. LLaMA统一处理
outputs = self.language_model(inputs_embeds=inputs_embeds)
return outputs
为什么这么简单就能工作?
关键1:LLaMA本身就是强大的序列建模器
- 训练过万亿tokens
- 可以处理任意长度的序列
- 只要输入是"tokens",就能处理
关键2:视觉投影层是桥梁
- 将ViT的1024维特征投影到LLaMA的4096维空间
- 相当于"翻译":视觉语言 → LLaMA语言
关键3:指令微调数据
- 让LLaMA学会"理解"前面256个tokens是图像
- 通过大量图文问答数据训练
3.3 训练策略:视觉指令微调
训练数据构造
# 构造指令格式的训练数据
def create_instruction_data(image, question, answer):
"""
输入:
- image: 图像
- question: "这张图片中有什么?"
- answer: "一只猫在沙发上"
输出:
- 统一格式的指令数据
"""
# 格式1:问答
prompt = f"USER: <image>\n{question}\nASSISTANT: {answer}"
# 格式2:描述
prompt = f"USER: <image>\n请描述这张图片。\nASSISTANT: {answer}"
# 格式3:推理
prompt = f"USER: <image>\n根据图片,回答:{question}\nASSISTANT: {answer}"
return prompt
# 训练时
image_tokens = vision_projection(vision_encoder(image)) # (256, 4096)
text_tokens = tokenizer(prompt) # (L, 4096)
inputs = concat([image_tokens, text_tokens]) # (256+L, 4096)
outputs = language_model(inputs)
# 损失:只计算ASSISTANT回答部分
loss = cross_entropy(outputs[answer_start:], targets)
训练阶段
Stage 1: 特征对齐(~100K图文对)
冻结:ViT + LLaMA
训练:视觉投影层
目标:让视觉特征对齐到LLaMA的token空间
Stage 2: 指令微调(~665K 多模态指令数据)
冻结:ViT
训练:LLaMA + 视觉投影层
数据:
- 对话:150K(LLaVA-Instruct-150K)
- VQA:80K
- 描述:400K
- 推理:35K
目标:让LLaMA学会理解视觉输入并回答问题
3.4 关键创新:GPT-4辅助数据生成
问题:如何获得高质量的多模态指令数据?
传统方法:人工标注
成本:每条数据 $1-5
质量:不稳定
规模:难以扩展到百万级
LLaVA的方案:GPT-4辅助生成
成本:API调用,约 $0.01/条
质量:接近人类水平
规模:可以生成百万级数据
数据生成流程
# 1. 输入:图像 + COCO标注(简单描述)
image_caption = "A cat sitting on a couch"
# 2. 让GPT-4生成多样化的问答对
prompt = f"""
给定图像描述:{image_caption}
请生成3种类型的问答对:
1. 对话型:自然的多轮对话
2. 详细描述:要求详细描述图片内容
3. 复杂推理:需要推理才能回答的问题
格式:
Q: [问题]
A: [答案]
"""
gpt4_output = openai.ChatCompletion.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}]
)
# 3. 得到高质量指令数据
# 输出示例:
# Q: 这张图片中的猫看起来怎么样?
# A: 猫看起来很放松,舒适地坐在沙发上,可能在休息或观察周围。
# Q: 为什么猫会选择坐在沙发上?
# A: 猫通常喜欢柔软舒适的地方休息。沙发提供了一个温暖、舒适的环境...
数据质量对比
| 数据来源 | 质量 | 成本 | 规模 | 多样性 |
|---|---|---|---|---|
| 人工标注 | ⭐⭐⭐⭐⭐ | 很高 | 小 | 中 |
| GPT-4生成 | ⭐⭐⭐⭐ | 低 | 大 | 高 |
| 现有数据集 | ⭐⭐⭐ | 免费 | 中 | 低 |