【读点论文】Mini-Monkey: Alleviating the Semantic Sawtooth Effect for MLLMs一种预处理方法保留高分辨率信息,换了一个更适配任务的多模态模型
在本研究中,我们介绍了一种互补图像金字塔(CIP ),旨在减轻多层线性模型的语义锯齿效应,从而提高其有效处理高分辨率图像的能力。CIP是即插即用的,可以低成本无缝集成到各种多模态大语言模型中。我们展示了所提出的CIP在不同架构、不同参数和不同使用配置下的有效性,从而带来一致的性能改进。此外,为了提高计算效率,我们提出了一种比例压缩机制(SCM)来压缩视觉标记。CIP不仅提高了一般的多模态理解性能,
Mini-Monkey: Alleviating the Semantic Sawtooth Effect for Lightweight MLLMs via Complementary Image Pyramid
Abstract
- 最近,在多模态大语言模型(MLLM)中,将图像缩放到高分辨率受到了广泛关注。大多数现有实践采用滑动窗口式裁剪策略来适应分辨率的提高。然而,这种裁剪策略很容易切断对象和连接区域,这会引入语义不连续性,从而阻碍MLLM识别小的或不规则形状的对象或文本,导致我们称之为语义锯齿效应的现象。这种效果在轻量级MLLM中尤为明显。为了解决这个问题,我们引入了一种互补图像金字塔(CIP),这是一种简单、有效、即插即用的解决方案,旨在减轻高分辨率图像处理过程中的语义不连续性。特别是,CIP动态构建了一个图像金字塔,为基于裁剪的MLLM提供互补的语义信息,使它们能够在各个层面上丰富地获取语义。
- 此外,我们引入了一种尺度压缩机制(SCM),通过压缩冗余的视觉标记来减少额外的计算开销。我们的实验表明,CIP可以持续提高不同架构(如MiniCPM-V-2、InternVL2和LLaVA OneVision)、各种型号容量(1B→8B) ,以及不同的使用配置(免训练和微调)。利用所提出的CIP和SCM,我们引入了一种轻量级的MLLM Mini Monkey,它在一般多模式理解和文档理解方面都取得了显著的性能。在OCRBench上,2B版Mini Monkey甚至以12分的优势超越了8B版InternVL2-8B。此外,训练MiniMonkey很便宜,只需要8个RTX 3090 GPU。该代码可在以下网址获得 : Monkey/project/mini_monkey at main · Yuliang-Liu/Monkey · GitHub.
- 论文地址:[2408.02034] Mini-Monkey: Alleviating the Semantic Sawtooth Effect for Lightweight MLLMs via Complementary Image Pyramid.
- Mini-Monkey是一款轻量级多模态大模型(MLLM),旨在通过互补图像金字塔(CIP) 缓解高分辨率图像处理中的语义锯齿效应(因裁剪导致的语义不连续问题),并结合尺度压缩机制(SCM) 减少视觉令牌冗余以降低计算成本。CIP 通过动态构建多尺度图像金字塔提供互补语义信息,SCM 利用 LLM 的注意力层筛选关键令牌。定位:轻量级多模态大模型(MLLM),目标:缓解语义锯齿效应,提升高分辨率处理能力,优势:高效训练(8台RTX 3090)、性能超越更大模型。
- 互补图像金字塔(CIP)作用:动态生成多尺度图像,提供互补语义。分组操作(详细组、自适应组、全局组)+动态生成图像。**尺度压缩机制(SCM)**作用:减少视觉令牌冗余,降低计算成本。利用LLM注意力层筛选关键令牌,无额外参数。
- 现有多模态模型处理高分辨率图像时,常采用滑动窗口裁剪策略,但易分割物体或文本,导致语义锯齿效应:语义模糊:被分割的物体可能被误认(如豚鼠鼻子被误判为猴子);语义破坏:文本分割后语义改变(如 “breakdown” 被分为 “break” 和 “down”)。该问题在轻量级 MLLM 中更显著,而重叠裁剪会引入冗余信息,反而降低性能。
Introduction
-
最近,大型语言模型(LLMs)因其强大的文本理解和生成能力而受到广泛关注。研究人员正在积极探索将视觉编码器集成到LLM中的方法,以将其升级到多模态大型语言模型(MLLM)。一些方法采用 Q-former,而另一些方法使用线性投影。尽管结果很有希望,但它们仅限于处理低分辨率图像,这限制了它们执行详细场景分析的能力。
-
为了解决这一局限性,最近的许多效果旨在使MLLM能够处理高分辨率图像。一个直接的解决方案是采用可以处理高分辨率图像的视觉编码器。然而,开发高质量的视觉编码器需要大量的训练资源。另一种更节省资源的策略是非重叠裁剪,它将高分辨率图像拆分为一组低分辨率子图像。
-
虽然非重叠裁剪策略显示出了有希望的结果,但它不可避免地切断了对象和连接区域,由于语义不连续,MLLM在识别小或不规则形状的对象时遇到了困难,特别是在文档理解的背景下。这主要导致两种后果:i)语义歧义:如果一个对象或字符被分割,它可能会被错误识别;例如,图1(b)中的豚鼠鼻子在修剪后看起来很像猴子;2)语义损伤:如果一个单词或句子被分割,分割后的单词的含义将完全改变;如果单词“breakdown”被分为“break”和“down”,那么被分割的单词将与原始单词无关。为简单起见,本文将这些现象称为语义锯齿效应。为了减轻这种影响,一个相当简单的想法是采用重叠裁剪。
-

