视觉语言模型


文档摘要

视觉语言模型 视觉语言模型(vision-language model,VLM)能够联合理解图像与文本,从而支持视觉问答、图像描述生成以及视觉推理。本文件涵盖 VQA、图像描述生成、视觉定位,以及 VisualBERT、BLIP、LLaVA、Flamingo、PaLI、Qwen-VL 等将视觉编码器与大型语言模型融合的架构。 想象一位博物馆讲解员,能看着一幅画就娓娓道来它的一切:画里有哪些物体、讲了一个什么故事、传达了怎样的情绪,还能回答游客提出的任何问题。视觉语言模型(vision-language model,VLM)就是它的计算等价物——一个联合理解图像和文本的系统,能够描述视觉场景、回答关于场景的问题、遵循视觉指令,甚至根据自然语言查询在图像中定位特定的物体。

视觉语言模型

视觉语言模型(vision-language model,VLM)能够联合理解图像与文本,从而支持视觉问答、图像描述生成以及视觉推理。本文件涵盖 VQA、图像描述生成、视觉定位,以及 VisualBERT、BLIP、LLaVA、Flamingo、PaLI、Qwen-VL 等将视觉编码器与大型语言模型融合的架构。

  • 想象一位博物馆讲解员,能看着一幅画就娓娓道来它的一切:画里有哪些物体、讲了一个什么故事、传达了怎样的情绪,还能回答游客提出的任何问题。**视觉语言模型(vision-language model,VLM)**就是它的计算等价物——一个联合理解图像和文本的系统,能够描述视觉场景、回答关于场景的问题、遵循视觉指令,甚至根据自然语言查询在图像中定位特定的物体。

  • VLM 处于第 8 章视觉编码器与第 7 章语言模型的交汇处。核心的工程挑战在于桥接两个截然不同的表征世界:视觉骨干网络那种空间化、连续的特征图,和语言模型那种序列化、离散的词元嵌入。本文件中介绍的每一种架构,本质上都是在回答同一个问题:你如何把视觉和语言融合起来?

VLM 的高层分类,展示双编码器、融合编码器和编码器–解码器三大类及其输入输出

视觉问答

  • 想象有人给你看一张照片,然后问:「公园里有几只狗?」你毫不费力地解析图像、找到狗、数出数量,并给出答案。**视觉问答(visual question answering,VQA)**把这件事形式化:给定一张图像 I 和一个自然语言问题 q,预测答案 a

  • 这个任务有几种设定方式。最常见的是把 VQA 视作开放式分类(open-ended classification):模型从一个固定的高频答案词表中选择(例如 VQA v2 中最常见的 3,129 个答案)。另一种方式是生成式作答(generative answering),模型生成一段自由格式的文本字符串——现代 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 个问题。研究者很快发现,模型可以通过钻**语言先验(language prior)**的空子来取得出人意料的高准确率——对「how many」类问题一律回答「2」,对「is there」类问题一律回答「yes」,完全不用看图像。

  • VQA v2(Goyal 等,2017)针对这一点,给每个问题配上了两幅相似但答案不同的图像。这迫使模型真正把推理建立在视觉内容上。这种平衡配对的设定大约让数据集翻倍,并让「只靠语言走捷径」几乎失效。

  • 其他重要的 VQA 数据集还包括 GQA(Hudson & Manning,2019),其中包含需要多步推理的组合性问题;OK-VQA(Marino 等,2019),需要图像之外的外部知识;以及 TextVQA(Singh 等,2019),其答案依赖于读取图像中的文字。

