基于HuggingFace PEFT的Prefix Tuning技术详解

基于HuggingFace PEFT的Prefix Tuning技术详解

notebooks Notebooks using the Hugging Face libraries 🤗 notebooks 项目地址: https://gitcode.com/gh_mirrors/note/notebooks

前言

在自然语言处理领域,微调大型语言模型(LLM)已成为标准做法。然而,随着模型规模的不断扩大,全参数微调变得越来越不切实际。本文将深入探讨一种名为Prefix Tuning的高效微调技术,它通过仅优化少量参数就能实现出色的性能。

什么是Prefix Tuning?

Prefix Tuning是一种参数高效的微调方法,其核心思想是在输入序列前添加一组连续的任务特定向量(称为前缀)。这些前缀参数会被优化并添加到模型每一层的隐藏状态中。输入序列的token可以将这些前缀视为"虚拟token"进行关注。

技术优势

  1. 参数效率高:仅需存储全参数微调模型千分之一的参数量
  2. 多任务兼容:同一个大模型可适配多种不同任务
  3. 性能接近全微调:在多项任务上表现与全参数微调相当

环境准备

在开始前,请确保安装以下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工作原理

  1. 前缀向量:在输入前添加可训练的任务特定向量
  2. 注意力机制:模型所有层都能关注这些前缀
  3. 参数隔离:仅前缀参数参与训练,原始模型参数冻结

为什么有效?

  • 前缀提供了任务特定的上下文引导
  • 通过注意力机制影响整个模型的表示
  • 避免了全参数微调的计算开销

实际应用建议

  1. 前缀长度:通常10-20个虚拟token足够
  2. 学习率:比全微调大1-2个数量级
  3. 适用场景:文本生成、序列到序列任务效果尤佳

总结

Prefix Tuning提供了一种高效利用大型语言模型的方法,在保持模型性能的同时大幅减少训练开销。本文通过金融情感分析任务展示了其实际应用,该技术可轻松扩展到其他NLP任务中。

notebooks Notebooks using the Hugging Face libraries 🤗 notebooks 项目地址: https://gitcode.com/gh_mirrors/note/notebooks

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵇子高Quintessa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值