Skip to content

前馈神经网络

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 结构,尽管它们的权重是独立学习的。

设 $x$ 为序列中单个位置输入 FFNN 子层的内容(维度为 $d_{model}$)。FFNN 操作可以数学表示为:

$$ ext{Layer}_1(x) = x W_1 + b_1 $$

这里,$W_1$ 是一个形状为 $(d_{model}, d_{ff})$ 的权重矩阵,$b_1$ 是一个形状为 $(d_{ff})$ 的偏置向量(bias vector)。这一层的输出维度为 $d_{ff}$。

ReLU 激活函数引入了非线性,使网络能够学习更复杂的模式。

$$ ext{Activation}( ext{Layer}_1(x)) = max(0, x W_1 + b_1) $$

$$ 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 建模复杂数据的能力中起着至关重要的作用:

自注意力机制主要捕捉序列中不同 token 之间的线性关系和依赖性。FFNN 引入了非线性,使模型能够学习 token 表示的更复杂变换。扩展到 $d_{ff}$ 维度并随后收缩回 $d_{model}$ 维度,可以看作是将表示投影到一个更高维的空间,在那里可以学习到更精细的特征,然后再投影回来。

尽管自注意力机制考虑输入序列中不同位置之间的关系,但 FFNN 子层独立地处理每个位置的表示。这使得模型能够根据每个 token 的个体特征(经过注意力层修改后)对其应用一致的变换,进一步细化这些表示。它起着逐 token 特征提取器/变换器的作用。

FFNN 层显著增加了 Transformer 模型的总参数数量。这种增加的容量使模型能够从海量数据中学习,并捕捉复杂 NLP 任务实现高性能所需的细微模式。

下面是一个使用 NumPy 实现的分步概念性 Python 代码示例,用于演示逐位置前馈网络子层在 Transformer 架构中如何运行。注意,实际的 Transformer 实现会使用 PyTorch 或 TensorFlow 等深度学习框架以提高效率和进行自动微分。

我们将为 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

在 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)

最后,我们实例化 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)

结合以上步骤,提供完整的概念性实现:

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 = 2
seq_len = 10
d_model = 512
d_ff = 2048 # 通常是 4 * d_model
x_input = np.random.rand(batch_size, seq_len, d_model)
# 实例化并运行 FFNN
ffnn = 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 模型精妙之处的关键。