第 13 章 Seq2Seq:读完再写为何形成瓶颈
13.1 读几步,不一定就写几步
Section titled “13.1 读几步,不一定就写几步”第 11 章 的 RNN 每读一个 token,就更新一次状态,最后从固定的两个颜色候选中选答案。翻译要求另一种输出:一个长度和词序都可能不同于输入的序列。读三个源 token,可能需要预测四个目标 token,其中还包括一个停止标记。每次预测时,负责写出的部分应该收到什么输入?
一个简单办法是先读完再写。第一个递归网络把源序列变成最终向量,叫作 编码器(encoder);第二个递归网络使用这个向量,逐个写出目标 token,叫作 解码器(decoder)。这种安排称为 序列到序列(sequence-to-sequence,Seq2Seq)。编码器和解码器各有自己的参数;各自的参数又在各自的时间步之间共享。因此,序列长度可以变化,不必为每个位置新建一套参数。Cho 等,2014,第 2.2 节
我们将分别检查两种失败。写出时,模型可能遇到不同于训练时正确前缀的输入;而在写出之前,读取过程也可能已经丢掉了所需区别。解决其中一种,不一定就能修好另一种。
13.2 先把源序列读成一个最终向量
Section titled “13.2 先把源序列读成一个最终向量”例 13.1 — 算出编码器的每一步状态。 这里做的是规则明确的小翻译任务,不是开放式中译英。源 token 严格规定为 [红色, 钥匙, 。],目标列表为 [red, key, ., EOS]。EOS 是特殊的 序列结束标记(end-of-sequence),表示完成,不是要显示给读者的普通词。稍后会用 序列开始标记(beginning-of-sequence) BOS 启动解码器;它属于输入,不在要求输出的 token 列表中。
为了看清压缩过程,我们选一个所有计算都能列进表格的小编码器。令 。第 6 章介绍过自然对数,因此 。选这个常数,是为了让后面的注意力计算出现简单分数。所有向量都只有两个坐标,按行向量计算,没有物理单位。这是一套新的编码器权重,不沿用第 11 章逐步减半的权重。
表 13.1 — 源词表及其全部嵌入。
| ID | 源 token | 给定嵌入 |
|---|---|---|
| 0 | 红色 | [a, 0] |
| 1 | 蓝色 | [0, 0] |
| 2 | 钥匙 | [−a, a] |
| 3 | 。 | [0, −a] |
蓝色对应的零向量是特意设定的已知词嵌入,不表示未知词或填充位置。输入矩阵和递归矩阵都是二阶单位矩阵,隐藏层偏置为 [0,0],初始状态为 。所以每一步只需把输入与旧状态相加,再把负坐标截成零:
下标 数 源序列 的位置, 留给目标序列的位置。编码器计算这些状态时,不需要输出头。
表 13.2 — 用同一套权重编码红、蓝两种输入。
| 源序列读取位置 | 红色输入:旧状态 + 输入 | 红色输入:新状态 | 蓝色输入:新状态 |
|---|---|---|---|
| 1:颜色 | [0, 0] + [a, 0] | [a, 0] | [0, 0] |
| 2:钥匙 | [a, 0] + [−a, a] | [0, a] | [0, a] |
| 3:句号 | [0, a] + [0, −a] | [0, 0] | [0, 0] |
蓝色输入的第二步,相加后得到 [-a,a],经过 ReLU 变成 [0,a]。两条路径第一次在这里完全相同。读到最后的句号后,两者又都得到 [0,0]。这些状态全部由声明的输入和权重算出,不是随手放在架构图上的标签。
把最终编码状态 叫作 上下文向量(context vector)。在最简单的“读完再写”结构里,这是解码器拿到的唯一源序列表示。下图把解码器初始状态设为 。解码器可以用上一步解码状态、上一个目标 token 的嵌入,以及同一个固定上下文来更新:
是解码器的隐藏行向量, 按目标 token 选出嵌入行。 代表第 11—12 章那样的递归更新,但使用解码器自己的参数。如果解码器宽度为 ,输出候选有 个,那么 有 行、 列, 有 项。这里规定的是架构,不是说已经列出了可供手算的解码器权重。也有些版本只通过初始解码状态注入源序列摘要。两种安排都要求源信息经过这个固定摘要。Sutskever 等,2014,第 2 节
- 一组数字红色 → [a, 0]
- 一组数字钥匙 → [0, a]
- 一组数字句号 → c = [0, 0]
ReLU 递归,a = ln(2)。基线写出过程拿不到较早的状态。
- 一组数字s₀ 接收 c
- 处理步骤BOS → red → key → . → EOS
- 模型保存的数值解码器参数独立,沿时间共享
本行画的是要求的目标路径,不是实测预测轨迹。预测 EOS 后停止。
图 13.1 把源序列与目标序列各自的时钟分开。读完本例源序列要更新三次,写完含 EOS 的目标则要预测四次。“序列到序列”并不意味着每个源位置都对应一个输出。本例的源句号只是有限列表里的普通 token,由列表边界告诉编码器何时读完;其他数据格式也可以另设源序列 EOS。
13.3 把目标右移,别让答案成为它自己的输入
Section titled “13.3 把目标右移,别让答案成为它自己的输入”训练样本已经给出了目标句子。我们要用它监督预测,又不能让模型在同一位置刚拿到一个 token,就来“预测”这个 token。要预测 red,先输入 BOS;要预测 key,则输入前一个正确 token red。按这个关系排下去,直到结束标记。
表 13.3 — 训练时间线:当前输入与应预测的目标。
| 目标步 | 本步之前的正确前缀 | 当前解码器输入 | 应预测的 token |
|---|---|---|---|
| 1 | 空 | BOS | red |
| 2 | red | red | key |
| 3 | red key | key | . |
| 4 | red key . | . | EOS |
这就是 目标右移(target shifting):在输入侧前面加上 BOS,让每个前置 token 与下一个目标对齐。EOS 是要预测的目标,本例不再对它后面的位置评分。因此有四个监督位置,不是三个,也不是五个。
训练时提供正确的前一个 token,叫作 教师强制(teacher forcing)。“教师”指数据集里的目标,不是有人在每次运行时现场干预。即使模型当前最可能的预测是错的,下一步输入也仍由正确目标决定。对于递归解码器,即使所有这些输入 token 都已知,也不能同时算出所有隐藏状态,因为每个状态仍依赖前一个状态。
例 13.2 — 一份可以完整检查的条件概率记录。 为了不训练翻译模型也能复现训练与生成的差异,我们给出下面这张条件概率表,源上下文保持固定。这个诊断中的解码器只按前一个 token 选行。一般的递归解码器可以通过状态依赖整个前缀,这张小表有意简化了这种依赖。表中概率是作者设定值,不是从例 13.1 的编码器算出的,也不是训练好的解码器的实测输出。
表 13.4 — 诊断中全部可能用到的条件概率行。 候选顺序为 [red, blue, key, door, ., EOS],每行之和均为一。每次选择最大项,若有平局则选排在前面的候选。
| 前一个 token | red | blue | key | door | . | EOS |
|---|---|---|---|---|---|---|
| BOS | 0.4 | 0.5 | 0.1 | 0 | 0 | 0 |
| red | 0 | 0 | 0.8 | 0.1 | 0.1 | 0 |
| blue | 0 | 0 | 0.2 | 0.7 | 0.1 | 0 |
| key | 0 | 0 | 0 | 0 | 0.9 | 0.1 |
| door | 0 | 0 | 0 | 0 | 0.9 | 0.1 |
| . | 0 | 0 | 0 | 0 | 0 | 1 |
表中恰好为零的概率属于给定的查表分布;有限且没有遮盖的 Softmax logits 会产生严格大于零的概率。选到 EOS 后就停止,因此不需要 EOS 作为输入的那一行。如果始终没选到 EOS,配套程序会在预设的八次预测上限处停止,并明确报告是触及上限,而不是遇到 EOS。
使用教师强制时,四个正确目标的概率分别为 0.4、0.8、0.9、1。第一行虽然更偏向 blue,第二步训练输入却仍是 red。要评价整条参考序列,就像第 4 章那样,沿正确前缀把条件概率相乘:
按第 6 章的自然对数损失,总损失为 nat,平均损失为 nat/token。分母取四,是因为我们声明的目标序列包含 EOS。仅凭这一条给定序列上的较小平均损失,不能证明自由生成会正确;它甚至在第一步就把最高概率分给了错误 token。
13.4 生成时,把自己的选择送进下一步
Section titled “13.4 生成时,把自己的选择送进下一步”生成时,源序列可用,正确目标却不可用。从 BOS 开始,先选一个 token,再把 自己选中的 token 作为下一步输入。这叫作 自回归生成(autoregressive generation):每次选择都成为前缀的一部分,影响后面的选择。第 4 章延长序列时已经用过这种依赖,这里则发生在以源序列为条件的解码器中。
表 13.5 — 严格使用表 13.4 的生成时间线。
| 步骤 | 当前输入 | 最大概率 | 选中输出 | 随后的操作 |
|---|---|---|---|---|
| 1 | BOS | 0.5 | blue | 输入 blue |
| 2 | blue | 0.7 | door | 输入 door |
| 3 | door | 0.9 | . | 输入 . |
| 4 | . | 1 | EOS | 停止 |
参考列表为 red key . EOS,实际按规则生成的列表为 blue door . EOS。第二步训练用的是 red 行,偏向 key;生成用的是 blue 行,偏向 door。两次运行遵循同一张表,输入之所以分开,是因为只有训练过程拿到了正确前缀。
- 文本片段1:BOS → red(概率 0.4)
- 文本片段2:red → key(概率 0.8)
- 文本片段3:key → .(概率 0.9)
- 文本片段4:. → EOS(概率 1)
第 1 步虽然偏向 blue,第 2 步仍由目标提供正确的 red。
- 文本片段1:BOS → blue(概率 0.5)
- 文本片段2:blue → door(概率 0.7)
- 文本片段3:door → .(概率 0.9)
- 文本片段4:. → EOS(概率 1)
把选中的 token 作为下一步输入;本条记录中的句号和 EOS 仍然正确。
图 13.2 标清了第一个分歧:错误预测发生在第 1 步,由此造成的 下一步输入不同 则发生在第 2 步。训练接触正确前缀,运行却接触模型自己产生的前缀,由此形成的分布差异常被称为 暴露偏差(exposure bias)。这个名称指出解码器可能遇到的上下文差异,却不能单独解释第一次预测为什么会错。S. Bengio 等,2015,第 1—2 节
这条记录展示了一种早期错误改变后续输入和输出的方式。它没有测量真实翻译模型多常遇到这种情况,也没有证明错误必然越积越多,更不能证明教师强制无效。本例的句号和 EOS 就恢复正确了。如果 blue 行改为强烈偏向 key,首个颜色错误也可能只影响颜色,不再带来名词错误。是否调整训练方法,需要在留出的生成样例上评价,不能仅凭这张人为构造的表决定。
13.5 唯一的源表示丢掉了区别,解码器就无从找回
Section titled “13.5 唯一的源表示丢掉了区别,解码器就无从找回”教师强制与压缩是两回事。即使每一步都提供正确目标前缀,表 13.2 中红、蓝两条源序列的最终向量仍完全相同。第一个目标步收到相同的 BOS 和相同的上下文,参数固定的确定性解码器就只能给出相同分布,无法可靠地为两条输入分别给出不同颜色。采样虽然可能产生不同字符串,但分布不随源颜色改变,并没有恢复丢失的区别。
这个例子有意把问题放大:小编码器只读两步就丢掉颜色。它证明的是 这套权重 下发生了碰撞,不是说每个 Seq2Seq 编码器都会丢掉颜色,或每个长句都会失败。更好的权重、更多坐标以及门控,都可能保留更多有用区别。
不过,固定向量结构确实给编码器提出了一项要求:在写出第一个输出之前,就要把以后可能需要的源信息,都变成能通过一个向量取用的内容。源序列更长,可能包含更多彼此独立的重要细节。固定数量、有限精度的坐标只能保存有限种数值组合,无法为任意长的所有可能输入都分配不同组合。这个计数观察不能预测训练好的模型会从哪里开始失败;任务也许只需要简短摘要,不需要保存完整输入。它也不是仅凭向量维度,就证明无限精度理想实数下的不可能性。实际问题是:能否表示任务需要的区别,并学会从表示中取出它们。
LSTM 单元可以改善信息传递路径,却不会让一个摘要变成无限大的存储。反过来,在实验中把正确前缀换成生成前缀,也不会改变编码器那两个相等的向量。提出修复办法前,应先指出究竟是哪条信息路径出了问题。
编码器在最终压缩之前,曾经算出过有用的中间状态。能否让解码器继续访问它们,并在每个输出位置取用不同的组合?这会改变源信息通向输出的路径,而不只是增大固定向量。
13.6 把轨迹分析做成可复核的实践
Section titled “13.6 把轨迹分析做成可复核的实践”data/part-iii/sequence-v1.json 保存了源词表、编码器权重、ln(2) 的精确系数、完整条件概率表、候选顺序、右移目标和停止规则。code/part-iii/run.py 计算两条编码路径,并分别走完两条解码时间线。第二个 Notebook 位于 notebooks/part-iii/02-seq2seq-attention.ipynb,会把这条记录延续到第 14 章。编程只要求完成 Python 入门;直接阅读和分析表格,也是一条完整的实践路径。
本章的实践验收不是“训练出了翻译模型”,而是复现或完整分析一次序列失败:找出第一次预测错误、第一次输入变化,以及编码器第一次状态碰撞,并解释为什么它们是不同事件。表 13.2—13.5 已经给出可核查答案。保存的程序运行记录验证了这个诊断在 CPU 上的行为,并不声称测量过训练时间、翻译质量或泛化能力。
13.7 本章小结
Section titled “13.7 本章小结”编码器可以把一个序列读成上下文,解码器再据此写出另一个序列。目标右移让前一个 token 与下一个监督目标对齐,并明确开始、结束边界。教师强制提供正确的前一个 token,自回归生成提供模型自己选出的 token;两者造成的前缀差异可能影响后续预测。
固定的源向量带来第二个问题:解码还没开始,不同源序列就可能已经无法区分。第 14 章 保留递归编码器和解码器,但允许每个目标步读取一组编码器状态。我们会亲手算出,这条新增路径到底返回了什么。
13.8 习题
Section titled “13.8 习题”- 换一条目标再右移。 目标为
[blue, door, ., EOS]。列出教师强制下的全部“解码输入—目标”对。BOS 贡献目标损失吗?EOS 呢? - 区分预测与反馈。 表 13.4 中,为什么第一次预测错了,训练的第二步输入却没有变成 blue?指出生成过程从哪一步开始出现输入分歧。
- 改变错误的后续影响。 只把 blue 行换成
[0,0,0.8,0.1,0.1,0],求完整的贪心输出。这支持关于错误传播的什么结论,又不支持什么结论? - 定位碰撞。 写出蓝色编码路径在读到钥匙时,激活前与经过 ReLU 后的状态。红、蓝路径在哪次读取后相同?完美的教师强制能让它们的第一个颜色预测不同吗?
- 解释分数。 仍评价原参考序列,但不计 EOS,计算平均目标损失。生成规则不变的情况下,损失计算中的哪些部分变了?
13.9 参考解答
Section titled “13.9 参考解答”- 依次为 BOS→blue、blue→door、door→句号、句号→EOS。BOS 只作输入,没有目标损失;EOS 是目标,要计入损失。共有四次评分预测。
- 教师强制从参考序列决定下一步输入,不采用模型当前概率最高的输出,所以训练第二步仍输入 red。生成则把第 1 步选出的 blue 送入第 2 步,这是输入首次分歧的位置;首次预测错误发生在前一步。
- 新输出为
blue key . EOS。颜色仍错,名词却正确了,说明早期错误不必导致后面每个输出都错。但只改一条诊断概率行,不能证明暴露偏差普遍无害。 - 蓝色首个嵌入为零,读到钥匙时激活前为
[-a,a],经过 ReLU 得到[0,a],与红色路径在第 2 次读取后的状态相同。最终上下文也相同。在第一个目标步,两边都有 BOS 和相同上下文;教师强制此时还没有提供任何先前的颜色 token 来区分它们。 - 去掉 EOS,就去掉一项 ,因此总损失的数值仍为 1.2447947988461912,但求和定义少了一项。改为除以三,得到约 0.414932 nat/token。评价事件定义不同的分数,不能当作同一种平均值直接比较;这里没有改变生成规则。
13.10 参考文献
Section titled “13.10 参考文献”- Cho 等,Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation(2014)。第 2.2 节定义以源序列为条件的解码,以及不同的输入、输出长度;论文实验与本章给定轨迹并非同一实验。
- Sutskever、Vinyals 和 Le,Sequence to Sequence Learning with Neural Networks,arXiv 第 3 版(2014)。第 2 节说明最终源表示、独立的两套 LSTM、目标概率分解与 EOS。论文中长句的成功结果也提醒我们,不应把小例子的碰撞说成普遍失败。
- S. Bengio 等,Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks,arXiv 第 3 版(2015)。第 1—2 节说明训练与生成所用前缀的差异;本章不据此推荐某种训练修补方案,也不沿用其测量结果。
- Bahdanau、Cho 和 Bengio,Neural Machine Translation by Jointly Learning to Align and Translate(2015)。第 2—3 节讨论固定摘要的负担,并提出访问多个编码器表示的思路,下一章继续追踪这条路径。