编码器层 - 代码实现
1 课程概览
本课实现编码器层(EncoderLayer)。编码器层是编码器的组成单元,由两个子层组成:多头注意力子层和前馈全连接子层。每个子层包含残差连接和规范化层。编码器层完成一次对输入特征的特征提取。通过 clones 函数克隆两个子层连接结构。
2 核心概念与定义
- EncoderLayer:编码器层,编码器的组成单元。
- clones:克隆函数,用于克隆多个子层连接结构。
- 子层1:多头注意力 + Add & Norm。
- 子层2:前馈全连接 + Add & Norm。
3 模型与算法详解
编码器层结构
输入 x
├── 子层1:多头注意力 + Add & Norm
└── 子层2:前馈全连接 + Add & Norm
输出
嵌套关系
组件 → 子层 → 编码器层 → 编码器
| 层级 | 说明 |
|---|---|
| 组件 | 多头注意力、前馈全连接、规范化层 |
| 子层 | 组件 + 残差连接 + 规范化层 |
| 编码器层 | 2 个子层 |
| 编码器 | 6 个编码器层 |
编码器层流程
1. 词向量 + 位置编码(输入处理)
2. 多头注意力 + 残差连接 + 规范化层
3. 前馈全连接 + 残差连接 + 规范化层
4. 输出
4 数学原理与推导
子层1(多头注意力)
$$\text{output}_1 = \text{LayerNorm}(x + \text{MultiHeadAttention}(x, x, x))$$
子层2(前馈全连接)
$$\text{output}_2 = \text{LayerNorm}(\text{output}_1 + \text{FeedForward}(\text{output}_1))$$
5 代码示例
import torch
import torch.nn as nn
from dm01_input import *
from dm02_position import *
from dm03_components import *
from dm07_sublayer import SubLayerConnection
from dm04_clones import clones
class EncoderLayer(nn.Module):
"""编码器层"""
def __init__(self, d_model, self_attn, feed_forward, dropout=0.1):
"""
初始化编码器层
:param d_model: 词向量维度,例如 512
:param self_attn: 多头注意力对象
:param feed_forward: 前馈全连接对象
:param dropout: 随机失活概率
"""
super().__init__()
# 1. 保存子层实例
self.d_model = d_model
self.self_attn = self_attn
self.feed_forward = feed_forward
# 2. 克隆 2 个子层连接结构
self.sublayer = clones(SubLayerConnection(d_model, dropout), 2)
def forward(self, x, mask):
"""
前向传播
:param x: 输入张量 (batch, seq_len, d_model)
:param mask: 掩码张量
:return: 编码器层输出
"""
# 1. 子层1:多头注意力 + Add & Norm
# 使用 lambda 定义子层函数
x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))
# 2. 子层2:前馈全连接 + Add & Norm
x = self.sublayer[1](x, self.feed_forward)
return x
# clones 函数
def clones(module, N):
"""克隆 N 个模块"""
return nn.ModuleList([module for _ in range(N)])
# 测试代码
def dm08_test_encoder_layer():
"""测试编码器层"""
from dm04_attention import MultiHeadAttention
from dm05_feedforward import FeedForward
d_model = 512
# 1. 准备数据
x = use_position() # (2, 4, 512)
# 2. 实例化子层对象
multi_head_attn = MultiHeadAttention(d_model, 8)
feed_forward = FeedForward(d_model, 2048, 0.1)
# 3. 创建编码器层对象
encoder_layer = EncoderLayer(d_model, multi_head_attn, feed_forward)
# 4. 构建掩码张量
mask = torch.zeros(2, 1, 4, 4)
# 5. 前向传播
output = encoder_layer(x, mask)
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
return output
def use_position():
"""获取位置编码结果"""
VOCAB_SIZE = 1000
D_MODEL = 512
my_embedding = Embeddings(D_MODEL, VOCAB_SIZE)
x = torch.LongTensor([[1, 2, 3, 4], [5, 6, 7, 8]])
embedded_x = my_embedding(x)
my_position = PositionalEncoding(D_MODEL, dropout=0.1, max_len=60)
position_x = my_position(embedded_x)
return position_x
if __name__ == "__main__":
dm08_test_encoder_layer()
代码说明
| 代码 | 说明 |
|---|---|
clones(SubLayerConnection(...), 2) | 克隆 2 个子层连接结构 |
self.sublayer[0](x, lambda x: ...) | 子层1:多头注意力 |
self.sublayer[1](x, self.feed_forward) | 子层2:前馈全连接 |
self.self_attn(x, x, x, mask) | 多头注意力(Q=K=V=x) |
6 重难点与易错提醒
- ❗重点:编码器层由 2 个子层组成。
- ❗重点:通过
clones函数克隆子层连接结构。 - ⚠️易错:子层函数用
lambda定义。 - 💡深入理解:嵌套关系:组件 → 子层 → 编码器层 → 编码器。
7 课堂问答精选
Q1:编码器层的作用是什么?
A:编码器层是编码器的组成单元,完成一次对输入特征的特征提取。由两个子层组成:多头注意力子层和前馈全连接子层。
Q2:编码器层有几个子层?
A:2 个子层。子层1:多头注意力 + Add & Norm;子层2:前馈全连接 + Add & Norm。
Q3:如何克隆子层连接结构?
A:使用 clones 函数,传入子层连接结构和数量 N。例如 clones(SubLayerConnection(d_model, dropout), 2)。
Q4:编码器层的嵌套关系是什么?
A:组件 → 子层 → 编码器层 → 编码器。组件(多头注意力、前馈全连接)封装成子层(组件+残差+规范化),子层拼接成编码器层,编码器层重复 6 次组成编码器。
8 本课小结
- 编码器层:2 个子层(多头注意力、前馈全连接)。
- 每个子层包含残差连接和规范化层。
- 使用
clones函数克隆子层连接结构。 - 嵌套关系:组件 → 子层 → 编码器层 → 编码器。
9 延伸思考
- 如何测试编码器层?
- 编码器如何由 6 个编码器层组成?