游乐游手机版
首页/AI热点日报/热点详情

PyTorch深度学习实战:层归一化与前馈网络

类型:热点整理2026-06-07
Transformer中的Add&Norm层由残差连接和层归一化组成,残差连接缓解梯度消失,层归一化针对每个样本的特征维度进行标准化以稳定训练。FeedForward层是两层全连接网络,先扩展维度再压缩回原维度,用于提取深层特征。

Add & Norm 层

在 Transformer 的整体架构中,**Add & Norm** 模块虽然看似不起眼,但实际上起到了承上启下的关键作用。它由“Add”与“Norm”两个部分组成,二者协同运作,确保模型能够稳定、高效地完成训练任务。其计算公式如下所示: [图片1:Add & Norm 计算公式] [图片2:Add & Norm 结构示意图] 需要特别说明的是,公式中的 **X** 代表 Multi‑Head Attention 或 Feed Forward 的输入,而 MultiHeadAttention(**X**) 和 FeedForward(**X**) 则分别表示对应子层的输出。值得注意的是,这些输出与输入 **X** 的维度是完全一致的,正因为维度相同,它们才能直接进行加法操作。

Add

**Add** 操作的本质,就是对 **X** 与 MultiHeadAttention(**X**)(或 FeedForward(**X**))执行逐元素相加。这是深度学习中常见的残差连接(Residual Connection)思想的具体体现。 为什么要引入这一设计?不妨想象在攀登一段陡峭的楼梯:如果每上一层都完全依靠自身力量,极易出现体力不支(梯度消失)或踩空(梯度爆炸)的情况。残差连接相当于在每个台阶旁增设了一个扶手,让网络能够“借用”上一层的部分输出。如此一来,网络只需关注学习当前层与上一层之间的“差异”——即增量部分,而无需从头学习全新的映射。这种设计在 ResNet 中被验证极为有效,能够助力训练更深层次的网络。 [图片3:残差连接示意图] [图片4:残差连接原理动画]

Norm 层

**Norm** 指的是层归一化(Layer Normalization)。该方法最初为 RNN 这类序列模型而设计,其核心功能是将每一层神经元的输入调整至统一的均值和方差分布,从而加速模型的收敛速度。接下来,我们从三个方面深入解析:计算方式、使用方法,以及 Transformer 为何最终选择了层归一化。

层归一化的计算方法

层归一化的目标十分纯粹:稳定神经网络的训练过程,避免梯度爆炸或梯度消失等“翻车”问题。其操作逻辑并不复杂——针对神经网络某一层的所有神经元输出,统一执行一次归一化处理。 举一个具体的例子。**假设某一层有4个神经元**,输入一个样本后,这4个神经元分别输出数值 **x1、x2、x3、x4**。层归一化要做的,正是针对这4个数值计算均值和方差,然后将它们整体缩放到一个合理、统一的数值范围内。 [图片5:层归一化计算过程示意图] [图片6:层归一化公式图解] 我们将公式中的每个符号逐一拆解,理解起来并不困难:**xi** 表示神经网络某一层第 i 个神经元的输出,**μ(平均值)** 是该层所有神经元输出的平均值,而 **x̂(带帽子的 x)** 则是层归一化处理后的最终结果。经过这一步,**层归一化后的 x1 至 x4** 的尺度趋于一致,这有助于神经网络更快地收敛,训练过程也变得更加稳定。 [图片7:层归一化后的数据分布]

如何使用层归一化?

在代码实现中,层归一化的调用非常便捷。只需使用 PyTorch 提供的 `nn.LayerNorm`,并传入当前层的输出维度即可。 观察下面的代码:我们定义了一个形状为 2×4 的张量 `x`,它可以理解为包含2个样本,每个样本有4个特征。对该张量进行层归一化时,会分别对每一行的4个特征独立进行归一化计算。 ```python import torch from torch import nn # 定义张量x # x是一个2×4大小的张量,代表了2个样本,每个样本有4个特征 # 当使用层归一化对x进行计算时,会对x的每一行的4个特征进行归一化 x = torch.tensor([[1.0, 2.0, 3.0, 4.0], [1.0, 1.0, 1.0, 1.0]]) # 定义层归一化实例 # 传入的参数4,表示层归一化的输入维度是4 # 也就是对应某一层的4个神经元的输出 layer_norm = nn.LayerNorm(4) # 计算层归一化的结果 output = layer_norm(x) print(output) ``` [图片8:代码运行结果示意图]

为啥要使用归一化?