VQA 流水线:图像经过视觉编码器、问题经过文本编码器,它们的表征被融合,融合后的向量被分类成一个答案

  • 早期 VQA 模型采用简单策略:从一个预训练 CNN(通常是第 8 章中 ResNet 或 VGGNet 的倒数第二层)抽取图像特征,用 LSTM(第 6 章)编码问题,再把它们组合起来。组合函数 g 经历了快速演进:从简单的逐元素乘法,到双线性池化(bilinear pooling),再到多模态 Tucker 分解。**双线性注意力(bilinear attention)**计算 v^T W h,其中 W 是可学习的交互矩阵,但完整的双线性形式有 O(d_v \times d_h) 个参数,数量大得惊人。MLB(多模态低秩双线性池化)把它分解成两个低秩投影,使其变得可行。

  • VQA 的突破来自注意力。Stacked Attention Networks(Yang 等,2016)用问题编码去关注空间化的图像区域,迭代地精修要聚焦图像的哪些部分。这个想法——让问题「去看」相关的图像区域——成为了标准。

图像描述生成

  • 想象一个朋友看着你的度假照片,一边看一边解说:「一只金毛猎犬在阳光明媚的沙滩上接飞盘。」**图像描述生成(image captioning)**的任务,就是为图像生成一段自然语言描述。与 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)通过引入**对图像区域的注意力(attention over image regions)**解决了这个问题。CNN 不再把图像编码成一个向量,而是产生一个空间特征网格(例如从 VGGNet 最后一层卷积得到 14 \times 14 \times 512)。在每个解码步骤,模型在这些空间位置上计算注意力权重,产生一个上下文向量,高亮与当前词最相关的区域。

  • 回想一下第 6 章的注意力机制:解码器的隐藏状态充当查询(query),空间特征充当键(key)和值(value),注意力权重告诉模型该看哪里。作者提出了两个变体:软注意力(soft attention)(可微,对所有区域加权平均)和硬注意力(hard attention)(随机采样单个区域,用 REINFORCE 训练)。

基于注意力的描述生成:在每个解码步骤,模型关注图像的不同空间区域,比如在生成「dog」一词时聚焦在狗所在的区域

  • 这些模型产生的注意力图具有惊人的可解释性:当生成「dog」时,注意力在狗的区域达到峰值;当生成「beach」时,注意力转移到沙子和水面。这是最早令人信服的展示之一,说明注意力能提供天然的可解释性。

  • CIDEr(Vedantam 等,2015)、METEORBLEUSPICE 是标准的描述评估指标。CIDEr 计算生成描述与参考描述之间 TF-IDF 加权的 n-gram 相似度,专门为描述评估设计。现代 VLM 通常在 MS COCO Captions 和 NoCaps 等描述基准上以 CIDEr 评估。

  • 后来的描述模型引入了自下而上的注意力(bottom-up attention)(Anderson 等,2018):先用一个目标检测器(Faster R-CNN,第 8 章)提出显著的图像区域,描述模型再在这些区域特征上做注意力,而不是在均匀网格上。在 ViT 类编码器接管之前,这是主流方法。

架构范式

  • 每个 VLM 都必须回答一个根本性的设计问题:视觉和语言在哪个时刻发生交互?答案定义了模型的架构家族。主要有三种范式,各有不同的取舍。

双编码器

  • 想象两位翻译官各自独立工作——一个读法语文档,另一个读英文文档——他们各自用一种共享的「世界语」写出摘要。翻译期间他们从不交流,但他们的摘要可以直接相互比较。这就是**双编码器(dual encoder)**范式。

  • 视觉编码器 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 或基于定位的描述生成等任务上的实用性。

融合编码器

  • 现在想象这两位翻译官在同一个房间里,积极地讨论两份文档。他们可以指出具体的段落、互相提问,从而建立联合理解。这就是**融合编码器(fusion encoder)**范式。

  • 两种模态都被编码,然后通过**交叉注意力层(cross-attention layer)**融合,一种模态的词元可以去关注另一种模态的词元。图像先被视觉编码器处理成一系列图块或区域词元 V = [v_1, \ldots, v_N]。文本被分词为 T = [t_1, \ldots, t_M]。在融合层中,文本词元通过交叉注意力去关注图像词元:

\text{CrossAttn}(T, V) = \text{softmax}\!\left(\frac{(TW_Q)(VW_K)^T}{\sqrt{d_k}}\right)(VW_V)
  • 这实现了细粒度交互:每个文本词元都可以关注它需要的特定图像区域。VisualBERT、VilBERT 和 UNITER 等模型采用这种范式。代价是你无法为检索预先计算独立的嵌入——每一对图像–文本都需要一次经过融合层的完整前向传播。

