Seq2Seq 架构 - 加入注意力机制
1 课程概览
本课讲解在 Seq2Seq 架构中加入注意力机制的方法。核心思想:每个时间步的解码都引入 QKV,将普通 Q 升级为更强大的 Q。生成不同词时,用不同的 Q 与所有词计算权重分布,再与中间语义张量 C 加权求和,得到该时间步专属的 $C_t$。
2 核心概念与定义
- 注意力权重分布:Q 与所有词计算相似度后得到的权重。
- 加权求和:用权重分布乘以中间语义张量 C。
- 专属 $C_t$:每个时间步动态计算的中间语义张量。
3 模型与算法详解
加入注意力机制的解码流程
每个时间步都要添加注意力机制,引入 QKV。
- 输入查询张量 Q:当前要生成的词
- Q 与所有词计算权重分布:得到注意力权重
- 权重分布 × 中间语义张量 C:加权求和
- 得到专属 $C_t$:用于当前时间步的解码
- 全连接分类:得到词向量
示例:"欢迎来北京" 翻译
生成 "Welcome"($Q_1$)
- $Q_1$ 与 "欢迎"、"来"、"北京" 计算权重
- 权重分布:
[0.8, 0.1, 0.1] - $C_1 = 0.8 \times V_1 + 0.1 \times V_2 + 0.1 \times V_3$
生成 "to"($Q_2$)
- $Q_2$ 与 "欢迎"、"来"、"北京" 计算权重
- 权重分布:
[0.3, 0.5, 0.2] - $C_2 = 0.3 \times V_1 + 0.5 \times V_2 + 0.2 \times V_3$
生成 "Beijing"($Q_3$)
- $Q_3$ 与 "欢迎"、"来"、"北京" 计算权重
- 权重分布:
[0.1, 0.1, 0.8](对"北京"权重最高) - $C_3 = 0.1 \times V_1 + 0.1 \times V_2 + 0.8 \times V_3$
加 vs 不加注意力机制
| 对比项 | 不加注意力 | 加注意力 |
|---|---|---|
| 中间语义张量 | 所有时间步用同一个 C | 每个时间步用不同的 $C_t$ |
| 权重 | 所有词权重相同 | 每个词权重不同 |
| 效果 | 一般 | 更好 |
核心变化
加注意力机制后,把 C 变成 $C_1, C_2, C_3$。
4 数学原理与推导
权重分布计算
$$\alpha_{t,i} = \text{softmax}(\text{score}(Q_t, K_i))$$
其中 $\alpha_{t,i}$ 是第 $t$ 个时间步对第 $i$ 个词的注意力权重。
加权求和
$$C_t = \sum_i \alpha_{t,i} V_i$$
解码
$$Y_t = \text{Decoder}(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 AttentionDecoder(nn.Module):
"""加入注意力机制的解码器"""
def __init__(self, hidden_size, output_size):
super().__init__()
self.gru = nn.GRU(hidden_size, hidden_size)
self.linear = nn.Linear(hidden_size, output_size)
self.softmax = nn.LogSoftmax(dim=1)
# 注意力权重计算层
self.attn = nn.Linear(hidden_size * 2, hidden_size)
self.attn_combine = nn.Linear(hidden_size * 2, hidden_size)
def forward(self, input, hidden, encoder_outputs):
"""
:param input: 查询张量 Q
:param hidden: 上一时刻隐藏状态
:param encoder_outputs: 编码器所有输出(K 和 V)
"""
# 1. 计算注意力权重
attn_weights = F.softmax(
torch.matmul(input, encoder_outputs.T),
dim=1
)
# 2. 加权求和得到 C_t
C_t = torch.matmul(attn_weights, encoder_outputs)
# 3. 解码
output, hidden = self.gru(C_t, hidden)
# 4. 全连接分类
output = self.linear(output)
output = self.softmax(output)
return output, hidden, attn_weights
# 示例:生成 "Welcome to Beijing"
# 假设有 3 个词的词向量
encoder_outputs = torch.randn(3, 128) # 3 个词,每个词 128 维
# 生成 "Welcome"
Q1 = torch.randn(1, 128)
hidden = torch.zeros(1, 1, 128)
output, hidden, weights1 = decoder(Q1, hidden, encoder_outputs)
# 生成 "to"
Q2 = torch.randn(1, 128)
output, hidden, weights2 = decoder(Q2, hidden, encoder_outputs)
# 生成 "Beijing"
Q3 = torch.randn(1, 128)
output, hidden, weights3 = decoder(Q3, hidden, encoder_outputs)
6 重难点与易错提醒
- ❗重点:每个时间步都要添加注意力机制,引入 QKV。
- ❗重点:加注意力机制后,C 变成 $C_1, C_2, C_3$(每个时间步不同)。
- ⚠️易错:权重分布是动态变化的,不同 Q 得到不同权重。
- 💡深入理解:生成不同词时,对输入词的关注度不同。
- 💡深入理解:注意力机制将普通 Q 升级为更强大的 Q。
7 课堂问答精选
Q1:加入注意力机制后,解码流程有什么变化?
A:每个时间步都要添加注意力机制,引入 QKV。生成不同词时,用不同的 Q 与所有词计算权重分布,再与中间语义张量 C 加权求和,得到该时间步专属的 $C_t$。
Q2:生成 "Welcome"、"to"、"Beijing" 时,权重分布有什么不同?
A:生成 "Welcome" 时权重为 [0.8, 0.1, 0.1],生成 "to" 时为 [0.3, 0.5, 0.2],生成 "Beijing" 时为 [0.1, 0.1, 0.8]。不同 Q 得到不同权重分布。
Q3:加注意力机制和不加有什么区别?
A:不加注意力机制时,所有时间步用同一个 C;加注意力机制后,每个时间步用不同的 $C_t$($C_1, C_2, C_3$),效果更好。
Q4:加注意力机制的核心变化是什么?
A:把 C 变成 $C_1, C_2, C_3$。每个时间步用不同的 Q 计算权重分布,再与 C 加权求和得到专属的 $C_t$。
8 本课小结
- 每个时间步添加注意力机制,引入 QKV。
- 生成不同词时,用不同 Q 计算权重分布。
- 权重分布 × 中间语义张量 C → 专属 $C_t$。
- 加注意力:C → $C_1, C_2, C_3$。
- 核心变化:把 C 变成 $C_1, C_2, C_3$。
9 延伸思考
- 权重分布是如何计算的?
- 如何用代码实现注意力机制的解码过程?