主流激活函数原理与演进详解:从 Sigmoid 到 SwiGLU
从 Sigmoid/Tanh 到 ReLU 家族,再到 GELU/SiLU 和当前 LLM 标准 SwiGLU,全面梳理激活函数的演进脉络、数学原理与工程实现。
1. 为什么需要激活函数
没有激活函数,多层神经网络等价于单层线性变换:
$$f(x) = W_2(W_1 x + b_1) + b_2 = W'x + b'$$
无论堆多少层,都只能学习线性关系。激活函数引入非线性,让网络能拟合任意复杂函数。
2. 早期经典激活函数
2.1 Sigmoid
$$\sigma(x) = \frac{1}{1 + e^{-x}}$$
$$\sigma'(x) = \sigma(x)(1 - \sigma(x))$$
import torch
import torch.nn.functional as F
def sigmoid(x):
return 1.0 / (1.0 + torch.exp(-x))
# 导数
def sigmoid_grad(x):
s = sigmoid(x)
return s * (1 - s)
输出范围: (0, 1)
最大梯度: 0.25 (在 x=0 处)
1 ───────────────────────── ← 饱和区,梯度≈0
/
/ ← 梯度最大区域
/
0 ──/─────────────────────────────── ← 饱和区,梯度≈0
-6 -4 -2 0 2 4 6
致命问题:
- 梯度饱和:$|x| > 4$ 时梯度趋近于 0 → 深层网络梯度消失
- 输出不以 0 为中心:输出恒正 → 后续层的梯度要么全正要么全负 → 更新效率低(zigzag 问题)
- 指数运算开销
现在的用途:二分类输出层、门控机制(LSTM 的门、GLU 的门)。
2.2 Tanh
$$\tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} = 2\sigma(2x) - 1$$
$$\tanh'(x) = 1 - \tanh^2(x)$$
def tanh(x):
return torch.tanh(x)
# 等价于 sigmoid 的线性变换
def tanh_from_sigmoid(x):
return 2 * sigmoid(2 * x) - 1
输出范围: (-1, 1) ← 以 0 为中心,解决了 sigmoid 的问题
最大梯度: 1.0 (在 x=0) ← 比 sigmoid 的 0.25 好很多
1 ─────────────────────
╱
╱
╱
╱─────────────────────── -1
改进:输出以 0 为中心 → 梯度更新效率更高。 仍有问题:$|x| > 3$ 时梯度仍然饱和 → 深层网络依然梯度消失。 现在的用途:RNN/LSTM 中仍常用。
3. ReLU 家族
3.1 ReLU(Rectified Linear Unit)
$$\text{ReLU}(x) = \max(0, x) = \begin{cases} x & x > 0 \ 0 & x \leq 0 \end{cases}$$
$$\text{ReLU}'(x) = \begin{cases} 1 & x > 0 \ 0 & x \leq 0 \end{cases}$$
def relu(x):
return torch.maximum(x, torch.tensor(0.0))
# 等价
def relu_v2(x):
return x * (x > 0).float()
╱
╱
╱───────── 0
-4 -2 0 2 4
深度学习里程碑:
- 正区间梯度恒为 1 → 完全解决梯度消失(正区间)
- 计算极简:一次比较 → 比 sigmoid/tanh 快 6~10 倍
- 稀疏激活:约 50% 神经元输出 0 → 天然正则化
致命问题——死亡 ReLU:
如果某个神经元的输入持续为负:
输出 = 0, 梯度 = 0 → 权重永远不更新 → 神经元"死掉"
场景: 学习率过大 → 权重更新跳到全负区域 → 永远回不来
实际: 训练后期可能有 10~40% 的神经元死亡
3.2 Leaky ReLU
$$\text{LeakyReLU}(x) = \begin{cases} x & x > 0 \ \alpha x & x \leq 0 \end{cases}, \quad \alpha = 0.01$$
def leaky_relu(x, alpha=0.01):
return torch.where(x > 0, x, alpha * x)
╱
╱
╱
_╱───────── 负区间有微小斜率 (0.01x)
解决死亡 ReLU:负区间有梯度 $\alpha$ → 神经元不会完全死掉。 问题:$\alpha$ 是超参数,需要手动设定。
3.3 PReLU(Parametric ReLU)
$$\text{PReLU}(x) = \begin{cases} x & x > 0 \ \alpha_i x & x \leq 0 \end{cases}$$
# α 是可学习参数,每个通道一个
class PReLU(torch.nn.Module):
def __init__(self, num_channels):
super().__init__()
self.alpha = torch.nn.Parameter(torch.full((num_channels,), 0.25))
def forward(self, x):
return torch.where(x > 0, x, self.alpha * x)
改进:$\alpha$ 可学习 → 网络自己决定最佳负区间斜率。 代价:多了少量参数(每个通道一个 $\alpha$)。
3.4 ELU(Exponential Linear Unit)
$$\text{ELU}(x) = \begin{cases} x & x > 0 \ \alpha(e^x - 1) & x \leq 0 \end{cases}, \quad \alpha = 1.0$$
def elu(x, alpha=1.0):
return torch.where(x > 0, x, alpha * (torch.exp(x) - 1))
╱
╱
╱
_╱~~~~~~~~~~ -α ← 负区间平滑趋近于 -α,不像 Leaky ReLU 无界下降
优势:
- 输出均值更接近 0 → 收敛更快
- 负区间有饱和(趋近 $-\alpha$)→ 对噪声更鲁棒
- 处处连续,x=0 处平滑
劣势:有指数运算 → 比 ReLU 慢
3.5 SELU(Scaled ELU)
$$\text{SELU}(x) = \lambda \begin{cases} x & x > 0 \ \alpha(e^x - 1) & x \leq 0 \end{cases}$$
$$\lambda \approx 1.0507, \quad \alpha \approx 1.6733$$
def selu(x):
alpha = 1.6732632423543772
lam = 1.0507009873554805
return lam * torch.where(x > 0, x, alpha * (torch.exp(x) - 1))
特殊性质——自归一化:
精心选择的 $\lambda$ 和 $\alpha$ 使得在特定条件下:
- 每层输出自动保持均值≈0、方差≈1
- 不需要 BatchNorm/LayerNorm
条件很苛刻:需要用 LeCun 初始化 + AlphaDropout + 全连接网络。实际使用有限。
3.6 ReLU 家族对比
| 函数 | 负区间 | 梯度消失 | 死亡问题 | 计算开销 |
|---|---|---|---|---|
| ReLU | $0$ | 正区间无 | 有 | 最低 |
| Leaky ReLU | $0.01x$ | 无 | 无 | 低 |
| PReLU | $\alpha_i x$ (可学习) | 无 | 无 | 低 |
| ELU | $\alpha(e^x-1)$ | 无 | 无 | 中 |
| SELU | $\lambda\alpha(e^x-1)$ | 无(自归一化) | 无 | 中 |
4. 现代主流激活函数
4.1 GELU(Gaussian Error Linear Unit)
Transformer 的默认选择:BERT、GPT、ViT 都用它。
$$\text{GELU}(x) = x \cdot \Phi(x)$$
其中 $\Phi(x) = P(X \leq x)$ 是标准正态分布的累积分布函数。
直觉:根据输入值的"大小"概率性地决定是否保留:
- $x$ 很大 → $\Phi(x) \approx 1$ → 几乎完全保留
- $x$ 很小 → $\Phi(x) \approx 0$ → 几乎完全抑制
- $x$ 在 0 附近 → 平滑过渡
# 精确实现
def gelu_exact(x):
return x * 0.5 * (1.0 + torch.erf(x / math.sqrt(2.0)))
# 近似实现(更快,常用)
def gelu_approx(x):
return 0.5 * x * (1.0 + torch.tanh(
math.sqrt(2.0 / math.pi) * (x + 0.044715 * x ** 3)
))
# PyTorch 内置
y = F.gelu(x)
导数:
$$\text{GELU}'(x) = \Phi(x) + x \cdot \phi(x)$$
其中 $\phi(x) = \frac{1}{\sqrt{2\pi}} e^{-x^2/2}$ 是标准正态的概率密度函数。
GELU vs ReLU 形状对比:
ReLU: ╱ GELU: ╱
╱ _╱
_____╱ ___~_╱
硬截断 平滑过渡 + 负区间有个小"坑"
为什么比 ReLU 好?
- 平滑 → 优化更容易(梯度连续)
- 非单调 → 负区间的"小坑"提供了正则化效果
- 概率性门控 → 比硬阈值更灵活
4.2 SiLU / Swish
$$\text{SiLU}(x) = x \cdot \sigma(x) = \frac{x}{1 + e^{-x}}$$
def silu(x):
return x * torch.sigmoid(x)
# PyTorch 内置
y = F.silu(x)
导数:
$$\text{SiLU}'(x) = \sigma(x) + x \cdot \sigma(x)(1 - \sigma(x)) = \sigma(x)(1 + x(1 - \sigma(x)))$$
def silu_grad(x):
s = torch.sigmoid(x)
return s * (1 + x * (1 - s))
Google 通过 NAS(神经架构搜索)发现:从搜索空间中自动找到 $x \cdot \sigma(\beta x)$,当 $\beta=1$ 时就是 SiLU。
形状与 GELU 极其接近,实践中效果差异极小。LLaMA、PaLM、Mistral 等用 SiLU。
4.3 GELU vs SiLU 对比
# 数值对比
x = torch.tensor([-2.0, -1.0, -0.5, 0.0, 0.5, 1.0, 2.0])
gelu_vals = F.gelu(x) # [-0.045, -0.159, -0.154, 0.0, 0.346, 0.841, 1.955]
silu_vals = F.silu(x) # [-0.238, -0.269, -0.186, 0.0, 0.311, 0.731, 1.762]
# 差异很小,趋势一致:
# - 正区间: 接近恒等 (≈x)
# - 负区间: 有一个小"谷底"(非单调)
# - x=0: 都等于 0
| 特性 | GELU | SiLU/Swish |
|---|---|---|
| 公式 | $x \cdot \Phi(x)$ | $x \cdot \sigma(x)$ |
| 来源 | 概率论推导 | NAS 自动搜索 |
| 典型使用 | BERT, GPT, ViT | LLaMA, PaLM, Mistral |
| 计算开销 | 稍高(erf 函数) | 中(sigmoid) |
| 负区间最小值 | ≈ -0.17 (x≈-0.74) | ≈ -0.28 (x≈-1.28) |
5. GLU 系列(Gated Linear Units)
5.1 GLU 的核心思想
不是单个激活函数,而是一种门控机制:把输入分成两半,一半通过激活函数做门控,另一半作为信息流,两者逐元素相乘。
$$\text{GLU}(x, W_1, W_2) = \sigma(xW_1) \odot (xW_2)$$
输入 x: (batch, hidden_size)
↓
┌──────┴──────┐
↓ ↓
x @ W₁ x @ W₂
↓ ↓
激活函数 恒等(信息流)
↓ ↓
└──── ⊙ ──────┘ ← 逐元素相乘
↓
输出: (batch, intermediate_size)
门控的直觉:$\sigma(xW_1)$ 的值在 0~1 之间,决定 $xW_2$ 的每个维度"放多少信息通过"。这比单个激活函数更灵活——模型可以学习在哪些维度上保留信息、在哪些维度上抑制。
5.2 SwiGLU — 当前 LLM 的标准配置
$$\text{SwiGLU}(x, W_1, W_2, W_3) = \text{SiLU}(xW_1) \odot (xW_3)$$
然后再经过一个 down projection:
$$\text{output} = \text{SwiGLU}(x) \cdot W_2$$
def swiglu_ffn(x, w1, w2, w3):
"""
LLaMA / DeepSeek / Mistral 的 FFN 层
x: (batch, hidden_size)
w1: (hidden_size, intermediate_size) gate_proj
w3: (hidden_size, intermediate_size) up_proj
w2: (intermediate_size, hidden_size) down_proj
"""
gate = F.silu(x @ w1) # (batch, intermediate_size) 门控
up = x @ w3 # (batch, intermediate_size) 信息流
x = gate * up # (batch, intermediate_size) 逐元素相乘
x = x @ w2 # (batch, hidden_size) 降维
return x
与标准 FFN 的对比:
标准 FFN (BERT/GPT-2): SwiGLU FFN (LLaMA/DeepSeek):
x → W₁ → GELU → W₂ → out x → W₁ → SiLU ─┐
x → W₃ ─────── ⊙ → W₂ → out
参数: 2 个矩阵 参数: 3 个矩阵(多 50%)
计算: 1 次激活 计算: 1 次激活 + 1 次乘法
代价:多了一个投影矩阵 $W_3$(参数量增加 50%)。 收益:PaLM 论文的系统对比显示,同等计算预算下 SwiGLU 效果显著优于 GELU FFN。
为什么要调小 intermediate_size?
标准 FFN: intermediate = 4 × hidden (如 4 × 4096 = 16384)
SwiGLU FFN: intermediate = 8/3 × hidden (如 8/3 × 4096 ≈ 10944)
因为 SwiGLU 多了一个矩阵,为了保持总参数量不变,需要缩小 intermediate_size。
8/3 ≈ 2.67,3 个矩阵 × 2.67 ≈ 8,约等于标准 FFN 的 2 个矩阵 × 4 = 8。
这也解释了 DeepSeek-V2-Lite 中 Dense 层 intermediate_size=10944 ≈ 8/3 × 4096 的由来(实际 hidden=2048,10944 ≈ 8/3 × 2048 × 2,做了 TP 相关调整)。
5.3 GeGLU
$$\text{GeGLU}(x) = \text{GELU}(xW_1) \odot (xW_3)$$
def geglu(x, w1, w3):
return F.gelu(x @ w1) * (x @ w3)
用 GELU 做门控,效果与 SwiGLU 接近。Gemma 模型使用。
5.4 ReGLU
$$\text{ReGLU}(x) = \text{ReLU}(xW_1) \odot (xW_3)$$
def reglu(x, w1, w3):
return F.relu(x @ w1) * (x @ w3)
用 ReLU 做门控,最简单但效果略差。
5.5 GLU 变体对比
PaLM 论文的系统实验结果(同等计算量):
效果排序: SwiGLU ≈ GeGLU > ReGLU > 标准 GELU FFN > 标准 ReLU FFN
| 变体 | 门控函数 | 使用模型 |
|---|---|---|
| SwiGLU | SiLU/Swish | LLaMA, DeepSeek, Mistral, Qwen |
| GeGLU | GELU | Gemma |
| ReGLU | ReLU | 较少使用 |
| 原始 GLU | Sigmoid | 最初的 GLU 论文 |
5.6 vLLM 中的实现
# vllm/vllm/model_executor/layers/activation.py 中:
class SiluAndMul(nn.Module):
"""SwiGLU 的融合实现: SiLU(x[:half]) * x[half:]"""
def forward(self, x):
d = x.shape[-1] // 2
return F.silu(x[..., :d]) * x[..., d:]
class GeluAndMul(nn.Module):
"""GeGLU 的融合实现"""
def forward(self, x):
d = x.shape[-1] // 2
return F.gelu(x[..., :d]) * x[..., d:]
注意:vLLM 中 $W_1$ 和 $W_3$ 合并为一个 gate_up_proj,输出维度是 $2 \times \text{intermediate_size}$,然后 SiluAndMul 将前后两半分别做 SiLU 和直通,再相乘。
6. 特殊用途的激活函数
6.1 Softmax
$$\text{Softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}}$$
def softmax(x, dim=-1):
# 数值稳定版本:减去最大值防止溢出
x_max = x.max(dim=dim, keepdim=True).values
e_x = torch.exp(x - x_max)
return e_x / e_x.sum(dim=dim, keepdim=True)
不是逐元素函数,而是对整个向量归一化为概率分布。
用途:
- Transformer Attention 中的 $\text{softmax}(QK^T/\sqrt{d})$
- 分类输出层
- MoE Router 的专家选择
6.2 Log-Softmax
$$\text{LogSoftmax}(x_i) = x_i - \log\sum_j e^{x_j}$$
def log_softmax(x, dim=-1):
return x - torch.logsumexp(x, dim=dim, keepdim=True)
与 NLLLoss 配合使用,比先 Softmax 再取 log 数值更稳定。
6.3 Mish
$$\text{Mish}(x) = x \cdot \tanh(\text{softplus}(x)) = x \cdot \tanh(\ln(1 + e^x))$$
def mish(x):
return x * torch.tanh(F.softplus(x))
与 SiLU/GELU 形状类似,在 YOLOv4 等 CV 模型中使用。
7. 激活函数演进时间线
1990s Sigmoid, Tanh ← 早期标准,梯度消失问题
↓
2010 ReLU (AlexNet) ← 深度学习爆发的关键
↓
2015 PReLU, ELU, SELU ← 修复死亡 ReLU
↓
2016 GELU ← Transformer 时代开启
↓
2017 Swish/SiLU (NAS) ← Google 自动搜索发现
↓
2020 SwiGLU (PaLM) ← 当前 LLM 的事实标准
↓
现在 SwiGLU 统治 LLM FFN
GELU 用于 Embedding/其他
Sigmoid 仅用于门控
8. LLM 中的激活函数使用
| 模型 | FFN 激活 | 类型 |
|---|---|---|
| BERT | GELU | 标准 FFN |
| GPT-2 | GELU | 标准 FFN |
| GPT-3 | GELU | 标准 FFN |
| LLaMA 1/2/3 | SwiGLU | GLU FFN |
| DeepSeek-V2/V3 | SwiGLU | GLU FFN |
| Mistral/Mixtral | SwiGLU | GLU FFN |
| Qwen 1.5/2/3 | SwiGLU | GLU FFN |
| Gemma | GeGLU | GLU FFN |
| PaLM | SwiGLU | GLU FFN |
| T5 | ReLU → GeGLU (v1.1) | 标准/GLU FFN |
结论:2023 年后的主流开源 LLM,FFN 层几乎全部采用 SwiGLU。
9. 完整代码:所有激活函数对比
import torch
import torch.nn.functional as F
import math
x = torch.linspace(-3, 3, 1000)
activations = {
"Sigmoid": torch.sigmoid(x),
"Tanh": torch.tanh(x),
"ReLU": F.relu(x),
"LeakyReLU": F.leaky_relu(x, 0.01),
"ELU": F.elu(x),
"GELU": F.gelu(x),
"SiLU/Swish": F.silu(x),
"Mish": x * torch.tanh(F.softplus(x)),
}
# 打印 x=[-2, -1, 0, 1, 2] 处的值
test_x = torch.tensor([-2.0, -1.0, 0.0, 1.0, 2.0])
print(f"{'函数':<15} {'x=-2':>8} {'x=-1':>8} {'x=0':>8} {'x=1':>8} {'x=2':>8}")
print("-" * 60)
for name, fn in [
("Sigmoid", torch.sigmoid),
("Tanh", torch.tanh),
("ReLU", F.relu),
("GELU", F.gelu),
("SiLU", F.silu),
]:
vals = fn(test_x)
print(f"{name:<15} {vals[0]:>8.4f} {vals[1]:>8.4f} {vals[2]:>8.4f} "
f"{vals[3]:>8.4f} {vals[4]:>8.4f}")
输出:
函数 x=-2 x=-1 x=0 x=1 x=2
------------------------------------------------------------
Sigmoid 0.1192 0.2689 0.5000 0.7311 0.8808
Tanh -0.9640 -0.7616 0.0000 0.7616 0.9640
ReLU 0.0000 0.0000 0.0000 1.0000 2.0000
GELU -0.0454 -0.1587 0.0000 0.8413 1.9545
SiLU -0.2384 -0.2689 0.0000 0.7311 1.7616
10. 一句话总结
Sigmoid/Tanh → ReLU(解决梯度消失)→ GELU/SiLU(平滑非单调)→ SwiGLU(门控机制,当前 LLM 标准)。核心趋势是从硬阈值走向平滑门控,从单一激活走向双路门控,SwiGLU 以多 50% 参数换来显著的效果提升,成为 2023 年后 LLM FFN 层的事实标准。