英译法案例 - 基于 GRU 有 Attention 的解码器思路分析
1 课程概览
本课分析英译法案例中基于 GRU 的有 Attention 解码器的思路。加入注意力机制后,需要 Q、K、V 三个张量。Q 是解码器的输入([1, 1, 256]),K 和 V 是编码器的输出([1, 6, 256])。通过注意力机制计算权重分布,增强解码器的生成能力。
2 核心概念与定义
- Q(Query):解码器的输入,形状 [1, 1, 256]。
- K(Key):编码器的输出,形状 [1, 6, 256]。
- V(Value):编码器的输出,形状 [1, 6, 256]。
- 注意力机制:计算 Q 与 K 的匹配程度,得到权重分布,与 V 相乘。
- Seq2Seq 架构:句子到句子的架构。
3 模型与算法详解
有 Attention 的解码器结构
input → Embedding → Dropout → Q
↓
Attention(Q, K, V) → attn_applied
↓
concat(attn_applied, Q) → GRU → Linear → LogSoftmax → output
↑
encoder_outputs (K, V)
Q、K、V 的形状
| 张量 | 形状 | 说明 |
|---|---|---|
| Q | [1, 1, 256] | 解码器输入 |
| K | [1, 6, 256] | 编码器输出(6 个单词) |
| V | [1, 6, 256] | 编码器输出(6 个单词) |
注意力计算流程
1. Q 和 K 拼接 → [1, 1, 512]
2. Linear 处理 → [1, 1, 10]
3. softmax → 权重分布 [1, 1, 10]
4. 权重分布与 V 相乘 → attn_applied [1, 1, 256]
5. attn_applied 与 Q 拼接 → [1, 1, 512]
6. GRU 处理 → output [1, 1, 256]
7. Linear → [1, 4345]
8. LogSoftmax → 概率分布
关键参数
| 参数 | 值 | 说明 |
|---|---|---|
| hidden_size | 256 | 隐藏层维度 |
| output_size | 4345 | 法语词汇表大小 |
| max_length | 10 | 句子最大长度 |
| dropout_p | 0.1 | 随机失活概率 |
4 数学原理与推导
注意力权重
$$\text{attn_weights} = \text{softmax}(\text{Linear}(\text{Q} \oplus \text{K}))$$
注意力应用
$$\text{attn_applied} = \text{bmm}(\text{attn_weights}, \text{V})$$
拼接
$$\text{output} = \text{concat}(\text{attn_applied}, \text{Q})$$
GRU
$$\text{output}, \text{hidden} = \text{GRU}(\text{output}, \text{prev_hidden})$$
线性层
$$\text{output} = \text{LogSoftmax}(\text{Linear}(\text{output}[0]))$$
5 代码示例
import torch
import torch.nn as nn
import torch.nn.functional as F
class DecoderGRUWithAttention(nn.Module):
"""基于 GRU 的有 Attention 解码器"""
def __init__(self, hidden_size, output_size, dropout_p=0.1, max_length=10):
"""
初始化函数
Args:
hidden_size: 隐藏层维度(256)
output_size: 输出维度(4345,法语词汇表大小)
dropout_p: 随机失活概率
max_length: 句子最大长度
"""
super(DecoderGRUWithAttention, self).__init__()
self.hidden_size = hidden_size
self.output_size = output_size
self.dropout_p = dropout_p
self.max_length = max_length
# 1. 词嵌入层
self.embedding = nn.Embedding(output_size, hidden_size)
# 2. 随机失活层
self.dropout = nn.Dropout(dropout_p)
# 3. 注意力权重计算层(Q 和 K 拼接后 512 → 10)
self.attn = nn.Linear(hidden_size * 2, max_length)
# 4. 注意力应用后的线性层(attn_applied + Q 拼接后 512 → 256)
self.attn_combine = nn.Linear(hidden_size * 2, hidden_size)
# 5. GRU 层
self.gru = nn.GRU(hidden_size, hidden_size)
# 6. 线性层
self.out = nn.Linear(hidden_size, output_size)
# 7. LogSoftmax
self.softmax = nn.LogSoftmax(dim=1)
def forward(self, input, prev_hidden, encoder_outputs):
"""
前向传播
Args:
input: 当前时间步的输入词索引 [1, 1]
prev_hidden: 上一个时间步的隐藏状态 [1, 1, 256]
encoder_outputs: 编码器所有时间步的输出 [1, 10, 256]
Returns:
output: 概率分布 [1, 4345]
hidden: 本次隐藏状态 [1, 1, 256]
attn_weights: 注意力权重分布 [1, 1, 10]
"""
# 1. 词嵌入:[1, 1] → [1, 1, 256]
embedded = self.embedding(input).view(1, 1, -1)
embedded = self.dropout(embedded)
# 2. 计算注意力权重
# Q 和 K 拼接:[1, 256] + [1, 256] → [1, 512]
attn_weights = F.softmax(
self.attn(torch.cat((embedded[0], prev_hidden[0]), 1)), # [1, 512] → [1, 10]
dim=1
)
# 3. 注意力应用
# attn_weights: [1, 1, 10], encoder_outputs: [1, 10, 256]
attn_applied = torch.bmm(attn_weights.unsqueeze(0), encoder_outputs.unsqueeze(0)) # [1, 1, 256]
# 4. 拼接 attn_applied 和 Q
# [1, 256] + [1, 256] → [1, 512]
output = torch.cat((attn_applied[0], embedded[0]), 1) # [1, 512]
# 5. 线性层:[1, 512] → [1, 256]
output = self.attn_combine(output).unsqueeze(0) # [1, 1, 256]
output = F.relu(output)
# 6. GRU:[1, 1, 256] → [1, 1, 256], [1, 1, 256]
output, hidden = self.gru(output, prev_hidden)
# 7. 线性层 + LogSoftmax:[1, 256] → [1, 4345]
output = self.softmax(self.out(output[0]))
return output, hidden, attn_weights
# 测试
if __name__ == "__main__":
print("=== 英译法案例 - 基于 GRU 有 Attention 的解码器 ===")
hidden_size = 256
output_size = 4345
max_length = 10
decoder = DecoderGRUWithAttention(hidden_size, output_size, dropout_p=0.1, max_length=max_length)
print(f"解码器:\n{decoder}")
# 创建输入
input_tensor = torch.tensor([[1]]) # [1, 1]
prev_hidden = torch.zeros(1, 1, hidden_size) # [1, 1, 256]
encoder_outputs = torch.randn(1, max_length, hidden_size) # [1, 10, 256]
# 前向传播
output, hidden, attn_weights = decoder(input_tensor, prev_hidden, encoder_outputs)
print(f"\n输出形状: {output.shape}") # [1, 4345]
print(f"隐藏状态形状: {hidden.shape}") # [1, 1, 256]
print(f"注意力权重形状: {attn_weights.shape}") # [1, 10]
代码说明
| 代码 | 说明 |
|---|---|
nn.Embedding(output_size, hidden_size) | 词嵌入层 |
nn.Dropout(dropout_p) | 随机失活层 |
nn.Linear(hidden_size * 2, max_length) | 注意力权重计算层 |
nn.Linear(hidden_size * 2, hidden_size) | 注意力应用后的线性层 |
torch.cat((embedded[0], prev_hidden[0]), 1) | Q 和 K 拼接 |
torch.bmm(attn_weights.unsqueeze(0), encoder_outputs.unsqueeze(0)) | 注意力应用 |
6 重难点与易错提醒
- ❗重点:有 Attention 后需要 Q、K、V 三个张量。
- ❗重点:Q 是解码器输入,K 和 V 是编码器输出。
- ❗重点:注意力权重计算层的输入是 Q 和 K 拼接后的 512 维。
- ❗重点:max_length=10 是句子最大长度。
- ⚠️易错:Linear 层只能处理二维,需要用
embedded[0]降维。
7 课堂问答精选
Q1:有 Attention 的解码器需要哪些输入?
A:需要三个输入:input(当前时间步的输入词索引)、prev_hidden(上一个时间步的隐藏状态)、encoder_outputs(编码器所有时间步的输出)。
Q2:Q、K、V 分别是什么?
A:Q 是解码器的输入(embedded),K 是编码器的隐藏状态(prev_hidden),V 是编码器的输出(encoder_outputs)。
Q3:注意力权重计算层的输入维度是多少?
A:512 维,是 Q 和 K 拼接后的维度(256 + 256 = 512)。
8 本课小结
- 有 Attention 的解码器需要 Q、K、V 三个张量。
- Q 是解码器输入,K 和 V 是编码器输出。
- 注意力计算:Q 和 K 拼接 → Linear → softmax → 与 V 相乘。
- 关键参数:hidden_size=256,output_size=4345,max_length=10。
9 延伸思考
- 如何用代码实现有 Attention 的解码器?
- 如何测试有 Attention 的解码器?