☰
Transformers 选择题(Multiple Choice)微调实战:基于 BERT 与 SWAG 数据集的完整指南
2026/10/4 21:02:58 网站建设 项目流程

Transformers 选择题(Multiple Choice)微调实战:基于 BERT 与 SWAG 数据集的完整指南

【免费下载链接】transformers🤗 Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and training.项目地址: https://gitcode.com/GitHub_Trending/tra/transformers

本指南面向希望在 🤗 Transformers 框架下完成选择题(Multiple Choice)任务微调的开发者。你将基于 SWAG 数据集的regular配置对 BERT 进行 fine-tuning,让模型学会在给定上下文后从多个候选中选出正确答案,并掌握预处理、动态填充(dynamic padding)、训练与推理的完整流程。读完本文,你将能够独立复现一条从原始数据集到可部署选择题模型的端到端链路。

什么是选择题(Multiple Choice)任务

选择题任务与问答(Question Answering)任务非常相似,区别在于:它会随上下文一并提供多个候选答案,模型被训练为从这些候选中选出正确的那一个。它天然适配阅读理解、常识推理、考试题目等场景,也是评测模型文本理解能力的重要基准任务之一。

在 🤗 Transformers 中,选择题任务的入口类是AutoModelForMultipleChoice。以本文使用的 BERT 为例,对应实现为 BertForMultipleChoice:它在 BERT 编码器之上挂接了一个 dropout 层和一个将隐藏状态映射为单一标量分数的线性分类头nn.Linear(config.hidden_size, 1),每个候选答案得到一个分数,最终通过 softmax 归一化后选择得分最高的候选。

环境准备

开始之前,请确认已安装所需依赖库:

pip install transformers datasets evaluate
  • transformers:提供模型、分词器、Trainer与数据整理器;
  • datasets:提供数据集加载与map预处理接口;
  • evaluate:提供准确率(accuracy)等评估指标。

如果你希望将训练好的模型上传分享到 Hugging Face Hub,可先登录账号(按提示输入 token):

>>> from huggingface_hub import notebook_login >>> notebook_login()

加载 SWAG 数据集

SWAG(Situations With Adversarial Generations)是一个用于常识推理的候选句选择数据集。使用 🤗 Datasets 库加载其regular配置:

>>> from datasets import load_dataset >>> swag = load_dataset("swag", "regular")

查看一个训练样本:

>>> swag["train"][0] {'ending0': 'passes by walking down the street playing their instruments.', 'ending1': 'has heard approaching them.', 'ending2': "arrives and they're outside dancing and asleep.", 'ending3': 'turns the lead singer watches the performance.', 'fold-ind': '3416', 'gold-source': 'gold', 'label': 0, 'sent1': 'Members of the procession walk down the street holding small horn brass instruments.', 'sent2': 'A drum line', 'startphrase': 'Members of the procession walk down the street holding small horn brass instruments. A drum line', 'video-id': 'anetv_jkn6uvmqwh4'}

字段虽多,但含义很直白:

  • sent1与sent2:共同描述一句话的开头,二者拼接即得到startphrase字段;
  • ending0~ending3:四个可能的句子结尾候选,其中只有一个正确;
  • label:标记正确结尾的索引(本例为 0)。

任务目标即:给定sent1 + sent2组成的句子开头,模型需要从四个ending中选出与label一致的正确答案。

数据预处理(Preprocess)

加载 BERT 分词器,用于处理句子开头与四个候选结尾:

>>> from transformers import AutoTokenizer >>> tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-uncased")

预处理函数需要完成三件事:

  1. 将sent1复制四份,并分别与sent2组合,还原句子的开头;
  2. 将sent2与四个候选结尾分别拼接;
  3. 将两个列表展平(flatten)以便批量 tokenize,之后再反展平(unflatten),使每个样本拥有对应的input_ids、attention_mask与labels字段。
>>> ending_names = ["ending0", "ending1", "ending2", "ending3"] >>> def preprocess_function(examples): ... first_sentences = [[context] * 4 for context in examples["sent1"]] ... question_headers = examples["sent2"] ... second_sentences = [ ... [f"{header} {examples[end][i]}" for end in ending_names] for i, header in enumerate(question_headers) ... ] ... first_sentences = sum(first_sentences, []) ... second_sentences = sum(second_sentences, []) ... tokenized_examples = tokenizer(first_sentences, second_sentences, truncation=True) ... return {k: [v[i : i + 4] for i in range(0, len(v), 4)] for k, v in tokenized_examples.items()}