双编码器与融合编码器:双编码器分别计算嵌入和一个相似度分数,而融合编码器通过交叉注意力层合并两种模态

编码器–解码器

  • **编码器–解码器(encoder-decoder)**范式把视觉编码器和一个自回归生成输出词元的文本解码器结合起来,类似第 7 章的 seq2seq 模型。视觉编码器产生带上下文的图像表征,文本解码器在生成输出文本时对它们做交叉注意力。

  • 这种范式天然支持生成式任务:描述生成、自由格式答案的 VQA,以及视觉对话。GIT(Generative Image-to-text Transformer,Wang 等,2022)、CoCa(Contrastive Captioner,Yu 等,2022)和 PaLI 都使用这种架构。CoCa 巧妙地把双编码器和编码器–解码器两种范式结合起来:文本解码器的前半部分充当单模态文本编码器(用于对比学习),后半部分对图像特征做交叉注意力(用于生成式描述),兼得两者之长。

  • 在这三种范式之间的取舍取决于目标任务。双编码器最适合大规模检索;融合编码器最适合细粒度理解任务;编码器–解码器最适合生成式任务。现代最先进的 VLM 越来越多地采用编码器–解码器或仅解码器范式,把每个视觉–语言任务都当作文本生成来处理。

Flamingo:少样本多模态学习

  • 想象一位资深专家,经过多年对艺术和文学的研究,在看过一两个例子后,就能针对一种全新的绘画风格侃侃而谈。Flamingo(Alonso 等,2022,DeepMind)正是基于同样的原理:它借助一个强大的预训练语言模型和一个预训练视觉编码器,用轻量级的架构组件把它们连接起来,从而在多模态任务上实现少样本学习。

  • Flamingo 的设计哲学保守而有效:保持预训练的视觉编码器(NFNet)和语言模型(Chinchilla)冻结,只学习连接它们的「胶水」。这个胶水由两部分组成:一个 Perceiver Resampler(感知器重采样器)门控交叉注意力层(gated cross-attention layer)

  • Perceiver Resampler 接收视觉编码器变长的输出(长度取决于图像分辨率),把它压缩成固定数量的 N 个视觉词元(通常 N = 64)。它的工作方式是初始化一组 N 个可学习的查询向量,用交叉注意力让这些查询去关注全部视觉编码器输出。这本质上是把 Perceiver 架构(Jaegle 等,2021)当作一个瓶颈来用——无论输入图像多大,它都能产生紧凑、固定大小的视觉表征。

z = \text{CrossAttn}(Q_{\text{learned}}, V_{\text{image}}) \in \mathbb{R}^{N \times d}
  • 门控交叉注意力层被穿插在冻结的语言模型层之间。在每一个这样的层里,语言模型的文本词元会对 Perceiver Resampler 产生的视觉词元做交叉注意力。关键在于,每个门控交叉注意力层都包含一个可学习的标量门 \alpha,初始化为零,它先把交叉注意力的输出相乘,然后才加到残差流里:
\hat{x} = x + \alpha \cdot \text{CrossAttn}(x, z)
  • \alpha = 0 初始化意味着训练开始时交叉注意力不起任何作用,模型的行为与原本冻结的语言模型完全一致。门会在训练过程中逐渐打开,平滑地整合视觉信息,而不破坏语言模型预训练得到的表征。

