Fine-tuning/微调:给AI上"专业课"
这篇文章带你理解AI的"专业培训"------微调,让通用模型变成领域专家。
前言
预训练出来的AI,就像一个读完图书馆的"通识生":
- 什么都知道一点
- 但什么都不精通
- 聊天还行,专业任务差点意思
怎么让它变专业?Fine-tuning(微调)
今天,我们来理解AI的"专业课"。
一、黑话原文 vs 人话翻译
场景模拟
arduino
🎯 AI算法团队:
工程师A:"我们用LoRA微调一下"
工程师B:"全量微调成本太高,用PEFT吧"
工程师C:"数据量不够,容易过拟合"
工程师A:"那用Few-shot微调"
工程师B:"记得冻结底层参数"
人话翻译表
| 黑话 | 人话翻译 | 一句话理解 |
|---|---|---|
| Fine-tuning | 微调 | 专业培训 |
| Full Fine-tuning | 全量微调 | 重新学所有内容 |
| PEFT | 参数高效微调 | 只改一小部分 |
| LoRA | 低秩适应 | 只学"差异" |
| 冻结参数 | Freezing | 不改基础内容 |
| 过拟合 | Overfitting | 死记硬背 |
二、微调是什么?
2.1 一句话定义
Fine-tuning = 在预训练模型基础上,用特定数据继续训练
人话版:给"通识生"上专业课,让它变成某个领域的专家。
2.2 为什么需要微调?
arduino
┌─────────────────────────────────────────────────────────────┐
│ 预训练模型的局限 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 预训练模型(通识生): │
│ │
│ ✅ 能力: │
│ ├── 会说人话 │
│ ├── 有基本常识 │
│ └── 能简单对话 │
│ │
│ ❌ 问题: │
│ ├── 不懂专业领域知识 │
│ ├── 说话风格不固定 │
│ ├── 输出格式不规范 │
│ └── 可能说"不合适"的话 │
│ │
│ 微调后(专家): │
│ │
│ ✅ 能力: │
│ ├── 懂专业领域知识 │
│ ├── 说话风格符合要求 │
│ ├── 输出格式规范 │
│ └── 知道什么该说什么不该说 │
│ │
└─────────────────────────────────────────────────────────────┘
2.3 生活类比
微调就像:
🎓 大学专业培训
预训练 = 高中毕业(什么都学过)
微调 = 大学专业(计算机/医学/法律...)
🏥 医生规培
医学院毕业 = 有医学知识
规培 = 学会临床技能
👔 入职培训
新人 = 有基本能力
培训 = 学会公司规范
三、微调的类型
3.1 Full Fine-tuning(全量微调)
scss
┌─────────────────────────────────────────────────────────────┐
│ 全量微调 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 原理:更新模型的所有参数 │
│ │
│ 预训练模型 ────→ 所有参数都改 ────→ 微调模型 │
│ (7B参数) (175亿个) (7B参数) │
│ │
│ 优点: │
│ ✅ 效果最好 │
│ ✅ 适应性强 │
│ │
│ 缺点: │
│ ❌ 成本高(需要更新所有参数) │
│ ❌ 显存要求大 │
│ ❌ 容易过拟合 │
│ ❌ 需要大量数据 │
│ │
│ 适用: │
│ - 大公司 │
│ - 资源充足 │
│ - 追求最佳效果 │
│ │
└─────────────────────────────────────────────────────────────┘
3.2 PEFT(参数高效微调)
erlang
┌─────────────────────────────────────────────────────────────┐
│ PEFT - 只改一小部分 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 原理:冻结大部分参数,只更新少量参数 │
│ │
│ 预训练模型 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ████████████████████████████████████████ ← 冻结 │ │
│ │ ████████████████████████████████████████ ← 冻结 │ │
│ │ ░░░░░░░░ ← 只改这部分(新增的少量参数) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 优点: │
│ ✅ 成本低(只需1%的参数) │
│ ✅ 显存要求小 │
│ ✅ 不容易过拟合 │
│ ✅ 可以保存多个微调版本 │
│ │
│ 缺点: │
│ ❌ 效果略差于全量微调 │
│ │
│ 代表方法:LoRA、Prefix Tuning、Adapter │
│ │
└─────────────────────────────────────────────────────────────┘
3.3 对比表
| 方法 | 更新参数量 | 显存需求 | 效果 | 适用场景 |
|---|---|---|---|---|
| 全量微调 | 100% | 很高 | 最好 | 大公司、追求极致 |
| LoRA | ~1% | 低 | 很好 | 个人、中小企业 |
| QLoRA | ~1% | 很低 | 好 | 消费级显卡 |
| Adapter | ~1% | 低 | 好 | 多任务场景 |
四、LoRA:最流行的微调方法
4.1 LoRA原理
less
┌─────────────────────────────────────────────────────────────┐
│ LoRA原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 原始矩阵 W (7B模型中的一个大矩阵) │
│ │
│ 传统微调:直接修改 W │
│ W_new = W + ΔW (ΔW也是大矩阵) │
│ │
│ LoRA的思路: │
│ ΔW = A × B (两个小矩阵相乘) │
│ - A: n×r 矩阵 │
│ - B: r×n 矩阵 │
│ - r 很小(如8、16) │
│ │
│ 举例: │
│ 原始 W: 4096×4096 = 16,777,216 个参数 │
│ LoRA: A(4096×8) + B(8×4096) = 65,536 个参数 │
│ 只需要原来的 0.4%! │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 原始W (冻结) │ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ 4096 × 4096 │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ │ + │ │
│ │ LoRA (可训练) │ │
│ │ ┌──────┐ ┌────────────────┐ │ │
│ │ │ 4096 │ │ │ │ │
│ │ │ ×8 │ × │ 8×4096 │ │ │
│ │ │ │ │ │ │ │
│ │ └──────┘ └────────────────┘ │ │
│ │ A B │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
4.2 LoRA代码示例
python
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM
# 加载预训练模型
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
# LoRA配置
lora_config = LoraConfig(
r=8, # LoRA的秩
lora_alpha=32, # 缩放系数
target_modules=["q_proj", "v_proj"], # 应用到哪些层
lora_dropout=0.1,
bias="none",
task_type="CAUSAL_LM"
)
# 应用LoRA
model = get_peft_model(model, lora_config)
# 查看可训练参数
model.print_trainable_parameters()
# 输出:trainable params: 4,194,304 || all params: 6,742,609,920 || trainable%: 0.06%
4.3 QLoRA:更省显存
python
# QLoRA = 量化 + LoRA
# 用4bit精度加载模型,再应用LoRA
from transformers import BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
# 4bit量化配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
)
# 加载4bit模型
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config
)
# 准备量化训练
model = prepare_model_for_kbit_training(model)
# 应用LoRA
model = get_peft_model(model, lora_config)
# 显存对比:
# 全量微调 7B: ~28GB
# LoRA 7B: ~16GB
# QLoRA 7B: ~6GB (单张RTX 3060就能跑!)
五、微调数据
5.1 微调数据格式
json
// 对话微调数据格式
{
"conversations": [
{"role": "user", "content": "什么是机器学习?"},
{"role": "assistant", "content": "机器学习是一种让计算机从数据中学习的技术..."}
]
}
// 指令微调数据格式
{
"instruction": "将下面的句子翻译成英文",
"input": "今天天气真好",
"output": "The weather is really nice today."
}
5.2 数据量需求
┌─────────────────────────────────────────────────────────────┐
│ 微调数据量参考 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 任务类型 最少数据量 推荐数据量 │
│ ────────── ───────── ──────────── │
│ 风格调整 100-500条 1,000-5,000条 │
│ 简单任务 500-1,000条 5,000-10,000条 │
│ 复杂任务 1,000+条 10,000+条 │
│ 领域知识 5,000+条 50,000+条 │
│ │
│ 注意: │
│ - 数据质量 > 数据数量 │
│ - 宁缺毋滥 │
│ - 数据要有多样性 │
│ │
└─────────────────────────────────────────────────────────────┘
5.3 数据准备技巧
yaml
微调数据准备:
1. 数据清洗:
- 去除重复
- 过滤低质量
- 检查格式
2. 数据增强:
- 改写
- 扩展
- 合成
3. 数据平衡:
- 类别均衡
- 长度分布
4. 数据划分:
- 训练集 80%
- 验证集 10%
- 测试集 10%
六、微调的坑
6.1 过拟合
┌─────────────────────────────────────────────────────────────┐
│ 过拟合 = 死记硬背 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 症状: │
│ - 训练数据表现很好 │
│ - 新数据表现很差 │
│ - 输出和训练数据一模一样 │
│ │
│ 原因: │
│ - 数据太少 │
│ - 训练太久 │
│ - 学习率太高 │
│ │
│ 解决方案: │
│ ├── 增加数据量 │
│ ├── Early Stopping(提前停止) │
│ ├── 降低学习率 │
│ ├── 使用Dropout │
│ └── 使用正则化 │
│ │
└─────────────────────────────────────────────────────────────┘
6.2 灾难性遗忘
┌─────────────────────────────────────────────────────────────┐
│ 灾难性遗忘 = 忘了老知识 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 症状: │
│ - 学会了新任务 │
│ - 忘了旧能力 │
│ - 通用能力下降 │
│ │
│ 原因: │
│ - 微调数据太单一 │
│ - 全量微调改动太大 │
│ │
│ 解决方案: │
│ ├── 混合微调数据(旧任务+新任务) │
│ ├── 使用PEFT(只改小部分参数) │
│ └── 控制学习率 │
│ │
└─────────────────────────────────────────────────────────────┘
七、微调实战
7.1 完整微调流程
python
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer
)
from peft import LoraConfig, get_peft_model
from datasets import load_dataset
# 1. 加载模型和tokenizer
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
# 2. 应用LoRA
lora_config = LoraConfig(r=8, lora_alpha=32, ...)
model = get_peft_model(model, lora_config)
# 3. 加载数据
dataset = load_dataset("json", data_files="train_data.json")
# 4. 数据处理
def preprocess(examples):
return tokenizer(examples["text"], truncation=True, padding="max_length")
tokenized_dataset = dataset.map(preprocess, batched=True)
# 5. 训练配置
training_args = TrainingArguments(
output_dir="./output",
num_train_epochs=3,
per_device_train_batch_size=4,
learning_rate=1e-4,
logging_steps=100,
save_steps=500,
)
# 6. 开始训练
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset["train"],
)
trainer.train()
# 7. 保存模型
model.save_pretrained("./my_finetuned_model")
小结
| 黑话 | 人话 | 记忆口诀 |
|---|---|---|
| Fine-tuning | 微调 | 专业培训 |
| Full Fine-tuning | 全量微调 | 全部重学 |
| PEFT | 高效微调 | 只改一点 |
| LoRA | 低秩适应 | 最流行的PEFT |
| 过拟合 | 死记硬背 | 只会做原题 |
| 灾难性遗忘 | 忘了老知识 | 学新忘旧 |
关键认知:
- 微调让通用模型变成专家
- LoRA是最流行的高效微调方法
- 数据质量比数量重要
- 注意避免过拟合和灾难性遗忘
黑话等级
arduino
⭐⭐⭐⭐ 中级
├── 理解微调的原理
├── 会用LoRA微调模型
└── 知道如何避免常见问题
下一期预告:RLHF - 让AI更"听话"的秘密
思考与练习
-
思考题:
- LoRA为什么能用很少的参数达到好的效果?
- 什么时候用全量微调,什么时候用PEFT?
-
动手练习:
- 用LoRA微调一个开源模型
- 对比微调前后的效果差异
-
延伸探索:
- 研究其他PEFT方法(Adapter、Prefix Tuning)
- 了解多任务微调
下期预告
下一篇文章,我们来聊:RLHF - 让AI更"听话"的秘密
会解答这些问题:
- RLHF是怎么让AI变"乖"的?
- 为什么要用人类反馈?
- PPO是什么?
关注专栏,不错过后续更新!
作者:ECH00O00 本文首发于掘金专栏《AI黑话翻译官》 欢迎评论区交流讨论,点赞收藏就是最大的鼓励