跳转到内容

第 14 章 Attention:当前输出应该看输入哪里

14.1 当前输出需要的,也许是较早的状态

Section titled “14.1 当前输出需要的,也许是较早的状态”

第 13 章 的编码器曾经产生有用的颜色状态,却在得到最终摘要之前丢掉了它。解码器只拿到 [0,0],无法区分红色和蓝色输入。交给解码器的唯一源表示里已经没有这种区别,把解码器做得更复杂,也不能凭空恢复它。

假设我们保存 全部三个编码器状态。写颜色之前,解码器可以按一种方式组合它们;写物体之前,再换一种组合。源序列仍由递归编码器读取,目标序列仍由递归解码器写出。我们改变的是每个输出步能访问哪些源信息。

这正是递归翻译模型引入注意力的动机:从保存的源表示中,为当前输出计算一份上下文,不再只依靠一个固定摘要。Bahdanau 等人用可学习的对齐函数实现了这样的模型。本章则先明确一种更简单的打分规则,再亲手算出整个机制。Bahdanau 等,2015,第 3 节

14.2 同一个解码位置,面对三个可读取的位置

Section titled “14.2 同一个解码位置,面对三个可读取的位置”

例 14.1 — 从算出的源状态中读取颜色信息。 沿用第 13 章的源序列 [红色, 钥匙, 。]、嵌入和 ReLU 编码器。令 a=ln⁡2≈0.693147a=\ln2\approx0.693147,按源位置保存的状态为:

h1=[a,0],h2=[0,a],h3=[0,0].h_1=[a,0],\qquad h_2=[0,a],\qquad h_3=[0,0].

这些状态由权重计算得来,不是原始输入嵌入。例如,钥匙的嵌入是 [-a,a],对应状态却是 [0,a]。一个已保存状态概括了涉及此前输入的计算,不一定是某个孤立词的向量。

现在给定一个解码状态,用来探查读取结果:q1=[1,0]q_1=[1,0]。字母 qq 表示 查询向量(query),当前输出位置用它与可用的源状态比较。在本章的递归结构中,查询取自上一步解码状态。本章使用的两个查询值都是特意给定的探查值,不是学出的解码轨迹,也不是第 13 章概率表产生的状态。这样可以单独看清读取操作,而不虚构尚未给出的解码器权重。

查询和源状态都只有两个坐标。规定分数为 未缩放的点积:对应坐标相乘,再把乘积相加。它不是余弦相似度,不除以向量长度;也不是下一章将引入的缩放点积。这是 Luong 等人讨论的一种打分方式,与 Bahdanau 原论文中可学习的加性打分不同。本章正文和代码始终使用同一条未缩放规则。Luong 等,2015,第 3.1 节,式(8)

表 14.1 — 从比较分数算出源位置权重。

源位置状态与 [1,0] 的点积指数值归一化权重
1:颜色[a, 0]1 × a + 0 × 0 = a22/4 = 1/2
2:钥匙[0, a]1 × 0 + 0 × a = 011/4
3:句号[0, 0]011/4

分数行是 [a,0,0]。按第 6 章的 Softmax,把它变成非负且总和为一的权重。由于 ea=2e^a=2、e0=1e^0=1,分母是 2+1+1=42+1+1=4。若除以三,算的是指数值的平均,而不是把它们归一化成概率分布。分数 为零,指数值仍是一,因此权重并不为零。

这些概率把一次读取分配给 三个源位置,不是分配给候选输出词。颜色状态的权重为 1/2,不等于输出 red 的概率为 50%。要得到上下文,需要把每个完整源向量乘上它的权重,再逐坐标相加:

c1=12[a,0]+14[0,a]+14[0,0]=[a/2,a/4]≈[0.346574,0.173287].\begin{aligned} c_1&=\tfrac12[a,0]+\tfrac14[0,a]+\tfrac14[0,0]\\ &=[a/2,a/4]\approx[0.346574,0.173287]. \end{aligned}

第一个坐标从颜色状态得到 a/2a/2,其余两项贡献为零;第二个坐标从钥匙状态得到 a/4a/4。句号状态贡献的是零向量,但它的正权重仍占据归一化总量的一部分,会影响其他状态分到多少权重。