Flamingo 架构:冻结的视觉编码器输出送入 Perceiver Resampler,产生定长的视觉词元,再通过穿插在 LM 块之间的门控交叉注意力层注入到一个冻结的 LM 中

  • Flamingo 原生支持交错图文序列(interleaved image-text sequence)。你可以给它喂一个包含多张图像、文字穿插的提示,比如:「[图 1] 这是一只猫。[图 2] 这是一只狗。[图 3] 这是一只___。」模型把每张图像送进视觉编码器和 Perceiver Resampler 处理,得到的视觉词元被插入到文本序列对应的位置。语言模型的因果注意力掩码保证每个文本词元只能关注当前图像和之前图像的视觉词元。

  • 这种交错能力带来了强大的少样本多模态学习(few-shot multimodal learning)。通过在上下文中给出几个图文示例,Flamingo 可以在没有任何梯度更新的情况下完成新任务。在 VQAv2、OK-VQA 和描述生成等基准上,800 亿参数的 Flamingo 取得了当时最强的少样本表现,常常仅用 4 个或 32 个示例就能匹敌甚至超越经过微调的专用模型。

LLaVA 与视觉指令微调

  • 想象你有一位出色的语言专家(一个 LLM)和一位出色的艺术评论家(一个视觉编码器)。如果你能教会这位评论家「说语言专家的那种语言」,他们就能无缝协作。LLaVA(Large Language and Vision Assistant,大型语言与视觉助手,Liu 等,2023)做的正是这件事:它用一个简单的线性层把视觉特征投影到 LLM 的词元嵌入空间,然后在整个系统上用指令跟随数据进行微调。

  • LLaVA 的架构惊人地简单。图像被预训练的 CLIP ViT-L/14 视觉编码器编码成图块特征网格 V \in \mathbb{R}^{N \times d_v},其中 N = 256 个图块(对于 336px 图像、14px 图块而言)。一个投影层(projection layer) W 把这些视觉特征映射到 LLM 的嵌入维度:

H_v = VW, \quad W \in \mathbb{R}^{d_v \times d_{\text{LLM}}}
  • 投影后的视觉词元 H_v 被简单地与文本词元嵌入拼接起来,作为一个单一序列送进 LLM(Vicuna,一个微调过的 LLaMA)。LLM 用它标准的因果自注意力来处理它们——没有特殊的交叉注意力层,没有 Perceiver,只是拼接而已。视觉词元被当作恰好编码了视觉信息的文本词元来对待。

LLaVA 架构:CLIP ViT 把图像编码为图块特征,一个线性投影把它们映射到 LLM 嵌入空间,投影后的视觉词元被前置到文本词元之前,一起送入 LLM

  • **视觉指令微调(visual instruction tuning)**是 LLaVA 关键的训练创新。作者用 GPT-4 从 COCO 图像生成了 158,000 个多模态指令跟随示例。每个示例由一张图像加上一段对话式指令组成(例如「详细描述这张图」「这张图有什么不寻常之处?」「如果我是个来这里的游客,我该知道些什么?」)。模型被训练成在给定图像和指令的情况下,生成 GPT-4 所写的回答。

  • 训练分两个阶段。阶段 1(预训练):只在图文对(来自 CC3M 的 595K 对)上训练投影层 W,视觉编码器和 LLM 都保持冻结。这教 W 把视觉特征与 LLM 的嵌入空间对齐。阶段 2(微调):在指令跟随数据上联合微调投影层和 LLM,视觉编码器保持冻结。这教会模型去遵循复杂的视觉指令。

  • LLaVA-1.5 用三个关键改动改进了原版:把单层线性投影换成两层 MLP(更具表达力的映射)、使用更高分辨率的图像(336px 而非 224px,产生更多图块词元)、并在训练混合中加入学术 VQA 数据集。这些看似不起眼的改动带来了基准性能的大幅跃升。

  • LLaVA 的方法证明,你不需要 Flamingo 那种 Perceiver Resampler 或门控交叉注意力之类的复杂架构创新。一个简单的线性投影,配合高质量的指令微调数据,就足以把视觉编码器有效连接到 LLM。这种简洁让 LLaVA 极具影响力——大多数后续的开源 VLM 都遵循类似的配方。

视觉语言模型的扩展

  • 这个领域迅速从概念验证级的 VLM 走向在数十亿对图文上训练的工业级系统。三个模型家族展示了不同的扩展路径。

