多模态
- 多模态发展
- 图像预处理
- 自适应图像切割
- 弥补语义损失
- 视觉编码器
- 视觉文本特征对齐
- 线性映射或MLP
- Cross Attention
- Perceiver Resampler
- Q-Former(Querying Transformer)
- 模型结构
- 表示学习
- 大语言模型
这篇文档主要讲解目前比较流行的缝合式的多模态大模型的基本模块。缝合式多模态大模型的架构:包括视觉编码器、视觉文本对齐层、大语言模型,流程主要是通过视觉编码器提取图像特征,再通过视觉文本对齐层将图像和文本特征对齐,也包括对视觉特征的在提取,主要是为了减少视觉 token 的数量,然后将视觉特征和文本特征一起输入到大模型中获取结果。涉及到的技术主要包括以下几个点:
- 图像预处理:从刚开始直接对图像进行缩放到固定尺寸到现在的保持图像宽高比的同时将图像切分为多块进行处理;
- 视觉编码器:目前比较常用的是使用 CLIP 训练的 ViT 图像编码器以及 SigLip 图像编码器;
- 视觉映射器:主要作用是将图像特征映射到与文本特征同样的维度、减少视觉 token 的数量;
- 大语言模型:学习图像和文本特征的关系,输出结果。
CLIP
Flamingo
BLIP(BLIP2、InstructBLIP)
BEIT(BEIT-2、BEIT-3)
LLaVA(LLaVA1.5、LLaVA-Next)
MiniGPT4
mPLUG(mPLUG-Owl1-3、mPLUG-DocOwl)
Qwen-VL
DeepSeek-VL
Idefics2
InternVL
MiniCPM-V
多模态发展
第一代:支持的图像分辨率非常低(224×224),研发的重心主要聚焦在模态对齐的原型验证上(CLIP、Flamingo、BLIP2、LLaVA、MiniGPT4、mPLUG-Owl)
第二代:从研发的角度主要是要为LMM增加目标定位的能力,相比粗粒度的图像内容问答,增加目标定位能力是多模态 LMM 能够在Agent、机器人、自动驾驶、安防巡检等场景中落地实用的必要前提(LLaVA1.5、Qwen-VL)
关键问题:
- 高分辨率输入的支持:2代LMM的分辨率基本都处在336~448之间
- 图文模态间的竞争:几乎所有的工作都回避了语言模型能力下降的问题
- 多模态的Scaling Law:缝合路线的天花板是否已经出现
第三代:支持高分辨率的文档图像
图像预处理
图像分辨率的问题:从一开始的图像分辨率固定为224,接下来图像分辨率增加至336-448,到最后支持1344的分辨率,基本上可以支持大部分的图片。
为什么需要支持高分辨率,分辨率不高带来的问题有哪些:
- 对于一些文档图像(字符密集图像),如果不考虑宽高比强制缩放至 448x448 会对其中的文本信息造成影响,导致对图像中的文本信息不能很好的理解,也就会影响最后的回答,比如 OCR 任务、VQA任务、信息抽取任务等。
为什么不一开始就支持很大的分辨率,主要制约因素是训练的成本和语言模型的窗口的大小:
-
图像分辨率高,图像编码后的图像 token 越多,对应的训练成本就越高
-
刚开始的语言模型的窗口大小普遍在1K 左右,过长的图像 token 长度会压缩文本输入的空间,导致问答能力下降
比如 224 x 224 的图像,patch size 为 14,则编码后的图像 token 数就是 224 x 224 14 x 14 = 256 \frac{224x224}{14x14}=256 14x14224x224=256 ,同样分辨率 448 的图像,对应图像 token 数是 1024,如果是 1344x896 的图像,首先分为 3x2=6 的 448x448 的图像块,总的图像 token 数就是 6x1024=6144。一刚开始的语言模型支持的窗口大小压根就不能支持高分辨率的图像,随着后续语言模型的窗口大小越来越大,才能支持更大分辨率的图像。
目前是怎么解决高分辨率的问题的: 自适应图像切割 + 全局图像,Mini-Monkey 认为对图像切割之后会对图像中的目标或文字的语义造成损失,提出了弥补语义损失的自适应分割。
自适应图像切割
自适应图像切割(混合分辨率支持)(LLaVA-Next、QwenVL-plus/max、InternVL、MiniCPM-V),以 InternVL2 的代码进行分析,其他的图像处理方式会有一些差别,但基本差不多。
- 首先预定义一些宽高比,可支持多种切割方式
target_ratios = set( (i, j) for n in range(min_num, max_num + 1) for i in range(1, n + 1) for j in range(1, n + 1) if i * j <= max_num and i * j >= min_num )
- 然后从这些宽高比中找到与原图宽高比最接近的一个,用于对图像切分
def find_closest_aspect_ratio(aspect_ratio, target_ratios, width, height, image_size): best_ratio_diff = float('inf') best_ratio = (1, 1) area = width * height for ratio in target_ratios: target_aspect_ratio = ratio[0] / ratio[1] ratio_diff = abs(aspect_ratio - target_aspect_ratio) if ratio_diff < best_ratio_diff: best_ratio_diff = ratio_diff best_ratio = ratio elif ratio_diff == best_ratio_diff: if area > 0.5 * image_size * image_size * ratio[0] * ratio[1]: best_ratio = ratio return best_ratio
- 然后根据找到的宽高比对图像进行缩放,这样可以保证原图的宽高比。然后再切分为多个 448x448 的小块
target_width = image_size * target_aspect_ratio[0] target_height = image_size * target_aspect_ratio[1] blocks = target_aspect_ratio[0] * target_aspect_ratio[1] # resize the image resized_img = image.resize((target_width, target_height)) processed_images = [] for i in range(blocks): box = ( (i % (target_width // image_size)) * image_size, (i // (target_width // image_size)) * image_size, ((i % (target_width // image_size)) + 1) * image_size, ((i // (target_width // image_size)) + 1) * image_size ) # split the image split_img = resized_img.crop(box) processed_images.append(split_img)
- 增加全局图像,把原图缩放至 448x448
if use_thumbnail and len(processed_images) != 1: thumbnail_img = image.resize((image_size, image_size)) processed_images.append(thumbnail_img)
- 最后将所有的图像块堆叠在一起
pixel_values = [transform(image) for image in images] pixel_values = torch.stack(pixel_values) # (N, H, W, C)
最终,把多个分块后的图像输入到图像编码中提取图像特征。
弥补语义损失
Mini-Monkey
考虑到对图像切割之后会对图像中的目标或文字的语义造成损失,提出了弥补语义损失的自适应分割。
在原始的分块基础上又增加了一种分块方法,该方法从预先定义的宽高比中选择一种与先前分割不相等、不成比例的宽高比作为候选,从中选择一种最优宽高比对图像进行切分。再和自适应切割、全图缩放拼接在一起作为输入到图像编码中的图像块。这样可以缓解由于切割带来的语义损失,
new_target_ratios = []
for i in target_ratios:
if prior_aspect_ratio[0]%i[0] or prior_aspect_ratio[1]%i[1]:
new_target_ratios.append(i)
else:
continue
视觉编码器
- ViT(使用CLIP预训练的)、SigLip(使用sigmoid损失函数)
- 混合图像编码器(SigLip+SAM)(DeepSeek-VL)
- ResNet
视觉文本特征对齐
作用:
-
对齐视觉特征和文本特征的维度
-
减少视觉特征的 token 数
由于分辨率支持的越来越高,视觉特征的 token 数也越来越多,虽然大语言模型可以支持上万的 token,但是视觉 token 太多会压缩文本特征的表征。另一方面,由于图像特征的稀疏性,有必要减少视觉 token,提炼出更重要的特征。
线性映射或MLP
在视觉特征较少的时候,不需要减少视觉特征,可简单的使用一个线性层或MLP对齐视觉特征和文本特征的维度,然后视觉特征和文本特征拼接输入到大模型中。
比如 DeepSeek-Vl、LlaVA、mPLUG-Owl3 采用的这种方式,InternVL2 采用 Pixel Shuffle + MLP 来减少视觉 token 数并对齐文本特征维度。
Cross Attention
有些多模态大模型,仅仅使用一个 Cross Attention 来减少视觉特征的 token数。通过定一个固定数量的可学习的 queries,通过和视觉特征进行交叉注意,来提取视觉特征。
比如 mPLUG-Owl1、mPLUG-Owl2、MiniCPM-V 采用的这种方式,
Perceiver Resampler
Flamingo 提出了 Perceiver Resampler
首先定义固定数量的可学习的 queries,然后通过和视觉编码器提取出的特征通过 resampler 输出固定长度的视觉特征。具体操作可以看以下的伪代码。其目的是为了将变长的特征数量使用固定长度的特征来表示。
def perceiver_resampler(
x_f, # The [T, S, d] visual features (T=time, S=space)
time_embeddings, # The [T, 1, d] time pos embeddings.
x, # R learned latents of shape [R, d]
num_layers, # Number of layers
):
"""The Perceiver Resampler model."""
# Add the time position embeddings and flatten.
x_f = x_f + time_embeddings
x_f = flatten(x_f) # [T, S, d] -> [T * S, d]
# Apply the Perceiver Resampler layers.
for i in range(num_layers):
# Attention.
x = x + attention_i(q=x, kv=concat([x_f, x]))
# Feed forward.
x = x + ffw_i(x)
return x
Q-Former(Querying Transformer)
BLIP2提出了Q-Former,旨在减少视觉特征的维度并提取出与文本更相关的视觉特征。
Q-Former 通过预先定义一个可学习的固定长度的 queries,通过与图像特征的交叉注意力来学习 queries 的表示,这个学到的 queries 就是提取出的图像特征,并且通过与文本的交互,这个特征也是与文本最相关的。
模型结构
Q-Former 的网络结构如上图所示,包括图像 Transformer 和 文本Transformer ,这两个模块的 self attention 共享特征, 图像编码器输入为可学习的 queries 和 图像编码器输出的图像特征,主要是通过 queries 学习图像特征的表示,文本 Transformer 可以作为文本编码器也可以作为文本解码器。
可学习的 queries 通过 self attention 可以相互交互,并通过 cross attention 与图像特征交互。除此之外,queries 还可以通过 self attention 与输入的文本交互。在 BLIP2 中,queries 的长度设置为 32, 维度设置为 768,可以大大减少图像特征的 token 数。queries 也可以根据需要设置不同的大小。
使用 BERTbase 的预训练权重初始化 QFormer,而交叉注意力层是随机初始化的。
表示学习
在表示学习阶段,将 Q-Former 与冻结的图像编码器链接,使用图像-文本对进行训练,最终让 queries 可以学习到提取文本信息量最大的视觉表示。主要通过优化三个预训练目标来学习:图像-文本对比学习、基于图像的文本生成、图像-文本匹配。
图像-文本对比学习(ITC)
图像文本对比学习学习对齐图像表示和文本表示,使它们的互信息最大化。通过将正对的图像-文本相似度与负对的图像文本相似度进行对比来实现。首先将图像 Transformer 的输出 queries 表示与文本 Transformer 输出的文本表示对齐,由于 queries 包含多个向量,所以先计算每个向量和文本表示的相似度,选择得分最高的作为图像-文本的相似度。在这一部分,queries 和文本不允许相互看到,避免信息泄漏。
这里为什么要计算得分最高的一个作为图像-文本的相似度呢?在BLIP和CLIP的图像-文本对比学习是有区别的。在CLIP里边,N个图像和N个文本最终图像编码器和文本编码器输出的N个图像向量和N个文本向量,可以直接计算出N个图像和N个文本的相似度。
在BLIP2中,因为需要学习使用 queries 来表示图像特征,而每个 queries 包含 32 个向量,所以需要计算出 queries 中每个向量和文本向量的相似度,BLIP2 选择最高的那个作为一张图和文本的相似度。
基于图像的文本生成(ITG)
基于图像的文本生成损失基于给定的输入图像来训练 Q-Former 的文本 Transformer (文本解码器)生成文本。由于图像编码器和文本解码器不能直接交互,需要通过 queries 先从图像特征中提取与文本相关联的信息,然后输入到文本解码器中和文本一起生成文本。在这一部分,由于是生成文本,所以在注意力掩码部分,queries 可以互相看见,但不能看见文本。每个文本 token 可以看见 queries 及其之前的文本 token 。
图像-文本匹配(ITM)
图像-文本匹配目的是学习图像和文本表示之间的细粒度对齐。是一个二分类任务,要求模型预测图像-文本对是匹配还是不匹配。这一部分使用双向自注意力掩码,所有的 queries 和文本都可以互相关注。使用图像 Transformer 输出的 queries 来获得图像和文本交互的信息,然后将每个query 输入到线性分类器中获得分数,并取所有 query 的评分分数作为图像-文本的输出分数。
通过这三个训练目标,Q-Former 就可以提取出与文本信息相关的视觉特征了。
BLIP2-Q-Former 相关源码
@classmethod
def init_Qformer(cls, num_query_token, vision_width, cross_attention_freq=2):
encoder_config = BertConfig.from_pretrained("bert-base-uncased")
encoder_config.encoder_width = vision_width
# insert cross-attention layer every other block
encoder_config.add_cross_attention = True
encoder_config.cross_attention_freq = cross_attention_freq
encoder_config.query_length = num_query_token
Qformer = BertLMHeadModel.from_pretrained(
"bert-base-uncased", config=encoder_config
)
query_tokens = nn.Parameter(
torch.zeros(1, num_query_token, encoder_config.hidden_size)
)
query_tokens.data.normal_(mean=0.0, std=encoder_config.initializer_range)
return Qformer, query_tokens
大语言模型
部分多模态大模型会对大语言模型针对视觉特征的融入做一些模型上的改造,比如:
-
门控交叉注意力(Flamingo)
-
Hyper Attention(mPLUG-Owl3)