解锁多模态大语言模型:从原理到实战,一文全掌握! 原创

发布于 2025-3-13 10:35
浏览
0收藏

引言:人工智能领域的新焦点

在人工智能飞速发展的大环境下,近年来备受瞩目的一个领域便是多模态大语言模型(MLLMs)。这些具有变革性的工具不仅能够处理和生成文本,还能处理图像、音频和视频等其他形式的数据。通过借助机器学习和深度学习算法的强大力量,多模态大语言模型能够捕捉人类在多种模态下交流的细微差别,为自然语言处理、计算机视觉和多媒体分析等领域的应用开辟了新的可能性。

随着我们不断拓展多模态大语言模型的能力边界,研究这些模型在结合视觉和听觉信息后生成新颖文本内容的潜力变得愈发重要。在一个由人工智能驱动的 “多模态世界” 中,系统能够无缝整合多种形式的数据以创造新的意义和价值,这已不再是科幻小说中的情节。

事实上,正如近期的研究和应用所展示的那样,多模态大语言模型在生成连贯、特定于上下文且富有细微差别的文本方面已经展现出了卓越的能力。例如,一个在图像和字幕数据集上训练的多模态模型,能够生成与新图像语义准确且在风格上与原始字幕一致的新颖描述。

作为 “让我们……” 系列的一部分,我们旨在通过进行一项实验,进一步探索多模态大语言模型的潜力,该实验旨在突破结合视觉和语言信息时的可能性极限。本研究有两个具体目标:一是掌握多模态大语言模型的基础知识;二是通过实验来展示多模态大语言模型的能力。

多模态大语言模型(MLLM)

多模态大语言模型(MLLM)是一种人工智能(AI)模型,它可以处理和生成文本,以及其他形式的数据,比如:

  1. 图像:多模态大语言模型能够理解图像的内容,包括其中的物体、场景和动作。
  2. 音频:它可以分析音频记录,包括语音、音乐和各种声音。
  3. 视频:能够处理视频内容,涵盖视觉和听觉信息。

换句话说,多模态大语言模型是一种在多样化的数据类型上进行训练的大语言模型,这使得它能够理解和生成与这些不同模态(或形式)的数据相关的文本。

多模态大语言模型旨在处理多模态数据的复杂性,这可能包括:

  1. 语义:理解多种语言中单词、短语和句子的含义。
  2. 句法:分析语言的结构,包括语法和句子结构。
  3. 视觉:识别图像和视频中的物体、场景和动作。
  4. 听觉:分析语音、音乐和声音。

解锁多模态大语言模型:从原理到实战,一文全掌握!-AI.x社区

多模态大语言模型(MLLMs)基于几个关键组件构建:

  1. 模态编码器:这个组件将图像、音频和视频等输入数据转换为特征表示。例如ViT、CLIP和HuBERT等。
  2. 输入投影器:该组件将编码器的特征与大语言模型的文本输入连接起来。方法包括线性、多层感知器(MLP)和交叉注意力。
  3. 大语言模型(LLMs):在多模态大语言模型中会使用像GPT、LLaMA和Flan-T5这样的大语言模型架构。
  4. 输出投影器:此组件将语言模型的特征映射到图像、视频或音频等数据类型的特征。例如MLP和Tiny Transformer。
  5. 模态生成器:该组件使用特征表示生成所需的数据。例如用于图像的Stable Diffusion、用于视频的Zeroscope和用于音频的AudioLDM。

解锁多模态大语言模型:从原理到实战,一文全掌握!-AI.x社区

自GPT-4发布以来,由于其多模态示例,对多模态语言模型(MLLMs)的研究激增。

多模态大语言模型的优点包括:

  1. 更好地理解人类交流:通过处理多种形式的数据,多模态大语言模型可以更好地理解人类在不同模态下的交流方式。
  2. 增强的多模态生成能力:多模态大语言模型可以生成更细致入微且特定于上下文的文本,同时考虑到场景或情况的视觉和听觉方面。
  3. 更好地表示语言:多模态大语言模型能够捕捉现实世界场景中语言使用的复杂性,在这些场景中通常存在多种形式的数据。

解锁多模态大语言模型:从原理到实战,一文全掌握!-AI.x社区

总之,多模态大语言模型是一种强大的人工智能工具,它可以处理、生成和理解文本以及其他形式的数据,如图像、音频和视频。

代码实现

