Seq2Seq
Sequence-to-Sequence(序列到序列)模型由 Sutskever et al. (2014) 和 Cho et al. (2014) 分别独立提出,是第一个成功解决变长输入→变长输出问题的神经网络架构。它开创了编码器-解码器(Encoder-Decoder)范式,这个范式后来被Transformer继承并发扬光大。
为什么需要Seq2Seq?
在RNN和LSTM笔记中,我们看到了几种输入输出结构:
- Many-to-One(情感分析):输入一个序列,输出一个标签
- Many-to-Many 同步(NER):输入和输出长度相同,一一对应
但很多任务的输入和输出长度不同,而且无法预知输出长度:
| 任务 | 输入 | 输出 | 长度关系 |
|---|---|---|---|
| 机器翻译 | "我喜欢猫"(3词) | "I like cats"(3词) | 可能相同也可能不同 |
| 文本摘要 | 一篇1000词的文章 | 50词的摘要 | 输出远短于输入 |
| 对话系统 | "你好吗?"(3词) | "我很好,谢谢你的关心"(7词) | 输出长于输入 |
| 语音识别 | 16000帧音频 | "今天天气真好"(5词) | 输入远长于输出 |
一个RNN/LSTM无法同时处理这种情况——它每个时间步消耗一个输入并产生一个输出,输入和输出被绑定在同一条时间线上。
Seq2Seq的解决思路:把问题拆成两个阶段:
- 编码器(Encoder):读完整个输入序列,压缩成一个固定大小的向量
- 解码器(Decoder):从这个向量出发,逐步生成任意长度的输出序列
两个阶段用两个独立的LSTM,中间通过一个上下文向量连接。
架构详解
完整架构图
下图展示了Seq2Seq的编码器-解码器结构(图源:Dive into Deep Learning):
各层的详细结构:
下面是用文字符号标注的等价示意图:
Encoder Decoder
(读入源语言) (生成目标语言)
"我" "喜欢" "猫" <BOS> "I" "like" "cats"
↓ ↓ ↓ ↓ ↓ ↓ ↓
Embed Embed Embed Embed Embed Embed Embed
↓ ↓ ↓ ↓ ↓ ↓ ↓
┌──────┐ ┌──────┐ ┌──────┐ ┌────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ LSTM │→│ LSTM │→│ LSTM │───→│ c │───→│ LSTM │→│ LSTM │→│ LSTM │→│ LSTM │
│ E │ │ E │ │ E │ │ │ │ D │ │ D │ │ D │ │ D │
└──────┘ └──────┘ └──────┘ └────┘ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘
h₁ᵉ h₂ᵉ h₃ᵉ 上下文向量 │ │ │ │
= h₃ᵉ Wᵥ+soft Wᵥ+soft Wᵥ+soft Wᵥ+soft
↓ ↓ ↓ ↓
"I" "like" "cats" <EOS>
关键设计:
- Encoder和Decoder是两个独立的LSTM,参数不共享
- 它们之间的连接点就是上下文向量 \(c\)
- Decoder是自回归的:每一步的输出作为下一步的输入
数学公式
编码阶段:
Encoder就是把LSTM在输入序列上跑一遍前向传播。如RNN笔记所述,每一步就是四次矩阵乘法(LSTM的四个门)。跑完 \(T\) 步后,最终隐藏状态 \(h_T^{\text{enc}}\) 就是上下文向量。
解码阶段:
Decoder也是LSTM前向传播,但有两个不同: 1. 初始状态来自Encoder(不是零向量) 2. 每一步的输入是上一步的预测结果(推理时)或真实目标词(训练时)
完整翻译示例(带数值)
翻译 "我 喜欢 猫" → "I like cats"。
设定:嵌入维度 \(d = 4\),隐藏维度 \(d_h = 4\),英文词表 \(V = 30000\)。
编码阶段
每一步都是LSTM前向传播(四次矩阵乘法 + 细胞状态更新,详见LSTM笔记)。
初始: h₀ᵉ = [0,0,0,0], C₀ᵉ = [0,0,0,0]
Step 1: x₁ = Embed("我") = [0.21, -0.45, 0.73, 0.12]
→ LSTM四个门的矩阵乘法
→ C₁ᵉ = [0.14, -0.27, 0.06, 0.07]
→ h₁ᵉ = [0.09, -0.11, 0.04, 0.02]
Step 2: x₂ = Embed("喜欢") = [0.65, 0.33, -0.18, 0.51]
→ 拼接 [h₁ᵉ; x₂],四次矩阵乘法
→ C₂ᵉ = [0.45, -0.17, -0.40, 0.37]
→ h₂ᵉ = [0.20, -0.11, -0.21, 0.14]
Step 3: x₃ = Embed("猫") = [0.82, -0.31, 0.56, 0.08]
→ 拼接 [h₂ᵉ; x₃],四次矩阵乘法
→ C₃ᵉ = [0.61, 0.05, -0.33, 0.52]
→ h₃ᵉ = [0.38, 0.03, -0.18, 0.29]
上下文向量:
4个浮点数,编码了"我喜欢猫"的全部语义信息。
解码阶段
Step 1:生成 "I"
输入: y₀ = Embed(<BOS>) = [0.01, 0.02, -0.01, 0.03]
初始: h₀ᵈ = c = [0.38, 0.03, -0.18, 0.29] ← 来自Encoder
C₀ᵈ = C₃ᵉ = [0.61, 0.05, -0.33, 0.52] ← 来自Encoder
(a) Decoder LSTM前向传播(四次矩阵乘法):
(b) 投影到词表 — \(W_{\text{vocab}} \in \mathbb{R}^{30000 \times 4}\) 乘以 \(h_1^{\text{dec}}\):
\(W_{\text{vocab}}\) 的每一行代表一个英文词。矩阵乘法的本质是计算 \(h_1^{\text{dec}}\) 与每个词的"匹配度":
(c) Softmax归一化:
所有30000个概率之和 = 1。"I"概率最高,输出 "I"。
Step 2:生成 "like"
输入: y₁ = Embed("I") ← 上一步的输出
状态: h₁ᵈ, C₁ᵈ ← 上一步的LSTM状态
→ LSTM前向传播 → \(h_2^{\text{dec}}\) → 投影 → Softmax
"like"最高,输出 "like"。(注意"love"也有0.15的概率——"喜欢"可以翻译为like或love)
Step 3-4:生成 "cats" 和 <EOS>
同样的流程,最终输出 "cats" 和结束标记。
训练过程
Teacher Forcing
训练时,Decoder每一步的输入不是模型自己的预测,而是真实的目标词:
训练时的Decoder输入: <BOS> "I" "like" "cats" ← 全是真实标签
↓ ↓ ↓ ↓
[LSTM_D] → [LSTM_D] → [LSTM_D] → [LSTM_D]
↓ ↓ ↓ ↓
训练时的目标输出: "I" "like" "cats" <EOS> ← 计算交叉熵损失
即使模型在Step 2预测错了(比如预测成"love"),Step 3依然喂入正确的"like"。这样做的好处:
- 加速收敛:每一步都从正确的上文出发,避免错误积累
- 稳定训练:早期模型几乎全部预测错误,如果靠自己的预测输入,训练信号极其嘈杂
损失函数:每个时间步的交叉熵之和
其中 \(y_t^*\) 是真实目标词,\(T'\) 是目标句子的长度。
Exposure Bias(暴露偏差)
Teacher Forcing带来一个问题:训练和推理的输入分布不一致。
训练: 每一步看到的都是正确答案 → 模型习惯了"完美的"前文
推理: 每一步看到的是自己的预测 → 一旦某步出错,后续步骤从未见过的分布开始
例: 训练时总是看到 "I like ___"
推理时如果预测成 "I love ___" ← 训练时没见过这个前缀
后续预测可能完全跑偏
缓解方法包括: - Scheduled Sampling:训练时以一定概率用模型自己的预测替代真实标签,概率随训练进行逐渐增大 - Beam Search(见下文):推理时不贪心地选最高概率,而是保留多个候选
推理策略
贪心搜索(Greedy Search)
最简单的策略:每一步选概率最高的词。
问题:局部最优不等于全局最优。
Step 1: P("I")=0.72 P("The")=0.20 → 选"I"
Step 2: P("like")=0.68 P("love")=0.15 → 选"like"
但如果选了"The":
Step 1: P("The")=0.20
Step 2: P("cat")=0.85 ← 这条路径后续概率很高
→ "The cat likes me" 可能整体概率更高
Beam Search(束搜索)
同时维护 \(k\) 条最优候选路径(\(k\) 称为beam width/束宽):
Beam Width = 2
Step 1: 保留top-2:
路径A: "I" (log P = -0.33)
路径B: "The" (log P = -1.61)
Step 2: 对每条路径展开所有可能,保留全局top-2:
路径A1: "I like" (log P = -0.33 + (-0.39) = -0.72)
路径A2: "I love" (log P = -0.33 + (-1.90) = -2.23)
路径B1: "The cat" (log P = -1.61 + (-0.16) = -1.77)
路径B2: "The dog" (log P = -1.61 + (-2.30) = -3.91)
→ 保留: A1="I like" (-0.72), B1="The cat" (-1.77)
Step 3: 继续展开...
最终选择累计对数概率最高的完整路径。
Beam Search的时间复杂度是贪心搜索的 \(k\) 倍,但翻译质量显著提高。实践中常用 \(k = 4 \sim 10\)。
长度归一化:长句子的累计对数概率总是比短句子低(因为每一步都乘一个<1的概率)。为了公平比较不同长度的候选:
\(\alpha\) 通常取0.6~0.7。
固定向量瓶颈
问题
不管源语言句子有多长,所有信息都被压缩到一个固定长度的向量 \(c\) 中:
12个词 → 256个浮点数。信息一定会损失。
实验证据:Cho et al. (2014) 发现,当句子长度超过20-30个词时,Seq2Seq的BLEU分数急剧下降。
为什么是瓶颈?
从信息论角度:假设每个词平均携带 \(H\) bits信息,句子有 \(T\) 个词,总信息量为 \(T \cdot H\)。上下文向量是 \(d_h\) 维的float32向量,最大信息容量为 \(32 \cdot d_h\) bits。
当 \(T\) 足够大时,\(T \cdot H > 32 \cdot d_h\),信息必然丢失。
类比:要求你读完一整本书后只写一句话来概括,然后另一个人根据这句话把整本书翻译出来。显然不可能。
解决方案 → Attention
下图展示了引入Attention后的Seq2Seq——Decoder可以动态关注Encoder的不同部分(图源:Jay Alammar):

Bahdanau et al. (2014) 提出了注意力机制:让Decoder在每一步都能回头看Encoder的所有隐藏状态,而不只是最后一个。
Seq2Seq: Encoder → c(一个固定向量)→ Decoder
Seq2Seq+Attention: Encoder → h₁, h₂, ..., hₜ(所有隐藏状态)
↑ ↑ ↑
Decoder每一步动态选择关注哪些
详见注意力机制笔记。
应用场景
机器翻译(核心应用)
Seq2Seq最初就是为翻译任务设计的。Google在2016年将其NMT系统(基于Seq2Seq + Attention)部署上线,替代了使用了10年的基于统计的翻译系统,翻译质量大幅提升。
文本摘要(Abstractive Summarization)
Encoder输入: 一篇长文章(可能数百个词)
Decoder输出: 一段简洁的摘要(几十个词)
与抽取式摘要(直接从原文挑选句子)不同,Seq2Seq可以生成原文中没有的句子,更像人类的摘要方式。
对话系统
Encoder输入: 用户的问题 "明天北京天气怎么样?"
Decoder输出: 系统的回答 "明天北京晴,最高气温25度"
早期的神经对话模型(如Google的Smart Reply)就是基于Seq2Seq的。
语音识别
Encoder输入: 音频特征序列(如梅尔频谱图,可能数千帧)
Decoder输出: 文本 "今天天气真好"
输入和输出的长度差异极大,完美契合Seq2Seq的设计。
Seq2Seq的历史地位与局限
| 贡献 | 说明 |
|---|---|
| 开创Encoder-Decoder范式 | 这个范式被Transformer直接继承("Attention is All You Need"中的架构仍然是Encoder-Decoder) |
| 证明了端到端学习的可行性 | 不需要手工设计翻译规则,直接从数据中学习 |
| 催生了Attention机制 | 固定向量瓶颈直接推动了Bahdanau Attention的提出 |
| 局限 | 原因 | 后续解决方案 |
|---|---|---|
| 固定向量瓶颈 | 所有信息压缩成一个向量 | Attention机制 |
| 顺序处理,无法并行 | LSTM的串行依赖 | Transformer |
| Exposure Bias | Teacher Forcing的副作用 | Scheduled Sampling, RL训练 |
| 长句子性能差 | 瓶颈 + LSTM本身的记忆限制 | Attention + Transformer |
技术演进路线:
下一步:→ 注意力机制(详解Attention的数学原理)→ Transformer架构