跳转到内容

第 13 章 Seq2Seq:读完再写为何形成瓶颈

第 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 列表中。

为了看清压缩过程,我们选一个所有计算都能列进表格的小编码器。令 a=ln⁡2≈0.693147a=\ln 2\approx0.693147。第 6 章介绍过自然对数,因此 ea=2e^a=2。选这个常数,是为了让后面的注意力计算出现简单分数。所有向量都只有两个坐标,按行向量计算,没有物理单位。这是一套新的编码器权重,不沿用第 11 章逐步减半的权重。

表 13.1 — 源词表及其全部嵌入。

ID源 token给定嵌入
0红色[a, 0]
1蓝色[0, 0]
2钥匙[−a, a]
3。[0, −a]

蓝色对应的零向量是特意设定的已知词嵌入,不表示未知词或填充位置。输入矩阵和递归矩阵都是二阶单位矩阵,隐藏层偏置为 [0,0],初始状态为 h0=[0,0]h_0=[0,0]。所以每一步只需把输入与旧状态相加,再把负坐标截成零:

Wx=Wh=[1001],bh=[0,0],hj=ReLU⁡(xj+hj−1).W_x=W_h=\begin{bmatrix}1&0\\0&1\end{bmatrix},\quad b_h=[0,0],\qquad h_j=\operatorname{ReLU}(x_j+h_{j-1}).

下标 jj 数 源序列 的位置,tt 留给目标序列的位置。编码器计算这些状态时,不需要输出头。

表 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]。这些状态全部由声明的输入和权重算出,不是随手放在架构图上的标签。

把最终编码状态 c=h3c=h_3 叫作 上下文向量(context vector)。在最简单的“读完再写”结构里,这是解码器拿到的唯一源序列表示。下图把解码器初始状态设为 s0=cs_0=c。解码器可以用上一步解码状态、上一个目标 token 的嵌入,以及同一个固定上下文来更新:

st=F(st−1,Ey(yt−1),c),pt=softmax⁡(stWo+bo).s_t=F(s_{t-1},E_y(y_{t-1}),c),\qquad p_t=\operatorname{softmax}(s_tW_o+b_o).

sts_t 是解码器的隐藏行向量,EyE_y 按目标 token 选出嵌入行。FF 代表第 11—12 章那样的递归更新,但使用解码器自己的参数。如果解码器宽度为 dd,输出候选有 KK 个,那么 WoW_o 有 dd 行、KK 列,bob_o 有 KK 项。这里规定的是架构,不是说已经列出了可供手算的解码器权重。也有些版本只通过初始解码状态注入源序列摘要。两种安排都要求源信息经过这个固定摘要。Sutskever 等,2014,第 2 节

编码 j = 1、2、3
  1. 一组数字红色 → [a, 0]
  2. 一组数字钥匙 → [0, a]
  3. 一组数字句号 → c = [0, 0]

ReLU 递归,a = ln(2)。基线写出过程拿不到较早的状态。

解码 t = 1、2、3、4
  1. 一组数字s₀ 接收 c
  2. 处理步骤BOS → red → key → . → EOS
  3. 模型保存的数值解码器参数独立,沿时间共享

本行画的是要求的目标路径,不是实测预测轨迹。预测 EOS 后停止。

图 13.1 — 源序列读三步,交出一个摘要,目标序列预测四步。 本书原创教学图,数值为给定值或按规则推导值,CC BY-SA 4.0;未复制论文图示。

图 13.1 把源序列与目标序列各自的时钟分开。读完本例源序列要更新三次,写完含 EOS 的目标则要预测四次。“序列到序列”并不意味着每个源位置都对应一个输出。本例的源句号只是有限列表里的普通 token,由列表边界告诉编码器何时读完;其他数据格式也可以另设源序列 EOS。

13.3 把目标右移,别让答案成为它自己的输入

Section titled “13.3 把目标右移,别让答案成为它自己的输入”

