阶段 3 · 处理序列

Seq2Seq:一句话压成一个向量
够吗

上一章 《LSTM / GRU》治好了梯度消失,记忆却仍卡在一格。 Seq2Seq 让"输入一句、输出一句"成为可能,代价是把整句话压成一个固定大小的向量。注意力让解码器可以每翻译一个词,回头看一眼原文。

1

编码器-解码器:两个 RNN 接力

Seq2Seq(Sequence to Sequence:把一句翻成另一句)2014 年提出,结构极简:两个 RNN,中间接一个向量。 第一个 RNN 读源句(输入的那句原文),第二个 RNN 写译文。

角色干什么输入 / 输出
编码器 Encoder从左往右读完整个源句,把信息积累到最后那个隐藏状态里 「我 昨天 看见 一只 猫」→ 一个向量 c
上下文向量 c整句话的"浓缩" 维度固定(比如 512 维),不管句子多长
解码器 Decoder拿着 c,一个词一个词地生成译文 c → "I" → "saw" → "a" → "cat"
⚠️ 瓶颈就在这里

c 的维度固定:源句 5 个词也好、50 个也好,都塞进同一个 512 维向量——这就是信息瓶颈:句子一长,前面的信息被后面的覆盖掉。 翻译质量因此随句长急剧下降,超过 20~30 个词就基本崩了 (BLEU = 机器翻译常用的自动打分,和人工参考译文重合越多分越高;Bahdanau 论文 Figure 2 里那条 随源句变长先升后降的曲线就是它)。

互动 · 瓶颈漏斗:真跑一遍编码器,看每个词还剩多少

怎么算:下面真跑一个 16 维的编码器(真实模型 512 维,缩到 16 维只为当场算完): 每读一个词,把它的词向量 wt 加进记忆 ht = tanh(W·ht−1 + wt), 读完最后一个词,hn 就是 c;∂c/∂wj 靠反向传播算。
谁是谁:wj 是第 j 个词自己的词向量,hj 是编码器读到它时的记忆; ∂c/∂wj 是 16 个数(演示网;真实模型里是 512 个),「合起来有多大」= 对 c 的影响。
怎么看:柱子越长 = 越没被忘掉;高度按对数画,差一格 = 差 10 倍。

同一盘沙盘的第二个视角 · 句子多长会崩

横轴是源句长度 n,纵轴是下面那条曲线追踪的那个词的影响强度 ‖∂c/∂w‖,对数刻度。
ρ 是每读一个词、旧记忆被乘上的倍数:小于 1 就一步步缩小,大于 1 就一步步放大(放大过头会 梯度爆炸)。 1.25 这档短句还撑得住,句子一长,前面的词照样被压没。
灰色带(1e-2 ~ 1e2)是梯度能正常传播的数量级区间——上下一两个数量级都算健康。 曲线掉出下边缘 = 梯度消失,穿出上边缘 = 梯度爆炸。
把「记忆保留率」三档依次点一遍,你会看到一件很反直觉的事: 调强记忆并不能救长句。

🎯 类比

像一个翻译,对方说了一整段话,他必须凭记忆全部记住, 然后不许再看笔记,一口气翻译出来。
短句还行,长段落必然丢三落四。 注意力要做的事就是:允许他翻译到哪儿,就翻回去看一眼原文对应的位置。

2

注意力:每一步都重新"扫一眼"原文

改动只有一处:不再只用编码器最后那个向量 c,而是把编码器每一步的输出都留着 (记作 h₁, h₂, ..., hn)。解码器每生成一个词时,先算一下 "原文的每个位置我该看多少",再按权重把它们加权加起来。 代价是内存:原来只存一个 c(512 个数),现在要存 n 组 h(n×512 个数)。

把鼠标移到公式里有虚线的部分上——它会点亮这一章对应的那根滑杆。

这三步就是注意力的骨架。至于「分到底怎么打」——凭什么用点积、为什么除以 √dk、 为什么后来变成 Q/K/V 三组、为什么要有多个头——那是另一个问题,单独占一章。

→ 完整的算法在《注意力机制》那一章

这一章只需要记住「每一步回头看一眼原文」这个动作本身。 上面那三步背后的完整推导(从「加权平均」出发,一路推到 softmax(QKᵀ/√dk)V)、以及 Q/K/V、多头、因果掩码、O(n²) 从哪来, 全部在 3.6《注意力机制》——就是下一章。

M

数学 · 三步:打分 → 归一化 → 加权求和

§2 那三步只有十几个字。下面把它算一遍—— 公式里每个符号,图上都有一块东西对应它。

核心大图 · 一张图走完三步:谁被打分、分到多少、混成什么

微型图解 · 「加权求和」在几何上是这样:五支箭按 α 的比例合成一支

橙色那一支就是 c。它一定落在五支箭围成的扇形里—— 因为 α 全是正数、加起来等于 1,c 只能是一支「混合箭」,不可能跑到外面去。

s—要生成哪个译文词 ej—打分(点积 ÷ √d) αj—softmax 之后(和 = 1) c—加权求和的结果
🎬 自己验一遍

