目录
[1. 引言](#1. 引言)
[2. 环境准备](#2. 环境准备)
[3. 加载模型和分词器](#3. 加载模型和分词器)
[4. LoRA 算法简介](#4. LoRA 算法简介)
[5. 配置 LoRA 参数](#5. 配置 LoRA 参数)
[6. 数据加载与预处理](#6. 数据加载与预处理)
[7. 训练超参数配置](#7. 训练超参数配置)
[8. 训练模型](#8. 训练模型)
[9. 结论](#9. 结论)
github代码仓库https://github.com/huangxiaoye6/LLM-tuning
在自然语言处理(NLP)领域,大模型的微调一直是一个热门话题。传统的全量微调需要大量的计算资源和时间,而低秩自适应(LoRA)微调则提供了一种更加高效的解决方案。本文将详细介绍如何使用 LoRA 对 Qwen3-0.6B 模型进行微调,并深入探讨其中涉及的算法和参数。
1. 引言
大语言模型在各种 NLP 任务中表现出色,但由于其庞大的参数数量,全量微调往往变得非常昂贵。LoRA 通过在模型的某些层引入可训练的低秩矩阵,减少了需要训练的参数数量,从而显著降低了计算成本和内存需求。
2. 环境准备
首先,我们需要导入必要的库,并设置一些环境变量以避免警告信息。
python
from peft import prepare_model_for_kbit_training,LoraConfig,get_peft_model
from transformers import AutoModelForCausalLM,AutoTokenizer,TrainingArguments,Trainer,default_data_collator
from datasets import load_dataset
import evaluate
import os
import warnings
warnings.filterwarnings('ignore')
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
3. 加载模型和分词器
我们选择 Qwen3-0.6B 作为基础模型,并使用AutoModelForCausalLM
和AutoTokenizer
来加载模型和分词器。
python
model_name="Qwen/Qwen3-0.6B"
model=AutoModelForCausalLM.from_pretrained(model_name,device_map='auto',torch_dtype="auto")
tokenizer=AutoTokenizer.from_pretrained(model_name)
4. LoRA 算法简介
LoRA 的核心思想是通过在预训练模型的权重矩阵上添加低秩分解矩阵来实现微调。具体来说,对于一个权重矩阵 W,LoRA 将其更新为 \(W + \Delta W\),其中 \(\Delta W = BA\),B 和 A 是低秩矩阵。这样,我们只需要训练 B 和 A,而不需要更新整个 W。
5. 配置 LoRA 参数
在使用 LoRA 进行微调之前,我们需要配置一些参数。
python
model = prepare_model_for_kbit_training(model)
lora_config = LoraConfig(
r=16, # LoRA矩阵的秩
lora_alpha=32, # LoRA alpha参数
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # 要应用LoRA的模块
lora_dropout=0.05, # Dropout概率
bias="none", # 是否训练偏置
task_type="CAUSAL_LM", # 任务类型
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # 打印可训练参数
参数解释
- r :LoRA 矩阵的秩,决定了低秩矩阵的维度。较小的
r
值可以减少训练参数的数量,但可能会影响模型的表达能力。 - lora_alpha:缩放因子,用于调整低秩矩阵的影响程度。
- target_modules :指定要应用 LoRA 的模块。在这个例子中,我们选择了注意力机制中的
q_proj
,k_proj
,v_proj
, 和o_proj
模块。 - lora_dropout:Dropout 概率,用于防止过拟合。
- bias:是否训练偏置项。
- task_type:任务类型,这里是因果语言模型(CAUSAL_LM)。
6. 数据加载与预处理
我们使用load_dataset
函数从 JSON 文件中加载训练集和验证集。
dataset = load_dataset('json', data_files={
'train': '../数据集/data/train.json',
'validation': '../数据集/data/eval.json'
})
然后,我们定义了两个函数来处理数据和进行分词。
def process_fun(example):
question=[]
answer=[]
for i in example['conversations']:
for j in i:
if j['from']=='human':
question.append(j['value'])
elif j['from']=='gpt':
answer.append(j['value'])
return {'question':question,'answer':answer}
process_data=dataset.map(process_fun,batched=True,remove_columns=dataset['train'].column_names)
def tokenizer_fun(examples):
# 构建完整的指令格式(问:{问题}\n答:{答案})
instructions = []
for q, a in zip(examples['question'], examples['answer']):
instruction = f"问:{q}\n答:{a}"
instructions.append(instruction)
encoded = tokenizer(
instructions,
max_length=512,
truncation=True,
padding="max_length",
return_tensors="pt"
)
labels = encoded["input_ids"].clone()
# 定位"答:"的位置,标记需要预测的部分
answer_start_token = tokenizer.encode("答:", add_special_tokens=False)[0]
# 遍历批次中的每个样本
for i in range(len(labels)):
# 找到每个样本中"答:"的第一个token位置
answer_positions = (labels[i] == answer_start_token).nonzero(as_tuple=True)[0]
if len(answer_positions) > 0:
# 只取第一个"答:"的位置
first_answer_pos = answer_positions[0]
# 将"答:"之前的token标记为-100(忽略计算损失)
labels[i, :first_answer_pos] = -100
return {
"input_ids": encoded["input_ids"],
"attention_mask": encoded["attention_mask"],
"labels": labels
}
tokenized_dataset = process_data.map(
tokenizer_fun,
batched=True,
remove_columns=process_data['train'].column_names
)
7. 训练超参数配置
我们使用TrainingArguments
来配置训练参数。
training_args = TrainingArguments(
output_dir="./lora_train_qwen_0.6B_model",
logging_steps=100,
logging_dir='./runs',
eval_strategy='epoch',
num_train_epochs=3,
per_device_train_batch_size=4,
per_device_eval_batch_size=4,
learning_rate=2e-5,
weight_decay=0.01,
save_strategy='epoch',
load_best_model_at_end=True,
metric_for_best_model='eval_loss',
gradient_accumulation_steps=4, # 如果GPU内存有限
)
参数解释
- output_dir:训练结果的保存目录。
- logging_steps:每多少步记录一次日志。
- eval_strategy:评估策略,这里选择按 epoch 评估。
- num_train_epochs:训练的轮数。
- per_device_train_batch_size:每个设备的训练批次大小。
- learning_rate:学习率,控制参数更新的步长。
- weight_decay:权重衰减,用于防止过拟合。
- save_strategy:保存策略,这里选择按 epoch 保存。
- load_best_model_at_end:训练结束后是否加载最优模型。
- metric_for_best_model:用于选择最优模型的指标,这里是验证集损失。
- gradient_accumulation_steps:梯度累积步数,用于处理 GPU 内存有限的情况。
8. 训练模型
最后,我们使用Trainer
类来训练模型。
trainer=Trainer(
model=model,
args=training_args,
eval_dataset=tokenized_dataset["validation"],
train_dataset=tokenized_dataset["train"],
data_collator=default_data_collator,
)
trainer.train()
trainer.save_model('./lora_train_qwen')
9. 结论
通过使用 LoRA 微调,我们可以在不牺牲太多模型性能的情况下,显著减少训练所需的计算资源和时间。本文详细介绍了如何使用 LoRA 对 Qwen3-0.6B 模型进行微调,并深入探讨了其中涉及的算法和参数。希望这篇文章能帮助你更好地理解和应用 LoRA 微调技术。