08-编码器结构 🏗️
本文档详细解析 Transformer 编码器(Encoder)的完整结构,涵盖编码器整体架构、编码器层内部组件、多头自注意力机制、位置前馈神经网络、残差连接与层归一化,以及完整的 PyTorch 代码实现。通过理论与实践相结合的方式,帮助读者深入理解编码器的工作原理和实现细节 🛠️
章节阅读路线图 🗺️
阅读顺序说明:
- 第1章 → 第2章:先了解编码器整体架构,再深入单个编码器层
- 第2章 → 第3章:理解编码器层后,学习第一个核心组件------多头自注意力
- 第3章 → 第4章:掌握注意力机制后,学习第二个核心组件------前馈神经网络
- 第4章 → 第5章:了解两个子层后,学习如何连接它们------残差连接和层归一化
- 第5章 → 第6章:把所有组件整合成完整可运行的代码
1. 编码器整体架构 🌐
本章介绍 Transformer 编码器的宏观结构和设计思想
Transformer 编码器是 Transformer 模型的核心组成部分之一,负责将输入序列转换为包含丰富语义信息的高维表示。
1.1 编码器结构概览 📊
Transformer 编码器由以下部分组成:
- 输入嵌入层(Input Embedding):将离散的 token 转换为连续向量
- 位置编码(Positional Encoding):为嵌入向量添加位置信息
- 编码器层堆栈(Encoder Layers Stack):由 N 个相同的编码器层组成(论文中 N=6)
css
输入序列 → 嵌入层 → + 位置编码 → [编码器层 × N] → 编码输出
1.2 为什么需要编码器?🤔
编码器的任务是 理解输入序列,并为每个位置生成一个包含全局上下文信息的表示。与传统的 RNN 不同,Transformer 编码器可以并行处理整个序列,并通过自注意力机制捕捉任意位置之间的依赖关系。
编码器的优势:
| 特性 | RNN/CNN | Transformer 编码器 |
|---|---|---|
| 并行计算 | ❌ 顺序处理 | ✅ 完全并行 |
| 长距离依赖 | ❌ 随距离衰减 | ✅ 直接建模 |
| 计算复杂度 | O(n) 或 O(n×k) | O(n²) |
| 可解释性 | ❌ 黑盒 | ✅ 注意力权重可视化 |
💡 n 表示序列长度,k 表示卷积核大小
参考资料:
- Attention Is All You Need 论文原文 -- arXiv ⭐值得阅读
- Transformer模型的核心机制:自注意力与多头注意力 -- 飞书文档
- Transformer各层网络结构详解! -- 腾讯云
2. 编码器层内部结构 🔬
本章深入剖析单个编码器层的内部组件
每个编码器层都包含两个核心子层,它们协同工作来提取和转换序列信息。
2.1 两个核心子层 🧩
第1子层:多头自注意力机制(Multi-Head Self-Attention)
- 让序列中的每个位置都能"看到"其他所有位置
- 捕捉序列内部的全局依赖关系
- 输出形状与输入相同:
[batch_size, seq_len, d_model]
第2子层:位置前馈神经网络(Position-wise Feed-Forward Network)
- 对每个位置独立进行非线性变换
- 提取和重组特征信息
- 输出形状保持不变:
[batch_size, seq_len, d_model]
2.2 数据流动过程 🔄
scss
输入 x
↓
[子层1: 多头自注意力]
↓
Add & Norm (残差连接 + 层归一化)
↓
[子层2: 位置前馈网络]
↓
Add & Norm (残差连接 + 层归一化)
↓
输出
每个子层后面都紧跟一个 残差连接 和 层归一化,这被称为 "Add & Norm" 操作。
2.3 维度设计 📐
为了保证数据能够在各个子层之间顺畅流动,编码器遵循一个重要的设计原则:
ini
所有子层的输入输出维度都保持为 d_model = 512
这意味着:
- 嵌入层输出维度 = 512
- 多头注意力输入输出 = 512
- 前馈网络输入输出 = 512
- 编码器层输入输出 = 512
这种设计使得我们可以轻松堆叠多个编码器层,而无需担心维度不匹配的问题。
3. 多头自注意力机制 🎯
本章详解编码器的第一个核心子层
多头自注意力是 Transformer 的灵魂组件,它让模型能够从不同的"视角"捕捉序列中的依赖关系。
3.1 从自注意力到多头注意力 🧠
自注意力(Self-Attention) 的基本思想是:让序列中的每个位置都与所有其他位置计算注意力,从而获得全局上下文信息。
多头注意力(Multi-Head Attention) 的改进在于:
- 将输入向量分割成 h 个头(head)
- 每个头独立计算注意力
- 最后将所有头的结果拼接起来
这样做的好处是: 不同的头可以关注不同类型的信息,比如语法关系、语义关系、位置关系等。
3.2 多头注意力的计算流程 📝
css
输入 Q, K, V
↓
线性变换(分割成 h 个头)
↓
[头1: 缩放点积注意力] → 输出1
[头2: 缩放点积注意力] → 输出2
...
[头h: 缩放点积注意力] → 输出h
↓
拼接所有头的输出
↓
线性变换(融合信息)
↓
多头注意力输出
具体计算步骤:
- 线性投影:将 Q、K、V 分别通过 h 个不同的线性变换,得到 h 组查询、键、值
- 缩放点积注意力:对每组 (Qᵢ, Kᵢ, Vᵢ) 计算注意力
- 拼接:将 h 个头的输出拼接起来
- 最终线性变换:通过一个线性层融合所有头的信息
3.3 代码实现 💻
在 04-缩放点积注意力代码实现(CSDN)中我们已经学习了缩放点积注意力的实现,现在来看多头注意力的完整代码:
python
import torch
import torch.nn as nn
import math
class MultiHeadAttention(nn.Module):
"""
多头自注意力机制
参数:
d_model: 模型维度(默认512)
n_heads: 注意力头数(默认8)
dropout: Dropout概率
"""
def __init__(self, d_model=512, n_heads=8, dropout=0.1):
super(MultiHeadAttention, self).__init__()
# 验证 d_model 是否能被 n_heads 整除
assert d_model % n_heads == 0, "d_model 必须能被 n_heads 整除"
self.d_model = d_model
self.n_heads = n_heads
self.d_k = d_model // n_heads # 每个头的维度
# 定义 Q, K, V 的线性变换层
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.W_o = nn.Linear(d_model, d_model)
# 缩放点积注意力(复用之前的实现)
self.attention = ScaledDotProductAttention(dropout=dropout)
def forward(self, Q, K, V, mask=None):
"""
前向传播
参数:
Q, K, V: 形状均为 [batch_size, seq_len, d_model]
mask: 可选的掩码
返回:
output: 多头注意力输出 [batch_size, seq_len, d_model]
attention_weights: 注意力权重 [batch_size, n_heads, seq_len, seq_len]
"""
batch_size = Q.size(0)
# 1. 线性变换并分割成多个头
# Q: [batch_size, seq_len, d_model] → [batch_size, seq_len, n_heads, d_k]
# → [batch_size, n_heads, seq_len, d_k]
Q = self.W_q(Q).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
K = self.W_k(K).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
V = self.W_v(V).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
# 2. 对每个头应用缩放点积注意力
# attention_weights: [batch_size, n_heads, seq_len, seq_len]
attn_output, attention_weights = self.attention(Q, K, V, mask)
# 3. 拼接所有头的输出
# [batch_size, n_heads, seq_len, d_k] → [batch_size, seq_len, n_heads, d_k]
# → [batch_size, seq_len, d_model]
output = attn_output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
# 4. 最终线性变换
output = self.W_o(output)
return output, attention_weights
class ScaledDotProductAttention(nn.Module):
"""缩放点积注意力机制(供多头注意力调用)"""
def __init__(self, dropout=0.1):
super(ScaledDotProductAttention, self).__init__()
self.dropout = nn.Dropout(dropout)
def forward(self, Q, K, V, mask=None):
d_k = Q.size(-1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
attention_weights = torch.softmax(scores, dim=-1)
attention_weights = self.dropout(attention_weights)
output = torch.matmul(attention_weights, V)
return output, attention_weights
代码解析:
d_k = d_model // n_heads:计算每个头的维度,例如 512 // 8 = 64.view()和.transpose():用于调整张量形状,将多头维度提取出来.contiguous():确保张量在内存中是连续的,避免后续操作出错self.W_o:最后的线性层将所有头的信息融合起来
参考资料:
- Transformer多头自注意力及掩码机制详解 -- CSDN
- Transformer的多头注意力机制 -- CSDN
- 注意力机制/自注意力机制/多头注意力机制/Transformer相关整理 -- 知乎
4. 位置前馈神经网络 🧮
本章详解编码器的第二个核心子层
位置前馈神经网络(Position-wise Feed-Forward Network, FFN)对序列中的每个位置独立进行非线性变换,提取和重组特征信息。
4.1 FFN 的结构 📐
FFN 由两个线性变换和一个激活函数组成:
scss
FFN(x) = max(0, xW₁ + b₁)W₂ + b₂
具体结构:
ini
输入 x [batch_size, seq_len, d_model]
↓
线性层1: d_model → d_ff(扩展维度,通常 d_ff = 2048)
↓
ReLU 激活函数
↓
Dropout
↓
线性层2: d_ff → d_model(还原维度)
↓
Dropout
↓
输出 [batch_size, seq_len, d_model]
4.2 为什么叫"位置前馈"?🤔
"位置前馈"这个名字强调了 FFN 的一个重要特性: 对每个位置独立应用相同的变换。
这意味着:
- 序列中第 i 个位置的 FFN 计算完全不依赖其他位置
- 所有位置共享相同的权重 W₁、b₁、W₂、b₂
- FFN 可以并行处理所有位置
这与多头注意力形成对比:
- 多头注意力:捕捉位置之间的关系(交互信息)
- FFN:对每个位置独立进行特征提取(独立处理)
4.3 维度扩展的意义 📊
FFN 先将维度从 512 扩展到 2048,然后再压缩回 512。这种"先扩展后压缩"的设计有以下好处:
- 增加模型容量:更大的隐藏层可以学习更复杂的特征变换
- 非线性表达能力:在高维空间中应用激活函数,增强非线性建模能力
- 信息重组:将多头注意力提取的信息进行重组和精炼
4.4 代码实现 💻
python
class PositionwiseFeedForward(nn.Module):
"""
位置前馈神经网络
参数:
d_model: 输入输出维度(默认512)
d_ff: 隐藏层维度(默认2048)
dropout: Dropout概率
"""
def __init__(self, d_model=512, d_ff=2048, dropout=0.1):
super(PositionwiseFeedForward, self).__init__()
# 两个线性层
self.fc1 = nn.Linear(d_model, d_ff)
self.fc2 = nn.Linear(d_ff, d_model)
# Dropout层
self.dropout = nn.Dropout(dropout)
def forward(self, x):
"""
前向传播
参数:
x: 输入张量 [batch_size, seq_len, d_model]
返回:
output: 输出张量 [batch_size, seq_len, d_model]
"""
# 1. 第一层线性变换 + ReLU激活 + Dropout
x = self.dropout(F.relu(self.fc1(x)))
# 2. 第二层线性变换 + Dropout
output = self.dropout(self.fc2(x))
return output
代码解析:
F.relu():使用 ReLU 激活函数max(0, x),引入非线性- 两次 Dropout:分别在两个线性层之后应用,防止过拟合
- 维度变化 :
[batch, seq, 512] → [batch, seq, 2048] → [batch, seq, 512]
参考资料:
5. 残差连接与层归一化 🔗
本章介绍连接子层的关键组件:残差连接和层归一化
残差连接(Residual Connection)和层归一化(Layer Normalization)是 Transformer 能够训练深层网络的关键技术。
5.1 残差连接 🔄
什么是残差连接?
残差连接是一种"捷径"(shortcut),它让输入直接跳过某些层,加到输出上:
ini
output = x + Sublayer(x)
其中:
- x:子层的输入
- Sublayer(x):子层的输出(如多头注意力或 FFN)
- output:残差连接的结果
为什么需要残差连接?
- 缓解梯度消失:深层网络中,梯度在反向传播时容易消失。残差连接提供了"高速公路",让梯度可以直接流向浅层
- 保留原始信息:即使子层学到的是恒等映射(不改变输入),残差连接也能保证信息不丢失
- 加速训练收敛:实验表明,残差连接可以显著加快训练速度
直观理解:
想象你在学习一门新知识:
- 没有残差连接:每次学习都完全依赖上一步的理解,容易"走偏"
- 有残差连接:在学习新知识的同时,保留原有的理解,逐步改进
5.2 层归一化 📏
什么是层归一化?
层归一化(Layer Normalization)对每个样本的所有特征进行归一化,使其均值为 0,方差为 1:
scss
LayerNorm(x) = γ × (x - μ) / σ + β
其中:
- μ:该样本所有特征的均值
- σ:该样本所有特征的标准差
- γ、β:可学习的缩放和平移参数
为什么需要层归一化?
- 稳定训练:归一化后的数据分布更稳定,避免梯度爆炸或消失
- 加速收敛:允许使用更大的学习率,减少训练时间
- 正则化效果:引入轻微噪声,有一定防止过拟合的作用
层归一化 vs 批归一化:
| 特性 | 层归一化(LayerNorm) | 批归一化(BatchNorm) |
|---|---|---|
| 归一化维度 | 对每个样本的所有特征 | 对每个特征的所有样本 |
| 适用场景 | NLP、序列数据 | CV、图像数据 |
| Batch大小影响 | 无影响 | 小batch效果差 |
| 序列长度影响 | 支持变长序列 | 需要固定长度 |
5.3 Add & Norm 组合 🔧
在 Transformer 中,残差连接和层归一化总是组合使用,称为 "Add & Norm":
ini
output = LayerNorm(x + Sublayer(x))
标准实现(Post-LN):
python
class SublayerConnection(nn.Module):
"""
残差连接后跟层归一化(Post-LN)
参数:
size: 特征维度
dropout: Dropout概率
"""
def __init__(self, size, dropout=0.1):
super(SublayerConnection, self).__init__()
self.norm = LayerNorm(size)
self.dropout = nn.Dropout(dropout)
def forward(self, x, sublayer):
"""
应用残差连接到任意子层
参数:
x: 输入
sublayer: 子层函数(如多头注意力或FFN)
返回:
output: 残差连接 + Dropout + 层归一化
"""
# x + Dropout(Sublayer(x)) 然后归一化
return x + self.dropout(sublayer(x))
class LayerNorm(nn.Module):
"""
层归一化实现
参数:
features: 特征维度
eps: 防止除以0的小常数
"""
def __init__(self, features, eps=1e-6):
super(LayerNorm, self).__init__()
# 可学习的缩放和平移参数
self.a_2 = nn.Parameter(torch.ones(features))
self.b_2 = nn.Parameter(torch.zeros(features))
self.eps = eps
def forward(self, x):
"""
层归一化前向传播
参数:
x: 输入张量 [batch_size, seq_len, features]
返回:
output: 归一化后的张量
"""
# 计算最后一个维度的均值和标准差
mean = x.mean(-1, keepdim=True)
std = x.std(-1, keepdim=True)
# 归一化 + 缩放和平移
return self.a_2 * (x - mean) / (std + self.eps) + self.b_2
代码解析:
keepdim=True:保持维度不变,方便广播计算self.a_2和self.b_2:可学习参数,让模型决定是否需要缩放和平移eps=1e-6:防止除以 0 的小常数,保证数值稳定性
💡 注意 :上面的实现是 Post-LN 版本(先子层,后归一化)。现代 Transformer 也常用 Pre-LN 版本(先归一化,后子层),训练更稳定。
5.4 Pre-LN vs Post-LN 📊
Post-LN(原始论文):
ini
output = LayerNorm(x + Sublayer(x))
Pre-LN(改进版):
ini
output = x + Sublayer(LayerNorm(x))
| 特性 | Post-LN | Pre-LN |
|---|---|---|
| 训练稳定性 | 需要学习率预热 | 更稳定,无需预热 |
| 收敛速度 | 较慢 | 较快 |
| 最终性能 | 略高 | 略低但差距小 |
| 使用场景 | 原始Transformer | 现代变体(如GPT) |
参考资料:
- Transformer中的残差连接与层归一化 -- 飞书文档
- Transformer中残差连接和层归一化原理解析 -- 腾讯云
- 探秘Transformer系列之(14)--- 残差网络和归一化 -- 博客园
6. 完整代码实现 🎯
本章提供从头到尾可运行的编码器完整代码
6.1 编码器层实现 🔨
将前面学习的组件组合起来,实现完整的编码器层:
python
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
import copy
class EncoderLayer(nn.Module):
"""
Transformer 编码器层
包含两个子层:
1. 多头自注意力
2. 位置前馈神经网络
每个子层后都有残差连接和层归一化
"""
def __init__(self, d_model=512, n_heads=8, d_ff=2048, dropout=0.1):
super(EncoderLayer, self).__init__()
# 两个核心子层
self.self_attn = MultiHeadAttention(d_model, n_heads, dropout)
self.feed_forward = PositionwiseFeedForward(d_model, d_ff, dropout)
# 两个残差连接模块
self.sublayer = clones(SublayerConnection(d_model, dropout), 2)
self.d_model = d_model
def forward(self, x, mask=None):
"""
前向传播
参数:
x: 输入 [batch_size, seq_len, d_model]
mask: 可选的掩码
返回:
output: 编码器层输出 [batch_size, seq_len, d_model]
"""
# 子层1: 多头自注意力 + 残差连接 + 层归一化
x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask)[0])
# 子层2: 前馈网络 + 残差连接 + 层归一化
output = self.sublayer[1](x, self.feed_forward)
return output
def clones(module, N):
"""创建 N 个相同的层"""
return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
6.2 完整编码器实现 🏗️
将多个编码器层堆叠起来,形成完整的编码器:
python
class Encoder(nn.Module):
"""
Transformer 编码器
由 N 个相同的编码器层堆叠而成
"""
def __init__(self, layer, N=6):
super(Encoder, self).__init__()
self.layers = clones(layer, N)
self.norm = LayerNorm(layer.d_model)
def forward(self, x, mask=None):
"""
前向传播
参数:
x: 输入嵌入 + 位置编码 [batch_size, seq_len, d_model]
mask: 可选的掩码
返回:
output: 编码器输出 [batch_size, seq_len, d_model]
"""
# 依次通过 N 个编码器层
for layer in self.layers:
x = layer(x, mask)
# 最后应用一次层归一化
return self.norm(x)
6.3 完整可运行示例 🚀
python
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
import copy
# ========== 基础组件 ==========
class LayerNorm(nn.Module):
"""层归一化"""
def __init__(self, features, eps=1e-6):
super(LayerNorm, self).__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):
"""残差连接 + 层归一化"""
def __init__(self, size, dropout=0.1):
super(SublayerConnection, self).__init__()
self.norm = LayerNorm(size)
self.dropout = nn.Dropout(dropout)
def forward(self, x, sublayer):
return x + self.dropout(sublayer(self.norm(x)))
def clones(module, N):
"""创建N个相同的层"""
return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
# ========== 注意力机制 ==========
class ScaledDotProductAttention(nn.Module):
"""缩放点积注意力"""
def __init__(self, dropout=0.1):
super(ScaledDotProductAttention, self).__init__()
self.dropout = nn.Dropout(dropout)
def forward(self, Q, K, V, mask=None):
d_k = Q.size(-1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
attention_weights = torch.softmax(scores, dim=-1)
attention_weights = self.dropout(attention_weights)
output = torch.matmul(attention_weights, V)
return output, attention_weights
class MultiHeadAttention(nn.Module):
"""多头自注意力"""
def __init__(self, d_model=512, n_heads=8, dropout=0.1):
super(MultiHeadAttention, self).__init__()
assert d_model % n_heads == 0
self.d_model = d_model
self.n_heads = n_heads
self.d_k = d_model // n_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.W_o = nn.Linear(d_model, d_model)
self.attention = ScaledDotProductAttention(dropout=dropout)
def forward(self, Q, K, V, mask=None):
batch_size = Q.size(0)
Q = self.W_q(Q).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
K = self.W_k(K).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
V = self.W_v(V).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
attn_output, attention_weights = self.attention(Q, K, V, mask)
output = attn_output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
output = self.W_o(output)
return output, attention_weights
# ========== 前馈网络 ==========
class PositionwiseFeedForward(nn.Module):
"""位置前馈神经网络"""
def __init__(self, d_model=512, d_ff=2048, dropout=0.1):
super(PositionwiseFeedForward, self).__init__()
self.fc1 = nn.Linear(d_model, d_ff)
self.fc2 = nn.Linear(d_ff, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
x = self.dropout(F.relu(self.fc1(x)))
output = self.dropout(self.fc2(x))
return output
# ========== 编码器层 ==========
class EncoderLayer(nn.Module):
"""编码器层"""
def __init__(self, d_model=512, n_heads=8, d_ff=2048, dropout=0.1):
super(EncoderLayer, self).__init__()
self.self_attn = MultiHeadAttention(d_model, n_heads, dropout)
self.feed_forward = PositionwiseFeedForward(d_model, d_ff, dropout)
self.sublayer = clones(SublayerConnection(d_model, dropout), 2)
self.d_model = d_model
def forward(self, x, mask=None):
x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask)[0])
output = self.sublayer[1](x, self.feed_forward)
return output
# ========== 完整编码器 ==========
class Encoder(nn.Module):
"""Transformer编码器"""
def __init__(self, layer, N=6):
super(Encoder, self).__init__()
self.layers = clones(layer, N)
self.norm = LayerNorm(layer.d_model)
def forward(self, x, mask=None):
for layer in self.layers:
x = layer(x, mask)
return self.norm(x)
# ========== 测试代码 ==========
def test_encoder():
"""测试编码器"""
torch.manual_seed(42)
# 参数设置
batch_size = 2
seq_len = 10
d_model = 512
n_heads = 8
d_ff = 2048
n_layers = 6
# 随机输入(模拟嵌入 + 位置编码)
x = torch.randn(batch_size, seq_len, d_model)
# 创建编码器
encoder_layer = EncoderLayer(d_model, n_heads, d_ff)
encoder = Encoder(encoder_layer, n_layers)
# 前向传播
output = encoder(x)
print("=" * 60)
print("Transformer 编码器测试")
print("=" * 60)
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
print(f"编码器层数: {n_layers}")
print(f"模型维度: {d_model}")
print(f"注意力头数: {n_heads}")
print(f"前馈网络维度: {d_ff}")
print("=" * 60)
return output
if __name__ == "__main__":
test_encoder()
6.4 运行结果示例 📊
makefile
============================================================
Transformer 编码器测试
============================================================
输入形状: torch.Size([2, 10, 512])
输出形状: torch.Size([2, 10, 512])
编码器层数: 6
模型维度: 512
注意力头数: 8
前馈网络维度: 2048
============================================================
可以看到:
- 输入输出形状一致,说明编码器保持了序列结构
- 通过 6 层编码器层的处理,输入的语义信息被逐步提炼和增强
7. 总结 📝
本节我们完成了 Transformer 编码器结构的全面解析,核心要点回顾:
7.1 编码器架构总结 🏗️
| 组件 | 作用 | 维度变化 |
|---|---|---|
| 输入嵌入 | Token → 向量 | [batch, seq, vocab] → [batch, seq, d_model] |
| 位置编码 | 添加位置信息 | [batch, seq, d_model] → [batch, seq, d_model] |
| 多头自注意力 | 捕捉全局依赖 | [batch, seq, d_model] → [batch, seq, d_model] |
| Add & Norm | 稳定训练 | [batch, seq, d_model] → [batch, seq, d_model] |
| 位置前馈网络 | 特征提取重组 | [batch, seq, d_model] → [batch, seq, d_model] |
| Add & Norm | 稳定训练 | [batch, seq, d_model] → [batch, seq, d_model] |
7.2 关键设计思想 💡
- 统一维度设计 :所有子层输入输出都是
d_model=512,方便堆叠 - 并行处理:自注意力和 FFN 都可以完全并行计算
- 残差连接:解决深层网络梯度消失问题
- 层归一化:稳定数据分布,加速训练收敛
- 多头机制:从不同视角捕捉序列依赖关系
7.3 与其他组件的关系 🔗
编码器是 Transformer 模型的重要组成部分,它生成的编码输出会被传递给解码器(在 Encoder-Decoder 架构中),或者直接用于下游任务(如 BERT)。
完整 Transformer 流程:
输入序列 → 编码器 → 编码输出 → 解码器 → 输出序列
在后续章节中,我们将继续学习解码器结构、嵌入层与位置编码等组件,最终构建完整的 Transformer 模型。
参考资料:
- 一文看懂Transformer内部原理(含PyTorch实现) -- 博客园 ⭐值得阅读
- 从零开始读懂Transformer:架构解析与PyTorch实现 -- CSDN
- Transformer原理及Pytorch代码实现 -- 知乎
- 使用Pytorch 一步一步实现Transformer Encoder -- 小昇的博客 ⭐值得阅读
- 动手学深度学习 -- zh.d2l.ai ⭐值得阅读
最后更新时间:2026-05-18