Chapter 8.1 Bahdanau Attention:从信息压缩到动态检索

作者

Brench

发布于

2026-05-19

修改于

2026-06-22

在前面的序列模型中,我们已经接触过一种很直接的做法:让编码器先读完整个输入序列,再把读到的信息压缩成一个向量,最后由解码器基于这个向量逐步生成输出。放到机器翻译里,就是编码器先处理源语言句子,得到一个上下文表示;解码器再利用这个表示生成目标语言句子。

这个流程符合最直观的“编码-解码”范式:输入是一句话,模型先把整句话编码成一个表示,再用这个表示生成另一句话。

问题在于:整个输入序列的信息都要挤进一个固定长度的向量里。

如果句子很短,这个限制未必明显;一旦句子变长,固定长度向量就很容易变成信息瓶颈。编码器最后的隐藏状态既要概括整句语义,又要尽量保留局部词语、语法结构和长距离依赖。我们相当于要求编码器把所有细节都塞进一个向量,随后解码器无论生成哪个目标词,都只能反复依赖这同一个压缩表示。

可以用一个类比理解这个约束:一个人先读完整篇文章,却只允许留下一句话摘要;另一个人再根据这句话逐字还原原文。短文本也许还能勉强应付,文本一长,许多细节必然会丢掉。

Bahdanau attention 正是从这个问题出发:怎样缓解固定长度上下文向量带来的压缩瓶颈?

8.1.1 固定长度上下文向量的瓶颈

先回到传统 seq2seq 的结构。

假设待翻译的句子有 \(T_x\) 个 token,编码器依次读入这些 token,并得到一串隐藏状态:

\[ h_1, h_2, \dots, h_{T_x} \]

在最朴素的 seq2seq 模型里,编码器往往只把最后一个隐藏状态交给解码器,作为整句输入的表示:

\[ c = h_{T_x} \]

这里的 \(c\) 就是上下文向量(context vector)。之后解码器每生成一个目标 token,都要依赖同一个 \(c\)。也就是说,无论当前生成的是目标句子的第一个词,还是后面某个位置的词,解码器拿到的上下文都是这个固定向量。

这会带来两个问题。

第一个问题是信息压缩。源句越长,把所有内容塞进固定长度向量就越困难。即便 LSTM、GRU 这类结构比普通 RNN 更擅长保留长期信息,这个瓶颈也不会消失。

第二个问题是缺少动态选择能力。翻译时,不同目标词往往需要参考源句的不同片段。生成动词时,模型可能更需要源句中的谓语信息;生成名词时,可能更依赖某个人、地点或物体。可是传统 seq2seq 提供给解码器的上下文向量始终不变,它无法在不同生成时刻灵活选择当前最该关注的位置。

从人工翻译过程看,我们通常不会先把整句话读完,只留下一个整体总结,然后完全依靠这个总结来写翻译。更自然的做法是:在读句子时保留每个词的含义和位置信息;真正翻译时,再动态地回看源句的不同位置,挑出和当前要生成的词最相关的信息。

因此,一个更合理的思路是:不要强行把整句话一次性压缩成单个向量,而是保留编码器在每个位置产生的隐藏状态。解码器每生成一个词,就根据当前状态,从这些隐藏状态中动态提取需要的信息。

这就是 Bahdanau attention 的核心思想。

8.1.2 Bahdanau Attention:生成时再决定看哪里

Bahdanau attention 的核心改动在于:编码器不再只传递一个固定上下文向量,而是把所有时间步的隐藏状态都保留下来。

\[ h_1, h_2, \dots, h_{T_x} \]

这些隐藏状态可以看成源句每个位置的上下文化表示。它们并不是孤立的词向量,而是编码器读到相应位置后形成的状态,因此已经融合了部分上下文信息。

接着,当解码器要生成第 \(t\) 个目标词时,模型不会再直接使用同一个固定的 \(c\),而是为当前时刻单独计算一个上下文向量 \(c_t\)。这个 \(c_t\) 来自编码器所有隐藏状态的加权求和:

\[ c_t = \sum_{i=1}^{T_x} \alpha_{t,i} h_i \]

其中 \(\alpha_{t,i}\) 表示在生成第 \(t\) 个目标词时,模型分配给源句第 \(i\) 个位置的注意力比例。比如 \(\alpha_{t,3}\) 很大,说明当前生成更依赖源句第 3 个位置;如果 \(\alpha_{t,7}\) 很小,就意味着第 7 个位置对当前决策贡献较弱。

这样,解码器在每个生成时刻都会拿到一个“为当前词定制”的上下文向量:

