🎯 课程主题
编码器的代码实现——单个编码器层与多层堆叠。
📝 核心知识点
1. 单个编码器层(EncoderLayer)
- 概念说明:编码器的基本单元,包含 2 个子层。
- 关键细节:
- 子层 1:多头自注意力(带填充 mask)
- 子层 2:前馈神经网络(FFN)
- 每个子层都经过 SublayerConnection(残差 + 层归一化)
- 克隆 2 个 SC 模块
2. 多层编码器(Encoder)
- 概念说明:$N=6$ 个编码器层堆叠。
- 关键细节:
- 克隆 6 个 EncoderLayer
- 循环调用,前一层的输出作为后一层的输入
- 最后经过一次层归一化
- 输出编码器信息,传给解码器
3. 数据流向
- 概念说明:编码器的输入输出流程。
- 关键细节:
- 第一层输入:词嵌入 + 位置编码
- 后续层输入:上一层编码器的输出
- mask 控制是否使用填充掩码
- 最终输出:编码器的编码信息
🧮 核心公式与推导
单个编码器层:
$$H_1 = \text{SublayerConn}_1(X, \text{MultiHeadAttn})$$
$$\text{output} = \text{SublayerConn}_2(H_1, \text{FFN})$$
多层编码器:
$$X_0 = \text{Embedding} + \text{PositionalEncoding}$$
$$X_i = \text{EncoderLayer}i(X{i-1}, \text{mask}), \quad i = 1, 2, ..., N$$
$$\text{output} = \text{LayerNorm}(X_N)$$
🏗️ 模型架构与数据流向
💻 代码实战
import torch
import torch.nn as nn
import copy
def clones(module, N):
"""克隆模块N次"""
return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
class LayerNorm(nn.Module):
"""层归一化"""
def __init__(self, features, eps=1e-6):
super().__init__()
self.a_2 = nn.Parameter(torch.ones(features))
self.b_2 = nn.Parameter(torch.zeros(features))
self.eps = eps
def forward(self, x):
mean = x.mean(-1, keepdim=True)
std = x.std(-1, keepdim=True)
return self.a_2 * (x - mean) / (std + self.eps) + self.b_2
class SublayerConnection(nn.Module):
"""子层连接: LayerNorm + sublayer + Dropout + 残差"""
def __init__(self, size, dropout):
super().__init__()
self.norm = LayerNorm(size)
self.dropout = nn.Dropout(dropout)
def forward(self, x, sublayer):
return x + self.dropout(sublayer(self.norm(x)))
class EncoderLayer(nn.Module):
"""单个编码器层 = 2个子层"""
def __init__(self, size, self_attn, feed_forward, dropout):
"""
size: d_model
self_attn: 多头自注意力模块
feed_forward: 前馈神经网络模块
dropout: dropout比例
"""
super().__init__()
self.self_attn = self_attn
self.feed_forward = feed_forward
# 克隆2个SublayerConnection (编码器有2个子层)
self.sublayer = clones(SublayerConnection(size, dropout), 2)
self.size = size
def forward(self, x, mask):
"""
x: [batch, seq_len, d_model]
mask: 填充掩码
return: [batch, seq_len, d_model]
"""
# 子层1: 多头自注意力 (Q=K=V=x)
x = self.sublayer[0](
x, lambda x: self.self_attn(x, x, x, mask)
)
# 子层2: 前馈神经网络
x = self.sublayer[1](x, self.feed_forward)
return x
class Encoder(nn.Module):
"""完整编码器 = N层EncoderLayer堆叠"""
def __init__(self, layer, N):
"""
layer: 单个EncoderLayer
N: 层数 (通常为6)
"""
super().__init__()
# 克隆N个EncoderLayer
self.layers = clones(layer, N)
self.norm = LayerNorm(layer.size) # 最后的层归一化
def forward(self, x, mask):
"""
x: [batch, seq_len, d_model] (词嵌入+位置编码)
mask: 填充掩码
return: [batch, seq_len, d_model] 编码器输出
"""
# 逐层传递
for layer in self.layers:
x = layer(x, mask)
# 最后的层归一化
return self.norm(x)
# 使用示例
if __name__ == "__main__":
from transformer_components import MultiHeadedAttention, PositionwiseFeedForward
d_model = 512
h = 8
d_ff = 2048
N = 6
dropout = 0.1
batch_size = 2
seq_len = 10
# 创建组件
attn = MultiHeadedAttention(h, d_model, dropout)
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
# 创建单个编码器层
encoder_layer = EncoderLayer(d_model, attn, ff, dropout)
# 创建完整编码器 (6层)
encoder = Encoder(encoder_layer, N)
# 前向传播
x = torch.randn(batch_size, seq_len, d_model) # 词嵌入+位置编码
mask = None # 或填充掩码
output = encoder(x, mask)
print(f"输入: {x.shape}")
print(f"输出: {output.shape}") # [2, 10, 512]
⚠️ 常见问题与避坑指南
- 编码器有 2 个子层(自注意力 + FFN),解码器有 3 个
- $N=6$ 是论文设置,可根据任务调整
- 最后的 LayerNorm 是必要的(Pre-Norm 架构)
- 自注意力的 Q=K=V 都来自同一输入
💡 个人总结与延伸
编码器通过 6 层堆叠,逐步提取输入序列的深层语义信息。每层的残差连接和层归一化保证训练稳定。现代大模型中,Encoder-Decoder 架构用于翻译等任务,而 Decoder-only 架构(GPT 系列)去掉了编码器,更适合生成任务。