这个加权和就是当前的 上下文向量(context vector)。它和每个被加权的状态一样,只有两个坐标,不会因为有三个位置就变成三维。在这个小计算里,结果的每个坐标都必须处于源状态对应坐标的最小值与最大值之间:非负且和为一的权重,形成的是加权平均,不能任意创造超出对应坐标范围的新值。

14.3 输出位置变了,读回的上下文也变了

Section titled “14.3 输出位置变了,读回的上下文也变了”

例 14.2 — 从同一组状态中读取物体信息。 所有源状态保持不变,改为给定第二个查询 q2=[0,1]q_2=[0,1]。分数变成 [0,a,0],因为点积现在取第二个坐标,而不是第一个。指数值为 [1,2,1],仍除以总和 4,得到权重 [1/4,1/2,1/4]。因此:

c2=14[a,0]+12[0,a]+14[0,0]=[a/4,a/2]≈[0.173287,0.346574].c_2=\tfrac14[a,0]+\tfrac12[0,a]+\tfrac14[0,0] =[a/4,a/2]\approx[0.173287,0.346574].

第一次读取给颜色状态最多权重,第二次给钥匙状态最多权重。保存的源状态没有改变,改变的是当前查询,因此返回的组合不同。这就是这里所说的 动态访问(dynamic access),并不暗示模型在有意识地搜索,或能可靠判断什么最重要。

读取 1:q = [1,0]
  1. 一组数字颜色 [a,0] × 1/2
  2. 一组数字钥匙 [0,a] × 1/4
  3. 一组数字句号 [0,0] × 1/4
  4. 一组数字求和 → [a/2, a/4]

分数 [a,0,0] → 指数值 [2,1,1] → 除以 4。

读取 2:q = [0,1]
  1. 一组数字颜色 [a,0] × 1/4
  2. 一组数字钥匙 [0,a] × 1/2
  3. 一组数字句号 [0,0] × 1/4
  4. 一组数字求和 → [a/4, a/2]

分数 [0,a,0] → 指数值 [1,2,1] → 除以 4。

图 14.1 — 源状态都不变,只换查询。 本书原创教学计算图,CC BY-SA 4.0。查询为给定探查值,源状态由声明的编码器计算。

图 14.1 保持源位置顺序,只交换前两个位置的权重。目标位置与源位置之间这种不必一一对应的软关联,常被称为 对齐(alignment)。它不必只选一个位置,不必沿对角线,也不必符合人类逐词标注的翻译对应。这里的权重来自已经声明的分数;这种读取能否帮助学出的解码器,还需要另外检验。

完整算过之后,再写一般形式。设源状态有 nn 个,每个宽度为 dd,查询宽度也为 dd。目标位置记作 tt,源位置记作 jj:

et,j=qt⋅hj,αt,j=exp⁡(et,j)∑k=1nexp⁡(et,k),ct=∑j=1nαt,jhj.e_{t,j}=q_t\cdot h_j,\qquad \alpha_{t,j}=\frac{\exp(e_{t,j})}{\sum_{k=1}^{n}\exp(e_{t,k})},\qquad c_t=\sum_{j=1}^{n}\alpha_{t,j}h_j.

标量 et,je_{t,j} 是比较分数,这里的字母本身不是指数函数的底数。下标 kk 遍历分母中包含的所有源位置。标量 αt,j\alpha_{t,j} 是该位置的权重,把缩放后的各行相加,就得到宽度为 dd 的行向量 ctc_t。例 14.1 中,n=3n=3、d=2d=2:有三个分数、三个权重,却只有两个上下文坐标。代入后会重新得到表 14.1 和 [a/2,a/4]。

这种操作叫作 交叉注意力(cross-attention),因为查询来自目标侧解码器,被读取的状态来自源侧编码器。本例直接拿源状态做比较,也直接把它们当作加权求和的值。独立的可学习投影,以及完整的 Q/K/V 记号,留到第 15 章再建立。

14.4 把上下文交给解码器,它还不是答案

Section titled “14.4 把上下文交给解码器,它还不是答案”