逐行拆解这段核心逻辑:

  • first_sentences = [[context] * 4 for context in examples["sent1"]]:每个上下文复制 4 份,形成(batch, 4)的嵌套结构;
  • second_sentences:对每个样本,把sent2(句头)与ending{i}(候选结尾)拼成"A drum line passes by walking down the street playing their instruments."这样的完整第二句;
  • sum(first_sentences, []):利用列表加法将二维列表展平成一维,保证first_sentences与second_sentences按序一一对应;
  • tokenizer(first_sentences, second_sentences, truncation=True):以句子对形式批量 tokenize,启用截断以适配模型最大长度;
  • 最后按每 4 个一组切片,把结果反展平回(batch, 4, seq_len)的嵌套结构,供选择题模型使用。

随后用 🤗 Datasets 的map方法将预处理函数应用到整个数据集。设置batched=True可一次处理多个元素,显著加速:

>>> tokenized_swag = swag.map(preprocess_function, batched=True)

动态填充:DataCollatorForMultipleChoice

在组批时,更高效的做法是动态填充(dynamically pad):只把每个 batch 内的句子填充到该 batch 的最长长度,而不是把整个数据集统一填充到最大长度。虽然也可以在tokenizer中直接传padding=True,但动态填充明显更节省显存与算力。

DataCollatorForMultipleChoice 专门处理选择题样本的组批。它的核心流程是:

  1. 先取出标签:把label/labels从样本中弹出(它们不是嵌套结构);
  2. 展平:把batch_size个、每个含num_choices个选择的嵌套样本,展平成batch_size * num_choices个普通样本(源码注释中的示例:2个样本各含2个选择,展平成4个普通样本);
  3. 统一填充:调用tokenizer.pad对所有展平样本按其最长长度填充;
  4. 反展平还原:将结果 reshape 回(batch_size, num_choices, seq_len)形状,并把标签重新加回 batch。

其构造参数与默认值如下(来自 data_collator.py):

参数默认值说明
tokenizer(必填)用于编码数据的PreTrainedTokenizer或PreTrainedTokenizerFast
paddingTrue填充策略:True/'longest'填充到 batch 最长序列;'max_length'填充到max_length指定长度;False/'do_not_pad'不填充
max_lengthNone返回列表的最大长度,同时也是可选填充长度
pad_to_multiple_ofNone将序列填充到该值的整数倍,利于在 NVIDIA Volta 及以上(计算能力 >= 7.5)硬件上启用 Tensor Cores 加速
return_tensors"pt"返回的张量类型,可选"np"或"pt"

实例化非常简单:

>>> from transformers import DataCollatorForMultipleChoice >>> collator = DataCollatorForMultipleChoice(tokenizer=tokenizer)

评估指标

在训练过程中引入评估指标,有助于实时监控模型表现。使用 🤗 Evaluate 库加载 accuracy 指标:

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

再编写一个把预测与标签传给compute计算准确率的函数。注意:模型输出的 logits 形状为(batch_size, num_choices),需要沿axis=1取 argmax 得到预测类别:

>>> import numpy as np >>> def compute_metrics(eval_pred): ... predictions, labels = eval_pred ... predictions = np.argmax(predictions, axis=1) ... return accuracy.compute(predictions=predictions, references=labels)

训练(Train)

加载 BERT 的选择题模型:

>>> from transformers import AutoModelForMultipleChoice, TrainingArguments, Trainer >>> model = AutoModelForMultipleChoice.from_pretrained("google-bert/bert-base-uncased")

提示:如果你对使用Trainer微调模型还不熟悉,可以先阅读本仓库的训练基础教程。

接下来只需三步:

  1. 在TrainingArguments中定义训练超参数(唯一必填项是output_dir,用于指定模型保存位置);
  2. 将训练参数连同模型、数据集、分词器、数据整理器与compute_metrics一起传给Trainer;
  3. 调用trainer.train()启动微调。
>>> training_args = TrainingArguments( ... output_dir="my_awesome_swag_model", ... eval_strategy="epoch", ... save_strategy="epoch", ... load_best_model_at_end=True, ... learning_rate=5e-5, ... per_device_train_batch_size=16, ... per_device_eval_batch_size=16, ... num_train_epochs=3, ... weight_decay=0.01, ... push_to_hub=True, ... ) >>> trainer = Trainer( ... model=model, ... args=training_args, ... train_dataset=tokenized_swag["train"], ... eval_dataset=tokenized_swag["validation"], ... processing_class=tokenizer, ... data_collator=collator, ... compute_metrics=compute_metrics, ... ) >>> trainer.train()

各超参数的作用:

参数值说明
output_dir"my_awesome_swag_model"模型与 checkpoint 的保存目录(必填)
eval_strategy"epoch"每个 epoch 结束时执行一次评估
save_strategy"epoch"每个 epoch 结束时保存一次 checkpoint
load_best_model_at_endTrue训练结束时自动加载验证集上表现最佳的 checkpoint
learning_rate5e-5Adam 优化器的初始学习率,BERT 类模型微调的常见取值
per_device_train_batch_size16单设备训练 batch 大小
per_device_eval_batch_size16单设备评估 batch 大小
num_train_epochs3训练轮数
weight_decay0.01权重衰减系数,用于正则化
push_to_hubTrue训练结束后将模型推送至 Hub(需已登录)

训练完成后,可通过push_to_hub将模型分享给社区:

>>> trainer.push_to_hub()

底层原理:选择题模型如何计算

从 BertForMultipleChoice.forward 的实现可以看到选择题模型的运行机制:

  1. 输入形状为(batch_size, num_choices, sequence_length);
  2. 模型先把前两个维度合并(view(-1, seq_len)),得到batch_size * num_choices条独立序列,一次性送入 BERT 编码;
  3. BERT 输出的pooled_output经 dropout 后进入线性分类头,产出(batch_size * num_choices, 1)的标量分数;
  4. 分数被 reshape 回(batch_size, num_choices),配合形状为(batch_size,)的labels(取值在[0, num_choices-1])计算交叉熵损失。

这正是预处理阶段必须把每个样本组织成(batch, 4, seq_len)嵌套结构、并由DataCollatorForMultipleChoice负责展平与还原的原因:展平是为了让编码器高效并行计算,还原是为了让每个样本的四个候选共享同一个标签并正确计算损失。

推理(Inference)

微调完成后,即可用训练好的模型做推理。假设我们有一段关于法国面包法律的文本和两个候选答案:

>>> prompt = "France has a bread law, Le Décret Pain, with strict rules on what is allowed in a traditional baguette." >>> candidate1 = "The law does not apply to croissants and brioche." >>> candidate2 = "The law applies to baguettes."

将每对 prompt 与候选答案一起 tokenize,并返回 PyTorch 张量,同时构造labels:

>>> from transformers import AutoTokenizer >>> tokenizer = AutoTokenizer.from_pretrained("username/my_awesome_swag_model") >>> inputs = tokenizer([[prompt, candidate1], [prompt, candidate2]], return_tensors="pt", padding=True) >>> labels = torch.tensor(0).unsqueeze(0)

把输入与标签传给模型,取回logits:

>>> from transformers import AutoModelForMultipleChoice >>> model = AutoModelForMultipleChoice.from_pretrained("username/my_awesome_swag_model") >>> outputs = model(**{k: v.unsqueeze(0) for k, v in inputs.items()}, labels=labels) >>> logits = outputs.logits

取概率最高的类别:

>>> predicted_class = logits.argmax().item() >>> predicted_class 0

这里logits.argmax().item()返回的正是被模型判定为最合理的候选索引,与训练数据的label字段语义一致。

进阶:仓库中的完整示例脚本

如果你想跳过交互式教程,直接跑一个可配置的训练脚本,仓库在 examples/pytorch/multiple-choice/ 提供了完整的 SWAG 微调实现:

  • run_swag.py:基于Trainer的完整训练/评估脚本。它通过HfArgumentParser把参数划分为ModelArguments(模型与分词器路径、是否启用 fast tokenizer、trust_remote_code等)、DataTrainingArguments(max_seq_length、pad_to_max_length、max_train_samples、preprocessing_num_workers等)和TrainingArguments三组,支持从 JSON 配置文件或命令行参数启动;
  • run_swag_no_trainer.py:不依赖Trainer的版本,基于 PyTorch 手写训练循环,配合 run_no_trainer.sh 可直接运行,更便于深入理解每一步底层逻辑。

值得注意的实现细节:脚本中max_seq_length默认取tokenizer.model_max_length并封顶 1024(run_swag.py),pad_to_max_length默认False以启用更高效的动态填充(但在 TPU 上建议置为True)。预处理逻辑(ending_names、context_name="sent1"、question_header_name="sent2")与本文教程完全一致,当你迁移到自定义数据集时,只需替换这几个字段名与候选数量即可。

小结

本文以 SWAG 数据集为实例,完整走通了 Transformers 选择题任务微调的全部环节:数据集加载与字段解读、句子对展平-反展平的预处理范式、DataCollatorForMultipleChoice的动态填充原理、基于Trainer的训练与评估、以及部署阶段的推理调用。结合 BertForMultipleChoice 的源码可以看到,选择题模型本质上是对编码器输出套一层线性打分头,关键在于把每个样本的多个候选组织成嵌套张量并与标签对齐——理解了这一点,迁移到其他编码器架构(如 RoBERTa、DeBERTa)或自定义多选任务都将非常直接。

【免费下载链接】transformers🤗 Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and training.项目地址: https://gitcode.com/GitHub_Trending/tra/transformers

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询