【人工智能】【深度学习】⑨ 编码器-解码器模型:从机器翻译到AI对话的通用骨架
📖目录
- 前言:快递员与翻译官的故事
- 1. 经典 RNN 编码器-解码器(Seq2Seq)
- 2. 注意力机制:给解码器一个“放大镜”
- 4. 从 Seq2Seq 到 Transformer:架构的进化
- 5. 行业应用:不止于翻译
- 6. 延伸阅读 & 经典书籍
- 🔗 7. 引导回你的系列
前言:快递员与翻译官的故事
为什么所有大模型都逃不开“先理解,再生成”?一文讲透 Seq2Seq 架构的原理、代码与演进
想象你要寄一个包裹从北京到纽约。
你不能直接把包裹扔进太平洋——你需要:
- 打包(编码):把物品装箱、贴标签、生成运单号;
- 运输+中转(隐藏状态传递);
- 拆包(解码):对方根据运单号取出物品、还原内容。
编码器-解码器(Encoder-Decoder)模型,干的就是这件事——只不过它“寄”的不是包裹,而是语义信息。
比如:
- 中文 → 英文(机器翻译)
- 语音波形 → 文字(语音识别)
- 图像 → 描述句子(图像 captioning)
- 用户问题 → AI 回答(对话系统)
💡 一句话定义:
编码器-解码器是一种将输入序列映射为输出序列的神经网络架构,由两部分组成:
- 编码器:读取输入,压缩成一个“语义向量”(context vector)
- 解码器:基于该向量,逐步生成输出序列
1. 经典 RNN 编码器-解码器(Seq2Seq)
1.1 架构图

