§1.3.23
Sigmoid/Tanh/ReLU/LeakyReLU/GELU/SiLU/SwiGLU/Softmax 的公式、导数、适用场景?
- —用 torch 手写 SwiGLU 模块
核心概念
激活函数(Activation Function)是神经网络中的核心组件,它被应用于神经元的输出之上,旨在为网络引入非线性。如果缺少激活函数,一个多层的神经网络本质上等同于一个单层的线性模型,无法学习和表示复杂的数据模式。一个理想的激活函数应具备非线性、可微(以便于梯度下降)、计算简单、对梯度消失/爆炸不敏感等特性。
原理与推导
我们将激活函数分为几类进行讨论:饱和激活函数、非饱和激活函数、平滑非线性函数、门控激活单元以及用于输出层的函数。
1. Sigmoid
- 公式:
- 导数: 推导过程: 令 ,使用链式法则: 为了得到更简洁的形式,我们进行变换: 最终形式:
- 直观解释: Sigmoid 函数将任意实数输入压缩到 区间内,常被解释为概率或某种状态的饱和度。其导数在输入远离0时迅速趋近于0(饱和区),导致梯度消失问题,使得深层网络训练困难。
2. Tanh (双曲正切)
- 公式:
- 导数: 推导过程: 令 , 。使用商法则 : 结合 的定义,我们得到:
- 直观解释: Tanh 将输入压缩到 区间。与 Sigmoid 相比,它的输出是零中心化的(均值为0),这使得下一层神经元的输入更接近零均值,有助于加速收敛。但它同样存在饱和区和梯度消失问题。
3. ReLU (Rectified Linear Unit)
- 公式:
- 导数: 在实践中,当 时,其梯度可以取0或1(次梯度),通常实现为0。
- 直观解释: ReLU 是一种非饱和激活函数。对于正输入,它保持原样;对于负输入,它直接置零。
- 优点: 计算极其高效(仅需一次比较);在正区间内梯度恒为1,有效缓解了梯度消失问题。
- 缺点: 输出非零中心化;可能导致“神经元死亡”(Dying ReLU),即如果一个神经元的输入恒为负,其梯度将永远为0,参数无法更新。
- 复杂度: 时间复杂度为 。
4. LeakyReLU (Leaky Rectified Linear Unit)
- 公式: 其中 是一个小的正常数,如 0.01。
- 导数:
- 直观解释: LeakyReLU 是对 ReLU 的改进,旨在解决“神经元死亡”问题。它为负输入分配了一个小的非零斜率 ,确保即使在负输入区域,神经元也能获得梯度进行更新。
5. GELU (Gaussian Error Linear Unit)
- 公式: 其中 是标准正态分布的累积分布函数 (CDF)。一个精确的表达式是: 近似计算公式(更快):
- 导数: 其中 是标准正态分布的概率密度函数 (PDF)。
- 直观解释: GELU 可以看作是 ReLU 的平滑近似。它的动机来自于将随机正则化(如 Dropout)与非线性激活相结合。一个输入的激活值 会乘以一个伯努利分布的掩码 。GELU 便是对这个随机过程的期望。它在 附近是弯曲的,而不是像 ReLU 那样尖锐,这种平滑性被认为对模型学习有益。
6. SiLU (Sigmoid-weighted Linear Unit), a.k.a. Swish
- 公式:
- 导数: 使用乘法法则 :
- 直观解释: SiLU 和 GELU 非常相似,都是平滑、非单调的激活函数。它在负值区域会有一个小的“凹陷”,然后趋近于0。这种“自门控”特性(输入 被其自身的 Sigmoid 值加权)被认为允许网络更好地控制信息流。它的计算比 GELU 简单,性能却常常不相上下甚至更好。
7. SwiGLU (Swish-Gated Linear Unit)
- 原理: SwiGLU 并非一个简单的标量函数,而是一个网络模块结构,属于门控线性单元(Gated Linear Unit, GLU)家族的变体。
- 公式: 对于输入 ,它首先通过两个独立的线性变换(权重矩阵为 和 ),然后将其中一个变换的结果通过 SiLU(或 Swish)激活,再与另一个变换的结果逐元素相乘。
其中 表示逐元素乘法。在
LLaMA等模型中,通常将 SiLU 替换为 Swish,公式变为: (注意:SiLU 和 Swish 是同一个函数,这里为了清晰区分门控函数和结构名称) - 直观解释: SwiGLU 引入了一个数据驱动的门控机制。 可以看作是“内容”流,而 是一个“门”,它根据输入动态地决定“内容”流中哪些部分可以通过,以及通过的强度。这种动态控制能力使得模型表达能力更强。
8. Softmax
- 公式: 对于一个向量 ,其 Softmax 输出 也是一个 K 维向量:
- 导数 (雅可比矩阵): Softmax 的输出 依赖于所有输入 ,因此其导数是一个雅可比矩阵。 可以统一写作: 其中 是克罗内克函数( 时为1,否则为0)。
- 直观解释: Softmax 将一个实数向量(logits)转换成一个概率分布。输出向量的每个元素都在 之间,且所有元素之和为1。它不是隐藏层的激活函数,而是专门用于多分类问题的输出层,以产生各类别的预测概率。
代码实现
python
1import torch2import torch.nn as nn3import torch.nn.functional as F4import numpy as np56class SwiGLU(nn.Module):7 """8 SwiGLU 门控线性单元模块。9 这个模块在 Transformer 的 FFN (Feed-Forward Network) 层中特别流行,例如 LLaMA 模型。10 它将输入维度扩大,然后通过门控机制进行缩减。11 """12 def __init__(self, in_features: int, hidden_features: int = None, out_features: int = None):13 """14 初始化 SwiGLU 模块。15 :param in_features: 输入特征维度16 :param hidden_features: 隐藏层特征维度。通常是 in_features 的倍数,如 4 倍。17 SwiGLU 内部会创建两个线性层,其输出维度都是 hidden_features。18 :param out_features: 输出特征维度。如果为 None,则默认为 in_features。19 """20 super().__init__()21 # 如果未指定 hidden_features,通常设置为输入维度的 2/3 * 4 = 8/3 倍,以保持与标准 FFN 近似的参数量22 # 这里为了简单,我们让用户明确指定,或默认为 4*in_features23 hidden_features = hidden_features or 4 * in_features24 # SwiGLU 的一个常见设计是 FFN 中间层维度是 2/3 * 4 * d_model,这里简化为 hidden_features25 # 并且这两个线性层输出维度相同26 self.hidden_features = hidden_features2728 # 定义第一个线性层,用于门控(gate)29 # 为什么需要这个?这是门控机制的一部分,它学习如何根据输入生成一个控制信号。30 self.gate_proj = nn.Linear(in_features, self.hidden_features, bias=False)3132 # 定义第二个线性层,用于内容(up)33 # 为什么需要这个?这是要被门控的主要信息流。34 self.up_proj = nn.Linear(in_features, self.hidden_features, bias=False)3536 # 定义输出线性层,将门控后的结果映射回期望的输出维度37 # 为什么需要这个?门控操作后维度是 hidden_features,需要一个线性层将其投影回原始或目标维度。38 self.down_proj = nn.Linear(self.hidden_features, out_features or in_features, bias=False)3940 def forward(self, x: torch.Tensor) -> torch.Tensor:41 """42 前向传播43 :param x: 输入张量,形状为 (batch_size, seq_len, in_features)44 :return: 输出张量,形状为 (batch_size, seq_len, out_features)45 """46 # 计算门控值47 # 为什么用 F.silu?这是 SwiGLU 的核心,使用 SiLU (Swish) 作为门控激活函数。48 # 它比 Sigmoid 效果更好,是现代 Transformer 的标配。49 gate = self.gate_proj(x)50 gate = F.silu(gate)5152 # 计算内容值53 up = self.up_proj(x)5455 # 逐元素相乘,实现门控56 # 为什么相乘?这是门控机制的本质。gate 中的每个元素值(范围大致在-0.28到+inf)57 # 决定了 up 中对应元素的信息能通过多少。58 fused_gate_up = gate * up5960 # 通过输出投影层61 output = self.down_proj(fused_gate_up)6263 return output6465# --- 代码练习:测试 SwiGLU 模块 ---66if __name__ == '__main__':67 # 定义模型参数68 batch_size = 469 seq_len = 1070 in_dim = 3271 hidden_dim = 64 # FFN 中间层维度72 out_dim = 327374 # 创建 SwiGLU 实例75 swiglu_layer = SwiGLU(in_features=in_dim, hidden_features=hidden_dim, out_features=out_dim)76 print("SwiGLU 模块结构:\n", swiglu_layer)7778 # 创建一个随机输入张量79 input_tensor = torch.randn(batch_size, seq_len, in_dim)80 print(f"\n输入张量形状: {input_tensor.shape}")8182 # 通过 SwiGLU 层83 output_tensor = swiglu_layer(input_tensor)84 print(f"输出张量形状: {output_tensor.shape}")8586 # 验证输出形状是否正确87 assert output_tensor.shape == (batch_size, seq_len, out_dim)88 print("\n代码练习成功:SwiGLU 模块手写并验证通过!")
工程实践
-
使用场景:
- Sigmoid: 主要用于二分类问题的输出层,或在旧的 RNN 结构(如 LSTM)中作为门控函数。在隐藏层中基本已被淘汰。
- Tanh: 在 RNN 的隐藏层中仍有使用,因其零中心化的特性优于 Sigmoid。但在前馈网络和
Transformer中较少见。 - ReLU: CNN 和许多基本 MLP 的默认首选。计算速度快,效果好,是进行实验的良好基线。
- LeakyReLU: 当怀疑模型受到“神经元死亡”问题困扰时(例如,训练过程中某些神经元输出恒为0),可以尝试使用 LeakyReLU 替代 ReLU。
- GELU / SiLU(Swish):
Transformer模型(如BERT, GPT,ViT)隐藏层 FFN 的标准激活函数。两者性能相近,SiLU 计算上略微简单。 - SwiGLU: 最新的 SOTA(State-of-the-Art)LLMs(如
LLaMA, PaLM, Mixtral)中 FFN 层的标配。它通过增加参数量和计算量换取了显著的模型性能提升。 - Softmax: 仅用于多分类问题的输出层,将模型的 logits 输出转换为概率分布。
-
超参数选择:
- LeakyReLU:
alpha(或negative_slope) 是一个需要调整的超参数,通常取值在[0.01, 0.2]之间。PReLU (Parametric ReLU) 甚至将alpha作为一个可学习的参数。 - 其他激活函数通常没有需要调整的超参数。
- LeakyReLU:
-
性能 / 显存 / 吞吐 的权衡:
- ReLU 是最快的,几乎没有计算开销。
- LeakyReLU 同样非常快。
- GELU 和 SiLU 计算相对复杂(涉及
exp,tanh或erf),但现代深度学习框架有优化实现,实际影响不大。 - SwiGLU 是计算和参数量最大的。一个标准的 FFN 块是
Linear -> Activation -> Linear。而 SwiGLU FFN 块是(Linear_gate, Linear_up) -> Multiply -> Linear_down。通常为了保持参数量近似,Linear_gate和Linear_up的输出维度会是标准 FFN 中间层维度的 2/3 左右。但即使如此,其计算量(FLOPs)仍然更高。这是一个典型的“用计算换性能”的例子。
-
常见坑和调试技巧:
- 使用 ReLU 时,注意配合合适的权重初始化方法(如 He 初始化),以降低神经元一开始就“死亡”的概率。
- 如果训练不稳定,梯度爆炸,可以检查是否在隐藏层误用了 Softmax。
- 如果模型训练后期loss不再下降,可以尝试将 ReLU 更换为 LeakyReLU 或 GELU/SiLU,看是否是神经元死亡或梯度饱和导致的问题。
常见误区与边界情况
-
误区1: Softmax 可以用在隐藏层。
- 错误。Softmax 的输出和为1,这会极大地限制隐藏层表示的多样性,强行引入了不必要的依赖关系,导致信息瓶颈。它只应用于表示互斥类别概率的输出层。
-
误区2: ReLU 在 处不可导,所以不能用梯度下降。
- 不完全正确。虽然在数学上 点导数未定义,但在实践中,我们可以使用次梯度(subgradient)。在 处,梯度可以被定义为0或1中的任意一个。深度学习框架通常会选择其中一个(如0),这在实践中运行良好,因为输入精确为0的概率极小。
-
误区3: SwiGLU 是一个像 ReLU 一样的激活函数。
- 错误。SwiGLU 是一个模块或计算块,它内部使用了 SiLU/Swish 激活函数作为门控机制的一部分,但其本身结构比单一的激活函数复杂,包含了多个线性层和逐元素乘法。
-
边界情况:Softmax 的数值稳定性
- 问题: 当输入 非常大时, 可能会导致上溢(overflow)变成
inf。当 是非常大的负数时, 可能下溢(underflow)变成0,导致分母为0或结果不精确。 - 解决方案: 在计算 Softmax 之前,将输入向量 的所有元素都减去 中的最大值 。 这个变换在数学上是等价的,但极大地增强了数值稳定性,因为指数函数的最大输入现在是0,避免了上溢。
- 问题: 当输入 非常大时, 可能会导致上溢(overflow)变成
-
常见面试追问:
- 问: 为什么 Tanh 通常比 Sigmoid 在隐藏层表现更好?
- 答: Tanh 的输出是零中心化的(范围-1到1),而 Sigmoid 是非零中心化的(范围0到1)。零中心化的输出使得下一层神经元的输入更接近零均值,这有助于梯度下降过程中的收敛,减少参数更新的“Z”字形抖动。
- 问: ReLU 相比 Sigmoid/Tanh 有什么核心优势?
- 答: 1. 缓解梯度消失:在正区间,梯度恒为1,梯度可以顺畅地在层间传播。2. 计算效率高:没有复杂的指数运算,只有一次比较。3. 稀疏性:它会使一些神经元输出为0,这是一种自然的稀疏化,可能有助于模型泛化。
- 问: Gated-FFN(如 SwiGLU)相比传统的 FFN(如 ReLU-FFN)好在哪里?
- 答: 它引入了数据依赖的门控机制。传统的 FFN 对所有输入应用相同的非线性变换。而 Gated-FFN 的门控部分 () 可以根据输入 的内容,动态地决定哪些信息(来自 )应该被保留或抑制。这种自适应的信息流控制赋予了模型更强的表达能力和灵活性,尤其在处理复杂的语言模式时效果显著。
- 问: 为什么 Tanh 通常比 Sigmoid 在隐藏层表现更好?