\[ c_1, c_2, \dots, c_{T_y} \]

这和传统 seq2seq 只使用一个固定 \(c\) 的方式明显不同。Attention 把上下文向量变成了随时刻变化的量:

当前要生成什么,就决定当前要看哪里。

这就是 attention 最关键的直觉。它不再要求模型在编码阶段一次性保存所有信息,而是允许解码器在生成过程中反复回看源句,并根据当前需求选择不同位置的信息。

8.1.3 注意力权重从哪里来?

理解了 attention 的直觉之后,下一步问题就是:权重 \(\alpha_{t,i}\) 到底从哪里来?

当解码器准备生成第 \(t\) 个目标词时,它会有一个当前状态。这个状态可以看作模型此刻的生成需求:前面的词已经生成,接下来要判断下一个词应该是什么。

假设解码器当前的隐藏状态为 \(s_{t-1}\),编码器第 \(i\) 个位置的隐藏状态为 \(h_i\)。一种想法是,用一个打分函数来衡量它们之间的相关性:

\[ e_{t,i} = a(s_{t-1}, h_i) \]

这里的 \(e_{t,i}\) 是还没有归一化的注意力分数,用来衡量源句第 \(i\) 个位置和当前解码状态之间的相关程度。

Bahdanau attention 使用的是一个小型前馈神经网络来计算这个分数:

\[ e_{t,i} = v_a^\top \tanh(W_a s_{t-1} + U_a h_i) \]

这里暂时不需要展开这个公式的推导。直观地看,它就是把当前解码状态 \(s_{t-1}\) 和某个编码器隐藏状态 \(h_i\) 放在一起,通过一个可学习函数输出相关性分数。

随后,对所有源句位置的分数做 softmax,就得到注意力权重:

\[ \alpha_{t,i} = \frac{\exp(e_{t,i})}{\sum_{j=1}^{T_x} \exp(e_{t,j})} \]

最后,用这些权重对编码器隐藏状态做加权求和,得到当前时刻需要的上下文向量:

\[ c_t = \sum_{i=1}^{T_x} \alpha_{t,i} h_i \]

因此,Bahdanau attention 的计算流程可以压缩成三步:

  1. 用当前解码状态分别和每个编码器隐藏状态计算相关性分数。
  2. 对这些分数进行 softmax,转换成注意力权重。
  3. 按注意力权重聚合编码器隐藏状态,形成当前时刻的上下文向量。

从本质上看,这就是一次动态信息检索。当前解码状态提出“我现在需要什么”,编码器的所有隐藏状态提供候选信息,attention 根据相关性分配权重,再把最有用的信息聚合回来。

8.1.4 为什么叫软对齐?

Bahdanau attention 最早出现在机器翻译场景中。翻译里有一个很自然的现象:目标语言中的某个词,往往会对应源语言中的一个或几个词。比如把英文翻译成中文时,生成某个中文词可能主要依赖英文句子里的几个单词。传统机器翻译把这种对应关系称为对齐(alignment)。

Attention 权重 \(\alpha_{t,i}\) 恰好可以承担类似对齐的角色。

对目标句子的第 \(t\) 个位置来说,\(\alpha_{t,i}\) 表示它和源句第 \(i\) 个位置的关联强度。如果把所有 \(\alpha_{t,i}\) 画成矩阵,就会得到类似对齐图的可视化:行对应目标词,列对应源词,颜色越深表示权重越大。

不过,这种对齐不是硬性的。硬对齐会要求当前目标词只对应源句中的某一个位置,比如第 3 个目标词只对齐第 5 个源词。Bahdanau attention 采用的是软对齐:它不强迫模型只选一个源位置,而是允许模型给多个位置分配连续权重:

\[ \alpha_{t,1}, \alpha_{t,2}, \dots, \alpha_{t,T_x} \]

这些权重总和为 1,但每个位置都可以贡献一部分信息。也就是说,当前目标词可以主要参考某个源词,同时少量吸收其他相关位置的信息。这就是“软”的意思。

软对齐的优势在于连续且可微。模型不需要额外标注告诉它哪个词应该对齐哪个词,也不必先训练一个独立的对齐模块。它可以在翻译任务本身的训练过程中,通过反向传播逐渐学出这种对应关系。

换句话说,模型不是先单独学对齐,再去学翻译;而是在学习翻译时,同时学会生成当前词应该关注源句的哪些位置。这也解释了 Bahdanau 论文标题中 “jointly learning to align and translate” 的含义:对齐和翻译是在同一个训练过程中共同学到的。

8.1.5 Bahdanau Attention 改变了什么?

