3. 视觉问答LoRA微调

任务目标

使用LoRA对视觉问答模型BLIP-2进行参数高效微调,使其能够针对特定领域(本文以自动驾驶场景为例)的图像回答指定问题。微调完成后,模型应能对未见过的领域图像给出符合预期的回答。

环境与依赖

# requirements.txt
torch==2.1.0
transformers==4.36.0
peft==0.7.0
datasets==2.16.0
pillow==10.1.0
accelerate==0.25.0

数据集准备

使用自定义小型数据集,包含自动驾驶场景图像及对应的问答对。每一条样本格式为:{"image": 图像路径, "question": "前方交通信号灯是什么颜色?", "answer": "红色"}。将数据组织为JSON文件,通过datasets库加载。

关键完整代码

import torch
from transformers import Blip2Processor, Blip2ForConditionalGeneration
from peft import LoraConfig, get_peft_model, TaskType
from datasets import load_dataset
from torch.utils.data import DataLoader
from PIL import Image
import json

# 1. 加载模型与处理器
model_id = "Salesforce/blip2-opt-2.7b"
processor = Blip2Processor.from_pretrained(model_id)
model = Blip2ForConditionalGeneration.from_pretrained(
    model_id, torch_dtype=torch.float16, device_map="auto"
)

# 2. 配置LoRA
lora_config = LoraConfig(
    task_type=TaskType.QUESTION_ANS,
    r=8,
    lora_alpha=32,
    lora_dropout=0.1,
    target_modules=["q_proj", "v_proj"],  # 仅微调注意力层的Q和V投影
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()  # 预期输出: trainable params: ~4.2M / 总参数 ~2.7B

# 3. 准备数据集
def preprocess(example):
    image = Image.open(example["image"]).convert("RGB")
    inputs = processor(images=image, text=example["question"], return_tensors="pt")
    labels = processor(text=example["answer"], return_tensors="pt").input_ids
    inputs["labels"] = labels
    return {k: v.squeeze(0) for k, v in inputs.items()}

dataset = load_dataset("json", data_files="data.json")["train"]
dataset = dataset.map(preprocess, remove_columns=["image", "question", "answer"])
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)

# 4. 训练循环
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)
model.train()

for epoch in range(3):
    for batch in dataloader:
        batch = {k: v.to(model.device) for k, v in batch.items()}
        outputs = model(**batch)
        loss = outputs.loss
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()
        print(f"Epoch {epoch} Loss: {loss.item():.4f}")

# 5. 保存LoRA权重
model.save_pretrained("./blip2-lora-autonomous-driving")

运行方式

# 1. 准备数据文件 data.json(格式见上文)
# 2. 执行训练脚本
python train_blip2_lora.py

# 3. 推理验证
python inference_blip2_lora.py

推理与验证代码

import torch
from transformers import Blip2Processor, Blip2ForConditionalGeneration
from peft import PeftModel
from PIL import Image

model_id = "Salesforce/blip2-opt-2.7b"
processor = Blip2Processor.from_pretrained(model_id)
base_model = Blip2ForConditionalGeneration.from_pretrained(
    model_id, torch_dtype=torch.float16, device_map="auto"
)
model = PeftModel.from_pretrained(base_model, "./blip2-lora-autonomous-driving")
model.eval()

# 测试图像
image = Image.open("test_traffic_light.jpg").convert("RGB")
question = "前方交通信号灯是什么颜色?"
inputs = processor(images=image, text=question, return_tensors="pt").to(model.device)

with torch.no_grad():
    generated_ids = model.generate(**inputs, max_new_tokens=20)
answer = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
print(f"问题: {question}")
print(f"模型回答: {answer}")

验证方法与通过条件

  • 训练收敛验证:训练过程中loss应持续下降,3个epoch后loss应低于初始值的50%。
  • 功能验证:使用训练集外的自动驾驶场景图像进行推理,模型应能正确回答如"红灯"、"绿灯"、"行人正在过马路"等特定领域答案。
  • 对比验证:加载未微调的原始BLIP-2模型对相同测试图像提问,原始模型回答应为通用描述或错误答案,而微调后模型能给出领域正确答案,以此证明LoRA微调生效。
  • 参数效率验证:训练完成后打印可训练参数量,应远小于全量微调(约占总参数0.15%),验证LoRA的高效性。

常见问题

  • 显存不足:若GPU显存小于16GB,可降低batch_size至1,或使用gradient_accumulation_steps=4模拟更大batch。
  • LoRA不生效:确认target_modules中的模块名与模型实际结构匹配。可通过print(model.base_model.model)查看BLIP-2的QFormer层结构,将模块名替换为实际存在的层名。
  • 数据格式错误:图像路径必须为路径或相对脚本运行目录的正确路径,否则Image.open会报错。