训练样本已经给出了目标句子。我们要用它监督预测,又不能让模型在同一位置刚拿到一个 token,就来“预测”这个 token。要预测 red,先输入 BOS;要预测 key,则输入前一个正确 token red。按这个关系排下去,直到结束标记。

表 13.3 — 训练时间线:当前输入与应预测的目标。

目标步本步之前的正确前缀当前解码器输入应预测的 token
1空BOSred
2redredkey
3red keykey.
4red key ..EOS

这就是 目标右移(target shifting):在输入侧前面加上 BOS,让每个前置 token 与下一个目标对齐。EOS 是要预测的目标,本例不再对它后面的位置评分。因此有四个监督位置,不是三个,也不是五个。

训练时提供正确的前一个 token,叫作 教师强制(teacher forcing)。“教师”指数据集里的目标,不是有人在每次运行时现场干预。即使模型当前最可能的预测是错的,下一步输入也仍由正确目标决定。对于递归解码器,即使所有这些输入 token 都已知,也不能同时算出所有隐藏状态,因为每个状态仍依赖前一个状态。

例 13.2 — 一份可以完整检查的条件概率记录。 为了不训练翻译模型也能复现训练与生成的差异,我们给出下面这张条件概率表,源上下文保持固定。这个诊断中的解码器只按前一个 token 选行。一般的递归解码器可以通过状态依赖整个前缀,这张小表有意简化了这种依赖。表中概率是作者设定值,不是从例 13.1 的编码器算出的,也不是训练好的解码器的实测输出。

表 13.4 — 诊断中全部可能用到的条件概率行。 候选顺序为 [red, blue, key, door, ., EOS],每行之和均为一。每次选择最大项,若有平局则选排在前面的候选。

前一个 tokenredbluekeydoor.EOS
BOS0.40.50.1000
red000.80.10.10
blue000.20.70.10
key00000.90.1
door00000.90.1
.000001

表中恰好为零的概率属于给定的查表分布;有限且没有遮盖的 Softmax logits 会产生严格大于零的概率。选到 EOS 后就停止,因此不需要 EOS 作为输入的那一行。如果始终没选到 EOS,配套程序会在预设的八次预测上限处停止,并明确报告是触及上限,而不是遇到 EOS。

使用教师强制时,四个正确目标的概率分别为 0.4、0.8、0.9、1。第一行虽然更偏向 blue,第二步训练输入却仍是 red。要评价整条参考序列,就像第 4 章那样,沿正确前缀把条件概率相乘:

p(red, key, ., EOS∣source)=0.4×0.8×0.9×1=0.288.p(\text{red, key, ., EOS}\mid\text{source}) =0.4\times0.8\times0.9\times1=0.288.

按第 6 章的自然对数损失,总损失为 −ln⁡(0.288)≈1.244795-\ln(0.288)\approx1.244795 nat,平均损失为 1.2447947988461912/4≈0.3111991.2447947988461912/4\approx0.311199 nat/token。分母取四,是因为我们声明的目标序列包含 EOS。仅凭这一条给定序列上的较小平均损失,不能证明自由生成会正确;它甚至在第一步就把最高概率分给了错误 token。

13.4 生成时,把自己的选择送进下一步

Section titled “13.4 生成时,把自己的选择送进下一步”

生成时,源序列可用,正确目标却不可用。从 BOS 开始,先选一个 token,再把 自己选中的 token 作为下一步输入。这叫作 自回归生成(autoregressive generation):每次选择都成为前缀的一部分,影响后面的选择。第 4 章延长序列时已经用过这种依赖,这里则发生在以源序列为条件的解码器中。

表 13.5 — 严格使用表 13.4 的生成时间线。

步骤当前输入最大概率选中输出随后的操作
1BOS0.5blue输入 blue
2blue0.7door输入 door
3door0.9.输入 .
4.1EOS停止