-
图1:不同图像裁剪策略的比较。(a)输入图像;非重叠种植;重叠种植;(d)我们的:互补形象金字塔。
-
-
然而,如图1(c)所示,这种策略将导致大量重复信息的处理。这种冗余可能会导致MLLM产生幻觉。此外,根据我们在第3.3节中的消融研究,它甚至会降低性能。此外,在轻量级MLLM中可以更明显地观察到语义锯齿效应。具有增强理解能力和特征提取能力的较大MLLM通常可以在一定程度上缓解这个问题。即使对象被分割,这些模型也可以通过强大的特征提取来理解对象。
-
为了更明确地减轻语义锯齿效应,我们提出了一种即插即用的方法,称为互补图像金字塔(CIP)。CIP可以很容易地集成到各种基于裁剪的MLLM中,使它们能够处理高分辨率图像,同时减少语义锯齿效应。CIP动态构建了一个图像金字塔,为MLLM提供互补的语义特征,使其能够在各个层次上获得丰富的语义。如果对象语义在一个尺度上丢失,它们可以用另一个尺度的语义来补偿。
-
与之前通过修改模型架构来解决这个问题的工作不同,我们的方法侧重于丰富图像语义本身。因此,CIP可以很容易地集成到各种MLLM中,使其能够处理高分辨率图像,同时减少语义锯齿效应。考虑到CIP引入了一些额外的计算开销,我们进一步提出了一种用于计算资源有限的情况的尺度压缩机制(SCM)。SCM既不需要训练,也不需要参数。它利用LLM训练有素的注意力层和多尺度信息来生成注意力权重,进而用于压缩冗余令牌。利用所提出的CIP和SCM,我们介绍了一种轻量级的MLLM, Mini-Monkey.
-
我们的实验证明了所提出方法的有效性:1)在17个基准测试中,2B参数MiniMonkey的评估指标平均比InternVL2-2B高出2.4%;2)Mini Monkey在OCRBench上的得分为806,比8B参数模型InternVL2-8B高出12分。此外,我们观察到,直接微调性能良好的预训练MLLM不会增强,反而会降低其性能。相比之下,使用CIP进行微调可以促进训练过程以提高绩效。总之,这项工作的贡献可以总结如下:
- CIP:即插即用互补图像金字塔,旨在缓解多模态大型语言模型的语义锯齿效应;
- Mini Monkey:一种轻量级、高效、训练高效的多模态大型语言模型,集成了互补图像金字塔和比例压缩机制;
- 我们的方法在8个通用多模态理解基准和9个文档理解基准上取得了有希望的结果,证明了减轻语义锯齿效应的好处。
Related Work
Multimodal Large Language Models
-
近年来,大型语言模型(LLMs)取得了重大进展。从这一进步中,人们已经做出了许多努力,将视觉编码器集成到大型语言模型中,以实现视觉语言理解。一种常用的方法是线性投影法,它将视觉编码器的输出映射到与大型语言模型的文本特征相同的特征空间。一些方法,如 Q-Former 、 Perceiver Resampler 或 Abstractor ,引入了一组可学习的查询来促进这种集成。尽管有这些显著的进步,但由于分辨率的限制,以前的方法往往难以理解详细的场景。
-
为了解决这个问题,最近的研究采用了两种主要策略:1)直接使用支持高分辨率输入的视觉编码器。一些方法利用两个视觉编码器,一个用于处理高分辨率图像,另一个用于低分辨率图像。其他则利用单个视觉编码器。然而,这些方法需要额外的训练数据和参数,或者对高分辨率图像的处理注意力会显著增加计算需求。2)另一种更节省资源的方法是裁剪策略,该策略将高分辨率图像划分为多个较低分辨率的子图像进行处理。一些方法应用固定大小的裁剪方案。
-
其他人采用动态裁剪方法来保持原始图像的纵横比。尽管裁剪策略在几个多模态基准上取得了有希望的结果,但它不可避免地会产生语义锯齿效应:1)如果一个对象或字符被分割,它可能无法被识别;2)如果对单词或句子进行切分,会造成切分单词的语义损伤。例如,单词“Breakdown”可能被分为“Break”和“down”,从而对被分割的单词造成语义损害。这将限制模型理解细节场景的能力。尽管一些方法试图通过引入新的模块来解决这个问题,但是它们向原始模型引入了额外的参数,并且需要从头开始训练这个模块。相比之下,建议的CIP设计为无缝集成,无需引入额外的参数,提供即插即用的解决方案。
-
最近的一项工作也试图利用多尺度图像来提高模型的能力。然而,它们采用固定的分辨率,缺乏根据输入图像的分辨率动态生成补充语义的能力。相反,CIP动态地构建了一个图像金字塔,在为MLLMs裁剪后提供互补的语义特征,使它们能够丰富地获取所有级别的语义。
Lightweight Multimodal Large Language Models
-
由于与多模态大型语言模型(MLLMs)相关联的大量计算成本,最近的一些努力集中在开发用于快速开发和真实世界应用的更有效的模型上。例如,LLaVA-Phi 和Imp 将一个轻量级的大型语言模型与一个视觉编码器集成在一起,以开发一个强大的多模态系统。MobileVLM 通过集成一个轻量级下采样投影仪来减少视觉标记的数量,从而进一步节省资源。Bunny 通过有效的数据压缩技术提高效率,最大限度地减少所需的预训练数据集。TinyGPT-V 采用了专门为轻量级多模态模型设计的多阶段训练流程。以上型号仅支持低分辨率输入。
-
为了提高轻量级MLLMs中细节场景的理解,最常用的方法之一是裁剪策略。例如,MiniCPM-V系列 采用了一种自适应视觉编码方法来适应不同纵横比的高分辨率图像。类似地,InternVL2-2B 通过采用动态高分辨率裁剪策略增强了轻量级MLLMs的性能。尽管有这些进步,裁剪策略将引入语义锯齿效应,这大大限制了轻量级多模态大型语言模型的性能。具有增强的理解能力的更大的MLLMs可以在一定程度上缓解这个问题,如在第3.4.节中所讨论的。
Mini-Monkey
-
整体架构如图2所示。Mini-Monkey由CIP、视觉编码器、MLP层、比例压缩机制和大型语言模型(LLM)组成。最初,CIP根据输入图像的分辨率动态生成图像金字塔。然后,我们将这些图像分成一组子图像。这些子图像然后由视觉编码器和MLP层处理,以提取图像标记。缩放压缩机制调整这些图像标记,并将它们转发给LLM,后者随后生成最终答案。
-