PaLI

  • PaLI(Pathways Language and Image model,Chen 等,2022,Google)同时扩展视觉编码器和语言模型。PaLI 用 ViT-e(40 亿参数)作视觉编码器,用 mT5(130 亿参数)作语言模型,总计 170 亿参数。图像被编码为图块词元序列,前置到文本词元之前,再送入编码器–解码器 mT5。

  • PaLI 的关键洞见是:扩展视觉编码器和扩展语言模型同样重要。以往的工作通常用一个固定的、中等规模的视觉骨干(如 ViT-B 或 ViT-L),把所有参数预算都倾注到 LLM 上。PaLI 表明,一个在 JFT-4B(40 亿张带标注图像)上预训练的 40 亿参数 ViT-e,能在 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 Resampler),把视觉编码器的输出压缩成固定的 256 个视觉词元。视觉词元与文本词元拼接,由 Qwen LLM 处理。

  • Qwen-VL 的训练采用三阶段配方。阶段 1:在 14 亿对弱监督图文上预训练,只解冻视觉编码器。阶段 2:在更高质量的多任务数据上预训练,包括 VQA、描述生成、定位和 OCR 数据集,全部参数解冻。阶段 3:在指令跟随和对话数据上有监督微调。这种从嘈杂网络数据到精选指令数据的渐进式精修,是大多数现代 VLM 共有的模式。

  • Qwen2-VL(2024)引入了**动态分辨率(dynamic resolution)**支持:它不再把所有图像缩放到固定大小,而是通过动态调整视觉词元数量,按原始分辨率处理图像。高分辨率图像产生更多词元,低分辨率图像产生更少词元。这提升了文档理解和细粒度识别等对细节敏感任务的性能,又不在低分辨率输入上浪费计算。

InternVL

  • InternVL(Chen 等,2024,上海 AI 实验室)激进地扩展视觉编码器,使用 InternViT-6B——一个 60 亿参数的视觉 transformer——搭配一个语言模型。关键的架构贡献是动态高分辨率处理(dynamic high-resolution processing):图像被切成 448x448 像素的瓦片(tile),每块独立地由视觉编码器处理,得到的瓦片特征与整图的缩略图特征拼接。这让模型能处理任意宽高比和分辨率的图像。

  • InternVL-2 进一步引入了渐进式对齐训练(progressive alignment training):先用对比目标(类似 CLIP)对齐视觉编码器,然后通过轻量级 MLP 连接器把它接到 LLM,最后在指令数据上端到端微调。这种渐进式策略防止了视觉编码器预训练表征的灾难性遗忘。

扩展 VLM:PaLI、Qwen-VL 和 InternVL 的对比,展示了连接视觉编码器和语言模型的不同方法,以及它们的训练阶段

  • 三个家族的一个共同主题是**训练数据精选(training data curation)**的重要性。原始的网络抓取图文对噪声很大,往往配对不准。 successive 的训练阶段逐步过滤和精炼数据,从数十亿嘈杂对走向数百万高质量指令示例。最终微调数据的质量,往往比模型原始参数数量更重要。

定位与指代

  • 想象你在人群中指着一个人说「戴红帽子的那位女士」。你是在用语言指代一个特定的空间区域。**视觉定位(visual grounding)**是反过来:给定一张图像和一个自然语言表达,模型必须识别(定位)被指代的目标。**指代表达理解(referring expression comprehension)**输出一个边界框;**指代表达分割(referring expression segmentation)**输出一个像素掩膜。

  • 形式上,给定一张图像 I 和一个指代表达 r(例如「左边那只棕色的大狗」),模型预测一个边界框 b = (x, y, w, h) 或一组坐标来定位目标。相关数据集包括 RefCOCORefCOCO+RefCOCOg,每个都包含多物体图像,并为每个物体提供无歧义的指代表达。

  • 早期的定位模型采用两阶段方法:先生成区域提议(来自 Faster R-CNN 等),再用一个融合模型对每个提议相对于语言查询打分。得分最高的区域就是预测。这种做法计算昂贵,且受限于提议的质量。

  • 现代 VLM 把定位直接整合进生成式框架中。关键想法是把边界框坐标表示成文本词元。你把连续的坐标空间离散化到若干桶中(例如 x, y, w, h 各 1000 个桶),并向词表里添加像 <loc_342> 这样的特殊位置词元。模型通过输出一串位置词元来生成一个边界框:

