前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >拥有LLM模型

拥有LLM模型

作者头像
磐创AI
发布2024-06-06 19:12:06
840
发布2024-06-06 19:12:06
举报

本文摘要:

  • 解释由Microsoft开发的GIT,一种视觉语言模型。
  • 使用PyTorch和Hugging Face的Transformers将GIT的语言模型替换为大型语言模型(LLMs)。
  • 介绍如何使用LoRA对GIT-LLM模型进行微调。
  • 测试和讨论已开发的模型。
  • 调查由GIT的图像编码器嵌入的“图像嵌入”是否指示与“文本嵌入”相同空间中的特定字符。

大型语言模型(LLM)越来越显示出其价值。将图像纳入LLMs使它们作为视觉语言模型更加有用。在本文中,我将解释一个名为GIT-LLM的模型的开发,这是一个简单但强大的视觉语言模型。一些部分,比如代码解释,可能会感觉有点繁琐,所以可以直接跳到结果部分。我进行了各种实验和分析,因此我认为你会喜欢看到我能够取得的成就。

该实现是公开的。

https://github.com/turingmotors/heron

将GIT转变为LLM

让我们深入探讨这个技术博客的主题。

GIT是什么?

生成图像到文本的转换器,或GIT,是Microsoft提出的一种视觉语言模型。

arXiv:https://arxiv.org/abs/2205.14100

代码:https://github.com/microsoft/GenerativeImage2Text

它的架构相当简单。它使用从图像编码器中提取的特征向量,通过投影模块将其转换为可以像文本一样处理的向量。然后将这些向量输入到语言模型中,以生成图像的字幕或执行问答。该模型可以以类似的方式处理视频。

尽管它很简单,但如果你查看“Paper with code”的排行榜,你会发现它在许多任务中排名很高。

https://paperswithcode.com/paper/git-a-generative-image-to-text-transformer

最初,GIT使用强大的模型如CLIP作为其图像编码器,并从头开始训练语言模型部分。然而,在本文中,我尝试使用强大的LLM并进行微调。在这里,我称该模型为“GIT-LLM”。


使用Hugging Face的Transformers的LLM

我将使用Hugging Face的Transformers库来开发GIT-LLM。Transformers是一个用于处理机器学习模型的Python库。它提供了许多最先进的预训练模型,你可以立即运行推理。它还提供了训练和微调模型的工具。

我相信Transformers对于最近LLM衍生物的发展做出了重要贡献。几乎所有可用的LLMs都可以使用Transformers进行处理,并且从它们衍生出的许多多模态模型在开发和微调时都使用Transformers作为基础。

以下是使用Transformers模型的最简单代码。你可以通过使用AutoModel和AutoTokenizer轻松尝试LLMs。

代码语言:javascript
复制
from transformers import AutoModelForCausalLM, AutoTokenizer

model_name = "facebook/opt-350m"

model = AutoModelForCausalLM.from_pretrained(model_name).to("cuda")
tokenizer = AutoTokenizer.from_pretrained(model_name)

prompt = "Hello, I'm am conscious and"
input_ids = tokenizer(prompt, return_tensors="pt").to("cuda")

sample = model.generate(**input_ids, max_length=64)
print(tokenizer.decode(sample[0]))
# Hello, I'm am conscious and I'm a bit of a noob. I'm looking for a good place to start.

让我们查看OPT模型持有的参数。打印由AutoModelForCausalLM创建的模型。

