扩展2 - 加入注意力机制的 Encoder-Decoder 框架
1 课程概览
本课扩展讲解加入注意力机制的 Encoder-Decoder 框架。以"汤姆追逐杰瑞"(Tom chases Jerry)翻译为例,讲解生成不同词时如何使用专属的中间语义张量 $C_t$。每个时间步用不同的 Q 计算权重分布,再与 C 加权求和得到专属 $C_t$。
2 核心概念与定义
- 专属 $C_t$:每个时间步动态计算的中间语义张量。
- 权重分布:Q 与所有词计算相似度后得到的权重。
- N vs M 架构:输入 N 个词,输出 M 个词(如 3 个英文词翻译成 3 个中文词)。
3 模型与算法详解
加入注意力机制的 Encoder-Decoder
输入序列 → [编码器] → V1, V2, V3(词向量)
↓
生成 Y1: Q1 与 V1,V2,V3 计算权重 → C1 → [解码器] → Y1
生成 Y2: Q2 与 V1,V2,V3 计算权重 → C2 → [解码器] → Y2
生成 Y3: Q3 与 V1,V2,V3 计算权重 → C3 → [解码器] → Y3
"汤姆追逐杰瑞" 翻译示例
| 生成词 | Q | 权重分布 | 专属 $C_t$ |
|---|---|---|---|
| 汤姆 | $Q_1$ | [0.5, 0.3, 0.2] | $C_1 = 0.5V_1 + 0.3V_2 + 0.2V_3$ |
| 追逐 | $Q_2$ | [0.3, 0.5, 0.2] | $C_2 = 0.3V_1 + 0.5V_2 + 0.2V_3$ |
| 杰瑞 | $Q_3$ | [0.2, 0.3, 0.5] | $C_3 = 0.2V_1 + 0.3V_2 + 0.5V_3$ |
预测流程
| 预测词 | 需要的信息 | 公式 |
|---|---|---|
| $Y_1$(汤姆) | $C_1$ | $Y_1 = F(C_1)$ |
| $Y_2$(追逐) | $C_2$, $S_1$ | $Y_2 = F(C_2, S_1)$ |
| $Y_3$(杰瑞) | $C_3$, $S_2$ | $Y_3 = F(C_3, S_2)$ |
普通 vs 加注意力
| 对比项 | 普通 | 加注意力 |
|---|---|---|
| 中间语义张量 | 所有时间步用同一个 C | 每个时间步用不同的 $C_t$ |
| 权重 | 所有词贡献相同 | 每个词贡献不同 |
| 效果 | 一般 | 更好 |
4 数学原理与推导
权重分布计算
$$\alpha_{t,i} = \text{softmax}(\text{score}(Q_t, K_i))$$
其中 $\alpha_{t,i}$ 是第 $t$ 个时间步对第 $i$ 个词的注意力权重。
专属 $C_t$ 计算
$$C_t = \sum_i \alpha_{t,i} V_i$$
解码
$$Y_t = F(C_t, S_{t-1})$$
完整公式
$$\text{Attention}(Q_t, K, V) = \text{softmax}\left(\frac{Q_t K^T}{\sqrt{d_k}}\right)V = C_t$$
5 代码示例
import torch
import torch.nn as nn
import torch.nn.functional as F
class AttentionEncoderDecoder(nn.Module):
"""加入注意力机制的 Encoder-Decoder 框架"""
def __init__(self, input_size, hidden_size, output_size):
super().__init__()
# 编码器
self.encoder = nn.GRU(input_size, hidden_size)
# 解码器
self.decoder = nn.GRU(hidden_size, hidden_size)
self.linear = nn.Linear(hidden_size, output_size)
self.softmax = nn.LogSoftmax(dim=1)
def forward(self, src, trg_len):
"""
:param src: 输入序列 (seq_len, batch, input_size)
:param trg_len: 目标序列长度
"""
# 1. 编码:得到所有词的词向量 V
encoder_outputs, hidden = self.encoder(src, None)
# encoder_outputs: (seq_len, batch, hidden_size) = V
# hidden: (1, batch, hidden_size)
outputs = []
decoder_hidden = hidden
for t in range(trg_len):
# 2. 生成 Q_t(当前要查询的问题)
Q_t = decoder_hidden[-1] # (batch, hidden_size)
# 3. 计算权重分布
attn_weights = F.softmax(
torch.matmul(Q_t, encoder_outputs.squeeze(1).T),
dim=1
)
# 4. 加权求和得到 C_t
C_t = torch.matmul(
attn_weights.unsqueeze(1),
encoder_outputs
).squeeze(1)
# 5. 解码
output, decoder_hidden = self.decoder(
C_t.unsqueeze(0),
decoder_hidden
)
# 6. 全连接分类
output = self.linear(output.squeeze(0))
output = self.softmax(output)
outputs.append(output)
return torch.stack(outputs)
# 示例:翻译 "Tom chases Jerry" → "汤姆追逐杰瑞"
input_size = 100
hidden_size = 128
output_size = 5000 # 词汇表大小
model = AttentionEncoderDecoder(input_size, hidden_size, output_size)
# 输入:3 个英文词
src = torch.randn(3, 1, input_size) # (seq_len, batch, input_size)
trg_len = 3 # 输出 3 个中文词
outputs = model(src, trg_len)
print("输出形状:", outputs.shape) # (3, 1, 5000)
6 重难点与易错提醒
- ❗重点:加入注意力机制后,每个时间步用不同的 $C_t$。
- ❗重点:$C_t$ 由 Q 与所有词计算权重分布,再加权求和得到。
- ⚠️易错:N vs M 架构,输入 N 个词可以输出 M 个词(不一定是 N=M)。
- 💡深入理解:生成不同词时,对输入词的关注度不同。
- 💡深入理解:$S_t$ 记录已生成的词,$C_t$ 记录当前关注的信息。
7 课堂问答精选
Q1:加入注意力机制后,每个时间步的 $C_t$ 如何计算?
A:用当前时间步的 Q 与所有词(V)计算权重分布,再用权重分布与 V 加权求和得到专属 $C_t$。
Q2:"汤姆追逐杰瑞"翻译中,生成不同词时的权重分布有什么不同?
A:生成"汤姆"时权重为 [0.5, 0.3, 0.2],生成"追逐"时为 [0.3, 0.5, 0.2],生成"杰瑞"时为 [0.2, 0.3, 0.5]。不同 Q 得到不同权重分布。
Q3:预测 $Y_2$(追逐)需要什么信息?
A:需要 $C_2$(当前时间步的专属中间语义张量)和 $S_1$(上一时刻的隐藏状态,记录已生成的"汤姆")。
Q4:N vs M 架构是什么意思?
A:输入 N 个词,输出 M 个词。例如 3 个英文词翻译成 3 个中文词是 N=3, M=3。但 N 和 M 不一定相等,取决于具体任务。
8 本课小结
- 加入注意力机制:每个时间步用不同的 $C_t$。
- $C_t$ = 权重分布 × V(加权求和)。
- 权重分布 = Q 与所有词计算相似度。
- 预测 $Y_t$ 需要 $C_t$ 和 $S_{t-1}$。
- N vs M 架构:输入 N 个词,输出 M 个词。
9 延伸思考
- 权重分布是如何反映模型对不同词的关注度的?
- 如何用代码实现完整的注意力机制?