Files
maths-cs-ai-compendium-zh/chapter 10: multimodal learning/02. vision language models.md
T
flykhan 2536c937e3 feat: 完整中文翻译 maths-cs-ai-compendium(数学·计算机科学·AI 知识大全)
翻译自英文原版 maths-cs-ai-compendium,共 20 章全部完成。

第01章 向量 | 第02章 矩阵 | 第03章 微积分
第04章 统计学 | 第05章 概率论 | 第06章 机器学习
第07章 计算语言学 | 第08章 计算机视觉 | 第09章 音频与语音
第10章 多模态学习 | 第11章 自主系统 | 第12章 图神经网络
第13章 计算与操作系统 | 第14章 数据结构与算法
第15章 生产级软件工程 | 第16章 SIMD与GPU编程
第17章 AI推理 | 第18章 ML系统设计
第19章 应用人工智能 | 第20章 前沿人工智能

翻译说明:
- 所有数学公式 $...$ / $$...$$、代码块、图片引用完整保留
- mkdocs.yml 配置中文导航 + language: zh
- README.md 已翻译为中文(兼 docs/index.md)
- docs/ 目录包含指向各章文件的 symlink
- 约 29,000 行中文内容,排除 .cache/ 构建缓存
2026-05-03 10:23:20 +08:00