先把上面那根「除以 √d 的 d」拖到 64,再拖到 1(默认是 8,三种都能拖到): 看上面读数里 e 那一行——最大的数会从 0.16 涨到 1.31,α 那一行跟着越来越尖。 这就是第 4 节那个互动说的同一件事:softmax 对分数的尺度极其敏感。

📐 看不明白 softmax?它只做一件事

把一排随便什么数变成一排正数、且加起来正好等于 1 的权重。 本来懂就跳过这一块。

3

亲手看一次对齐

下面每个词有一个手工设定的「概念向量」(8 个数;同一个意思的中英词共享一维,点积才会真的对上)。 s₁ 是解码器第 1 步的状态,hj 是编码器留下的第 j 个记忆: 把 s 和 hj 对应位置相乘再相加,就是点积——乘积越大,两边意思越匹配。 再除以 √d、过 softmax。点译文里的词,看它的注意力落在原文哪些位置上。

互动 · 真实的点积注意力

互动 · 同一例子,两种打分器各画一张对齐图

左边是 Luong 的乘性打分 s · h(点积):打分函数本身一个参数都没有——靠编码器/解码器把向量学好,权重自然对上。 右边是 Bahdanau 的加性打分 vᵀ·tanh(W₁h + W₂s),其中 W₁、W₂、v 是参数,要从数据里学。
TV 距离量的是两张对齐图差多少:0 = 完全一样,1 = 完全不重叠;掉到 0.05 以下就基本学会了。
右边刚开始是纯随机初始化——两张图能差多远?把下面的滑块从 0 拖到 500:每一步都是真的梯度下降 (交叉熵损失 + 反向传播 + SGD 更新),看 TV 怎么掉下去。

💡 注意那张图上的"对角线"

对于语序相同的语言对,对齐矩阵会明显偏向同一条对角线——第 i 个译文词最看重的往往就是第 i 个原文词。
但如果是"我 昨天 看见 一只 猫" → "I saw a cat yesterday",对角线会在 yesterday 处拐一下—— 因为语序变了。这个"拐弯"正是注意力比固定向量强的地方:它不要求两种语言的语序一致。

4

怎么打分:两种经典做法

两种打分器的区别只有一行公式。 「为什么最后是点积赢了」的完整推导在 《注意力机制》第 9 节, 「为什么除以 √d」的推导与实测在第 5 节。

对比项Bahdanau(加性)Luong(乘性 / 点积)
公式 vT · tanh(W₁h + W₂s) sT · h(或 sT·W·h)
计算成本较高(要过一层小网络)很低(就是矩阵乘法)
效果在隐藏维度很大时稍稳维度小时和 Bahdanau 打平
谁赢了2014 年提出,早期主流 2015 年提出,成为后来所有注意力的基础(Transformer 用的就是点积)
为什么要除以 √d 点积的方差随维度 d 线性增长。d 大时分数会变得极大, softmax 之后就变成"只有一个位置是 1,其余全是 0"——梯度消失。 除以 √d 把方差拉回 1。

互动 · 为什么非要除以 √d:把隐藏维度调大试试

随机生成 d 维的 s 和 5 个 h,真做点积、真过 softmax。右图取 5 次试验里最典型的一次, 左边数字是平均值(单次只有 5 个数,噪声大)。
极差 = 最大分 − 最小分;熵越大分布越平均、越小越集中在少数位置 (5 个位置完全平均时熵 = ln5 = 1.609,这是上限)。
拖动滑块看 d 从 8 涨到 512:注意「平均熵」那一列——原始点积掉向 0,除以 √d 的那行留在 1 以上。

原始点积的极差随 d 一起变大:d 个独立项相加,方差是 d 而不是 1。 极差一大,softmax 就退化成 one-hot——只有最大的那个位置拿到梯度,其余全被压成 0。

5

注意力解决的和没解决的

问题注意力解决了吗
长句信息瓶颈 解决了。解码器每一步都能直接访问编码器所有位置,不再经过压缩
长距离梯度 大幅改善。从"必须穿过 n 个时间步"变成"一步直达"
语序不一致 解决了。对齐是学出来的,不要求单调
可解释性 顺带一个福利:权重可以画出来看,知道模型"在看哪"
不能并行 完全没解决。编码器和解码器还都是 RNN,还是串行的
计算量 变大了。每生成一个词都要和所有源位置算一遍

互动 · 注意力把「要穿多少步」压成 1 步

互动 · 省下的那部分代价去哪了:比对次数从 m 涨到 m × n

💡 通往 Transformer 的最后一步

2017 年那篇论文的标题就是答案:《Attention Is All You Need》——把 RNN 整个删掉。
既然注意力已经能让任意两个位置直接通信,那循环带来的"顺序感"还有什么用? 删掉 RNN 之后:所有位置可以同时计算(并行),路径长度变成 O(1), 而且注意力本身可以叠很多层。
代价是:没了循环,就没了"词序"的概念,必须另外加位置编码 (《位置编码与长上下文》会补上这一课)。

6

小结

