§1.3.23

Sigmoid/Tanh/ReLU/LeakyReLU/GELU/SiLU/SwiGLU/Softmax 的公式、导数、适用场景?

手写练习
  • 用 torch 手写 SwiGLU 模块

核心概念

激活函数(Activation Function)是神经网络中的核心组件,它被应用于神经元的输出之上,旨在为网络引入非线性。如果缺少激活函数,一个多层的神经网络本质上等同于一个单层的线性模型,无法学习和表示复杂的数据模式。一个理想的激活函数应具备非线性、可微(以便于梯度下降)、计算简单、对梯度消失/爆炸不敏感等特性。

原理与推导

我们将激活函数分为几类进行讨论:饱和激活函数、非饱和激活函数、平滑非线性函数、门控激活单元以及用于输出层的函数。

1. Sigmoid

  • 公式: σ(x)=11+ex\sigma(x) = \frac{1}{1 + e^{-x}}
  • 导数: 推导过程: 令 σ(x)=(1+ex)1\sigma(x) = (1 + e^{-x})^{-1},使用链式法则: σ(x)=1(1+ex)2(ex1)=ex(1+ex)2\sigma'(x) = -1 \cdot (1 + e^{-x})^{-2} \cdot (e^{-x} \cdot -1) = \frac{e^{-x}}{(1 + e^{-x})^2} 为了得到更简洁的形式,我们进行变换: σ(x)=1+ex1(1+ex)2=11+ex1(1+ex)2=σ(x)σ(x)2\sigma'(x) = \frac{1 + e^{-x} - 1}{(1 + e^{-x})^2} = \frac{1}{1 + e^{-x}} - \frac{1}{(1 + e^{-x})^2} = \sigma(x) - \sigma(x)^2 最终形式: σ(x)=σ(x)(1σ(x))\sigma'(x) = \sigma(x) (1 - \sigma(x))
  • 直观解释: Sigmoid 函数将任意实数输入压缩到 (0,1)(0, 1) 区间内,常被解释为概率或某种状态的饱和度。其导数在输入远离0时迅速趋近于0(饱和区),导致梯度消失问题,使得深层网络训练困难。

2. Tanh (双曲正切)

  • 公式: tanh(x)=exexex+ex=2σ(2x)1\tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} = 2\sigma(2x) - 1
  • 导数: 推导过程: 令 f(x)=exexf(x) = e^x - e^{-x}, g(x)=ex+exg(x) = e^x + e^{-x}。使用商法则 (fg)=fgfgg2(\frac{f}{g})' = \frac{f'g - fg'}{g^2}tanh(x)=(ex+ex)(ex+ex)(exex)(exex)(ex+ex)2\tanh'(x) = \frac{(e^x + e^{-x})(e^x + e^{-x}) - (e^x - e^{-x})(e^x - e^{-x})}{(e^x + e^{-x})^2} =(e2x+2+e2x)(e2x2+e2x)(ex+ex)2=4(ex+ex)2= \frac{(e^{2x} + 2 + e^{-2x}) - (e^{2x} - 2 + e^{-2x})}{(e^x + e^{-x})^2} = \frac{4}{(e^x + e^{-x})^2} 结合 tanh(x)\tanh(x) 的定义,我们得到: tanh(x)=1tanh2(x)\tanh'(x) = 1 - \tanh^2(x)
  • 直观解释: Tanh 将输入压缩到 (1,1)(-1, 1) 区间。与 Sigmoid 相比,它的输出是零中心化的(均值为0),这使得下一层神经元的输入更接近零均值,有助于加速收敛。但它同样存在饱和区和梯度消失问题。

