解码器层 - 代码实现
1 课程概览
本课实现解码器层(DecoderLayer)。解码器层比编码器层简单,因为组件已经实现完毕。解码器层由 3 个子层组成:掩码多头注意力子层、多头注意力子层(编码-解码注意力)、前馈全连接子层。每个子层包含残差连接和规范化层。需要导入编码器,因为解码器需要拿到编码器的输出结果。
2 核心概念与定义
- DecoderLayer:解码器层,由 3 个子层组成。
- self_attn:掩码多头注意力,处理解码器输入序列。
- src_attn:多头注意力(编码-解码注意力),原序列(编码器输出)。
- feed_forward:前馈全连接,强化特征。
3 模型与算法详解
解码器层结构
输入 x(解码器输入)
├── 子层1:掩码多头注意力 + Add & Norm
├── 子层2:多头注意力(编码-解码)+ Add & Norm
└── 子层3:前馈全连接 + Add & Norm
输出
3 个子层
| 子层 | 组件 | 说明 |
|---|---|---|
| 子层1 | 掩码多头注意力 | 处理解码器输入序列内部关系 |
| 子层2 | 多头注意力(编码-解码) | 编码器输出与解码器输出的注意力 |
| 子层3 | 前馈全连接 | 强化特征 |
self_attn vs src_attn
| 参数 | 说明 | 处理对象 |
|---|---|---|
| self_attn | 掩码多头注意力 | 解码器输入序列 |
| src_attn | 多头注意力(编码-解码) | 编码器输出 + 解码器输出 |
解码器层流程
1. 解码器输入 → 掩码多头注意力 → Add & Norm
2. 编码器输出 + 上一步结果 → 多头注意力 → Add & Norm
3. 上一步结果 → 前馈全连接 → Add & Norm
4. 输出
4 数学原理与推导
子层1(掩码多头注意力)
$$\text{output}_1 = \text{LayerNorm}(x + \text{MaskedMultiHeadAttention}(x, x, x, \text{self_mask}))$$
子层2(编码-解码注意力)
$$\text{output}_2 = \text{LayerNorm}(\text{output}_1 + \text{MultiHeadAttention}(\text{output}_1, \text{memory}, \text{memory}, \text{src_mask}))$$
其中 memory 是编码器的输出。
子层3(前馈全连接)
$$\text{output}_3 = \text{LayerNorm}(\text{output}_2 + \text{FeedForward}(\text{output}_2))$$
5 代码示例
import torch
import torch.nn as nn
from dm05_encoder import Encoder
from dm07_sublayer import SubLayerConnection
from dm04_clones import clones
class DecoderLayer(nn.Module):
"""解码器层"""
def __init__(self, d_model, self_attn, src_attn, feed_forward, dropout=0.1):
"""
初始化解码器层
:param d_model: 词向量维度
:param self_attn: 掩码多头注意力对象(处理解码器输入)
:param src_attn: 多头注意力对象(编码-解码注意力)
:param feed_forward: 前馈全连接对象
:param dropout: 随机失活概率
"""
super().__init__()
# 1. 定义属性
self.d_model = d_model
self.self_attn = self_attn
self.src_attn = src_attn
self.feed_forward = feed_forward
# 2. 定义 3 个子层连接结构
self.sublayer = clones(SubLayerConnection(d_model, dropout), 3)
def forward(self, x, memory, src_mask, tgt_mask):
"""
前向传播
:param x: 解码器输入 (batch, tgt_len, d_model)
:param memory: 编码器输出 (batch, src_len, d_model)
:param src_mask: 源数据掩码(编码器掩码)
:param tgt_mask: 目标数据掩码(解码器掩码)
:return: 解码器层输出
"""
# 1. 子层1:掩码多头注意力(Q=K=V=x,使用 tgt_mask)
x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))
# 2. 子层2:编码-解码注意力(Q=x, K=V=memory,使用 src_mask)
x = self.sublayer[1](x, lambda x: self.src_attn(x, memory, memory, src_mask))
# 3. 子层3:前馈全连接
x = self.sublayer[2](x, self.feed_forward)
return x
# 测试代码
def dm10_test_decoder_layer():
"""测试解码器层"""
from dm04_attention import MultiHeadAttention
from dm05_feedforward import FeedForward
d_model = 512
# 1. 准备数据
x = torch.randn(2, 4, d_model) # 解码器输入
memory = torch.randn(2, 6, d_model) # 编码器输出
# 2. 实例化子层对象
self_attn = MultiHeadAttention(d_model, 8) # 掩码多头注意力
src_attn = MultiHeadAttention(d_model, 8) # 编码-解码注意力
feed_forward = FeedForward(d_model, 2048, 0.1)
# 3. 创建解码器层对象
decoder_layer = DecoderLayer(d_model, self_attn, src_attn, feed_forward)
# 4. 构建掩码张量
src_mask = torch.zeros(2, 1, 1, 6) # 源数据掩码
tgt_mask = torch.zeros(2, 1, 4, 4) # 目标数据掩码
# 5. 前向传播
output = decoder_layer(x, memory, src_mask, tgt_mask)
print(f"解码器输入形状: {x.shape}")
print(f"编码器输出形状: {memory.shape}")
print(f"解码器层输出形状: {output.shape}")
return output
if __name__ == "__main__":
dm10_test_decoder_layer()
代码说明
| 代码 | 说明 |
|---|---|
clones(SubLayerConnection(...), 3) | 克隆 3 个子层连接结构 |
self.self_attn(x, x, x, tgt_mask) | 子层1:掩码多头注意力 |
self.src_attn(x, memory, memory, src_mask) | 子层2:编码-解码注意力 |
self.feed_forward | 子层3:前馈全连接 |
memory | 编码器输出 |
6 重难点与易错提醒
- ❗重点:解码器层由 3 个子层组成。
- ❗重点:self_attn 是掩码多头注意力,src_attn 是编码-解码注意力。
- ❗重点:子层2 的 Q=x, K=V=memory(编码器输出)。
- ⚠️易错:需要导入编码器,因为解码器需要编码器输出。
- 💡深入理解:解码器比编码器多一个子层(编码-解码注意力)。
7 课堂问答精选
Q1:解码器层有几个子层?
A:3 个子层:①掩码多头注意力子层;②多头注意力子层(编码-解码注意力);③前馈全连接子层。
Q2:self_attn 和 src_attn 的区别是什么?
A:self_attn 是掩码多头注意力,处理解码器输入序列内部关系;src_attn 是编码-解码注意力,处理编码器输出与解码器输出的关系。
Q3:子层2(编码-解码注意力)的 Q、K、V 是什么?
A:Q=x(解码器输入),K=V=memory(编码器输出)。使用 src_mask。
Q4:为什么需要导入编码器?
A:因为解码器需要拿到编码器的输出结果(memory),作为子层2(编码-解码注意力)的 K 和 V。
8 本课小结
- 解码器层:3 个子层(掩码多头注意力、编码-解码注意力、前馈全连接)。
- self_attn:掩码多头注意力,处理解码器输入。
- src_attn:编码-解码注意力,Q=x, K=V=memory。
- 需要导入编码器,获取编码器输出。
- 解码器比编码器多一个子层。
9 延伸思考
- 解码器如何由 6 个解码器层组成?
- 解码器与编码器有什么区别?