BLIP-2模型架构将问题和图像作为输入进行处理,其输出是基于问题和图像上下文的答案。BLIP-2由以下组件组成:

  1. 图像编码器:使用CLIP ViT(视觉变换器)从输入图像中提取视觉特征。
  2. 输入投影器:Q-Former负责将问题和图像特征投影为适合大语言模型的统一表示。
  3. 大语言模型(LLMs):我们使用Flan-T5/OPT作为其语言模型核心,它根据问题和图像的组合上下文生成最终答案。

BLIP-2是一种高效且有效的视觉与语言预训练策略,它利用冻结的预训练模型和轻量级的查询变换器,以显著更少的可训练参数实现了领先的性能。这种方法使模型在各种任务中表现出色,包括零样本VQAv2以及带有自然语言指令的图像到文本生成。

在接下来的部分,我们将使用transformers库在VQA数据集上训练BLIP-2模型。VQA数据集包含14,550个样本,每个样本由一个输入(图像 + 问题)和一个输出(答案)组成。

环境设置

我们从指定位置下载VQA数据集以及必要的库。

# 下载并解压数据集
!gdown 1--CmXocqkrcPR-bbSDztnxBM9dBC8uUJ 
!unzip /content/IconDomainVQAData.zip

# 安装必要的库
!pip install -q peft transformers bitsandbytes datasets
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

然后,我们调用在实验过程中会用到的库。

import os, json
from PIL import Image

from tqdm import tqdm

import torch
from torch.utils.data import Dataset, DataLoader
from torch import optim

from peft import LoraConfig, get_peft_model

from transformers import BlipProcessor, BlipForQuestionAnswering

from datasets import load_dataset, DatasetDict
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

实验设置

在这一步,我们准备数据集,构建模型,并制定训练策略,以确保模型在视觉问答任务中表现最佳。

现在,将数据集加载到数据集设置中:

ds = load_dataset("json", data_files=f"{data_path}/train.jsonl")
ds
  • 1.
  • 2.

输出结果为:

DatasetDict({
    train: Dataset({
        features: ['question', 'answer', 'ques_type', 'grade', 'label', 'pid', 'unit', 'hint'],
        num_rows: 14551
    })
})
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

由于我们已经拥有了数据集,接下来开始构建在训练过程中起着至关重要作用的Dataset对象。

# 构建Dataset
class VQADataset(Dataset):

    def __init__(self, dataset, processor, data_path):
        self.dataset = dataset
        self.processor = processor
        self.data_path = data_path

    def __len__(self):
        return len(self.dataset)

    def __getitem__(self, idx):

        question = self.dataset[idx]['question']
        answer = self.dataset[idx]['answer']
        image_id = self.dataset[idx]['pid']
        image_path = os.path.join(self.data_path, f"train_fill_in_blank/train_fill_in_blank/{image_id}/image.png")
        image = Image.open(image_path).convert("RGB")
        text = question

        encoding = self.processor(image, text, padding="max_length", truncatinotallow=True, return_tensors="pt")
        labels = self.processor.tokenizer.encode(answer, max_length=8, pad_to_max_length=True, return_tensors="pt")
        encoding['labels'] = labels
        for k, v in encoding.items():
            encoding[k] = v.squeeze()

        return encoding
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

由于我们在VQADataset对象中有了处理器,接下来加载处理所需的内容。我们使用Salesforce/blip-vqa-base模型,并对模型进行一些量化处理。

model_id = "Salesforce/blip-vqa-base"
processor = BlipProcessor.from_pretrained(model_id)
model = BlipForQuestionAnswering.from_pretrained(model_id)

lora_config = LoraConfig(
                    r=16,
                    lora_alpha=32,
                    lora_dropout=0.05,
                    bias="none",
                    target_modules=["query", "key"]
                    )

model = get_peft_model(model, lora_config )
device = torch.device("cuda"if torch.cuda.is_available() else"cpu")
model.to(device)
model.print_trainable_parameters()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

输出结果为:

trainable params: 2,359,296 || all params: 387,031,868 || trainable%: 0.6096
  • 1.

我们创建DataLoader。首先,为训练数据集和验证数据集创建Dataset对象。

train_dataset = VQADataset(dataset=ds["train"],
                           processor=processor,
                           data_path=data_path)
val_dataset = VQADataset(dataset=ds["train"],
                           processor=processor,
                           data_path=data_path)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

然后,进行创建DataLoader所需的操作。

batch_size=8
train_dataloader = DataLoader(train_dataset,
                              batch_size=batch_size,
                              shuffle=True,
                              pin_memory=True)
val_dataloader = DataLoader(val_dataset,
                            batch_size=batch_size,
                            shuffle=False,
                            pin_memory=True)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

此时我们可以开始训练过程。