-
图2: Mini-Monkey 的整体架构。H-Attn代表高关注权重。L-Attn代表低关注权重。具有低关注权重的令牌将被过滤。共享LLM层表示在SCM中使用LLM的块层。
-
-
互补图像金字塔(CIP),通过多尺度图像提供互补语义,缓解语义锯齿效应。分组操作:将预设宽高比分为三组 —— 详细组(>9 块,提供细节)、自适应组(3-8 块,增强边界特征)、全局组(1:1,提供全局视角);动态生成:从每组选一个宽高比生成图像,确保自适应组与详细组裁剪线不重叠(非整数倍关系),模拟跨窗口特征交互。插件式设计,无需额外参数,兼容多种裁剪基模型。
- CIP 通过动态构建互补图像金字塔,从详细组(提供细节)、自适应组(补充边界特征)、全局组(提供全局视角)生成多尺度图像,确保某一尺度丢失的语义可由其他尺度补偿。例如,自适应组与详细组的裁剪线不重叠,避免文本或物体被重复分割,从而减少语义模糊与破坏。
-
尺度压缩机制(SCM),减少视觉令牌数量,降低计算开销。利用 LLM 的前两层注意力层,以自适应组、全局组和文本令牌为查询,对详细组令牌计算注意力权重;筛选权重前 K 的令牌,保留关键信息。
- SCM 无需额外参数,利用 LLM 已训练的注意力层,以高信息密度令牌(自适应组、全局组、文本)为查询筛选低信息密度令牌(详细组),针对性更强。相比 FastV(压缩视觉和文本令牌),SCM 在 50% 压缩率下 TextVQA 得分高 1.3%,90% 压缩率下 OCRBench 得分高 9 分,更高效保留关键信息。
-
Mini-Monkey 在性能上为何能超越更大参数的模型?其核心在于 CIP 有效缓解了语义锯齿效应,使轻量级模型也能准确理解高分辨率图像中的细节(如小文本、不规则物体);同时 SCM 减少冗余令牌,提升计算效率。
-
在推理时,图像经过预处理后得到的
pixel_values和文本问题一起作为输入传递给模型。在demo.py中,调用model.chat方法进行推理,将图像和文本信息融合。-
image = Image.open(image_file).convert('RGB') images, target_aspect_ratio = dynamic_preprocess(image, image_size=input_size, use_thumbnail=True, min_num=min_num, max_num=max_num) transform = build_transform(input_size=input_size) pixel_values = [transform(image) for image in images] pixel_values = torch.stack(pixel_values) response, history = model.chat(tokenizer, pixel_values, target_aspect_ratio, question, generation_config, history=None, return_history=True) -
模型在接收到图像和文本信息后,会对其进行解析。在
Monkey/project/mini_monkey/internvl/model/internvl_chat/modeling_minimonkey_chat.py文件中的MiniMonkeyChatModel类的forward方法中,对图像特征进行提取,并将其与文本嵌入进行融合。 -
vit_embeds = self.extract_feature(pixel_values) vit_embeds = vit_embeds[image_flags == 1] input_embeds = self.language_model.get_input_embeddings()(input_ids).clone() selected = (input_ids == self.img_context_token_id) input_embeds[selected] = input_embeds[selected] * 0.0 + vit_embeds.reshape(-1, C)
-
-
训练过程中模态信息的融合与解析,在
Monkey/project/mini_monkey/internvl/train/minimonkey_chat_finetune.py文件中,定义了LazySupervisedDataset类用于数据准备。该类会读取图像和文本数据,并进行相应的预处理。-
class LazySupervisedDataset(Dataset): def __init__(self, ...): with open(meta['annotation'], 'r') as f: self.raw_data = f.readlines() self.root = meta['root'] self.cached_data_dict = {} self.tcs_loader = tcs_loader -
训练时的图像模态处理与推理时类似,也会进行多尺度自适应裁剪和归一化操作。在
LazySupervisedDataset类中,可能会调用dynamic_preprocess和build_transform等函数进行处理。在训练过程中,图像和文本信息同样会被融合。在MiniMonkeyChatModel类的forward方法中,将图像特征与文本嵌入进行融合,并输入到语言模型中进行训练。 -
outputs = self.language_model( inputs_embeds=input_embeds, attention_mask=attention_mask, position_ids=position_ids, past_key_values=past_key_values, use_cache=use_cache, output_attentions=output_attentions, output_hidden_states=output_hidden_states, return_dict=return_dict, ) -
模型对融合后的信息进行解析,并计算损失。在
MiniMonkeyChatModel类的forward方法中,使用交叉熵损失函数计算损失。 -
loss = None if labels is not None: shift_logits = logits[..., :-1, :].contiguous() shift_labels = labels[..., 1:].contiguous() loss_fct = CrossEntropyLoss() shift_logits = shift_logits.view(-1, self.language_model.config.vocab_size) shift_labels = shift_labels.view(-1) shift_labels = shift_labels.to(shift_logits.device) loss = loss_fct(shift_logits, shift_labels)
-
-
Mini - Monkey 在推理和训练过程中,对模态信息的融合与解析主要通过图像预处理、特征提取、特征融合和损失计算等步骤完成。在推理时,重点在于根据输入的图像和文本信息生成响应;在训练时,重点在于通过计算损失来优化模型参数。
-
在 Mini - Monkey 处理多模态数据时,主要通过以下几个方面来保证图像和文本信息的一致性:
- 多尺度自适应裁剪策略,Mini - Monkey 采用多尺度自适应裁剪策略,通过
dynamic_preprocess和dynamic_preprocess2函数实现。该策略会根据图像的宽高比选择合适的裁剪方式,确保图像的关键信息不被丢失。- 动态计算目标宽高比:通过
find_closest_aspect_ratio函数找到与原始图像宽高比最接近的目标宽高比,避免因裁剪不当导致的信息丢失。互补裁剪:在dynamic_preprocess2函数中,会生成与第一次裁剪方式互补的裁剪结果,进一步确保图像信息的完整性。这种互补裁剪策略可以捕获不同尺度和角度的图像信息,从而与文本信息更好地对应。
- 动态计算目标宽高比:通过
- 图像特征与文本嵌入的对齐,在模型内部,通过
MiniMonkeyChatModel类的forward方法实现图像特征与文本嵌入的对齐。- 使用视觉模型提取图像特征,得到
vit_embeds。将文本嵌入中特定的图像占位符(如img_context_token_id)替换为对应的图像特征,实现图像和文本信息在特征层面的对齐。
- 使用视觉模型提取图像特征,得到
- 训练过程中的对齐优化,在训练过程中,通过设计合适的损失函数来优化图像和文本信息的一致性。
- 交叉熵损失:在
MiniMonkeyChatModel类的forward方法中,使用交叉熵损失函数计算预测结果与标签之间的损失,促使模型学习图像和文本之间的关联。
- 交叉熵损失:在
- 推理过程中的一致性保证,在推理过程中,通过特定的处理流程确保图像和文本信息的一致性。
- 对输入的图像和文本进行统一的预处理,确保它们在进入模型之前具有一致的格式和表示。上下文感知:模型在生成回答时,会考虑图像和文本的上下文信息,确保生成的回答与图像内容一致。
- 多尺度自适应裁剪策略,Mini - Monkey 采用多尺度自适应裁剪策略,通过
Complementary Image Pyramid
-
现有的裁剪策略直接将高分辨率图像分成一组子图像,这将导致语义锯齿效应。为了解决这个问题,我们提出了一种即插即用的方法,称为互补图像金字塔(CIP),以促进不同尺度图像之间的协同作用,以减轻语义锯齿效应。CIP的过程如图2 (b)所示。
-
分组操作。我们首先生成一组预定义的纵横比。在测试期间,我们将 tiles 的最大数量限制为24。较大数量的 tiles 对应于具有较高分辨率的图像。然后,通过分组操作将这些纵横比分成三组,包括详细组Dg、适应性组Ag和全局组Gg。该分类基于以下标准:(1)宽高比,该宽高比导致9个以上的小块被分配给细节组,实现最大可能的图像尺寸,从而更清楚地描绘其中的对象。(2)对于产生3到8个 tiles 之间的纵横比,我们将它们分类到自适应组中,该组负责增强作物边界处的精细细节。(3)1:1的宽高比被指定给全局组,提供整个图像的低分辨率、综合视图。分组操作生成用于生成图像的三组不同的纵横比。
-
动态生成图像。在分组操作之后,我们将从每组中选择一个长宽比,以产生总共三个图像。对于细节组,我们计算输入图像的长宽比,然后通过计算绝对差值将其与细节组内的长宽比进行比较。最匹配输入图像的长宽比被表示为Dh,Dw,其中Dh是高度片的数量,Dw是宽度片的数量,然后被用于调整图像的大小并将调整大小的图像分成一系列子图像。在获得细节图像后,自适应组将基于Dh,Dw动态地生成一个纵横比,确保细节组上的裁剪线和自适应组上的裁剪线不重叠。为此,我们确保自适应组和细节组的纵横比不是彼此的整数倍。因此,我们将动态地删除任何正好是Dh,Dw倍数的纵横比。这可以表述如下:
-
∀ k ∈ Z , D h ≠ k ⋅ A h a n d D w ≠ k ⋅ A w ; ( 1 ) ∀k ∈ Z, D_h\neq k · A_h and D_w\neq k · A_w; (1) ∀k∈Z,Dh=k⋅AhandDw=k⋅Aw;(1)
-
其中Ah和Aw分别表示自适应组中长宽比的高度和宽度分量。然后,我们将通过从剩余的纵横比中选择最接近原始图像的纵横比的比率来调整图像的大小。
-
-
由于视觉编码器中子图像的独立处理,先前基于裁剪的MLLMs没有促进不同子图像之间的特征交互。在我们的方法中,与细节组相比,自适应组采用不同的纵横比来划分窗口,从而模拟细节组内的跨窗口特征交互。详细图像提供关于图像的详细信息。自适应图像增强了细节图像的裁剪边界处的精细特征。全局图像捕获模型的整个图像的全局信息。与以前的方法不同,提出的CIP从图像的角度减轻了语义锯齿效应,带来了几个优点:(I)它是即插即用的,不需要额外的参数;(ii)它与利用种植策略的现有MLLMs无缝集成,导致一致的性能改进;以及(iii)它可以在没有训练的情况下使用,并且它的有效性可以通过微调来进一步提高。
Scale Compression Mechanism
-
尽管所提出的CIP显著地增强了模型性能,但是某些场景可能会限制可用的计算资源的水平。为了应对这一挑战,我们引入了一种称为缩放压缩机制(SCM)的无参数令牌压缩方法,用于减少可视令牌,如图2 ©所示。详细组提供信息密度较低的令牌,而自适应组和全局组生成信息密度更高的令牌。因此,我们主要关注压缩来自详细组的标记。具体来说,来自MLLM的训练有素的LLM可以基于输入问题有效地选择必要的视觉特征。
-
因此,SCM利用LLM的第一层和第二层来选择可视标记,而不生成任何附加参数。包括 V d ∈ R L 1 × C 、 V a ∈ R L 2 × C 和 V g ∈ R L 3 × C V_d ∈ \R ^{L_1×C}、V_a ∈ \R^{L_2×C}~~和~~V_g ∈ \R ^{L_3×C} Vd∈RL1×C、Va∈RL2×C 和 Vg∈RL3×C 的输入视觉标记和文本标记 T t ∈ R T × C T_t ∈ \R^{T ×C} Tt∈RT×C 将被发送到LLM的层。Vd表示来自详细组的令牌。Va代表自适应组中的令牌。Vg表示来自全局组的令牌。值得注意的是,我们重用LLM的层作为这个LLM的层。LLM的层将输出一个注意力地图。我们使用来自自适应组、全局组和文本标记的可视标记来处理来自详细组的可视标记。注意力的计算可以用公式表示如下:
-
Q = c a t ( V a , V g , T t ) , ( 2 ) A t t n w = s o f t m a x ( ( Q + P E ( Q ) ) ( V d + P E ( V d ) ) T D ) . ( 3 ) Q = cat(V_a, V_g, T_t), (2)\\ Attn_w = softmax(\frac {(Q + PE(Q))(V_d + PE(V_d))^T} {\sqrt D} ). (3) Q=cat(Va,Vg,Tt),(2)Attnw=softmax(D(Q+PE(Q))(Vd+PE(Vd))T).(3)
-
其中PE表示位置编码,D表示LLM的维度。Cat()表示序列串联操作。在计算出注意力机制后,我们对注意力地图的第一维 A t t n w ∈ R ( L 2 + L 3 + T ) × L 1 Attn_w ∈ \R^{(L_2+L_3+T)×L_1} Attnw∈R(L2+L3+T)×L1 进行平均,得到一个权重向量 W a ∈ R L 1 W_a ∈ \R^{L_1} Wa∈RL1。随后,我们基于该权重向量Wa从细节层中选择顶部K个视觉特征。这些选择的标记,以及来自自适应组、全局组和文本标记的标记,被输入到LLM中以生成结果。与FastV 相比,SCM与CIP协同工作,通过使用相对信息密度较高的令牌来压缩信息密度较低的令牌,更具针对性。
-
Experiments
Implementation Details
-
我们使用 InternVL2-2B 作为基线来开发 Mini-Monkey。根据之前的工作,我们使用(448,448)作为InternViT 的输入分辨率。用于训练模型的训练数据集包括DocVQA 、ChartQA 、DVQA 、AI2D 、GeoQA+ 和LLaVA-150K (zh) 。我们使用AdamW 作为优化器。基础学习率是4e-8。
-
评价。继之前的工作之后,我们在11个通用多通道理解基准上评估Mini-Monkey,包括MathVista testmini ,SEED Image ,RealWorldQA ,AI2D test ,POPE ,CCBench ,MME 和HallusionBench 。对于文档理解,根据之前的工作,我们采用两种不同类型的度量来验证迷你猴子的性能。最初,我们利用基准提供的标准度量来评估Mini-Monkey。我们使用ChartQA 、DocVQA 、InfoVQA 、TextVQA 、STVQA 、FUNSD 、SROIE 、POIE 和OCRBench 等基准。我们还应用准确性度量来验证性能。对于这一指标,来自 Mini-Monkey 的完全抓住事实真相的响应被认为是真正的肯定。有关此指标和所用基准的更多详细信息,请参考附录。
Comparison with State-of-the-art Methods
-
一般多模态理解。我们按照对 Mini-Monkey 的一般多模态理解进行评估。结果显示在表1中。Mini-Monkey 在8项基准测试中超越了其他2B参数模型。在MathVista和POPE上,Mini-Monkey分别比之前最先进的方法InternVL2-2B高出1%和2.8%。在HallusionBench上,MiniMonkey比MiniCPM-V-2高出2.7%。这些结果展示了 Mini-Monkey 处理一般多模态理解和推理任务的能力。
-

