T5:Exploring the limits of Transfer Learning with a Unified Text-to-Text Transformer

论文:[1910.10683] Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer

Code:google-research/text-to-text-transfer-transformer: Code for the paper “Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer”

一句话概述:把所有 NLP 任务统一成 Text-to-Text 格式使用 Transformer 统一处理。

摘要:迁移学习在 NLP 领域已经是最有效的方法,本文引入了统一的文本处理框架——将所有文本问题统一成 Text-to-Text 的格式。为了验证效果,构建了 C4 数据集(Colossal Clean Crawled Cropus),结果发现取得了很好的效果。

初衷

首先,我们看统一的样式,拿 Paper 里的一张图来说明,几种不同的任务被统一成如下格式:

从直觉上看其实思想很简单,Encoder 把任务和 X 放进去,Decoder 生成对应的 Y。X 部分基本没啥说的,一般就是正常的自然语言文本;Y 的形式比较多样,毕竟 NLP 有那么多种任务,但总的来说还是可以分为两大类:分类(包括回归)和生成。分类就让 Decoder 生成对应的 Label,生成(摘要、翻译等)则让 Decoder 像正常一样生成文本即可。总的来说这个想法是比较 Make Sense 的。

那么,为什么要这样做呢?最直观的原因就是:简单、省事。由于 NLP 已经步入了预训练时代,衡量不同预训练模型、不同的预训练目标、不同的数据集等等就变得非常容易。不同的任务不仅不会成为一个变量,反而会成为衡量这些因素的一个很好的标准——效果越好,在不同任务上表现就应当越好,这比单一任务有说服力,还不用那么麻烦地一个个处理。我想这应该来自于 GPT-3 这样 NLG 大模型的 Zero-Shot 和 Few-Shot 能力,结合 Bert 优秀的 NLU 能力,喜欢「偷懒」的程序员们应该老早就想怎么避免单个处理任务,这才有了这篇规模浩大的「实验报告」。而且,论文还提到说目的不是提出一个新方法,而是提供一个 NLP 领域全面综合的视角。从他们的过程和结果来看,的确做到了这点。

We emphasize that our goal is not to propose new methods but instead to provide a comprehensive perspective on where the field stands.

配置

这部分有几个要点得明确:模型、数据集、下游任务和输入输出格式。这些都是基本设置,有了这些才能开始接下去的实验。值得一提的是,无论是开始的 Introduction 还是之后的部分,论文对历史发展介绍都比较全面,不光是在做实验,同时也是在综述领域演变,而且很细。确实是在提供更全面综合的视角。

模型

这部分详细介绍了 Transformer 架构,以及它的由来和演变,本文采用的模型架构也是 Transformer,不过有几个地方调整:

  • 移除了 Layer Norm 的偏置项。
  • 将层归一化放在残差连接路径之外。
  • 位置编码。

归一化的具体代码如下:

1
2
3
4
5
6
7
8
9
10
##### Layer Norm #####
# Transformer,代码来自 Tensorflow 官方文档
attn_output, _ = self.mha(x, x, x, mask) # (batch_size, input_seq_len, d_model)
attn_output = self.dropout(attn_output, training=training)
out = self.layernorm(x + attn_output) # (batch_size, input_seq_len, d_model)
# T5
x = self.layernorm(x)
attn_output, _ = self.mha(x, x, x, mask)
attn_output = self.dropout(attn_output, training=training)
out = x + attn_output

T5 这个也是 OpenNMT 的做法,具体可以看这篇 Paper:https://arxiv.org/pdf/2002.04745.pdf

位置编码:

1
2
3
4
5
6
7
8
9
10
11
12
# 代码来自 Transformers
context_position = tf.range(query_length)[:, None]
memory_position = tf.range(key_length)[None, :]
relative_position = memory_position - context_position # shape (query_length, key_length)
relative_position_bucket = self._relative_position_bucket(
relative_position,
bidirectional=(not self.is_decoder),
num_buckets=self.relative_attention_num_buckets,
)
# relative_attention_bias 是初始化的参数
values = tf.gather(self.relative_attention_bias, relative_position_bucket)
values = tf.expand_dims(tf.transpose(values, [2, 0, 1]), axis=0)

感兴趣的可以阅读 Transformer 中 T5 的源代码。另外需要提醒的是,T5 仓库中并没有模型代码,模型用的是 mesh 里面的,Mesh 是一个分布式计算平台。

数据集

为了这个任务专门搞了个大数据集,确实到位。数据集是从网上抓取的,有以下特点:

  • 原始来源是 2019 年 4 月的数据。
  • Common Crawl 每个月产生 20 T 数据集。
  • 只保留了英文数据。
  • 预处理后得到 750G。