[Input: 我 爱 中国]
↓
[Encoder RNN] → h_t (固定长度向量)
↓
[Decoder RNN] → I love China.
1.2 核心思想(大白话)
编码器像一个“速记员”,听完你说完一整句话后,记下一个摘要笔记(h_T)。
解码器像一个“翻译官”,拿着这个笔记,一句一句往外翻。
1.3 数学表达(专业性)
设输入序列为 X = (x1, x2, …, xT) ,输出为 Y = (y1, y2, …, yT’)
-
编码器(通常是 LSTM/GRU):
![[
h_t = \text{RNN}_{\text{enc}}(x_t, h_{t-1})
]](https://i-blog.csdnimg.cn/direct/94c13d5b61f24df0882b027ab1f7af5c.png)
最终得到上下文向量 ( c = h_T )
-
解码器:
![[
s_t = \text{RNN}_{\text{dec}}(y_{t-1}, s_{t-1}, c)
]
[
P(y_t | y_{<t}, X) = \text{softmax}(W s_t + b)
]](https://i-blog.csdnimg.cn/direct/e1c9c46d3081491199a2ac47ec6b8b4b.png)
⚠️ 致命缺陷:
所有输入信息被压缩到一个固定长度的向量 c 中。
当句子很长时(比如一篇论文),这个“小本本”根本记不下!
→ 这就是 信息瓶颈问题
2. 注意力机制:给解码器一个“放大镜”
此处可参考我之前的文章:【人工智能】【深度学习】 ⑤ 注意力机制:从原理到代码实现,看懂模型如何“聚焦”关键信息
2.1 改进思路(大白话):
不再让解码器只看“摘要笔记”,而是允许它随时回头查阅原文!
就像你在写作文时,可以不断翻看参考资料。
2.2 架构图

Encoder: [我]→h_1, [爱]→h_2, [中国]→h_3
↗
Decoder: 生成 "I" 时,重点关注 h_1(“我”)
生成 "love" 时,重点关注 h_2(“爱”)
2.3 核心公式
-
计算注意力权重(对每个解码步 t):

-
加权求和得到上下文向量:

-
解码器输入变为:

✅ 效果:长句翻译质量大幅提升!Google Neural Machine Translation (GNMT) 就靠它称霸一时。
2.4 核心公式深度解析:注意力机制如何“动态聚焦”?
在经典编码器-解码器模型中,解码器只能看到一个固定长度的语义向量(比如把整句话压缩成一句话摘要)。
但人类翻译时不会这样——我们会边翻边回头看原文。
注意力机制就是给 AI 装上“回头查阅”的能力。
下面我们一步步拆解它的数学原理。
2.4.1 第一步:解码器当前状态 vs 编码器所有位置 → 打分!
💡 大白话:
解码器正在生成第 t 个词(比如英文的 “love”),它想知道:
“中文原文里,哪个词最相关?是‘我’?‘爱’?还是‘中国’?”
于是它对每个中文词打一个“相关分”。
📐 数学表达(加性注意力,Bahdanau et al., 2015):

🔎 符号解释(逐个击破):
| 符号 | 含义 | 生活类比 |
|---|---|---|
![]() |
解码器在生成第 t 个词之前的隐藏状态(记忆) | 翻译官当前脑子里想的:“我要表达‘喜欢’这个意思” |
![]() |
编码器对输入第 i 个词(如“爱”)的表示 | 原文笔记里关于“爱”这个词的详细记录 |
![]() |
把两个向量拼接起来 | 把“我想表达的意思”和“原文某个词的记录”放在一起对比 |
![]() |
可学习的权重矩阵(把拼接向量映射到中间空间) | 一个“打分规则模板”,AI 自己学出来的 |
![]() |
激活函数,把数值压缩到 [-1, 1] | 防止分数爆炸,像给打分加个“冷静滤镜” |
![]() |
另一个可学习向量(把中间表示压成一个标量) | 最终的“打分笔”,输出一个具体分数 |
✅ 为什么叫“加性”注意力?
因为它通过加法+非线性变换计算相似度(区别于 Transformer 的“点积”)。
优势:即使 s 和 h 维度不同,也能打分!
2.4.2 第二步:把所有分数变成“关注度比例” → Softmax 归一化
💡 大白话:
现在有三个分数:
- “我”:0.8
- “爱”:3.2
- “中国”:0.5
但这些数字不好直接用。我们需要知道:每个词占总注意力的百分比。
就像投票:不是看绝对票数,而是看占比。
📐 数学表达:

🔎 关键点解析:
- 指数函数 exp(·):把负分变小、正分变大,放大差异(“强者更强”)
- 分母求和:确保所有 α_{t,i} 加起来等于 1(概率分布)
- 结果 α_{t,i} ∈ (0,1):表示“生成第 t 个词时,应分配给输入第 i 个词的注意力比例”
✅ 举个栗子:
如果 e = [0.8, 3.2, 0.5],则
exp(e) ≈ [2.23, 24.53, 1.65]
总和 ≈ 28.41
所以 α ≈ [0.078, 0.863, 0.058]
→ 86.3% 的注意力给了“爱”,完美!
2.4.3 第三步:用注意力权重“加权平均”编码器信息 → 上下文向量
💡 大白话:
现在我知道该重点看“爱”了,但不能只看“爱”一个词(万一上下文重要呢?)。
所以我按比例混合所有词的信息,得到一个“定制化上下文包”。
📐 数学表达:

🔎 直观理解:
- 这是一个加权求和(weighted sum)
- 权重 α_{t,i} 越大,对应的 h_i 对最终结果影响越大
- 结果 c_t 是一个动态上下文向量,专为生成第 t 个词而生
✅ 对比传统 Seq2Seq:
旧方法:c = h_T(固定,全句压缩)
新方法:c_t(动态,每步不同)→ 信息无损,灵活聚焦
2.4.4 第四步:把上下文向量喂给解码器 → 生成更准的词
💡 大白话:
翻译官现在手里有两个东西:
- 自己的记忆(s_{t-1})
- 刚查到的重点资料包(c_t)
他把这两样东西合起来,决定下一个词说什么。
📐 解码器输入更新:

其中 y_{t-1} 是上一个已生成的词(或其嵌入)。
这样,解码器每一步都既记得历史,又看得原文,翻译自然更准!
🔄 完整流程图(文字版)
[输入序列] → Encoder → [h₁, h₂, ..., h_T]
↓
[解码步 t] → s_{t-1} + {h_i} → 计算 e_{t,i} → Softmax → α_{t,i} → 加权求和 → c_t
↓
[y_{t-1}; c_t] → Decoder → y_t
📌 为什么这个设计如此重要?
| 问题 | 传统 Seq2Seq | 带注意力的 Seq2Seq |
|---|---|---|
| 长句信息丢失 | ❌ 压缩成固定向量 | ✅ 每步动态查阅原文 |
| 可解释性 | ❌ 黑箱 | ✅ 可视化 α_{t,i} 看对齐 |
| 性能上限 | 低(BLEU 分不高) | 高(Google 翻译 2016 年靠它大幅提升) |
💬 终极比喻:
传统模型像闭卷考试——考前把整本书背成一句话;
注意力模型像开卷考试——答题时可以随时翻书,只看关键页。
📘 公式出处(权威来源)
- 原始论文:Bahdanau, D., Cho, K., & Bengio, Y. (2015). Neural Machine Translation by Jointly Learning to Align and Translate. ICLR.
👉 https://arxiv.org/abs/1409.0473 - 命名由来:“Attention” 一词首次在此文中用于描述这种动态聚焦机制。
3. PyTorch 代码示例(可运行)
# ==============================
# 1. 定义带注意力的编码器-解码器
# ==============================
# =============================================================
# 文件说明:基于注意力机制的编码器-解码器模型(Encoder-Decoder with Attention)
# 用途:适用于机器翻译、文本摘要等序列到序列任务
# 特点:使用双向LSTM编码器 + 单向LSTM解码器 + 加性注意力(Bahdanau-style)
# =============================================================
import torch
import torch.nn as nn
import torch.nn.functional as F
# ==============================
# 1. 编码器(Encoder)模块
# 功能:将输入序列(如中文句子)编码为一系列隐藏状态和最终上下文表示
# ==============================
class Encoder(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim):
"""
初始化编码器
:param vocab_size: 词表大小(例如 10000)
:param embed_dim: 词嵌入维度(例如 256)
:param hidden_dim: RNN 隐藏层维度(注意:双向后实际输出为 hidden_dim * 2)
"""
super(Encoder, self).__init__()
# 词嵌入层:将单词索引转换为稠密向量
self.embedding = nn.Embedding(vocab_size, embed_dim)
# 双向LSTM:从前向后和从后向前同时读取序列,捕获更丰富的上下文
# batch_first=True 表示输入形状为 (batch, seq_len, features)
self.rnn = nn.LSTM(embed_dim, hidden_dim, batch_first=True, bidirectional=True)
def forward(self, x):
"""
前向传播
:param x: 输入张量,形状 [batch_size, src_seq_len],元素为词索引
:return:
outputs: 所有时间步的隐藏状态,形状 [batch_size, src_seq_len, hidden_dim * 2]
hidden: 最后一个时间步的正向+反向隐藏状态拼接,形状 [batch_size, hidden_dim * 2]
"""
# 将输入词索引转换为词向量(嵌入)
# 输入 x: [batch, src_len] → 输出 embedded: [batch, src_len, embed_dim]
embedded = self.embedding(x)
# 通过双向LSTM处理嵌入序列
# outputs: 每个时间步的输出(拼接正向+反向),形状 [batch, src_len, hidden_dim*2]
# (hidden, cell): 最后时刻的隐藏状态和细胞状态,各为 [2, batch, hidden_dim]
outputs, (hidden, cell) = self.rnn(embedded)
# 将正向(forward)和反向(backward)的最后一个隐藏状态拼接
# hidden[0] 是正向LSTM的最后隐藏状态,hidden[1] 是反向LSTM的最后隐藏状态
# 拼接后:[batch, hidden_dim * 2]
hidden = torch.cat((hidden[0], hidden[1]), dim=1)
# 返回所有时间步的输出(用于注意力)和最终隐藏状态(可选初始解码状态)
return outputs, hidden
# ==============================
# 2. 注意力机制(Attention)模块
# 功能:计算解码器当前步应“关注”输入序列的哪些位置
# 使用加性注意力(Bahdanau Attention)
# ==============================
class Attention(nn.Module):
def __init__(self, enc_hidden_dim, dec_hidden_dim):
"""
初始化注意力模块
:param enc_hidden_dim: 编码器单向隐藏维度(实际输入是 enc_hidden_dim * 2)
:param dec_hidden_dim: 解码器隐藏维度
"""
super(Attention, self).__init__()
# 将拼接后的 [decoder_hidden, encoder_output] 映射到中间维度
# 输入维度 = dec_hidden_dim + enc_hidden_dim*2
self.W = nn.Linear(enc_hidden_dim * 2 + dec_hidden_dim, dec_hidden_dim)
# 将中间表示压缩为标量(能量分数)
self.v = nn.Linear(dec_hidden_dim, 1, bias=False)
def forward(self, dec_hidden, enc_outputs):
"""
计算注意力权重
:param dec_hidden: 解码器当前隐藏状态,形状 [batch_size, dec_hidden_dim]
:param enc_outputs: 编码器所有时间步输出,形状 [batch_size, src_len, enc_hidden_dim*2]
:return: attention_weights,形状 [batch_size, src_len],表示每个输入词的重要性
"""
# 获取源序列长度(用于后续重复操作)
src_len = enc_outputs.shape[1]
# 将解码器隐藏状态扩展为与源序列长度一致的维度
# 原始 dec_hidden: [batch, dec_hidden_dim]
# 扩展后: [batch, src_len, dec_hidden_dim]
dec_hidden_expanded = dec_hidden.unsqueeze(1).repeat(1, src_len, 1)
# 拼接解码器状态和每个编码器输出
# [batch, src_len, dec_hidden_dim + enc_hidden_dim*2]
concatenated = torch.cat((dec_hidden_expanded, enc_outputs), dim=2)
# 通过线性层 + tanh 激活得到能量向量
# energy: [batch, src_len, dec_hidden_dim]
energy = torch.tanh(self.W(concatenated))
# 将能量向量压缩为标量(每个位置一个分数)
# attention_scores: [batch, src_len, 1] → squeeze 后 [batch, src_len]
attention_scores = self.v(energy).squeeze(2)
# 对注意力分数做 Softmax,得到归一化的权重(和为1)
# attention_weights: [batch, src_len]
attention_weights = F.softmax(attention_scores, dim=1)
return attention_weights
# ==============================
# 3. 解码器(Decoder)模块
# 功能:基于编码器输出和注意力权重,逐步生成目标序列
# ==============================
class Decoder(nn.Module):
def __init__(self, vocab_size, embed_dim, enc_hidden_dim, dec_hidden_dim):
"""
初始化解码器
:param vocab_size: 目标词表大小
:param embed_dim: 词嵌入维度
:param enc_hidden_dim: 编码器单向隐藏维度(注意:实际接收的是 *2)
:param dec_hidden_dim: 解码器隐藏维度
"""
super(Decoder, self).__init__()
# 目标语言的词嵌入层
self.embedding = nn.Embedding(vocab_size, embed_dim)
# 注意力模块
self.attention = Attention(enc_hidden_dim, dec_hidden_dim)
# LSTM解码器:输入 = 词嵌入 + 上下文向量(来自注意力)
# 因此输入维度 = embed_dim + enc_hidden_dim*2
self.rnn = nn.LSTM(embed_dim + enc_hidden_dim * 2, dec_hidden_dim, batch_first=True)
# 输出层:将解码器输出映射回词表
# 输入 = 解码器隐藏状态 + 上下文向量
self.fc = nn.Linear(dec_hidden_dim + enc_hidden_dim * 2, vocab_size)
def forward(self, input_token, dec_hidden, enc_outputs):
"""
解码器单步前向传播(一次生成一个词)
:param input_token: 当前输入词索引,形状 [batch_size, 1]
:param dec_hidden: 解码器上一时刻隐藏状态,形状 [batch_size, dec_hidden_dim]
:param enc_outputs: 编码器所有输出,形状 [batch_size, src_len, enc_hidden_dim*2]
:return:
prediction: 当前步的词表概率分布,形状 [batch_size, vocab_size]
new_hidden: 当前步的隐藏状态,形状 [batch_size, dec_hidden_dim]
attention_weights: 当前步的注意力权重,形状 [batch_size, src_len]
"""
# 将当前输入词转换为嵌入向量
# input_token: [batch, 1] → embedded: [batch, 1, embed_dim]
embedded = self.embedding(input_token)
# 计算当前步对编码器各位置的注意力权重
# weights: [batch, src_len]
attention_weights = self.attention(dec_hidden, enc_outputs)
# 利用注意力权重对编码器输出进行加权求和,得到上下文向量
# attention_weights: [batch, src_len] → unsqueeze: [batch, 1, src_len]
# enc_outputs: [batch, src_len, enc_hidden_dim*2]
# 矩阵乘法后 context: [batch, 1, enc_hidden_dim*2]
context = torch.bmm(attention_weights.unsqueeze(1), enc_outputs)
# 将词嵌入和上下文向量拼接,作为LSTM的输入
# rnn_input: [batch, 1, embed_dim + enc_hidden_dim*2]
rnn_input = torch.cat((embedded, context), dim=2)
# 通过LSTM处理拼接后的输入
# output: [batch, 1, dec_hidden_dim]
# (hidden, cell): 隐藏状态 [1, batch, dec_hidden_dim]
output, (hidden, cell) = self.rnn(rnn_input)
# 为了后续使用,去掉LSTM输出的第一个维度(seq_len=1)
output = output.squeeze(1) # [batch, dec_hidden_dim]
context = context.squeeze(1) # [batch, enc_hidden_dim*2]
# 将LSTM输出和上下文向量再次拼接,送入全连接层预测下一个词
# combined: [batch, dec_hidden_dim + enc_hidden_dim*2]
combined = torch.cat((output, context), dim=1)
# 全连接层输出未归一化的 logits(词表大小)
prediction = self.fc(combined) # [batch, vocab_size]
# 返回预测结果、新的隐藏状态(用于下一步)、注意力权重(可用于可视化)
return prediction, hidden.squeeze(0), attention_weights
# ==============================
# 4. 完整模型封装(可选)
# ==============================
class Seq2Seq(nn.Module):
def __init__(self, encoder, decoder):
super(Seq2Seq, self).__init__()
self.encoder = encoder
self.decoder = decoder
def forward(self, src, trg, teacher_forcing_ratio=0.5):
"""
训练时的完整前向过程(支持教师强制)
:param src: 源序列 [batch, src_len]
:param trg: 目标序列 [batch, trg_len]
:param teacher_forcing_ratio: 使用真实标签作为输入的概率(0~1)
:return: 所有时间步的预测 logits,形状 [trg_len, batch, vocab_size]
"""
batch_size = trg.shape[0]
trg_len = trg.shape[1]
vocab_size = self.decoder.fc.out_features
# 初始化输出张量
outputs = torch.zeros(trg_len, batch_size, vocab_size)
# 编码器处理源序列
enc_outputs, hidden = self.encoder(src)
# 第一个解码输入是 <sos>(假设 trg[:, 0] 是起始符)
input_token = trg[:, 0].unsqueeze(1) # [batch, 1]
# 逐时间步解码
for t in range(1, trg_len):
# 解码一步
output, hidden, _ = self.decoder(input_token, hidden, enc_outputs)
outputs[t] = output
# 决定是否使用教师强制
use_teacher_forcing = torch.rand(1).item() < teacher_forcing_ratio
if use_teacher_forcing:
# 使用真实目标词作为下一步输入
input_token = trg[:, t].unsqueeze(1)
else:
# 使用模型预测的最高概率词作为下一步输入
top1 = output.argmax(1).unsqueeze(1) # [batch, 1]
input_token = top1
return outputs
✅ 使用说明:
此代码可直接用于训练简单的机器翻译模型(需配合数据预处理和损失函数)。
Seq2Seq 类中的 teacher_forcing_ratio 是训练技巧,防止误差累积。
📌 提示:此代码为简化版,实际训练需配合教师强制(teacher forcing)和损失函数。
推理时(无 trg),需手动循环调用 decoder,每次传入上一步预测的 token。
4. 从 Seq2Seq 到 Transformer:架构的进化
虽然注意力机制解决了长距离依赖,但 RNN 本身无法并行计算,训练慢如蜗牛。
于是 2017 年,Google 提出 Transformer ——
彻底抛弃 RNN,只用注意力机制,实现:
- 完全并行化
- 更强的长程建模能力
- 成为 BERT、GPT、LLaMA 等大模型的基石
🔜 预告:下一篇文章《【人工智能】【深度学习】⑩ Transformer 再解析:为什么它是大模型的“心脏”?》将深入剖析其如何用“多头自注意力 + 前馈网络”重构整个 AI 世界。
5. 行业应用:不止于翻译
| 领域 | 应用 | 编码器-解码器变体 |
|---|---|---|
| 智能客服 | 用户问 → AI答 | Transformer 解码器(如 GPT) |
| 语音助手 | 语音 → 文字 → 意图 → 回复 | ASR (编码) + NLU + NLG (解码) |
| 代码生成 | 自然语言 → Python 代码 | CodeT5、StarCoder |
| AI 外呼 | 客户语音 → 意图识别 → 话术生成 | 我之前写的 AI外呼系统 |
6. 延伸阅读 & 经典书籍
- 《Deep Learning》(花书):第 10 章 Sequence Modeling 详细讲解 RNN 与 Seq2Seq
- 《Speech and Language Processing》(Jurafsky & Martin):第 10 章 Neural Machine Translation
- 《动手学深度学习》:第 9 章“现代循环神经网络”含注意力与 Seq2Seq 实战(PyTorch/MXNet)
- ** Seq2Seq 模型详解 **
🔗 源码推荐学习:
- HuggingFace Transformers:
EncoderDecoderModel- OpenNMT-py:工业级 Seq2Seq 框架
- TensorFlow NMT Tutorial
🔗 7. 引导回你的系列
如果你觉得本文有帮助,欢迎继续阅读我的 AI 系列:
- 【人工智能】人工智能发展历程全景解析:从图灵测试到大模型时代(含CNN、Q-Learning深度实践)
- 【人工智能】【深度学习】 ① RNN核心算法介绍:从循环结构到LSTM门控机制
- 【人工智能】【深度学习】 ② 从Q-Learning到DQN:强化学习的革命
- 【人工智能】【深度学习】 ③ GAN核心算法解析:生成对抗网络的原理与应用
- 【人工智能】【深度学习】 ④ Stable Diffusion核心算法解析:从DDPM到文本生成图像的飞跃
- 【人工智能】【深度学习】 ⑤ 注意力机制:从原理到代码实现,看懂模型如何“聚焦”关键信息
- 【人工智能】【深度学习】 ⑦ 从零开始AI学习路径:从Python到大模型的实战指南
- 【人工智能】【深度学习】 ⑧ 一文讲清Transformer工作原理:从自注意力到大语言模型的革命
- 【人工智能】【应用】AI Agent的商业化价值:从Archy到Parahelp的行业应用全景
更多推荐








所有评论(0)