序列到序列 (Seq2Seq) 模型详解
序列到序列(Sequence-to-Sequence,简称 Seq2Seq)是一种将变长输入序列映射为变长输出序列的端到端学习框架。它由 Google 的 Sutskever 等人于 2014 年首次提出,最初用于机器翻译——将一段英文(变长)翻译成一段中文(变长),中间无需任何手工规则。这一范式迅速成为自然语言处理领域的基石技术,并演化为今天 Transformer 的核心思想。
核心架构:编码-解码范式
Seq2Seq 的本质是”先压缩、再展开”:编码器把输入序列压缩为一个语义表示,解码器从这个表示出发逐步生成输出序列。整个架构由三个核心组件构成:编码器、解码器、上下文向量。

编码器 (Encoder)
- 定义:编码器是一个循环神经网络(RNN / LSTM / GRU),逐时刻读取输入序列 $x = (x_1, x_2, \ldots, x_n)$,在每个时刻 $t$ 更新隐状态 $h_t = f(h_{t-1}, x_t)$,最终输出一组隐状态序列 $(h_1, h_2, \ldots, h_n)$ 和最后一个隐状态 $h_n$。
- 特点:编码器不直接输出”结果”,它的产物是隐状态序列——可以理解为输入序列的”记忆”。LSTM 和 GRU 因门控机制能缓解梯度消失,实践中几乎完全取代了普通 RNN。双向编码器(BiLSTM / BiGRU)同时从左到右和从右到左扫描,能捕获更完整的上下文信息。
- 注意:编码器的隐状态维度是一个关键超参数。维度太小,语义信息表达不足;维度太大,计算开销和过拟合风险增加。常见选择为 256、512 或 1024。
解码器 (Decoder)
- 定义:解码器同样是一个循环神经网络,但它的工作方式是自回归的——在每个时刻 $t$,它以前一时刻的输出 $y_{t-1}$ 和当前隐状态 $s_t$ 为输入,预测当前时刻的输出 $y_t$,直到生成结束标记<EOS> 为止。
- 特点:解码器的第一步输入通常是特殊的起始标记<BOS>(Beginning of Sequence)。在训练阶段,解码器每一步接收的是真实标签(Teacher Forcing),而非模型自己的预测;在推理阶段,则只能用上一步的实际输出作为输入。这种训练-推理的差异被称为”暴露偏差”(exposure bias),是 Seq2Seq 的已知痛点之一。
- 注意:解码器和编码器可以使用不同类型的 RNN、不同数量的层数、不同的词表。例如,英译中模型中编码器使用英文词表,解码器使用中文词表,两者完全独立。
上下文向量 (Context Vector)
- 定义:上下文向量 $C$ 是编码器传递给解码器的”桥梁”。在最基础的 Seq2Seq 中,$C$ 就是编码器的最终隐状态 $h_n$,直接作为解码器的初始隐状态 $s_0 = C$。
- 特点:上下文向量的维度是固定的(等于编码器隐状态维度),与输入序列长度无关。这意味着无论输入是 5 个词还是 50 个词,信息都被压缩到同一个维度的向量中。
- 注意:这正是 Seq2Seq 的核心瓶颈——固定维度的向量难以承载长序列的全部信息。当一个句子有 40 个词时,要求 $h_n$”记住”所有细节是不现实的,编码器会不可避免地丢失早期信息。这一问题直接催生了注意力机制。

注意力机制:突破固定向量瓶颈
注意力机制让解码器在每一步都能”回看”编码器的所有隐状态,动态决定该关注输入的哪些位置,而不是被迫只依赖一个固定向量。这一机制由 Bahdanau 等人于 2015 年提出,是 Seq2Seq 架构最重要的改进。
注意力的核心思想
在基础 Seq2Seq 中,解码器每一步只能通过初始隐状态 $s_0 = h_n$ 获取输入信息。注意力机制的思路是:解码器在生成第 $t$ 个输出词时,不再只依赖 $s_t$,而是计算一个”动态上下文向量” $c_t$——它是编码器所有隐状态的加权平均,权重由当前解码器状态 $s_t$ 与各编码器隐状态 $h_i$ 的相似度决定。
直观地说,翻译 “I love deep learning” 时,生成 “深” 这个字,注意力应集中在 “deep” 上;生成 “学” 时,注意力应集中在 “learning” 上。注意力权重正好捕捉了这种对齐关系。
注意力计算流程
注意力的计算分三步:对齐打分 → 归一化 → 加权求和。
第一步:对齐打分。给定解码器当前隐状态 $s_t$ 和编码器第 $i$ 个隐状态 $h_i$,计算一个标量分数 $e_{t,i} = \text{score}(s_t, h_i)$,衡量 $s_t$ 与 $h_i$ 的相关程度。常见的打分函数有三种:
- Bahdanau (Additive):$e_{t,i} = v^\top \tanh(W_s s_t + W_h h_i)$,通过一个小的前馈网络计算分数。
- Luong Dot:$e_{t,i} = s_t^\top h_i$,直接做内积,要求 $s_t$ 和 $h_i$ 维度相同。
- Luong General:$e_{t,i} = s_t^\top W h_i$,在内积基础上加一个可学习矩阵 $W$。
第二步:归一化。对所有分数做 Softmax,得到注意力权重:
$$\alpha_{t,i} = \frac{\exp(e_{t,i})}{\sum_{j=1}^{n} \exp(e_{t,j})}$$
第三步:加权求和。用注意力权重对编码器隐状态加权求和,得到当前时刻的上下文向量:
$$ c_t = \sum_{i=1}^{n} \alpha_{t,i} \cdot h_i$$
最终,解码器将 $s_t$ 和 $c_t$ 拼接后送入输出层预测 $y_t$。注意力机制使每个输出词都能”看到”所有输入词,且权重 $\alpha_{t,i}$ 可视化后即为对齐矩阵——这为模型提供了天然的可解释性。