在 Transformer 模型中,选择层归一化而非其他归一化方法,是基于其独特的“序列数据组织形式”而设计。 Transformer 中序列数据的尺寸通常表示为 B×Q×D,各维度的含义如下: - **B(Batch size)**:批量大小,即一次输入模型的样本数量。 - **Q(Sequence Length)**:序列长度,即每个样本(如句子)包含的元素(如单词)数量。 - **D(Features Dim)**:特征维度,即每个元素对应的向量维度(如词向量的维度)。 在 Transformer 中,层归一化会**针对每个样本的特征维度(D 维度)进行归一化**,也就是对 B×Q×D 中每个 D 维度的向量独立计算均值和方差。这样做能够完美适配序列数据的结构,稳定模型训练。 从刚才的代码结果可以看到,进行层归一化时,会对 `x` 中每行每个单词对应的4个维度分别进行计算。在两个样本中一共有6个单词,因此会进行6次独立的层归一化计算。**单词与单词之间完全互不影响**。 **总结一下:** 无论批量大小 B 是多少,也无论同一批次中不同序列的长度 Q 是否相同,都不会影响层归一化的计算。它始终只对最后一个特征维度 D 进行操作。 我们再通过一个更贴近实际场景的例子加以验证。这次输入的形状是 (B, Q, D) -> (2, 3, 4),即2个样本,每个样本有3个单词,每个单词用4个维度表示。层归一化只会作用在最后的特征维度 D 上。 ```python import torch import torch.nn as nn # 当使用层归一化计算序列数据时,层归一化只会作用在最后的特征维度D上 # 其中批量大小B与序列长度Q,这两个维度都不会影响层归一化的计算 # 定义输入数据 x,形状为 (B, Q, D) -> (2, 3, 4) # 2个样本,每个样本3个单词,每个单词用4个维度表示 x = torch.tensor([ # 第1个样本: [ [1.0, 2.0, 3.0, 4.0], # 第1个单词的4个维度 [1.0, 1.0, 1.0, 1.0], # 第2个单词的4个维度 [1.0, 1.0, 1.0, 1.0] # 第3个单词的4个维度 ], # 第2个样本: [ [1.0, 1.0, 1.0, 1.0], # 第1个单词的4个维度 [1.0, 1.0, 1.0, 1.0], # 第2个单词的4个维度 [1.0, 1.0, 1.0, 1.0] # 第3个单词的4个维度 ] ]) # 初始化层归一化层 layer_norm = nn.LayerNorm(4) # x输入至层归一化layer_norm中 output = layer_norm(x) # 输出结果 print(output) ``` [图片9:代码运行结果示意图] [图片10:层归一化对序列数据处理的示意图]

FeedForward

讨论完核心的归一化模块,再来审视另一个关键组件——FeedForward 层。这一结构相对直观,本质上是**一个两层的全连接网络**。其经典设计为:第一层使用 **Relu** 作为激活函数,第二层则**不使用激活函数**。计算公式如下: [图片11:FeedForward 计算公式] 公式中,**X** 是输入,Feed Forward 最终输出的矩阵维度与 **X** 保持一致。其中的 `max` 操作即为 ReLU。那么,**FeedForward 的作用**就很明确了:通过两次线性变换,先将数据映射到一个更高维度的空间(通常是模型维度的4倍),再映射回原始的低维空间。这一过程的核心目的,在于**提取更深层次、更抽象的特征**。 它的输入从何而来?正是 Multi‑Head Attention 的输出,经过残差连接和 Layer Normalization 处理之后的数据。FeedForward 层接收这些数据,执行两次线性变换,**旨在更加深入地挖掘和提炼特征**。 更具体地说,Feed‑Forward Layer 是一个标准的全连接神经网络,**包含两个线性变换和一个激活函数**。其输入是经过残差连接和正则化后的 Multi‑Head Attention 输出。**Feed‑Forward Layer 的主要任务**是进一步深化特征提取,让模型能够更好地理解和处理输入数据中的复杂模式。 [图片12:FeedForward 结构示意图] **代码实现:** 下面的代码实现了一个标准且完整的 FeedForward 层,其中包含了维度扩展、激活函数选择、Dropout 以及参数初始化等常用技巧,你可以直接将其集成到自己的项目中。 ```python import torch import torch.nn as nn import torch.nn.functional as F class FeedForward(nn.Module): def __init__(self, d_model: int, d_ff: int = None, activation: str = "gelu", dropout: float = 0.1): """ Args: d_model: 输入/输出维度(即 Transformer 的隐藏层维度) d_ff: 中间层维度(默认扩展为 4*d_model) activation: 激活函数,支持 "gelu" 或 "relu" dropout: Dropout 概率 """ super().__init__() d_ff = d_ff or 4 * d_model # 默认扩展比例为4倍 # 定义两个线性层 self.linear1 = nn.Linear(d_model, d_ff) # 扩展维度 self.linear2 = nn.Linear(d_ff, d_model) # 压缩回原维度 # 激活函数选择 self.activation = F.gelu if activation == "gelu" else F.relu # Dropout 层(可选) self.dropout = nn.Dropout(dropout) if dropout > 0 else nn.Identity() # 参数初始化(关键!) self._init_weights() def _init_weights(self): # 使用 He/Kaiming 初始化(适合 ReLU) nn.init.kaiming_normal_(self.linear1.weight, nonlinearity='relu') nn.init.zeros_(self.linear1.bias) # 缩小输出层的初始化范围 nn.init.xa vier_normal_(self.linear2.weight, gain=0.02) nn.init.zeros_(self.linear2.bias) def forward(self, x: torch.Tensor) -> torch.Tensor: """ 输入形状: (batch_size, seq_len, d_model) 输出形状: (batch_size, seq_len, d_model) """ x = self.linear1(x) # (batch, seq, d_ff) x = self.activation(x) # 非线性激活 x = self.dropout(x) # 可选 Dropout x = self.linear2(x) # (batch, seq, d_model) return x ``` [图片13:FeedForward 代码运行结果示意图]
来源:https://developer.aliyun.com/article/1739924

相关热点

继续查看同栏目近期热点。

延伸阅读

补充最近整理过的热点入口。