上下文为预测提供信息,但自身不是预测结果。沿用第 13 章的递归顺序,令 qt=st−1q_t=s_{t-1},先读出注意力上下文,再更新解码器:

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

旧解码状态是 st−1s_{t-1},前一个目标嵌入是 Ey(yt−1)E_y(y_{t-1}),新增的是随读取而变的上下文 ctc_t。FF、WoW_o、bob_o 的职责与第 13 章相同。例如,这三个输入行向量若各有两个坐标,拼接后就给递归更新提供六个坐标,不是六个输出词概率。最后的 Softmax 仍在输出词表上归一化,与源位置上的注意力 Softmax 分开。

训练时仍由教师强制提供前一个正确目标,生成时仍使用前一个选中目标。能访问源状态,并没有消除两种前缀的差异。本章给定的两个查询,也不能证明训练好的解码器实际会在那些位置说什么。

不过,新增路径确实让小编码器较早保留的区别重新可用。蓝色源序列的状态是 [0,0]、[0,a]、[0,0]。用第一个查询打分,三个分数都是零,权重都是 1/3,上下文为 [0,a/3]。它不同于红色源序列的 [a/2,a/4],虽然两者最终编码状态相同。解码器如今拿到了一项随源输入变化的区别,可以利用它;是否能学会合适的输出规则,单凭这个计算还不能确定。

14.5 删去一个位置之前,先说明固定什么

Section titled “14.5 删去一个位置之前,先说明固定什么”

例 14.1 中,颜色状态的权重最大。能否因此认定,它就是后续任何输出的主要原因?作出这个判断,需要先确定观察哪个输出量、做什么干预,不能只看排名。

例 14.3 — 读取之前,删去一个已保存状态。 固定第一个查询,并固定其他源状态的数值。把指定位置从分数列表和加权和中完全移除,再对剩余位置重新归一化。这不是把被删位置的分数改成零,因为零分数仍会贡献指数值 1。

为了衡量影响,先规定一个简单的下游读数:上下文第一个坐标,加上第二个坐标的两倍。把这两个坐标记作 ct,1c_{t,1}、ct,2c_{t,2},定义 r=ct,1+2ct,2r=c_{t,1}+2c_{t,2}。这个读数也是作者设定的诊断量,不是学出的翻译分数。删除前,它等于 a/2+2(a/4)=aa/2+2(a/4)=a。

表 14.2 — 固定其余状态,用第一个查询重算删除结果。 权重仍按原源位置排列,零表示该位置被排除。

删去的位置权重:颜色、钥匙、句号新上下文读数 r
不删除[1/2, 1/4, 1/4][a/2, a/4]a
1:颜色[0, 1/2, 1/2][0, a/2]a
2:钥匙[2/3, 0, 1/3][2a/3, 0]2a/3
3:句号[2/3, 1/3, 0][2a/3, a/3]4a/3

删去颜色后,剩下两个零分数,所以权重各为 1/2。上下文变了,但选定的读数 没有变:第二个坐标增大后再乘二,恰好补回第一个坐标丢掉的贡献。删去钥匙后,剩下的指数值是 2 和 1,分母变成三,读数降到 2a/32a/3。删去值为零的句号状态,也会改变归一化,使读数升到 4a/34a/3。

因此,权重最大的状态,不一定就是删去后最影响这个输出量的状态。如果改用另一种读数,例如只看上下文的第一个坐标,结果又会不同。注意力权重说明这次读取怎样组合状态;要说明某个输出的因果关系,还需追踪下游计算,并明确测试中的改动。这是一个有限结论,不是说注意力永远不能提供有用证据。Jain 和 Wallace,2019,第 4 节;Wiegreffe 和 Pinter,2019,第 2 节

删除 原始输入 token 又是另一种干预。它可能改变后面各位置的状态,因此必须重新运行编码器。例如,删去源 token 钥匙,留下 [红色, 。]。第一个状态为 [a,0];读句号时加上 [0,-a],得到 [a,-a],经过 ReLU 变成 [a,0]。两个剩余状态如今都是 [a,0]。仍固定第一个查询,两个分数都为 a,权重各为 1/2,上下文为 [a,0],读数为 a。这与只删去已保存的钥匙状态时得到的 2a/32a/3 不同。