\text{Output: } \texttt{<loc\_102><loc\_215><loc\_487><loc\_398>}
  • 这种分词技巧让任何自回归语言模型都能做定位,而无需任何架构改动——它只是学会了「说坐标」。Pix2Seq(Chen 等,2022)在目标检测上率先采用此方法,Qwen-VL、Ferret 和 Kosmos-2 等模型把它扩展到了指代表达理解和短语定位。

  • Kosmos-2(Peng 等,2023,微软)通过把空间位置表示成嵌入在生成文本中的特殊词元,为多模态 LLM 增加了定位能力。例如,它可以生成:「A <phrase> golden retriever </phrase> <box> <loc_102> <loc_215> <loc_487> <loc_398> </box> is catching a frisbee.」这种文本与空间词元的交错,实现了同时进行描述生成和定位。

通过坐标分词实现定位:模型生成穿插着离散化边界框坐标词元的文本词元,从而定位说明中提到的物体

  • **指向(pointing)**把定位推得更远:模型不再输出边界框,而是预测一个点(通常是目标物体的中心)。这对于交互式应用很有用,比如用户问「最近的出口在哪里?」,模型在图像上叠加一个坐标作为回应。Shikra、Ferret 等模型在框定位之外,还支持基于点的指代。

免 OCR 的文档理解

  • 传统的文档理解流水线很复杂:先跑一个 OCR 引擎提取文本和版面,再把提取的文本送进语言模型。这种多阶段方法很脆弱——OCR 错误会向下游传播,而且空间版面信息往往丢失或表达得很差。如果模型能像你一样直接从像素中读取信息呢?

  • Donut(Document Understanding Transformer,Kim 等,2022)完全去掉了 OCR。它用一个 Swin Transformer(第 8 章)作视觉编码器处理文档图像,再用一个 BART 风格的 transformer 解码器直接从视觉特征生成结构化文本输出。解码器可以生成 JSON、键值对或纯文本,取决于具体任务。

  • Donut 的训练分两个阶段。预训练:模型通过做合成的 OCR 来学习阅读——给定一张文档图像,它生成全部文本内容。这是在数百万张由文本语料渲染的合成文档图像上训练的,教会视觉编码器识别字符、字体和版面。微调:把模型适配到具体的下游任务,比如收据解析、表单理解或文档分类,训练它生成任务特定的结构化输出。

  • Donut 的解码器采用一种特殊的提示方式:任务由一个提示词元指定(例如 <doc_class> 表示分类,<parse_receipt> 表示收据解析),模型在这个提示条件下生成输出。这种统一接口让一个模型就能处理多种文档理解任务。

  • Pix2Struct(Lee 等,2023,Google)把免 OCR 的思路应用到网页理解和图表/插图理解上。关键的预训练目标是屏幕截图解析(screenshot parsing):给定一张被遮罩的网页截图,模型生成产生可见区域的底层 HTML。这教会模型理解视觉渲染与结构化标记之间的关系。

  • Pix2Struct 引入了变分辨率输入处理(variable-resolution input processing):它不再把所有图像缩放到固定大小(这会扭曲宽高比、破坏精细文字),而是在保留原始宽高比的前提下,把图像打包到固定数量的图块中。一张又高又窄的文档会产生一个又高又窄的图块网格。这对文档理解至关重要,因为宽高比承载着语义信息(收据又窄又高,电子表格又宽又短)。