-
表1:在8个多模态基准上与SoTA模型的比较。通用的多模态基准测试包括:MME ,RealWorldQA ,AI2D test ,CCBench ,SEED Image ,HallusionBench 和POPE 。此外,数学数据集包括MathVista testmini 。我们报告的MME结果是感知和认知得分的总和。特殊符号 代表OpenCompass排行榜的结果。
-
-
文件理解。对于第一种类型的指标,结果显示在表2中。我们对ChartQA使用宽松的准确度衡量标准,对DocVQA和InfoVQA使用ANLS,对TextVQA使用VQA评分。结果表明,在2B参数的多模态大语言模型中,Mini-Monkey达到了最先进的性能。与InternVL2-2B相比,我们的方法在TextVQA、InfoVQA和OCRBench上分别优于它2.6%、1.2%和22%。由于ChartQA的原始分辨率较小,所以它受裁剪操作的影响较小,从而使我们的方法有了微小的改进。
-

-
表2:在OCR相关任务上与最先进的MLLMs的比较。Mini-Monkey在2B参数MLLMs中取得了最好的结果。代表OpenCompass排行榜的结果。
-
-
值得注意的是,在OCRBench中,Mini-Monkey甚至分别超过了8B参数的大型多模态模型InternVL2-8B和9B参数的大型多模态模型GLM4-V 12和20。这些结果证明了互补图像金字塔在增强文档理解方面的优势。对于精确度指标,结果显示在表3中。与TextMonkey-9B 相比,Mini-Monkey的平均性能提高了15.6%,证明了我们方法的有效性。在多个文本相关的基准测试中,Mini-Monkey也优于最先进的方法。具体来说,Mini-Monkey在TextVQA、InfoVQA和POIE上分别比InternVL2-2B高出2.6%、3.2%和4.4%。这些结果进一步表明了Mini-Monkey在文档理解任务中的巨大潜力。
-

-
表3:我们的模型与现有多模态大型语言模型(MLLMs)在几个基准上的定量准确度(%)比较。遵循TextMonkey ,我们使用准确性度量来评估我们的方法。
-
Ablation Study
-
在本节中,我们对一般多通道理解和文档理解基准进行了消融研究,以验证我们方法的有效性。我们采用TextVQA 、OCRBench 、HallusionBench 、MME 和POPE 进行消融研究。
-
互补图像金字塔。我们进行了消融研究来调查CIP的有效性。我们将我们的方法与几个备选方案进行了比较:动态高分辨率策略,该策略保持纵横比以增加分辨率。固定大小的高分辨率策略,使用固定大小来提高分辨率。重叠种植策略使用高分辨率的方法,但作物覆盖。多尺度策略,将多尺度策略引入MLLM。如表4所示。提出的CIP取得了最好的效果。我们的方法在一般多模态理解和文档理解上都优于先前的多尺度策略。值得注意的是,覆盖裁剪策略不仅没有提高模型的性能,反而降低了模型的性能。
-

-
表4:互补图像金字塔的消融研究。我们将我们的方法与现有的裁剪策略和覆盖裁剪策略进行了比较。各种型号容量。我们进行了消融研究,以评估CIP对不同模型容量的模型的影响。如表6所示,CIP不断提高不同型号容量的性能。
-
-
不同的使用配置。为了进一步验证CIP引入的改进,我们对使用配置进行了实验:免培训配置和微调配置。如表5中所示。即使在未经训练的情况下使用,CIP也能提高性能。通过微调可以进一步提高性能。此外,我们惊讶地发现,CIP甚至可以促进模型微调过程。如表5中第二行所示。直接微调基线模型不仅不能提高性能,而且在某些情况下,还会导致性能下降。相反,在基线微调过程中纳入CIP,导致了一般多式联运理解和单据理解的实质性改善,如表5第4行所示。
-

-
表5:探索CIP的不同使用配置。训练代表微调模型。
-
-
将CIP合并到各种MLLMs。提出的互补图像金字塔(CIP)可以无缝集成到基于作物的方法。为了证明其有效性,我们将CIP整合到MLLM的各种结构中,如MiniCPM-V-2 ,InternVL 2 ,LLaVA-OV 。如表6中所示。CIP始终如一地提高了不同MLLM结构的性能,从而验证了我们方法的有效性。
-

-
表6:将互补图像金字塔(CIP)整合到其他MLLMs的消融研究。代表OpenCompass排行榜的结果。
-
-
子图像的数量。为了研究性能的提高是否归因于子图像数量的增加,我们通过递增基线的子图像数量来进行实验。总结在表8中的发现表明,增加子图像的数量不会导致更好的性能;反而可能导致衰落。相比之下,CIP可以有效地提高模型的性能。这些结果进一步证明了CIP的有效性。
-

