Skip to content

视觉语言模型

视觉语言模型共同理解图像和文本,实现视觉问答、图像描述和视觉推理。本文涵盖VQA、图像描述、视觉定位,以及VisualBERT、BLIP、LLaVA、Flamingo、PaLI和Qwen-VL等融合视觉编码器与大型语言模型的架构。

  • 想象一位博物馆向导,他能看着一幅画并清晰地描述关于它的一切:画里有什么物体,讲述了什么故事,传达了哪些情感,并能回答参观者提出的任何问题。视觉语言模型(VLM) 就是计算领域的等价物——一个能够共同理解图像和文本的系统,从而描述视觉场景、回答关于它们的问题、遵循视觉指令,甚至根据自然语言查询在图像中定位特定物体。

  • VLM位于第08章介绍的视觉编码器与第07章的语言模型的交叉点。核心的工程挑战是桥接两个截然不同的表示世界:视觉骨干网络的空间连续特征图与语言模型的序列离散词元嵌入。本文中的每一种架构,其核心都是对不同答案的探索:如何融合视觉和语言?

高层次VLM分类图,展示了双编码器、融合编码器和编码器-解码器家族及其输入输出

视觉问答

  • 想象有人给你看一张照片并问:“公园里有多少只狗?”你不费吹灰之力地解析图像,定位狗,数出它们,然后给出答案。视觉问答(VQA) 就是这个问题形式化后的任务:给定图像 \(I\) 和自然语言问题 \(q\),预测答案 \(a\)

  • 该任务可以通过几种方式构建。最常见的是将VQA视为开放词汇分类:模型从常见答案的固定词汇表(例如VQA v2中排名前3129的答案)中选择答案。或者,也可以将其视为生成式问答,模型生成自由形式的文本字符串——这是现代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 & Manning,2019),包含需要多步推理的组合性问题;OK-VQA(Marino等人,2019),需要超出图像的外部知识;以及TextVQA(Singh等人,2019),答案取决于读取图像内的文字。

VQA流程:图像通过视觉编码器,问题通过文本编码器,它们的表示被融合,融合后的向量被分类为答案

  • 早期的VQA模型采用简单策略:从预训练的CNN(通常是ResNet或VGGNet的倒数第二层)提取图像特征,用LSTM(第06章)编码问题,然后组合它们。组合函数 \(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})\]
  • 整个模型通过最大化真实描述的似然进行端到端训练。推理时,使用波束搜索(第07章)来寻找高概率的描述。

  • Show and Tell的问题在于整个图像被压缩为一个单一向量。对于复杂场景,一个向量无法捕获所有相关细节。空间信息丢失了——模型在生成不同单词时无法“回头查看”图像的特定部分。

  • Show, Attend and Tell(Xu等人,2015)通过引入图像区域上的注意力解决了这个问题。CNN不再将图像编码为一个向量,而是生成一个空间特征网格(例如,来自VGGNet最后一个卷积层的 \(14 \times 14 \times 512\))。在每个解码步骤,模型计算这些空间位置上的注意力权重,产生一个突出显示与当前单词最相关区域的上下文向量。

  • 回顾第06章的注意力机制:解码器隐藏状态作为查询,空间特征作为键和值,注意力权重告诉模型看哪里。作者提出了两种变体:软注意力(可微分,所有区域的加权平均)和硬注意力(对单个区域进行随机采样,使用REINFORCE训练)。

基于注意力的图像描述:在每个解码步骤,模型关注图像的不同空间区域,在生成词“狗”时聚焦于狗的区域

  • 这些模型产生的注意力图具有显著的可解释性:当生成“狗”时,注意力峰值在狗的区域;当生成“海滩”时,它转移到沙子和水。这是最早证明注意力机制提供内置可解释性的有力证据之一。

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

  • 后来的描述模型引入了自底向上的注意力(Anderson等人,2018),其中目标检测器(Faster R-CNN,第08章)首先提出显著的图像区域,然后描述模型在这些区域特征上关注,而不是在均匀网格上。在基于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或带定位的描述等任务上的实用性。

融合编码器

  • 现在想象两位翻译在同一个房间里,积极讨论两份文档。他们可以指向特定的段落,互相提问,并建立共同的理解。这就是融合编码器模式。

  • 两种模态都被编码,然后通过交叉注意力层进行融合,其中来自一种模态的词元关注来自另一种模态的词元。图像首先由视觉编码器处理成一系列图像块或区域词元 \(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)\]
  • 这使得细粒度的交互成为可能:每个文本词元可以关注它所需的特定图像区域。VisualBERTVilBERTUNITER等模型使用这种模式。代价是你无法为检索预先计算单独的嵌入——每个图像-文本对都需要一次完整的前向传播通过融合层。