免 OCR 的文档理解:Donut 和 Pix2Struct 直接通过视觉编码器处理文档图像,并生成结构化文本输出,无需任何 OCR 预处理

  • Nougat(Blecher 等,2023,Meta)把 Donut 架构专门应用于学术论文,直接从 PDF 页面图像生成完整的 LaTeX 标记。它能处理复杂的数学公式、表格和插图——这些任务正是传统 OCR 流水线最头疼的地方。模型在 PDF 页面图像及其对应的 LaTeX 源码对上训练。

  • 免 OCR 模型的成功证明了深度学习中一个更广泛的原则:直接从原始输入(像素)学习的端到端模型,往往优于复杂的多阶段流水线,因为它们可以联合优化所有组件,并学到专门为最终任务定制的表征。中间的 OCR 步骤是一个瓶颈,限制了模型能学到的东西。

视觉词元流水线

  • 不管属于哪个架构家族,每个 VLM 都必须把图像转换成语言模型可处理的词元序列。理解这条流水线至关重要。具体过程因模型而异,但大致流程是:

  • 第 1 步:图块提取。 把图像(高 H、宽 W)切成大小为 P \times P 的不重叠图块,得到 N = HW / P^2 个图块。对于 336x336 的图像、14x14 的图块,N = 576

  • 第 2 步:视觉编码。 每个图块被线性投影,然后经过视觉编码器(通常是 ViT)。输出是一串带上下文的图块嵌入 V = [v_1, \ldots, v_N] \in \mathbb{R}^{N \times d_v}。这些嵌入既携带局部外观信息,也携带全局上下文(来自自注意力)。

  • 第 3 步:词元压缩(可选)。 有些模型把 N 个视觉词元压缩成更少的 M \ll N 个词元,以减轻语言模型的计算负担。Flamingo 用 Perceiver Resampler(M = 64);Qwen-VL 用交叉注意力(M = 256);Q-Former(用于 BLIP-2,Li 等,2023)用一组 M = 32 个可学习查询词元,对视觉编码器输出做交叉注意力。

  • 第 4 步:投影。 视觉词元(全集或压缩集)通过一个线性层或 MLP 投影到语言模型的嵌入空间。投影后,视觉词元与文本词元嵌入维度相同,可以与它们拼接。

  • 第 5 步:注入 LLM。 投影后的视觉词元被插入到词元序列中特殊 <image> 占位符的位置,合并后的序列由语言模型处理。LLM 的自注意力允许文本词元关注视觉词元,反之亦然。

视觉词元流水线:提取图像图块、由 ViT 编码、可选地由 Perceiver 或 Q-Former 压缩、投影到 LLM 维度,再与文本词元拼接

  • 视觉词元的数量直接影响计算成本。每个视觉词元都参与 LLM 的自注意力,其开销随序列长度呈二次增长。高分辨率、图块多的图像可能产生数百甚至数千个视觉词元,占满 LLM 的上下文窗口。这就是词元压缩为何重要:把 576 个视觉词元压缩到 64 个,能将视觉部分对注意力的贡献削减约 9 倍。

  • BLIP-2(Li 等,2023)以其高效的桥接策略著称。它引入了一个轻量级的 Q-Former(一个带可学习查询的小型 transformer),介于冻结的视觉编码器和冻结的 LLM 之间。Q-Former 是唯一可训练的组件——视觉编码器和 LLM 都保持冻结。它分两个阶段预训练:先用图文对比学习、匹配和描述目标(把它连接到视觉编码器),再用语言生成目标(把它连接到 LLM)。这种模块化设计允许 BLIP-2 把任何视觉编码器插到任何 LLM 上。

训练目标

  • VLM 用一组组合目标训练,具体取决于架构范式:

  • 图文对比损失(image-text contrastive loss,ITC):在共享嵌入空间中对齐图像和文本表征,如 CLIP。这是双编码器的主要目标,也常被用作融合模型的预训练目标。该损失就是上一篇文件中的 InfoNCE 损失。

  • 图文匹配(image-text matching,ITM):一个二分类目标——给定一张图像和一段文本,预测它们是否匹配。难负样本(相似但与不同图像配对的文本)让任务具有挑战性,迫使模型学习细粒度对齐。

  • 语言建模(language modelling,LM):标准的自回归语言建模目标——给定之前所有词元,预测下一个词元。对于 VLM,「之前的词元」包括视觉词元,因此模型要学会在视觉输入条件下生成文本。这是编码器–解码器和仅解码器 VLM 的主要目标。

