在 RACE 阅读理解任务上微调 RoBERTa:基于 fairseq sentence_ranking 的完整实战指南(unilm / infoxlm 仓库)
2026/9/14 2:17:40 网站建设 项目流程

在 RACE 阅读理解任务上微调 RoBERTa:基于 fairseq sentence_ranking 的完整实战指南(unilm / infoxlm 仓库)

【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm

导读

本文基于 infoxlm/fairseq/examples/roberta/README.race.md 展开,系统讲解如何在 RACE(ReAding Comprehension from Examinations)多项选择阅读理解数据集上微调 RoBERTa 模型:从数据下载、脚本预处理(提取段落/题目/选项并做 BPE 编码与 fairseq-preprocess 二进制化),到一条可直接运行的fairseq-train微调命令及其全部超参数含义。文章还将深入 fairseq 的sentence_ranking任务与损失函数源码,解释"4 个候选选项打分 + 交叉熵排序"这一核心训练机制。读完本文,你将掌握在 fairseq 框架内把任意 RoBERTa 类预训练模型适配到多项选择类任务的完整技能。

一、RACE 任务与句子排序范式

RACE 是面向中学生与高中生的英文阅读理解测试数据(源自 Lai 等人的论文),每个样本包含一篇正文段落、若干道选择题,每道题有 4 个候选选项。它天然适合用句子排序(sentence ranking)范式求解:把"段落 + 题目 + 每个候选选项"分别拼接成 4 条输入,让模型为每条输入打一个分数,再对这 4 个分数做 softmax 归一化并用交叉熵监督正确答案。

在 infoxlm/fairseq/examples/roberta/README.md 中可以看到roberta.large在该任务上的公开结果:测试集整体准确率 83.2%,其中 Middle 难度 86.5%、High 难度 81.3%。这说明在大规模预训练模型上以句子排序方式微调,可以在 RACE 上取得较强表现。

说明:本文所述流程位于本仓库infoxlm/fairseq(一个基于 fairseq 的代码副本)中,其中所有脚本与模型文件均可直接复用;InfoXLM 项目自身的跨语言预训练细节不属于本文范围。

二、第一步:下载 RACE 数据

原始文档要求先从 RACE 官方渠道(CMU 提供的下载页面)下载原始数据。下载后得到的目录结构约定如下(供后续预处理脚本使用):

<race_data_folder>/ ├── train/ │ ├── middle/ # 中学难度训练样本(每个 .json 一个段落) │ └── high/ # 高中难度训练样本 ├── dev/ │ ├── middle/ │ └── high/ └── test/ ├── middle/ └── high/

每个 JSON 文件包含article(正文段落)、questions(问题列表)、options(每个问题对应的 4 个选项列表)与answers(正确答案索引,A/B/C/D)四个字段,预处理脚本正是依据这些字段工作的。

三、第二步:预处理 RACE 数据

原文档给出的预处理分为两个阶段:先用 Python 脚本从原始 JSON 中抽取并整理文本,再用 Shell 脚本完成 BPE 编码与 fairseq 二进制化。

3.1 阶段一:抽取段落、题目与选项

python ./examples/roberta/preprocess_RACE.py --input-dir <input-dir> --output-dir <extracted-data-dir>

(在本文档所在仓库中,该脚本的实际路径为 infoxlm/fairseq/examples/roberta/preprocess_RACE.py。)

阅读源码可知其内部逻辑:

  • get_examples 遍历middlehigh两个难度层级,读取每个 JSON:将article中的换行与多余空白归一化为单空格(re.sub(r'\s+', ' ', ...)),并把answers中的字符标签转为 0~3 的整数下标(ord(answers[i]) - ord("A"))。
  • 对每个问题的 4 个选项,构造qa_cat:若题目文本含占位符_,则将选项文本替换进占位符;否则直接用空格拼接"题目 + 选项",同样做空白归一化。
  • main 对traindevtest-middletest-high四个集合分别输出 6 个文本文件:
    • input0:正文段落,每行一个样本;
    • input1~input4:四个候选选项各自对应的"题目 + 选项"拼接文本,第 i 个选项写入input{i+1}
    • label:正确答案下标(0~3),每行一个。

