首页
/ Transformers 微调预训练模型实战:数据集处理、Trainer 工作流与原生 PyTorch 训练循环

Transformers 微调预训练模型实战:数据集处理、Trainer 工作流与原生 PyTorch 训练循环

2026-09-04 23:31:55作者:袁立春Spencer

本文基于 Transformers 仓库的官方教程文档 training.md 展开,系统讲解如何把一个预训练语言模型微调(fine-tuning)到特定任务上:从 Yelp 评论分类数据集的加载与预处理,到使用 Trainer 一行命令完成训练与评估,再到完全手写 PyTorch 原生训练循环的完整实现。读完后,你将掌握微调的标准工作流、TrainingArgumentsget_scheduler 等关键 API 的实际行为,并能独立搭建不依赖 Trainer 的自定义训练管线。

为什么选择微调而不是从头训练

文档开篇即点明了微调(fine-tuning)的核心价值:

  • 降低计算成本:直接复用预训练权重,避免从头训练的巨大开销;
  • 减小环境影响:更少的训练算力意味着更低的能耗;
  • 快速用上最强基座:无需自己训练,即可使用社区中最新的预训练模型。

使用预训练模型时,你在自己的任务数据集上继续训练它,这个过程就叫"微调"(Arabic 文档中的 الضبط الدقيق)。Transformers 提供了数千个预训练模型供选择,而教程给出的三条路线是:

  1. 使用 Trainer 微调(本文重点);
  2. 在 TensorFlow 中使用 Keras 微调;
  3. 在原生 PyTorch 中微调(本文重点)。

下面的两条 PyTorch 路线以 Yelp 评论五分类 为贯穿案例,代码均可直接复制运行。

数据集准备:加载与批处理分词

加载 Yelp 评论数据集

微调的第一步是准备训练数据。官方教程使用的是 Yelp Reviews 数据集(load_dataset("yelp_review_full")),其每条样本包含一个 text 字段和一个 label 字段(0–4 共五个星级):

>>> from datasets import load_dataset

>>> dataset = load_dataset("yelp_review_full")
>>> dataset["train"][100]
{'label': 0,
 'text': 'My expectations for McDonalds are t rarely high. But for one to still fail so spectacularly...'}

map 一步完成批处理分词

模型无法直接消费原始文本,需要 tokenizer 处理,并配合填充(padding)与截断(truncation)策略应对变长序列。教程使用 Datasets 库的 map 方法,把分词函数一次性应用到整个数据集:

>>> from transformers import AutoTokenizer

>>> tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased")


>>> def tokenize_function(examples):
...     return tokenizer(examples["text"], padding="max_length", truncation=True)

>>> tokenized_datasets = dataset.map(tokenize_function, batched=True)

其中 padding="max_length" 会把所有序列填充到模型的最大长度,truncation=True 负责截断超长序列,batched=True 则让 tokenize_function 接收批量字段(字典而非单条样本),显著提升处理速度。

切出小子集以加速实验

Yelp 全量数据训练耗时较长,教程建议先切出 1000 条样本做快速验证:

>>> small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))
>>> small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))

注意固定 seed=42 保证结果可复现。

路线一:使用 PyTorch Trainer 微调

Trainer 是 Transformers 官方封装的训练类,免去了手写训练循环,开箱支持日志记录(logging)、梯度累积(gradient accumulation)、混合精度(mixed precision)等大量训练特性。

加载带分类头的模型

根据 Yelp 数据集的五个星级标签,加载 BERT 基座并指定分类数:

>>> from transformers import AutoModelForSequenceClassification

>>> model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5)

这里会看到一条关于"部分预训练权重未被使用、部分权重被随机初始化"的警告,这是完全正常的现象:BERT 预训练模型的原始任务头被丢弃,替换为一个随机初始化的序列分类头(num_labels=5)。后续微调会把这个新头与预训练骨干的知识对齐,这正是微调"迁移知识"的机制所在。

训练超参数:TrainingArguments

TrainingArguments 是一个 dataclass,集中管理所有可调整的训练超参数与激活项(输出目录、学习率、批大小、评估策略等)。最小配置只需指定检查点保存位置:

>>> from transformers import TrainingArguments

>>> training_args = TrainingArguments(output_dir="test_trainer")

从源码结构看,TrainingArgumentstraining_args.py 中以 dataclass field 逐一声明默认值,例如评估相关的 eval_strategy 默认为 "no"(不评估),可选 'no''steps''epoch' 三种取值,见 training_args.py#L1081-L1084。其余超参数(learning_rateper_device_train_batch_sizenum_train_epochs 等)均保留默认值即可启动训练,实际使用时可按需覆盖。

评估指标:logits 转预测再算 accuracy

Trainer 不会自动评估模型,需要你传入一个指标函数。教程使用 Evaluate 库的 accuracy 指标,并强调了一个关键点:所有 Transformers 模型返回的都是原始 logits,必须先转成最终预测:

>>> import numpy as np
>>> import evaluate

>>> metric = evaluate.load("accuracy")

>>> def compute_metrics(eval_pred):
...     logits, labels = eval_pred
...     predictions = np.argmax(logits, axis=-1)
...     return metric.compute(predictions=predictions, references=labels)

若希望每个训练 epoch 结束都看到评估指标,在 TrainingArguments 中打开 eval_strategy

>>> from transformers import TrainingArguments, Trainer

>>> training_args = TrainingArguments(output_dir="test_trainer", eval_strategy="epoch")

这与源码中的定义一致:eval_strategy"epoch" 时,Trainer 会在每个 epoch 末自动对 eval_dataset 跑一轮评估,并把 compute_metrics 的返回值记入日志。

创建 Trainer 并启动训练

把模型、训练参数、数据与评估函数组装进 Trainer

>>> trainer = Trainer(
...     model=model,
...     args=training_args,
...     train_dataset=small_train_dataset,
...     eval_dataset=small_eval_dataset,
...     compute_metrics=compute_metrics,
... )

>>> trainer.train()

trainer.train() 是整条训练管线的总入口。从源码看,Trainer.train 定义了 resume_from_checkpoint、超参数搜索 trial 等参数,内部负责:随机种子设置、梯度检查点激活、优化器/调度器构建、设备与分布式后端协调、检查点保存与评估循环调度——这些在教程视角下都被这一行调用完全封装。

路线二:原生 PyTorch 训练循环

对于偏好完全掌控训练过程的用户,Transformers 模型同样可以直接嵌入手写的 PyTorch 循环。这一路线的所有要素(DataLoader、优化器、调度器、训练循环)都是标准 PyTorch 写法。

释放内存并手工整理数据格式

先清理上一路线的对象释放显存:

del model
del trainer
torch.cuda.empty_cache()

接着对 tokenized_datasets 做三步整理:

  1. 移除 text——模型不接受原始文本输入:

    >>> tokenized_datasets = tokenized_datasets.remove_columns(["text"])
    
  2. 重命名 labellabels——模型期望的输入参数名是 labels

    >>> tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
    
  3. 设置数据集格式为 torch——让取出的样本直接是 PyTorch 张量而非 Python 列表:

    >>> tokenized_datasets.set_format("torch")
    

之后同样切出 1000 条的小数据集加速实验:

>>> small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))
>>> small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))

DataLoader 与设备

为训练/评估集创建 DataLoader,批量大小为 8,训练集打乱顺序:

>>> from torch.utils.data import DataLoader

>>> train_dataloader = DataLoader(small_train_dataset, shuffle=True, batch_size=8)
>>> eval_dataloader = DataLoader(small_eval_dataset, batch_size=8)

重新加载模型(同样指定 num_labels=5):

>>> from transformers import AutoModelForSequenceClassification

>>> model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5)

优化器与学习率调度器

使用 PyTorch 的 AdamW,学习率 5e-5(BERT 微调的典型取值):

>>> from torch.optim import AdamW

>>> optimizer = AdamW(model.parameters(), lr=5e-5)

再创建与 Trainer 默认行为一致的学习率调度器:

>>> from transformers import get_scheduler