# ======== 设置部分========
# 
optimizer = optim.AdamW(model.parameters(),
                        lr=4e-5)
scheduler = optim.lr_scheduler.ExponentialLR(optimizer,
                                             gamma=0.9,
                                             last_epoch=-1,
                                             verbose=True)
n_epochs = 1
min_val_loss = float("inf")
scaler = torch.cuda.amp.GradScaler()

# ======== 训练部分========
# 
for epoch in range(n_epochs):

    # 训练设置
    train_loss = []
    model.train()
    for idx, batch in zip(tqdm(range(len(train_dataloader)),
                               desc=f"Training Batch {epoch+1}"), train_dataloader):
        input_ids = batch.pop("input_ids").to(device)
        pixel_values = batch.pop("pixel_values").to(device)
        attention_masked = batch.pop('attention_mask').to(device)
        labels = batch.pop('labels').to(device)

        with torch.amp.autocast(device_type='cuda', dtype=torch.float16):
            outputs = model(input_ids=input_ids,
                            pixel_values=pixel_values,
                            labels=labels)

        loss = outputs.loss
        train_loss.append(loss.item())

        ## 反向传播
        optimizer.zero_grad()
        scaler.scale(loss).backward()
        scaler.step(optimizer)
        scaler.update()

    # 验证设置
    val_loss = []
    model.eval()
    for idx, batch in zip(tqdm(range(len(val_dataloader)),
                               desc=f"Validating Batch {epoch+1}"), val_dataloader):
        input_ids = batch.pop("input_ids").to(device)
        pixel_values = batch.pop("pixel_values").to(device)
        attention_masked = batch.pop('attention_mask').to(device)
        labels = batch.pop('labels').to(device)

        with torch.amp.autocast(device_type='cuda', dtype=torch.float16):
            outputs = model(input_ids=input_ids,
                            pixel_values=pixel_values,
                            attention_mask=attention_masked,
                            labels=labels)

        loss = outputs.loss
        val_loss.append(loss.item())

        avg_train_loss = sum(train_loss)/len(train_loss)
        avg_val_loss = sum(val_loss)/len(val_loss)
        lr_per_epoch = optimizer.param_groups[0]["lr"]
    print(f"Epoch: {epoch + 1} - Training Loss: {avg_train_loss} - Eval Loss: {avg_val_loss} - LR: {lr_per_epoch}")

    scheduler.step()

    if avg_val_loss < min_val_loss:
        model.save_pretrained('./save_model', from_pt=True)
        print ("Saved model to ./save_model")
        min_eval_loss = avg_val_loss
processor.save_pretrained ("./save_model", from_pt=True )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.

最后,我们可以通过以下步骤测试模型的能力:

调用刚刚训练好的模型

retrain_model_dir = './save_model'
processor = BlipProcessor.from_pretrained(retrain_model_dir)
model = BlipForQuestionAnswering.from_pretrained(retrain_model_dir).to(device)
  • 1.
  • 2.
  • 3.

选择一个样本进行测试

test_data_dir = f"{data_path}/test_data/test_data"
samples = os.listdir(test_data_dir)
sample_path = os.path.join(test_data_dir, samples[0])
json_path = os.path.join(sample_path, "data.json")

with open(json_path, "r") as json_file:
    data = json.load(json_file)
    question = data["question"]
    image_id = data["id"]

"""
question
"""

"""
image_path = os.path.join(test_data_dir, f"{image_id}", "image.png")
image = Image.open(image_path).convert("RGB")
image
"""
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

使用检索到的样本向模型提问答案

encoding = processor(image,
                     question,
                     return_tensors="pt").to(device, torch.float16)

outputs = model.generate(**encoding)

generated_text = processor.decode(outputs[0], skip_special_tokens=True)

generated_text
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

结论

我们探索了多模态大语言模型(MLLMs)这个充满魅力的世界,深入研究了它们的基本原理以及在各种应用中,尤其是在视觉问答领域所蕴含的令人兴奋的潜力。此外,我们看到了这些模型如何经过训练来理解和推理文本和视觉信息,使它们能够以令人印象深刻的准确性回答与图像和视频相关的问题。

随着我们不断前进,多模态大语言模型的研究和开发将继续推动人工智能领域的可能性边界。我们可以期待看到更复杂的模型,这些模型能够处理需要深入理解我们周围世界的复杂任务。


本文转载自公众号Halo咯咯    作者:基咯咯

原文链接:​​https://mp.weixin.qq.com/s/oC3yW2E7LvW3nAVKDgkyyg​

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报


回复
相关推荐