微调基础与策略
课程简介
何时需要微调、数据准备、基础微调工作流。
🎬 本课程视频:Finetuning LLMs — 大模型微调
微调基础与策略
一、什么是微调?
微调(Fine-tuning)是在预训练的大语言模型基础上,使用特定领域的数据进一步训练,使模型适应特定任务的过程。
预训练模型(如 GPT-4、Llama 3)已经在大规模通用语料上学习了语言的基本规律和广泛的知识。微调的目的是:在通用能力的基础上,强化特定领域的知识或能力。
1.1 预训练 vs 微调
| 维度 | 预训练 | 微调 |
|---|---|---|
| 数据规模 | TB 级 | MB-GB 级 |
| 计算资源 | 数千 GPU × 数月 | 单/多 GPU × 数小时-数天 |
| 目标 | 学习通用语言知识 | 适应特定任务/领域 |
| 成本 | 数百万美元 | 数百-数千美元 |
| 频率 | 一次(极少重复) | 多次(根据需要) |
二、何时需要微调?
在投入微调之前,应该先评估是否真的需要。Andrew Ng 建议按以下顺序尝试:
2.1 先尝试提示工程
微调前,先确认提示词工程是否已经达到了效果的天花板。
Step 1: 基础提示词 → 是否满足需求?
Step 2: 优化提示词(角色、格式、示例) → 是否满足?
Step 3: 使用高级技巧(CoT、少样本) → 是否满足?
Step 4: 如果以上都不满足,考虑微调
2.2 需要微调的信号
- 提示词策略已经用到极限:再怎么调整提示词,效果都没有明显提升
- 模型需要掌握大量专有知识:如法律合同的特定条款格式、医疗病历的专用术语和书写规范
- 需要减少推理阶段的 Token 消耗:微调后模型可以直接输出正确格式,不需要复杂提示词的引导
- 响应格式有严格要求:需要模型始终输出特定结构的结果
2.3 不需要微调的情况
- 简单的分类或提取任务(提示工程通常已足够)
- 只需要少量领域知识的场景(RAG 更合适)
- 任务频繁变化(微调赶不上变化速度)
三、数据准备:微调成功的决定性因素
3.1 质量远比数量重要
Andrew Ng 反复强调的一个原则:500 条高质量、多样化、精心准备的样本,效果远远优于 10000 条低质量、重复、有噪声的数据。
高质量数据的特点:
- 准确性:每条数据的目标输出都是正确的
- 多样性:覆盖各种输入变化和边缘情况
- 一致性:同类问题的输出风格和格式一致
- 代表性:反映真实的使用场景
3.2 数据格式
微调数据通常使用对话格式:
{
"messages": [
{"role": "system", "content": "你是一个客服助手。用简洁专业的语气回答。"},
{"role": "user", "content": "我的订单已经三天了还没到货,怎么办?"},
{"role": "assistant", "content": "很抱歉给您带来不便。订单号是多少?我帮您查询物流状态。"}
]
}
各部分的作用:
- system:定义模型的行为模式、角色定位和输出风格
- user:用户输入,覆盖各种真实场景
- assistant:预期输出,模型要学习的目标
3.3 数据清洗
数据清洗是数据准备中最重要但最容易被忽视的环节:
def clean_training_data(raw_data):
'''清洗微调数据'''
issues = []
cleaned = []
for i, item in enumerate(raw_data):
# 1. 检查重复
if is_duplicate(item, cleaned):
issues.append(f"#{i}: 重复样本")
continue
# 2. 检查格式
if not validate_format(item):
issues.append(f"#{i}: 格式错误")
continue
# 3. 检查长度
if len(item["messages"][-1]["content"]) > 2048:
issues.append(f"#{i}: 输出过长")
continue
# 4. 检查有害内容
if contains_harmful(item):
issues.append(f"#{i}: 包含有害内容")
continue
# 5. 检查一致性
if not check_consistency(item, cleaned[-5:]):
issues.append(f"#{i}: 与近期样本不一致")
continue
cleaned.append(item)
return cleaned, issues
3.4 数据平衡
确保数据集中各类样本的比例合理:
- 正常场景:65%
- 边界场景:20%
- 困难案例:10%
- 异常输入:5%
四、训练流程
4.1 基础流程
1. 准备数据集(训练集、验证集、测试集)
2. 选择基础模型(根据任务和资源)
3. 设置训练参数
4. 执行训练
5. 评估效果
6. 迭代优化
7. 部署
4.2 关键训练参数
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 学习率(learning_rate) | 1e-5 ~ 5e-5 | 微调时使用较小的学习率 |
| 训练轮数(num_epochs) | 2 ~ 4 | 防止过拟合 |
| 批次大小(batch_size) | 根据显存调整 | 越大越稳定 |
| 权重衰减(weight_decay) | 0.01 ~ 0.1 | 防止过拟合 |
| 学习率调度器 | cosine / linear | 推荐 cosine |
from transformers import TrainingArguments
training_args = TrainingArguments(
output_dir="./ft_output",
learning_rate=2e-5,
num_train_epochs=3,
per_device_train_batch_size=4,
per_device_eval_batch_size=4,
warmup_ratio=0.03,
weight_decay=0.01,
logging_steps=10,
evaluation_strategy="steps",
save_strategy="steps",
save_total_limit=2,
load_best_model_at_end=True
)
4.3 过拟合检测
在训练过程中监控训练集和验证集的损失(loss):
- 训练 loss 持续下降,验证 loss 开始上升 → 过拟合,提前停止
- 两个 loss 同步下降 → 正常训练
- 两个 loss 都不下降 → 学习率太低或数据问题
五、全流程示例
from datasets import Dataset
from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer
# 1. 准备数据
train_data = [
{"messages": [
{"role": "system", "content": "你是一个 Python 编程助手。"},
{"role": "user", "content": "用 Python 写一个函数计算斐波那契数列"},
{"role": "assistant", "content": "...[代码实现]..."}
]},
# ... 更多数据
]
# 2. 加载模型
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3-8B")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3-8B")
# 3. 数据预处理
def format_data(example):
text = tokenizer.apply_chat_template(
example["messages"],
tokenize=False
)
return tokenizer(text, truncation=True, max_length=2048)
dataset = Dataset.from_list(train_data)
tokenized_dataset = dataset.map(format_data)
# 4. 训练
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset,
eval_dataset=eval_dataset
)
trainer.train()
# 5. 保存
model.save_pretrained("./my_finetuned_model")
tokenizer.save_pretrained("./my_finetuned_model")
六、总结
微调是在预训练模型基础上进一步训练以适应特定任务的过程。在投入微调前应确认提示工程已经达到极限。数据的质量是微调成功的决定性因素——500 条高质量样本胜于万条低质数据。Base 模型更适合微调,数据格式采用对话格式,合理的训练参数和过拟合监控是训练成功的关键。
七、数据准备流程
7.1 数据收集
- 确定数据来源:内部日志、人工标注、公开数据集
- 数据量要求:一般需要 500-10000 条高质量样本
- 质量优先于数量:1000 条优质数据优于 10000 条噪声数据
7.2 数据清洗
def clean_dataset(data):
# 去重
data = remove_duplicates(data)
# 过滤低质量
data = [d for d in data if quality_score(d) > threshold]
# 格式标准化
data = standardize_format(data)
# 平衡标签
data = balance_labels(data)
return data
7.3 数据格式化
根据微调方法的不同,数据格式也不同:
- 全量微调:输入-输出对
- LoRA:指令-输入-输出三元组
- RLHF:提示-偏好对(好回答 vs 差回答)
八、训练流程详解
- 加载预训练模型和分词器
- 准备数据集并进行 Tokenize
- 配置训练参数(学习率、批次大小、epochs)
- 设定评估指标
- 开始训练并监控 loss
- 保存检查点
- 评估微调后的模型
九、总结
关键要点回顾:
- 微调的核心是“用特定数据调整模型参数以适应特定任务”
- 微调适用于改变行为风格、学习新格式、纠正系统性错误
- 数据质量比数据量更重要
- 需要建立评估基准来衡量微调效果
- 全量微调是最完整但成本最高的方案
七、数据准备流程
7.1 数据收集
- 确定数据来源:内部日志、人工标注、公开数据集
- 数据量要求:一般需要 500-10000 条高质量样本
- 质量优先于数量:1000 条优质数据优于 10000 条噪声数据
7.2 数据清洗
def clean_dataset(data):
# 去重
data = remove_duplicates(data)
# 过滤低质量
data = [d for d in data if quality_score(d) > threshold]
# 格式标准化
data = standardize_format(data)
# 平衡标签
data = balance_labels(data)
return data
7.3 数据格式化
根据微调方法的不同,数据格式也不同:
- 全量微调:输入-输出对
- LoRA:指令-输入-输出三元组
- RLHF:提示-偏好对(好回答 vs 差回答)
八、训练流程详解
- 加载预训练模型和分词器
- 准备数据集并进行 Tokenize
- 配置训练参数(学习率、批次大小、epochs)
- 设定评估指标
- 开始训练并监控 loss
- 保存检查点
- 评估微调后的模型
九、总结
关键要点回顾:
- 微调的核心是“用特定数据调整模型参数以适应特定任务”
- 微调适用于改变行为风格、学习新格式、纠正系统性错误
- 数据质量比数据量更重要
- 需要建立评估基准来衡量微调效果
- 全量微调是最完整但成本最高的方案
七、数据准备流程
7.1 数据收集
- 确定数据来源:内部日志、人工标注、公开数据集
- 数据量要求:一般需要 500-10000 条高质量样本
- 质量优先于数量:1000 条优质数据优于 10000 条噪声数据
7.2 数据清洗
def clean_dataset(data):
# 去重
data = remove_duplicates(data)
# 过滤低质量
data = [d for d in data if quality_score(d) > threshold]
# 格式标准化
data = standardize_format(data)
# 平衡标签
data = balance_labels(data)
return data
7.3 数据格式化
根据微调方法的不同,数据格式也不同:
- 全量微调:输入-输出对
- LoRA:指令-输入-输出三元组
- RLHF:提示-偏好对(好回答 vs 差回答)
八、训练流程详解
- 加载预训练模型和分词器
- 准备数据集并进行 Tokenize
- 配置训练参数(学习率、批次大小、epochs)
- 设定评估指标
- 开始训练并监控 loss
- 保存检查点
- 评估微调后的模型
九、总结
关键要点回顾:
- 微调的核心是“用特定数据调整模型参数以适应特定任务”
- 微调适用于改变行为风格、学习新格式、纠正系统性错误
- 数据质量比数据量更重要
- 需要建立评估基准来衡量微调效果
- 全量微调是最完整但成本最高的方案
延伸阅读
- 📺 B 站播放列表:Finetuning LLMs — 大模型微调
- 📚 更多学习资源,请访问 deeplearning.ai 官网