Transformer 编码器层包括用于捕获上下文关系的多头自注意力机制、用于转换表示的位置维度前馈网络,以及用于稳定性和梯度流动的加和归一化步骤。这些部分将使用 Python 和 PyTorch 或 TensorFlow 等深度学习框架组装成一个能工作的编码器层。本次实践练习假设您已经有了多头注意力机制的实现(可能是在前一章的实践部分完成的),并且了解您所选框架中的基本类定义和张量操作。我们将侧重于构建 EncoderLayer 模块本身。核心组件回顾在编写代码之前,让我们快速回顾一下单个编码器层内的子层:多头自注意力机制: 接收输入序列嵌入,并计算所有位置对之间的注意力分数,生成一个输出,该输出中每个位置的表示都根据相关性受到其他位置的影响。加和归一化 (1): 将原始输入添加到自注意力层的输出中(残差连接),然后应用层归一化。这有助于缓解梯度消失问题并稳定激活值。位置维度前馈网络 (FFN): 一个简单的网络(通常是两个线性层,中间夹着一个 ReLU 或 GELU 激活函数),独立应用于序列中的每个位置。它进一步处理这些表示。加和归一化 (2): 将输入 FFN 的数据添加到 FFN 的输出中(另一个残差连接),并再次应用层归一化。在多头注意力输出和 FFN 输出之后,通常也会应用 Dropout 以防止训练期间的过拟合。定义位置维度前馈网络这是一个直接的组成部分。它由两次线性变换组成,中间夹着一个非线性激活函数。通常,第一个线性层会扩展维度,而第二个线性层会将其压缩回原始的模型维度 ($d_{model}$)。常见的扩展因子是 4。我们用类似 PyTorch 的语法来表示它:import torch import torch.nn as nn class PositionWiseFeedForward(nn.Module): def __init__(self, d_model, d_ff, dropout=0.1): """ 初始化位置维度前馈网络。 参数: d_model (int):输入和输出的维度。 d_ff (int):内部层的维度。 dropout (float):Dropout 概率。 """ super().__init__() self.linear_1 = nn.Linear(d_model, d_ff) self.activation = nn.ReLU() # 或 nn.GELU() self.dropout = nn.Dropout(dropout) self.linear_2 = nn.Linear(d_ff, d_model) def forward(self, x): """ FFN 的前向传播。 参数: x (torch.Tensor):形状为 (batch_size, seq_len, d_model) 的输入张量。 返回: torch.Tensor:形状为 (batch_size, seq_len, d_model) 的输出张量。 """ x = self.linear_1(x) x = self.activation(x) x = self.dropout(x) # Dropout 通常在激活后应用 x = self.linear_2(x) return x 这个 PositionWiseFeedForward 模块接收一个形状为 (batch_size, seq_len, d_model) 的张量,并返回一个相同形状的张量,它在每个序列位置独立应用了变换。组装编码器层现在,我们将多头自注意力机制(我们假设它作为一个名为 MultiHeadAttention 的模块是可用的)、上面定义的位置维度前馈网络、层归一化、残差连接和 Dropout 组合成一个 EncoderLayer。class EncoderLayer(nn.Module): def __init__(self, d_model, num_heads, d_ff, dropout=0.1): """ 初始化一个 Transformer 编码器层。 参数: d_model (int):输入/输出特征(嵌入)的维度。 num_heads (int):注意力头的数量。 d_ff (int):前馈网络的内部维度。 dropout (float):Dropout 概率。 """ super().__init__() self.self_attn = MultiHeadAttention(d_model, num_heads) # 假定已实现 self.feed_forward = PositionWiseFeedForward(d_model, d_ff, dropout) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) self.dropout1 = nn.Dropout(dropout) self.dropout2 = nn.Dropout(dropout) def forward(self, x, mask): """ 编码器层的前向传播。 参数: x (torch.Tensor):形状为 (batch_size, seq_len, d_model) 的输入张量。 mask (torch.Tensor):注意力掩码(可选,用于填充)。 形状通常为 (batch_size, 1, 1, seq_len) 或类似 取决于 MultiHeadAttention 的实现。 返回: torch.Tensor:形状为 (batch_size, seq_len, d_model) 的输出张量。 """ # 1. 多头自注意力机制 + 加和归一化 attn_output = self.self_attn(query=x, key=x, value=x, mask=mask) # 残差连接 1:将输入 'x' 添加到注意力输出 # 在添加之前对注意力输出应用 Dropout x = self.norm1(x + self.dropout1(attn_output)) # 2. 位置维度前馈网络 + 加和归一化 ff_output = self.feed_forward(x) # 残差连接 2:将输入到 FFN 的数据 ('x') 添加到 FFN 输出 # 在添加之前对 FFN 输出应用 Dropout x = self.norm2(x + self.dropout2(ff_output)) return x 在这个 EncoderLayer 中,输入 x 首先经过多头自注意力机制。注意力机制的输出接着经过 Dropout (dropout1),然后加回到原始输入 x(第一个残差连接),其和进行归一化 (norm1)。这个归一化后的输出随后作为位置维度前馈网络的输入。FFN 的输出经过 Dropout (dropout2),然后添加到进入 FFN 的输入(第二个残差连接),这个和进行归一化 (norm2),从而生成编码器层的最终输出。编码器层的数据流可视化下面的图表描绘了我们刚刚定义的 EncoderLayer 内的数据流。digraph EncoderLayer { rankdir=TB; node [shape=box, style="filled", fillcolor="#e9ecef", fontname="sans-serif"]; edge [fontname="sans-serif"]; subgraph cluster_0 { style=filled; fillcolor="#f8f9fa"; label = "编码器层"; rankdir=TB; Input [label="输入 (x)", shape=ellipse, fillcolor="#a5d8ff"]; MHA [label="多头\n自注意力", fillcolor="#bac8ff"]; Dropout1 [label="Dropout", fillcolor="#ffc9c9"]; Add1 [label="+", shape=circle, fillcolor="#b2f2bb", width=0.3, height=0.3, fixedsize=true]; Norm1 [label="层归一化", fillcolor="#ffec99"]; FFN [label="位置维度\n前馈网络", fillcolor="#bac8ff"]; Dropout2 [label="Dropout", fillcolor="#ffc9c9"]; Add2 [label="+", shape=circle, fillcolor="#b2f2bb", width=0.3, height=0.3, fixedsize=true]; Norm2 [label="层归一化", fillcolor="#ffec99"]; Output [label="输出", shape=ellipse, fillcolor="#a5d8ff"]; # Connections Input -> MHA [label=" Q, K, V"]; Input -> Add1 [headport="w", minlen=2]; MHA -> Dropout1; Dropout1 -> Add1; Add1 -> Norm1; Norm1 -> FFN; Norm1 -> Add2 [headport="w", minlen=2]; FFN -> Dropout2; Dropout2 -> Add2; Add2 -> Norm2; Norm2 -> Output; } }单个 Transformer 编码器层内部的数据流,描绘了多头注意力机制和前馈子层,每个子层都接着 Dropout、残差连接(相加)和层归一化。这种结构被多次重复(在原始 Transformer 论文中通常重复 6 次或 12 次),形成了完整的编码器堆栈。每个层都将前一层的输出作为其输入,使得模型能够构建输入序列越来越复杂的表示。您现在有了一个 Transformer 核心组成部分的实践实现。在下一章中,我们将介绍如何有效地训练这些模型。