基于HuggingFace PEFT的Prefix Tuning技术详解
前言
在自然语言处理领域,微调大型语言模型(LLM)已成为标准做法。然而,随着模型规模的不断扩大,全参数微调变得越来越不切实际。本文将深入探讨一种名为Prefix Tuning的高效微调技术,它通过仅优化少量参数就能实现出色的性能。
什么是Prefix Tuning?
Prefix Tuning是一种参数高效的微调方法,其核心思想是在输入序列前添加一组连续的任务特定向量(称为前缀)。这些前缀参数会被优化并添加到模型每一层的隐藏状态中。输入序列的token可以将这些前缀视为"虚拟token"进行关注。
技术优势
- 参数效率高:仅需存储全参数微调模型千分之一的参数量
- 多任务兼容:同一个大模型可适配多种不同任务
- 性能接近全微调:在多项任务上表现与全参数微调相当
环境准备
在开始前,请确保安装以下Python库:
pip install peft transformers datasets
项目配置
首先定义模型、分词器及相关超参数:
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
from peft import PrefixTuningConfig, get_peft_model
import os
os.environ["TOKENIZERS_PARALLELISM"] = "false"
# 模型配置
model_name = "t5-large"
tokenizer_name = "t5-large"
# 数据配置
text_column = "sentence"
label_column = "text_label"
max_length = 128
# 训练配置
lr = 1e-2
num_epochs = 5
batch_size = 8
数据处理
数据集加载
使用金融情感分析数据集financial_phrasebank的sentences_allagree子集:
from datasets import load_dataset
dataset = load_dataset("financial_phrasebank", "sentences_allagree")
dataset = dataset["train"].train_test_split(test_size=0.1)
dataset["validation"] = dataset["test"]
del dataset["test"]
# 将标签ID转换为可读文本
classes = dataset["train"].features["label"].names
dataset = dataset.map(
lambda x: {"text_label": [classes[label] for label in x["label"]]},
batched=True,
num_proc=1,
)
数据预处理
创建分词和填充函数:
tokenizer = AutoTokenizer.from_pretrained(tokenizer_name)
def preprocess_function(examples):
inputs = examples[text_column]
targets = examples[label_column]
model_inputs = tokenizer(inputs, max_length=max_length, padding="max_length",
truncation=True, return_tensors="pt")
labels = tokenizer(targets, max_length=2, padding="max_length",
truncation=True, return_tensors="pt")
labels = labels["input_ids"]
labels[labels == tokenizer.pad_token_id] = -100
model_inputs["labels"] = labels
return model_inputs
应用预处理并创建数据加载器:
processed_datasets = dataset.map(
preprocess_function,
batched=True,
num_proc=1,
remove_columns=dataset["train"].column_names,
load_from_cache_file=False,
)
train_dataloader = DataLoader(
processed_datasets["train"],
shuffle=True,
collate_fn=default_data_collator,
batch_size=batch_size
)
eval_dataloader = DataLoader(
processed_datasets["validation"],
collate_fn=default_data_collator,
batch_size=batch_size
)
模型准备
初始化PEFT模型
peft_config = PrefixTuningConfig(
task_type=TaskType.SEQ_2_SEQ_LM,
inference_mode=False,
num_virtual_tokens=20 # 前缀token数量
)
model = AutoModelForSeq2SeqLM.from_pretrained(model_name)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
典型输出显示可训练参数仅占全部参数的0.13%:
trainable params: 983040 || all params: 738651136 || trainable%: 0.133
优化器设置
optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
lr_scheduler = get_linear_schedule_with_warmup(
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=len(train_dataloader) * num_epochs,
)
模型训练
完整的训练循环实现:
model = model.to(device)
for epoch in range(num_epochs):
model.train()
total_loss = 0
for batch in train_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss
total_loss += loss.detach().float()
loss.backward()
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
# 验证阶段
model.eval()
eval_loss = 0
eval_preds = []
for batch in eval_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
with torch.no_grad():
outputs = model(**batch)
loss = outputs.loss
eval_loss += loss.detach().float()
eval_preds.extend(
tokenizer.batch_decode(
torch.argmax(outputs.logits, -1).detach().cpu().numpy(),
skip_special_tokens=True
)
)
# 计算评估指标
eval_epoch_loss = eval_loss / len(eval_dataloader)
eval_ppl = torch.exp(eval_epoch_loss)
train_epoch_loss = total_loss / len(train_dataloader)
train_ppl = torch.exp(train_epoch_loss)
print(f"Epoch {epoch}: Train PPL={train_ppl:.3f}, Eval PPL={eval_ppl:.3f}")
模型评估
计算验证集准确率:
correct = 0
total = 0
for pred, true in zip(eval_preds, dataset["validation"]["text_label"]):
if pred.strip() == true.strip():
correct += 1
total += 1
accuracy = correct / total * 100
print(f"Accuracy: {accuracy:.2f}%")
典型结果可达97%以上的准确率,证明Prefix Tuning的有效性。
技术解析
Prefix Tuning工作原理
- 前缀向量:在输入前添加可训练的任务特定向量
- 注意力机制:模型所有层都能关注这些前缀
- 参数隔离:仅前缀参数参与训练,原始模型参数冻结
为什么有效?
- 前缀提供了任务特定的上下文引导
- 通过注意力机制影响整个模型的表示
- 避免了全参数微调的计算开销
实际应用建议
- 前缀长度:通常10-20个虚拟token足够
- 学习率:比全微调大1-2个数量级
- 适用场景:文本生成、序列到序列任务效果尤佳
总结
Prefix Tuning提供了一种高效利用大型语言模型的方法,在保持模型性能的同时大幅减少训练开销。本文通过金融情感分析任务展示了其实际应用,该技术可轻松扩展到其他NLP任务中。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考