Skip to content

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的解决思路:把问题拆成两个阶段:

  1. 编码器(Encoder):读完整个输入序列,压缩成一个固定大小的向量
  2. 解码器(Decoder):从这个向量出发,逐步生成任意长度的输出序列

两个阶段用两个独立的LSTM,中间通过一个上下文向量连接。


架构详解

完整架构图

下图展示了Seq2Seq的编码器-解码器结构(图源:Dive into Deep Learning):

Seq2Seq架构

各层的详细结构:

Seq2Seq详细层结构

下面是用文字符号标注的等价示意图:

                    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是自回归的:每一步的输出作为下一步的输入

数学公式

编码阶段

\[ h_t^{\text{enc}},\ C_t^{\text{enc}} = \text{LSTM}_{\text{enc}}(h_{t-1}^{\text{enc}},\ C_{t-1}^{\text{enc}},\ x_t) \]
\[ c = h_T^{\text{enc}} \quad \text{(取最后一步的隐藏状态作为上下文向量)} \]

Encoder就是把LSTM在输入序列上跑一遍前向传播。如RNN笔记所述,每一步就是四次矩阵乘法(LSTM的四个门)。跑完 \(T\) 步后,最终隐藏状态 \(h_T^{\text{enc}}\) 就是上下文向量。

解码阶段

\[ h_0^{\text{dec}} = c, \quad C_0^{\text{dec}} = C_T^{\text{enc}} \quad \text{(用Encoder的最终状态初始化Decoder)} \]
\[ h_t^{\text{dec}},\ C_t^{\text{dec}} = \text{LSTM}_{\text{dec}}(h_{t-1}^{\text{dec}},\ C_{t-1}^{\text{dec}},\ \text{Embed}(y_{t-1})) \]
\[ P(y_t | y_{<t}, x) = \text{softmax}(W_{\text{vocab}} \cdot h_t^{\text{dec}} + b) \]

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]

上下文向量

\[ c = h_3^{\text{enc}} = [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前向传播(四次矩阵乘法):

\[ h_1^{\text{dec}} = [0.71,\ 0.15,\ -0.23,\ 0.52] \]

(b) 投影到词表 — \(W_{\text{vocab}} \in \mathbb{R}^{30000 \times 4}\) 乘以 \(h_1^{\text{dec}}\)

\[ \text{logits} = W_{\text{vocab}} \cdot h_1^{\text{dec}} + b \in \mathbb{R}^{30000} \]

\(W_{\text{vocab}}\) 的每一行代表一个英文词。矩阵乘法的本质是计算 \(h_1^{\text{dec}}\) 与每个词的"匹配度":

\[ \text{logits} = [\underset{\text{"I"}}{2.8},\ \underset{\text{"like"}}{0.3},\ \underset{\text{"cats"}}{-0.5},\ \underset{\text{"the"}}{1.1},\ \ldots] \]

(c) Softmax归一化:

\[ P(y_1) = \text{softmax}(\text{logits}) = [\underset{\text{"I"}}{\mathbf{0.72}},\ \underset{\text{"like"}}{0.03},\ \underset{\text{"cats"}}{0.01},\ \underset{\text{"the"}}{0.08},\ \ldots] \]

所有30000个概率之和 = 1。"I"概率最高,输出 "I"。

Step 2:生成 "like"

输入:  y₁ = Embed("I")      ← 上一步的输出
状态:  h₁ᵈ, C₁ᵈ            ← 上一步的LSTM状态

→ LSTM前向传播 → \(h_2^{\text{dec}}\) → 投影 → Softmax

\[ P(y_2) = [\underset{\text{"I"}}{0.01},\ \underset{\text{"like"}}{\mathbf{0.68}},\ \underset{\text{"love"}}{0.15},\ \ldots] \]

"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"。这样做的好处:

  1. 加速收敛:每一步都从正确的上文出发,避免错误积累
  2. 稳定训练:早期模型几乎全部预测错误,如果靠自己的预测输入,训练信号极其嘈杂

损失函数:每个时间步的交叉熵之和

\[ \mathcal{L} = -\sum_{t=1}^{T'} \log P(y_t^* | y_{<t}^*, x) \]

其中 \(y_t^*\) 是真实目标词,\(T'\) 是目标句子的长度。

Exposure Bias(暴露偏差)

Teacher Forcing带来一个问题:训练和推理的输入分布不一致

训练:  每一步看到的都是正确答案 → 模型习惯了"完美的"前文
推理:  每一步看到的是自己的预测 → 一旦某步出错,后续步骤从未见过的分布开始

例: 训练时总是看到  "I like ___"
    推理时如果预测成  "I love ___"   ← 训练时没见过这个前缀
    后续预测可能完全跑偏

缓解方法包括: - Scheduled Sampling:训练时以一定概率用模型自己的预测替代真实标签,概率随训练进行逐渐增大 - Beam Search(见下文):推理时不贪心地选最高概率,而是保留多个候选


推理策略

最简单的策略:每一步选概率最高的词。

\[ y_t = \arg\max_{w} P(w | y_{<t}, x) \]

问题:局部最优不等于全局最优。

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" 可能整体概率更高

同时维护 \(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的概率)。为了公平比较不同长度的候选:

\[ \text{score}(y) = \frac{1}{|y|^\alpha} \sum_{t=1}^{|y|} \log P(y_t | y_{<t}, x) \]

\(\alpha\) 通常取0.6~0.7。


固定向量瓶颈

问题

不管源语言句子有多长,所有信息都被压缩到一个固定长度的向量 \(c\) 中:

\[ \text{"Le chat noir est assis sur le tapis rouge dans le salon"} \xrightarrow{\text{Encoder}} c \in \mathbb{R}^{256} \]

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):

Seq2Seq + Attention

Bahdanau et al. (2014) 提出了注意力机制:让Decoder在每一步都能回头看Encoder的所有隐藏状态,而不只是最后一个。

Seq2Seq:         Encoder → c(一个固定向量)→ Decoder

Seq2Seq+Attention: Encoder → h₁, h₂, ..., hₜ(所有隐藏状态)
                                    ↑ ↑ ↑
                                  Decoder每一步动态选择关注哪些
\[ c_t = \sum_{j=1}^{T} \alpha_{tj} \cdot h_j^{\text{enc}} \quad \text{(每个解码步有不同的 $c_t$)} \]

详见注意力机制笔记。


应用场景

机器翻译(核心应用)

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

技术演进路线

\[ \text{Seq2Seq} \xrightarrow{\text{+Attention}} \text{Seq2Seq+Attn} \xrightarrow{\text{去掉RNN}} \text{Transformer} \]

下一步:→ 注意力机制(详解Attention的数学原理)→ Transformer架构


评论 #