多模态指令微调
多模态指令微调(Multimodal Instruction Tuning)是赋予视觉-语言模型(VLM)遵循复杂视觉指令能力的关键技术。通过在特定格式的图文数据上进行有监督微调,模型能够理解人类意图并生成符合预期的多模态响应。1
1. 指令微调基础
1.1 视觉指令微调的定义
视觉指令微调(Visual Instruction Tuning)是指在多模态大模型训练过程中,使用包含视觉信息和自然语言指令的数据,让模型学会根据图像内容响应多样化的用户查询。与传统视觉任务不同,指令微调使模型能够处理开放式、复杂化的视觉理解任务。2
核心目标:
- 让模型理解图像中的视觉内容
- 遵循自然语言指令执行特定任务
- 生成连贯、准确的多模态响应
1.2 与传统VQA的区别
传统视觉问答(Visual Question Answering, VQA)与视觉指令微调存在本质差异:
| 维度 | 传统VQA | 视觉指令微调 |
|---|---|---|
| 问题类型 | 固定形式 | 开放式、自由格式 |
| 响应格式 | 短答案/选择题 | 详细描述、推理过程 |
| 指令遵循 | 无 | 强指令跟随能力 |
| 任务泛化 | 单任务 | 多任务统一处理 |
| 示例 | ”图中有几只猫?" | "请详细描述这幅画的艺术风格,并分析其构图特点” |
传统VQA的数据集(如VQA v2、CLEVR)通常包含有限的问题类型和固定答案格式。而视觉指令微调强调模型对任意形式视觉指令的理解和执行能力,这与大型语言模型(LLM)的通用性追求一脉相承。3
1.3 指令格式设计
LLaVA等经典工作设计了简洁而有效的指令格式:
# 用户输入格式
[USER] <image>
请描述这张图片的内容。
[ASSISTANT] 这是一张显示[具体描述]的图片。
# 多轮对话格式
[USER] <image>
图中的物体是什么?[ASSISTANT] 图中有一个[答案1]。[USER]
它是什么颜色的?[ASSISTANT] 它是[答案2]。[USER]
进一步描述它的特征。[ASSISTANT] [详细描述]关键设计要素:
<image>:特殊token标记图像插入位置[USER]/[ASSISTANT]:角色标记便于对话建模<stop>:结束标记控制生成长度- 模板化指令:支持数据高效生成
2. 指令数据构建
2.1 LLaVA数据生成方法
LLaVA的数据构建采用半自动化流水线,核心思想是利用GPT-4的语言能力辅助生成高质量指令数据。1
# LLaVA数据生成示意
class LLaVADataGenerator:
def __init__(self, gpt4_api, image_captioner):
self.gpt4 = gpt4_api # GPT-4V用于图像理解
self.captioner = image_captioner # COCO Caption等
def generate_instruction_data(self, image_path, num_samples=3):
"""
为单张图像生成多条指令数据
"""
# Step 1: 获取图像描述
captions = self.captioner.generate(image_path)
# Step 2: 使用GPT-4生成多样化指令
conversation = self.gpt4.create_conversation(
image_path=image_path,
context=captions,
num_turns=num_samples
)
return conversationLLaVA-150K数据集构建流程:
- 图像选择:从CC3M中选取59K张图像,要求具有丰富视觉内容
- 描述生成:使用GPT-4V为每张图生成两种描述
- 简短描述(ShareGPT格式)
- 详细描述(包含更多视觉细节)
- 指令生成:基于描述生成多样化的对话数据
2.2 GPT-4V辅助标注
GPT-4V在数据标注中扮演关键角色,其强大的视觉理解能力显著降低了标注成本:
# GPT-4V辅助标注示例
def gpt4v_annotation_pipeline(image, annotation_type="conversation"):
"""
使用GPT-4V进行半自动化标注
"""
if annotation_type == "conversation":
# 生成多轮对话
prompt = """
基于图像内容,生成一段多轮对话,包含:
1. 关于图像基本内容的问答
2. 需要推理的复杂问题
3. 开放性描述请求
"""
elif annotation_type == "detailed_caption":
# 生成详细描述
prompt = """
详细描述图像中的:物体、场景、颜色、布局、动作、情感氛围等。
"""
response = gpt4v.generate(prompt, image)
return parse_and_format(response)GPT-4V标注的优势:
- 高质量视觉理解能力
- 支持复杂推理问题生成
- 可生成开放域、长格式响应
- 降低人工标注成本
2.3 数据质量vs数量权衡
指令微调中,数据质量与数量的权衡是一个核心问题。研究表明这一权衡与模型规模密切相关:
| 模型规模 | 推荐策略 | 说明 |
|---|---|---|
| 小模型(<7B) | 质量优先 | 少量高质量数据,避免过拟合 |
| 中模型(7B-13B) | 平衡策略 | 10-100K级别,数据多样化 |
| 大模型(>70B) | 数量优先 | 可从大规模数据中学习 |
数据质量评估维度:
def evaluate_instruction_quality(instruction, response, image):
"""
评估指令-响应对的质量
"""
criteria = {
# 指令质量
"instruction_diversity": measure_diversity(instruction), # 指令多样性
"instruction_clarity": measure_clarity(instruction), # 指令清晰度
# 响应质量
"response_accuracy": verify_accuracy(response, image), # 响应准确性
"response_relevance": measure_relevance(response, instruction), # 相关性
"response_completeness": measure_completeness(response), # 完整性
# 匹配度
"alignment": measure_instruction_response_alignment(
instruction, response
)
}
return {k: v for k, v in criteria.items() if v > threshold}2.4 Self-Instruct方法
Self-Instruct是一种利用模型自身能力生成指令数据的技术,最初由Alpaca提出并被引入多模态领域。4
class SelfInstructMultimodal:
"""
多模态Self-Instruct方法
"""
def __init__(self, base_model, image_processor):
self.model = base_model
self.image_proc = image_processor
def generate_instructions(self, image, num_instructions=10):
"""
自举式指令生成
"""
# Step 1: 获取图像基本信息
caption = self.model.describe_image(image)
# Step 2: 生成种子指令
seed_instructions = [
"描述这张图片",
"这张图片中的主要物体是什么",
"图片中有什么有趣的地方",
# ...更多种子指令
]
# Step 3: 使用LLM生成新指令
generated = []
for seed in seed_instructions[:num_instructions]:
new_instructions = self.model.generate(
prompt=f"""
基于图像描述:{caption}
生成3个与以下指令类似但不完全相同的新指令:
{seed}
"""
)
generated.extend(parse_instructions(new_instructions))
# Step 4: 过滤低质量指令
filtered = self.filter_instructions(generated)
return filtered
def filter_instructions(self, instructions):
"""
过滤掉无效或低质量的指令
"""
valid = []
for inst in instructions:
# 过滤条件
if len(inst) < 5: # 太短
continue
if self.is_duplicate(inst, valid): # 重复
continue
if self.is_too_similar_to_seeds(inst): # 与种子太相似
continue
valid.append(inst)
return validSelf-Instruct的迭代优化:
- 初始化:使用人工编写的种子指令(通常5-10条)
- 生成:基于种子指令和图像内容生成新指令
- 过滤:移除无效、重复或质量较低的指令
- 扩展:使用高质量指令进一步扩展数据集
3. 训练策略
3.1 两阶段训练:预训练 + 指令微调
LLaVA等模型采用经典的两阶段训练范式,这是当前多模态指令微调的主流方法:
┌─────────────────────────────────────────────────────────────┐
│ 阶段1:预训练(特征对齐) │
├─────────────────────────────────────────────────────────────┤
│ 数据:CC3M / LAION-CC-SB (59K-600K图文对) │
│ 目标:学习视觉特征到语言空间的映射 │
│ 训练:仅更新投影层参数,冻结ViT和LLM │
│ │
│ CLIP ViT 投影层W LLaMA │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 冻结 │ ──→ │ 可训练 │ ──→ │ 冻结 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ Loss = Σ BCE(image_tokens, text_tokens) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 阶段2:指令微调 │
├─────────────────────────────────────────────────────────────┤
│ 数据:LLaVA-Instruct-150K(对话数据) │
│ 目标:让模型学会遵循视觉指令 │
│ 训练:投影层冻结,微调LLM和特殊token │
│ │
│ CLIP ViT 投影层W LLaMA │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 冻结 │ ──→ │ 冻结 │ ──→ │ 可训练 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ Loss = Σ CE(output_ids, target_ids) │
└─────────────────────────────────────────────────────────────┘
两阶段训练的数学形式化:
阶段1的预训练目标:
其中 是图像, 是文本描述, 是投影层参数。
阶段2的指令微调目标:
3.2 全参数微调 vs LoRA适配器
多模态指令微调面临与NLP类似的全参数微调(Full Fine-tuning)与参数高效微调(PEFT)的权衡:
| 维度 | 全参数微调 | LoRA/QLoRA |
|---|---|---|
| 显存需求 | 高(需存储所有梯度) | 低(仅训练适配器) |
| 训练速度 | 慢 | 快 |
| 参数量 | 所有参数 | 1-5%原始参数 |
| 泛化能力 | 可能过拟合 | 更好的泛化 |
| 硬件要求 | A100-80G | 单卡RTX 3090可行 |
全参数微调代码示例:
import torch
from transformers import AutoModelForVision2Seq, AutoProcessor
def full_finetune_training_loop(
model_name: str,
train_dataset,
num_epochs: int = 3,
learning_rate: float = 2e-5,
warmup_steps: int = 100
):
"""
全参数微调训练循环
"""
# 加载模型和处理器
model = AutoModelForVision2Seq.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto"
)
processor = AutoProcessor.from_pretrained(model_name)
# 配置优化器
optimizer = torch.optim.AdamW(
model.parameters(),
lr=learning_rate,
weight_decay=0.01
)
# 学习率调度器
def lr_lambda(step):
if step < warmup_steps:
return step / warmup_steps
return max(0.1, 0.5 * (1 + math.cos(math.pi * step / total_steps)))
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)
# 训练循环
for epoch in range(num_epochs):
model.train()
for batch_idx, batch in enumerate(train_dataset):
# 前向传播
outputs = model(
pixel_values=batch["pixel_values"],
input_ids=batch["input_ids"],
attention_mask=batch["attention_mask"],
labels=batch["labels"]
)
loss = outputs.loss
# 反向传播
loss.backward()
# 梯度裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
scheduler.step()
optimizer.zero_grad()
if batch_idx % 100 == 0:
print(f"Epoch {epoch}, Step {batch_idx}, Loss: {loss.item():.4f}")
return modelLoRA微调代码示例:
from peft import LoraConfig, get_peft_model, TaskType
def lora_finetune_training_loop(
model_name: str,
train_dataset,
num_epochs: int = 3,
lora_rank: int = 16,
lora_alpha: int = 32,
lora_dropout: float = 0.05,
learning_rate: float = 1e-4
):
"""
LoRA参数高效微调训练循环
"""
# 加载基础模型
model = AutoModelForVision2Seq.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto"
)
# 配置LoRA
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=lora_rank, # LoRA秩
lora_alpha=lora_alpha, # 缩放因子
lora_dropout=lora_dropout,
target_modules=[ # 目标模块
"q_proj", "v_proj",
"k_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
],
bias="none"
)
# 应用LoRA
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 输出:trainable params: 4.2M || all params: 7B || trainable%: 0.06%
# 后续训练循环与全参数微调类似
# ...3.3 视觉指令微调最佳实践
基于LLaVA、InstructBLIP等工作的实践经验,以下是视觉指令微调的关键最佳实践:
1. 数据配比策略
class InstructionDataMixer:
"""
指令微调数据混合策略
"""
def __init__(self):
self.data_sources = {
"conversation": 0.4, # 对话数据
"detailed_caption": 0.2, # 详细描述
"reasoning": 0.2, # 推理问题
"multi_turn": 0.2 # 多轮对话
}
def create_balanced_batch(self, datasets, batch_size=32):
"""
创建均衡的batch
"""
batch = []
for source, ratio in self.data_sources.items():
num_samples = int(batch_size * ratio)
samples = datasets[source].sample(num_samples)
batch.extend(samples)
# 打乱顺序
random.shuffle(batch)
return batch2. 课程学习(Curriculum Learning)
训练从简单到复杂的指令类型,有助于稳定收敛:
阶段1 → 简单描述:"这张图里有什么?"
阶段2 → 复杂推理:"为什么图中的人物会这样做?"
阶段3 → 开放式对话:"这幅画反映了什么社会现象?"
3. 训练稳定性技巧:
- 梯度累积:小batch下模拟大批次训练
- 混合精度:bfloat16/float16减少显存
- 早停机制:基于验证集loss监控过拟合
3.4 Scaling Instruction-Tuned MLLMs
arXiv:2309.09958系统研究了缩放视觉指令微调的效果,发现了若干重要规律。5
主要发现:
-
指令数据量与模型能力的关系
- 规模法则(Scaling Law)在多模态领域同样适用
- 指令数据规模超过一定阈值后,模型出现”涌现”能力
-
指令多样性比数量更重要
- 多样化的指令类型比单纯增加数据量更有效
- 覆盖更多任务类型有助于泛化
-
模型-数据匹配原则
- 小模型在大规模指令数据上可能过拟合
- 大模型能从更多样的指令中受益
# Scaling实验示意
def run_scaling_experiment(
model_sizes=[7, 13, 34, 70], # 模型参数量(B)
data_sizes=[10, 50, 150, 500], # 指令数据量(K)
eval_tasks=["vqa", "caption", "reasoning"]
):
"""
模拟scaling实验
"""
results = []
for model_size in model_sizes:
for data_size in data_sizes:
# 训练模型
model = train_model(size=model_size, data_size=data_size)
# 评估各任务
task_results = {}
for task in eval_tasks:
score = evaluate(model, task)
task_results[task] = score
results.append({
"model_size": model_size,
"data_size": data_size,
"avg_score": np.mean(list(task_results.values())),
"task_scores": task_results
})
# 分析scaling曲线
analyze_scaling_curves(results)
return results4. 高效微调方法
4.1 LLaVA-1.5设计选择
LLaVA-1.5在架构设计上做了若干关键改进,显著提升了模型性能:6
核心改进:
- MLP投影层:使用2层MLP替代线性投影
- 高质量数据:引入SFT数据质量过滤
- 更丰富的指令类型:VQA、详细描述、推理等多类型数据
class LLaVA15Projector(nn.Module):
"""
LLaVA-1.5 MLP投影层设计
"""
def __init__(self, vision_dim: int, llm_dim: int, hidden_dim: int = 4096):
super().__init__()
# 两层MLP:降维/升维 + GELU激活
self.projector = nn.Sequential(
nn.Linear(vision_dim, hidden_dim),
nn.GELU(),
nn.Linear(hidden_dim, llm_dim)
)
def forward(self, vision_features):
return self.projector(vision_features)
class LLaVA15Config:
"""
LLaVA-1.5关键配置
"""
def __init__(self):
# 视觉编码器
self.vision_model = "CLIP-ViT-L/14"
self.image_size = 336 # 高分辨率
# 投影层
self.use_mlp_projector = True
self.mlp_hidden_dim = 4096
# 训练配置
self.lr = 1e-3 # 预训练阶段高学习率
self.lr_ft = 2e-5 # 微调阶段低学习率
# 数据配置
self.instruction_types = [
"conversation", # 对话
"detailed_desc", # 详细描述
"complex_reasoning", # 复杂推理
"regional_caption" # 区域描述
]4.2 MiniGPT-4对齐策略
MiniGPT-4采用了与LLaVA不同的对齐策略,强调保持预训练知识的同时学习视觉理解:7
核心设计:
class MiniGPT4AlignmentStrategy:
"""
MiniGPT-4对齐策略
"""
def __init__(self, vision_encoder, llm, q_former=None):
self.vision_encoder = vision_encoder
self.llm = llm
self.q_former = q_former # 可选的Q-Former中间层
def stage1_pretraining(self, image_text_pairs):
"""
阶段1:特征对齐预训练
冻结:ViT、Q-Former、LLM
训练:线性投影层
"""
for param in self.vision_encoder.parameters():
param.requires_grad = False
for param in self.q_former.parameters():
param.requires_grad = False
for param in self.llm.parameters():
param.requires_grad = False
projection = self._init_projection()
# 对齐损失:视觉特征 → 语言空间
loss = self.compute_alignment_loss(projection)
return loss
def stage2_instruction_tuning(self, instruction_data):
"""
阶段2:指令微调
冻结:ViT、Q-Former
训练:投影层 + LLM(部分层)
"""
for param in self.vision_encoder.parameters():
param.requires_grad = False
# 可选:只微调LLM的后面几层
self._freeze_early_layers(self.llm, num_frozen=30)
# 指令微调损失
loss = self.compute_instruction_loss(instruction_data)
return loss4.3 QLoRA在多模态中的应用
QLoRA(Quantized Low-Rank Adaptation)将量化和LoRA结合,可在消费级GPU上微调大型多模态模型:8
from transformers import BitsAndBytesConfig
from peft import prepare_model_for_kbit_training, get_peft_model
def qlora_multimodal_finetune(
model_name: str,
train_data,
quantization_config=None
):
"""
QLoRA多模态微调
"""
# 量化配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True, # 4bit量化加载
bnb_4bit_quant_type="nf4", # NF4量化类型
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True # 双重量化
)
# 加载量化模型
model = AutoModelForVision2Seq.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto"
)
# 准备k-bit训练
model = prepare_model_for_kbit_training(model)
# LoRA配置
lora_config = LoraConfig(
r=64,
lora_alpha=16,
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
lora_dropout=0.05,
bias="none",
task_type=TaskType.CAUSAL_LM
)
# 应用LoRA
model = get_peft_model(model, lora_config)
# 训练(显存占用大幅降低)
trainer = CustomTrainer(model=model, data=train_data)
return trainer.train()QLoRA的优势:
| 指标 | 标准微调 | QLoRA微调 |
|---|---|---|
| 7B模型显存 | ~28GB | ~6GB |
| 13B模型显存 | ~56GB | ~12GB |
| 训练速度 | 基准 | 略慢(量化开销) |
| 模型质量 | 基准 | ~99%保持 |
4.4 视觉适配器设计
视觉适配器(Vision Adapter)是连接视觉编码器和语言模型的关键模块,设计选择直接影响模型性能:
主要类型:
# 1. 线性投影(Simple Linear)
class LinearProjector(nn.Module):
"""最简单的投影方式"""
def __init__(self, vision_dim, llm_dim):
super().__init__()
self.proj = nn.Linear(vision_dim, llm_dim)
def forward(self, x):
return self.proj(x)
# 2. MLP投影(LLaVA-1.5风格)
class MLPProjector(nn.Module):
"""两层MLP,增强表达能力"""
def __init__(self, vision_dim, llm_dim, hidden_dim=4096):
super().__init__()
self.proj = nn.Sequential(
nn.Linear(vision_dim, hidden_dim),
nn.GELU(),
nn.Linear(hidden_dim, llm_dim)
)
def forward(self, x):
return self.proj(x)
# 3. Q-Former投影(BLIP-2风格)
class QFormerProjector(nn.Module):
"""使用交叉注意力机制"""
def __init__(self, vision_dim, llm_dim, num_queries=32):
super().__init__()
self.q_former = QueryTransformer(vision_dim, llm_dim, num_queries)
self.num_queries = num_queries
def forward(self, vision_features):
# vision_features: [B, N, D_vision]
# 输出: [B, num_queries, D_llm]
return self.q_former(vision_features)
# 4. Cross-Attention适配器
class CrossAttentionAdapter(nn.Module):
"""交叉注意力适配器"""
def __init__(self, vision_dim, llm_dim, num_heads=8):
super().__init__()
self.cross_attn = nn.MultiheadAttention(
embed_dim=llm_dim,
num_heads=num_heads,
kdim=vision_dim,
vdim=vision_dim
)
self.norm = nn.LayerNorm(llm_dim)
self.ffn = nn.Sequential(
nn.Linear(llm_dim, llm_dim * 4),
nn.GELU(),
nn.Linear(llm_dim * 4, llm_dim)
)
def forward(self, text_features, vision_features):
# text_features: [B, T, D_llm]
# vision_features: [B, N, D_vision]
attn_out, _ = self.cross_attn(
query=text_features,
key=vision_features,
value=vision_features
)
return self.ffn(self.norm(attn_out))5. 多模态RLHF与对齐
5.1 视觉-语言偏好对齐
多模态RLHF(Reinforcement Learning from Human Feedback)将人类反馈机制引入视觉-语言对齐,是提升模型安全性和有用性的关键步骤。9
核心流程:
┌─────────────────────────────────────────────────────────────────┐
│ 多模态RLHF流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 阶段1: 收集人类偏好数据 │
│ ┌─────────┐ ┌─────────┐ │
│ │ <image> │ │ <image> │ │
│ └────┬────┘ └────┬────┘ │
│ ↓ ↓ │
│ ┌─────────────────────────────┐ │
│ │ Response A vs Response B │ → 人类选择偏好 │
│ │ 哪个回答更好?(A/B) │ │
│ └─────────────────────────────┘ │
│ │
│ 阶段2: 训练多模态奖励模型 │
│ ┌──────────────────────────────────────┐ │
│ │ Reward Model: (image, question, ans) → r ∈ ℝ │
│ │ 预测人类偏好分数 │ │
│ └──────────────────────────────────────┘ │
│ │
│ 阶段3: 使用RL优化 │
│ ┌──────────────────────────────────────┐ │
│ │ PPO/GRPO优化策略 │ │
│ │ 最大化reward同时约束KL散度 │ │
│ └──────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
5.2 多模态奖励模型
多模态奖励模型(Reward Model)的设计需要同时理解图像、问题和回答:
class MultimodalRewardModel(nn.Module):
"""
多模态奖励模型
"""
def __init__(self, vision_encoder, llm, reward_head_dim=512):
super().__init__()
self.vision_encoder = vision_encoder
self.llm = llm
# 冻结视觉编码器
for param in self.vision_encoder.parameters():
param.requires_grad = False
# 投影层
self.projector = nn.Linear(
vision_encoder.embed_dim,
llm.hidden_size
)
# 奖励头
self.reward_head = nn.Sequential(
nn.Linear(llm.hidden_size, reward_head_dim),
nn.GELU(),
nn.Linear(reward_head_dim, 1)
)
def forward(self, images, input_ids, attention_mask):
"""
计算偏好分数
"""
# 视觉编码
vision_features = self.vision_encoder(images)
vision_tokens = self.projector(vision_features)
# 文本编码
text_tokens = self.llm.get_input_embeddings()(input_ids)
# 合并并通过LLM
inputs_embeds = self._merge_inputs(vision_tokens, text_tokens, input_ids)
outputs = self.llm(
inputs_embeds=inputs_embeds,
attention_mask=attention_mask,
output_hidden_states=True
)
# 使用最后一层最后一个token的表示作为奖励
last_hidden = outputs.hidden_states[-1]
reward = self.reward_head(last_hidden[:, -1, :])
return reward.squeeze(-1)
def compute_preference_loss(
self,
chosen_rewards,
rejected_rewards,
margin=0.5
):
"""
Bradley-Terry偏好损失
Loss = -log(σ(r_chosen - r_rejected - margin))
"""
diff = chosen_rewards - rejected_rewards
loss = -F.logsigmoid(diff - margin).mean()
return loss5.3 多模态RLHF流程
class MultimodalPPOTrainer:
"""
多模态PPO训练
"""
def __init__(
self,
policy_model, # 待优化的策略模型
ref_model, # 参考模型(约束KL)
reward_model, # 奖励模型
vision_encoder,
tokenizer
):
self.policy = policy_model
self.ref = ref_model
self.reward = reward_model
self.vision_encoder = vision_encoder
self.tokenizer = tokenizer
# PPO超参数
self.clip_eps = 0.2
self.kl_coef = 0.04
def step(self, batch):
"""
单步PPO更新
"""
images, prompts, responses = batch
# 1. 在当前策略下采样响应
with torch.no_grad():
log_probs_old = self.get_log_probs(images, prompts, responses)
values_old = self.get_values(images, prompts, responses)
# 2. 计算奖励
rewards = self.compute_rewards(images, prompts, responses)
# 3. 计算GAE(广义优势估计)
advantages, returns = self.compute_gae(rewards, values_old)
# 4. PPO策略更新
for _ in range(self.ppo_epochs):
# 当前策略的log_probs
log_probs = self.get_log_probs(images, prompts, responses)
# 计算比率
ratio = torch.exp(log_probs - log_probs_old)
# Clipped surrogate loss
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1-self.clip_eps, 1+self.clip_eps) * advantages
policy_loss = -torch.min(surr1, surr2).mean()
# KL散度损失
kl_loss = self.compute_kl_divergence(images, prompts)
# 总损失
total_loss = policy_loss + self.kl_coef * kl_loss
# 反向传播
self.optimizer.zero_grad()
total_loss.backward()
self.optimizer.step()
return {
"policy_loss": policy_loss.item(),
"kl_loss": kl_loss.item(),
"reward_mean": rewards.mean().item()
}
def compute_rewards(self, images, prompts, responses):
"""
计算每个token的reward
"""
rewards = []
for i in range(len(images)):
# 拼接prompt和response
full_text = prompts[i] + responses[i]
# 使用奖励模型评分
reward_score = self.reward(
images[i].unsqueeze(0),
self.tokenizer(full_text, return_tensors="pt")["input_ids"]
)
# 在响应末尾添加reward
reward = torch.zeros(len(responses[i]))
reward[-1] = reward_score
rewards.append(reward)
return rewards6. 最新进展
6.1 视觉提示调优
视觉提示调优(Visual Prompt Tuning)将NLP领域的prompt tuning扩展到多模态领域:
class VisualPromptTuning(nn.Module):
"""
视觉提示调优:在视觉编码器输入端添加可学习提示
"""
def __init__(self, vision_encoder, num_prompts=8, prompt_dim=None):
super().__init__()
self.vision_encoder = vision_encoder
vision_dim = vision_encoder.embed_dim
self.prompt_dim = prompt_dim or vision_dim
# 可学习的视觉提示
self.visual_prompts = nn.Parameter(
torch.randn(1, num_prompts, self.prompt_dim) * 0.02
)
def forward(self, images):
# 获取原始视觉特征
features = self.vision_encoder(images) # [B, N, D]
# 在特征前添加可学习提示
prompts = self.visual_prompts.expand(images.size(0), -1, -1)
enhanced_features = torch.cat([prompts, features], dim=1)
return enhanced_features6.2 多模态Chain-of-Thought
多模态思维链(Multimodal CoT)将CoT推理能力引入视觉-语言模型:
class MultimodalChainOfThought:
"""
多模态思维链推理
"""
def __init__(self, model):
self.model = model
def think(self, image, question, max_steps=3):
"""
逐步推理
"""
context = {
"image_description": None,
"relevant_objects": None,
"reasoning_steps": []
}
# 步骤1:理解图像内容
context["image_description"] = self.describe(image)
# 步骤2:识别相关物体
context["relevant_objects"] = self.identify_objects(
image,
context["image_description"]
)
# 步骤3:执行推理
for step in range(max_steps):
reasoning = self.model.generate(
prompt=self._build_step_prompt(
question,
context,
step
),
image=image
)
context["reasoning_steps"].append(reasoning)
if self.is_final_answer(reasoning):
break
# 最终答案
final_answer = self.model.generate(
prompt=self._build_answer_prompt(question, context),
image=image
)
return {
"description": context["image_description"],
"reasoning": context["reasoning_steps"],
"answer": final_answer
}6.3 指令跟随能力提升
提升模型的指令跟随能力是当前研究的热点方向:
主要方法:
- 数据层面:构建更高质量的指令微调数据
- 训练层面:改进训练策略和损失函数
- 架构层面:设计更强大的视觉-语言融合模块
class InstructionFollowingEvaluator:
"""
指令跟随能力评估
"""
def __init__(self, model, tokenizer):
self.model = model
self.tokenizer = tokenizer
# 评估维度
self.metrics = {
"format_adherence": self.check_format,
"content_relevance": self.check_relevance,
"instruction_clarity": self.check_clarity,
"task_completion": self.check_completion
}
def evaluate_instruction_following(
self,
image,
instruction,
response
):
"""
多维度评估指令跟随能力
"""
scores = {}
for metric_name, metric_fn in self.metrics.items():
scores[metric_name] = metric_fn(
instruction,
response
)
# 综合评分
scores["overall"] = np.mean(list(scores.values()))
return scores
def check_format(self, instruction, response):
"""
检查格式遵循度
"""
# 检查是否遵循指令要求的格式
# 如:是否使用markdown、是否分点回答等
format_requirements = self.parse_format_requirements(instruction)
return self.compute_format_score(response, format_requirements)
def check_relevance(self, instruction, response):
"""
检查内容相关性
"""
instruction_entities = self.extract_entities(instruction)
response_entities = self.extract_entities(response)
overlap = len(instruction_entities & response_entities)
return overlap / max(len(instruction_entities), 1)相关内容
| 主题 | 链接 |
|---|---|
| LLaVA模型 | LLaVA:大型多模态模型 |
| 多模态学习基础 | 多模态学习 |
| 多模态融合方法 | 多模态对齐与融合 |
| VLM架构分类 | 视觉语言模型架构分类 |
| CLIP对比学习 | CLIP与对比语言-图像预训练 |
| LoRA微调 | LoRA参数高效微调 |
| PEFT方法 | PEFT参数高效微调概述 |
| Adapter方法 | Adapter微调方法 |
参考文献
Footnotes
-
Liu H, Li C, Wu Q, et al. Visual Instruction Tuning [A]. NeurIPS, 2023. (LLaVA原始论文) ↩ ↩2
-
Liu H, Li C, Wu Q, et al. Improved Baselines with Visual Instruction Tuning [A]. arXiv, 2024. (LLaVA-1.5) ↩
-
Zhou S, Wang L, Yao J, et al. Linear Projector: Emerging Architecture in Multimodal Learning [A]. ICLR Workshop, 2024. ↩
-
Wang Y, Kordi Y, Mishra S, et al. Self-Instruct: Aligning Language Model with Self Generated Instructions [A]. AAAI, 2023. ↩
-
Sun Q, Cui Y, Xia Z, et al. Multimodal Instruction Tuning [A]. arXiv:2309.09958, 2023. ↩
-
Liu H, Li C, Zhang Y, et al. LLaVA-1.5: Improved Results with Data Efficiency [A]. arXiv:2310.03744, 2023. ↩
-
Zhu D, Chen J, Shen X, et al. MiniGPT-4: Enhancing Vision Language Understanding with One Single Projection Layer [A]. arXiv:2304.10592, 2023. ↩
-
Dettmers T, Pagnoni A, Holtzman A, et al. QLoRA: Efficient Finetuning of Quantized LLMs [A]. NeurIPS, 2023. ↩
-
Sun Z, Shen S, Cao S, et al. Aligning Large Multimodal Models with Factually Augmented RLHF [A]. arXiv:2309.14304, 2023. ↩