unilm(EdgeLM)Shuffled Word Order:词序打乱预训练 RoBERTa 在 GLUE 与 PAWS 上微调的完整实践指南
【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm
本篇指南基于 edgelm/examples/shuffled_word_order/README.finetuning.md 展开,讲解如何在 Shuffled Word Order(词序打乱)项目中,将各类打乱语料预训练得到的 RoBERTa base 模型在 GLUE 与 PAWS 任务上完成微调与评估。读完本文,你将掌握:PAWS 数据的获取与 TSV 化、复用 RoBERTa 预处理脚本的列号注意事项、一条可直接运行的fairseq-train微调命令及其全部关键参数、各打乱模型的--lr/--batch_size最优超参表,以及基于RobertaModel的推理评估代码,并理解sentence_prediction任务与损失函数在仓库源码中的实际实现。
1. 背景:微调协议与报告口径
Shuffled Word Order 研究(对应 edgelm/examples/shuffled_word_order/README.md)在 BookWiki(16GB)语料的各种词序打乱变体上预训练 RoBERTa base,验证"分布假说下词序对预训练影响之小"。配套的可下载模型包括:
roberta.base.orig:自然语料训练;roberta.base.shuffle.n1~n4:n=1~4 元(sentence 内词打乱)数据训练;roberta.base.shuffle.512:512 词块 unigram 打乱;roberta.base.shuffle.corpus/corpus_uniform:整库词 unigram 打乱(后者均匀采样);roberta.base.nopos:无位置编码、自然语料训练。
微调评估的口径在原文档中明确:对每个任务(GLUE 八个子任务与 PAWS),对每个模型单独做超参搜索,报告最佳模型在 5 个随机种子下的均值与标准差。微调流程本身与 RoBERTa 官方流程一致,区别仅在于--restore-file指向打乱预训练得到的 checkpoint(如roberta.base.shuffle.n1的model.pt)。
2. 数据准备
2.1 获取 GLUE 与 PAWS 原始数据
GLUE 各任务的原始数据获取方式与 RoBERTa 微调文档 edgelm/examples/roberta/README.glue.md 完全相同:先从 GLUE 官网下载download_glue_data.py脚本,再执行:
wget https://gist.githubusercontent.com/W4ngatang/60c2bdb54d156a41194446737ce03e2e/raw/17b8dd0d724281ed7c3b2aeeda662b92809aadd5/download_glue_data.py python download_glue_data.py --data_dir glue_data --tasks allPAWS 数据则可以直接使用 HuggingFace datasets 库的load_dataset("paws", "labeled_final")获取。为了与后续的 fairseq 预处理脚本对齐,需要把 HuggingFace 数据落成三个 TSV 文件(train.tsv、dev.tsv、test.tsv)。原文档给出的完整转换脚本如下,注意列顺序为id, sentence1, sentence2, label:
from datasets import load_dataset import pandas as pd from pathlib import Path key2file = { "paws": { "loc": "paws_data", "columns": ["id", "sentence1", "sentence2", "label"], "train": "train.tsv", "validation": "dev.tsv", "test": "test.tsv" } } task_data = load_dataset("paws", "labeled_final") task_config = key2file["paws"] save_path = Path(task_config["loc"]) save_path.mkdir(exist_ok=True, parents=True) for key, fl in task_config.items(): if key in ["loc", "columns"]: continue print(f"Reading {key}") columns = task_config["columns"] df = pd.DataFrame(task_data[key]) print(df.columns) df = df[columns] print(f"Got {len(df)} records") save_loc = save_path / fl print(f"Saving to : {save_loc}") df.to_csv(save_loc, sep="\t", header=None, index=None)这里to_csv(sep="\t", header=None, index=None)保证了输出是无表头、无索引的纯 TSV,使sentence1、sentence2、label恰好落在第 1、2、3 列(0 起为 0、1、2)——这正是下一步预处理脚本需要记住的列号。
2.2 使用 RoBERTa GLUE 预处理脚本
原文档要求:使用 RoBERTa 的 GLUE 预处理脚本完成 BPE 编码与 binarize,但要记住你所保存数据中sentence1、sentence2、label的列号(若按上面示例保存,即为 0、1、2)。
仓库中该脚本为 edgelm/examples/roberta/preprocess_GLUE_tasks.sh,调用方式为:
./examples/roberta/preprocess_GLUE_tasks.sh glue_data <glue_task_name> # <glue_task_name> ∈ {ALL, QQP, MNLI, QNLI, MRPC, RTE, STS-B, SST-2, CoLA}从脚本源码可以看到每个任务在原始 TSV 中的输入列与标签列映射,例如 RTE 为INPUT_COLUMNS=(2 3)、LABEL_COLUMN=4(1 起,即第 2、3 句与第 4 列标签),见 preprocess_GLUE_tasks.sh;PAWS 并非 GLUE 脚本内置任务,所以需要自行按其列号(0、1、2)套用同一流程:先cut出input0/input1/label三列,再经 multiprocessing_bpe_encoder.py 用 GPT-2 风格 BPE 编码,最后fairseq-preprocess --only-source生成Task-bin/input0、Task-bin/input1、Task-bin/label目录。
3. 微调命令:以 RTE 任务为例(RoBERTa large 配置)
原文档给出的完整微调命令(以打乱预训练模型替换--restore-file路径)如下,可直接复制修改后运行:
TOTAL_NUM_UPDATES=30875 # 10 epochs through RTE for bsz 16 WARMUP_UPDATES=1852 # 6 percent of the number of updates LR=2e-05 # Peak LR for polynomial LR scheduler. NUM_CLASSES=2 MAX_SENTENCES=16 # Batch size. SHUFFLED_ROBERTA_PATH=/path/to/shuffled_roberta/model.pt CUDA_VISIBLE_DEVICES=0 fairseq-train RTE-bin/ \ --restore-file $SHUFFLED_ROBERTA_PATH \ --max-positions 512 \ --batch-size $MAX_SENTENCES \ --max-tokens 4400 \ --task sentence_prediction \ --reset-optimizer --reset-dataloader --reset-meters \ --required-batch-size-multiple 1 \ --init-token 0 --separator-token 2 \ --arch roberta_large \ --criterion sentence_prediction \ --num-classes $NUM_CLASSES \ --dropout 0.1 --attention-dropout 0.1 \ --weight-decay 0.1 --optimizer adam --adam-betas "(0.9, 0.98)" --adam-eps 1e-06 \ --clip-norm 0.0 \ --lr-scheduler polynomial_decay --lr $LR --total-num-update $TOTAL_NUM_UPDATES --warmup-updates $WARMUP_UPDATES \ --fp16 --fp16-init-scale 4 --threshold-loss-scale 1 --fp16-scale-window 128 \ --max-epoch 10 \ --find-unused-parameters \ --best-checkpoint-metric accuracy --maximize-best-checkpoint-metric;关键参数说明:
| 参数 | 说明 |
|---|---|
--restore-file | 打乱预训练 checkpoint 路径(不同模型替换为对应model.pt) |
--task sentence_prediction | 句子对分类任务,见 sentence_prediction.py 实现 |
--init-token 0/--separator-token 2 | 分别为<s>与<sep>,拼接句对时插入 |
--criterion sentence_prediction/--num-classes 2 | 分类损失与类别数(RTE 为二分类) |
--lr-scheduler polynomial_decay | 多项式衰减学习率,峰值即--lr |
--fp16 --fp16-init-scale 4 --threshold-loss-scale 1 --fp16-scale-window 128 | 半精度训练配置 |
--find-unused-parameters | 预训练模型部分参数(如 LM head)在分类任务中未参与前向,需该标志 |
--best-checkpoint-metric accuracy --maximize-best-checkpoint-metric | 按验证集 accuracy 保留最佳 checkpoint |
两个由原文档明确给出的计算规则:
TOTAL_NUM_UPDATES依据--batch_size与数据集规模计算(示例中为 16 批量下遍历 RTE 训练集 10 个 epoch 的总更新步数);WARMUP_UPDATES取TOTAL_NUM_UPDATES的 6%。
源码级印证:
- 任务侧,SentencePredictionTask 在
setup_task中从RTE-bin/input0/dict.txt与RTE-bin/label/dict.txt分别加载输入词典与标签词典,并强制num_classes > 0;加载数据时,init_token会被PrependTokenDataset前置到句首,separator_token前置到第二句前,再用ConcatSentencesDataset拼成句对(sentence_prediction.py); build_model会以classification_head_name(默认sentence_classification_head)在模型上注册分类头,类别数即--num-classes(sentence_prediction.py);- 损失侧,SentencePredictionCriterion 对分类目标计算
F.nll_loss(log_softmax 后),对回归目标(如 STS-B,--regression-target)计算F.mse_loss,并在日志中统计ncorrect供--best-checkpoint-metric accuracy使用; - 标签的取值偏移在数据集中处理:标签 token id 经过
OffsetTokensDataset(offset=-label_dictionary.nspecial)偏移为普通类别下标,这正是推理代码中label + nspecial反偏移的原因,见 sentence_prediction.py。
4. 各模型最优超参:学习率与批量大小
原文档对 10 个模型(自然语料基线与 9 种打乱变体)在 8 个任务上做了搜索,报告的最佳--lr与--batch_size如下(完整继承原文档表格):
4.1 最优--lr
| name | RTE | MRPC | SST-2 | CoLA | QQP | QNLI | MNLI | PAWS | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | original | 2e-05 | 2e-05 | 1e-05 | 2e-05 | 1e-05 | 1e-05 | 1e-05 | 2e-05 |
| 1 | n_1 | 2e-05 | 1e-05 | 1e-05 | 1e-05 | 3e-05 | 1e-05 | 2e-05 | 2e-05 |
| 2 | n_2 | 2e-05 | 2e-05 | 1e-05 | 1e-05 | 2e-05 | 1e-05 | 1e-05 | 3e-05 |
| 3 | n_3 | 3e-05 | 1e-05 | 2e-05 | 2e-05 | 3e-05 | 1e-05 | 1e-05 | 2e-05 |
| 4 | n_4 | 3e-05 | 1e-05 | 2e-05 | 2e-05 | 2e-05 | 1e-05 | 1e-05 | 2e-05 |
| 5 | r512 | 1e-05 | 3e-05 | 2e-05 | 2e-05 | 3e-05 | 2e-05 | 3e-05 | 2e-05 |
| 6 | rand_corpus | 2e-05 | 1e-05 | 3e-05 | 1e-05 | 3e-05 | 3e-05 | 3e-05 | 2e-05 |
| 7 | rand_uniform | 2e-05 | 1e-05 | 3e-05 | 2e-05 | 3e-05 | 3e-05 | 3e-05 | 1e-05 |
| 8 | rand_init | 1e-05 | 1e-05 | 3e-05 | 1e-05 | 1e-05 | 1e-05 | 2e-05 | 1e-05 |
| 9 | no_pos | 1e-05 | 3e-05 | 2e-05 | 1e-05 | 1e-05 | 1e-05 | 1e-05 | 1e-05 |
4.2 最优--batch_size
| name | RTE | MRPC | SST-2 | CoLA | QQP | QNLI | MNLI | PAWS | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | orig | 16 | 16 | 32 | 16 | 16 | 32 | 32 | 16 |
| 1 | n_1 | 32 | 32 | 16 | 32 | 32 | 16 | 32 | 16 |
| 2 | n_2 | 32 | 16 | 32 | 16 | 32 | 32 | 16 | 32 |
| 3 | n_3 | 32 | 32 | 16 | 32 | 32 | 16 | 32 | 32 |
| 4 | n_4 | 32 | 16 | 32 | 16 | 32 | 32 | 32 | 32 |
| 5 | r512 | 32 | 16 | 16 | 32 | 32 | 16 | 16 | 16 |
| 6 | rand_corpus | 16 | 16 | 16 | 16 | 32 | 16 | 16 | 32 |
| 7 | rand_uniform | 16 | 32 | 16 | 16 | 32 | 16 | 16 | 16 |
| 8 | rand_init | 16 | 16 | 32 | 16 | 16 | 16 | 32 | 16 |
| 9 | no_pos | 16 | 32 | 16 | 16 | 32 | 16 | 16 | 16 |
表格中的模型名与 edgelm/examples/shuffled_word_order/README.md 中的可下载模型一一对应:original对应roberta.base.orig,n_1~n_4对应roberta.base.shuffle.n1~n4,r512对应roberta.base.shuffle.512,rand_corpus/rand_uniform对应roberta.base.shuffle.corpus/corpus_uniform,no_pos对应roberta.base.nopos(微调表中另含rand_init变体)。需要强调:这些是固定搜索空间内的建议配置,原文档亦说明若做更宽的搜索可能得到更好的指标;同时TOTAL_NUM_UPDATES要随--batch_size的取值重新计算。
5. 推理与评估:PAWS 示例
微调完成后,推理方式与 RoBERTa 一致。原文档以 PAWS dev 集为例给出完整评估代码:
from fairseq.models.roberta import RobertaModel roberta = RobertaModel.from_pretrained( 'checkpoints/', checkpoint_file='checkpoint_best.pt', data_name_or_path='PAWS-bin' ) label_fn = lambda label: roberta.task.label_dictionary.string( [label + roberta.task.label_dictionary.nspecial] ) ncorrect, nsamples = 0, 0 roberta.cuda() roberta.eval() with open('paws_data/dev.tsv') as fin: fin.readline() for index, line in enumerate(fin): tokens = line.strip().split('\t') sent1, sent2, target = tokens[0], tokens[1], tokens[2] tokens = roberta.encode(sent1, sent2) prediction = roberta.predict('sentence_classification_head', tokens).argmax().item() prediction_label = label_fn(prediction) ncorrect += int(prediction_label == target) nsamples += 1 print('| Accuracy: ', float(ncorrect)/float(nsamples))几个实现细节值得注意:
from_pretrained的data_name_or_path='PAWS-bin'用于从预处理后的 binarize 目录加载任务词典,与第 2 节脚本产出的目录结构一致;label_fn中的label + nspecial把类别下标还原为标签词典中的 token id,这与训练侧OffsetTokensDataset(offset=-nspecial)的偏移互为逆操作(sentence_prediction.py);- PAWS 的 TSV 按第 2 节约定,
sent1, sent2, target取tokens[0..2](而 GLUE 原始 TSV 的列号不同,例如 RTE 为tokens[1..3],可对照 edgelm/examples/roberta/README.glue.md 中的推理代码); roberta.encode(sent1, sent2)内部会按--init-token/--separator-token的约定组装句对 token 序列,与训练时的数据加载逻辑(sentence_prediction.py)保持一致;roberta.predict('sentence_classification_head', ...)直接调用微调时注册的分类头输出 logits,argmax 即预测类别。
无位置编码模型的注意事项:微调表中no_pos对应的roberta.base.nopos是去掉位置编码的 RoBERTa 变体,普通的RobertaModel.from_pretrained无法直接加载其权重。按 edgelm/examples/shuffled_word_order/README.md 的说明,需要构造新的RoBERTaModel对象并关闭位置编码(旧版设置use_positional_embeddings=False,新版代码中对应no_token_positional_embeddings=True,见 model.py 的参数读取),再逐层加载权重。
6. 小结与可复现要点
- 整条链路为:原始 TSV(列号 0/1/2)→ 按 RoBERTa 流程 BPE + binarize →
fairseq-train以sentence_prediction任务/损失微调 →RobertaModel推理;打乱预训练模型与自然语料 RoBERTa 的差异仅体现在--restore-file与最优超参表上。 - 所有关键实现均可在仓库内查证:任务定义与句对拼接在 edgelm/fairseq/tasks/sentence_prediction.py,分类/回归损失在 edgelm/fairseq/criterions/sentence_prediction.py,预处理脚本在 edgelm/examples/roberta/preprocess_GLUE_tasks.sh,微调与推理文档在 edgelm/examples/shuffled_word_order/README.finetuning.md 与 edgelm/examples/roberta/README.glue.md。
- 复现时的前提与限制:命令默认单卡(
CUDA_VISIBLE_DEVICES=0)、roberta_large架构与 32GB 显存量级的硬件(可按需增大--update-freq、减小--batch-size);TOTAL_NUM_UPDATES/WARMUP_UPDATES必须随数据集与批量重新计算;评估指标为 dev 集单模型、5 种子统计口径。
【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考