编码器 - 代码实现及测试
1 课程概览
本课实现编码器(Encoder)。编码器由 6 个编码器层组成,通过 clones 函数克隆 N 个编码器层。每个编码器层有 2 个子层(多头注意力子层、前馈全连接子层)。最后经过一个规范化层。代码越往后越简单,因为底层组件已经拼接好,上层只需组装。
2 核心概念与定义
- Encoder:编码器,由 6 个编码器层组成。
- clones:克隆函数,克隆 N 个编码器层。
- 最终规范化层:6 个编码器层处理完后,再进行一次规范化。
3 模型与算法详解
编码器结构
输入 x
├── 编码器层 1(2 个子层)
├── 编码器层 2(2 个子层)
├── 编码器层 3(2 个子层)
├── 编码器层 4(2 个子层)
├── 编码器层 5(2 个子层)
├── 编码器层 6(2 个子层)
└── 最终规范化层
输出
编码器总结
| 层级 | 数量 | 说明 |
|---|---|---|
| 编码器层 | 6 个 | 默认 6 个,可灵活调整 |
| 子层 | 2 个/层 | 多头注意力子层、前馈全连接子层 |
| 最终规范化层 | 1 个 | 6 个编码器层处理后 |
学习思路
代码越往后越简单,底层组件已拼接好,上层只需组装。
- 抽丝剥茧,一层一层往上搭建
- 站在宏观角度,思维顺畅
- 脑子里要有框架和轮廓
N 参数的灵活性
写 N 而不是 6,因为不同模型编码器层数不同。
- 传统 BERT 默认 6 个编码器层
- 后续变形可能是 12 层
- 写 N 更灵活
4 数学原理与推导
编码器前向传播
$$\text{output} = \text{LayerNorm}(\text{EncoderLayer}_6(...\text{EncoderLayer}_1(x)))$$
每个编码器层
$$\text{output}_i = \text{EncoderLayer}i(\text{output}{i-1}, \text{mask})$$
5 代码示例
import torch
import torch.nn as nn
from dm04_encoder_layer import EncoderLayer
from dm04_clones import clones
from dm06_layer_norm import LayerNorm
class Encoder(nn.Module):
"""编码器"""
def __init__(self, layer, N):
"""
初始化编码器
:param layer: 单个编码器层
:param N: 编码器层的数量
"""
super().__init__()
# 1. 克隆 N 个编码器层对象
self.layers = clones(layer, N)
# 2. 定义最终的规范化层
self.norm = LayerNorm(layer.d_model)
def forward(self, x, mask):
"""
前向传播
:param x: 输入张量 (batch, seq_len, d_model)
:param mask: 掩码张量
:return: 编码器输出
"""
# 1. 依次通过每个编码器层
for layer in self.layers:
x = layer(x, mask)
# 2. 最终规范化
return self.norm(x)
# clones 函数
def clones(module, N):
"""克隆 N 个模块"""
return nn.ModuleList([module for _ in range(N)])
# 测试代码
def dm09_test_encoder():
"""测试编码器"""
from dm01_input import Embeddings, PositionalEncoding
from dm04_attention import MultiHeadAttention
from dm05_feedforward import FeedForward
from dm08_encoder_layer import EncoderLayer
d_model = 512
N = 6 # 编码器层数量
# 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. 创建编码器(6 个编码器层)
encoder = Encoder(encoder_layer, N)
# 5. 构建掩码张量
mask = torch.zeros(2, 1, 4, 4)
# 6. 前向传播
output = encoder(x, mask)
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
print(f"编码器层数量: {N}")
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__":
dm09_test_encoder()
代码说明
| 代码 | 说明 |
|---|---|
clones(layer, N) | 克隆 N 个编码器层 |
LayerNorm(layer.d_model) | 最终规范化层 |
for layer in self.layers: | 依次通过每个编码器层 |
self.norm(x) | 最终规范化 |
6 重难点与易错提醒
- ❗重点:编码器由 6 个编码器层组成。
- ❗重点:最后有一个最终规范化层。
- ❗重点:N 参数灵活,不同模型层数不同。
- 💡深入理解:代码越往后越简单,底层组件已拼接好。
- 💡学习建议:站在宏观角度,脑子里要有框架和轮廓。
7 课堂问答精选
Q1:编码器由几个编码器层组成?
A:默认 6 个编码器层。但写 N 参数更灵活,因为不同模型层数不同(如 BERT 变形可能是 12 层)。
Q2:编码器的最后一步是什么?
A:6 个编码器层处理完后,再经过一个最终规范化层。
Q3:为什么写 N 而不是 6?
A:传统 BERT 默认 6 个编码器层,但后续变形可能是 12 层。写 N 更灵活。
Q4:编码器的前向传播过程是什么?
A:输入 x 依次通过 6 个编码器层,每个编码器层有 2 个子层(多头注意力、前馈全连接),最后经过最终规范化层。
8 本课小结
- 编码器:6 个编码器层 + 最终规范化层。
- 每个编码器层:2 个子层(多头注意力、前馈全连接)。
- 使用
clones函数克隆 N 个编码器层。 - N 参数灵活,不同模型层数不同。
- 代码越往后越简单,上层只需组装。
9 延伸思考
- 解码器层如何实现?
- 解码器层与编码器层有什么区别?