参数高效微调:LoRA、Prefix Tuning 与 Prompt Tuning
欢迎来到今天的讲座!🚀
大家好,今天我们要聊的是一个非常热门的话题——参数高效微调(Parameter-Efficient Fine-Tuning)。在这个领域里,有三个非常有趣的技术:LoRA、Prefix Tuning 和 Prompt Tuning。它们的目标都是在不破坏预训练模型的强大能力的前提下,用尽可能少的参数来实现特定任务的优化。听起来很酷对吧?那我们就开始吧!
1. 为什么需要参数高效微调?🤔
想象一下,你有一个巨大的语言模型,比如 GPT-3 或者 BERT,它们拥有数亿甚至数十亿的参数。这些模型在各种自然语言处理任务上表现得非常出色,但问题是,当你想为某个特定的任务进行微调时,直接调整所有参数不仅计算成本高昂,而且可能会导致“灾难性遗忘”——即模型忘记了之前学到的知识。
因此,我们需要一种方法,能够在保持模型原有性能的同时,只调整一小部分参数,甚至不调整任何参数!这就是参数高效微调的意义所在。它不仅能节省计算资源,还能让模型更快地适应新任务。
2. LoRA:低秩自适应 🧮
什么是 LoRA?
LoRA(Low-Rank Adaptation)是由 Microsoft Research 提出的一种参数高效微调方法。它的核心思想是:不是直接调整模型的所有参数,而是通过引入低秩矩阵来调整模型的某些层。具体来说,LoRA 在每个线性层中插入了一个低秩矩阵 ( A ) 和 ( B ),并通过 ( A times B ) 来近似原始的权重矩阵 ( W ) 的变化。
用公式表示就是:
[
W’ = W + (A times B)
]
其中,( W ) 是原始的权重矩阵,( A ) 和 ( B ) 是两个低秩矩阵,它们的维度远小于 ( W )。通过这种方式,LoRA 只需要训练少量的参数(即 ( A ) 和 ( B )),而不需要调整整个模型的权重。
LoRA 的优点
- 参数量极少:相比于传统的全参数微调,LoRA 只需要训练少量的参数,通常只有几千个。
- 避免灾难性遗忘:由于只修改了模型的部分权重,LoRA 不会破坏模型原有的知识。
- 快速收敛:因为只需要训练少量参数,LoRA 的训练速度通常比全参数微调要快得多。
代码示例
下面是一个简单的 LoRA 实现示例,使用 PyTorch:
import torch
from transformers import AutoModelForSequenceClassification, Trainer, TrainingArguments
# 加载预训练模型
model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased")
# 应用 LoRA
def apply_lora(model, rank=4):
for name, param in model.named_parameters():
if 'weight' in name and param.dim() == 2:
# 创建低秩矩阵 A 和 B
A = torch.nn.Parameter(torch.randn(param.shape[0], rank))
B = torch.nn.Parameter(torch.randn(rank, param.shape[1]))
# 替换原始权重
param.data = param.data + torch.matmul(A, B)
# 冻结原始权重
param.requires_grad = False
# 解冻 A 和 B
A.requires_grad = True
B.requires_grad = True
apply_lora(model)
# 定义训练参数
training_args = TrainingArguments(
output_dir="./results",
num_train_epochs=3,
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
warmup_steps=500,
weight_decay=0.01,
logging_dir="./logs",
)
# 使用 Trainer 进行训练
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
)
trainer.train()
3. Prefix Tuning:前缀调整 📝
什么是 Prefix Tuning?
Prefix Tuning 是由 Google Brain 提出的一种参数高效微调方法。它的核心思想是:在模型的输入序列前面添加一个可学习的“前缀”(prefix),而不是直接调整模型的参数。这个前缀可以看作是一个额外的上下文,帮助模型更好地理解输入。
具体来说,Prefix Tuning 在每个 Transformer 层的输入中插入一个可学习的向量 ( p ),这个向量会在训练过程中不断优化。通过这种方式,模型可以在不改变原有参数的情况下,学会如何处理新的任务。
用公式表示就是:
[
X’ = [p; X]
]
其中,( X ) 是原始的输入序列,( p ) 是可学习的前缀。
Prefix Tuning 的优点
- 参数量极少:Prefix Tuning 只需要训练前缀向量,而不需要调整模型的任何参数,因此参数量非常少。
- 灵活性高:前缀可以针对不同的任务进行定制,使得模型能够更好地适应多种任务。
- 避免灾难性遗忘:由于没有修改模型的参数,Prefix Tuning 不会破坏模型原有的知识。
代码示例
下面是一个简单的 Prefix Tuning 实现示例,使用 Hugging Face 的 transformers
库:
import torch
from transformers import AutoModelForSequenceClassification, Trainer, TrainingArguments
# 加载预训练模型
model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased")
# 定义前缀长度和维度
prefix_length = 10
hidden_dim = 768
# 添加前缀
class PrefixModel(torch.nn.Module):
def __init__(self, model, prefix_length, hidden_dim):
super().__init__()
self.model = model
self.prefix_embeddings = torch.nn.Parameter(torch.randn(prefix_length, hidden_dim))
def forward(self, input_ids, attention_mask, labels=None):
# 将前缀嵌入与输入拼接
batch_size = input_ids.size(0)
prefix_embeddings = self.prefix_embeddings.unsqueeze(0).expand(batch_size, -1, -1)
inputs_embeds = self.model.bert.embeddings(input_ids)
inputs_embeds = torch.cat([prefix_embeddings, inputs_embeds], dim=1)
# 更新注意力掩码
prefix_attention_mask = torch.ones(batch_size, prefix_length).to(input_ids.device)
attention_mask = torch.cat([prefix_attention_mask, attention_mask], dim=1)
# 传递给模型
outputs = self.model(inputs_embeds=inputs_embeds, attention_mask=attention_mask, labels=labels)
return outputs
model = PrefixModel(model, prefix_length, hidden_dim)
# 定义训练参数
training_args = TrainingArguments(
output_dir="./results",
num_train_epochs=3,
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
warmup_steps=500,
weight_decay=0.01,
logging_dir="./logs",
)
# 使用 Trainer 进行训练
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
)
trainer.train()
4. Prompt Tuning:提示调整 🗣️
什么是 Prompt Tuning?
Prompt Tuning 是由 Stanford NLP 提出的一种参数高效微调方法。它的核心思想是:通过设计特定的提示(prompt),引导模型生成符合任务要求的输出,而不是直接调整模型的参数。提示可以是一些自然语言句子,也可以是特殊的标记或符号。
具体来说,Prompt Tuning 在输入序列中插入一些可学习的提示词,这些提示词会在训练过程中不断优化。通过这种方式,模型可以在不改变原有参数的情况下,学会如何处理新的任务。
用公式表示就是:
[
X’ = [P; X]
]
其中,( X ) 是原始的输入序列,( P ) 是可学习的提示词。
Prompt Tuning 的优点
- 参数量极少:Prompt Tuning 只需要训练提示词,而不需要调整模型的任何参数,因此参数量非常少。
- 灵活性高:提示词可以针对不同的任务进行定制,使得模型能够更好地适应多种任务。
- 避免灾难性遗忘:由于没有修改模型的参数,Prompt Tuning 不会破坏模型原有的知识。
代码示例
下面是一个简单的 Prompt Tuning 实现示例,使用 Hugging Face 的 transformers
库:
import torch
from transformers import AutoModelForCausalLM, Trainer, TrainingArguments
# 加载预训练模型
model = AutoModelForCausalLM.from_pretrained("gpt2")
# 定义提示长度和词汇表大小
prompt_length = 10
vocab_size = model.config.vocab_size
# 添加提示
class PromptModel(torch.nn.Module):
def __init__(self, model, prompt_length, vocab_size):
super().__init__()
self.model = model
self.prompt_embeddings = torch.nn.Parameter(torch.randn(prompt_length, vocab_size))
def forward(self, input_ids, attention_mask, labels=None):
# 将提示嵌入与输入拼接
batch_size = input_ids.size(0)
prompt_embeddings = self.prompt_embeddings.unsqueeze(0).expand(batch_size, -1, -1)
inputs_embeds = self.model.transformer.wte(input_ids)
inputs_embeds = torch.cat([prompt_embeddings, inputs_embeds], dim=1)
# 更新注意力掩码
prompt_attention_mask = torch.ones(batch_size, prompt_length).to(input_ids.device)
attention_mask = torch.cat([prompt_attention_mask, attention_mask], dim=1)
# 传递给模型
outputs = self.model(inputs_embeds=inputs_embeds, attention_mask=attention_mask, labels=labels)
return outputs
model = PromptModel(model, prompt_length, vocab_size)
# 定义训练参数
training_args = TrainingArguments(
output_dir="./results",
num_train_epochs=3,
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
warmup_steps=500,
weight_decay=0.01,
logging_dir="./logs",
)
# 使用 Trainer 进行训练
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
)
trainer.train()
5. 总结与对比 📊
方法 | 参数量 | 训练速度 | 灵活性 | 适用场景 |
---|---|---|---|---|
LoRA | 较少 | 快 | 中等 | 各种任务 |
Prefix Tuning | 极少 | 快 | 高 | 生成任务 |
Prompt Tuning | 极少 | 快 | 高 | 分类任务 |
从表格中可以看出,三种方法各有优劣。如果你希望在保持模型性能的同时,尽量减少参数量和训练时间,那么 LoRA 是一个不错的选择。如果你更关注任务的灵活性,尤其是生成类任务,那么 Prefix Tuning 和 Prompt Tuning 可能更适合你。
6. 结语 🎉
今天我们一起探讨了三种参数高效微调的方法:LoRA、Prefix Tuning 和 Prompt Tuning。每种方法都有其独特的应用场景和优势。希望今天的讲座能帮助你更好地理解和应用这些技术,让你在未来的项目中更加游刃有余!
如果你有任何问题或想法,欢迎在评论区留言讨论!😊
谢谢大家,下次见!👋