-
表8:关于子图像数量的消融研究。该数字表示子图像计数。
-
-
规模压缩机制。我们将提出的尺度压缩机制与相关工作FastV 进行了比较。对于不同的方法,我们将视觉标记的数量压缩了50%。对于我们的方法和FastV,我们进一步进行了90%压缩的实验。遵循FastV的论文,我们将FastV中的K设为2。如表7所示。当使用50%和90%压缩时,我们的方法分别比FastV高21.5%和4.4%,证明了它的有效性。FastV在 Transformer 块中压缩输入标记,包括视觉和文本标记。相比之下,我们的方法与CIP结合工作,并且通过使用具有高相对信息密度的令牌来压缩具有低信息密度的令牌而更有针对性。
-

-
表7: scale compression mechanism 的消融研究。我们使用不同的压缩比与FastV 进行比较。(0.5)表示50%的压缩率,而(0.9)表示90%的压缩率。
-
Qualitative Results
- 在本节中,我们提供了一些定性结果来证明我们的方法的有效性。首先,我们验证了语义锯齿效应在轻量级MLLMs中特别明显,它采用了InternVL2-2B和InternVL2-26B。如图3©所示,InternVL2-26B可以正确回答问题。然而,由于单词“classrooms”和“school”被裁剪,InternVL2-2B给出了一个错误的答案,该答案涉及原始图像左下角的文本。MiniMonkey可以克服这种语义锯齿效应,并提供正确的答案,如图3(d)所示。比较图3(b)和图3(d ),我们可以看到重叠裁剪策略引入了一些幻觉,并且不能基于图像准确地回答问题,而我们的方法可以有效地解决语义锯齿效应。
-

