介绍
XLNet 是一种基于新颖的广义排列语言建模目标的新型无监督语言表示学习方法。此外,XLNet 使用 Transformer-XL 作为主干模型,在涉及长上下文的语言任务中表现出色。总体而言,XLNet 在各种下游语言任务中(包括问答、自然语言推理、情感分析和文档排序)取得了最新的(SOTA)成果。
有关技术细节和实验结果的详细说明,请参阅我们的论文:
XLNet: Generalized Autoregressive Pretraining for Language Understanding
Zhilin Yang*, Zihang Dai*, Yiming Yang, Jaime Carbonell, Ruslan Salakhutdinov, Quoc V. Le
(*: 贡献相同)
预印本 2019年
发布说明
- 2019年7月16日:XLNet-Base。
- 2019年6月19日:XLNet-Large 和代码的初次发布。
结果
截至 2019年6月19日,XLNet 在20项任务上优于BERT,并在18项任务上取得了最新的成果。以下是 XLNet-Large 和 BERT-Large 的一些比较,它们的模型大小相似:
阅读理解的结果
模型 | RACE 准确率 | SQuAD1.1 EM | SQuAD2.0 EM |
---|---|---|---|
BERT-Large | 72.0 | 84.1 | 78.98 |
XLNet-Base | 80.18 | ||
XLNet-Large | 81.75 | 88.95 | 86.12 |
我们在表中使用 SQuAD 开发结果来排除使用其他训练数据或数据增强技术等因素。测试数据请参见 SQuAD 排行榜。
文本分类的结果
模型 | IMDB | Yelp-2 | Yelp-5 | DBpedia | Amazon-2 | Amazon-5 |
---|---|---|---|---|---|---|
BERT-Large | 4.51 | 1.89 | 29.32 | 0.64 | 2.63 | 34.17 |
XLNet-Large | 3.79 | 1.55 | 27.80 | 0.62 | 2.40 | 32.26 |
以上数字为错误率。
GLUE 的结果
模型 | MNLI | QNLI | QQP | RTE | SST-2 | MRPC | CoLA | STS-B |
---|---|---|---|---|---|---|---|---|
BERT-Large | 86.6 | 92.3 | 91.3 | 70.4 | 93.2 | 88.0 | 60.6 | 90.0 |
XLNet-Base | 86.8 | 91.7 | 91.4 | 74.0 | 94.7 | 88.2 | 60.2 | 89.5 |
XLNet-Large | 89.8 | 93.9 | 91.8 | 83.8 | 95.6 | 89.2 | 63.6 | 91.8 |
我们在表中使用单任务开发结果来排除多任务学习或使用集成方法等因素。
预训练模型
已发布的模型
截至 2019年7月16日,以下模型已经发布:
XLNet-Large, Cased
:24层,1024隐藏单元,16头XLNet-Base, Cased
:12层,768隐藏单元,12头。此模型在完整数据上进行了训练(与论文中的不同)。
我们目前仅发布了有区分大小写的模型,因为在我们考虑的任务中,我们发现:(1)对于基本设置,有区分大小写和无区分大小写的模型性能相似;(2)对于大模型设置,有区分大小写的模型在某些任务上略胜一筹。
每个 .zip 文件包含三项内容:
- 包含预训练权重的 TensorFlow 检查点 (
xlnet_model.ckpt
)(实际上是三个文件)。 - 用于(解)标记化的 Sentence Piece 模型 (
spiece.model
)。 - 指定模型超参数的配置文件 (
xlnet_config.json
)。
未来发布计划
我们还计划继续发布在不同设置下的更多预训练模型,包括:
- 在 Wikipedia 上微调的预训练模型。这可以用于像 SQuAD 和 HotpotQA 这样的维基百科文本任务。
- 具有其他超参数配置的预训练模型,针对特定的下游任务。
- 受益于新技术的预训练模型。
订阅 Google Groups 上的 XLNet
要接收有关更新、公告和新发布的通知,我们建议订阅 Google Groups 上的 XLNet:Google Groups。
使用 XLNet 进行微调
截至 2019年6月19日,此代码库已在 Python2 下使用 TensorFlow 1.13.1 进行了测试。
微调期间的内存问题
- 我们论文中的大多数 SOTA 结果都是在 RAM 比较大的 TPU 上产生的。因此,目前使用 12GB - 16GB RAM 的 GPU 很难(成本较高)重现论文中
XLNet-Large
的大多数 SOTA 结果,因为 16GB 的 GPU 只能容纳 一个长度为 512 的单序列 用于XLNet-Large
。因此,需要大量 GPU(范围从 32 到 128,即batch_size
)来重现论文中的许多结果。 - 我们正在尝试使用梯度累积来可能减轻内存负担,这可能会在不久的将来更新中包含。
- 替代方法 在 renatoviolin's repo 中展示了在 受限硬件 上微调 XLNet 的方法,该方法在具有 8GB 内存的 GPU 上获得了 86.24 F1 的 SQuAD2.0 分数。
鉴于上述内存问题,使用默认微调脚本 (run_classifier.py
和 run_squad.py
),我们在单个 16GB GPU 上使用 TensorFlow 1.13.1 对最大批量大小进行了基准测试:
系统 | 序列长度 | 最大批量大小 |
---|---|---|
XLNet-Base | 64 | 120 |
... | 128 | 56 |
... | 256 | 24 |
... | 512 | 8 |
XLNet-Large | 64 | 16 |
... | 128 | 8 |
... | 256 | 2 |
... | 512 | 1 |
在大多数情况下,可以减小批量大小 train_batch_size
或最大序列长度 max_seq_length
以适应给定的硬件。性能的下降取决于任务和可用资源。
文本分类/回归
用于执行分类/回归微调的代码在 run_classifier.py
中。它还包含了标准单文档分类、单文档回归和文档对分类的示例。这里,我们提供了 run_classifier.py
的两个具体使用例子。
从这里开始,我们假设 XLNet-Large 和 XLNet-Base 已分别下载到 $LARGE_DIR
和 $BASE_DIR
。
(1) STS-B: 句子对相关性回归(使用 GPU)
-
使用 4 个 V100 GPU 进行 多 GPU 微调 XLNet-Large,运行以下命令
CUDA_VISIBLE_DEVICES=0,1,2,3 python run_classifier.py \ --do_train=True \ --do_eval=False \ --task
-
使用一个 TPU v3-8 进行训练时,可以在设置好 TPU 和 Google 存储后,直接运行脚本
scripts/tpu_squad_large.sh
。 -
run_squad.py
将自动在 SQuAD 的开发集上进行阈值搜索并输出分数。使用scripts/tpu_squad_large.sh
,预期的 F1 分数应该在 88.6 左右(多次运行的中位数)。
另外,也可以使用 GPU(如三块 V100)搭配 XLNet-Base。一个合理的超参数设置可以在脚本 scripts/gpu_squad_base.sh
中找到。
RACE 阅读理解
用于阅读理解任务 RACE 的代码包含在 run_race.py
中。
- 值得注意的是,RACE 中篇章的平均长度超过 300 个 token(而非 peices),这比其他流行的阅读理解数据集(如 SQuAD)明显更长。
- 此外,许多问题可能非常困难,机器需要复杂的推理才能解决(参见此处的示例)。
运行代码步骤:
(1) 从官方网站下载 RACE 数据集,并将原始数据解压到 $RACE_DIR
。
(2) 进行训练和评估:
- RACE 的 SOTA 性能(准确率 81.75)是使用 XLNet-Large、序列长度 512 和批量大小 32 在 pod 设置中使用大 TPU v3-32 产生的。请参阅脚本
script/tpu_race_large_bsz32.sh
获取此设置。 - 使用 TPU v3-8 搭配 XLNet-Large、序列长度 512 和批量大小 8 可以得到大约 80.3 的准确率(见
script/tpu_race_large_bsz8.sh
)。
使用 Google Colab
我们提供了一个使用 Google Colab 搭配 GPU 的示例。需要注意的是,由于示例中的硬件资源有限,结果会比最佳结果差。这主要作为一个示例,应该根据需要进行修改以最大化性能。
XLNet 的自定义使用
XLNet 抽象
对于微调,您可能需要修改现有文件(如 run_classifier.py
、run_squad.py
和 run_race.py
)来完成手头的任务。不过,我们还提供了 XLNet 的抽象,以便更灵活地使用。以下是一个示例:
import xlnet
# 此处省略了一些代码...
# 初始化 FLAGS
# 初始化 tf.Tensor 实例,包括 input_ids、seg_ids 和 input_mask
# XLNetConfig 包含特定于模型检查点的超参数。
xlnet_config = xlnet.XLNetConfig(json_path=FLAGS.model_config_path)
# RunConfig 包含可能在预训练和微调之间不同的超参数。
run_config = xlnet.create_run_config(is_training=True, is_finetune=True, FLAGS=FLAGS)
# 构建一个 XLNet 模型
xlnet_model = xlnet.XLNetModel(
xlnet_config=xlnet_config,
run_config=run_config,
input_ids=input_ids,
seg_ids=seg_ids,
input_mask=input_mask)
# 使用最后一个隐藏状态获取序列摘要
summary = xlnet_model.get_pooled_out(summary_type="last")
# 获取序列输出
seq_out = xlnet_model.get_sequence_output()
# 基于 `summary` 或 `seq_out` 构建您的应用程序
分词
以下是 XLNet 中进行分词的一个示例:
import sentencepiece as spm
from prepro_utils import preprocess_text, encode_ids
# 此处省略了一些代码...
# 初始化 FLAGS
text = "An input text string."
sp_model = spm.SentencePieceProcessor()
sp_model.Load(FLAGS.spiece_model_file)
text = preprocess_text(text, lower=FLAGS.uncased)
ids = encode_ids(sp_model, text)
其中,FLAGS.spiece_model_file
是与预训练模型在同一压缩包中的 SentencePiece 模型文件,FLAGS.uncased
是一个布尔值,用于指示是否进行大小写转换。
使用 XLNet 进行预训练
请参考 train.py
在 TPU 上进行预训练,参考 train_gpu.py
在 GPU 上进行预训练。首先,我们需要将文本数据预处理为 tfrecords 格式。
python data_utils.py \
--bsz_per_host=32 \
--num_core_per_host=16 \
--seq_len=512 \
--reuse_len=256 \
--input_glob=*.txt \
--save_dir=${SAVE_DIR} \
--num_passes=20 \
--bi_data=True \
--sp_path=spiece.model \
--mask_alpha=6 \
--mask_beta=1 \
--num_predict=85
其中,input_glob
定义了所有输入的文本文件,save_dir
是 tfrecords 的输出目录,sp_path
是一个 SentencePiece 模型。以下是我们训练 SentencePiece 模型的脚本
spm_train \
--input=$INPUT \
--model_prefix=sp10m.cased.v3 \
--vocab_size=32000 \
--character_coverage=0.99995 \
--model_type=unigram \
--control_symbols=<cls>,<sep>,<pad>,<mask>,<eod> \
--user_defined_symbols=<eop>,.,(,),",-,–,£,€ \
--shuffle_input_sentence \
--input_sentence_size=10000000
使用了特殊符号,包括 control_symbols
和 user_defined_symbols
。我们使用 <eop>
和 <eod>
来表示段落结束和文档结束。
data_utils.py
的输入文本文件必须使用以下格式:
- 每行是一句句子。
- 空行表示文档结束。
- (可选)如果还想要建模段落结构,可以在某些行的末尾(不加空格)插入
<eop>
以指示相应的句子结束段落。
例如,文本输入文件可以是:
This is the first sentence.
This is the second sentence and also the end of the paragraph.<eop>
Another paragraph.
Another document starts here.
预处理完成后,我们就可以开始预训练 XLNet 了。以下是用于预训练 XLNet-Large 的超参数:
python train.py
--record_info_dir=$DATA/tfrecords \
--train_batch_size=2048 \
--seq_len=512 \
--reuse_len=256 \
--mem_len=384 \
--perm_size=256 \
--n_layer=24 \
--d_model=1024 \
--d_embed=1024 \
--n_head=16 \
--d_head=64 \
--d_inner=4096 \
--untie_r=True \
--mask_alpha=6 \
--mask_beta=1 \
--num_predict=85
其中我们只列出了最重要的参数,其他参数可以根据具体的使用场景进行调整。