双编码器与融合编码器的对比:双编码器计算单独的嵌入和相似度分数,而融合编码器通过交叉注意力层合并模态

编码器-解码器

  • 编码器-解码器模式将视觉编码器与文本解码器相结合,解码器自回归地生成输出词元,类似于第07章的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)冻结,只学习连接它们的“胶水”。这种胶水由两个组件组成:一个感知器重采样器门控交叉注意力层

  • 感知器重采样器接收视觉编码器的可变长度输出(取决于图像分辨率),并将其压缩为一组固定的 \(N\) 个视觉词元(通常 \(N = 64\))。它的工作原理是初始化一组 \(N\) 个可学习的查询向量,并使用交叉注意力让这些查询关注完整的视觉编码器输出集。这本质上是将感知器架构(Jaegle等人,2021)作为瓶颈应用——无论输入图像大小如何,它都能产生一个紧凑的、固定大小的视觉表示。

\[z = \text{CrossAttn}(Q_{\text{learned}}, V_{\text{image}}) \in \mathbb{R}^{N \times d}\]
  • 门控交叉注意力层交错放置在冻结的语言模型层之间。在每一个这样的层中,语言模型的文本词元会交叉关注由感知器重采样器产生的视觉词元。关键在于,每个门控交叉注意力层都包含一个可学习的标量门 \(\alpha\),初始化为零,它将交叉注意力的输出乘以门值后再加到残差流中:
\[\hat{x} = x + \alpha \cdot \text{CrossAttn}(x, z)\]
  • 初始化 \(\alpha = 0\) 意味着在训练开始时,交叉注意力没有任何贡献,模型的行为完全像原始的冻结语言模型。在训练过程中,门逐渐打开,平滑地整合视觉信息,而不会扰乱语言模型的预训练表示。

Flamingo架构:冻结的视觉编码器输入到感知器重采样器,产生固定长度的视觉词元,这些词元通过交错在LM模块之间的门控交叉注意力和层注入冻结的LM中

  • Flamingo原生支持交错的图像-文本序列。你可以向其输入包含多个图像与文本交织的提示,例如:"[图像1] 这是一只猫。[图像2] 这是一只狗。[图像3] 这是一只___。" 模型通过视觉编码器和感知器重采样器处理每个图像,产生的视觉词元被插入到文本序列中的相应位置。语言模型的因果注意力掩码确保每个文本词元只能关注当前及之前图像的视觉词元。

  • 这种交错使得强大的少样本多模态学习成为可能。通过在上下文中提供几个图像-文本示例,Flamingo可以在没有任何梯度更新的情况下执行新任务。在VQAv2、OK-VQA和图像描述等基准上,拥有80B参数的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)。一个投影层 \(W\) 将这些视觉特征映射到LLM的嵌入维度:

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

LLaVA架构:CLIP ViT将图像编码为图像块特征,线性投影将它们映射到LLM嵌入空间,投影后的视觉词元被添加到文本词元之前,并馈入LLM

  • 视觉指令微调是LLaVA的关键训练创新。作者使用GPT-4从COCO图像中生成了158,000个多模态指令跟随示例。每个示例包含一张图像配对一个对话式指令(例如,“详细描述这张图像”,“这张图像有什么不寻常之处?”,“如果我是游览此地的游客,我应该知道什么?”)。模型被训练为在给定图像和指令的情况下生成GPT-4撰写的响应。

  • 训练分两个阶段进行。阶段1(预训练):仅训练投影层 \(W\),使用图像-描述对(来自CC3M的595K对),同时视觉编码器和LLM保持冻结。这教会了 \(W\) 将视觉特征与LLM的嵌入空间对齐。阶段2(微调):投影层和LLM在指令跟随数据上共同微调,而视觉编码器保持冻结。这教会了模型遵循复杂的视觉指令。

  • LLaVA-1.5 通过三个关键改动改进了原始模型:用双层MLP替换单层线性投影(更具表达力的映射);使用更高分辨率的图像(336px而不是224px,产生更多的图像块词元);以及将学术VQA数据集加入训练混合中。这些看似微小的改动带来了基准性能的大幅跃升。

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

扩展视觉语言模型

  • 该领域从概念验证VLM迅速发展为在数十亿图像-文本对上训练的工业级系统。三个模型家族展示了不同的扩展方法。

