T5:把任务写成文本,把缺口写成 Span¶
T5 常被概括成“把所有 NLP 任务都转成 text-to-text”。这句话抓住了接口,却漏掉了论文真正严谨的部分:T5 在同一框架中系统比较架构、无监督目标、数据、迁移方式和规模,随后选择 span corruption、encoder–decoder 与任务前缀组成统一配方。预印本首次公开于 2019 年,完整版本发表于 2020 年 JMLR;原始实现与后来的 T5X 属于不同代码世代。
为什么不只做 token masking¶
BERT 的 MLM 独立选择 token 并在原位置预测,擅长双向表示,但输出接口与开放式生成不同。T5 把连续被破坏 span 从输入中删去,用 sentinel token 标记缺口,再让 decoder 自回归生成被删内容。
原序列:
若 C D 与 F 是两个 span,输入和目标变成:
sentinel 同时标记缺口身份和 target 中 span 的边界。与逐 token mask 相比,decoder 必须恢复连续内容,也不会为一个 span 在输入里留下多个重复 mask。
论文系统比较了多种 corruption 与架构后,采用约 15% noise density、平均 span 长度 3 的设置。平均 span 变长会缩短 sentinel target,却让每个缺口包含更长的局部结构;noise density 变高则同时减少 encoder 可见上下文、增加 decoder 目标。二者共同决定训练计算和任务难度,不能只记录“使用 span corruption”。
Span corruption 的最小可逆实现¶
def span_corrupt(tokens, mask, sentinel=99):
source, target, i, current = [], [], 0, sentinel
while i < len(tokens):
if not mask[i]:
source.append(tokens[i])
i += 1
continue
source.append(current)
target.append(current)
while i < len(tokens) and mask[i]:
target.append(tokens[i])
i += 1
current -= 1
target.append(current)
return source, target
def restore(source, target):
spans, i = {}, 0
while i < len(target) - 1:
key, i = target[i], i + 1
values = []
while i < len(target) and target[i] < 90:
values.append(target[i])
i += 1
spans[key] = values
output = []
for token in source:
output.extend(spans[token] if token in spans else [token])
return output
tokens = [10, 11, 12, 13, 14, 15, 16]
source, target = span_corrupt(tokens, [False, False, True, True, False, True, False])
assert source == [10, 11, 99, 14, 98, 16]
assert target == [99, 12, 13, 98, 15, 97]
assert restore(source, target) == tokens
这段代码只固定 span 合并、sentinel 顺序和 round-trip。真实 collator 还要随机采样噪声密度与 span 长度、预留 sentinel 词表、处理 EOS,并在 batch padding 上构造 encoder/decoder mask。
T5 target 最后追加新的 sentinel,标记最后一个 span 的结束。若 collator 省略它,decoder 仍可能训练,但 target grammar 已经改变。sentinel 必须是 tokenizer 中互不冲突的 special token,不能在普通文本 normalization 中被拆分。
统一的是条件生成接口¶
分类可以输出标签文本,翻译输出目标语言,摘要输出摘要,问答输出答案。所有任务共享
输入前缀如 translate English to German: 指示任务。统一接口减少专用 head 和任务代码,让不同任务可以混合训练;它并没有消除标签词选择、模板、采样比例和输出解析的影响。
输出 "entailment" 与输出类别 ID 在语义上可等价,在计算上却要经过 tokenizer 和自回归解码。分类任务若只追求延迟和严格标签约束,专用 head 仍可能更合适。
encoder–decoder 还改变了推理账本:encoder 对输入只计算一次,decoder 每步通过 cross-attention 读取 encoder memory,并维护自己的 self-attention KV。对长输入、短输出任务,这与把输入输出全部放进 decoder-only 序列有不同的计算和 cache 结构。
C4 让数据不能再退到脚注¶
T5 同时构建并使用 Colossal Clean Crawled Corpus(C4)。这使论文能够在大规模英文网页数据上系统比较目标,也提醒后来工作:架构与数据不能从结果中分离。过滤规则、重复内容和语言范围会决定模型学到什么;“C4 训练”也必须绑定具体版本和预处理。
论文的“统一比较”依赖共同数据、训练预算与评测框架。C4 的英文网页范围、清洗规则和重复内容也是实验条件;后续使用同名数据的不同快照,不能默认与原报告完全等价。
三种目标怎样分叉¶
| 路线 | 输入可见性 | 预训练输出 | 自然下游接口 |
|---|---|---|---|
| GPT | causal 左上下文 | 下一个 token | 自回归生成 |
| BERT | 被 mask 输入的双向上下文 | 被选 token | 表示与任务 head |
| T5 | 双向 encoder + causal decoder | 被删 span 序列 | text-to-text |
T5 不是把 BERT 和 GPT 机械拼在一起。encoder–decoder cross-attention、span target 与任务混合共同决定训练信号和推理成本。
从 transfer learning 到 instruction tuning¶
span corruption 成为 encoder–decoder 预训练的重要基线,text-to-text 也为 instruction tuning 提供天然接口。FLAN 在此基础上用多任务自然语言指令改善未见任务泛化。
原论文支持的是其系统比较范围内的设计选择,不意味着:
- text-to-text 对每种任务都最省计算;
- span corruption 对 decoder-only 模型可原样复用;
- 一个统一模板足以解决任务冲突;
- 更大的 T5 在任意数据和预算下都优于其他架构。
任务前缀让自然语言 instruction 成为输入接口,但 T5 原始多任务配方仍依赖预定义任务与模板。FLAN 进一步扩大 instruction mixture,才把“任务写成文本”推进到未见任务的 instruction generalization。两者应视为连续演进,不能把后来的 zero-shot 结论倒写进 T5。
预训练目标的完整分叉见同一 Transformer 的三种信息流,实现层的 mask 与序列构造见训练目标和序列构造与打包,encoder–decoder 推理状态见推理运行时。