预处理策略如下:

  • 只保留结束标点结束的行。
  • 丢弃少于 5 个句子的页面,只保留至少包含 3 个单词的行。
  • 删除了包含不良词表中任意词的页面。
  • 删除了带有 JavaScript 的行。
  • 删除了包含「lorem ipsum」占位符的页面。
  • 删除了包含大括号的页面。
  • 丢弃了数据集中不止一次出现的任何三句跨度中的一个。

任务集

主要包括:

  • 机器翻译:WMT English 翻 German, French 和 Romanian
  • QA:SQuAD
  • 摘要:CNN/Daily Mail
  • 分类:GLUE、SuperGLUE

输入输出

主要就是在原始句子前面添加一个任务前缀,就像前面的图 1,具体可以参考附录 D,不再赘述。

实验

接下来就是本文的重头了,整整 30 页的实验报告,我们重点关注要验证什么,以及结果如何。大的方面主要包括以下几个:

  • 架构
  • 训练目标
  • 数据集
  • 迁移策略
  • 缩放

基准

Model

和 BertBase 同大小:

  • 12 个 block
  • FFN 3072 维
  • ReLU
  • AttnHeadDim 64
  • HeadsNum 12
  • HiddenDim 768
  • 220 million 参数(110 × 2)
  • Dropout 0.1

Training

  • Teacher forcing
  • 交叉熵损失
  • AdaFactor 优化器
  • Greedy 解码
  • C4 上预训练 2^19=524288 步
  • MaxSeqLen=512
  • BatchSize=128
  • 共 2^35=34B Tokens
  • 「inverse square root」 Learning Rate Schedule:1/sqrt(max(n,k)),n 是当前训练的 iteration,k 是 warm-up steps(10^4),即前 10^4 步学习率保持为 0.01,然后指数衰减直到预训练结束。
  • 微调 2^18 步,BatchSize=128,MaxSeqLen=512,学习率固定 0.01,每 5000 步根据最佳验证集结果保存。值得注意的是,每个任务都是单独选择最佳的 checkpoint。

词表

  • 32000 词条
  • 包含非英文的其他语种(德、法、罗马)
  • 输入输出共享词表

预训练目标

  • Denoising 目标函数(即 MLM)
  • 随机采样,丢掉 15% 的 Token,连续的 span 会被替换为特殊的符号
  • 预测替换位置的 Token

表现

五角星是 Baseline,加粗的表示相差 2 个标准差之内的。

架构

Encoder-Decoder 参数量虽然相比单独的 Encoder 或 Decoder 增加了一倍,但计算量却差不多。这是因为 Encoder 仅应用于输入序列,Decoder 仅应用于输出序列,语言模型(BERT)中的 L 层必须同时应用于输入和输出序列。

对比实验包括(Text-to-Text 格式可以使用任一种架构):

  • Encoder-Decoder 模型,各 L 层,共 2P 参数,M FLOPS
  • 模型同上,L 层,参数共享,共 P 参数,M FLOPS
  • 模型同上,各 L/2 层,共 P 参数,M/2 FLOPS
  • Decoder,L 层,P 参数,M FLOPS
  • Decoder PrefixLM + 输入完全可见自注意力

无监督目标

不同的训练目标(重要):

注意,PrefixLM 是从中间随机选个位置切开的,BERT 15%(90% Mask,10% 随机替换)。

效果如下:

BERT 风格变种的效果如下:

注意后两种,不仅效果可以,而且由于 target 更短,训练更快。

然后是不同损坏比例的效果:

最后是不同 Span 的长度:

长度 2-3 效果都可以,另外 Span 也有加速训练效果。需要注意的是,15% 是所有损坏的 Token 数(而不是 Span 数)。

预训练数据集

好家伙,连数据集的效果都要测一下,效果如下:

得出一个结论:领域数据集上预训练能够提升下有效果(为啥感觉像是句废话……)。

接下来是数据量大小的实验:

结论如下:

  • 随着预训练数据集缩小,模型的训练损失明显更小,这表明可能存在记忆。(可不就是疯狂复制的锅)
  • 较大的模型可能更容易过度拟合到较小的预训练数据集。

训练策略

首先是不同的微调方法:

  • 适配层:在 Transformer 每个 Block 的前馈网络后添加的 dense-ReLU-dense Block,微调时,仅仅适配层和层归一化参数调整。
  • 逐层解冻:微调时,从最后一层开始,随着训练的继续,之前的层逐步解开,直到所有参数都可以微调更新。具体是每隔 2^18/12 步微调一个额外的层。

结论:低资源任务(SQuAD)在小 d 时效果也可以;高资源任务则需要更大的 d。说明只要将维度适当地缩放到任务大小,就可以在较少的参数上进行微调。

