Skip to content

多模态模型原理 — 从 CLIP 到原生多模态 ​

#多模态 · #CLIP · #LLaVA · #视觉语言 · #Vision-Language · #对比学习

多模态模型让 AI 不仅能读文字,还能"看"图像、"听"声音。本文从 CLIP 的对比学习原理出发,逐层深入到 LLaVA 和原生多模态架构。


为什么需要多模态? ​

纯文本 LLM 的根本局限:世界不只有文字。

mermaid
graph LR
    subgraph "纯文本模型"
        T1["用户: 这张图里有什么?"] --> LLM1["LLM"]
        LLM1 --> ANS1["❌ 无法处理图像"]
    end

    subgraph "多模态模型"
        T2["用户: 这张图里有什么?"] --> MM["多模态模型"]
        IMG["📷 图像"] --> MM
        MM --> ANS2["✅ 图中有一只在草地上奔跑的金毛犬"]
    end

    style ANS1 fill:#e74c3c,color:#fff
    style ANS2 fill:#2ecc71,color:#fff
模态应用场景挑战
文本对话、代码、翻译已有成熟方案
图像OCR、医学影像、自动驾驶像素 → 语义的鸿沟
音频语音助手、会议纪要时序 + 频域双重表示
视频监控分析、内容审核时空双重维度
3D/点云机器人、AR/VR稀疏、不规则结构

CLIP — 多模态革命的起点 ​

核心思想:对比学习桥接文本与图像 ​

CLIP (Contrastive Language-Image Pre-training, OpenAI 2021) 的核心洞察:让图像和文本在同一个向量空间中对齐。一张狗的图片,应该和文字"一只狗"的向量距离很近,和文字"一辆汽车"距离很远。

mermaid
graph TD
    subgraph "CLIP 训练流程"
        IMG_B["图像批次<br/>N 张图片"] --> IMG_ENC["图像编码器<br/>ViT / ResNet"]
        IMG_ENC --> IMG_EMB["图像嵌入 I<br/>(N × d)"]

        TXT_B["文本批次<br/>N 条描述"] --> TXT_ENC["文本编码器<br/>Transformer"]
        TXT_ENC --> TXT_EMB["文本嵌入 T<br/>(N × d)"]

        IMG_EMB --> SIM["相似度矩阵<br/>S = I × Tᵀ<br/>(N × N)"]
        TXT_EMB --> SIM

        SIM --> LOSS["对比损失<br/>对角线 = 正样本<br/>其余 = 负样本"]
    end

    style LOSS fill:#e74c3c,color:#fff
    style IMG_ENC fill:#3498db,color:#fff
    style TXT_ENC fill:#2ecc71,color:#fff

数学公式与直觉 ​

CLIP 使用对称交叉熵损失,同时优化图像→文本和文本→图像两个方向:

LCLIP=12(LI→T+LT→I)

其中每个方向的损失为:

LI→T=−1N∑i=1Nlog⁡exp⁡(Sii/τ)∑j=1Nexp⁡(Sij/τ)
符号含义直觉
Sij第 i 张图与第 j 条文字的余弦相似度配对程度
Sii对角线元素:正确的图文对正样本
τ温度参数(可学习)控制分布的"锐度"
exp⁡(Sii/τ)正确配对的得分越高越好
∑jexp⁡(Sij/τ)所有配对的得分之和分母,归一化

一句话理解:对每一张图片,模型要在 N 条文字描述中找到它的正确配对——相当于在 N 个选项中做单选题。N 越大训练越难,但最终模型越好。

python
"""
CLIP 对比损失的手工实现
"""
import torch
import torch.nn.functional as F

def clip_loss(image_embeddings: torch.Tensor,
              text_embeddings: torch.Tensor,
              temperature: float = 0.07) -> torch.Tensor:
    """
    Args:
        image_embeddings: (N, d) L2 归一化后的图像嵌入
        text_embeddings:  (N, d) L2 归一化后的文本嵌入
        temperature: 温度参数
    Returns:
        loss: 标量损失
    """
    # 计算相似度矩阵: (N, N)
    logits = (image_embeddings @ text_embeddings.T) / temperature

    # 对角线是正样本: 第 i 张图对应第 i 条文字
    labels = torch.arange(len(image_embeddings),
                          device=image_embeddings.device)

    # 对称损失: 图像找文字 + 文字找图像
    loss_i2t = F.cross_entropy(logits, labels)       # 图像→文本
    loss_t2i = F.cross_entropy(logits.T, labels)     # 文本→图像

    return (loss_i2t + loss_t2i) / 2