3.2 阶段二:BPE 编码与 fairseq 二进制化

./examples/roberta/preprocess_RACE.sh <extracted-data-dir> <output-dir>

对应脚本为 infoxlm/fairseq/examples/roberta/preprocess_RACE.sh,其工作流程可分为三步:

  1. 获取 GPT-2 BPE 资源(脚本 L19-L21):下载encoder.jsonvocab.bpedict.txt三份文件(fairseq 官方提供的 GPT-2 BPE 编码表与词表)。RoBERTa 使用与 GPT-2 相同的 BPE 子词切分,因此这三份文件是编码与建字典的基础。
  2. 多进程 BPE 编码(脚本 L23-L39):对input0~input4这 5 类输入、train/dev/test-middle/test-high这 4 个集合逐一执行python -m examples.roberta.multiprocessing_bpe_encoder,将纯文本转为 token id 序列(以空格分隔的数字串),输出为.bpe后缀文件。该编码器实现在 infoxlm/fairseq/examples/roberta/multiprocessing_bpe_encoder.py,内部通过Pool(workers)多进程并行编码,--keep-empty用于保留空行以免破坏与 label 文件的行对齐。
  3. fairseq-preprocess 生成索引数据集(脚本 L41-L52):对每类输入分别调用fairseq-preprocess --only-source,以train.bpe为训练集、dev.bpe为验证集、test-middle.bpetest-high.bpe拼接为测试集,写入$OUT_DATA_FOLDER/input{0..4}/各子目录,并使用前面下载的dict.txt作为源词表(--srcdict)。

最终还会整理 label 文件(脚本 L54-L59):

cp train.label -> $OUT_DATA_FOLDER/label/train.label cp dev.label -> $OUT_DATA_FOLDER/label/valid.label cp test-middle.label -> $OUT_DATA_FOLDER/label/test.label cp test-high.label -> $OUT_DATA_FOLDER/label/test1.label

预处理完成后的output-dir目录结构为:

<output-dir>/ ├── input0/ # 段落(正文上下文) ├── input1/ # 选项 A 的题目+选项 ├── input2/ # 选项 B ├── input3/ # 选项 C ├── input4/ # 选项 D └── label/ # train.label / valid.label / test.label / test1.label

这个目录就是后续fairseq-trainDATA_DIR

四、第三步:微调 RoBERTa

4.1 完整微调命令

以下命令原样继承自原文档,可直接在 fairseq 环境中执行:

MAX_EPOCH=5 # Number of training epochs. LR=1e-05 # Peak LR for fixed LR scheduler. NUM_CLASSES=4 MAX_SENTENCES=1 # Batch size per GPU. UPDATE_FREQ=8 # Accumulate gradients to simulate training on 8 GPUs. DATA_DIR=/path/to/race-output-dir ROBERTA_PATH=/path/to/roberta/model.pt CUDA_VISIBLE_DEVICES=0,1 fairseq-train $DATA_DIR --ddp-backend=no_c10d \ --restore-file $ROBERTA_PATH \ --reset-optimizer --reset-dataloader --reset-meters \ --best-checkpoint-metric accuracy --maximize-best-checkpoint-metric \ --task sentence_ranking \ --num-classes $NUM_CLASSES \ --init-token 0 --separator-token 2 \ --max-option-length 128 \ --max-positions 512 \ --truncate-sequence \ --arch roberta_large \ --dropout 0.1 --attention-dropout 0.1 --weight-decay 0.01 \ --criterion sentence_ranking \ --optimizer adam --adam-betas '(0.9, 0.98)' --adam-eps 1e-06 \ --clip-norm 0.0 \ --lr-scheduler fixed --lr $LR \ --fp16 --fp16-init-scale 4 --threshold-loss-scale 1 --fp16-scale-window 128 \ --max-sentences $MAX_SENTENCES \ --required-batch-size-multiple 1 \ --update-freq $UPDATE_FREQ \ --max-epoch $MAX_EPOCH

4.2 关键参数逐项解析