接下来是多任务数据混合策略,注意,虽然是多任务训练,但是评估表现时,不同的任务会选择不同的 checkpoint。

  • Examples-proportional 混合:给定任务 n 的样本数量为 en,训练时从第 m 个任务采样的概率 rm=min(em, K)/Σmin(en, K),K 是人工数据集大小限制。
  • Temperature-scaled 混合:给定 temperature T,每个任务的混合率 rm 提高到 1/T 次幂并重新归一化。T=1 时就是上面的混合方法。
  • Equal 混合:从每个任务中等概率采样。具体来说,每个批次中的每个样本都是均匀随机地从一个数据集上采样的。

训练步骤都是 2^19 + 2^18 = 786,432,结果显示还是预训练+微调效果好,Equal 的效果最差,猜测是低资源任务过拟合,高资源任务又数据不足。

最后看看多任务学习+微调策略。

注意,多任务预训练+微调采用 K=2^19,结果显示:

  • 多任务预训练+微调效果很不错。
  • Leave-one 只是稍微差了些,说明多任务上训练的模型可以适配新任务。
  • 多任务预训练在翻译任务上效果没那么差,说明翻译不太依赖预训练,无监督预训练在其他任务起了非常重要的作用。

总而言之,对于普通任务(数据量没那么大)无监督的预训练很有用;多任务预训练是有一定的 Zero-Shot 潜力的。

缩放

主要指更大的模型、更多的训练步骤和更大的 BatchSize。

基本上,大模型多步骤=更好=多钱。需要注意的是集成策略时在最终输出到 SoftMax 前会将多个模型的 logits 平均。结论是:将小型模型预训练更长时间是个不错的策略。一个预训练模型+4个不同的微调版本效果还是不行,大模型时代,集成效果都要打折扣了。

汇总

意味着要把之前最好的都放一块整一个「完美」的版本出来了:

  • 目标:MLM+Span,15% 的损坏 Token,Span 平均长度 3
  • 长时间训练:1 million 步,BatchSize=2^11,序列长度 512
  • 模型尺寸:Base 220M,Small 60M,Large 770M,3B 和 11B
  • 多任务预训练:对无监督和监督任务的多任务组合进行预训练,再微调
  • 分别在 GLUE 和 SuperGLUE 单个任务上微调:BatchSize=8,序列长度 512 在每一个任务上微调
  • Beam Search:width=4,α=0.6
  • 测试集:使用测试集而不是验证集

先看一下和之前的效果对比:

看起来有不小的提升,我咋觉得全靠更多的训练步骤呢。。。

再看看和之前 SOAT 的对比:

嗯,差距还是不小,后面 T5 又搞出来个 1.1 版,具体有这么些改进:

  • 前馈层使用 GEGLU 代替 ReLU
  • 预训练时关闭 Dropout,微调时打开
  • 只在 C4 上预训练,不包含下游任务
  • Embedding 和 Classifier 不共享参数

具体见参考文献【3】。

结论

要点

  • Text-to-Text:方便地将多个任务放在一个模型
  • 架构:Enc-Dec 的 Transformer(Enc 和 Dec 可以共享参数)效果最好。是不是最符合人类直觉,先理解再说话?
  • 无监督目标:MLM,使用 Span 加速预训练
  • 数据集:C4,大数据集预训练,数据集太小(重复采样)没用
  • 训练策略:微调预训练模型的所有参数效果最好;多任务预训练+微调取得和无监督预训练+微调相当的效果
  • 缩放:更多数据的小模型比大模型少步好
  • 极限:11B,无监督预训练+多任务预训练+单任务上微调

展望

  • 大模型的不便:提倡研究以更便宜的模型实现更强性能的方法。
  • 更高效地知识抽取:怀疑这种简单化的技术(大规模预训练)可能不是教授模型通用知识的非常有效的方法。
  • 形式化任务之间的相似性:领域内未标记数据预训练可以提升下游相关任务性能,所以制定一个更严格的「预训练任务和下游任务之间的相似性」的概念是有用的,这样我们就可以对使用什么未标记的数据来源做出更有原则的选择。我理解这其实就是说 T5 自己。
  • 与语言(自然语言)无关的模型:进一步研究与语言无关的模型,即无论文本的语言如何,都可以以良好的性能执行给定 NLP 任务的模型。

T5 在模型上并没太多创新,但是这个统一各种任务的模式却是不错,诚如 paper 所言,这可以更方便地评估大模型的各种能力,可以让研究者将重心放在设计模型架构、预训练目标等方面。就这方面来说,这篇 paper 又开启了一个小时代。

参考