代码语言:javascript
复制
OPTForCausalLM(
  (model): OPTModel(
    (decoder): OPTDecoder(
      (embed_tokens): Embedding(50272, 512, padding_idx=1)
      (embed_positions): OPTLearnedPositionalEmbedding(2050, 1024)
      (project_out): Linear(in_features=1024, out_features=512, bias=False)
      (project_in): Linear(in_features=512, out_features=1024, bias=False)
      (layers): ModuleList(
        (0-23): 24 x OPTDecoderLayer(
          (self_attn): OPTAttention(
            (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
            (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
            (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
            (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
          )
          (activation_fn): ReLU()
          (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
          (fc1): Linear(in_features=1024, out_features=4096, bias=True)
          (fc2): Linear(in_features=4096, out_features=1024, bias=True)
          (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        )
      )
    )
  )
  (lm_head): Linear(in_features=512, out_features=50272, bias=False)
)

它相当简单。初始embed_tokens的输入维度和最终lm_head的输出维度是50,272,表示训练此模型时使用的令牌数量。让我们验证tokenizer的词汇表大小:

代码语言:javascript
复制
print(tokenizer.vocab_size)
# 50265

包括特殊标记如bos_token、eos_token、unk_token、sep_token、pad_token、cls_token和mask_token,它从总共50,272种类型的标记中预测下一个词的概率。

通过查看实现方式,你可以了解这些模型是如何连接的。一个简单的图表可以表示流程如下:

结构和数据流相当简单。〇〇Model和〇〇ForCausalLM在不同语言模型之间具有相似的框架。〇〇Model类主要表示语言模型的“Transformer”部分。例如,如果你想执行文本分类等任务,只需使用这部分。〇〇ForCausalLM类用于文本生成,在使用Transformer处理向量后,将分类器应用于标记计数。损失的计算也在该类的forward方法中完成。embed_positions表示位置编码,它添加到project_in中。


使用Transformers进行GIT

我将基于GIT的官方文档页面尝试一下。由于我还将处理图像,我将使用一个同时包含Tokenizer的Processor。

代码语言:javascript
复制
from PIL import Image
import requests
from transformers import AutoProcessor, AutoModelForCausalLM

model_name = "microsoft/git-base-coco"

model = AutoModelForCausalLM.from_pretrained(model_name)
processor = AutoProcessor.from_pretrained(model_name)

# Downloading and preprocess an image
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = Image.open(requests.get(url, stream=True).raw)
pixel_values = processor(images=image, return_tensors="pt").pixel_values

# Preprocessing text
prompt = "What is this?"
inputs = processor(
            prompt,
            image,
            return_tensors="pt",
            max_length=64
        )

sample = model.generate(**inputs, max_length=64)
print(processor.tokenizer.decode(sample[0]))
# two cats sleeping on a couch

鉴于输入图像产生了输出“两只猫在沙发上睡觉”,看起来工作得很好。

让我们也看一下模型的结构:

代码语言:javascript
复制
GitForCausalLM(
  (git): GitModel(
    (embeddings): GitEmbeddings(
      (word_embeddings): Embedding(30522, 768, padding_idx=0)
      (position_embeddings): Embedding(1024, 768)
      (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
      (dropout): Dropout(p=0.1, inplace=False)
    )
    (image_encoder): GitVisionModel(
      (vision_model): GitVisionTransformer(
        ...
      )
    )
    (encoder): GitEncoder(
      (layer): ModuleList(
        (0-5): 6 x GitLayer(
          ...
        )
      )
    )
    (visual_projection): GitProjection(
      (visual_projection): Sequential(
        (0): Linear(in_features=768, out_features=768, bias=True)
        (1): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
      )
    )
  )
  (output): Linear(in_features=768, out_features=30522, bias=True)
)

虽然有点冗长,但如果拆分开来,也很简单。在GitForCausalLM中,有一个GitModel,其中包含以下模块:

  • embeddings(GitEmbeddings)
  • image_encoder(GitVisionModel)
  • encoder(GitEncoder)
  • visual_projection(GitProjection)
  • output(Linear)

与OPT的主要区别在于存在GitVisionModel和GitProjection,它们正是将图像转换为类似提示的向量的模块。虽然语言模型在OPT中使用解码器,在GIT中使用编码器,但这仅表示注意力掩码的构建方式不同。在变压器层中可能存在轻微差异,但它们的功能基本相同。GIT使用编码器的名称,因为它使用一种独特的注意力掩码,该掩码对图像的所有特征应用注意力,并对文本特征使用因果掩码。

查看模型的连接方式:

GitVisionModel和GitProjection通过处理图像信息来匹配文本的嵌入。之后,将其与文本的嵌入一起输入到语言模型的“Transformer”层中。虽然存在细微差异,但与语言模型相关的部分几乎是相同开发方式。

GIT的注意力掩码

通常语言模型和GIT语言模型的架构几乎相同,但应用注意力掩码的方式不同。

对于语言模型,在预测未来标记时,会应用一种称为“因果注意力”的注意力掩码,对应于以下图的左侧。第一列标记仅引用自身,确保不对后续单词应用自注意力。第二列对前两个单词应用自注意力,而从第三个单词开始,自注意力变为0。这种掩码使其能够有效地训练以预测下一个单词。

GIT输入有两种类型的标记:图像标记和文本标记。由于所有图像标记同时使用,并且不用于预测下一个标记,因此因果注意力不适用。另一方面,文本标记仍然需要因果注意力。图中右侧的掩码旨在实现这一点。对于图像信息的前三行,将应用所有标记信息的自注意力。从文本标记开始,向下移动一列会增加可以引用的单词数量。

让我们也检查创建GIT掩码的代码。创建GIT掩码的代码片段如下:

代码语言:javascript
复制
import torch

def create_git_attention_mask(
    tgt: torch.Tensor,
    memory: torch.Tensor,
) -> torch.Tensor:
    num_tgt = tgt.shape[1]
    num_memory = memory.shape[1]

    # Areas where attention is applied are 0, areas without attention are -inf
    top_left = torch.zeros((num_memory, num_memory))
    top_right = torch.full(
        (num_memory, num_tgt),
        float("-inf"),
    )
    bottom_left = torch.zeros(
        (num_tgt, num_memory),
    )

    # Causal Attention Mask
    bottom_right = torch.triu(torch.ones(tgt.shape[1], tgt.shape[1]), diagonal=1)
    bottom_right = bottom_right.masked_fill(bottom_right == 1, float("-inf"))

    # Concatenate masks
    left = torch.cat((top_left, bottom_left), dim=0)
    right = torch.cat((top_right, bottom_right), dim=0)

    # add axis for multi-head
    full_attention_mask = torch.cat((left, right), dim=1)[None, None, :]

    return full_attention_mask

# batch_size, sequence, feature_dim
visual_feature = torch.rand(1, 3, 128)
text_feature = torch.rand(1, 4, 128)

mask = create_git_attention_mask(tgt=text_feature, memory=visual_feature)
print(mask)

"""
tensor([[[[0., 0., 0., -inf, -inf, -inf, -inf],
          [0., 0., 0., -inf, -inf, -inf, -inf],
          [0., 0., 0., -inf, -inf, -inf, -inf],
          [0., 0., 0., 0., -inf, -inf, -inf],
          [0., 0., 0., 0., 0., -inf, -inf],
          [0., 0., 0., 0., 0., 0., -inf],
          [0., 0., 0., 0., 0., 0., 0.]]]])
"""

将掩码添加到注意力权重中。因此,自注意力发生的部分为0,不包含在注意力中的部分为负无穷。通过向前提供此掩码,只有文本部分才能进行因果注意力。对于视觉语言模型,有效地创建和使用这样的掩码是很重要的。

连接GIT和OPT

现在,让我们连接GIT和OPT。目标是创建如图所示的模型。

有关一般实现,你可以参考 modeling_git.py。

最重要的部分是GitOPTModel。在其中,需要将一个视觉编码器与一个LLM连接起来。我将解释一些关键组件。

代码语言:javascript
复制
class GitOPTModel(OPTModel):
    def __init__(self, config: OPTConfig):
        super(GitOPTModel, self).__init__(config)

        self.image_encoder = CLIPVisionModel.from_pretrained(config.vision_model_name)
        self.visual_projection = GitProjection(config)

init函数内,实例化了各种模块。super初始化了OPTModel。在GIT中,建议使用经过CLIP训练的强大图像编码器,因此我已经将其与经过CLIP训练的ViT兼容。GitProjection取自原始GIT实现。

让我们查看forward函数的内部。实现基于OPTDecoder的前向部分,其中还添加了来自图像编码器的信息。虽然代码有点冗长,但我在代码中添加了注释,请按照每个步骤进行理解。

代码语言:javascript
复制
class GitOPTModel(OPTModel):
    ...

    def forward(
        self,
        input_ids: Optional[torch.Tensor] = None,
        attention_mask: Optional[torch.Tensor] = None,
        pixel_values: Optional[torch.Tensor] = None,
    ) -> BaseModelOutputWithPooling:
        seq_length = input_shape[1]

        # 1. Extract image features using ViT
        visual_features = self.image_encoder(pixel_values).last_hidden_state

        # 2. Convert features extracted by ViT into prompt-like Image Embeddings
        projected_visual_features = self.visual_projection(visual_features)

        # 3. Vectorize the tokens
        inputs_embeds = self.decoder.embed_tokens(input_ids)

        # 4. Obtain Positional Encoding
        pos_embeds = self.embed_positions(attention_mask, 0)

        # 5. Dimension adjustment of Text Embeddings specific to OPT
        inputs_embeds = self.decoder.project_in(inputs_embeds)

        # 6. Text Embeddings + Positional Encoding
        embedding_output = inputs_embeds + pos_embeds

        # 7. Concatenate Image Embeddings and Text Embeddings
        hidden_states = torch.cat((projected_visual_features, embedding_output), dim=1)

        # 8. Create Causal Attention Mask for Text region
        tgt_mask = self._generate_future_mask(
            seq_length, embedding_output.dtype, embedding_output.device
        )

        # 9. Create Attention Mask for GIT
        combined_attention_mask = self.create_attention_mask(
            tgt=embedding_output,
            memory=projected_visual_features,
            tgt_mask=tgt_mask,
            past_key_values_length=0,
        )

        # 10. Pass through the Decoder layer repeatedly, the main part of the language model
        for idx, decoder_layer in enumerate(self.decoder.layers):
            layer_outputs = decoder_layer(
                hidden_states,
                attention_mask=combined_attention_mask,
                output_attentions=output_attentions,
                use_cache=use_cache,
            )

            hidden_states = layer_outputs[0]

        # 11. Dimension adjustment MLP specific to OPT
        hidden_states = self.decoder.project_out(hidden_states)

        # 12. Align the output interface
        return BaseModelOutputWithPast(
            last_hidden_state=hidden_states,
            past_key_values=next_cache,
            hidden_states=all_hidden_states,
            attentions=all_self_attns,
        )

尽管它可能看起来复杂,但如果你逐步进行,你会发现它遵循图示流程。实际代码可能看起来有点复杂,但首先把握主要过程将使理解其他部分变得更容易。这只是伪代码,因此对于详细部分,请参考已发布的实现。

最后,让我们简要看一下GITOPTForCausalLM部分。

代码语言:javascript
复制
class GitOPTForCausalLM(OPTForCausalLM):
    def __init__(
        self,
        config,
    ):
        super(GitOPTForCausalLM, self).__init__(config)
        self.model = GitOPTModel(config)

    def forward(
        ...
    ) -> CausalLMOutputWithPast:

        outputs = self.model(
            ...
        )

        sequence_output = outputs[0]
        logits = self.lm_head(sequence_output)

        loss = None
        if labels is not None:
            # Predict the next word as the task
            num_image_tokens = self.image_patch_tokens
            shifted_logits = logits[:, num_image_tokens:-1, :].contiguous()
            labels = labels[:, 1:].contiguous()
            loss_fct = CrossEntropyLoss()
            loss = loss_fct(shifted_logits.view(-1, self.config.vocab_size), labels.view(-1))

        return CausalLMOutputWithPast(
            loss=loss,
            logits=logits,
            ...
        )

模型内部的处理很简单。当提供标签时,即在训练期间,损失计算也在前向中执行。在shifted_logits中,从文本标记的第一个标记到倒数第二个标记获取了标记。然后,使用移动一个单词的标签计算交叉熵损失作为正确答案。

值得注意的是,在初始化函数中分配GitOPTModel的变量的名称应为self.model。如果检查OPTForCausalLM的父类的实现,你将看到在super初始化期间首先将OPT放在self.model中。如果更改此实例变量名称,将最终持有两个OPT,这可能会占用内存。


LoRA扩展

为了有效地微调LLM,我将使用一个名为Parameter-Efficient Fine-Tuning(PEFT)的库。由于它由Hugging Face开发,因此可以与Transformers无缝集成。虽然PEFT中有各种方法,但这次我将使用一个常见的称为Low-rank adaptation(LoRA)的方法进行一些实验。

如果模型支持PEFT,只需几行代码即可应用LoRA。

代码语言:javascript
复制
from transformers import AutoModelForCausalLM
from peft import get_peft_config, get_peft_model, LoraConfig

model = AutoModelForCausalLM.from_pretrained('microsoft/git-base')

peft_config = LoraConfig(
    task_type="CAUSAL_LM",
    r=8,
    lora_alpha=32,
    lora_dropout=0.1,
    target_modules=["v_proj"]
)

peft_model = get_peft_model(model, peft_config)

target_modules参数指定要转换为LoRA的模块。如果将target_modules提供为列表,它将被实现为对以每个字符串结尾的模块应用LoRA。LoRA仅应用于自注意力模块的“value”(v_proj)部分,以保持简单。

在模型中,ViT用于图像编码器部分。请注意,像这样指定ViT的self attention部分可能也会应用LoRA。这有点繁琐,但通过将其指定到键名不重叠的部分并将其传递给target_modules,可以避免这种情况。

代码语言:javascript
复制
target_modules = [f"model.image_encoder.vision_model.encoder.{i}.self_attn.v_proj" for i in range(len(model.model.decoder))]

生成的模型成为PeftModelForCausalLM类的实例。它具有名为base_model的实例变量,该变量保存将原始模型转换为LoRA的部分。作为示例,我展示了LoRA应用于ViT中self attention的v_proj。

代码语言:javascript
复制
(self_attn): GitVisionAttention(
  (k_proj): Linear(in_features=768, out_features=768, bias=True)
  (v_proj): Linear(
    in_features=768, out_features=768, bias=True
    (lora_dropout): ModuleDict(
      (default): Dropout(p=0.1, inplace=False)
    )
    (lora_A): ModuleDict(
      (default): Linear(in_features=768, out_features=8, bias=False)
    )
    (lora_B): ModuleDict(
      (default): Linear(in_features=8, out_features=768, bias=False)
    )
    (lora_embedding_A): ParameterDict()
    (lora_embedding_B): ParameterDict()
  )
  (q_proj): Linear(in_features=768, out_features=768, bias=True)
  (out_proj): Linear(in_features=768, out_features=768, bias=True)
)

在v_proj Linear中,你将找到添加的全连接层,如lora_A和lora_B。LoRA转换的Linear模块是一个名为Linear的类,它继承自PyTorch的Linear和LoraLayer。这是一个有点独特的模块,因此对于详情感兴趣的人应该查看实现。

请注意,使用PEFT创建的模型默认不保存除LoRA部分以外的任何内容。虽然有使用merge_and_unload方法保存的方法,但你可能希望使用Trainer在训练中间保存所有模型。重载Trainer的_save_checkpoints方法是一种方法,但为了避免麻烦,这次在训练阶段仅提取了PeftModel内部保存的原始模型部分。

代码语言:javascript
复制
model = get_peft_model(model, peft_config)
model.base_model.model.lm_head = model.lm_head
model = model.base_model.model

我相信有更有效的处理方法,因此我仍在进行研究。


对GIT-LLM进行实验

现在,让我们对迄今为止开发的模型进行一些实验。

有关训练配置和其他设置的详细信息,请参阅已发布的实现,因为它们基本上遵循相同的方法。

数据集:M3IT

对于实验,我希望使用一个将图像与文本配对且易于集成的数据集。在探索Hugging Face的Datasets时,我遇到了M3IT,这是由上海人工智能实验室开发的用于指令调整的多模态数据集。指令调整是一种在有限的数据量下取得惊人结果的方法。看起来M3IT专门为指令调整重新注释了各种现有数据集。

https://huggingface.co/datasets/MMInstruction/M3IT

这个数据集很容易使用,因此我决定在以下实验中利用它。

要使用M3IT进行训练,需要创建一个自定义的PyTorch数据集。

代码语言:javascript
复制
class SupervisedDataset(Dataset):
    def __init__(
        self,
        vision_model_name: str,
        model_name: str,
        loaded_dataset: datasets.GeneratorBasedBuilder,
        max_length: int = 128,
    ):
        super(SupervisedDataset, self).__init__()
        self.loaded_dataset = loaded_dataset
        self.max_length = max_length

        self.processor = AutoProcessor.from_pretrained("microsoft/git-base")

        # Setting up the corresponding Processor for each model
        self.processor.image_processor = CLIPImageProcessor.from_pretrained(vision_model_name)
        self.processor.tokenizer = AutoTokenizer.from_pretrained(
            model_name, padding_side="right", use_fast=False
        )

    def __len__(self) -> int:
        return len(self.loaded_dataset)

    def __getitem__(self, index) -> dict:
        # cf: https://huggingface.co/datasets/MMInstruction/M3IT#data-instances
        row = self.loaded_dataset[index]

        # Creating text input
        text = f'##Instruction: {row["instruction"]} ##Question: {row["inputs"]} ##Answer: {row["outputs"]}'

        # Loading the image
        image_base64_str_list = row["image_base64_str"]  # str (base64)
        img = Image.open(BytesIO(b64decode(image_base64_str_list[0])))

        inputs = self.processor(
            text,
            img,
            return_tensors="pt",
            max_length=self.max_length,
            padding="max_length",
            truncation=True,
        )
        # batch size 1 -> unbatch
        inputs = {k: v[0] for k, v in inputs.items()}
        inputs["labels"] = inputs["input_ids"]
        return inputs

init函数中,image_processor和tokenizer对应于它们各自的模型。传递的loaded_dataset参数应该来自MMInstruction/M3IT数据集。

代码语言:javascript
复制
coco_datasets = datasets.load_dataset("MMInstruction/M3IT", "coco")
test_dataset = coco_datasets["test"]

对于COCO指令调整数据集,训练、验证和测试之间的拆分与原始数据集相同,分别为566,747、25,010和25,010个图像文本对。其他数据集,如VQA或Video,也可以类似地处理,使其成为用于验证目的的多功能数据集。

样本数据如下:

这张图片的说明如下:

Instruction: 对图像进行简洁的描述,捕捉其主要组成部分、它们之间的关系以及任何显着的细节。Question: 问题。Answer: 回答。对于COCO数据集,即用于标题的数据集,将问题部分留空。

让我们更深入地了解processor的操作。基本上,它对图像进行标准化并对文本进行标记。长度小于max_length的输入也会被填充。processor返回的处理过的数据是一个包含:

  • input_ids:一个标记化文本的数组。
  • attention_mask:用于标记化文本的掩码(填充为0)。
  • pixel_values:一个标准化图像的数组,也转换为通道优先。

这些键名对应于模型forward函数的参数,因此不应更改它们。最后,input_ids直接传递给名为labels的键。GitOPTForCausalLM的forward函数通过预测下一个移位的单词来计算损失。


实验1:确定微调位置

在关于GIT模型的研究论文中,解释了使用强大的视觉编码器和采用语言模型的随机参数。这一次,由于最终目标是使用7B级语言模型,将对语言模型应用预训练模型。将对以下模块进行微调的检查。GIT Projection作为初始化模块总是包括在内。某些组合可能看起来多余,但在此试验中无需过多考虑。

为了进行培训,对设置为训练的模块给予梯度,而对其余模块进行修改以不具有梯度。

代码语言:javascript
复制
# Specifying the parameters to train (training all would increase memory usage)
for name, p in model.model.named_parameters():
    if np.any([k in name for k in keys_finetune]):
        p.requires_grad = True
    else:
        p.requires_grad = False

视觉编码器和LLM用于此检查的模块是:

  • openai/clip-vit-base-patch16
  • facebook/opt-350m

训练使用COCO数据集,持续5个时期。

以下是在每个实验期间训练的目标模块:

  • Proj:GIT Projection。随机初始化,因此始终进行训练。
  • LoRA:在语言模型的自注意力中应用查询、键和值。
  • OPT:所有层都进行了训练。
  • ViT:所有层都进行了训练。
  • Head:对OPT的最终lm_head进行了训练。

(注意:虽然LoRA可以应用于ViT,但为了避免实验过于复杂,这次没有包括它。)

这张图显示了训练损失。图例中的Proj、LoRA、OPT、ViT和Head是上述训练的模块的解释。(图由作者制作) 如训练损失图所示,显然有些组表现不佳,这是在训练中包括OPT时的情况。尽管所有实验在相当相似的条件下进行,但在微调语言模型时可能需要更详细的调整,例如学习率。接下来将研究不包括OPT在内的模型的结果。

这张图显示了验证损失。图例中的Proj、LoRA、OPT、ViT和Head是上述训练的模块的解释。(图由作者制作) 使用Projection+LoRA模型,训练和验证损失都最大程度地减小。微调最终Head层显示出几乎相同的结果。如果还对ViT进行训练,损失似乎会略高,结果似乎不稳定。即使在ViT训练期间添加LoRA,损失仍然往往较高。对于使用此数据进行微调,似乎使用没有更新其参数的预训练ViT模型会产生更稳定的结果。LoRA的有效性在各个地方都得到了承认,从这个实验中可以看出,将LoRA添加到LLM中改善了训练和验证损失。

回顾一下对一些测试数据的推断结果:

当训练OPT本身时,结果就像损失的结果一样差,使模型无言以对。此外,当训练ViT时,输出在语义上是有道理的,但与给定的图像完全描述不同的事物。然而,其他结果似乎在某种程度上捕捉了图像的特征。例如,第一张图提到了“猫”和“香蕉”,第二张图则识别了“交通标志”。与没有LoRA的结果相比,后者倾向于重复使用相似的词语,但使用LoRA似乎使其略显更加自然。训练Head产生了有趣的输出,例如在第一张图中使用“playing”而不是“eating”。尽管这些结果中有一些不自然的元素,但可以推断出训练成功地捕捉了图像特征。

实验2:比较十亿级别的模型

在先前的实验中,微调条件下使用了一个稍小的语言模型OPT-350m。现在,意图是将语言模型切换到一个7B模型。不仅仅满足于OPT,还将引入更强大的LLM,LLaMA和MPT。

整合这两个模型可以以与OPT类似的方式完成。参考LlamaModel和MPTModel的forward函数,将投影的图像向量与文本标记相结合,并将蒙版从因果关注蒙版更改为GIT的关注蒙版。请注意:对于MPT,蒙版不是(0,-inf),而是(False,True)。随后的过程可以类似地实现。

要使用OPT的7B级模型,只需将模型名称从facebook/opt-350m更改为facebook/opt-6.7b

对于LLaMA,由于LLaMA2已经可用,它将成为首选模型。要使用这个预训练模型,需要Meta和Hugging Face的批准。在Hugging Face上设置账户是必要的,确保设置好。批准通常在几小时内完成。之后,在执行训练的终端上登录Hugging Face。

代码语言:javascript
复制
huggingface-cli login

你可以使用在Hugging Face账户→设置→访问令牌中创建的令牌登录。

训练参数保持一致,使用COCO数据集,持续3个时期。根据实验1的结果,微调的模块集为Projection + LoRA。

让我们来看看结果。

回顾损失,明显可以看出使用LLaMA2和MPT作为LLM的模型显示出更为令人满意的减少。让我们也观察一下推断的结果。

关于第一张图片,对于所有模型,表情似乎比OPT-350m更自然。没有“香蕉与香蕉”的奇怪表达,突显了LLM的强大之处。对于第二张图片,对于“交通灯”或“建筑”等短语仍然存在一些困难。对于这样复杂的图像,可能需要考虑升级ViT模型。

最后,让我们对与GPT-4一起变得流行的图像进行推理。

尽管由于使用了LLM,预计会有流利的响应,但结果却相当简单。这可能是因为该模型仅在COCO上进行了训练。

实验3. 增加数据

鉴于先前实验的令人失望的结果,决定在训练中引入除COCO以外的数据。当前正在使用的M3IT数据集非常全面,可以处理与COCO相同格式的大量数据。

计划使用来自此来源的数据,但不包括“中文”和“视频”类别。最初,COCO训练数据集包含566,747个数据。通过与其他来源结合,这一数字增加到1,361,650。尽管大小大致翻了一番,但由于任务多样性的增加,认为数据集的质量已经提高。

使用ConcatDataset可以轻松处理多个PyTorch数据集。

代码语言:javascript
复制
dataset_list = [
    datasets.load_dataset("MMInstruction/M3IT", i) for i in m3it_name_list
]
train_dataset = torch.utils.data.ConcatDataset([d["train"] for d in dataset_list])

进行了1个时期的训练,使用LLaMA2模型对Projection和LoRA进行微调,类似于实验2。

由于这次没有损失可比较,让我们直接进入推理结果。

除了解决简单问题外,模型现在还处理更复杂的挑战。通过添加比仅进行字幕更为复杂的任务的数据集,其功能得到了显著扩展。仅在进行了1个时期的训练后实现这一准确度水平令人惊讶。

让我们测试以下示例图像。鉴于数据集的增加多样性,提问方式稍有修改。

虽然描述仍然是“伞”,但感觉好像变得更好了。为了进一步改进,需要增加训练时期的数量,添加更多类型或量级的数据集,并利用更强大的ViT或LLM。尽管如此,仅在半天内利用计算和数据资源就能开发出这样的模型令人印象深刻。


额外实验. 图像是否变成了文字?

让我们再次看一下GIT结构。

如图所示,在通过视觉编码器进行特征提取后,图像通过视觉投影与矢量化文本平等对待。换句话说,视觉投影可能将图像向量转换为文本向量。为了了解在Visual Projection之后这些向量的样子,进行了一项调查。

虽然有使用Head将向量在投影后还原回文本的选项,但发现即使使用Embedding模块矢量化的向量也不能使用此方法还原为原始文本。因此,在输入LLM之前紧密类似于文本向量的向量应被指定为相应的单词。使用Embedding模块向tokenizer中注册的所有标记进行矢量化,选择余弦相似度最高的标记作为目标词。

此实验使用的图像是一只猫。

现在,让我们进行分析(整个分析在这里可用)。首先,对所有注册标记进行矢量化。

代码语言:javascript
复制
coco_datasets = datasets.load_dataset("MMInstruction/M3IT", "coco")
test_dataset = coco_datasets["test"]
supervised_test_dataset = SupervisedDataset(model_name, vision_model_name, test_dataset, 256)

ids = range(supervised_test_dataset.processor.tokenizer.vocab_size)
all_ids = torch.tensor([i for i in ids]).cuda()
token_id_to_features = model.model.embed_tokens(all_ids)

接下来,提取ViT和Projection将被转换为单词的图像向量。

代码语言:javascript
复制
inputs = supervised_test_dataset[0] # Picking a sample arbitrarily
pixel_values = inputs["pixel_values"]
out_vit = model.model.image_encoder(pixel_values).last_hidden_state
out_vit = model.model.visual_projection(out_vit)

计算这些向量与单词向量的点积,并将具有最大值的结果解码为相关的标记ID。

代码语言:javascript
复制
# Dot product
nearest_token = out_vit[0] @ token_id_to_features.T

# The index of the maximum value corresponds to the relevant token ID
visual_out = nearest_token.argmax(-1).cpu().numpy()
decoded_text = supervised_test_dataset.processor.tokenizer.batch_decode(visual_out)
print(decoded_text)

"""
['otr', 'eg', 'anto', 'rix', 'Nas', ...]
"""

如所打印的decoded_text,出现了一些不熟悉的词汇。由于一些词汇重复出现,对其进行了计数。

代码语言:javascript
复制
print(pd.Series(decoded_text).value_counts())
"""
mess        43
atura       29
せ           10
Branch      10
Enum         9
bell         9
worden       7
...
"""

看起来出现了大量不熟悉的词汇。根据位置的不同,它们可能传达了有意义的信息。让我们将这些词汇绘制在猫图像上。

代码语言:javascript
复制
n_patches = 14
IMAGE_HEIGHT = 468
IMAGE_WIDTH = 640

y_list = np.arange(15, IMAGE_HEIGHT, IMAGE_HEIGHT//n_patches)
x_list = np.arange(10, IMAGE_WIDTH, IMAGE_WIDTH//n_patches)

plt.figure()
plt.axis("off")
plt.imshow(np.array(image), alpha=0.4)
for index in np.arange(n_patches ** 2):
    y_pos = index // n_patches
    x_pos = index - y_pos * n_patches

    y = y_list[y_pos]
    x = x_list[x_pos]

    # The first token is the bos token, so it is excluded
    word = decoded_text[index + 1]

    # For differentiating words by color
    plt.annotate(word, (x, y), size=7, color="blue")
plt.show()
plt.clf()
plt.close()

频繁出现的词语以颜色区分。结果似乎表明它们不仅仅是被投影到有意义的单词上。虽然单词"Cat"可能叠加在猫图像上,使其具有一定的相关性,但其含义仍然不清楚。

这个实验的无定论结果可能是因为强制选择具有高余弦相似度的单词。无论如何,这种方法并不涉及简单地投射单词并创建图像提示。从图像中提取的向量通过Visual Projection转换为token空间中的向量,这些向量在含义上似乎有些相似,充当着神秘提示的功能。可能最好不要深入研究这个问题。

结论

在这篇技术博客文章中,介绍了将LLMs整合到视觉语言模型GIT中的方法。此外,使用开发的模型进行了各种实验。虽然有成功和失败,但希望继续使用视觉语言模型进行实验,积累见解。请将此文章视为参考,并鼓励你创建自己的视觉语言模型,探索其潜力。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-06-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 磐创AI 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 将GIT转变为LLM
  • GIT是什么?
  • 使用Hugging Face的Transformers的LLM
  • 使用Transformers进行GIT
  • GIT的注意力掩码
  • 连接GIT和OPT
  • LoRA扩展
  • 对GIT-LLM进行实验
  • 数据集:M3IT
  • 实验1:确定微调位置
  • 实验2:比较十亿级别的模型
  • 实验3. 增加数据
  • 额外实验. 图像是否变成了文字?
  • 结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档