PaLI

  • PaLI(Pathways Language and Image model,Chen等人,2022,Google)同时扩展了视觉编码器和语言模型。PaLI使用ViT-e(4B参数)作为视觉编码器,使用mT5(13B参数)作为语言模型,总共17B参数。图像被编码为一系列图像块词元,这些词元被添加到文本词元之前,然后馈入编码器-解码器结构的mT5。

  • PaLI的关键见解是:扩展视觉编码器与扩展语言模型同样重要。先前的工作通常使用固定的、中等规模的视觉骨干网络(例如ViT-B或ViT-L),并将所有参数预算投入LLM。PaLI表明,一个4B参数的ViT-e,在JFT-4B(40亿张带标签图像)上预训练,能显著提高OCR和空间推理等细粒度视觉任务的性能。

  • PaLI在WebLI上训练,这是一个包含109种语言、100亿图像-文本对的数据集,使其天生多语言。模型使用任务混合进行预训练:图像描述、VQA和图像-文本匹配,全部被转化为文本到文本生成(遵循第07章的T5范式)。PaLI-X(55B参数)和PaLI-3(5B,使用SigLIP作为视觉编码器)是后续迭代版本。

Qwen-VL

  • Qwen-VL(Bai等人,2023,阿里巴巴)在Qwen LLM的基础上增加了ViT视觉编码器和一个单层交叉注意力模块(类似于Flamingo的感知器重采样器),该模块将视觉编码器的输出压缩为一组固定的256个视觉词元。视觉词元与文本词元拼接,由Qwen LLM处理。

  • Qwen-VL的训练使用三阶段方案。阶段1:在14亿弱监督图像-文本对上预训练,仅解冻视觉编码器。阶段2:在包括VQA、图像描述、定位和OCR数据集的高质量数据上进行多任务预训练,解冻整个模型。阶段3:在指令跟随和对话数据上进行监督微调。这种从嘈杂网络数据到精心策划指令数据的渐进式精炼,是大多数现代VLM共有的模式。

  • Qwen2-VL(2024)引入了动态分辨率支持:不再将所有图像调整为固定尺寸,而是在保持原生分辨率的情况下,动态调整视觉词元的数量。更高分辨率的图像产生更多的词元,更低分辨率的图像产生更少的词元。这提高了在文档理解和细粒度识别等对细节敏感的任务上的性能,而不会在低分辨率输入上浪费计算。

InternVL

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

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

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

  • 三个模型家族共有的一个主题是训练数据策划的重要性。原始的从网页抓取的图像-文本对是嘈杂且常常错位的。连续的训练阶段逐步过滤和精炼数据,从数十亿对嘈杂数据到数百万个高质量的指令示例。最终微调数据的质量往往比模型的原始参数计数更重要。

定位与指代

  • 想象一下,在人群中指着一个人说“戴红帽子的女人”。你是在用语言指代一个特定的空间区域。视觉定位则是反过来:给定一张图像和一个自然语言表达式,模型必须识别(定位)所指代的对象。指代表达理解输出一个边界框;指代表达分割输出一个像素掩膜。

  • 形式化地说,给定图像 \(I\) 和指代表达 \(r\)(例如,“左边那只棕色的大狗”),模型预测一个边界框 \(b = (x, y, w, h)\) 或一组坐标来定位所指物。数据集包括RefCOCORefCOCO+RefCOCOg,每个都包含带有多个物体和针对每个物体的明确指代表达的图像。

  • 早期的定位模型使用两阶段方法:首先生成区域提议(来自Faster R-CNN或类似模型),然后使用融合模型对每个提议相对于语言查询进行评分。得分最高的区域即为预测。这种方法计算量大,且受限于提议的质量。

  • 现代VLM将定位直接整合到生成式框架中。关键思想是将边界框坐标表示为文本词元。你将连续的坐标空间离散化到多个区间(例如,为 \(x, y, w, h\) 各设1000个区间),并向词汇表中添加像 <loc_342> 这样的特殊位置词元。然后模型通过输出一系列位置词元来生成边界框:

\[\text{输出: } \texttt{<loc\_102><loc\_215><loc\_487><loc\_398>}\]
  • 这种分词技巧允许任何自回归语言模型执行定位,而无需任何架构更改——它只需学会“说坐标”。Pix2Seq(Chen等人,2022)在目标检测中开创了这种方法,而Qwen-VL、Ferret和Kosmos-2等模型将其扩展到了指代表达理解和短语定位。

  • Kosmos-2(Peng等人,2023,微软)通过将空间位置表示为嵌入在生成文本中的特殊词元,为多模态LLM增加了定位能力。例如,它可以生成:"一只 <phrase> 金毛寻回犬 </phrase> <box> <loc_102> <loc_215> <loc_487> <loc_398> </box> 正在接飞盘。" 这种文本和空间词元的交织使得同时进行图像描述和定位成为可能。

通过坐标分词进行定位:模型生成文本词元,其中穿插离散化的边界框坐标词元,定位描述中提到的物体

  • 指向比定位更进一步:模型预测一个点(通常是指向物体的中心),而不是边界框。这对于交互式应用很有用,例如用户问“最近的出口在哪里?”,模型返回一个叠加在图像上的坐标。像ShikraFerret这样的模型除了基于框的定位外,还支持基于点的指代。

