主题
多模态融合模型:CLIP、BLIP-2、Flamingo 与 ImageBind
这一节讲什么?
上一节我们学习了视觉 Transformer(ViT 系列)和音频模型(Whisper),它们各自在单一模态上取得了巨大成功。但真实世界的信息从来不是单一的——我们看一张图时会同时理解图像内容和图中的文字,听一段声音时也会结合上下文语义。
多模态融合(Multimodal Fusion) 的核心目标是:让模型能够同时理解和关联来自不同感官通道(视觉、文本、音频等)的信息。
这一节我们将学习:
- CLIP 深入解析——对比学习的图文对齐机制
- BLIP-2 / SigLIP——更高效的 CLIP 变体
- Flamingo / Otter——交错注意力实现真正的"边看边说"
- ImageBind / EVA-02——统一的 8 种模态对齐
- HF 实战——构建简单的多模态检索系统
一、为什么需要多模态融合?
1.1 单模态模型的局限
python
def single_modal_limitations():
"""单模态模型的局限性"""
limitations = [
{
"场景": "给一张图片写文案",
"单模态方案": "用 CLIP 做图文匹配",
"问题": "只能从已有选项中选, 不能自由生成创意性文案",
"需要": "能理解图片细节 + 自由生成连贯文本的模型",
},
{
"场景": "视频理解",
"单模态方案": "ViT 处理视频帧 + 文本分类器",
"问题": "帧级特征丢失时间动态信息; 无法建模跨帧关系",
"需要": "同时处理视觉流和语义流的统一架构",
},
{
"场景": "机器人操作",
"单模态方案": "视觉检测目标位置 → 控制器",
"问题": "无法理解人类指令 '把红色的杯子移到左边'",
"需要": "视觉感知 + 语言指令理解的端到端系统",
},
]
print("=" * 70)
print("单模态模型的局限性")
print("=" * 70)
for l in limitations:
print(f"\n📌 场景: {l['场景']}")
print(f" 方案: {l['单模态方案']}")
print(f" ❌ 问题: {l['问题']}")
print(f" ✅ 需要: {l['需要']}")
single_modal_limitations()1.2 多模态的三个层次
python
def multimodal_levels():
"""多模态融合的三个层次"""
levels = """
╔══════════════════════════════════════════════════════════╗
║ 多模态融合的三个层次 ║
╠════════════════════════════════════════════════════════╣
║ ║
║ Level 1: 特征级融合 (Feature-Level Fusion) ║
║ ────────────────────────────────────────────────────── ║
║ • 方法: 拼接 (Concat) / 门控 (Gated) / 注意力 (Cross-Attn) ║
║ • 各模态编码器独立提取特征, 在某个点合并 ║
║ • 代表: Early Fusion / VisualBERT ║
║ • 优点: 简单高效; 可以利用预训练的单模态 Encoder ║
║ • 缺点: 信息在早期可能丢失; 模态间交互有限 ║
║ ║
║ ══════════════════════════════════════════════════════════ ║
║ Level 2: 表示级融合 (Representation-Level Fusion) ║
║ ────────────────────────────────────────────────────── ║
║ • 方法: 对比损失 (Contrastive) / 协同训练 ║
║ • 让不同模态的特征表示在同一个嵌入空间中对齐 ║
║ • 代表: CLIP / ALIGN / SALIGN ║
║ • 优点: 学到了通用的跨模态语义表示空间 ║
║ • 缺点: 需要大量配对数据训练; 不支持生成式交互 ║
║ ║
║ ══════════════════════════════════════════════════════════ ║
║ Level 3: 生成式融合 (Generative/Interaction-Level Fusion) ║
║ ────────────────────────────────────────────────────── ║
# • 方法: 交错注意力 (Interleaved Attention) ║
# • 不同模态可以互相"看到"对方的内部表示 ║
# • 代表: Flamingo / Otter / BLIP-2 / LLaVA ║
# • 优点: 最灵活; 支持自由生成; 真正的多轮对话 ║
# • 缺点: 训练复杂; 推理开销大 ║
║ ║
╚══════════════════════════════════════════════════════════╝
"""
print(levels)
multimodal_levels()二、CLIP:对比学习的图文桥梁
2.1 CLIP 训练流程详解
python
def clip_training_deep_dive():
"""CLIP 训练流程深度解析"""
training = """
┌───────────────────────────────────────────────────────────────┐
│ CLIP Training Pipeline │
├───────────────────────────────────────────────────────────────┤
│ │
│ 数据准备阶段: │
│ │
│ 从互联网收集 (image, text) pair: │
│ ├── ("a photo of a dog", 🐱_001.jpg) ← 正样本 (positive) │
│ ├── ("a photo of a cat", 🐱_002.jpg) ← 负样本 (negative) │
│ ├── ("beautiful sunset", 🌅_003.jpg) ← 正样本 (positive) │
│ ... │
│ 总计: ~4 亿对 (image, text) │
│ │
│ 关键设计: │
│ • 每个 batch 中, 一个 image 配对一个正确的 text │
│ • 同一个 batch 内的所有其他 (image, text) 都是负样本 │
│ • 这叫做 In-Batch Negative (IBN) │
│ │
│ 双编码器结构: │
│ ┌─────────────────────┐ ┌──────────────────────┐ │
│ │ Image Encoder │ │ Text Encoder │ │
│ │ (ViT-based) │ │ (GPT-2 based) │ │
│ │ 输出: img_embed │ │ 输出: text_embed │ │
│ │ (batch, D) │ │ (batch, D) │ │
│ └─────────────────────┘ └──────────────────────┘ │
│ │
│ 训练目标: │
│ 对于每个 (image_i, text_j) pair: │
│ │
│ if i == j (正样本): │
│ 最大化 cos(img_i, text_j) │
│ → 把 image 和 text 的 embedding 尽量拉近 │
│ │
│ if i ≠ j (负样本): │
│ 最小化 cos(img_i, text_j) │
│ → 把 image 和 text 的 embedding 尽量推开 │
│ │
│ Loss = InfoNCE Loss: │
│ L = -log[exp(sim(i,pos) / Σ exp(sim(i,j)))] │
│ │
│ 温度参数 T 控制 distribution 的 "锐度": │
│ • T 大 → 分布平 (更多探索) │
│ • T 小 → 分布尖锐 (更确定) │
│ 通常使用 T 可学习的温度参数 (per-head learnable) │
│ │
│ 结果: │
│ Image Encoder 和 Text Encoder 被映射到同一个 D 维向量空间 │
│ → 可以直接计算任意 (image, text) 的余弦相似度! │
│ → 实现零样本/少样本的跨模态检索和理解 │
└───────────────────────────────────────────────────────────────┘
"""
print(training)
clip_training_deep_dive()2.2 CLIP 的两种使用模式
python
def clip_usage_modes():
"""CLIP 的两种主要使用模式"""
modes = """
Mode 1: Zero-Shot Classification (零样本分类)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
用法:
from transformers import pipeline
classifier = pipeline(
model="openai/clip-vit-large-patch14",
model="laion/CLIP-ViT-L-14-DataAug.xt",
)
results = classifier(
image="cat.jpg",
candidate_labels=["dog", "cat", "bird", "car"],
)
输出:
{
"label": "cat",
"score": 0.92,
}
特点:
• 无需任何微调或额外训练即可使用
• 支持自定义标签 (可描述性标签!)
• 适合快速原型验证
Mode 2: Feature Extraction (特征提取 + 相似度计算)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
用法:
from transformers import CLIPModel, AutoProcessor
model = CLIPModel.from_pretrained("openai/clip-vit-base")
processor = AutoProcessor.from_pretrained("openai/clip-vit-base")
# 编码图像
img_features = model.get_image_features(processor(images))
# shape: (num_images, hidden_size)
# 编码文本
txt_features = model.get_text_features(processor(texts))
# shape: (num_texts, hidden_size)
# 计算相似度
similarity = (img_features @ txt_features.T).softmax(dim=-1)
输出:
similarity[0][0] = 0.89 # "cat.jpg" ↔ "a cute cat"
similarity[0][1] = 0.12 # "cat.jpg" ↔ "a fast car"
特点:
• 得到细粒度的相似度分数 (不只是 top-1)
• 可以用于排序、聚类、检索
• 是 RAG (检索增强生成) 的核心技术组件
"""
print(modes)
clip_usage_modes()三、Flamingo 与新一代交互式多模态
3.1 为什么 CLIP 不够?
CLIP 虽然强大,但它有一个根本限制:编码器是独立的,只在最后做一次特征对齐。这意味着:
- 图像编码器看不到文本解码器生成了什么
- 文本编码器也不知道图像编码器"看"到了什么细节
- 两者之间没有真正的"对话"
Flamingo(2023)通过交错注意力(Interleaved Attention) 解决了这个问题:
python
def flamingo_vs_clip():
"""Flamingo vs CLIP 的关键区别"""
comparison = """
╔══════════════════════════════════════════════════════════╗
║ Flamingo vs CLIP: 架构差异 ║
╠══════════════════════════════════════════════════════════╣
║ ║
║ CLIP (表示级融合): ║
║ ║
║ Image ──→ [ViT Encoder] ──→ img_hidden (D维) ──┐──→ 文本输出 ║
║ ↑ ║
║ Text ──→ [Text Encoder] ──→ txt_hidden (D维) ──┘→ 余弦相似度 ║
║ ║
║ 问题: ║
║ • Image Encoder 不知道 Text Decoder 生成了什么 ║
║ • 两者只有一次"碰面"(最终特征层) ║
║ • 无法支持多轮交互式对话 ║
║ ║
║ Flamingo (交互式融合): ║
║ ║
║ Image ──→ [Vision Encoder] ──┬──────────────────────┐ ║
║ ↓ Cross-Attention ↘ ║
║ Text ──→→ [Language Model] ──┤──────────────────────┤ ║
║ ↓ (每层都做!) ║
║ Language Model 输出 next token ──→ 再送回 Vision Encoder ║
║ ↓ Cross-Attention ↗ ║
║ ... (循环直到生成 <EOS>) ║
║ ║
║ 关键创新: ║
║ • 语言模型的每一层都能"看到"视觉特征的内部状态 ║
║ • 视觉编码器也能看到已生成的文本内容 ║
║ • 真正的双向信息流动! ║
║ • 支持 多轮对话 (像聊天一样!) ║
║ ║
║ 结果: ║
║ • 更好的视觉理解能力 (因为可以根据文本调整关注区域) ║
║ • 更准确的视觉问答 (可以指着图片中的具体位置回答) ║
║ • 支持复杂的推理任务 (如 OCR-free VQA) ║
╚════════════════════════════════════════════════════════════╝
"""
print(comparison)
flamingo_vs_clip()3.2 Flamingo 的核心:Gated Cross-Attention
Flamingo 的核心是一个精巧的设计——门控交叉注意力(Gated Cross-Attention):
python
def gated_cross_attention():
"""Flamingo 的 Gated Cross-Attention 详解"""
code = """
import torch
import torch.nn as nn
class GatedCrossAttention(nn.Module):
"""
Flamingo 的 Gated Cross-Attention
核心思想: 不是所有时刻都需要"看"图像!
只在语言模型需要视觉信息时才激活视觉连接。
这就像人说话时不一定每句话都要盯着图片看,
只有提到"图中左下角的红圈"时才去看一眼。
"""
def __init__(self, vision_dim, lang_dim, num_heads=8, cross_attn_every=2):
super().__init__()
self.lang_dim = lang_dim
self.vision_dim = vision_dim
self.num_heads = num_heads
self.head_dim = lang_dim // num_heads
# 语言模型的 Query 投影 (始终存在)
self.lang_q_proj = nn.Linear(lang_dim, lang_dim)
# 视觉的 Key/Value 投影 (按需激活)
self.vision_kv_proj = nn.Linear(vision_dim, lang_dim * 2)
# 门控信号: 决定当前是否需要"看"图像
self.gate = nn.Sigmoid(lang_dim) # (D,) → (D,)
self.cross_attn_every = cross_attn_every
self.scale = lang_dim ** -0.5
self.norm_k = nn.LayerNorm(lang_dim)
self.norm_v = nn.LayerNorm(lang_dim)
self.proj_out = nn.Linear(lang_dim, lang_dim)
def forward(self, lang_hidden, visual_features, attention_mask=None):
"""
Args:
lang_hidden: (B, T, D) 语言模型的隐藏状态
visual_features: (B, N, D_v) 视觉特征
attention_mask: (B, N, N) 视觉 padding mask
"""
B, T, D = lang_hidden.shape
batch_size = B
# Step 1: 语言 Query (始终计算)
q = self.lang_q_proj(lang_hidden) # (B, T, D)
# Step 2: 判断是否需要交叉注意力
gate = self.gate(lang_hidden[:, -1, :]) # (B, D)
output = lang_hidden.clone()
for i in range(T):
if (i + 1) % self.cross_attn_every == 0 and gate.mean() > 0.5:
# 需要看图像了!
k, v = self.vision_kv_proj(visual_features).chunk(2, -1) # (B*2, D)
k = self.norm_k(k).view(B * 2, -1, self.num_heads, self.head_dim).transpose(1, 2)
v = self.norm_v(v).view(B * 2, -1, self.num_heads, self.head_dim)
# Scaled Dot-Product Attention
attn = torch.matmul(q[:, i:i+1, :], k) * self.scale
if attention_mask is not None:
attn = attn.masked_fill(attention_mask.bool(), float('-inf'))
attn = torch.softmax(attn, dim=-1)
# 加权求和
attended = torch.matmul(attn, v) # (B*H, 1, D)
attended = attended.squeeze(2) # (B*H, D)
# 残差连接 + 门控
output[:, i+1, :] = output[:, i+1, :] + gate[:, i:i+1, :] * attended
else:
# 不看图像, 直接用语言自身的 FFN 继续
output[:, i+1, :] = output[:, i+1, :] + gate[:, i:i+1, :] * self.proj_out(output[:, i, :])
return output
# 测试
gca = GatedCrossAttention(vision_dim=768, lang_dim=768, num_heads=8, cross_attn_every=2)
lang_h = torch.randn(2, 20, 768)
vis_f = torch.randn(2, 196, 768)
out = gca(lang_h, vis_f)
print(f"输入: lang={lang_h.shape}, vis={vis_f.shape}")
print(f"输出: {out.shape}")
# 应该是 (2, 21, 768) — 每个时间步都可能被视觉信息影响
gated_cross_attention()四、ImageBind:统一 8 种模态
4.1 ImageBind 的野心
ImageBind (2023) 来自 Intel Labs,它的目标是创建一个真正通用的多模态嵌入空间,不仅限于视觉和文本:
python
def imagebind_coverage():
"""ImageBind 支持的模态类型"""
modalities = """
╔══════════════════════════════════════════════════════════╗
║ ImageBind: 统一 8 种模态 ║
╠════════════════════════════════════════════════════════╣
║ ║
║ 🖼️ Image (RGB) → ViT / SigLIP / DINOv2 ║
║ 🖼️ Image (Thermal) → ViT-H (热成像) ║
║ 🎵 Audio (Waveform) → Wav2Vec2 / HuBERT ║
║ 📄 Text (Natural Lang) → RoBERTa / XLM-R / Qwen ║
║ 🔢 Depth (Depth Map) → DINOv2 / Depth Anything ║
║ 🎨 Video (Video Frame) ├── VideoMAE / InternVideo ║
║ 📍 Point Cloud (3D) └── Point-BERT / ULIP ║
║ 🧊 Table (Table Struct.) └── Table-BERT / TAPEx ║
║ ║
║ 核心方法: ║
║ • InfoNCE Contrastive Learning (类似 CLIP) ║
║ • 所有模态共享同一个 Embedding Space (D=768 维) ║
║ • 训练数据: 70 亿 (image, text) pairs + 其他模态配对 ║
║ ║
║ 应用场景: ║
║ • 通用多模态搜索引擎 (以图搜文, 以文搜图...) ║
║ • 开放词汇表扩展 (图片中识别新词) ║
║ • 多模态 RAG (混合多种证据源) ║
║ • 机器人操作 (视觉指令 + 语言规划) ║
╚══════════════════════════════════════════════════════════╝
"""
print(modalities)
imagebind_coverage()五、HF 实战:构建多模态检索系统
python
def multimodal_retrieval_demo():
"""基于 CLIP/SigLIP 的多模态检索系统实战"""
try:
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
CLIPModel, CLIPTokenizer,
)
from PIL import Image
import os
print("=" * 60)
print("多模态检索系统实战")
print("=" * 60)
# ========== Step 1: 加载模型 ==========
clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
clip_tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-base-patch32")
print("\n[1] CLIP 模型加载完成")
# ========== Step 2: 准备图像库 ==========
image_dir = "./demo_images"
os.makedirs(image_dir, exist_ok=True)
# 创建示例图像 (实际应用中应该有真实的图像库)
demo_images = ["cat.jpg", "dog.jpg", "sunset.jpg", "city.jpg"]
images = []
for fname in demo_images:
fpath = os.path.join(image_dir, fname)
if not os.path.exists(fpath):
# 创建虚拟图像
from PIL import Image as PILImage
img = PILImage.new('RGB', (256, 256), color=(255, 200, 100))
img.save(fpath)
images.append(fpath)
print(f"\n[2] 准备了 {len(images)} 张示例图像")
# ========== Step 3: 定义查询函数 ==========
def search(query_text, image_paths, top_k=3):
"""执行图文相似度检索"""
# 编码文本
text_inputs = clip_tokenizer(
[query_text],
padding=True, truncation=True, return_tensors="pt"
)
with torch.no_grad():
text_features = clip_model.get_text_features(text_inputs)
results = []
for img_path in image_paths[:top_k]: # 只取 top-k 张
image = Image.open(img_path).convert("RGB")
image_inputs = clip_processor(
images=image,
return_tensors="pt",
)
with torch.no_grad():
image_features = clip_model.get_image_features(image_inputs)
# 计算余弦相似度
similarity = (text_features @ image_features.T).softmax(dim=-1)
score = similarity[0].item()
results.append({
"image_path": img_path,
"score": round(score, 4),
"similarity": f"{score:.4f}",
})
# 按相似度降序排列
results.sort(key=lambda x: x["score"], reverse=True)
return results
# ========== Step 4: 执行检索 ==========
query = "一只金毛狗在草地上奔跑"
print(f"\n[3] 查询: '{query}'")
results = search(query, images, top_k=3)
print(f"\n{'排名':<6} {'文件名':<25} {'相似度':<12} {'置信度':<10}")
print("-" * 55)
for rank, r in enumerate(results, 1):
fname = os.path.basename(r["image_path"])
print(f"{rank:<6}{fname:<25}{r['similarity']:<12}{r['confidence']}")
except ImportError:
print("\n⚠️ 请安装依赖: pip install transformers Pillow torch")
multimodal_retrieval_demo()六、本章小结
这一节我们学习了多模态融合的核心技术:
| 模型 | 融合方式 | 核心创新 | 适用场景 |
|---|---|---|---|
| CLIP | 表示级 (InfoNCE) | 对齐图文到同一空间 | 零样本分类/检索 |
| SigLIP | 表示级 (SigLIP-Loss) | 更高效的 CLIP 变体 | 高效检索 |
| BLIP-2 | 表示级 + 轻量 | 更小更强的 CLIP | 移动端部署 |
| Flamingo | 交互式 (Gated X-Attn) | 交错注意力, 真正双向对话 | 视觉问答/VQA |
| Otter | 交互式 (Per-Resampler) | Per-Resampler 注意力 | 长上下文理解 |
| ImageBind | 统一 8 模态 | 通用 Embedding Space | 通用多模态搜索 |
核心要点:
- 多模态融合有三个层次——特征级(简单但受限)、表示级(CLIP 主流)、交互式(Flamingo 最强但最复杂)
- CLIP 是多模态时代的基石——它证明了对比学习可以让模型学会跨模态语义对齐
- Flamingo 代表了新范式——让语言模型能"看着"图像生成回答,这是 LLaVA/IDEFICS/GPT-4V 的基础思想
- 实际项目中,CLIP + 重排序就是最实用的多模态方案——简单、高效、效果好
下一节我们将学习 视频理解模型——VideoLLaVA、AViD、InternVideo 等。