英译法案例 - 基于 GRU 有 Attention 的解码器代码实现(下)
1 课程概览
本课讲解英译法案例中基于 GRU 的有 Attention 解码器的 forward 方法实现。前向传播接收 input、prev_hidden、encoder_outputs 三个参数,经过词嵌入、随机失活、注意力权重计算、注意力应用、拼接、GRU、线性层、LogSoftmax,输出概率分布。
2 核心概念与定义
- forward 方法:前向传播,接收 input、prev_hidden、encoder_outputs。
- input:当前时间步的输入词索引 [1, 1]。
- prev_hidden:上一个时间步的隐藏状态 [1, 1, 256]。
- encoder_outputs:编码器所有时间步的输出 [1, 10, 256]。
- torch.cat:张量拼接。
3 模型与算法详解
forward 方法流程
1. 词嵌入:[1, 1] → [1, 1, 256]
2. 随机失活:[1, 1, 256]
3. 计算注意力权重:
- Q 和 K 拼接:[1, 256] + [1, 256] → [1, 512]
- Linear:[1, 512] → [1, 10]
- softmax:[1, 10] → [1, 10]
4. 注意力应用:
- bmm:[1, 1, 10] × [1, 10, 256] → [1, 1, 256]
5. 拼接 attn_applied 和 Q:[1, 256] + [1, 256] → [1, 512]
6. 线性层:[1, 512] → [1, 256]
7. GRU:[1, 1, 256] → [1, 1, 256]
8. 线性层 + LogSoftmax:[1, 256] → [1, 4345]
形状变化
| 步骤 | 输入形状 | 输出形状 | 说明 |
|---|---|---|---|
| Embedding | [1, 1] | [1, 1, 256] | 词嵌入 |
| Dropout | [1, 1, 256] | [1, 1, 256] | 随机失活 |
| cat(Q, K) | [1, 256] + [1, 256] | [1, 512] | 拼接 |
| attn (Linear) | [1, 512] | [1, 10] | 注意力权重 |
| softmax | [1, 10] | [1, 10] | 归一化 |
| bmm | [1, 1, 10] × [1, 10, 256] | [1, 1, 256] | 注意力应用 |
| cat(attn, Q) | [1, 256] + [1, 256] | [1, 512] | 拼接 |
| attn_combine | [1, 512] | [1, 256] | 线性层 |
| GRU | [1, 1, 256] | [1, 1, 256] | GRU |
| out + LogSoftmax | [1, 256] | [1, 4345] | 输出 |
4 数学原理与推导
词嵌入
$$\text{embedded} = \text{Dropout}(\text{Embedding}(\text{input}))$$
注意力权重
$$\text{attn_weights} = \text{softmax}(\text{Linear}(\text{cat}(\text{embedded}[0], \text{prev_hidden}[0])))$$
注意力应用
$$\text{attn_applied} = \text{bmm}(\text{attn_weights}.\text{unsqueeze}(0), \text{encoder_outputs}.\text{unsqueeze}(0))$$
拼接
$$\text{output} = \text{cat}(\text{attn_applied}[0], \text{embedded}[0])$$
线性层
$$\text{output} = \text{ReLU}(\text{attn_combine}(\text{output}))$$
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):
super(DecoderGRUWithAttention, self).__init__()
self.hidden_size = hidden_size
self.output_size = output_size
self.dropout_p = dropout_p
self.max_length = max_length
self.embedding = nn.Embedding(output_size, hidden_size)
self.dropout = nn.Dropout(dropout_p)
self.attn = nn.Linear(hidden_size * 2, max_length)
self.attn_combine = nn.Linear(hidden_size * 2, hidden_size)
self.gru = nn.GRU(hidden_size, hidden_size)
self.out = nn.Linear(hidden_size, output_size)
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, 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]
# Linear:[1, 512] → [1, 10]
# softmax:[1, 10]
attn_weights = F.softmax(
self.attn(torch.cat((embedded[0], prev_hidden[0]), 1)),
dim=1
)
# 3. 注意力应用
# bmm:[1, 1, 10] × [1, 10, 256] → [1, 1, 256]
attn_applied = torch.bmm(
attn_weights.unsqueeze(0), # [1, 1, 10]
encoder_outputs.unsqueeze(0) # [1, 10, 256]
)
# 4. 拼接 attn_applied 和 Q:[1, 256] + [1, 256] → [1, 512]
output = torch.cat((attn_applied[0], embedded[0]), 1)
# 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)
# 创建输入
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]
print(f"输入形状: {input_tensor.shape}")
print(f"隐藏状态形状: {prev_hidden.shape}")
print(f"编码器输出形状: {encoder_outputs.shape}")
# 前向传播
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]
print(f"注意力权重和: {attn_weights.sum().item():.4f}")
代码说明
| 代码 | 说明 |
|---|---|
self.embedding(input).view(1, 1, -1) | 词嵌入并调整形状 |
self.dropout(embedded) | 随机失活 |
torch.cat((embedded[0], prev_hidden[0]), 1) | Q 和 K 拼接 |
self.attn(...) | 注意力权重计算 |
F.softmax(..., dim=1) | softmax 归一化 |
torch.bmm(attn_weights.unsqueeze(0), encoder_outputs.unsqueeze(0)) | 注意力应用 |
self.attn_combine(output).unsqueeze(0) | 线性层并增加维度 |
F.relu(output) | ReLU 激活 |
self.gru(output, prev_hidden) | GRU 处理 |
self.softmax(self.out(output[0])) | 线性层 + LogSoftmax |
6 重难点与易错提醒
- ❗重点:forward 方法接收三个参数:input、prev_hidden、encoder_outputs。
- ❗重点:使用
torch.cat拼接 Q 和 K。 - ❗重点:使用
torch.bmm计算注意力应用。 - ❗重点:Linear 层只能处理二维,需要用
[0]降维。 - ⚠️易错:
unsqueeze(0)增加批次维度。
7 课堂问答精选
Q1:forward 方法接收哪些参数?
A:接收三个参数:input(当前时间步的输入词索引)、prev_hidden(上一个时间步的隐藏状态)、encoder_outputs(编码器所有时间步的输出)。
Q2:如何拼接 Q 和 K?
A:使用 torch.cat((embedded[0], prev_hidden[0]), 1),拼接后维度为 512。
Q3:如何计算注意力应用?
A:使用 torch.bmm(attn_weights.unsqueeze(0), encoder_outputs.unsqueeze(0)),将注意力权重与编码器输出相乘。
8 本课小结
- forward 方法接收 input、prev_hidden、encoder_outputs 三个参数。
- 流程:词嵌入 → 随机失活 → 注意力权重 → 注意力应用 → 拼接 → 线性层 → GRU → 线性层 + LogSoftmax。
- 使用
torch.cat拼接,torch.bmm计算注意力应用。 - 输出形状为 [1, 4345]。
9 延伸思考
- 如何测试有 Attention 的解码器?
- 如何训练有 Attention 的解码器?