Seq2Seq模型实现了"输入序列→输出序列"的映射,是机器翻译、文本摘要的基础。本课深入Encoder-Decoder架构和训练技巧。
编码器:将输入序列(x₁,...,xₙ)编码为上下文向量c
c = hₙ = RNN_enc(x₁,...,xₙ)
解码器:逐词生成输出序列(y₁,...,yₘ)
sₜ = RNN_dec(sₜ₋₁, yₜ₋₁, c)
训练时用真实标签作为下一步输入,而不是模型自己的预测。加速收敛但可能导致暴露偏差。
贪心搜索:每步选概率最高的词 → 局部最优
束搜索(beam_size=k):保留k个最佳候选 → 更接近全局最优
import torch
import torch.nn as nn
import random
# Seq2Seq实现
class Encoder(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.rnn = nn.GRU(embed_dim, hidden_dim, batch_first=True)
def forward(self, x):
embedded = self.embedding(x)
outputs, hidden = self.rnn(embedded)
return outputs, hidden
class Decoder(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.rnn = nn.GRU(embed_dim, hidden_dim, batch_first=True)
self.fc = nn.Linear(hidden_dim, vocab_size)
def forward(self, x, hidden):
embedded = self.embedding(x)
output, hidden = self.rnn(embedded, hidden)
prediction = self.fc(output)
return prediction, hidden
# 简单数字翻译任务:将数字序列"翻译"成逆序
vocab_size = 20
embed_dim = 32
hidden_dim = 64
encoder = Encoder(vocab_size, embed_dim, hidden_dim)
decoder = Decoder(vocab_size, embed_dim, hidden_dim)
optimizer = torch.optim.Adam(list(encoder.parameters()) + list(decoder.parameters()), lr=0.001)
loss_fn = nn.CrossEntropyLoss(ignore_index=0)
# 生成训练数据:输入[1,2,3] → 目标[3,2,1]
def generate_data(batch_size, max_len=8):
src = torch.randint(1, 10, (batch_size, max_len))
tgt = src.flip(1)
# 加SOS和EOS
tgt_input = torch.cat([torch.full((batch_size,1), 11), tgt], dim=1)
tgt_output = torch.cat([tgt, torch.full((batch_size,1), 12)], dim=1)
return src, tgt_input, tgt_output
# 生成固定训练数据集
train_src, train_tgt_in, train_tgt_out = generate_data(64)
print("=== Seq2Seq训练(数字逆序任务)===")
for epoch in range(501):
optimizer.zero_grad()
# 编码
_, hidden = encoder(train_src)
# 解码(Teacher Forcing)
output, _ = decoder(train_tgt_in, hidden)
loss = loss_fn(output.reshape(-1, vocab_size), train_tgt_out.reshape(-1))
loss.backward()
optimizer.step()
if epoch % 100 == 0:
print(f"Epoch {epoch}: loss={loss.item():.4f}")
# 推理演示(自回归解码)
print("\n=== 推理示例 ===")
encoder.eval()
decoder.eval()
test_src = torch.randint(1, 10, (3, 8))
test_tgt_expected = test_src.flip(1)
with torch.no_grad():
_, hidden = encoder(test_src)
input_token = torch.full((3, 1), 11, dtype=torch.long) # SOS
result = []
for t in range(8):
output, hidden = decoder(input_token, hidden)
pred = output.argmax(2)
result.append(pred)
input_token = pred
result = torch.cat(result, dim=1)
for i in range(3):
correct = (result[i] == test_tgt_expected[i]).all().item()
print(f"输入: {test_src[i].tolist()} → 输出: {result[i].tolist()} → 期望: {test_tgt_expected[i].tolist()} {'✓' if correct else '✗'}")
本课的核心知识点构成了序列模型学习的重要一环。让我们从更高维度审视这些知识之间的关系:
⚠️ 初学者常犯的错误:
💡 最佳实践:
深度学习是一个整体——每一课的知识都不是孤立的:
💡 继续深入的学习路径:
在Encoder-Decoder中加入Bahdanau注意力,对比效果。
实现beam_size=5的束搜索,对比贪心搜索。
实现覆盖向量防止重复翻译。
⚠️ 需要避免的典型错误:
💡 提升模型性能的系统方法:
理解本课内容需要将其置于更大的知识网络中。每个核心概念都不是孤立的,它们相互关联、相互支撑:
建立正确的直觉比记住公式更重要:
1. 维度直觉:高维空间中,数据分布与低维直觉截然不同(维度诅咒)
2. 信息瓶颈:神经网络逐层压缩信息,保留任务相关的、丢弃无关的
3. 流形假设:高维数据实际分布在低维流形上,学习即发现流形结构
4. 偏差-方差权衡:简单模型欠拟合(高偏差),复杂模型过拟合(高方差)
本课作为序列模型阶段的核心内容,与前后课程紧密关联:
| 关联课程 | 关联点 | 协同效应 |
|---|---|---|
| 前序课程 | 提供了本课的基础知识 | 循序渐进的理解 |
| 后续课程 | 本课内容是后续的基础 | 逐步构建能力 |
| 平行课程 | 同一阶段的互补知识 | 多角度深入理解 |
| 实战项目 | 综合运用所有知识 | 理论与实践结合 |
💡 准备面试时,确保能回答以下问题:
掌握本课内容后,可以通过以下方式继续深入:
将本课涉及的关键公式整理如下,方便回顾和记忆。理解每个公式背后的直觉比死记硬背更重要——试着用自然语言解释每个公式在做什么。
| 概念A | 概念B | 关键区别 |
|---|---|---|
| 参数 | 超参数 | 参数通过训练学习,超参数需要手动设定 |
| 训练误差 | 泛化误差 | 训练误差衡量拟合程度,泛化误差衡量预测能力 |
| 偏差 | 方差 | 偏差是系统性误差,方差是随机波动 |
| L1正则 | L2正则 | L1产生稀疏解,L2产生平滑解 |
| BatchNorm | LayerNorm | BN沿batch维度,LN沿特征维度 |
以下是本课核心概念的标准PyTorch实现模板,可以直接用于实际项目:
💡 调试建议:当结果不符合预期时,先检查数据,再检查损失函数,最后检查模型结构。90%的问题出在前两个环节。
Seq2Seq连接了两种语言——你已经能构建翻译系统!