表 14.3 — 删去原输入后,重新编码再读取。 查询和读数规则保持固定,因此这仍不是完整的解码生成过程。

删去的源 token重算的状态上下文读数 r
颜色[0,a];[0,0][0,a/2]a
钥匙[a,0];[a,0][a,0]a
句号[a,0];[0,a][2a/3,a/3]4a/3

有些结果与表 14.2 恰好一致,钥匙那一行却不同。只说“删掉一个词,答案变了”,证据仍不完整:后面的状态重算了吗?查询和之后生成的 token 呢?高权重与表面相关性的对应关系值得调查,但它本身不是干预结果。即使干预记录完整,也只建立了特定条件下的影响,不会自动成为对模型的普遍解释。

至少还得有一个可读取状态。如果把全部位置都排除,就没有正的归一化总量,这条规则下也就无法定义加权读取。配套代码会报告错误,不会编造一组全零“概率”。

14.6 访问范围更大,存储与顺序代价仍在

Section titled “14.6 访问范围更大,存储与顺序代价仍在”

保存全部源状态,让每个已保存表示都多了一条直接通向当前目标读取的路径。旧结构中,较早源信息必须经过剩余的每次编码更新,才能到达最终上下文。新读取可以直接使用较早保存的状态,绕过这一段路;训练梯度也可以沿读取路径到达那个状态。这不意味着所有长路径都消失了,更不保证网络一定学会有用的表示与对齐。

即使在这个小例子里,也能数清存储代价。固定上下文保存两个数,三个源状态保存六个数。若明确假设每个数占八字节,就是 逻辑激活存储量 16 字节与 48 字节的区别,不含参数、容器、解码状态或训练中间量。源位置有 nn 个、状态宽度为 dd 时,需要保存 ndnd 个数。这是按假设计数,不是进程内存实测。

四个输出步若每步都为三个源位置打分,就要算十二次点积。每次二维点积含两次标量乘法和一次加法;整个操作还要取指数、归一化、计算加权和。源序列越长,每次读取的工作也越多。

源序列依赖
  1. 一组数字h₀ → h₁ → h₂ → h₃
  2. 处理步骤保存三个状态

较早保存的状态可以直接进入读取,不必再经过后面的编码更新。

查询已知
  1. 一组数字qₜ = sₜ₋₁
  2. 处理步骤qₜ·h₁、qₜ·h₂、qₜ·h₃
  3. 一组数字归一化并求和 → cₜ

三次比较可以并行,但都要先拿到当前查询。

目标序列依赖
  1. 处理步骤旧状态 + 前一个 token + cₜ → sₜ
  2. 一组数字下一个查询使用 sₜ

教师强制提供 token,不提供未来隐藏状态;生成时还要先选出下一步输入 token。

图 14.2 — 比较可以并行,外层递归仍要依次计算。 本书原创教学计算图,CC BY-SA 4.0。查询为给定探查值,源状态由声明的编码器计算。

图 14.2 标出了哪些计算仍然要等。查询已知后,它与各源状态的比较,以及加权求和的部分计算可以并行。但本例的递归编码器仍需依次更新三次,解码器仍需依次更新四次。即使用教师强制,下一个解码状态也依赖前一个;生成时还多了对前一个选中 token 的依赖。注意力没有把整条递归序列变成相互独立的并行更新。

访问也仍受应用提供的信息限制。如果更新后的 750 元差旅政策从未进入编码器,注意力不能从系统外把它检索回来。某个源状态的相关分数,既不能核实政策版本,也不能授予订单操作权限。

14.7 第三部分检查点:先认清路径,再提出修复

Section titled “14.7 第三部分检查点:先认清路径,再提出修复”

表 14.4 — 四种诊断,不要都归为“记性差”。

观察到的证据已建立的问题怎样改变相关路径?什么仍未得到证明?
末尾两个输入 token 相同,要求的颜色不同固定窗口的输入碰撞提供更多上下文,或传递计算状态模型能正确使用新增线索
局部损失信号跨越许多依赖边后衰减反向敏感程度过弱改善状态路径和训练设置,门控可能有帮助任意距离上的可靠保留
训练第二步收到 red,生成第二步收到 blue正确前缀与生成前缀不一致用生成前缀评价后续行为每个早期错误都会继续扩散
不同源序列得到同一个最终向量这个编码器的固定摘要发生碰撞让目标步访问较早的状态正确的对齐与输出选择能被学会

