3、全量微调实战:在HuggingFace Trainer中实现全量微调一个BERT模型
好,咱们直接进入正题。
前面聊了那么多理论,现在该动手了。全量微调BERT,说白了就是拿着一个预训练好的模型,在你的任务数据上把所有参数都更新一遍。我刚开始做这个的时候,总觉得全量微调就是"无脑训练",后来踩了几个坑才发现——没那么简单。
3.1 准备工作:环境与数据
先说说环境。我个人习惯用HuggingFace的Trainer,它封装得特别好,能省掉不少样板代码。你需要装这几个库:
pip install transformers datasets evaluate accelerate
嗯,这里要注意版本。我之前有一次用了个老版本的transformers,结果Trainer的参数名都变了,排查了半天。建议用最新版。
数据方面,我用一个文本分类任务来演示。假设你手头有一批新闻标题,要分成"科技"、"体育"、"娱乐"三类。数据格式大概是这样的:
{
"text": "苹果发布新款iPhone,A18芯片性能提升30%",
"label": 0 # 0:科技, 1:体育, 2:娱乐
}
加载数据我用datasets库,一行代码搞定:
from datasets import load_dataset
dataset = load_dataset("json", data_files="news_data.json")
dataset = dataset["train"].train_test_split(test_size=0.2)
你想想看,要是自己写数据加载、划分、缓存,得多少行?HuggingFace把这些都封装好了,真香。
3.2 核心代码:用Trainer实现全量微调
好,重头戏来了。全量微调的核心代码其实不长,但每一步都有讲究。我直接贴代码,然后逐行解释。
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification,
TrainingArguments,
Trainer
)
import evaluate
import numpy as np
# 1. 加载模型和分词器
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(
model_name,
num_labels=3
)
# 2. 数据预处理
def tokenize_function(examples):
return tokenizer(
examples["text"],
padding="max_length",
truncation=True,
max_length=128
)
tokenized_datasets = dataset.map(tokenize_function, batched=True)
# 3. 定义训练参数
training_args = TrainingArguments(
output_dir="./bert-finetuned",
evaluation_strategy="epoch",
save_strategy="epoch",
learning_rate=2e-5,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
num_train_epochs=3,
weight_decay=0.01,
logging_dir="./logs",
logging_steps=500,
load_best_model_at_end=True,
metric_for_best_model="accuracy",
)
# 4. 定义评估指标
accuracy = evaluate.load("accuracy")
def compute_metrics(eval_pred):
predictions, labels = eval_pred
predictions = np.argmax(predictions, axis=1)
return accuracy.compute(predictions=predictions, references=labels)
# 5. 创建Trainer并训练
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["test"],
tokenizer=tokenizer,
compute_metrics=compute_metrics,
)
trainer.train()
这段代码看着简单,但有几个关键点我得跟你说道说道。
3.3 关键参数解读:为什么这么设?
学习率 2e-5:全量微调的学习率通常比LoRA小很多。为什么?因为你要动所有参数,步子大了容易扯着蛋。我曾经试过1e-4,结果loss直接飞了,模型学成了"复读机"。
batch size 16:BERT-base有1.1亿参数,全量微调时显存消耗很大。16是我在24G显存卡上的经验值。如果你显存小,降到8或者4都行。
epochs=3:全量微调不需要太多轮次。BERT本身已经很强了,你只是让它适应你的任务分布。3轮够了,再多容易过拟合。我见过有人跑10轮的,结果在验证集上越跑越差——典型的过拟合。
weight_decay=0.01:这个参数很多人忽略。它是个正则化手段,防止模型参数过大。全量微调时尤其重要,因为所有参数都在动,不加约束容易学偏。
核心区别:全量微调的学习率通常比LoRA小一个数量级(2e-5 vs 1e-4),训练轮次也更少(3轮 vs 5-10轮)。因为全量微调更新的是全部参数,收敛更快,但也更容易过拟合。
3.4 避坑指南:我踩过的三个坑
坑一:分词器与模型不匹配
我曾经犯过一个低级错误——用BERT的分词器去处理RoBERTa的模型。结果呢?tokenize出来的id完全对不上,模型输出一堆乱码。记住:tokenizer和model必须来自同一个预训练模型。
坑二:padding策略选错
上面代码里我用了padding="max_length",这是最稳妥的方式。但如果你用padding="longest",每个batch的序列长度不一样,虽然省计算量,但容易在分布式训练时出问题。我建议新手直接用max_length,省心。
坑三:忘记设置load_best_model_at_end=True
这个参数的意思是:训练结束后,自动加载验证集上表现最好的checkpoint。如果不设,你拿到的就是最后一轮的模型——万一最后一轮过拟合了呢?我吃过这个亏,后来养成了习惯,每次都设上。
小技巧:训练过程中可以随时用trainer.evaluate()查看当前模型在验证集上的表现。我一般每500步看一眼,如果loss不降反升,就赶紧停掉调整参数。
3.5 全量微调的内存消耗分析
很多人问我:全量微调到底要多少显存?我拿BERT-base给你算笔账。
| 组件 | 参数量 | 显存占用(FP32) | 显存占用(FP16) |
|---|---|---|---|
| 模型参数 | 1.1亿 | ~420MB | ~210MB |
| 梯度 | 1.1亿 | ~420MB | ~210MB |
| 优化器状态(AdamW) | 2.2亿 | ~840MB | ~840MB |
| 激活值(batch=16, seq=128) | - | ~2-4GB | ~1-2GB |
| 总计 | - | ~3.7-5.7GB | ~2.3-3.3GB |
看到了吧?光是优化器状态就占了将近1GB。这就是为什么全量微调比LoRA吃显存得多——LoRA不需要存储全量参数的优化器状态。
注意:上面算的是单卡推理时的显存。如果你用梯度累积或多卡训练,显存占用会成倍增加。我建议至少准备一张16GB显存的卡来跑全量微调,8GB的卡跑BERT-base会很吃力。
3.6 全量微调 vs LoRA:一个直观对比
为了让你更直观地理解两者的区别,我画了一张流程图:
这张图很直观地展示了核心区别:全量微调是"大动干戈",所有参数都参与更新;LoRA是"精准打击",只动一小部分。说白了,全量微调用的是蛮力,LoRA用的是巧劲。
3.7 什么时候该用全量微调?
根据我的经验,以下场景适合全量微调:
- 你有充足的计算资源:比如公司给配了A100,显管够。这时候全量微调能榨干模型的所有潜力。
- 你的任务与预训练任务差异很大:比如BERT是通用语料训练的,你要做医疗文本分类。领域差异大,全量微调能让模型更好地适应新分布。
- 你的数据量足够大:至少几万条。数据太少的话,全量微调容易过拟合,LoRA反而更稳。
- 你需要极致的性能:在一些竞赛或生产环境中,0.1%的精度提升都值得。全量微调的上限通常比LoRA高。
反过来,如果你的显卡只有8GB显存、数据只有几千条、或者只是做个快速验证——那还是用LoRA吧。别跟自己过不去。
我的建议:先跑一个LoRA版本看看baseline,如果效果不满意,再上全量微调。这样既省时间,又能心里有数。我在项目中经常这么干,屡试不爽。
好了,全量微调的实战部分就到这里。代码你直接复制就能跑,参数也给了推荐值。下一节我们会深入LoRA的实现细节,到时候你会看到,同样的任务,LoRA的代码量能少一半,显存能省三分之二——但效果嘛,不一定差哦。
公众号:蓝海资料掘金营,微信deep3321