>>> num_epochs = 3
>>> num_training_steps = num_epochs * len(train_dataloader)
>>> lr_scheduler = get_scheduler(
...     name="linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_training_steps
... )

get_scheduler 是 Transformers 提供的调度器统一 API,见 optimization.py#L960-L984。从源码签名看,它接受调度器名称(如 linear)、优化器、num_warmup_stepsnum_training_steps 四个核心参数;除 constant 等少数调度器外,其余类型(含 linear)都必须提供 num_warmup_steps,缺失时函数会抛出 ValueError——教程中显式传 num_warmup_steps=0 正是满足这一约束。

最后选择训练设备,有 GPU 时务必使用 GPU,否则 CPU 上训练可能需要数小时而非几分钟:

>>> import torch

>>> device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
>>> model.to(device)

训练循环

tqdm 添加进度条,核心循环是教科书式的"前向-反向-更新":

>>> from tqdm.auto import tqdm

>>> progress_bar = tqdm(range(num_training_steps))

>>> model.train()
>>> for epoch in range(num_epochs):
...     for batch in train_dataloader:
...         batch = {k: v.to(device) for k, v in batch.items()}
...         outputs = model(**batch)
...         loss = outputs.loss
...         loss.backward()

...         optimizer.step()
...         lr_scheduler.step()
...         optimizer.zero_grad()
...         progress_bar.update(1)

几个值得注意的细节:

  • model(**batch) 把整理过的数据字典(input_idsattention_masklabels)解包为模型输入,模型内部会利用 labels 自动计算并返回 outputs.loss,无需手写损失函数;
  • 每个 batch 内严格按 loss.backward()optimizer.step()lr_scheduler.step()optimizer.zero_grad() 的顺序执行;
  • 外层 for epoch 与内层 for batch 的双层结构使 progress_bar 按总步数平滑推进。

评估:用 add_batch 累积后一次性计算

Trainer 中"每 epoch 末计算指标"不同,手写循环的惯用做法是用 metric.add_batch 逐批累积预测与标签,循环结束后一次性 compute()

>>> import evaluate

>>> metric = evaluate.load("accuracy")
>>> model.eval()
>>> for batch in eval_dataloader:
...     batch = {k: v.to(device) for k, v in batch.items()}
...     with torch.no_grad():
...         outputs = model(**batch)

...     logits = outputs.logits
...     predictions = torch.argmax(logits, dim=-1)
...     metric.add_batch(predictions=predictions, references=batch["labels"])

>>> metric.compute()

评估时记得 model.eval() 切换模型模式(关闭 dropout 等训练态行为),并用 torch.no_grad() 关闭梯度计算以节省显存。

延伸资源

  • 仓库中的 examples/pytorch/text-classification 目录提供了完整可运行的分类脚本:run_classification.py(基于 Trainer)与 run_glue_no_trainer.py(基于 Accelerator 的手写循环,可对照本文原生 PyTorch 路线查看 get_schedulerDataCollatorWithPadding 等 API 在完整脚本中的用法),以及对应的 README
  • examples/pytorch 下还有语言建模、文本生成、问答等其他任务的训练脚本,可作为迁移本文流程到其他任务的参考。
  • 若要在 TensorFlow/Keras 路线上微调,可结合仓库文档树中的 TensorFlow 训练章节(英文版训练教程)查看对应写法。

小结

环节 Trainer 路线 原生 PyTorch 路线
数据整理 自动(DataCollator 处理填充/格式) 手动 remove_columns / rename_column / set_format
超参数 TrainingArguments dataclass 自行管理各组件
优化器/调度器 内部自动构建 AdamW + get_scheduler 手工创建
评估 compute_metrics + eval_strategy metric.add_batch 累积后 compute
启动成本 一行 trainer.train() 完整训练循环代码

两条路线共享完全相同的数据预处理逻辑与模型加载方式,选择取决于你对训练过程控制粒度的需求:追求效率与特性(日志、断点续训、分布式)选 Trainer;需要深度定制(特殊损失、自定义梯度处理、非标准数据流)时选原生 PyTorch 循环。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384