检查点要落到具体操作上:在图 11.1 中指出输入、状态和共享参数;从表 12.1 算回总梯度 −0.75;在表 13.5 定位第一次输入变化;算出表 14.1 的权重和上下文。然后用表 14.2—14.3 解释,为什么一张注意力图不自动构成因果解释。四章静态可见的参考解答给出了各步理由。

共享实践程序为 code/part-iii/run.py,固定数值和预期结果位于 data/part-iii/。notebooks/part-iii/02-seq2seq-attention.ipynb 会执行轨迹、比较两种删除操作,并提供可选的 NumPy 复算。标准库路径只要求完成 Python 入门。核心结果都已列在正文中,理解机制不需要账号、GPU、付费 API 或运行 Notebook。

交叉注意力从当前解码查询出发,算出源位置分布,再返回加权后的源状态向量。我们的两个查询从不变的状态集合中读回不同上下文,使最终摘要丢掉的区别重新可用。上下文之后仍需要解码器的输出规则,而单个注意力权重不能独自解释那条规则的结果。

源序列和目标序列的递归依赖仍然存在。第 15 章将追问:同一序列内部 的位置能否直接交互,不再只能通过相邻递归状态传递信息?这会改变另一条信息路径,形成自注意力。可学习投影、缩放以及后面的 Transformer 结构,都属于接下来的论证。

  1. 换一个查询。 对原来的三个源状态使用查询 [1,1],计算全部分数、权重、上下文坐标及诊断读数。尽量把答案写成 a 的分数倍。
  2. 诊断分母。 一位读者因为例 14.1 有三个源位置,算出权重 [2/3,1/3,1/3]。错在哪里?若改用查询 [0,0],权重又是多少?
  3. 比较两种删除。 对“删去钥匙”分别求上下文:先只删已保存状态,再删输入 token 并重新编码。指出哪个剩余状态改变了。
  4. 检验解释。 颜色状态的权重为 1/2,报告因此认定它对最终回复的因果影响必然最大。从本章给出数值反例,并说明这个反例实际检验的输出量与干预是什么。
  5. 数代价与依赖。 假设有 100 个二维源状态和四个目标步,每个数占八字节,需要多少逻辑源状态存储量?要做多少次点积比较?训练目标已知时,哪些递归依赖仍在?
  1. 分数为 [a,a,0],指数值为 [2,2,1],总和为五。权重是 [2/5,2/5,1/5],上下文为 [2a/5,2a/5],读数为 2a/5+2(2a/5)=6a/52a/5+2(2a/5)=6a/5。三个权重之和为一,两个上下文坐标之和则不必为一。
  2. 分母必须是指数值之和,所以应为四,不能直接数位置。错误权重之和为 4/3,不是概率分布。零查询则得到三个零分数、三个指数值 1;在这个不同条件下,分母确实为三,权重均为 1/3。
  3. 只删已保存的钥匙状态,留下 [a,0] 和 [0,0],权重为 2/3、1/3,上下文为 [2a/3,0]。删原输入中的钥匙,则没有钥匙嵌入来抵消颜色坐标,后面的句号状态也变成 [a,0]。两个剩余状态都是 [a,0],等权平均后仍为 [a,0]。
  4. 固定其余状态,按声明的标量规则读出时,删去权重最高的颜色状态,读数仍为 a;删去钥匙却降到 2a/3。这足以反驳针对该输出量、该干预的自动排序说法,但并没有测量译词准确率或整条生成回复。
  5. 状态中有 100×2=200100\times2=200 个数,按假设占 200×8=1600200\times8=1600 字节。四次读取需要 100×4=400100\times4=400 次点积。编码器仍按顺序更新源状态,解码器仍按顺序更新目标状态;已知目标 token,不会让递归隐藏状态相互独立。这些计数不含其他张量和实现开销。