核心概念:
前缀序列:一个可训练的序列,插入到输入序列的前面,用于引导模型生成特定任务的输出。
参数冻结:在微调过程中,预训练模型的参数保持不变,只训练前缀序列的参数。
任务特定:前缀序列可以根据不同的任务进行训练,使得模型能够适应多种任务。
工作原理:
1.前缀序列的插入:
在输入序列之前插入一个可训练的前缀序列。这个前缀序列可以看作是任务特定的提示,用于引导模型生成特定任务的输出。前缀序列的长度通常较短,例如几十个token。
2.模型结构:
假设我们有一个预训练的语言模型 M,输入序列为 x,前缀序列为 p。
在微调过程中,模型的输入变为 [p,x],即前缀序列和输入序列的拼接。
模型的输出仍然是基于整个输入序列 [p,x] 生成的。
3.训练过程:
只有前缀序列的参数在微调过程中被更新,预训练模型的参数保持不变。
通过反向传播,优化前缀序列的参数,使其能够引导模型生成特定任务的输出。
实现细节:
-
定义前缀序列:
- 前缀序列通常是一个随机初始化的张量,形状为 [1,prefixlength,hiddensize],其中
prefix_length
是前缀序列的长度,hidden_size
是模型的隐藏层大小。 - 前缀序列可以插入到模型的每个Transformer层之前,或者只插入到第一个Transformer层之前。
- 前缀序列通常是一个随机初始化的张量,形状为 [1,prefixlength,hiddensize],其中
2.插入前缀序列:
import torch
import torch.nn as nn
from transformers import GPT2LMHeadModel, GPT2Tokenizer
# 加载预训练的GPT-2模型和分词器
model = GPT2LMHeadModel.from_pretrained("gpt2")
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
# 定义前缀序列的长度和隐藏层大小
prefix_length = 10
hidden_size = model.config.hidden_size
# 初始化前缀序列
prefix = nn.Parameter(torch.randn(1, prefix_length, hidden_size))
def add_prefix(input_ids):
prefix_ids = prefix.repeat(input_ids.size(0), 1, 1)
input_embeddings = model.transformer.wte(input_ids)
prefixed_input = torch.cat([prefix_ids, input_embeddings], dim=1)
return prefixed_input
# 输入示例
input_text = "Translate English to French: Hello"
input_ids = tokenizer(input_text, return_tensors="pt").input_ids
# 插入前缀序列
prefixed_input = add_prefix(input_ids)
# 通过模型
outputs = model(inputs_embeds=prefixed_input)
logits = outputs.logits
3.训练:
from transformers import Trainer, TrainingArguments
# 定义训练参数
training_args = TrainingArguments(
output_dir="./results",
num_train_epochs=3,
per_device_train_batch_size=8,
per_device_eval_batch_size=16,
warmup_steps=500,
weight_decay=0.01,
logging_dir="./logs",
)
# 定义训练数据集
train_dataset = ... # 你的训练数据集
eval_dataset = ... # 你的验证数据集
# 定义训练器
class PrefixTuningTrainer(Trainer):
def compute_loss(self, model, inputs, return_outputs=False):
prefixed_input = add_prefix(inputs["input_ids"])
outputs = model(inputs_embeds=prefixed_input, labels=inputs["labels"])
loss = outputs.loss
return (loss, outputs) if return_outputs else loss
# 创建训练器
trainer = PrefixTuningTrainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
)
# 开始训练
trainer.train()
优势
参数效率:相比于全模型微调,Prefix Tuning 只需要训练前缀序列的参数,大大减少了计算资源的消耗。
任务适应性:前缀序列可以根据不同的任务进行训练,使得模型能够适应多种任务。
灵活性:可以轻松地将新的任务添加到现有的前缀集合中,而不需要重新训练整个模型。