Bahdanau attention 不只是给 seq2seq 额外插入了一个小模块,更重要的是它改变了编码器和解码器之间的信息传递方式。

在传统 seq2seq 中,编码器和解码器之间只有一条固定长度的信息通道。编码器必须把输入的所有信息压缩到一个向量里,解码器随后也只能依赖这个向量生成完整输出。

加入 attention 以后,两者之间的通信方式变得更灵活。编码器保留每个位置的隐藏状态,解码器在每个生成时刻都可以重新访问这些状态,并按当前需要动态聚合信息。

因此,attention 至少带来了三个重要变化。

首先,它缓解了固定长度向量造成的信息瓶颈。源句信息不再只靠最后一个隐藏状态传递,而是由所有编码器隐藏状态共同提供。其次,它让上下文向量具备动态检索能力。不同目标词可以拥有不同的 \(c_t\),模型生成不同词时也能关注源句的不同区域。最后,它带来了一种有解释价值的中间结构。虽然不能把 attention 权重简单等同于完整解释,但在机器翻译里,它确实能展示模型生成某个目标词时更偏向参考哪些源位置。

从这个角度看,attention 的核心不是某个具体公式,而是一种思想:

不要提前把所有信息压成一个固定表示,而是在需要的时候,根据当前任务动态检索信息。

这个思想后来不断被扩展。最初,它主要服务于 RNN seq2seq 的编码器-解码器架构;之后,它被抽象成更通用的 query、key、value 形式;再往后,Self-Attention 把这种动态检索机制搬到同一个序列内部,最终成为 Transformer 的核心组成部分。

8.1.6 Bahdanau Attention 和现代 Attention 的关系

按现代 attention 的术语回看,Bahdanau attention 可以理解为该机制的早期形态。

现代 attention 经常用 query、key、value 这三个概念来描述:query 表示当前想寻找什么,key 表示候选信息用什么参与匹配,value 则表示最终真正取回的内容。

如果用这套语言重新理解 Bahdanau attention,那么解码器隐藏状态 \(s_{t-1}\) 类似 query,而编码器隐藏状态 \(h_i\) 同时承担 key 和 value 的作用。

说它像 key,是因为模型会用 \(h_i\)\(s_{t-1}\) 来计算匹配分数:

\[ e_{t,i} = a(s_{t-1}, h_i) \]

说它也像 value,是因为最后真正被加权求和并传回解码器的,仍然是这些 \(h_i\)

\[ c_t = \sum_{i=1}^{T_x} \alpha_{t,i} h_i \]

也就是说,在 Bahdanau attention 中,用来匹配的信息和最终取回的信息还没有被清楚拆开。到了后来的现代 attention,模型通常会通过不同线性变换显式得到 \(Q\)\(K\)\(V\),从而让“用什么来匹配”和“取回什么内容”进入两个可分别学习的表示空间。不过核心逻辑没有变:先根据当前需求和候选信息计算相关性,再用相关性权重聚合信息。

这也是 Bahdanau attention 很适合作为 Transformer 起点的原因。它先在具体的 seq2seq 翻译场景中把 attention 要解决的问题讲清楚:固定长度表示太僵硬,而生成过程需要能够动态回看输入。

理解这一点之后,再看后面的 cross-attention、self-attention 和 multi-head attention,就不会觉得它们是割裂的概念。它们本质上都在回答同一个问题:

当模型处理一个位置时,它应该如何从一组候选信息中,动态地找出当前最相关的部分?

8.1.7 本章小结

这一节从传统 seq2seq 的固定长度上下文向量出发,介绍了 Bahdanau attention 的核心思想。

传统 seq2seq 会把整个源句压缩成一个固定向量,这既容易造成信息瓶颈,也让解码器在生成不同目标词时缺少动态选择信息的能力。Bahdanau attention 则保留编码器所有时间步的隐藏状态,让解码器在每个生成时刻重新计算注意力权重,并据此得到当前专属的上下文向量。

直观地说,attention 就是在生成时动态决定“现在该看哪里”。从机器翻译角度看,它也可以理解为一种软对齐:模型不再硬性选中某一个源词,而是给所有源位置分配连续权重,并通过端到端训练自动学到这种对应关系。

这一节的重点不是背下某个具体打分函数,而是理解 attention 试图解决什么问题,以及它改变了什么。它让模型从“一次性压缩输入”转向“在需要时动态检索相关信息”。下一节会把这个思想进一步抽象成现代形式,讨论 cross-attention 和 self-attention,并正式引入 query、key、value 这套表示方式。

二次使用