# 数值示例: 4 对图文
N, d = 4, 512
torch.manual_seed(42)

img_emb = F.normalize(torch.randn(N, d), dim=-1)   # 随机图像嵌入
txt_emb = F.normalize(torch.randn(N, d), dim=-1)   # 随机文本嵌入

loss = clip_loss(img_emb, txt_emb)
print(f"CLIP Loss: {loss:.4f}")

# 理想情况: 对角线相似度高,其余低
sim = (img_emb @ txt_emb.T).detach()
print("\n相似度矩阵:")
print(f"   {'T0':>6}  {'T1':>6}  {'T2':>6}  {'T3':>6}")
for i in range(N):
    vals = "  ".join(f"{sim[i,j]:6.3f}" for j in range(N))
    print(f"I{i} {vals}")

CLIP 的训练规模 ​

维度规模说明
训练数据4 亿对图文从互联网抓取,远超人工标注
图像编码器ViT-L/14 或 ResNet-50x64Visual Transformer 效果最佳
文本编码器12 层 Transformer63M 参数
Batch Size32,768相当于每步做 32768 选 1
训练时间约 2 周,256 GPU—

LLaVA — 面向对话的多模态 LLM ​

CLIP 的局限性 ​

CLIP 只能判断"图片和文字是否匹配",不能描述图片内容,也不能回答关于图片的问题。LLaVA 将 CLIP 的视觉理解能力接入 LLM,使模型能像聊天一样理解图像。

mermaid
graph TD
    subgraph "LLaVA 架构"
        IMG["输入图像<br/>224×224"] --> VIT["CLIP ViT-L<br/>视觉编码器<br/>(冻结)"]
        VIT --> PROJ["投影层 W<br/>d_visual → d_llm"]
        PROJ --> LLM["LLM (Vicuna/LLaMA)<br/>处理图文混合序列"]
        TXT["文本指令<br/>'描述这张图'"] --> LLM
        LLM --> OUT["生成的回答<br/>'图中有一只在草地上...'"]
    end

    style VIT fill:#3498db,color:#fff
    style PROJ fill:#e74c3c,color:#fff
    style LLM fill:#2ecc71,color:#fff

两阶段训练 ​

阶段训练内容数据目的
阶段1: 特征对齐只训练投影层 W~558K 图文描述对让视觉特征"说 LLM 的语言"
阶段2: 指令微调训练投影层 + LLM~158K 多模态指令学会看图问答/推理
python
"""
LLaVA 的核心组件: 视觉投影 + 多模态序列拼接
"""
import torch
import torch.nn as nn

class LLaVAProjector(nn.Module):
    """将视觉特征投影到 LLM 的 embedding 空间"""

    def __init__(self, d_visual: int = 1024, d_llm: int = 4096):
        super().__init__()
        self.proj = nn.Sequential(
            nn.Linear(d_visual, d_llm),
            nn.GELU(),
            nn.Linear(d_llm, d_llm),
        )

    def forward(self, visual_features: torch.Tensor) -> torch.Tensor:
        """visual_features: (batch, num_patches, d_visual) → (batch, num_patches, d_llm)"""
        return self.proj(visual_features)


def build_multimodal_input(
    image: torch.Tensor,
    prompt: str,
    vision_encoder,
    projector,
    tokenizer,
    max_length: int = 2048,
) -> dict:
    """
    构建多模态输入: 图像 token + 文本 token

    序列结构: [IMG_START] [图像特征1] ... [图像特征N] [IMG_END] [文本 tokens]
    """
    # 1. 提取视觉特征
    with torch.no_grad():
        visual_features = vision_encoder(image)  # (B, num_patches, d_visual)

    # 2. 投影到 LLM 空间
    projected = projector(visual_features)  # (B, num_patches, d_llm)

    # 3. 构建文本序列
    # 格式: "<image>请描述这张图片</image>"
    multimodal_prompt = f"<image>\n{prompt}\n</image>"
    text_ids = tokenizer.encode(multimodal_prompt)

    # 4. 插入图像 token 占位符
    # 实际推理时,在 Embedding 层将占位符替换为 projected features
    img_token_id = tokenizer.encode("<image>")[0]

    return {
        "visual_features": projected,
        "text_ids": text_ids,
        "img_token_positions": [i for i, tid in enumerate(text_ids)
                                if tid == img_token_id],
    }

