LoRA(Low-Rank Adaptation)是一种用于大型语言模型微调的高效方法,它允许在保持原始模型参数不变的同时,仅通过训练一小部分额外参数来适应下游任务。这种方法特别适合于资源有限或者数据较少的情况下对预训练大模型进行快速且有效的调整。
在LoRA中,主要的思想是在权重矩阵上并行添加低秩矩阵,并只训练这些新增加的低秩参数。具体操作如下:
1、增加低秩矩阵:对于每一个要微调的层,LoRA不改变原有模型参数矩阵,而是引入两个低秩矩阵A和B,维度分别为dxr和rxd,其中r远小于d(d是原权重矩阵的列数或行数,通常是模型的隐藏维度大小)。这样,新增的参数量大大减少,从而降低了计算和存储成本。
2、旁路结构:在实际应用中,LoRA通常会在原始网络权重矩阵W旁边创建一个“旁路”,这个旁路通过将A和B相乘得到一个新的矩阵,该矩阵与W相加或相乘以更新网络的前向传播过程。
3、冻结原始参数:在微调过程中,原有的模型参数W会被冻结,即在训练时不会更新,只针对新增的低秩参数A和B进行优化。
4、微调流程:使用特定下游任务的数据集,只训练A和B矩阵,使得模型能够根据新的任务数据学习到必要的适应性。
5、合并参数:训练完成后,新学到的低秩矩阵参数与原始模型参数结合,共同作用于模型预测,在保证模型对新任务有良好表现的同时,避免了在推理阶段增加额外的计算负担。
总之,LoRA提供了一种在不显著增加训练复杂性和资源消耗的前提下,对大规模语言模型进行有效微调的技术方案,特别适用于那些需要在资源受限环境或少量样本数据上做迁移学习的场景。
下面简单介绍通过peft实现Lora对模型进行微调的大致流程。
# 基于peft使用Lora对生成式对话模型进行微调
from datasets import Dataset
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer
# 加载数据集
ds = Dataset.load_from_disk("./alpaca_data_zh")
print(ds[:3])
# 数据集处理
tokenizer = AutoTokenizer.from_pretrained("../models/bloom-1b4-zh")
# 数据处理函数
def process_func(example):
MAX_LENGTH = 256
input_ids, attention_mask, labels = [], [], []
instruction = tokenizer("\n".join(["Human: " + example["instruction"], example["input"]]).strip() + "\n\nAssistant: ")
response = tokenizer(example["output"] + tokenizer.eos_token)
input_ids = instruction["input_ids"] + response["input_ids"]
attention_mask = instruction["attention_mask"] + response["attention_mask"]
labels = [-100] * len(instruction["input_ids"]) + response["input_ids"]
if len(input_ids) > MAX_LENGTH:
input_ids = input_ids[:MAX_LENGTH]
attention_mask = attention_mask[:MAX_LENGTH]
labels = labels[:MAX_LENGTH]
return {
"input_ids": input_ids,
"attention_mask": attention_mask,
"labels": labels
}
# 数据处理
tokenized_ds = ds.map(process_func, remove_columns=ds.column_names)
tokenized_ds
# 创建模型
model = AutoModelForCausalLM.from_pretrained("../models/bloom-1b4-zh", low_cpu_mem_usage=True)
from peft import LoraConfig, TaskType, get_peft_model
# 配置微调参数
config = LoraConfig(task_type=TaskType.CAUSAL_LM,
target_modules=".*\.1.*query_key_value", # 正则表达式
modules_to_save=["word_embeddings"])
# 创建模型
model = get_peft_model(model, config)
# 查看模型微调参数
model.print_trainable_parameters()
# 配置训练参数
args = TrainingArguments(
output_dir="./chatbot",
per_device_train_batch_size=1,
gradient_accumulation_steps=8,
logging_steps=10,
num_train_epochs=1
)
# 创建训练器
trainer = Trainer(
model=model,
args=args,
train_dataset=tokenized_ds,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)
# 模型训练
trainer.train()
# 模型推理
model = model.cuda()
ipt = tokenizer("Human: {}\n{}".format("周末去贵阳怎么玩?", "").strip() + "\n\nAssistant: ", return_tensors="pt").to(model.device)
res = tokenizer.decode(model.generate(**ipt, max_length=128, do_sample=True)[0], skip_special_tokens=True)
print(res)