解码器层 - 代码测试
1 课程概览
本课讲解 Transformer 解码器层的代码测试。定义变量记录解码器输入(Q),创建词嵌入层、位置编码层、多头注意力层(使用深拷贝确保参数不共享)、前馈全连接层,构建三个子层并测试。
2 核心概念与定义
- 解码器层:Transformer 的解码器层。
- Q:解码器的输入。
- 深拷贝(copy.deepcopy):确保多头注意力层参数不共享。
- 三个子层:掩码多头自注意力层、编码器-解码器注意力层、前馈全连接层。
3 模型与算法详解
测试流程
1. 定义变量记录解码器输入(Q)
2. 创建词嵌入层
3. 将 Y 输入变成词向量形式
4. 创建位置编码层,处理 Y
5. 创建多头注意力层(使用深拷贝)
6. 创建前馈全连接层
7. 构建三个子层
8. 测试
三个子层
| 子层 | 说明 | 注意力层 |
|---|---|---|
| 子层 1 | 掩码多头自注意力 | self_attn |
| 子层 2 | 编码器-解码器注意力 | src_attn |
| 子层 3 | 前馈全连接层 | ff |
深拷贝的作用
确保多头注意力层参数不共享。
import copy
# 多头注意力层
multihead_attn = MultiHeadAttention(...)
self_attn = copy.deepcopy(multihead_attn) # 自注意力
src_attn = copy.deepcopy(multihead_attn) # 编码器-解码器注意力
参数说明
| 参数 | 值 | 说明 |
|---|---|---|
| vocab_size | 4345 | 词汇表大小 |
| d_model | 512 | 词向量维度 |
| num_heads | 8 | 多头数量 |
4 数学原理与推导
词嵌入
$$\text{embedded} = \text{Embedding}(\text{Y})$$
位置编码
$$\text{pe_output} = \text{PositionalEncoding}(\text{embedded})$$
多头注意力
$$\text{output} = \text{MultiHeadAttention}(\text{Q}, \text{K}, \text{V})$$
前馈全连接
$$\text{output} = \text{FeedForward}(\text{output})$$
5 代码示例
import torch
import torch.nn as nn
import copy
class MultiHeadAttention(nn.Module):
"""多头注意力机制"""
def __init__(self, d_model, num_heads):
super(MultiHeadAttention, self).__init__()
self.d_model = d_model
self.num_heads = num_heads
self.head_dim = d_model // num_heads
self.w_q = nn.Linear(d_model, d_model)
self.w_k = nn.Linear(d_model, d_model)
self.w_v = nn.Linear(d_model, d_model)
self.fc = nn.Linear(d_model, d_model)
def forward(self, Q, K, V):
batch_size = Q.size(0)
Q = self.w_q(Q).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
K = self.w_k(K).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
V = self.w_v(V).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.head_dim ** 0.5)
attn_weights = torch.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, V)
output = output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
output = self.fc(output)
return output
class PositionalEncoding(nn.Module):
"""位置编码层"""
def __init__(self, d_model, max_len=5000):
super(PositionalEncoding, self).__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1).float()
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-torch.log(torch.tensor(10000.0)) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x):
return x + self.pe[:, :x.size(1)]
class FeedForward(nn.Module):
"""前馈全连接层"""
def __init__(self, d_model, d_ff=2048):
super(FeedForward, self).__init__()
self.fc1 = nn.Linear(d_model, d_ff)
self.fc2 = nn.Linear(d_ff, d_model)
def forward(self, x):
return self.fc2(torch.relu(self.fc1(x)))
def use_decoder_layer():
"""测试解码器层"""
print("=== 解码器层 - 代码测试 ===")
# 1. 定义变量记录解码器输入(Q)
vocab_size = 4345
d_model = 512
num_heads = 8
batch_size = 1
seq_len = 10
Y = torch.randint(0, vocab_size, (batch_size, seq_len)) # [1, 10]
print(f"输入 Y 形状: {Y.shape}")
# 2. 创建词嵌入层
embedding = nn.Embedding(vocab_size, d_model)
# 3. 将 Y 输入变成词向量形式
embedded = embedding(Y) # [1, 10, 512]
print(f"词嵌入后形状: {embedded.shape}")
# 4. 创建位置编码层,处理 Y
my_position = PositionalEncoding(d_model)
position_y = my_position(embedded) # [1, 10, 512]
print(f"位置编码后形状: {position_y.shape}")
# 5. 创建多头注意力层(使用深拷贝)
multihead_attn = MultiHeadAttention(d_model, num_heads)
self_attn = copy.deepcopy(multihead_attn) # 自注意力
src_attn = copy.deepcopy(multihead_attn) # 编码器-解码器注意力
# 6. 创建前馈全连接层
ff = FeedForward(d_model)
# 7. 构建三个子层
print(f"\n三个子层:")
print(f" 子层 1(掩码多头自注意力): {self_attn}")
print(f" 子层 2(编码器-解码器注意力): {src_attn}")
print(f" 子层 3(前馈全连接层): {ff}")
# 8. 测试
# 子层 1:掩码多头自注意力
output1 = self_attn(position_y, position_y, position_y)
print(f"\n子层 1 输出形状: {output1.shape}") # [1, 10, 512]
# 子层 2:编码器-解码器注意力
encoder_output = torch.randn(batch_size, seq_len, d_model) # 编码器输出
output2 = src_attn(output1, encoder_output, encoder_output)
print(f"子层 2 输出形状: {output2.shape}") # [1, 10, 512]
# 子层 3:前馈全连接层
output3 = ff(output2)
print(f"子层 3 输出形状: {output3.shape}") # [1, 10, 512]
# 测试
if __name__ == "__main__":
use_decoder_layer()
代码说明
| 代码 | 说明 |
|---|---|
nn.Embedding(vocab_size, d_model) | 词嵌入层 |
PositionalEncoding(d_model) | 位置编码层 |
MultiHeadAttention(d_model, num_heads) | 多头注意力层 |
copy.deepcopy(multihead_attn) | 深拷贝 |
FeedForward(d_model) | 前馈全连接层 |
6 重难点与易错提醒
- ❗重点:使用深拷贝确保多头注意力层参数不共享。
- ❗重点:解码器层有三个子层。
- ❗重点:子层 1 是自注意力,子层 2 是编码器-解码器注意力。
- ⚠️易错:深拷贝需要
import copy。
7 课堂问答精选
Q1:为什么使用深拷贝?
A:深拷贝确保多头注意力层参数不共享,各玩各的。
Q2:解码器层有几个子层?
A:三个子层:掩码多头自注意力层、编码器-解码器注意力层、前馈全连接层。
Q3:子层 1 和子层 2 的区别是什么?
A:子层 1 是自注意力(Q=K=V),子层 2 是编码器-解码器注意力(Q 来自解码器,K 和 V 来自编码器)。
8 本课小结
- 解码器层有三个子层:掩码多头自注意力、编码器-解码器注意力、前馈全连接。
- 使用深拷贝确保多头注意力层参数不共享。
- 测试流程:创建词嵌入层 → 位置编码层 → 多头注意力层 → 前馈全连接层。
9 延伸思考
- 如何实现解码器层?
- 如何构建完整的解码器?