\mathcal{L}_{\text{LM}} = -\sum_{t=1}^{T} \log p(w_t \mid w_{<t}, V)
  • 前缀语言建模(prefix language modelling):一种变体,图像和一段文本前缀作为上下文提供(不参与训练),模型只训练生成后续内容。PaLI、SimVLM 等模型采用这种方式。

  • 大多数现代 VLM 在预训练时组合多个目标(例如 BLIP 用 ITC + ITM + LM,CoCa 用 ITC + LM),然后在指令数据上用纯 LM 目标微调。

编程练习(使用 CoLab 或 notebook)

  1. 实现一个简单的基于注意力的图像描述解码器。用随机的「图像特征」作为编码器输出,训练解码器生成一个固定的描述,观察每一步解码时注意力权重如何在空间位置上移动。
import jax import jax.numpy as jnp import matplotlib.pyplot as plt # 模拟一个 4x4 空间网格的图像特征(16 个区域,维度=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 # query 投影 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 式步骤(这里仅为示意,用 linear + 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'Attending when\ngenerating after "{w}"') ax.axis('off') plt.suptitle('Attention Over Image Regions at Each Decoding Step') plt.tight_layout(); plt.show() # 试着改变 img_features,看注意力模式如何变化!
  1. 模拟视觉词元流水线:把图像切成图块,把图块投影到嵌入空间,与文本词元嵌入拼接,再对合并后的序列跑一层自注意力。
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 图块 -> 4 个图块 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"Number of patches: {patches.shape[0]}, patch dim: {patches.shape[1]}") # 第 2 步:把图块投影到嵌入维度(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 步:创建文本词元嵌入(模拟 3 个文本词元) text_tokens = jax.random.normal(k3, (3, d_model)) * 0.1 # 第 4 步:拼接视觉 + 文本词元 combined = jnp.concatenate([visual_tokens, text_tokens], axis=0) # (7, 16) print(f"Combined sequence length: {combined.shape[0]} (4 visual + 3 text)") # 第 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('Key'); ax.set_ylabel('Query') ax.set_title('Self-Attention: Visual (V) and Text (T) Tokens') plt.colorbar(im, ax=ax); plt.tight_layout(); plt.show() # 观察:文本词元关注视觉词元(跨模态注意力)!
  1. 实现用于视觉定位的坐标分词。给定一个边界框,把它转成离散词元;给定离散词元,重建边界框。在不同桶分辨率下可视化量化误差。
import jax.numpy as jnp import matplotlib.pyplot as plt def encode_bbox(bbox, num_bins=1000): """把 [0,1] 范围内的连续 bbox (x, y, w, h) 转成离散词元。""" tokens = jnp.round(jnp.array(bbox) * (num_bins - 1)).astype(jnp.int32) return tokens def decode_bbox(tokens, num_bins=1000): """把离散词元转回连续 bbox。""" 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"Bins={n_bins:>5d} | Tokens={tokens} | " f"Reconstructed={reconstructed} | Max error={error:.6f}") fig, ax = plt.subplots(figsize=(8, 4)) ax.plot(bin_sizes, errors, 'o-', color='#e74c3c', linewidth=2, markersize=8) ax.set_xlabel('Number of Bins'); ax.set_ylabel('Max Quantisation Error') ax.set_title('Bounding Box Quantisation Error vs Bin Resolution') ax.set_xscale('log'); ax.set_yscale('log') ax.grid(True, alpha=0.3); plt.tight_layout(); plt.show() # 试试:当桶非常少(比如 5)时会发生什么?误差什么时候可以接受?

作者与出处
原作者: HenryNdubuaku
来源:HenryNdubuaku
许可证:Apache-2.0
整理: 灏天文库整理
由灏天文库结构化整理,提供目录导航、全文检索与在线阅读,便于系统化学习
发布者: 作者: HenryNdubuaku 转发
评论区 (0)
U