无OCR文档理解

  • 传统的文档理解流程很复杂:首先运行OCR引擎提取文本和布局,然后将提取的文本输入语言模型。这种多阶段方法很脆弱——OCR错误会向下游传播,空间布局信息常常丢失或表示不佳。如果模型能像你一样直接从像素读取,那会怎样?

  • Donut(Document Understanding Transformer,Kim等人,2022)完全摒弃了OCR。它使用Swin Transformer(第08章)作为视觉编码器来处理文档图像,并使用一个BART风格的Transformer解码器直接从视觉特征生成结构化文本输出。解码器可以根据任务生成JSON、键值对或纯文本。

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

  • Donut解码器使用特殊的提示方案:任务由一个提示词元(例如,用于分类的 <doc_class> 或用于收据解析的 <parse_receipt>)指定,模型以此为条件生成输出。这种统一接口允许单个模型处理多个文档理解任务。

  • Pix2Struct(Lee等人,2023,Google)将无OCR的思想应用于网页理解以及图表/图形理解。关键的预训练目标是截图解析:给定一张网页的带掩码截图,模型生成产生可见区域的底层HTML。这教会了模型理解视觉渲染和结构化标记之间的关系。

  • Pix2Struct引入了可变分辨率输入处理:不是将所有图像调整为固定大小(这会扭曲宽高比并破坏精细文本),而是在保持原始宽高比的同时,将图像打包成固定数量的图像块。一个又高又窄的文档产生一个又高又窄的图像块网格。这对于文档理解至关重要,因为宽高比携带着语义信息(收据是窄而高的;电子表格是宽而短的)。

无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使用感知器重采样器(\(M = 64\));Qwen-VL使用交叉注意力(\(M = 256\));Q-Former(在BLIP-2中使用,Li等人,2023)使用一组 \(M = 32\) 个可学习的查询词元,它们交叉关注视觉编码器的输出。

  • 步骤4:投影。 视觉词元(完整集或压缩集)通过线性层或MLP投影到语言模型的嵌入空间中。投影后,视觉词元与文本词元嵌入具有相同的维度,可以与之拼接。

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

视觉词元流程:图像块被提取,由ViT编码,可选地由感知器或Q-Former压缩,投影到LLM维度,并与文本词元拼接

  • 视觉词元的数量直接影响计算成本。每个视觉词元都参与LLM的自注意力,而自注意力的复杂度是序列长度的平方。具有许多图像块的高分辨率图像可能产生数百或数千个视觉词元,主导LLM的上下文窗口。这就是词元压缩重要的原因:将576个视觉词元减少到64个,可将视觉部分对注意力的贡献减少约9倍。

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

训练目标

  • VLM根据架构模式使用目标的组合进行训练:

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

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

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

\[\mathcal{L}_{\text{LM}} = -\sum_{t=1}^{T} \log p(w_t \mid w_{<t}, V)\]
  • 前缀语言建模: 一种变体,提供图像和文本前缀作为上下文(不对其进行训练),模型仅被训练生成后续部分。这在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  # 查询投影
    
    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-like步骤(仅为说明,使用线性+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}"后的注意力')
        ax.axis('off')
    plt.suptitle('每个解码步骤中图像区域上的注意力')
    plt.tight_layout(); plt.show()
    # 尝试更改img_features,观察注意力模式如何变化!
    

  2. 模拟视觉词元流程:将图像块化,将图像块投影到嵌入空间,与文本词元嵌入拼接,并在组合序列上运行单层自注意力。

    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"图像块数量: {patches.shape[0]}, 图像块维度: {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.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)词元')
    plt.colorbar(im, ax=ax); plt.tight_layout(); plt.show()
    # 观察:文本词元关注了视觉词元(跨模态注意力)!
    

  3. 为视觉定位实现坐标分词。给定一个边界框,将其转换为离散词元;给定离散词元,重建边界框。可视化不同区间分辨率下的量化误差。

    import jax.numpy as jnp
    import matplotlib.pyplot as plt
    
    def encode_bbox(bbox, num_bins=1000):
        """将连续的边界框(x, y, w, h)在[0,1]范围内转换为离散词元。"""
        tokens = jnp.round(jnp.array(bbox) * (num_bins - 1)).astype(jnp.int32)
        return tokens
    
    def decode_bbox(tokens, num_bins=1000):
        """将离散词元转换回连续的边界框。"""
        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} | 词元={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('边界框量化误差与区间分辨率的关系')
    ax.set_xscale('log'); ax.set_yscale('log')
    ax.grid(True, alpha=0.3); plt.tight_layout(); plt.show()
    # 尝试:如果区间数很少(例如5)会发生什么?误差在什么情况下是可以接受的?