参数取值示例含义与作用
--task sentence_ranking固定指定多项选择排序任务,对应任务类实现在 infoxlm/fairseq/fairseq/tasks/sentence_ranking.py,它会按num_classes读取input1..inputN
--num-classes 44候选选项个数(RACE 为 4 选 1)。任务端据此决定拼接几路输入、损失端据此决定 logits 的最后一维宽度
--init-token 0/--separator-token 20 / 2RoBERTa 词表中<s>=0、</s>=2。init-token拼在每个选项序列开头(即模型输入端),separator-token拼在段落序列开头,用于区分上下文与选项两个片段
--max-option-length 128128每个"题目+选项"序列被截断的最大长度(对应任务端TruncateDataset),防止单个选项过长撑爆长度预算
--max-positions 512512单条拼接后序列的最大长度(RoBERTa 预训练长度),同时也是--truncate-sequence的截断上限
--truncate-sequence开关对"选项+段落"拼接后的完整序列按max_positions做尾部截断
--arch roberta_largeroberta_large模型架构。roberta_large为 355M 参数规模(24 层 Transformer);显存不足时可换roberta.base(125M)
--criterion sentence_ranking固定排序损失,对应 infoxlm/fairseq/fairseq/criterions/sentence_ranking.py,对 4 路分数做 log-softmax 交叉熵
--best-checkpoint-metric accuracy --maximize-best-checkpoint-metric开关以验证集 accuracy(而非 loss)作为挑选最优检查点的指标并取最大值,保证保存的是分类准确率最高的 checkpoint
--reset-optimizer --reset-dataloader --reset-meters开关加载预训练权重后重置优化器、数据加载器与统计量,避免继承预训练阶段的训练状态
--restore-file模型路径加载的预训练 checkpoint(roberta.pt
--lr-scheduler fixed --lr 1e-05fixed / 1e-5使用固定学习率调度器(RACE 任务文档选用 fixed 而非 warmup 衰减),学习率峰值 1e-5
--fp16系列混合精度训练:--fp16-init-scale 4初始缩放因子、--threshold-loss-scale 1触发缩放的阈值、--fp16-scale-window 128动态缩放窗口
--max-sentences 11每 GPU 批大小。RACE 段落较长,故压到 1
--update-freq 88梯度累积步数,等效放大批大小 8 倍(配合 2 卡即等效 16 样本/更新)
--max-epoch 55训练轮数上限

4.3 原文档附带的三个重要说明

原文档在命令之后给出了三条实战注意事项,值得原样保留并展开:

  • a) 长上下文的批大小策略:RACE 的正文段落相对较长,单样本即可占据大量 token,因此单 GPU 批大小被压到 1,同时通过提高--update-freq来凑出更大的有效批大小。required-batch-size-multiple 1也去除了 fairseq 对批大小的倍数限制,进一步配合小批训练。
  • b) 显存适配原则:上述参数组合是在单张 NVIDIA V100 32GB 上验证过的。若显存更小,可继续增大--update-freq、减小--max-sentences,二者乘积(等效批大小)保持不变即可近似复现训练效果。
  • c) 超参搜索空间说明:该设置来自一次固定搜索空间内的超参搜索(为跨模型公平比较而统一设定),换用更大的搜索空间(如不同学习率、批大小组合)可能找到更优指标,不必视为最优解。

五、原理纵深:sentence_ranking 任务与损失是怎么工作的

原文档给出了可直接复用的命令,而理解其底层实现有助于调试与二次开发。下面结合本仓库 fairseq 源码展开。

5.1 任务端:输入如何被拼接与截断

任务类SentenceRankingTask(infoxlm/fairseq/fairseq/tasks/sentence_ranking.py)定义了--num-classes--init-token--separator-token--max-option-length--truncate-sequence等全部相关参数(L38-L52)。

