前馈神经网络
Transformer 中的前馈神经网络
Section titled “Transformer 中的前馈神经网络”Transformer 模型在论文《Attention Is All You Need》中被提出,彻底改变了自然语言处理(NLP)和其他序列到序列任务的领域。尽管其架构主要依赖多头自注意力机制(multi-head self-attention)来捕捉上下文关系,但另一个同样对其成功至关重要的组件是前馈神经网络(Feed-Forward Neural Network, FFNN),也称为逐位置前馈网络(Position-wise Feed-Forward Network)。
这个 FFNN 子层,存在于 Transformer 的编码器和解码器堆栈中,在转换注意力机制学习到的表示方面起着至关重要的作用,使模型能够捕捉输入数据序列中更复杂的模式。本章将解释 FFNN 子层,其在 Transformer 架构中的作用,并提供一个概念性的 Python 实现。
Transformer 中的前馈神经网络(FFNN)子层
Section titled “Transformer 中的前馈神经网络(FFNN)子层”在 Transformer 架构中,每个编码器和解码器层包含两个主要的子层:一个多头自注意力机制和一个逐位置前馈网络。FFNN 子层通常紧随注意力子层(及其相关的 Add & Norm 步骤)之后。它的输入是前一层归一化(layer normalization)步骤的输出,其维度为 $d_{model}$(例如,$d_{model} = 512$)。
描述 Transformer 编码器单个块的图表会显示输入首先通过一个多头注意力子层,接着是 Add & Norm 步骤(残差连接 residual connection 和层归一化)。这个 Add & Norm 层的输出然后馈送到前馈神经网络(FFNN)子层。FFNN 本身之后也紧接着另一个 Add & Norm 步骤,最后才产生该编码器/解码器块的最终输出。这种结构突出了 FFNN 在信息处理中位于注意力机制之后的顺序位置。
FFNN 子层的关键特征:
- 全连接(Fully Connected):它是一个标准的、全连接的前馈网络。
- 逐位置(Position-wise):FFNN 对序列中的每个位置(即每个 token 的表示)独立地且以相同的方式应用。这意味着虽然网络独立地应用于每个位置,但在给定层中,所有位置使用同一组权重($W_1, b_1, W_2, b_2$)。这与在局部区域共享权重的卷积网络(convolutional network)或按顺序处理 token 的循环网络(recurrent network)不同。
- 结构:它包含两个线性变换,中间夹着一个激活函数。最常用的激活函数是 ReLU(Rectified Linear Unit,线性整流单元),尽管 GELU(Gaussian Error Linear Unit,高斯误差线性单元)在现代 Transformer 中也很流行。
- 维度:FFNN 子层的输入和输出维度均为 $d_{model}$(例如 512)。内层通常具有更大的维度,常记为 $d_{ff}$(例如 $d_{ff} = 2048$,即 $4 \times d_{model}$)。这种扩展和收缩使得模型能够学习更复杂的变换。
- 在编码器和解码器中相同:在编码器和解码器层中使用相似的 FFNN 结构,尽管它们的权重是独立学习的。
FFNN 子层的数学表示
Section titled “FFNN 子层的数学表示”设 $x$ 为序列中单个位置输入 FFNN 子层的内容(维度为 $d_{model}$)。FFNN 操作可以数学表示为:
1. 第一次线性变换
Section titled “1. 第一次线性变换”$$ ext{Layer}_1(x) = x W_1 + b_1 $$
这里,$W_1$ 是一个形状为 $(d_{model}, d_{ff})$ 的权重矩阵,$b_1$ 是一个形状为 $(d_{ff})$ 的偏置向量(bias vector)。这一层的输出维度为 $d_{ff}$。
2. 激活函数(例如,ReLU)
Section titled “2. 激活函数(例如,ReLU)”ReLU 激活函数引入了非线性,使网络能够学习更复杂的模式。
$$ ext{Activation}( ext{Layer}_1(x)) = max(0, x W_1 + b_1) $$
3. 第二次线性变换
Section titled “3. 第二次线性变换”$$ ext{FFNN}(x) = (max(0, x W_1 + b_1)) W_2 + b_2 $$
这里,$W_2$ 是一个形状为 $(d_{ff}, d_{model})$ 的权重矩阵,$b_2$ 是一个形状为 $(d_{model})$ 的偏置向量。对于位置 $x$,FFNN 子层的最终输出维度为 $d_{model}$。
FFNN 子层在 Transformer 中的重要性
Section titled “FFNN 子层在 Transformer 中的重要性”FFNN 子层在 Transformer 建模复杂数据的能力中起着至关重要的作用:
非线性变换与特征丰富化
Section titled “非线性变换与特征丰富化”自注意力机制主要捕捉序列中不同 token 之间的线性关系和依赖性。FFNN 引入了非线性,使模型能够学习 token 表示的更复杂变换。扩展到 $d_{ff}$ 维度并随后收缩回 $d_{model}$ 维度,可以看作是将表示投影到一个更高维的空间,在那里可以学习到更精细的特征,然后再投影回来。
尽管自注意力机制考虑输入序列中不同位置之间的关系,但 FFNN 子层独立地处理每个位置的表示。这使得模型能够根据每个 token 的个体特征(经过注意力层修改后)对其应用一致的变换,进一步细化这些表示。它起着逐 token 特征提取器/变换器的作用。
增加模型容量
Section titled “增加模型容量”FFNN 层显著增加了 Transformer 模型的总参数数量。这种增加的容量使模型能够从海量数据中学习,并捕捉复杂 NLP 任务实现高性能所需的细微模式。
前馈网络的 Python 概念实现
Section titled “前馈网络的 Python 概念实现”下面是一个使用 NumPy 实现的分步概念性 Python 代码示例,用于演示逐位置前馈网络子层在 Transformer 架构中如何运行。注意,实际的 Transformer 实现会使用 PyTorch 或 TensorFlow 等深度学习框架以提高效率和进行自动微分。
步骤 1:定义 FeedForwardNN 类
Section titled “步骤 1:定义 FeedForwardNN 类”我们将为 FFNN 定义一个类。在 Transformer 中,$d_{model}$ 是模型的嵌入维度(embedding dimension)(例如 512),$d_{ff}$ 是内层维度(例如 2048)。
import numpy as np
class FeedForwardNN: def __init__(self, d_model, d_ff): # 初始化两个线性变换的权重和偏置 # 权重通常用小的随机值初始化(例如,Xavier/Glorot 初始化) # 为简化起见,使用 np.random.randn 并乘以 0.01 self.W1 = np.random.randn(d_model, d_ff) * 0.01 self.b1 = np.zeros((1, d_ff)) # 第一层的偏置
self.W2 = np.random.randn(d_ff, d_model) * 0.01 self.b2 = np.zeros((1, d_model)) # 第二层的偏置
def relu(self, x): return np.maximum(0, x)
def forward(self, x): # x 的形状:(批量大小, 序列长度, d_model) # FFNN 是逐位置应用的,因此我们可以使用批处理矩阵乘法。
# 第一次线性变换 # (batch_size, seq_len, d_model) @ (d_model, d_ff) -> (batch_size, seq_len, d_ff) layer1_output = np.dot(x, self.W1) + self.b1
# ReLU 激活 activation_output = self.relu(layer1_output)
# 第二次线性变换 # (batch_size, seq_len, d_ff) @ (d_ff, d_model) -> (batch_size, seq_len, d_model) output = np.dot(activation_output, self.W2) + self.b2 return output步骤 2:创建示例输入数据
Section titled “步骤 2:创建示例输入数据”在 Transformer 中,输入通常是形状为 (batch_size, seq_len, d_model) 的张量(tensor)。让我们创建一些示例输入数据。
# 示例输入维度batch_size = 2 # 批次中的序列数量seq_len = 10 # 每个序列的长度(token 数量)d_model = 512 # 模型的维度(FFNN 的输入/输出维度)d_ff = 2048 # FFNN 内层的维度
# 形状为 (batch_size, seq_len, d_model) 的随机输入张量# 这将是前一层(注意力 + add_norm)的输出。x_input = np.random.rand(batch_size, seq_len, d_model)步骤 3:实例化并使用 FFNN
Section titled “步骤 3:实例化并使用 FFNN”最后,我们实例化 FeedForwardNN 类,并使用我们的示例输入张量执行前向传播(forward pass)。
# 创建 FFNN 实例ffnn = FeedForwardNN(d_model, d_ff)
# 执行前向传播output = ffnn.forward(x_input)
# 打印输出形状(应与输入形状相同:(批量大小, 序列长度, d_model))print("Input shape:", x_input.shape)print("Output shape:", output.shape)完整实现示例
Section titled “完整实现示例”结合以上步骤,提供完整的概念性实现:
import numpy as np
class FeedForwardNN: def __init__(self, d_model, d_ff): self.W1 = np.random.randn(d_model, d_ff) * 0.01 self.b1 = np.zeros((1, d_ff)) self.W2 = np.random.randn(d_ff, d_model) * 0.01 self.b2 = np.zeros((1, d_model))
def relu(self, x): return np.maximum(0, x)
def forward(self, x): layer1_output = np.dot(x, self.W1) + self.b1 activation_output = self.relu(layer1_output) output = np.dot(activation_output, self.W2) + self.b2 return output
# 示例参数batch_size = 2seq_len = 10d_model = 512d_ff = 2048 # 通常是 4 * d_model
x_input = np.random.rand(batch_size, seq_len, d_model)
# 实例化并运行 FFNNffnn = FeedForwardNN(d_model, d_ff)output = ffnn.forward(x_input)
print("Input shape:", x_input.shape)print("Output shape:", output.shape)
# 进一步学习,可以探索:# - 实际实现中使用的 tf.keras.layers.Dense 或 torch.nn.Linear。# - 现代 Transformer 中常用的 GELU 激活函数。# - FFNN 周围的层归一化(Layer Normalization)和残差连接(Residual Connections)。运行上面的脚本将打印输入和输出张量的形状,验证 FFNN 在处理数据的同时,在每个位置保持了 $d_{model}$ 的维度。
Input shape: (2, 10, 512)Output shape: (2, 10, 512)逐位置前馈网络(FFNN)是 Transformer 架构中每个层内的基本组成部分。它通过独立地对每个 token 的表示应用非线性变换,增强了模型捕捉输入数据序列中复杂模式和关系的能力。
通过用这些局部的、逐位置的变换来补充自注意力机制的全局上下文聚合能力,FFNN 显著提升了 Transformer 在各种 NLP 和序列建模任务中的能力和有效性。理解其结构和功能是充分理解 Transformer 模型精妙之处的关键。