389 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 视觉语言模型
*视觉语言模型共同理解图像和文本,实现视觉问答、图像描述和视觉推理。本文件涵盖 VQA、图像描述、视觉定位,以及 VisualBERT、BLIP、LLaVA、Flamingo、PaLI 和 Qwen-VL 等将视觉编码器与大型语言模型融合的架构。*
- 想象一位博物馆导览员,他能看着一幅画并清晰描述画中的一切:有哪些物体、讲述了什么故事、传达了怎样的情感,还能回答参观者的任何问题。**视觉语言模型(VLM)** 就是计算领域的等价物——一个能同时理解图像和文本的系统,能够描述视觉场景、回答相关问题、执行视觉指令,甚至根据自然语言查询在图像中定位特定物体。
- VLM 位于你在第 8 章学到的视觉编码器和第 7 章的语言模型的交汇点。核心工程挑战在于桥接两个截然不同的表征世界:视觉骨干网络产生的空间化、连续的 feature map,与语言模型产生的序列化、离散的 token 嵌入。本文件中的每一种架构,本质上都是对同一个问题的不同回答:如何融合视觉和语言?
![VLM 高层次分类:双编码器、融合编码器和编码器-解码器家族及其输入与输出](../images/vlm_taxonomy.svg)
## 视觉问答
- 想象有人向你展示一张照片并问:"公园里有几只狗?"你毫不费力地解析图像、定位狗、数出数量并给出答案。**视觉问答(VQA)** 将这一过程形式化:给定一张图像 $I$ 和一个自然语言问题 $q$,预测答案 $a$。
- 该任务可以有多种定义方式。最常见的方式将 VQA 视为**开放式分类**:模型从最常见的答案构成的固定词汇表中选择(例如 VQA v2 中排名前 3,129 的答案)。另一种方式是**生成式回答**,模型生成自由形式的文本字符串——这是现代 VLM 采用的方法。
- 形式上,你需要学习一个最大化正确答案似然的函数 $f(I, q) \to a$。在分类设置中,这变为:
$$p(a \mid I, q) = \text{softmax}(W \cdot g(v, h))$$
- 其中 $v$ 是视觉特征向量(来自 CNN 或 ViT),$h$ 是问题编码(来自 LSTM 或 Transformer),$g$ 是融合函数。$g$ 的设计正是真正的架构创造力所在。
- **VQA v1**Antol 等人,2015)引入了该基准,包含来自 MS COCO 的 204,000 张图像上的 614,000 个问题。研究人员很快发现,模型可以通过利用**语言先验**达到惊人高的准确率——对"多少个"问题回答"2",对"有没有"问题回答"是",甚至不需要看图像。
- **VQA v2**Goyal 等人,2017)通过为每个问题配对不同答案的两张相似图像来解决这个问题。这迫使模型真正将其推理建立在视觉内容之上。平衡配对设置使数据集规模大约翻倍,并使纯语言捷径的效果大打折扣。
- 其他重要的 VQA 数据集包括 **GQA**Hudson & Manning2019),包含需要多步推理的组合性问题;**OK-VQA**Marino 等人,2019),需要超出图像范围的外部知识;以及 **TextVQA**(Singh 等人,2019),答案依赖于读取图像中的文字。
![VQA 流水线:图像经视觉编码器编码,问题经文本编码器编码,两者表征融合后,融合向量被分类为答案](../images/vqa_pipeline.svg)
- 早期的 VQA 模型使用简单策略:从预训练 CNN 中提取图像特征(通常是第 8 章中 ResNet 或 VGGNet 的倒数第二层),用 LSTM(第 6 章)对问题进行编码,然后将它们组合。组合函数 $g$ 演变迅速:从简单的逐元素乘法,到双线性池化,再到多模态 Tucker 分解。**双线性注意力**计算 $v^T W h$,其中 $W$ 是可学习的交互矩阵,但完整的双线性形式有 $O(d_v \times d_h)$ 个参数,规模过大。**MLB**(多模态低秩双线性池化)将其分解为两个低秩投影,使其变得可行。
- VQA 的突破是注意力机制。**堆叠注意力网络**(Yang 等人,2016)使用问题编码在空间图像区域上施加注意力,迭代式地精炼需要关注的图像部分。这个思想——让问题"关注"相关图像区域——成为了标准做法。
## 图像描述
- 想象一位朋友看着你的度假照片并叙述他们所看到的:"一只金毛猎犬在阳光明媚的沙滩上接飞盘。"**图像描述**是生成图像的自然语言描述的任务。与 VQA 不同,这里没有提问——模型必须自行决定哪些内容值得描述。
- **Show and Tell**Vinyals 等人,2015)建立了描述任务的标准编码器-解码器架构。CNN 编码器(如 Inception 或 ResNet)生成一个单一图像特征向量 $v$。该向量被用作 LSTM 解码器的初始隐藏状态,然后逐词自回归地生成描述:
$$p(w_t \mid w_{1:t-1}, I) = \text{LSTM}(w_{t-1}, h_{t-1})$$
- 整个模型通过最大化真实描述的对数似然进行端到端训练。推理时使用束搜索(第 7 章)来找到高概率的描述。
- Show and Tell 的问题在于整张图像被压缩成一个单一向量。对于复杂场景,单一向量无法捕捉所有相关细节。你会丢失空间信息——模型在生成不同词语时无法"回看"图像的特定区域。
- **Show, Attend and Tell**Xu 等人,2015)通过引入**图像区域上的注意力**解决了这个问题。模型不是将图像编码为一个向量,而是由 CNN 产生一个空间特征网格(例如来自 VGGNet 最后一个卷积层的 $14 \times 14 \times 512$)。在每个解码步骤,模型计算这些空间位置上的注意力权重,生成一个突出当前词语最相关区域的上下文向量。
- 回顾第 6 章的注意力机制:解码器隐藏状态充当查询,空间特征充当键和值,注意力权重告诉模型应该看哪里。作者提出了两种变体:**软注意力**(可微分,所有区域的加权平均)和**硬注意力**(对单个区域进行随机采样,使用 REINFORCE 训练)。
![基于注意力的描述:在每个解码步骤,模型关注图像的不同空间区域,例如在生成"狗"这个词时聚焦于狗所在区域](../images/attention_captioning.svg)
- 这些模型产生的注意力图具有显著的可解释性:生成"狗"时,注意力集中在狗的区域;生成"海滩"时,注意力转移到沙子和水面。这是注意力机制提供内置可解释性的最早令人信服的演示之一。
- **CIDEr**Vedantam 等人,2015)、**METEOR**、**BLEU** 和 **SPICE** 是标准描述评估指标。CIDEr 计算生成描述与参考描述之间的 TF-IDF 加权 n-gram 相似度,专门为描述评估设计。现代 VLM 通常在 MS COCO Captions 和 NoCaps 等描述基准上用 CIDEr 进行评估。
- 后来的描述模型引入了**自底向上注意力**(Anderson 等人,2018),其中目标检测器(Faster R-CNN,第 8 章)首先提出显著的图像区域,然后描述模型在这些区域特征而非均匀网格上施加注意力。在基于 ViT 的编码器接管之前,这是主导方法。
## 架构模式
- 每个 VLM 都必须回答一个基本设计问题:视觉和语言在哪个节点交互?答案决定了模型的架构家族。有三种主要模式,各自具有不同的权衡。
### 双编码器
- 想象两位独立工作的译者——一位读法语文件,另一位读英语文件——他们各自用一种共享的"通用语言"生成摘要。他们在翻译过程中从不交流,但他们的摘要可以直接比较。这就是**双编码器**模式。
- 视觉编码器 $f_v$ 和文本编码器 $f_t$ 独立地将各自的输入映射到一个维度为 $d$ 的共享嵌入空间。图像嵌入为 $v = f_v(I) \in \mathbb{R}^d$,文本嵌入为 $t = f_t(q) \in \mathbb{R}^d$。相似度通过点积或余弦相似度计算:$\text{sim}(I, q) = v^T t / (\|v\| \|t\|)$。
- **CLIP**Radford 等人,2021),在前一篇关于多模态表示的文件中已介绍,是典型的双编码器。它在从互联网抓取的 4 亿图像-文本对上使用对比目标函数(InfoNCE)进行训练。由于编码器相互独立,你可以预计算并缓存所有图像嵌入,使检索极其高效——搜索时只需对查询文本进行编码。
- 双编码器的缺点在于视觉和语言从未在特征层面进行交互。模型无法进行细粒度的跨模态推理:例如,它无法确定描述中的特定词是否对应图像中的特定区域。这限制了它在 VQA 或 grounded 描述等任务中的实用性。
### 融合编码器
- 现在想象两位译者共处一室,积极讨论两篇文件。他们可以指向特定段落、互相提问,并建立共同的理解。这就是**融合编码器**模式。
- 两种模态都被编码,然后通过**交叉注意力层**进行融合,其中一种模态的 token 关注另一种模态的 token。图像首先由视觉编码器处理为一系列 patch 或区域 token $V = [v_1, \ldots, v_N]$。文本被分词化为 $T = [t_1, \ldots, t_M]$。在融合层中,文本 token 通过交叉注意力关注图像 token:
$$\text{CrossAttn}(T, V) = \text{softmax}\!\left(\frac{(TW_Q)(VW_K)^T}{\sqrt{d_k}}\right)(VW_V)$$
- 这实现了细粒度的交互:每个文本 token 都可以关注其所需的特定图像区域。**VisualBERT**、**VilBERT** 和 **UNITER** 等模型使用这种模式。代价是你无法为检索预计算独立的嵌入——每个图像-文本对都需要通过融合层进行完整的前向传播。
![双编码器与融合编码器对比:双编码器计算独立嵌入和相似度得分,而融合编码器通过交叉注意力层合并模态](../images/dual_vs_fusion_encoder.svg)
### 编码器-解码器
- **编码器-解码器**模式将视觉编码器与自回归生成输出 token 的文本解码器相结合,类似于第 7 章中的 seq2seq 模型。视觉编码器产生上下文图像表征,文本解码器在生成输出文本时对其执行交叉注意力。
- 这种模式天然支持生成式任务:图像描述、自由形式答案的 VQA 以及视觉对话。**GIT**Generative Image-to-text TransformerWang 等人,2022)、**CoCa**Contrastive CaptionerYu 等人,2022)和 **PaLI** 使用这种架构。CoCa 巧妙地将双编码器和编码器-解码器模式结合起来:文本解码器的前半部分作为单模态文本编码器(用于对比学习),而后半部分对图像特征执行交叉注意力(用于生成式描述),兼得两者之优势。
- 这三种模式的选择取决于目标任务。双编码器最适合大规模检索。融合编码器最适合细粒度理解任务。编码器-解码器对于生成任务最为通用。现代最先进的 VLM 越来越多地采用编码器-解码器或仅解码器范式,将每项视觉语言任务都视为文本生成。
## Flamingo:少样本多模态学习
- 想象一位经验丰富的专家,经过多年对艺术和文学的研究,只需要看一两个例子就能优雅地描述一种全新的绘画风格。**Flamingo**Alonso 等人,2022DeepMind)基于相同原理构建:它利用强大的预训练语言模型和预训练视觉编码器,通过轻量级架构组件将其连接,实现多模态任务上的少样本学习。
- Flamingo 的设计理念保守而有效:保持预训练的视觉编码器(NFNet)和语言模型(Chinchilla)冻结,仅学习连接它们的"胶水"。这种胶水由两个组件组成:**Perceiver 重采样器**和**门控交叉注意力层**。
- **Perceiver 重采样器**将视觉编码器的变长输出(取决于图像分辨率)压缩为一组固定数量的 $N$ 个视觉 token(通常 $N = 64$)。它的工作原理是初始化一组 $N$ 个可学习的查询向量,并使用交叉注意力让这些查询关注完整的视觉编码器输出。这本质上是 Perceiver 架构(Jaegle 等人,2021)作为瓶颈的应用——无论输入图像大小如何,它都能生成紧凑的、固定大小的视觉表示。
$$z = \text{CrossAttn}(Q_{\text{learned}}, V_{\text{image}}) \in \mathbb{R}^{N \times d}$$
- **门控交叉注意力层**交错插入在冻结的语言模型层之间。在每个这样的层中,语言模型的文本 token 对 Perceiver 重采样器产生的视觉 token 执行交叉注意力。关键之处在于,每个门控交叉注意力层包含一个可学习的标量门控 $\alpha$,初始化为零,将交叉注意力输出乘以 $\alpha$ 后再加到残差流中:
$$\hat{x} = x + \alpha \cdot \text{CrossAttn}(x, z)$$
- 初始化 $\alpha = 0$ 意味着训练开始时交叉注意力不贡献任何信息,模型行为与原始的冻结语言模型完全相同。门控在训练过程中逐渐打开,平滑地整合视觉信息,同时不破坏语言模型的预训练表示。
![Flamingo 架构:冻结的视觉编码器输入 Perceiver 重采样器,生成固定长度的视觉 token,通过交错在 LM 块之间的门控交叉注意力层注入冻结的 LM](../images/flamingo_architecture.svg)
- Flamingo 原生支持**交错图像-文本序列**。你可以向它输入包含多张图像穿插文本的提示,例如:"[图像 1] 这是一只猫。[图像 2] 这是一只狗。[图像 3] 这是一个 ___。"模型将每张图像通过视觉编码器和 Perceiver 重采样器处理,得到的视觉 token 插入到文本序列中的对应位置。语言模型的因果注意力掩码确保每个文本 token 只能关注当前及之前图像的视觉 token。
- 这种交错机制实现了强大的**少样本多模态学习**。通过在上下文中提供少量图像-文本示例,Flamingo 可以在没有任何梯度更新的情况下执行新任务。在 VQAv2、OK-VQA 和描述等基准上,具有 800 亿参数的 Flamingo 实现了最先进的少样本性能,仅需 4 到 32 个示例即可匹配甚至超越经过微调的专家模型。
## LLaVA 与视觉指令微调
- 想象你有一位出色的语言专家(一个 LLM)和一位出色的艺术评论家(一个视觉编码器)。如果你能教会艺术评论家"说语言专家的语言",他们就可以无缝协作。**LLaVA**Large Language and Vision AssistantLiu 等人,2023)正是这样做的:它使用一个简单的线性层将视觉特征投影到 LLM 的 token 嵌入空间,然后在指令遵循数据上微调整个系统。
- LLaVA 的架构出奇地简单。图像由一个预训练的 CLIP ViT-L/14 视觉编码器编码为一个 patch 特征网格 $V \in \mathbb{R}^{N \times d_v}$,其中 $N = 256$ 个 patch(对于 336px 图像和 14px patch)。一个**投影层** $W$ 将这些视觉特征映射到 LLM 的嵌入维度:
$$H_v = VW, \quad W \in \mathbb{R}^{d_v \times d_{\text{LLM}}}$$
- 投影后的视觉 token $H_v$ 直接与文本 token 嵌入拼接,作为一个单一序列输入到 LLM(Vicuna,一个微调后的 LLaMA)。LLM 使用其标准因果自注意力处理它们——没有特殊的交叉注意力层,没有 perceiver,只有拼接。视觉 token 被当作恰好编码了视觉信息的文本 token 来处理。
![LLaVA 架构:CLIP ViT 将图像编码为 patch 特征,线性投影将其映射到 LLM 嵌入空间,投影后的视觉 token 拼接在文本 token 之前并输入到 LLM](../images/llava_architecture.svg)
- **视觉指令微调**是 LLaVA 的关键训练创新。作者使用 GPT-4 从 COCO 图像生成了 158,000 个多模态指令遵循示例。每个示例包含一张图像和一个对话式指令(例如"详细描述这张图像"、"这张图像有什么不寻常之处?"、"如果我是一名游客参观这个地方,我应该知道什么?")。模型接受训练,根据图像和指令生成 GPT-4 撰写的回答。
- 训练分为两个阶段。**阶段 1(预训练)**:仅训练投影层 $W$,使用图像-描述对(来自 CC3M 的 595K 数据),视觉编码器和 LLM 都保持冻结。这教会 $W$ 将视觉特征与 LLM 的嵌入空间对齐。**阶段 2(微调)**:投影层和 LLM 在指令遵循数据上联合微调,视觉编码器保持冻结。这教会模型遵循复杂的视觉指令。
- **LLaVA-1.5** 通过三项关键更改改进了原始版本:将单层线性投影替换为两层 MLP(更具表现力的映射),使用更高分辨率的图像(336px 而非 224px,产生更多 patch token),以及在训练混合数据中加入学术 VQA 数据集。这些看似细微的修改带来了基准性能的大幅提升。
- LLaVA 的方法证明,你不需要像 Flamingo 的 Perceiver 重采样器或门控交叉注意力那样复杂的架构创新。一个简单的线性投影,结合高质量的指令微调数据,就足以有效地将视觉编码器连接到 LLM。这种简洁性使得 LLaVA 极具影响力——后续大多数开源 VLM 都遵循类似的方案。
## 扩展视觉语言模型
- 该领域从概念验证型 VLM 迅速发展为在数十亿图像-文本对上训练的工业级系统。三个模型家族展示了不同的扩展方法。
### PaLI
- **PaLI**Pathways Language and Image modelChen 等人,2022Google)同时扩展视觉编码器和语言模型。PaLI 使用 ViT-e(40 亿参数)作为视觉编码器,mT5(130 亿参数)作为语言模型,总计 170 亿参数。图像被编码为一系列 patch token,拼接在文本 token 之前,输入到编码器-解码器架构的 mT5。
- PaLI 的关键洞见是**扩展视觉编码器与扩展语言模型同样重要**。先前的工作通常使用固定的、中等规模的视觉骨干网络(如 ViT-B 或 ViT-L),将参数预算全部投入 LLM。PaLI 表明,一个 40 亿参数的 ViT-e,在 JFT-4B(40 亿张标注图像)上预训练后,能够显著提升 OCR 和空间推理等细粒度视觉任务的性能。
- PaLI 在 WebLI(一个包含 109 种语言、100 亿图像-文本对的数据集)上训练,因此天然具备多语言能力。模型通过混合任务进行预训练:图像描述、VQA 和图像-文本匹配,全部作为文本到文本生成任务(遵循第 7 章的 T5 范式)。**PaLI-X**550 亿参数)和 **PaLI-3**(50 亿,使用 SigLIP 作为视觉编码器)是后续迭代版本。
### Qwen-VL
- **Qwen-VL**Bai 等人,2023,阿里巴巴)在 Qwen LLM 基础上增加了一个 ViT 视觉编码器和一个单层交叉注意力模块(类似于 Flamingo 的 Perceiver 重采样器),将视觉编码器的输出压缩为一组固定的 256 个视觉 token。视觉 token 与文本 token 拼接后由 Qwen LLM 处理。
- Qwen-VL 的训练采用三阶段方案。阶段 1:在 14 亿个弱监督图像-文本对上预训练,仅解冻视觉编码器。阶段 2:在更高质量的数据上进行多任务预训练,包括 VQA、描述、定位和 OCR 数据集,整个模型解冻。阶段 3:在指令遵循和对话数据上进行监督微调。这种从噪声网络数据到精选指令数据的渐进式精炼,是大多数现代 VLM 共享的模式。
- **Qwen2-VL**2024)引入了**动态分辨率**支持:模型不是将所有图像缩放到固定大小,而是通过动态调整视觉 token 数量以原始分辨率处理图像。更高分辨率的图像产生更多 token,更低分辨率的图像产生更少 token。这在不浪费低分辨率输入计算量的前提下,提升了文档理解和细粒度识别等对细节敏感的任务的性能。
### InternVL
- **InternVL**Chen 等人,2024,上海人工智能实验室)激进地扩展了视觉编码器,使用 InternViT-6B——一个 60 亿参数的视觉 Transformer——与语言模型配对。关键的架构贡献是**动态高分辨率处理**:图像被分割为 448x448 像素的图块,每个图块由视觉编码器独立处理,得到的图块特征与完整图像的缩略图特征拼接。这使得模型能够处理任意宽高比和分辨率的图像。
- InternVL-2 进一步引入了**渐进对齐训练**:首先用对比目标(如 CLIP)对齐视觉编码器,然后通过轻量级 MLP 连接器将其连接到 LLM,最后在指令数据上进行端到端微调。这种渐进策略防止了视觉编码器预训练表示的灾难性遗忘。
![扩展 VLMPaLI、Qwen-VL 和 InternVL 的比较,展示了连接视觉编码器和语言模型的不同方法,包括其训练阶段](../images/scaling_vlms_comparison.svg)
- 所有三个模型家族的一个共同主题是**训练数据精选**的重要性。从网络抓取的原始图像-文本对是噪声大且常常不对齐的。后续的训练阶段逐步过滤和精炼数据,从数十亿噪声对过渡到数百万高质量指令示例。最终微调数据的质量往往比模型的原始参数数量更为重要。
## 定位与指代
- 想象你在人群中指着一个人说"戴红帽子的女士"。你在用语言指代一个特定的空间区域。**视觉定位**是相反的过程:给定一张图像和一个自然语言表述,模型必须识别(定位)所指的对象。**指代表达理解**产生边界框;**指代表达分割**产生像素掩码。
- 形式上,给定一张图像 $I$ 和一个指代表达 $r$(例如"左边那只大型棕色狗"),模型预测一个边界框 $b = (x, y, w, h)$ 或一组定位所引用对象的坐标。数据集包括 **RefCOCO**、**RefCOCO+** 和 **RefCOCOg**,每个数据集包含具有多个对象的图像以及每个对象的明确指代表达。
- 早期的定位模型使用两阶段方法:首先生成区域提议(使用 Faster R-CNN 或类似方法),然后使用融合模型对每个提议与语言查询进行评分。评分最高的区域即为预测结果。这种方法计算代价高昂,且受限于提议的质量。
- 现代 VLM 将定位直接整合到生成式框架中。关键思想是将边界框坐标表示为**文本 token**。你将连续的坐标空间离散化为槽位(例如 $x, y, w, h$ 各 1000 个槽位),并向词汇表中添加特殊的位置 token,如 `<loc_342>`。然后模型通过输出一系列位置 token 来生成边界框:
$$\text{输出: } \texttt{<loc\_102><loc\_215><loc\_487><loc\_398>}$$
- 这种 token 化技巧使得任何自回归语言模型无需架构更改即可执行定位——它只需学会"说坐标"。**Pix2Seq**Chen 等人,2022)率先将这种方法用于目标检测,而 Qwen-VL、Ferret 和 Kosmos-2 等模型将其扩展到指代表达理解和短语定位。
- **Kosmos-2**Peng 等人,2023Microsoft)通过将空间位置表示为嵌入在生成文本中的特殊 token,为多模态 LLM 增加了定位能力。例如,它可以生成:"一只 `<phrase>` 金毛猎犬 `</phrase>` `<box>` `<loc_102>` `<loc_215>` `<loc_487>` `<loc_398>` `</box>` 正在接飞盘。"这种文本和空间 token 的交错融合实现了同步描述和定位。
![通过坐标 token 化实现定位:模型生成文本 token 与离散化的边界框坐标 token 交错在一起,定位描述中提到的物体](../images/grounding_coordinate_tokens.svg)
- **定点指向**将定位更进一步:模型不再输出边界框,而是预测一个单一的点(通常是指代物体的中心)。这对于交互式应用非常有用,例如用户问"最近的出口在哪里?",模型返回一个叠加在图像上的坐标。**Shikra** 和 **Ferret** 等模型支持基于点的指代以及基于框的定位。
## 免 OCR 文档理解
- 传统的文档理解流水线很复杂:首先运行 OCR 引擎提取文本和布局,然后将提取的文本输入语言模型。这种多阶段方法很脆弱——OCR 错误向下游传播,空间布局信息常常丢失或表征不良。如果模型能像人类一样直接从像素中读取信息呢?
- **Donut**Document Understanding TransformerKim 等人,2022)完全消除了 OCR。它使用 Swin Transformer(第 8 章)作为视觉编码器处理文档图像,并使用 BART 风格的 Transformer 解码器直接从视觉特征生成结构化文本输出。解码器可以根据任务生成 JSON、键值对或纯文本。
- Donut 的训练分为两个阶段。**预训练**:模型通过执行合成 OCR 来学习阅读——给定一张文档图像,生成完整的文本内容。这在从文本语料库渲染的数百万张合成文档图像上进行训练,教会视觉编码器识别字符、字体和布局。**微调**:模型通过训练生成特定于任务的结构化输出,适应特定的下游任务,如收据解析、表格理解或文档分类。
- Donut 解码器使用特殊的提示方案:任务由提示 token 指定(例如分类用 `<doc_class>`,收据解析用 `<parse_receipt>`),模型根据此提示生成输出。这种统一接口使得单个模型可以处理多种文档理解任务。
- **Pix2Struct**Lee 等人,2023Google)将免 OCR 思想应用于网页理解和图表/图形理解。关键的预训练目标是**截图解析**:给定一个网页的带掩码截图,模型生成产生可见区域的底层 HTML。这教会模型理解视觉呈现与结构化标记之间的关系。
- Pix2Struct 引入了**可变分辨率输入处理**:它并不是将所有图像缩放到固定大小(这会扭曲宽高比并破坏精细文字),而是在保持原始宽高比的同时将图像打包为固定数量的 patch。一个高而窄的文档产生一个高而窄的 patch 网格。这对于文档理解至关重要,因为宽高比携带着语义信息(收据窄而高;表格宽而短)。
![免 OCR 文档理解:Donut 和 Pix2Struct 通过视觉编码器直接处理文档图像,无需任何 OCR 预处理即可生成结构化文本输出](../images/ocr_free_document_understanding.svg)
- **Nougat**Blecher 等人,2023Meta)将 Donut 架构专门应用于学术论文,直接从 PDF 页面图像生成完整的 LaTeX 标记。它可以处理复杂的数学方程、表格和图形——这些任务正是传统 OCR 流水线难以应付的。该模型在 PDF 页面图像及其对应的 LaTeX 源代码对上进行训练。
- 免 OCR 模型的成功展示了深度学习中的一个更广泛原则:直接从原始输入(像素)学习的端到端模型通常优于复杂的多阶段流水线,因为它们可以联合优化所有组件,并学习专门针对最终任务定制的表示。中间的 OCR 步骤是一个瓶颈,限制了模型能够学习的内容。
## 视觉 Token 流水线
- 无论架构家族如何,每个 VLM 都必须将图像转换为语言模型可以处理的一系列 token。理解这一流水线至关重要。不同模型的处理过程有所差异,但总体流程如下:
- **第 1 步:Patch 提取。** 图像(高度 $H$,宽度 $W$)被划分为不重叠的、大小为 $P \times P$ 的 patch,产生 $N = HW / P^2$ 个 patch。对于 336x336 图像和 14x14 patch$N = 576$。
- **第 2 步:视觉编码。** 每个 patch 经过线性投影并通过视觉编码器(通常是 ViT)。输出是一系列上下文 patch 嵌入 $V = [v_1, \ldots, v_N] \in \mathbb{R}^{N \times d_v}$。这些嵌入既携带局部外观信息,也携带全局上下文(来自自注意力)。
- **第 3 步:Token 压缩(可选)。** 一些模型将 $N$ 个视觉 token 压缩为更少的 $M \ll N$ 个 token,以减少语言模型的计算负担。Flamingo 使用 Perceiver 重采样器($M = 64$);Qwen-VL 使用交叉注意力($M = 256$);**Q-Former**(在 BLIP-2 中使用,Li 等人,2023)使用一组 $M = 32$ 个可学习查询 token,对视觉编码器的输出执行交叉注意力。
- **第 4 步:投影。** 视觉 token(全部或压缩后的集合)通过线性层或 MLP 投影到语言模型的嵌入空间。投影后,视觉 token 与文本 token 嵌入具有相同维度,可以与它们拼接。
- **第 5 步:注入 LLM。** 投影后的视觉 token 在特殊 `<image>` 占位符 token 的位置插入到 token 序列中,组合后的序列由语言模型处理。LLM 的自注意力使文本 token 能够关注视觉 token,反之亦然。
![视觉 token 流水线:图像 patch 被提取,由 ViT 编码,可选地由 Perceiver 或 Q-Former 压缩,投影到 LLM 维度,并与文本 token 拼接](../images/visual_token_pipeline.svg)
- 视觉 token 的数量直接影响计算成本。每个视觉 token 参与 LLM 的自注意力,其复杂度与序列长度的平方成正比。具有多个 patch 的高分辨率图像可能产生数百或数千个视觉 token,占据 LLM 上下文窗口的主导地位。这就是 token 压缩的重要性所在:将 576 个视觉 token 减少到 64 个,可将视觉部分在注意力中的贡献减少约 9 倍。
- **BLIP-2**Li 等人,2023)以其高效的桥接策略而闻名。它引入了一个轻量级的 **Q-Former**(一个带有可学习查询的小型 Transformer),位于冻结的视觉编码器和冻结的 LLM 之间。Q-Former 是唯一可训练的组件——视觉编码器和 LLM 都保持冻结。它的预训练分为两个阶段:首先是图像-文本对比学习、匹配和描述目标(连接视觉编码器),然后是语言生成目标(连接 LLM)。这种模块化设计使得 BLIP-2 可以将任何视觉编码器插入到任何 LLM 中。
## 训练目标
- VLM 使用多种目标的组合进行训练,具体取决于架构模式:
- **图像-文本对比损失(ITC):** 在共享嵌入空间中对齐图像和文本表示,如 CLIP 中所示。这是双编码器的主要目标,也常被用作融合模型的预训练目标。该损失就是上一篇文件中的 InfoNCE 损失。
- **图像-文本匹配(ITM):** 一个二分类目标——给定图像和文本,预测它们是否匹配。困难负样本(与不同图像配对的相似文本)使这项任务具有挑战性,迫使模型学习细粒度的对齐。
- **语言建模(LM):** 标准的自回归语言建模目标——给定之前的所有 token 预测下一个 token。对于 VLM"之前的 token" 包括视觉 token,因此模型学习在视觉输入条件下生成文本。这是编码器-解码器和仅解码器 VLM 的主要目标。
$$\mathcal{L}_{\text{LM}} = -\sum_{t=1}^{T} \log p(w_t \mid w_{<t}, V)$$
- **前缀语言建模:** 一种变体,其中图像和文本前缀作为上下文提供(不进行训练),模型仅训练生成后续部分。这用于 PaLI 和 SimVLM 等模型。
- 大多数现代 VLM 在预训练期间结合多个目标(例如 BLIP 中的 ITC + ITM + LMCoCa 中的 ITC + LM),然后在指令数据上使用纯 LM 目标进行微调。
## 编程练习(使用 CoLab 或 notebook
1. 实现一个简单的基于注意力的图像描述解码器。使用随机的"图像特征"作为编码器输出,训练解码器生成固定的描述,观察注意力权重在每个解码步骤如何跨空间位置移动。
```python
import jax
import jax.numpy as jnp
import matplotlib.pyplot as plt
# 模拟 4x4 空间网格的图像特征(16 个区域,dim=32)
key = jax.random.PRNGKey(42)
k1, k2, k3 = jax.random.split(key, 3)
img_features = jax.random.normal(k1, (16, 32)) # 16 个空间区域,32 维
# 词汇表:0=<start>, 1="a", 2="red", 3="car", 4=<end>
vocab_size, embed_dim, hidden_dim = 5, 16, 32
W_embed = jax.random.normal(k2, (vocab_size, embed_dim)) * 0.1
W_attn_q = jax.random.normal(k3, (hidden_dim, 32)) * 0.1 # 查询投影
def attend(h, img_feats, W_q):
"""在给定解码器状态 h 的情况下计算图像特征上的软注意力。"""
query = h @ W_q # (32,)
scores = img_feats @ query # (16,)
weights = jax.nn.softmax(scores) # (16,)
context = weights @ img_feats # (32,)
return context, weights
# 简单的 GRU 风格步骤(为说明目的,仅用线性 + tanh)
W_h = jax.random.normal(jax.random.PRNGKey(0), (embed_dim + 32, hidden_dim)) * 0.1
def decode_step(h, word_idx, img_feats):
context, attn_weights = attend(h, img_feats, W_attn_q)
word_emb = W_embed[word_idx] # (16,)
inp = jnp.concatenate([word_emb, context]) # (48,)
h_new = jnp.tanh(inp @ W_h) # (32,)
return h_new, attn_weights
# 运行解码序列:<start> -> "a" -> "red" -> "car" -> <end>
target_seq = [0, 1, 2, 3, 4]
h = jnp.zeros(hidden_dim)
all_attn = []
for word_idx in target_seq[:-1]:
h, attn_w = decode_step(h, word_idx, img_features)
all_attn.append(attn_w)
# 可视化每一步的注意力图(重塑为 4x4 网格)
words = ["<start>", "a", "red", "car"]
fig, axes = plt.subplots(1, 4, figsize=(14, 3))
for i, (ax, w) in enumerate(zip(axes, words)):
ax.imshow(all_attn[i].reshape(4, 4), cmap='viridis')
ax.set_title(f'生成"{w}"\n关注的区域')
ax.axis('off')
plt.suptitle('每个解码步骤的图像区域注意力')
plt.tight_layout(); plt.show()
# 尝试修改 img_features,观察注意力模式如何变化!
```
2. 模拟视觉 token 流水线:将图像划分为 patch,将 patch 投影到嵌入空间,与文本 token 嵌入拼接,并在组合序列上运行单层自注意力。
```python
import jax
import jax.numpy as jnp
import matplotlib.pyplot as plt
key = jax.random.PRNGKey(7)
# 创建一个合成的 8x8 "图像"3 个通道
k1, k2, k3, k4 = jax.random.split(key, 4)
image = jax.random.uniform(k1, (8, 8, 3))
# 第 1 步:划分为 4x4 patch -> 4 个 patch
patch_size = 4
patches = image.reshape(2, patch_size, 2, patch_size, 3)
patches = patches.transpose(0, 2, 1, 3, 4).reshape(4, patch_size * patch_size * 3) # (4, 48)
print(f"Patch 数量: {patches.shape[0]}, Patch 维度: {patches.shape[1]}")
# 第 2 步:将 patch 投影到嵌入维度 (d=16)
d_model = 16
W_patch = jax.random.normal(k2, (patches.shape[1], d_model)) * 0.1
visual_tokens = patches @ W_patch # (4, 16)
# 第 3 步:创建文本 token 嵌入(模拟 3 个文本 token)
text_tokens = jax.random.normal(k3, (3, d_model)) * 0.1
# 第 4 步:拼接视觉 + 文本 token
combined = jnp.concatenate([visual_tokens, text_tokens], axis=0) # (7, 16)
print(f"组合序列长度: {combined.shape[0]} (4 个视觉 + 3 个文本)")
# 第 5 步:在组合序列上运行单头自注意力
W_Q = jax.random.normal(k4, (d_model, d_model)) * 0.1
k5, k6 = jax.random.split(k4)
W_K = jax.random.normal(k5, (d_model, d_model)) * 0.1
W_V = jax.random.normal(k6, (d_model, d_model)) * 0.1
Q = combined @ W_Q
K = combined @ W_K
V = combined @ W_V
attn_scores = (Q @ K.T) / jnp.sqrt(d_model)
attn_weights = jax.nn.softmax(attn_scores, axis=-1) # (7, 7)
output = attn_weights @ V # (7, 16)
# 可视化跨模态注意力模式
labels = ['V1', 'V2', 'V3', 'V4', 'T1', 'T2', 'T3']
fig, ax = plt.subplots(figsize=(6, 5))
im = ax.imshow(attn_weights, cmap='Blues')
ax.set_xticks(range(7)); ax.set_xticklabels(labels)
ax.set_yticks(range(7)); ax.set_yticklabels(labels)
ax.set_xlabel(''); ax.set_ylabel('查询')
ax.set_title('自注意力:视觉(V)和文本(T)Token')
plt.colorbar(im, ax=ax); plt.tight_layout(); plt.show()
# 观察:文本 token 关注视觉 token(跨模态注意力)!
```
3. 实现用于视觉定位的坐标 token 化。给定一个边界框,将其转换为离散 token;给定离散 token,重构边界框。在不同槽位分辨率下可视化量化误差。
```python
import jax.numpy as jnp
import matplotlib.pyplot as plt
def encode_bbox(bbox, num_bins=1000):
"""将连续的边界框 (x, y, w, h)(在 [0,1] 范围内)转换为离散 token。"""
tokens = jnp.round(jnp.array(bbox) * (num_bins - 1)).astype(jnp.int32)
return tokens
def decode_bbox(tokens, num_bins=1000):
"""将离散 token 转换回连续的边界框。"""
return tokens.astype(jnp.float32) / (num_bins - 1)
# 真实边界框(归一化到 [0, 1])
gt_bbox = jnp.array([0.123, 0.456, 0.333, 0.222])
# 测试不同槽位分辨率下的量化
bin_sizes = [10, 50, 100, 500, 1000]
errors = []
for n_bins in bin_sizes:
tokens = encode_bbox(gt_bbox, n_bins)
reconstructed = decode_bbox(tokens, n_bins)
error = jnp.max(jnp.abs(gt_bbox - reconstructed))
errors.append(float(error))
print(f"槽位数={n_bins:>5d} | Token={tokens} | "
f"重构={reconstructed} | 最大误差={error:.6f}")
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(bin_sizes, errors, 'o-', color='#e74c3c', linewidth=2, markersize=8)
ax.set_xlabel('槽位数'); ax.set_ylabel('最大量化误差')
ax.set_title('边界框量化误差 vs 槽位分辨率')
ax.set_xscale('log'); ax.set_yscale('log')
ax.grid(True, alpha=0.3); plt.tight_layout(); plt.show()
# 尝试:槽位非常少时(如 5)会发生什么?误差在何时是可接受的?
```