08-编码器结构 🏗️

08-编码器结构 🏗️

本文档详细解析 Transformer 编码器(Encoder)的完整结构,涵盖编码器整体架构、编码器层内部组件、多头自注意力机制、位置前馈神经网络、残差连接与层归一化,以及完整的 PyTorch 代码实现。通过理论与实践相结合的方式,帮助读者深入理解编码器的工作原理和实现细节 🛠️

章节阅读路线图 🗺️

flowchart LR A["1. 编码器整体架构"]:::overview --> B["2. 编码器层内部结构"]:::layer B --> C["3. 多头自注意力机制"]:::attention C --> D["4. 位置前馈神经网络"]:::ffn D --> E["5. 残差连接与层归一化"]:::residual E --> F["6. 完整代码实现"]:::code F --> G["7. 总结"]:::summary classDef overview fill:#e3f2fd,stroke:#1565c0 classDef layer fill:#e8f5e9,stroke:#2e7d32 classDef attention fill:#fff3e0,stroke:#ef6c00 classDef ffn fill:#f3e5f5,stroke:#6a1b9a classDef residual fill:#fce4ec,stroke:#c62828 classDef code fill:#e0f2f1,stroke:#00695c classDef summary fill:#fff8e1,stroke:#f57f17

阅读顺序说明

  • 第1章 → 第2章:先了解编码器整体架构,再深入单个编码器层
  • 第2章 → 第3章:理解编码器层后,学习第一个核心组件------多头自注意力
  • 第3章 → 第4章:掌握注意力机制后,学习第二个核心组件------前馈神经网络
  • 第4章 → 第5章:了解两个子层后,学习如何连接它们------残差连接和层归一化
  • 第5章 → 第6章:把所有组件整合成完整可运行的代码

1. 编码器整体架构 🌐

本章介绍 Transformer 编码器的宏观结构和设计思想

Transformer 编码器是 Transformer 模型的核心组成部分之一,负责将输入序列转换为包含丰富语义信息的高维表示。

1.1 编码器结构概览 📊

Transformer 编码器由以下部分组成:

  1. 输入嵌入层(Input Embedding):将离散的 token 转换为连续向量
  2. 位置编码(Positional Encoding):为嵌入向量添加位置信息
  3. 编码器层堆栈(Encoder Layers Stack):由 N 个相同的编码器层组成(论文中 N=6)
css 复制代码
输入序列 → 嵌入层 → + 位置编码 → [编码器层 × N] → 编码输出

1.2 为什么需要编码器?🤔

编码器的任务是 理解输入序列,并为每个位置生成一个包含全局上下文信息的表示。与传统的 RNN 不同,Transformer 编码器可以并行处理整个序列,并通过自注意力机制捕捉任意位置之间的依赖关系。

编码器的优势

特性 RNN/CNN Transformer 编码器
并行计算 ❌ 顺序处理 ✅ 完全并行
长距离依赖 ❌ 随距离衰减 ✅ 直接建模
计算复杂度 O(n) 或 O(n×k) O(n²)
可解释性 ❌ 黑盒 ✅ 注意力权重可视化

💡 n 表示序列长度,k 表示卷积核大小


参考资料:


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
  ↓
拼接所有头的输出
  ↓
线性变换(融合信息)
  ↓
多头注意力输出

具体计算步骤

  1. 线性投影:将 Q、K、V 分别通过 h 个不同的线性变换,得到 h 组查询、键、值
  2. 缩放点积注意力:对每组 (Qᵢ, Kᵢ, Vᵢ) 计算注意力
  3. 拼接:将 h 个头的输出拼接起来
  4. 最终线性变换:通过一个线性层融合所有头的信息

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:最后的线性层将所有头的信息融合起来

参考资料:


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。这种"先扩展后压缩"的设计有以下好处:

  1. 增加模型容量:更大的隐藏层可以学习更复杂的特征变换
  2. 非线性表达能力:在高维空间中应用激活函数,增强非线性建模能力
  3. 信息重组:将多头注意力提取的信息进行重组和精炼

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:残差连接的结果

为什么需要残差连接?

  1. 缓解梯度消失:深层网络中,梯度在反向传播时容易消失。残差连接提供了"高速公路",让梯度可以直接流向浅层
  2. 保留原始信息:即使子层学到的是恒等映射(不改变输入),残差连接也能保证信息不丢失
  3. 加速训练收敛:实验表明,残差连接可以显著加快训练速度

直观理解

想象你在学习一门新知识:

  • 没有残差连接:每次学习都完全依赖上一步的理解,容易"走偏"
  • 有残差连接:在学习新知识的同时,保留原有的理解,逐步改进

5.2 层归一化 📏

什么是层归一化?

层归一化(Layer Normalization)对每个样本的所有特征进行归一化,使其均值为 0,方差为 1:

scss 复制代码
LayerNorm(x) = γ × (x - μ) / σ + β

其中:

  • μ:该样本所有特征的均值
  • σ:该样本所有特征的标准差
  • γ、β:可学习的缩放和平移参数

为什么需要层归一化?

  1. 稳定训练:归一化后的数据分布更稳定,避免梯度爆炸或消失
  2. 加速收敛:允许使用更大的学习率,减少训练时间
  3. 正则化效果:引入轻微噪声,有一定防止过拟合的作用

层归一化 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_2self.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)

参考资料:


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 关键设计思想 💡

  1. 统一维度设计 :所有子层输入输出都是 d_model=512,方便堆叠
  2. 并行处理:自注意力和 FFN 都可以完全并行计算
  3. 残差连接:解决深层网络梯度消失问题
  4. 层归一化:稳定数据分布,加速训练收敛
  5. 多头机制:从不同视角捕捉序列依赖关系

7.3 与其他组件的关系 🔗

编码器是 Transformer 模型的重要组成部分,它生成的编码输出会被传递给解码器(在 Encoder-Decoder 架构中),或者直接用于下游任务(如 BERT)。

完整 Transformer 流程

复制代码
输入序列 → 编码器 → 编码输出 → 解码器 → 输出序列

在后续章节中,我们将继续学习解码器结构、嵌入层与位置编码等组件,最终构建完整的 Transformer 模型。


参考资料:


最后更新时间:2026-05-18

相关推荐
掘金安东尼1 小时前
Buildsom |老板说要加码 AI 推广?我调研后发现:77% 的品牌,其实都在“盲投”
人工智能
Android出海1 小时前
5月合规风暴眼:Google Play权限大限与欧盟游戏监管新棋局
人工智能·游戏·google play·谷歌开发者·android开发者·google开发者·google play开发者
在繁华处1 小时前
轻棋局(一):项目总览与架构设计
人工智能·windows
TechubNews2 小时前
稳定币下一战:不是谁发币,而是谁掌握结算通道
人工智能·web3·区块链
火山引擎开发者社区2 小时前
钛投标基于火山引擎 ArkClaw 构建招投标垂直智能服务生态
人工智能
deephub2 小时前
Agent = Model + Harness:模型决定上限Harness 决定下限
人工智能·大语言模型·agent·harness
GalenZhang8882 小时前
Hermes Agent v0.14.0:AI Agent 基建时代正式到来
人工智能·hermes
俊哥V3 小时前
每日 AI 研究简报 · 2026-05-17
人工智能·ai
johnrui3 小时前
RAG系统“入口”和“出口”的进化
人工智能