这一章真正想说的只有一句: 对付“信息太多装不下”的办法,不是把盒子做大,是不要盒子。 而这背后的张力,属于第一性原理那七条线里的第一条(下面那张表是另一套问题清单:六条暗线)。

它对应哪条线 ① 表达力 vs 泛化——第 3 节那个小实验: 给打分器装上三组可学的矩阵,它只能追上零参数的点积。多出来的表达能力没被数据用上。
一句话 取舍是:不再把源句压成一个向量(有损压缩), 而是把编码器每一步的输出全留着,需要的时候现查现加权—— 用更高的计算量换掉了信息瓶颈。 而“怎么查”这一步,最后被交给了一个几乎不需要学的函数(点积)—— “意思对得上,点积就大”这个假设是预先写进结构里的(这就是先验)。
它牺牲了什么 牺牲了成本:每生成一个词都要和所有源位置算一遍, 计算量从一个向量变成 O(n) 次比对;也牺牲了「相邻的更相关」这个假设(回扣暗线 B、E)。
💡 检验一下:你现在能指着哪个互动说这句话

回到第 3 节第二个互动(两种打分器各画一张对齐图)。 把最下面那个「训练步数」滑块从 0 拖到 500:右边加性打分器的三个矩阵, 每一步都是真的反向传播 + SGD,TV 从 0.5 左右掉下去,学会了左边那张图的形状。

但左边那张图的成本:一个参数都没有。500 步梯度、三组矩阵和一份训练数据,换回来的只是“追平”。 这就是「表达力 vs 泛化」:当结构本身就带着正确答案的形状(“意思对得上,点积就大”), 再加参数只是给它一个变笨的机会。

它在暗线里站在哪

暗线这一章的回答
A 信息流动 数据形状:源句 [nsrc] → 编码器 → [nsrc, h](这一章最大的变化:从“只要最后一个”变成“全都留着”)→ 每个解码步拿 [h] 和所有源位置算分 → softmax → 加权求和 → 输出 [V]。(记号:nsrc 源句词数、ntgt 译文词数、h 记忆宽度、V 词表大小。) 信息不再被压扁,而是按需抽取——这是它和 Seq2Seq 的根本差别
B 什么被牺牲了 牺牲了 O(n) 的成本(每步和所有源位置比对)和「相邻的词更相关」这个假设; 换回来的是任意两个位置一步直达,路径从 O(n) 降到 O(1)。 但“不能并行”没解决:编码器和解码器还是 RNN,串行链没变
C 参数账本 注意力自己加了多少参数?加性(Bahdanau)W₁、W₂、v:h=512 时 ≈ 52 万, 和一个单层 RNN 相当;乘性(Luong 全局)一个 h×h,≈ 26 万;点积:0。
对比 Seq2Seq 原文(4 层编码器 + 4 层解码器、每层 1000 维, 两个 LSTM 共 ≈ 6400 万):救回长句的部件不到总参数的 1%
D 跑在什么上 训练和推理分开看:训练时把正确译文整句喂进去(teacher forcing), 所有位置的分数能攒成一次大矩阵乘([ntgt, nsrc])—— 一次大乘法比很多次小乘法快,这一步已经暗示了 Transformer;
推理时每生成一个词才能算下一次注意力,只能逐字串行,最贵的是等上一个词
E 它假设了什么 ① 相关性可以用一个打分函数表示——每个位置都得分一点权重,不能干脆跳过不看(这叫软对齐)。
② 看一眼原文 = 对原文的表示做加权平均——α 是非负、和为 1 的 softmax,c 只能是 h 的混合。
③ 位置本身不重要——去掉 RNN 之后词序就真的一无所有了, 所以后面位置编码必须被显式加回来
F 违背了哪个直觉 “计算量更大 = 更慢”——在 GPU 上不一定:n 次小比对凑成一整块大矩阵乘,反而可能更快。
“长句就得把记忆加大”——错:救回长句的是零参数的点积。
“加模块 = 加成本”——这次它直接拆掉了瓶颈:固定向量 c 消失了
🎯 前后钩子

它接住了上一章的什么:LSTM(《LSTM / GRU》)保住了记忆, 最后还是得把它压成一个向量交给解码器;这一章把压缩这一步取消了。

它给下一章留了什么:注意力已经让任意两个位置直接对视,循环还剩什么用? 把 RNN 也删掉,就只剩注意力本身了——完整推导在《注意力机制》。

一句话带走注意力

注意力只有三步:打分 → softmax 归一化 → 加权求和。
它解决的问题是 Seq2Seq 的固定长度瓶颈——让解码器每一步都能回头直接看原文, 而不是靠一个被压缩过很多次的向量。
它顺手解决了长距离梯度、语序不一致、可解释性三个问题,但没解决并行。 再往后两章把 RNN 删掉,就得到 Transformer。

7

拓展阅读

上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。

📄 这一章的说法从哪来

💻 工业界怎么写

这一章讲的注意力,工业界真的在跑——下面就是那个解码器。 注意这一段只是解码器的一步;完整的训练程序要再往后两章,在 《Transformer 自注意力》里。

∑ 更严格的形式