跳转到内容

多模态融合模型:CLIP、BLIP-2、Flamingo 与 ImageBind

这一节讲什么?

上一节我们学习了视觉 Transformer(ViT 系列)和音频模型(Whisper),它们各自在单一模态上取得了巨大成功。但真实世界的信息从来不是单一的——我们看一张图时会同时理解图像内容和图中的文字,听一段声音时也会结合上下文语义。

多模态融合(Multimodal Fusion) 的核心目标是:让模型能够同时理解和关联来自不同感官通道(视觉、文本、音频等)的信息

这一节我们将学习:

  1. CLIP 深入解析——对比学习的图文对齐机制
  2. BLIP-2 / SigLIP——更高效的 CLIP 变体
  3. Flamingo / Otter——交错注意力实现真正的"边看边说"
  4. ImageBind / EVA-02——统一的 8 种模态对齐
  5. 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通用多模态搜索

核心要点

  1. 多模态融合有三个层次——特征级(简单但受限)、表示级(CLIP 主流)、交互式(Flamingo 最强但最复杂)
  2. CLIP 是多模态时代的基石——它证明了对比学习可以让模型学会跨模态语义对齐
  3. Flamingo 代表了新范式——让语言模型能"看着"图像生成回答,这是 LLaVA/IDEFICS/GPT-4V 的基础思想
  4. 实际项目中,CLIP + 重排序就是最实用的多模态方案——简单、高效、效果好

下一节我们将学习 视频理解模型——VideoLLaVA、AViD、InternVideo 等。

基于 MIT 许可发布