为什么需要 Seq2Seq
在 Seq2Seq 出现之前,处理”变长输入到变长输出”的任务主要依赖两类方法:统计机器翻译(SMT)和基于规则的系统。它们各有明显短板。
- SMT 的困境:统计机器翻译(如基于短语的 Moses 系统)需要大量人工设计的特征——词对齐模型、短语提取、语言模型重排序等。整个流水线被切分为多个独立阶段,每个阶段只能局部优化,错误会逐级累积。开发者需要深厚的语言学知识和工程经验才能调出可用的系统。
- 固定维度模型的局限:传统的分类模型(如 CNN 分类器)要求输入和输出维度固定。如果要翻译一个 40 词的句子,要么截断超出部分(丢失信息),要么填充到最大长度(浪费计算)。更关键的是,CNN 的卷积核感受野有限,难以建模词与词之间的长距离依赖。
Seq2Seq 带来了三个根本性改进:
- 端到端学习:从输入到输出只需一个神经网络,无需手工特征工程,梯度可以从输出端直接回传到输入端,全局联合优化。
- 变长映射:输入和输出长度可以不同,由<EOS> 标记自动决定终止时机,天然适配翻译、摘要、对话等任务。
- 可解释性:注意力权重矩阵直接呈现源词与目标词的对齐关系,让模型行为不再是黑箱——这在调试和信任建立方面价值巨大。
模型演进与对比矩阵
Seq2Seq 架构经历了三代演进:基础 RNN 编码器-解码器、引入注意力机制、全面转向 Transformer。下表从六个维度对比这三代架构的核心差异。
| 维度 | RNN Encoder-Decoder (2014) | RNN + Attention (2015) | Transformer (2017) |
| 编码器 | LSTM / GRU | BiLSTM / BiGRU | 多层自注意力 |
| 上下文传递 | 固定向量 $h_n$ | 动态加权上下文 $c_t$ | 全程隐状态序列(无压缩) |
| 注意力 | 无 | Bahdanau / Luong | 自注意力(Self-Attention) |
| 并行性 | 编码器可并行,解码器串行 | 编码器可并行,解码器串行 | 训练时编码器和解码器均可并行 |
| 长距离依赖 | 差(信息在 $h_n$ 处衰减) | 较好(任意位置可直接访问) | 优秀(任意两位置直连) |
| 代表模型 | seq2seq (Sutskever 2014) | Bahdanau NMT, Luong NMT | Transformer, BERT, GPT |
从表中可以看出,演进的核心驱动力是消除信息瓶颈:从固定向量到动态加权,再到完全保留序列信息;同时从串行走向并行,大幅提升训练效率。
从零实现:PyTorch 代码与场景示例
本节用 PyTorch 实现一个完整的带 Bahdanau 注意力的 Seq2Seq 模型,涵盖编码器、注意力层、解码器和训练循环,并展示训练与推理两种工作模式的差异。
编码器实现
编码器将输入序列(已转为索引张量)通过 Embedding 层和 GRU 处理,输出隐状态序列和最终隐状态。
import torch
import torch.nn as nn
import torch.nn.functional as F
class Encoder(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, num_layers=1, dropout=0.1):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
self.rnn = nn.GRU(embed_dim, hidden_dim, num_layers,
batch_first=True, bidirectional=True)
self.dropout = nn.Dropout(dropout)
# 双向 GRU 输出 2*hidden_dim,需投影到 hidden_dim 供解码器使用
self.fc = nn.Linear(hidden_dim * 2, hidden_dim)
def forward(self, src):
# src: [batch, seq_len]
embedded = self.dropout(self.embedding(src)) # [batch, seq_len, embed_dim]
outputs, hidden = self.rnn(embedded) # outputs: [batch, seq_len, 2*hidden]
# 拼接双向最后隐状态并投影
hidden = torch.cat([hidden[-2], hidden[-1]], dim=1) # [batch, 2*hidden]
hidden = torch.tanh(self.fc(hidden)) # [batch, hidden]
return outputs, hidden
Bahdanau 注意力层
注意力层接收解码器当前隐状态和编码器全部输出,计算对齐分数并返回上下文向量和注意力权重。
class BahdanauAttention(nn.Module):
def __init__(self, hidden_dim):
super().__init__()
# 解码器隐状态投影
self.W_s = nn.Linear(hidden_dim, hidden_dim, bias=False)
# 编码器输出投影
self.W_h = nn.Linear(hidden_dim * 2, hidden_dim, bias=False)
# 对齐打分向量
self.v = nn.Linear(hidden_dim, 1, bias=False)
def forward(self, decoder_hidden, encoder_outputs):
# decoder_hidden: [batch, hidden]
# encoder_outputs: [batch, seq_len, 2*hidden]
# 扩展解码器隐状态以便与编码器输出逐位置配对
# [batch, 1, hidden] -> [batch, seq_len, hidden]
dec_proj = self.W_s(decoder_hidden).unsqueeze(1)
# [batch, seq_len, hidden]
enc_proj = self.W_h(encoder_outputs)
# 对齐打分: v^T * tanh(W_s * s_t + W_h * h_i)
# score: [batch, seq_len, 1]
score = self.v(torch.tanh(dec_proj + enc_proj))
# 注意力权重 (Softmax 归一化)
# alpha: [batch, seq_len]
alpha = F.softmax(score.squeeze(-1), dim=1)
# 加权求和得到上下文向量
# context: [batch, 2*hidden]
context = torch.bmm(alpha.unsqueeze(1), encoder_outputs).squeeze(1)
return context, alpha
解码器实现
解码器在每个时刻接收当前输入词、前一时刻隐状态和编码器输出,通过注意力计算上下文向量后预测输出词。
class Decoder(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, num_layers=1, dropout=0.1):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
self.attention = BahdanauAttention(hidden_dim)
# GRU 输入 = embed_dim + 2*hidden_dim (上下文向量维度)
self.rnn = nn.GRU(embed_dim + hidden_dim * 2, hidden_dim,
num_layers, batch_first=True)
# 输出层输入 = hidden + context + embed
self.fc_out = nn.Linear(hidden_dim + hidden_dim * 2 + embed_dim, vocab_size)
self.dropout = nn.Dropout(dropout)
def forward(self, input_token, hidden, encoder_outputs):
# input_token: [batch] (当前时刻输入词索引)
# hidden: [batch, hidden]
# encoder_outputs: [batch, seq_len, 2*hidden]
embedded = self.dropout(self.embedding(input_token)) # [batch, embed]
# 计算注意力上下文
context, alpha = self.attention(hidden, encoder_outputs) # [batch, 2*hidden]
# 拼接 embedding 和 context 作为 GRU 输入
rnn_input = torch.cat([embedded, context], dim=1).unsqueeze(1) # [batch, 1, embed+2*hidden]
output, hidden = self.rnn(rnn_input, hidden.unsqueeze(0)) # output: [batch, 1, hidden]
# 拼接所有信息送入输出层
output = output.squeeze(1) # [batch, hidden]
pred_input = torch.cat([output, context, embedded], dim=1)
prediction = self.fc_out(pred_input) # [batch, vocab_size]
return prediction, hidden.squeeze(0), alpha
Seq2Seq 封装与训练
将编码器和解码器组合为完整模型,训练阶段使用 Teacher Forcing——每一步将真实标签而非模型预测喂给解码器。
class Seq2Seq(nn.Module):
def __init__(self, encoder, decoder, device):
super().__init__()
self.encoder = encoder
self.decoder = decoder
self.device = device
def forward(self, src, trg, teacher_forcing_ratio=0.8):
# src: [batch, src_len]
# trg: [batch, trg_len]
batch_size = src.shape[0]
trg_len = trg.shape[1]
trg_vocab_size = self.decoder.fc_out.out_features
# 存储每一步的预测
outputs = torch.zeros(batch_size, trg_len, trg_vocab_size).to(self.device)
# 编码
encoder_outputs, hidden = self.encoder(src)
# 第一步输入 标记
input_token = trg[:, 0] # [batch]
for t in range(1, trg_len):
# 解码一步
prediction, hidden, alpha = self.decoder(input_token, hidden, encoder_outputs)
outputs[:, t] = prediction
# Teacher Forcing: 以概率 teacher_forcing_ratio 使用真实标签
if random.random() < teacher_forcing_ratio:
input_token = trg[:, t]
else:
input_token = prediction.argmax(dim=1)
return outputs
def inference(self, src, max_len=50, bos_idx=1, eos_idx=2):
"""推理阶段:自回归生成,无 Teacher Forcing"""
self.eval()
with torch.no_grad():
encoder_outputs, hidden = self.encoder(src)
batch_size = src.shape[0]
input_token = torch.full((batch_size,), bos_idx, dtype=torch.long).to(self.device)
output_tokens = []
for t in range(max_len):
prediction, hidden, alpha = self.decoder(input_token, hidden, encoder_outputs)
input_token = prediction.argmax(dim=1)
output_tokens.append(input_token)
# 全部样本都生成了 则提前停止
if (input_token == eos_idx).all():
break
return torch.stack(output_tokens, dim=1) # [batch, gen_len]
上述代码清晰地展示了两条执行路径:forward 方法用于训练,通过 Teacher Forcing 加速收敛;inference 方法用于推理,逐词自回归生成。
场景示例
- 场景一:英中机器翻译。传统统计机器翻译(如 Moses)需要先训练词对齐模型,再提取短语表,最后用语言模型重排序——三阶段独立优化,调试周期长达数周。引入 Seq2Seq + Attention 后,只需准备平行语料(英文-中文句对),一个模型端到端训练即可。注意力矩阵还自动呈现英中词对齐关系,无需额外设计对齐算法。BLEU 分数通常提升 3~5 分,且模型可以持续用新数据微调迭代。
- 场景二:文本摘要。传统抽取式摘要依赖 TextRank 等图排序算法,从原文中”挑选”关键句拼接——无法改写、无法概括跨句信息。Seq2Seq 生成式摘要则能”理解”全文语义后重新组织语言输出,可以浓缩、改写、合并跨段落信息。在 CNN/DailyMail 数据集上,带注意力的 Seq2Seq 模型 ROUGE-L 分数可达 28~30,显著优于纯抽取式方法的 24~26。
局限性与适用边界
Seq2Seq 并非万能方案,在以下场景中存在明确局限:
- 暴露偏差 (Exposure Bias):训练时解码器每步看到的是正确答案,推理时只能依赖自身输出。一旦某步预测错误,后续输入就是”错误”的,误差会逐步放大。缓解方法包括 Scheduled Sampling(训练时逐步降低 Teacher Forcing 比例)和 RL 微调(直接优化 BLEU 等任务指标)。
- 自回归生成速度慢:解码器必须逐词生成,无法并行化。翻译一个 50 词的句子需要 50 次前向传播。这一局限在 Transformer 中通过非自回归解码(Non-Autoregressive Decoding)部分缓解,但质量仍不如自回归方法。
- RNN 长距离衰减未根除:即使有注意力机制,RNN 编码器本身仍按时刻串行处理,相隔很远的词之间的梯度仍然需要经过多步传递。Transformer 用自注意力彻底解决了这一问题——任意两个位置之间只经过一次矩阵乘法即可交互。
- 生成重复与不连贯:Seq2Seq 在长文本生成中容易出现重复短语(如”我觉得我觉得”)或中途话题漂移。这通常需要 Beam Search、长度惩罚、重复惩罚等解码策略来缓解,但治标不治本。
结论
Seq2Seq 的核心贡献在于确立了”编码-解码”这一处理变长序列映射的通用范式,注意力机制则让这一范式真正实用化。从 2014 年的 RNN 编码器-解码器,到 2015 年的注意力机制,再到 2017 年 Transformer 全面采用自注意力,演进的主线是消除信息瓶颈、增强长距离建模能力、提升并行效率。
对于实践者而言:如果任务是翻译、摘要或对话且算力有限,带注意力的 RNN Seq2Seq 仍然是一个轻量可行的选择;如果追求最优效果且算力充足,直接使用基于 Transformer 的模型(如 T5、BART)是当前的最佳实践。理解 Seq2Seq 的编码-解码思想和注意力机制,是理解一切现代序列生成模型的基础。
参考文献 / 扩展阅读
- Sutskever, I., Vinyals, O., & Le, Q. V. (2014). Sequence to Sequence Learning with Neural Networks.NeurIPS 2014.
- Cho, K., et al. (2014). Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation.EMNLP 2014.
- Bahdanau, D., Cho, K., & Bengio, Y. (2015). Neural Machine Translation by Jointly Learning to Align and Translate.ICLR 2015.
- Luong, M. T., Pham, H., & Manning, C. D. (2015). Effective Approaches to Attention-based Neural Machine Translation.EMNLP 2015.
- Vaswani, A., et al. (2017). Attention Is All You Need.NeurIPS 2017.