在 load_dataset 中,数据加载逻辑与上文预处理产物一一对应:

  • 读取input0作为上下文段落,其余input1..inputN(N=num_classes)作为候选选项(L100-L107);
  • 段落序列前前置separator_token(即</s>,token id 2),每个选项序列前前置init_token(即<s>,token id 0),随后用ConcatSentencesDataset将"选项 + 段落"拼成单条序列(L109-L118);
  • 选项先按max_option_length截断,拼接后的整条序列再按max_positions(即--max-positions 512)截断(L116-L120);
  • 4 路序列各自RightPadDataset对齐后,与label目录下读取的整数标签(RawLabelDataset)一起打包,并按随机置换顺序打乱(L123-L166)。

拼接后单条输入形如(以实际 token id 表示):<s> 题目 + 选项A </s> 段落 ...,即一个两段式的标准 RoBERTa 输入。

5.2 模型端:分类头的注册

任务在 build_model 中调用model.register_classification_head('sentence_classification_head', num_classes=1),为 RoBERTa 顶层挂上一个打分头。该头部定义于 infoxlm/fairseq/fairseq/models/roberta/model.py,由预训练权重之上的一个可训练全连接层构成,输出维度为 1(单路打分)。

5.3 损失端:四路打分与交叉熵

SentenceRankingCriterion(infoxlm/fairseq/fairseq/criterions/sentence_ranking.py)在 forward 中完成核心计算:

  1. 对 4 个候选(num_classes=4)分别调用模型,取sentence_classification_head的输出作为该选项的分数(L46-L51);
  2. 将 4 路分数沿最后一维拼接为logits(L53);
  3. F.log_softmax(logits, dim=-1)归一化后,与正确答案下标(targets)计算负对数似然损失F.nll_loss(L56-L62);
  4. 在 aggregate_logging_outputs 中统计accuracy = ncorrect / nsentences,即验证集准确率——这正是--best-checkpoint-metric accuracy所监控的指标。

由此可见,"段落 + 题目 + 选项"被整体编码后,仅靠一个线性打分头与交叉熵即可完成 4 选 1 的排序学习;整个流程与分类头的对接关系清晰,方便替换为其他候选数(如 5 选 1)的任务。

六、同源实践:把同一范式迁移到 Commonsense QA

句子排序范式不限于 RACE。本仓库 infoxlm/fairseq/examples/roberta/commonsense_qa/README.md 展示了完全同构的迁移:对每个问题构造 5 路输入(--num-classes 5),每路为"题目 + 候选答案"拼接,复用--criterion sentence_ranking,并使用--user-dir examples/roberta/commonsense_qa加载该任务实现(commonsense_qa_task.py)。

该例还给出一个值得借鉴的输入格式技巧:在题目前加Q:前缀、在答案前加A:前缀,使输入形如<s> Q: Where would I not want a fox? </s> A: hen house </s>,有助于模型区分角色。评估阶段则可复用sentence_classification_head:对每个候选调用roberta.predict('sentence_classification_head', input, return_logits=True)取分数,argmax即预测答案。这与 RACE 的"4 路打分取最大"机制完全一致,可作为 RACE 推理脚本的参考模板。

七、常见问题与调参建议

  • 显存不足(OOM):优先执行"降--max-sentences、升--update-freq",保持二者乘积稳定;其次考虑改用--arch roberta_base
  • 收敛过慢 / 指标不升:可在1e-5 ~ 3e-5范围扫描--lr;也可替换--lr-scheduler fixedpolynomial_decay(配合--warmup-updates)观察效果,但需注意与 RACE 文档的基准设定保持可比性。
  • 希望保存最优模型:务必保留--best-checkpoint-metric accuracy --maximize-best-checkpoint-metric,否则默认按验证 loss 选点。
  • 调试数据对齐:预处理后务必核对input0..input4label各文件行数一致;multiprocessing_bpe_encoder--keep-empty保证空行不被过滤,避免行号错位。

八、延伸阅读

  • RoBERTa 微调总览(含 GLUE、RACE 等公开结果)
  • GLUE 任务微调指南
  • 自定义分类任务微调指南
  • RoBERTa 预训练指南
  • Commonsense QA 同范式实践
  • WSC(WinoGrande)任务微调实践

上述所有脚本与任务/损失实现均位于infoxlm/fairseq/examples/roberta/infoxlm/fairseq/fairseq/目录下,可直接查看、复现与改造。

【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm

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

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

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

立即咨询