解鎖多模態大語言模型:從原理到實戰,一文全掌握! 原創
引言:人工智能領域的新焦點
在人工智能飛速發展的大環境下,近年來備受矚目的一個領域便是多模態大語言模型(MLLMs)。這些具有變革性的工具不僅能夠處理和生成文本,還能處理圖像、音頻和視頻等其他形式的數據。通過借助機器學習和深度學習算法的強大力量,多模態大語言模型能夠捕捉人類在多種模態下交流的細微差別,為自然語言處理、計算機視覺和多媒體分析等領域的應用開辟了新的可能性。
隨著我們不斷拓展多模態大語言模型的能力邊界,研究這些模型在結合視覺和聽覺信息后生成新穎文本內容的潛力變得愈發重要。在一個由人工智能驅動的 “多模態世界” 中,系統能夠無縫整合多種形式的數據以創造新的意義和價值,這已不再是科幻小說中的情節。
事實上,正如近期的研究和應用所展示的那樣,多模態大語言模型在生成連貫、特定于上下文且富有細微差別的文本方面已經展現出了卓越的能力。例如,一個在圖像和字幕數據集上訓練的多模態模型,能夠生成與新圖像語義準確且在風格上與原始字幕一致的新穎描述。
作為 “讓我們……” 系列的一部分,我們旨在通過進行一項實驗,進一步探索多模態大語言模型的潛力,該實驗旨在突破結合視覺和語言信息時的可能性極限。本研究有兩個具體目標:一是掌握多模態大語言模型的基礎知識;二是通過實驗來展示多模態大語言模型的能力。
多模態大語言模型(MLLM)
多模態大語言模型(MLLM)是一種人工智能(AI)模型,它可以處理和生成文本,以及其他形式的數據,比如:
- 圖像:多模態大語言模型能夠理解圖像的內容,包括其中的物體、場景和動作。
- 音頻:它可以分析音頻記錄,包括語音、音樂和各種聲音。
- 視頻:能夠處理視頻內容,涵蓋視覺和聽覺信息。
換句話說,多模態大語言模型是一種在多樣化的數據類型上進行訓練的大語言模型,這使得它能夠理解和生成與這些不同模態(或形式)的數據相關的文本。
多模態大語言模型旨在處理多模態數據的復雜性,這可能包括:
- 語義:理解多種語言中單詞、短語和句子的含義。
- 句法:分析語言的結構,包括語法和句子結構。
- 視覺:識別圖像和視頻中的物體、場景和動作。
- 聽覺:分析語音、音樂和聲音。
多模態大語言模型(MLLMs)基于幾個關鍵組件構建:
- 模態編碼器:這個組件將圖像、音頻和視頻等輸入數據轉換為特征表示。例如ViT、CLIP和HuBERT等。
- 輸入投影器:該組件將編碼器的特征與大語言模型的文本輸入連接起來。方法包括線性、多層感知器(MLP)和交叉注意力。
- 大語言模型(LLMs):在多模態大語言模型中會使用像GPT、LLaMA和Flan-T5這樣的大語言模型架構。
- 輸出投影器:此組件將語言模型的特征映射到圖像、視頻或音頻等數據類型的特征。例如MLP和Tiny Transformer。
- 模態生成器:該組件使用特征表示生成所需的數據。例如用于圖像的Stable Diffusion、用于視頻的Zeroscope和用于音頻的AudioLDM。
自GPT-4發布以來,由于其多模態示例,對多模態語言模型(MLLMs)的研究激增。
多模態大語言模型的優點包括:
- 更好地理解人類交流:通過處理多種形式的數據,多模態大語言模型可以更好地理解人類在不同模態下的交流方式。
- 增強的多模態生成能力:多模態大語言模型可以生成更細致入微且特定于上下文的文本,同時考慮到場景或情況的視覺和聽覺方面。
- 更好地表示語言:多模態大語言模型能夠捕捉現實世界場景中語言使用的復雜性,在這些場景中通常存在多種形式的數據。
總之,多模態大語言模型是一種強大的人工智能工具,它可以處理、生成和理解文本以及其他形式的數據,如圖像、音頻和視頻。
代碼實現
BLIP-2模型架構將問題和圖像作為輸入進行處理,其輸出是基于問題和圖像上下文的答案。BLIP-2由以下組件組成:
- 圖像編碼器:使用CLIP ViT(視覺變換器)從輸入圖像中提取視覺特征。
- 輸入投影器:Q-Former負責將問題和圖像特征投影為適合大語言模型的統一表示。
- 大語言模型(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
然后,我們調用在實驗過程中會用到的庫。
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
實驗設置
在這一步,我們準備數據集,構建模型,并制定訓練策略,以確保模型在視覺問答任務中表現最佳。
現在,將數據集加載到數據集設置中:
ds = load_dataset("json", data_files=f"{data_path}/train.jsonl")
ds
輸出結果為:
DatasetDict({
train: Dataset({
features: ['question', 'answer', 'ques_type', 'grade', 'label', 'pid', 'unit', 'hint'],
num_rows: 14551
})
})
由于我們已經擁有了數據集,接下來開始構建在訓練過程中起著至關重要作用的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
由于我們在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()
輸出結果為:
trainable params: 2,359,296 || all params: 387,031,868 || trainable%: 0.6096
我們創建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)
然后,進行創建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)
此時我們可以開始訓練過程。
# ======== 設置部分========
#
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 )
最后,我們可以通過以下步驟測試模型的能力:
調用剛剛訓練好的模型:
retrain_model_dir = './save_model'
processor = BlipProcessor.from_pretrained(retrain_model_dir)
model = BlipForQuestionAnswering.from_pretrained(retrain_model_dir).to(device)
選擇一個樣本進行測試:
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
"""
使用檢索到的樣本向模型提問答案:
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
結論
我們探索了多模態大語言模型(MLLMs)這個充滿魅力的世界,深入研究了它們的基本原理以及在各種應用中,尤其是在視覺問答領域所蘊含的令人興奮的潛力。此外,我們看到了這些模型如何經過訓練來理解和推理文本和視覺信息,使它們能夠以令人印象深刻的準確性回答與圖像和視頻相關的問題。
隨著我們不斷前進,多模態大語言模型的研究和開發將繼續推動人工智能領域的可能性邊界。我們可以期待看到更復雜的模型,這些模型能夠處理需要深入理解我們周圍世界的復雜任務。
本文轉載自公眾號Halo咯咯 作者:基咯咯