3. ReLU (Rectified Linear Unit)

  • 公式: ReLU(x)=max(0,x)\text{ReLU}(x) = \max(0, x)
  • 导数: ReLU(x)={1if x>00if x<0undefinedif x=0\text{ReLU}'(x) = \begin{cases} 1 & \text{if } x > 0 \\ 0 & \text{if } x < 0 \\ \text{undefined} & \text{if } x = 0 \end{cases} 在实践中,当 x=0x=0 时,其梯度可以取0或1(次梯度),通常实现为0。
  • 直观解释: ReLU 是一种非饱和激活函数。对于正输入,它保持原样;对于负输入,它直接置零。
    • 优点: 计算极其高效(仅需一次比较);在正区间内梯度恒为1,有效缓解了梯度消失问题。
    • 缺点: 输出非零中心化;可能导致“神经元死亡”(Dying ReLU),即如果一个神经元的输入恒为负,其梯度将永远为0,参数无法更新。
  • 复杂度: 时间复杂度为 O(1)O(1)

4. LeakyReLU (Leaky Rectified Linear Unit)

  • 公式: LeakyReLU(x)=max(αx,x)={xif x0αxif x<0\text{LeakyReLU}(x) = \max(\alpha x, x) = \begin{cases} x & \text{if } x \ge 0 \\ \alpha x & \text{if } x < 0 \end{cases} 其中 α\alpha 是一个小的正常数,如 0.01。
  • 导数: LeakyReLU(x)={1if x>0αif x<0\text{LeakyReLU}'(x) = \begin{cases} 1 & \text{if } x > 0 \\ \alpha & \text{if } x < 0 \end{cases}
  • 直观解释: LeakyReLU 是对 ReLU 的改进,旨在解决“神经元死亡”问题。它为负输入分配了一个小的非零斜率 α\alpha,确保即使在负输入区域,神经元也能获得梯度进行更新。

5. GELU (Gaussian Error Linear Unit)

  • 公式: GELU(x)=xΦ(x)\text{GELU}(x) = x \cdot \Phi(x) 其中 Φ(x)\Phi(x) 是标准正态分布的累积分布函数 (CDF)。一个精确的表达式是: GELU(x)=x12[1+erf(x/2)]\text{GELU}(x) = x \cdot \frac{1}{2} [1 + \text{erf}(x/\sqrt{2})] 近似计算公式(更快): GELU(x)0.5x(1+tanh[2/π(x+0.044715x3)])\text{GELU}(x) \approx 0.5x(1 + \tanh[\sqrt{2/\pi}(x + 0.044715x^3)])
  • 导数: GELU(x)=Φ(x)+xϕ(x)\text{GELU}'(x) = \Phi(x) + x \cdot \phi(x) 其中 ϕ(x)\phi(x) 是标准正态分布的概率密度函数 (PDF)。
  • 直观解释: GELU 可以看作是 ReLU 的平滑近似。它的动机来自于将随机正则化(如 Dropout)与非线性激活相结合。一个输入的激活值 xx 会乘以一个伯努利分布的掩码 mBernoulli(Φ(x))m \sim \text{Bernoulli}(\Phi(x))。GELU 便是对这个随机过程的期望。它在 x=0x=0 附近是弯曲的,而不是像 ReLU 那样尖锐,这种平滑性被认为对模型学习有益。

6. SiLU (Sigmoid-weighted Linear Unit), a.k.a. Swish

  • 公式: SiLU(x)=xσ(x)=x1+ex\text{SiLU}(x) = x \cdot \sigma(x) = \frac{x}{1 + e^{-x}}
  • 导数: 使用乘法法则 (uv)=uv+uv(uv)' = u'v + uv': SiLU(x)=σ(x)+xσ(x)=σ(x)+xσ(x)(1σ(x))=σ(x)(1+x(1σ(x)))\text{SiLU}'(x) = \sigma(x) + x \cdot \sigma'(x) = \sigma(x) + x \cdot \sigma(x)(1-\sigma(x)) = \sigma(x)(1 + x(1-\sigma(x)))
  • 直观解释: SiLU 和 GELU 非常相似,都是平滑、非单调的激活函数。它在负值区域会有一个小的“凹陷”,然后趋近于0。这种“自门控”特性(输入 xx 被其自身的 Sigmoid 值加权)被认为允许网络更好地控制信息流。它的计算比 GELU 简单,性能却常常不相上下甚至更好。

7. SwiGLU (Swish-Gated Linear Unit)

  • 原理: SwiGLU 并非一个简单的标量函数,而是一个网络模块结构,属于门控线性单元(Gated Linear Unit, GLU)家族的变体。
  • 公式: 对于输入 xx,它首先通过两个独立的线性变换(权重矩阵为 WWVV),然后将其中一个变换的结果通过 SiLU(或 Swish)激活,再与另一个变换的结果逐元素相乘。 SwiGLU(x,W,V)=SiLU(xW)(xV)\text{SwiGLU}(x, W, V) = \text{SiLU}(xW) \odot (xV) 其中 \odot 表示逐元素乘法。在 LLaMA 等模型中,通常将 SiLU 替换为 Swish,公式变为: SwiGLU(x,W,V)=Swish(xW)(xV)\text{SwiGLU}(x, W, V) = \text{Swish}(xW) \odot (xV) (注意:SiLU 和 Swish 是同一个函数,这里为了清晰区分门控函数和结构名称)
  • 直观解释: SwiGLU 引入了一个数据驱动的门控机制。xVxV 可以看作是“内容”流,而 SiLU(xW)\text{SiLU}(xW) 是一个“门”,它根据输入动态地决定“内容”流中哪些部分可以通过,以及通过的强度。这种动态控制能力使得模型表达能力更强。

8. Softmax

  • 公式: 对于一个向量 z=(z1,z2,...,zK)z = (z_1, z_2, ..., z_K),其 Softmax 输出 SS 也是一个 K 维向量: Si=Softmax(zi)=ezij=1Kezjfor i=1,...,KS_i = \text{Softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}} \quad \text{for } i=1, ..., K
  • 导数 (雅可比矩阵): Softmax 的输出 SiS_i 依赖于所有输入 zjz_j,因此其导数是一个雅可比矩阵。 Sizj={Si(1Si)if i=jSiSjif ij\frac{\partial S_i}{\partial z_j} = \begin{cases} S_i(1 - S_i) & \text{if } i = j \\ -S_i S_j & \text{if } i \neq j \end{cases} 可以统一写作: Sizj=Si(δijSj)\frac{\partial S_i}{\partial z_j} = S_i(\delta_{ij} - S_j) 其中 δij\delta_{ij} 是克罗内克函数(i=ji=j 时为1,否则为0)。
  • 直观解释: Softmax 将一个实数向量(logits)转换成一个概率分布。输出向量的每个元素都在 (0,1)(0, 1) 之间,且所有元素之和为1。它不是隐藏层的激活函数,而是专门用于多分类问题的输出层,以产生各类别的预测概率。

代码实现

python
1import torch
2import torch.nn as nn
3import torch.nn.functional as F
4import numpy as np
5
6class 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_features
23 hidden_features = hidden_features or 4 * in_features
24 # SwiGLU 的一个常见设计是 FFN 中间层维度是 2/3 * 4 * d_model,这里简化为 hidden_features
25 # 并且这两个线性层输出维度相同
26 self.hidden_features = hidden_features
27
28 # 定义第一个线性层,用于门控(gate)
29 # 为什么需要这个?这是门控机制的一部分,它学习如何根据输入生成一个控制信号。
30 self.gate_proj = nn.Linear(in_features, self.hidden_features, bias=False)
31
32 # 定义第二个线性层,用于内容(up)
33 # 为什么需要这个?这是要被门控的主要信息流。
34 self.up_proj = nn.Linear(in_features, self.hidden_features, bias=False)
35
36 # 定义输出线性层,将门控后的结果映射回期望的输出维度
37 # 为什么需要这个?门控操作后维度是 hidden_features,需要一个线性层将其投影回原始或目标维度。
38 self.down_proj = nn.Linear(self.hidden_features, out_features or in_features, bias=False)
39
40 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)
51
52 # 计算内容值
53 up = self.up_proj(x)
54
55 # 逐元素相乘,实现门控
56 # 为什么相乘?这是门控机制的本质。gate 中的每个元素值(范围大致在-0.28到+inf)
57 # 决定了 up 中对应元素的信息能通过多少。
58 fused_gate_up = gate * up
59
60 # 通过输出投影层
61 output = self.down_proj(fused_gate_up)
62
63 return output
64
65# --- 代码练习:测试 SwiGLU 模块 ---
66if __name__ == '__main__':
67 # 定义模型参数
68 batch_size = 4
69 seq_len = 10
70 in_dim = 32
71 hidden_dim = 64 # FFN 中间层维度
72 out_dim = 32
73
74 # 创建 SwiGLU 实例
75 swiglu_layer = SwiGLU(in_features=in_dim, hidden_features=hidden_dim, out_features=out_dim)
76 print("SwiGLU 模块结构:\n", swiglu_layer)
77
78 # 创建一个随机输入张量
79 input_tensor = torch.randn(batch_size, seq_len, in_dim)
80 print(f"\n输入张量形状: {input_tensor.shape}")
81
82 # 通过 SwiGLU 层
83 output_tensor = swiglu_layer(input_tensor)
84 print(f"输出张量形状: {output_tensor.shape}")
85
86 # 验证输出形状是否正确
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 作为一个可学习的参数。
    • 其他激活函数通常没有需要调整的超参数。
  • 性能 / 显存 / 吞吐 的权衡:

    • ReLU 是最快的,几乎没有计算开销。
    • LeakyReLU 同样非常快。
    • GELUSiLU 计算相对复杂(涉及 exp, tanherf),但现代深度学习框架有优化实现,实际影响不大。
    • SwiGLU 是计算和参数量最大的。一个标准的 FFN 块是 Linear -> Activation -> Linear。而 SwiGLU FFN 块是 (Linear_gate, Linear_up) -> Multiply -> Linear_down。通常为了保持参数量近似,Linear_gateLinear_up 的输出维度会是标准 FFN 中间层维度的 2/3 左右。但即使如此,其计算量(FLOPs)仍然更高。这是一个典型的“用计算换性能”的例子。
  • 常见坑和调试技巧:

    • 使用 ReLU 时,注意配合合适的权重初始化方法(如 He 初始化),以降低神经元一开始就“死亡”的概率。
    • 如果训练不稳定,梯度爆炸,可以检查是否在隐藏层误用了 Softmax。
    • 如果模型训练后期loss不再下降,可以尝试将 ReLU 更换为 LeakyReLU 或 GELU/SiLU,看是否是神经元死亡或梯度饱和导致的问题。

常见误区与边界情况

  • 误区1: Softmax 可以用在隐藏层。

    • 错误。Softmax 的输出和为1,这会极大地限制隐藏层表示的多样性,强行引入了不必要的依赖关系,导致信息瓶颈。它只应用于表示互斥类别概率的输出层。
  • 误区2: ReLU 在 x=0x=0 处不可导,所以不能用梯度下降。

    • 不完全正确。虽然在数学上 x=0x=0 点导数未定义,但在实践中,我们可以使用次梯度(subgradient)。在 x=0x=0 处,梯度可以被定义为0或1中的任意一个。深度学习框架通常会选择其中一个(如0),这在实践中运行良好,因为输入精确为0的概率极小。
  • 误区3: SwiGLU 是一个像 ReLU 一样的激活函数。

    • 错误。SwiGLU 是一个模块计算块,它内部使用了 SiLU/Swish 激活函数作为门控机制的一部分,但其本身结构比单一的激活函数复杂,包含了多个线性层和逐元素乘法。
  • 边界情况:Softmax 的数值稳定性

    • 问题: 当输入 ziz_i 非常大时,ezie^{z_i} 可能会导致上溢(overflow)变成 inf。当 ziz_i 是非常大的负数时,ezie^{z_i} 可能下溢(underflow)变成0,导致分母为0或结果不精确。
    • 解决方案: 在计算 Softmax 之前,将输入向量 zz 的所有元素都减去 zz 中的最大值 c=max(z)c = \max(z)Softmax(zi)=ezicjezjc\text{Softmax}(z_i) = \frac{e^{z_i - c}}{ \sum_j e^{z_j - c} } 这个变换在数学上是等价的,但极大地增强了数值稳定性,因为指数函数的最大输入现在是0,避免了上溢。
  • 常见面试追问:

    • : 为什么 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 的门控部分 (SiLU(xW)\text{SiLU}(xW)) 可以根据输入 xx 的内容,动态地决定哪些信息(来自 xVxV)应该被保留或抑制。这种自适应的信息流控制赋予了模型更强的表达能力和灵活性,尤其在处理复杂的语言模式时效果显著。
相关题目