-
图3: Mini-Monkey 的定性结果。(a)输入图像和 GT。(b)使用重叠种植策略的结果。OSC代表重叠种植策略。© InternVL2 2-2B和InternVL2-26B的结果。 (d)Mini-Monkey 的结果。
-
Conclusion
-
在本研究中,我们介绍了一种互补图像金字塔(CIP ),旨在减轻多层线性模型的语义锯齿效应,从而提高其有效处理高分辨率图像的能力。CIP是即插即用的,可以低成本无缝集成到各种多模态大语言模型中。我们展示了所提出的CIP在不同架构、不同参数和不同使用配置下的有效性,从而带来一致的性能改进。此外,为了提高计算效率,我们提出了一种比例压缩机制(SCM)来压缩视觉标记。CIP不仅提高了一般的多模态理解性能,还显示了在文档理解任务中的持续改进。此外,我们的实验结果表明,配备CIP的2B参数MLLM甚至超过了OCRBench上更大的8B参数最新模型,如InternVL2-8B。
-
局限性:为了确保我们的方法在各种架构上的无缝应用,我们采用了以图像为中心的方法来构建图像金字塔,而没有引入额外的参数。在未来的工作中,我们将探索将可训练特征金字塔网络(FPN)用于MLLMs,旨在更有效地利用多尺度特征。
-
Mini-Monkey 的架构围绕 “缓解语义锯齿效应” 和 “提升轻量级模型效率” 设计,核心模块包括互补图像金字塔(CIP)、尺度压缩机制(SCM)、视觉编码器、MLP 层及大语言模型(LLM),各模块的底层逻辑、作用、理论支撑及参数设置如下:
- 互补图像金字塔(CIP),解决高分辨率图像裁剪导致的 “语义锯齿效应”(物体 / 文本被分割引发的语义不连续),通过多尺度图像提供互补语义信息,确保某一尺度丢失的语义可由其他尺度补偿。动态生成多尺度图像金字塔,覆盖 “细节 - 边界 - 全局” 语义:详细组提供高分辨率细节(如小文字、密集文本),自适应组补充裁剪边界特征(避免文本分割),全局组提供整体上下文(如文档布局);通过 “非整数倍宽高比” 设计,确保自适应组与详细组的裁剪线不重叠,模拟跨窗口特征交互,避免文字被重复分割。最大瓦片数(子图像数量):24(高分辨率图像对应更多瓦片,平衡细节与计算量);分组宽高比规则:详细组(>9 块,优先匹配输入图像宽高比)、自适应组(3-8 块,排除详细组宽高比的整数倍)、全局组(1:1,低分辨率全局视图)。直接提升场景文字定位与识别能力 —— 详细组保留文字细节,自适应组修复被裁剪的文字边界(如 “breakdown” 避免分割为 “break” 和 “down”),全局组提供文字布局上下文,减少语义模糊。
- 尺度压缩机制(SCM),CIP 引入的多尺度图像会增加视觉令牌数量,SCM 通过筛选冗余令牌降低计算成本,同时保留关键语义信息(尤其文字特征)。利用 LLM 已训练的注意力层,以自适应组、全局组的视觉令牌及文本令牌为 “查询”,计算详细组令牌的注意力权重,筛选高权重令牌(文字区域通常权重更高);无额外参数,仅依赖 LLM 自身注意力机制,兼容轻量级模型的效率需求。压缩比例:支持 0.5(保留 50% 令牌)和 0.9(保留 90% 令牌),默认优先使用 0.9(平衡效率与文字信息保留);注意力计算维度:与 LLM 隐藏层维度一致(如 2B 模型对应隐藏层维度为 1024)。
- 视觉编码器与 MLP 层,将图像子区域转换为与文本兼容的特征向量,实现视觉 - 文本模态对齐。视觉编码器(基于 InternViT)处理 CIP 生成的子图像,提取局部视觉特征(如文字的边缘、纹理);MLP 层将视觉特征投影到 LLM 的文本特征空间,确保 “文字视觉特征” 可被语言模型理解。输入分辨率:448x448(与视觉编码器适配,平衡细节与计算量);MLP 投影维度:与 LLM 隐藏层维度一致(2B 模型为 1024)。
- 大语言模型(LLM),基于 2B 参数的轻量级 LLM(如 internlm2-chat-1.8b),负责文本生成与问答推理,整合视觉与文本特征输出最终结果。处理问答交互:理解用户问题(如 “图中招牌文字是什么?”),结合视觉特征生成答案;融合多尺度信息:将 CIP 提供的互补语义与 SCM 筛选的关键令牌整合,提升推理连贯性。模型规模:2B 参数(平衡效率与推理能力,训练仅需 8 台 RTX 3090 GPU);上下文长度:2048 tokens(支持长文本问答,如多轮文档交互)。
-
模块 核心参数 对场景文字识别的影响 对问答交互的影响 CIP 最大瓦片数 = 24 瓦片数增加可覆盖更多文字区域,但过多会增加冗余 全局组瓦片提供上下文,提升问答相关性 CIP 自适应组宽高比规则 避免文字分割,提升 “长文本” 识别准确率(+3.2%) 边界特征补充,减少问答中 “文字断句” 错误 SCM 压缩比例 = 0.9 保留更多文字令牌,识别准确率提升 1.5% 关键文字不丢失,问答答案更精准 视觉编码器 输入分辨率 = 448x448 高分辨率捕捉小文字细节,准确率超 224x224 达 8.3% 细节丰富使问答描述更具体(如 “模糊文字推测”) LLM 上下文长度 = 2048 支持长文本识别结果输出 多轮问答中保持上下文记忆 -
Mini-Monkey 通过 CIP 和 SCM 的创新设计,在轻量级模型中实现了对语义锯齿效应的有效缓解,尤其在场景文字识别和文档理解中表现突出。进一步优化需聚焦于模块参数的场景适配性(如文字专用分组策略)、令牌筛选的针对性(如结合文字显著性)及问答交互的意图感知,以充分释放其在复杂场景中的潜力。
-
模型定义模块,文件路径:Monkey/project/mini_monkey/internvl/model/internvl_chat/modeling_minimonkey_chat.py。
-
class MiniMonkeyChatModel(PreTrainedModel): config_class = InternVLChatConfig main_input_name = 'pixel_values' _no_split_modules = ['InternVisionModel', 'LlamaDecoderLayer', 'InternLM2DecoderLayer', 'Phi3DecoderLayer', 'Qwen2DecoderLayer'] _supports_flash_attn_2 = True def __init__(self, config: InternVLChatConfig, vision_model=None, language_model=None): super().__init__(config) # 检查transformers库的版本是否符合要求 assert version_cmp(transformers.__version__, '4.37.0', 'ge') image_size = config.force_image_size or config.vision_config.image_size patch_size = config.vision_config.patch_size self.patch_size = patch_size self.select_layer = config.select_layer self.template = config.template # 计算图像token的数量 self.num_image_token = int((image_size // patch_size) ** 2 * (config.downsample_ratio ** 2)) self.downsample_ratio = config.downsample_ratio self.ps_version = config.ps_version self.llm_arch_name = config.llm_config.architectures[0] logger.info(f'num_image_token: {self.num_image_token}') logger.info(f'ps_version: {self.ps_version}') # 初始化视觉模型 if vision_model is not None: self.vision_model = vision_model else: self.vision_model = InternVisionModel(config.vision_config) # 初始化语言模型 if language_model is not None: self.language_model = language_model else: if config.llm_config.architectures[0] == 'LlamaForCausalLM': self.language_model = LlamaForCausalLM(config.llm_config) elif config.llm_config.architectures[0] == 'InternLM2ForCausalLM': self.language_model = InternLM2ForCausalLM(config.llm_config) elif config.llm_config.architectures[0] == 'Phi3ForCausalLM': self.language_model = Phi3ForCausalLM(config.llm_config) elif config.llm_config.architectures[0] == 'Qwen2ForCausalLM': self.language_model = Qwen2ForCausalLM(config.llm_config) else: raise NotImplementedError(f'{config.llm_config.architectures[0]} is not implemented.') vit_hidden_size = config.vision_config.hidden_size llm_hidden_size = config.llm_config.hidden_size # 定义多层感知机(MLP)用于视觉特征和语言特征的映射 self.mlp1 = nn.Sequential( nn.LayerNorm(vit_hidden_size * int(1 / self.downsample_ratio) ** 2), nn.Linear(vit_hidden_size * int(1 / self.downsample_ratio) ** 2, llm_hidden_size), nn.GELU(), nn.Linear(llm_hidden_size, llm_hidden_size) ) self.img_context_token_id = None self.conv_template = get_conv_template(self.template) if hasattr(config, 'system_message'): self.system_message = config.system_message else: self.system_message = self.conv_template.system_message self.num_samples = 0 # 应用LoRA技术对视觉模型进行微调 if config.use_backbone_lora: self.wrap_backbone_lora(r=config.use_backbone_lora, lora_alpha=2 * config.use_backbone_lora) # 应用LoRA技术对语言模型进行微调 if config.use_llm_lora: self.wrap_llm_lora(r=config.use_llm_lora, lora_alpha=2 * config.use_llm_lora) def wrap_backbone_lora(self, r=128, lora_alpha=256, lora_dropout=0.05): lora_config = LoraConfig( r=r, target_modules=['attn.qkv', 'attn.proj', 'mlp.fc1', 'mlp.fc2'], lora_alpha=lora_alpha, lora_dropout=lora_dropout, ) self.vision_model = get_peft_model(self.vision_model, lora_config) self.vision_model.print_trainable_parameters() def wrap_llm_lora(self, r=128, lora_alpha=256, lora_dropout=0.05): # 根据语言模型的架构确定目标模块 if self.llm_arch_name == 'InternLM2ForCausalLM': target_modules = ['attention.wqkv', 'attention.wo', 'feed_forward.w1', 'feed_forward.w2', 'feed_forward.w3'] elif self.llm_arch_name == 'Phi3ForCausalLM': target_modules = ['mlp.down_proj', 'mlp.gate_up_proj', 'self_attn.o_proj', 'self_attn.qkv_proj'] elif self.llm_arch_name in ['Qwen2ForCausalLM', 'LlamaForCausalLM']: target_modules = ['self_attn.q_proj', 'self_attn.k_proj', 'self_attn.v_proj', 'self_attn.o_proj', 'mlp.gate_proj', 'mlp.down_proj', 'mlp.up_proj'] else: raise NotImplemented lora_config = LoraConfig( r=r, target_modules=target_modules, lora_alpha=lora_alpha, lora_dropout=lora_dropout, task_type='CAUSAL_LM' ) self.language_model = get_peft_model(self.language_model, lora_config) self.language_model.enable_input_require_grads() self.language_model.print_trainable_parameters() def forward( self, pixel_values: torch.FloatTensor, input_ids: torch.LongTensor = None, attention_mask: Optional[torch.Tensor] = None, position_ids: Optional[torch.LongTensor] = None, image_flags: Optional[torch.LongTensor] = None, past_key_values: Optional[List[torch.FloatTensor]] = None, labels: Optional[torch.LongTensor] = None, use_cache: Optional[bool] = None, output_attentions: Optional[bool] = None, output_hidden_states: Optional[bool] = None, return_dict: Optional[bool] = None, ) -> Union[Tuple, CausalLMOutputWithPast]: return_dict = return_dict if return_dict is not None else self.config.use_return_dict image_flags = image_flags.squeeze(-1) # 获取语言模型的输入嵌入 input_embeds = self.language_model.get_input_embeddings()(input_ids).clone() # 提取视觉特征 vit_embeds = self.extract_feature(pixel_values) vit_embeds = vit_embeds[image_flags == 1] vit_batch_size = pixel_values.shape[0] B, N, C = input_embeds.shape input_embeds = input_embeds.reshape(B * N, C) if torch.distributed.is_initialized() and torch.distributed.get_rank() == 0: print(f'dynamic ViT batch size: {vit_batch_size}, images per sample: {vit_batch_size / B}, dynamic token length: {N}') input_ids = input_ids.reshape(B * N) selected = (input_ids == self.img_context_token_id) try: # 将视觉特征替换到输入嵌入中 input_embeds[selected] = input_embeds[selected] * 0.0 + vit_embeds.reshape(-1, C) ignore_flag = False except Exception as e: vit_embeds = vit_embeds.reshape(-1, C) print(f'warning: {e}, input_embeds[selected].shape={input_embeds[selected].shape}, ' f'vit_embeds.shape={vit_embeds.shape}') n_token = selected.sum() input_embeds[selected] = input_embeds[selected] * 0.0 + vit_embeds[:n_token] ignore_flag = True input_embeds = input_embeds.reshape(B, N, C) # 将处理后的输入嵌入输入到语言模型中 outputs = self.language_model( inputs_embeds=input_embeds, attention_mask=attention_mask, position_ids=position_ids, past_key_values=past_key_values, use_cache=use_cache, output_attentions=output_attentions, output_hidden_states=output_hidden_states, return_dict=return_dict, ) logits = outputs.logits loss = None if labels is not None: # 计算损失 shift_logits = logits[..., :-1, :].contiguous() shift_labels = labels[..., 1:].contiguous() loss_fct = CrossEntropyLoss() shift_logits = shift_logits.view(-1, self.language_model.config.vocab_size) shift_labels = shift_labels.view(-1) shift_labels = shift_labels.to(shift_logits.device) loss = loss_fct(shift_logits, shift_labels) if ignore_flag: loss = loss * 0.0 if not return_dict: output = (logits,) + outputs[1:] return (loss,) + output if loss is not None else output return CausalLMOutputWithPast( loss=loss, logits=logits, past_key_values=outputs.past_key_values, hidden_states=outputs.hidden_states, attentions=outputs.attentions, ) -
MiniMonkeyChatModel是一个多模态模型,由视觉模型和语言模型组成。视觉模型负责提取图像特征,语言模型负责生成文本。特征融合:通过mlp1多层感知机将视觉特征映射到与语言特征相同的维度,然后将视觉特征替换到语言模型的输入嵌入中,实现图像和文本特征的融合。使用低秩自适应(LoRA)技术对视觉模型和语言模型进行微调,减少可训练参数的数量,提高训练效率。在forward方法中,首先提取视觉特征,然后将视觉特征与语言输入嵌入进行融合,最后将融合后的特征输入到语言模型中进行文本生成。
-
-
数据处理模块,文件路径:
Monkey/project/mini_monkey/internvl/train/dataset.py-
class LazySupervisedDataset(Dataset): def __init__( self, template_name, meta, tokenizer, tcs_loader, ds_name, num_image_token, image_size=224, is_train=True, pad2square=False, group_by_length=False, dynamic_image_size=False, use_thumbnail=False, min_dynamic_patch=1, max_dynamic_patch=6, min_num_frame=4, # for video data max_num_frame=12, # for video data sampling_method='rand', # for video data repeat_time=1, normalize_type='imagenet', random_seed=0, ): super(LazySupervisedDataset, self).__init__() self.ds_name = ds_name self.tokenizer = tokenizer self.template_name = template_name self.num_image_token = num_image_token logger.info(f'[Dataset] num_image_token: {num_image_token}') logger.info(f'[Dataset] dynamic_image_size: {dynamic_image_size}') logger.info(f'[Dataset] use_thumbnail: {use_thumbnail}') logger.info(f'[Dataset] min_dynamic_patch: {min_dynamic_patch}, max_dynamic_patch: {max_dynamic_patch}') self.image_size = image_size self.is_train = is_train self.pad2square = pad2square self.max_num_frame = max_num_frame self.min_num_frame = min_num_frame self.sampling_method = sampling_method logger.info('Formatting inputs...Skip in lazy mode') assert meta['annotation'].endswith('jsonl'), f'annotation must be jsonl, but got {meta["annotation"]}' # 读取数据标注文件 with open(meta['annotation'], 'r') as f: self.raw_data = f.readlines() if repeat_time < 1: self.raw_data = self.raw_data[:int(len(self.raw_data) * repeat_time)] if repeat_time > 1: assert isinstance(repeat_time, int) self.raw_data = self.raw_data * repeat_time self.rng = np.random.default_rng(seed=random_seed) self.rng.shuffle(self.raw_data) gc.collect() self.root = meta['root'] self.cached_data_dict = {} self.tcs_loader = tcs_loader self.group_by_length = group_by_length def __getitem__(self, idx): if idx not in self.cached_data_dict: # 从标注文件中获取一条数据 data_item = json.parse(self.raw_data[idx]) # 加载图像 image_path = self.get_image_path(data_item['image']) image = self.load_image(image_path) # 对图像进行预处理 transform = self.get_transform() if self.dynamic_image_size: images = dynamic_preprocess(image, min_num=self.min_dynamic_patch, max_num=self.max_dynamic_patch, image_size=self.image_size, use_thumbnail=self.use_thumbnail) else: images = [image] pixel_values = [transform(image) for image in images] pixel_values = torch.stack(pixel_values) # 对文本进行预处理 sources = data_item['conversations'] ret = preprocess_mpt(sources, self.tokenizer, num_image=len(images), num_image_token_list=[self.num_image_token] * len(images)) ret = {k: v[0] for k, v in ret.items()} ret['pixel_values'] = pixel_values ret['image_flags'] = torch.tensor([1] * len(images), dtype=torch.long) self.cached_data_dict[idx] = ret return self.cached_data_dict[idx] def __len__(self): return len(self.raw_data) -
LazySupervisedDataset类用于加载多模态数据集,包括图像和文本数据。数据标注文件为jsonl格式,其中包含图像路径和对话文本。使用分词器对对话文本进行分词,并将图像占位符替换为图像特征的表示。为了提高数据加载效率,使用缓存机制将处理后的数据存储在内存中。
-
-
训练脚本模块,文件路径:
Monkey/project/mini_monkey/shell/minimonkey/minimonkey_finetune_full.sh-
# minimonkey_finetune_full.sh set -x GPUS=${GPUS:-8} BATCH_SIZE=${BATCH_SIZE:-32} PER_DEVICE_BATCH_SIZE=${PER_DEVICE_BATCH_SIZE:-2} GRADIENT_ACC=$((BATCH_SIZE / PER_DEVICE_BATCH_SIZE / GPUS)) export PYTHONPATH="${PYTHONPATH}:$(pwd)" export MASTER_PORT=34229 export TF_CPP_MIN_LOG_LEVEL=3 export LAUNCHER=pytorch OUTPUT_DIR='work_dirs/minimonkey_chat' if [ ! -d "$OUTPUT_DIR" ]; then mkdir -p "$OUTPUT_DIR" fi # 使用torchrun进行分布式训练 torchrun \ --nnodes=1 \ --node_rank=0 \ --master_addr=127.0.0.1 \ --nproc_per_node=${GPUS} \ --master_port=${MASTER_PORT} \ internvl/train/minimonkey_chat_finetune.py \ --model_name_or_path "OpenGVLab/InternVL2-2B" \ --conv_style "internlm2-chat" \ --output_dir ${OUTPUT_DIR} \ --meta_path "shell/data/internvl_1_2_finetune.json" \ --overwrite_output_dir True \ --force_image_size 448 \ --max_dynamic_patch 8 \ --down_sample_ratio 0.5 \ --drop_path_rate 0.1 \ --freeze_llm False \ --freeze_mlp True \ --freeze_backbone True \ --vision_select_layer -1 \ --dataloader_num_workers 4 \ --bf16 True \ --num_train_epochs 1 \ --per_device_train_batch_size ${PER_DEVICE_BATCH_SIZE} \ --gradient_accumulation_steps ${GRADIENT_ACC} \ --evaluation_strategy "no" \ --save_strategy "steps" \ --save_steps 1000 \ --save_total_limit 1 \ --learning_rate 4e-9 \ --weight_decay 0.01 \ --warmup_ratio 0.03 \ --lr_scheduler_type "cosine" \ --logging_steps 1 \ --max_seq_length 8192 \ --do_train True \ --grad_checkpoint True \ --group_by_length True \ --dynamic_image_size True \ --use_thumbnail True \ --ps_version 'v2' \ --deepspeed "zero_stage3_config.json" \ --report_to "tensorboard" \ 2>&1 | tee -a "${OUTPUT_DIR}/training_log.txt" -
使用
torchrun进行分布式训练,支持多 GPU 并行计算。通过命令行参数配置训练的各种参数,包括模型路径、数据路径、批量大小、学习率、训练轮数等。使用梯度累积技术,在多个小批量数据上累积梯度,然后进行一次参数更新,以模拟更大的批量大小。使用 DeepSpeed 库进行优化,包括零冗余优化器(ZeRO)和梯度检查点等技术,减少内存占用,提高训练效率。
-
-
在
Monkey/project/mini_monkey/demo.py文件中,包含了图像预处理的相关函数,实现了多尺度自适应裁剪的思想。-
def build_transform(input_size): MEAN, STD = IMAGENET_MEAN, IMAGENET_STD transform = T.Compose([ T.Lambda(lambda img: img.convert('RGB') if img.mode != 'RGB' else img), T.Resize((input_size, input_size), interpolation=InterpolationMode.BICUBIC), T.ToTensor(), T.Normalize(mean=MEAN, std=STD) ]) return transform 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 def dynamic_preprocess(image, min_num=1, max_num=12, image_size=448, use_thumbnail=False): orig_width, orig_height = image.size aspect_ratio = orig_width / orig_height # 计算目标宽高比集合 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) target_ratios = sorted(target_ratios, key=lambda x: x[0] * x[1]) # 找到最接近的宽高比 target_aspect_ratio = find_closest_aspect_ratio( aspect_ratio, target_ratios, orig_width, orig_height, image_size) # 计算目标宽度和高度 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] # 调整图像大小 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_img = resized_img.crop(box) processed_images.append(split_img) assert len(processed_images) == blocks if use_thumbnail and len(processed_images) != 1: thumbnail_img = image.resize((image_size, image_size)) processed_images.append(thumbnail_img) return processed_images, target_aspect_ratio -
build_transform函数:对图像进行基本的预处理,包括转换为 RGB 格式、调整大小、转换为张量和归一化操作。find_closest_aspect_ratio函数:根据输入图像的宽高比,从目标宽高比集合中找到最接近的宽高比。dynamic_preprocess函数:实现了多尺度自适应裁剪的核心思想。首先计算目标宽高比集合,然后找到最接近输入图像宽高比的目标宽高比,根据该宽高比调整图像大小并进行裁剪,最后将裁剪后的图像存储在列表中。数据流转:输入为 PIL 图像,经过一系列处理后输出裁剪后的图像列表和目标宽高比。
-
-
同样在
Monkey/project/mini_monkey/demo.py文件中,包含了模型加载和推理的代码。-
# 加载模型和分词器 path = 'minimonkey' model = AutoModel.from_pretrained( path, torch_dtype=torch.bfloat16, low_cpu_mem_usage=True, trust_remote_code=True).eval().cuda() tokenizer = AutoTokenizer.from_pretrained(path, trust_remote_code=True, use_fast=False) # 加载图像 pixel_values, target_aspect_ratio = load_image('xxx.jpg', min_num=4, max_num=12) pixel_values = pixel_values.to(torch.bfloat16).cuda() pixel_values2 = load_image2('xxx.jpg', min_num=3, max_num=7, target_aspect_ratio=target_aspect_ratio) pixel_values2 = pixel_values2.to(torch.bfloat16).cuda() pixel_values = torch.cat([pixel_values2[:-1], pixel_values[:-1], pixel_values2[-1:]], 0) # 生成配置 generation_config = dict(do_sample=False, max_new_tokens=512) # 提出问题并进行推理 question = "Read the all text in the image." response, history = model.chat(tokenizer, pixel_values, target_aspect_ratio, question, generation_config, history=None, return_history=True) print(f'User: {question} Assistant: {response}') -
使用
AutoModel.from_pretrained和AutoTokenizer.from_pretrained加载预训练的模型和分词器。调用load_image和load_image2函数加载并处理图像,将处理后的图像转换为张量并移动到 GPU 上。设置生成配置,提出问题并调用model.chat方法进行推理。数据流转:图像文件 -> 图像预处理 -> 图像张量 -> 模型输入 -> 模型输出(回答)。
-
-
在
Monkey/project/mini_monkey/internvl/model/internvl_chat/modeling_minimonkey_chat.py文件中,包含了 LoRA 微调的相关代码。-
class MiniMonkeyChatModel(PreTrainedModel): # ... 其他代码 ... def wrap_backbone_lora(self, r=128, lora_alpha=256, lora_dropout=0.05): lora_config = LoraConfig( r=r, target_modules=['attn.qkv', 'attn.proj', 'mlp.fc1', 'mlp.fc2'], lora_alpha=lora_alpha, lora_dropout=lora_dropout, ) self.vision_model = get_peft_model(self.vision_model, lora_config) self.vision_model.print_trainable_parameters() def wrap_llm_lora(self, r=128, lora_alpha=256, lora_dropout=0.05): # 根据语言模型的架构确定目标模块 if self.llm_arch_name == 'InternLM2ForCausalLM': target_modules = ['attention.wqkv', 'attention.wo', 'feed_forward.w1', 'feed_forward.w2', 'feed_forward.w3'] elif self.llm_arch_name == 'Phi3ForCausalLM': target_modules = ['mlp.down_proj', 'mlp.gate_up_proj', 'self_attn.o_proj', 'self_attn.qkv_proj'] elif self.llm_arch_name in ['Qwen2ForCausalLM', 'LlamaForCausalLM']: target_modules = ['self_attn.q_proj', 'self_attn.k_proj', 'self_attn.v_proj', 'self_attn.o_proj','mlp.gate_proj', 'mlp.down_proj', 'mlp.up_proj'] else: raise NotImplemented lora_config = LoraConfig( r=r, target_modules=target_modules, lora_alpha=lora_alpha, lora_dropout=lora_dropout, task_type='CAUSAL_LM' ) self.language_model = get_peft_model(self.language_model, lora_config) self.language_model.enable_input_require_grads() self.language_model.print_trainable_parameters() -
wrap_backbone_lora函数:对视觉模型进行 LoRA 微调,通过LoraConfig配置 LoRA 参数,并使用get_peft_model将 LoRA 应用到视觉模型上。wrap_llm_lora函数:根据语言模型的架构确定目标模块,然后对语言模型进行 LoRA 微调。数据流转:模型 -> LoRA 配置 -> 应用 LoRA 的模型。
-
-
在
Monkey/project/mini_monkey/eval/mmbench/evaluate_mmbench.py文件中,包含了模型评估的相关代码。-
class MMBenchDataset(torch.utils.data.Dataset): # ... 其他代码 ... def __getitem__(self, idx): index = self.df.iloc[idx]['index'] image = self.df.iloc[idx]['image'] question = self.df.iloc[idx]['question'] answer = self.df.iloc[idx]['answer'] if 'answer' in self.df.iloc[0].keys() else None image = Image.open(BytesIO(base64.b64decode(image))).convert('RGB') if self.dynamic_image_size: images = dynamic_preprocess(image, image_size=self.input_size, use_thumbnail=self.use_thumbnail, max_num=self.max_num) else: images = [image] pixel_values = [self.transform(image) for image in images] pixel_values = torch.stack(pixel_values) # ... 其他代码 ... return { 'question': question, 'pixel_values': pixel_values, 'answer': answer, 'index': index, 'option': options } def evaluate_chat_model(): # ... 其他代码 ... for ds_name in args.datasets: dataset = MMBenchDataset( root=ds_collections[ds_name]['root'], prompt=prompt, language=ds_collections[ds_name]['language'], input_size=image_size, dynamic_image_size=args.dynamic, use_thumbnail=use_thumbnail, max_num=args.max_num ) dataloader = torch.utils.data.DataLoader( dataset=dataset, sampler=InferenceSampler(len(dataset)), batch_size=args.batch_size, num_workers=args.num_workers, pin_memory=True, drop_last=False, collate_fn=partial(collate_fn, tokenizer=tokenizer), ) outputs = [] for _, (pixel_values, questions, answers, indexes, options) in tqdm(enumerate(dataloader)): pixel_values = pixel_values.to(torch.bfloat16).cuda() generation_config = dict( num_beams=args.num_beams, max_new_tokens=ds_collections[ds_name]['max_new_tokens'], min_new_tokens=ds_collections[ds_name]['min_new_tokens'], do_sample=True if args.temperature > 0 else False, temperature=args.temperature, ) pred = model.chat( tokenizer=tokenizer, pixel_values=pixel_values, question=questions[0], generation_config=generation_config ) preds = [post_process(pred, options[0])] # ... 其他代码 ... outputs.append({ 'question': question, 'answer': pred, 'gt_answers': answer, 'index': int(index) }) # ... 其他代码 ... if torch.distributed.get_rank() == 0: # ... 保存结果 ... cur_df.to_excel(output_path, index=False, engine='openpyxl') print('Results saved to {}'.format(output_path)) -
MMBenchDataset类:定义了评估数据集,对图像进行预处理并返回问题、图像张量、答案等信息。evaluate_chat_model函数:加载数据集,使用DataLoader进行数据加载,调用model.chat方法进行推理,对推理结果进行后处理,并将结果保存到 Excel 文件中。数据流转:评估数据集 -> 数据加载器 -> 模型推理 -> 结果后处理 -> 结果保存。
-
-
Mini - Monkey 仓库中的代码通过图像预处理、模型加载与推理、LoRA 微调、评估等模块,实现了论文中的多尺度自适应裁剪等思想,使用了图像裁剪、宽高比匹配、预训练模型加载、LoRA 微调、数据集定义等关键技术。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)