LLaVA 的演进 ​

版本关键改进亮点
LLaVA 1.0CLIP ViT-L + Vicuna-13B首个开源多模态对话模型
LLaVA 1.5升级 ViT、MLP 投影、学术数据超越多数闭源模型
LLaVA 1.6动态高分辨率、AnyRes支持任意分辨率输入
LLaVA-NeXT更强的 LLM + 数据混合接近 GPT-4V 水平

原生多模态 vs 拼接式多模态 ​

mermaid
graph TD
    subgraph "拼接式 (CLIP → LLM)"
        A1["图像"] --> VENC["视觉编码器"]
        VENC --> ADPT["适配层"]
        A2["文本"] --> LLM_A["LLM"]
        ADPT --> LLM_A
        LLM_A --> OUT_A["输出"]
    end

    subgraph "原生多模态 (GPT-5.6 / Claude Fable)"
        B1["图像"] --> UNI["统一 Transformer<br/>图文混排输入"]
        B2["文本"] --> UNI
        B3["音频"] --> UNI
        UNI --> OUT_B["输出<br/>可生成文本/图像/音频"]
    end

    style OUT_B fill:#e74c3c,color:#fff
维度拼接式 (LLaVA 类)原生多模态 (GPT-5.6 类)
训练方式分阶段:先对齐再微调从头联合训练
视觉编码器独立预训练 (CLIP ViT)Transformer 内部统一处理
Token 化图像→特征向量→投影图像→离散 Token (类似文本)
生成能力仅输出文本可输出文本/图像/音频
跨模态推理中等强(如看图定位+描述)
部署复杂度两个模型需要协调单一模型

多模态模型的关键技术 ​

1. 图像 Tokenization ​

将图像转换为 LLM 可以处理的 Token 序列:

方案原理代表
Patch Embedding切分图像为固定 patch,线性投影ViT, LLaVA
离散 VQ-VAE编码 → 在 codebook 中查找 → 离散 IDDALL-E, GPT-5.6
Resampler可学习的查询向量从特征图中提取信息Flamingo, Qwen-VL
SigLIP + MLP更强的对比学习视觉编码 + MLP 投影LLaVA 1.5+

2. 高分辨率处理 ​

python
"""
LLaVA 1.6 的 AnyRes: 动态切分高分辨率图像
"""

def anyres_split(image, base_size=336, max_tiles=4):
    """
    将大图切分为多个 base_size × base_size 的 tile

    例如 1024×768 的图:
    - 横向: ceil(1024/336) = 4 tiles
    - 纵向: ceil(768/336) = 3 tiles
    - 最多保留 max_tiles 个 tile
    """
    h, w = image.shape[-2:]

    # 计算切分数量
    num_h = min((h + base_size - 1) // base_size, max_tiles)
    num_w = min((w + base_size - 1) // base_size, max_tiles)

    tiles = []
    for i in range(num_h):
        for j in range(num_w):
            y1 = i * base_size
            x1 = j * base_size
            tile = image[:, :, y1:y1+base_size, x1:x1+base_size]
            tiles.append(tile)

    # 同时保留缩略图(原图 resize 到 base_size)
    from torch.nn.functional import interpolate
    thumbnail = interpolate(image, size=(base_size, base_size),
                            mode='bilinear', align_corners=False)

    return thumbnail, tiles

3. 跨模态注意力模式 ​

模式结构适用
Cross-Attention文本 Q → 图像 K,VFlamingo
Self-Attention 拼接图文序列直接拼接后做自注意力LLaVA, GPT-5.6
Q-Former可学习 Queries 压缩视觉信息BLIP-2
MLP 投影最简单的线性/nonlinear 投影LLaVA 1.0-1.5

多模态评测基准 ​

基准维度说明
MMBench综合20 个能力维度,中英文双语
MME感知 + 认知14 个子任务
SEED-Bench空间/时间推理19K 多选题
MMMU专家级多学科6 大学科,30+ 子领域
OCRBench文字识别29 个 OCR 相关任务
Video-MME视频理解多帧视频问答

参考 ​

批注模式

💬 文章评论

暂无评论,来说点什么吧 👇

编程学习笔记