参考列表为 red key . EOS,实际按规则生成的列表为 blue door . EOS。第二步训练用的是 red 行,偏向 key;生成用的是 blue 行,偏向 door。两次运行遵循同一张表,输入之所以分开,是因为只有训练过程拿到了正确前缀。

训练输入 → 目标
  1. 文本片段1:BOS → red(概率 0.4)
  2. 文本片段2:red → key(概率 0.8)
  3. 文本片段3:key → .(概率 0.9)
  4. 文本片段4:. → EOS(概率 1)

第 1 步虽然偏向 blue,第 2 步仍由目标提供正确的 red。

生成输入 → 选择
  1. 文本片段1:BOS → blue(概率 0.5)
  2. 文本片段2:blue → door(概率 0.7)
  3. 文本片段3:door → .(概率 0.9)
  4. 文本片段4:. → EOS(概率 1)

把选中的 token 作为下一步输入;本条记录中的句号和 EOS 仍然正确。

图 13.2 — 同一张条件概率表,收到不同前缀。 本书原创教学图,数值为给定值或按规则推导值,CC BY-SA 4.0;未复制论文图示。

图 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 上的行为,并不声称测量过训练时间、翻译质量或泛化能力。

编码器可以把一个序列读成上下文,解码器再据此写出另一个序列。目标右移让前一个 token 与下一个监督目标对齐,并明确开始、结束边界。教师强制提供正确的前一个 token,自回归生成提供模型自己选出的 token;两者造成的前缀差异可能影响后续预测。

固定的源向量带来第二个问题:解码还没开始,不同源序列就可能已经无法区分。第 14 章 保留递归编码器和解码器,但允许每个目标步读取一组编码器状态。我们会亲手算出,这条新增路径到底返回了什么。

  1. 换一条目标再右移。 目标为 [blue, door, ., EOS]。列出教师强制下的全部“解码输入—目标”对。BOS 贡献目标损失吗?EOS 呢?
  2. 区分预测与反馈。 表 13.4 中,为什么第一次预测错了,训练的第二步输入却没有变成 blue?指出生成过程从哪一步开始出现输入分歧。
  3. 改变错误的后续影响。 只把 blue 行换成 [0,0,0.8,0.1,0.1,0],求完整的贪心输出。这支持关于错误传播的什么结论,又不支持什么结论?
  4. 定位碰撞。 写出蓝色编码路径在读到钥匙时,激活前与经过 ReLU 后的状态。红、蓝路径在哪次读取后相同?完美的教师强制能让它们的第一个颜色预测不同吗?
  5. 解释分数。 仍评价原参考序列,但不计 EOS,计算平均目标损失。生成规则不变的情况下,损失计算中的哪些部分变了?
  1. 依次为 BOS→blue、blue→door、door→句号、句号→EOS。BOS 只作输入,没有目标损失;EOS 是目标,要计入损失。共有四次评分预测。
  2. 教师强制从参考序列决定下一步输入,不采用模型当前概率最高的输出,所以训练第二步仍输入 red。生成则把第 1 步选出的 blue 送入第 2 步,这是输入首次分歧的位置;首次预测错误发生在前一步。
  3. 新输出为 blue key . EOS。颜色仍错,名词却正确了,说明早期错误不必导致后面每个输出都错。但只改一条诊断概率行,不能证明暴露偏差普遍无害。
  4. 蓝色首个嵌入为零,读到钥匙时激活前为 [-a,a],经过 ReLU 得到 [0,a],与红色路径在第 2 次读取后的状态相同。最终上下文也相同。在第一个目标步,两边都有 BOS 和相同上下文;教师强制此时还没有提供任何先前的颜色 token 来区分它们。
  5. 去掉 EOS,就去掉一项 −ln⁡1=0-\ln1=0,因此总损失的数值仍为 1.2447947988461912,但求和定义少了一项。改为除以三,得到约 0.414932 nat/token。评价事件定义不同的分数,不能当作同一种平均值直接比较;这里没有改变生成规则。