一、激活函数的作用与核心意义
激活函数(Activation Function)是神经网络中不可或缺的组成部分,其核心作用是引入非线性变换。如果没有激活函数,无论神经网络有多少层,每一层的输出都是输入的线性组合,整个网络就退化为一个线性模型,其表达能力极其有限。激活函数使神经网络能够学习和表示复杂的非线性映射关系。
激活函数的三大核心作用
- 引入非线性: 使神经网络可以拟合任意复杂函数(Universal Approximation Theorem)
- 控制信息流: 通过激活/抑制状态控制神经元输出的信息量
- 梯度传导: 在反向传播中作为梯度流的通道,影响训练效率
二、Sigmoid 激活函数
Sigmoid 是深度学习早期最常用的激活函数之一,它将任意实数输入映射到 (0,1) 区间,具有平滑的 S 形曲线。
数学定义
Sigmoid(x) = 1 / (1 + e-x)
Sigmoid'(x) = Sigmoid(x) * (1 - Sigmoid(x))
Python 实现与可视化
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-x))
def sigmoid_derivative(x):
s = sigmoid(x)
return s * (1 - s)
x = np.linspace(-10, 10, 100)
y = sigmoid(x)
dy = sigmoid_derivative(x)
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(x, y, label='Sigmoid', color='blue', linewidth=2)
plt.title('Sigmoid 函数')
plt.grid(True, alpha=0.3)
plt.axhline(y=0, color='gray', linestyle='--', linewidth=0.5)
plt.subplot(1, 2, 2)
plt.plot(x, dy, label='Sigmoid Derivative', color='red', linewidth=2)
plt.title('Sigmoid 导数')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Sigmoid 的优缺点分析
优点
- 输出范围在 (0,1) 之间,可解释为概率值
- 函数平滑连续,处处可导
- 在二分类问题的输出层中有天然优势
缺点
- 梯度饱和(Gradient Saturation): 当输入绝对值很大时(|x| > 5),导数趋近于 0,导致梯度消失
- 非零中心(Not Zero-Centered): 输出恒为正值,导致后续层输入偏移,降低收敛速度
- 指数运算开销大: 相比简单函数计算成本高
梯度饱和的量化分析
Sigmoid 的导数最大值为 0.25(在 x=0 处)。当输入 x=5 时,Sigmoid(x) ≈ 0.9933,导数 ≈ 0.0067——梯度已经缩减了 99% 以上。在深层网络中,多个 Sigmoid 层连乘会导致梯度指数级衰减,这就是著名的梯度消失问题(Vanishing Gradient Problem)。
def vanishing_gradient_demo(n_layers=10, x=3.0):
"""模拟 n_layers 个 Sigmoid 层反向传播时的梯度连乘效果"""
grad = sigmoid_derivative(x)
for i in range(1, n_layers + 1):
grad *= sigmoid_derivative(x)
print(f"第 {i:2d} 层反向梯度: {grad:.10f}")
vanishing_gradient_demo(n_layers=8, x=3.0)
三、Tanh 双曲正切激活函数
Tanh(双曲正切)是 Sigmoid 的改进版本,将输入映射到 (-1, 1) 区间,解决了 Sigmoid 非零中心的问题。
数学定义
Tanh(x) = (ex - e-x) / (ex + e-x) = 2 * Sigmoid(2x) - 1
Tanh'(x) = 1 - Tanh2(x)
Python 实现
def tanh(x):
return np.tanh(x)
def tanh_derivative(x):
t = tanh(x)
return 1.0 - t ** 2
x = np.linspace(-5, 5, 100)
tanh_y = tanh(x)
sigmoid_y = sigmoid(x)
plt.figure(figsize=(10, 5))
plt.plot(x, tanh_y, label='Tanh', color='purple', linewidth=2)
plt.plot(x, sigmoid_y, label='Sigmoid', color='blue', linewidth=2)
plt.axhline(y=0, color='gray', linestyle='--')
plt.legend(); plt.grid(True, alpha=0.3)
plt.title('Tanh vs Sigmoid:Tanh 以 0 为中心')
plt.show()
Tanh 的核心特性
- 零中心输出: 输出范围为 (-1, 1),有助于缓解梯度偏移问题,收敛速度通常快于 Sigmoid
- 更强的梯度: 在 x=0 处导数为 1.0(比 Sigmoid 的 0.25 强 4 倍)
- 但仍存在饱和: 当输入绝对值较大时,导数同样趋近于 0,梯度饱和问题仍然存在
在 RNN(循环神经网络)和 LSTM 网络中,Tanh 仍然被广泛使用,尤其是在门控机制中。但在标准的前馈网络中,Tanh 已基本被 ReLU 取代。
四、ReLU 及其变体家族
ReLU(Rectified Linear Unit,修正线性单元)的提出是深度学习领域的里程碑式突破。它的简单性带来了训练效率和表达能力的双重提升。
4.1 标准 ReLU
ReLU(x) = max(0, x)
ReLU'(x) = 1 (if x > 0), 0 (if x < 0)
def relu(x):
return np.maximum(0, x)
def relu_derivative(x):
return np.where(x > 0, 1.0, 0.0)
import torch
import torch.nn as nn
relu_layer = nn.ReLU()
x_tensor = torch.tensor([-2.0, -1.0, 0.0, 1.0, 3.0])
output = relu_layer(x_tensor)
print(output)
ReLU 的革命性优势
- 计算简单: 仅需比较操作,无需指数运算,前向和反向传播极快
- 缓解梯度消失: 正区间导数为 1,梯度可以无损传递
- 稀疏激活: 约 50% 的神经元输出为 0,天然产生稀疏表示,提升计算效率
- 生物合理性: 更接近生物神经元的单侧抑制特性
Dying ReLU 问题(神经元死亡)
当输入为负时,ReLU 输出恒为 0 且导数为 0,这意味着一旦某个神经元进入负区域,它将永远无法恢复——因为梯度为 0,权重无法更新。大量的死亡神经元会导致网络表达能力急剧下降。这个问题在初始化不当或学习率过高时尤为严重。
Dying ReLU 的定量分析
假设一个神经元有 100 个输入连接,每个输入经过 ReLU 后约 50% 被置零。如果学习率过大导致权重调整后该神经元对所有训练样本的输出均为负,则该神经元永久死亡。在深层网络(50层以上)中,如果初始死亡率为 5%/层,经过 50 层后仅有 (0.95)^50 ≈ 7.7% 的神经元存活。
4.2 LeakyReLU —— 给负区间留一条通道
LeakyReLU(x) = x (if x >= 0), αx (if x < 0)
其中 α 通常取 0.01
def leaky_relu(x, alpha=0.01):
return np.where(x >= 0, x, alpha * x)
leaky_relu_layer = nn.LeakyReLU(negative_slope=0.01)
output = leaky_relu_layer(torch.tensor([-2.0, 0.0, 3.0]))
print(output)
4.3 PReLU —— 可学习的负斜率
PReLU(Parametric ReLU)将 LeakyReLU 中的 α 作为可训练参数,让网络自行学习最佳的负区间斜率:
class PReLU_Layer(nn.Module):
def __init__(self, num_parameters=1):
super().__init__()
self.alpha = nn.Parameter(torch.tensor([0.25]))
def forward(self, x):
return torch.where(x >= 0, x, self.alpha * x)
4.4 ELU —— 指数线性单元
ELU(x) = x (if x >= 0), α(ex - 1) (if x < 0)
ELU 在负区间的指数衰减特性使其输出均值更接近 0,进一步加速收敛。但相比 ReLU,ELU 的计算开销稍大(涉及指数运算)。
def elu(x, alpha=1.0):
return np.where(x >= 0, x, alpha * (np.exp(x) - 1))
def elu_derivative(x, alpha=1.0):
return np.where(x >= 0, 1.0, alpha * np.exp(x))
4.5 各 ReLU 变体对比总结
| 变体 | 负区间行为 | 参数 | 优点 | 缺点 |
| ReLU | 截断为 0 | 无 | 计算最快,稀疏激活 | Dying ReLU |
| LeakyReLU | 固定小斜率 | α=0.01 | 缓解死亡问题 | 斜率需手动调参 |
| PReLU | 可学习斜率 | α 可训练 | 自适应调整 | 增加参数量,有过拟合风险 |
| ELU | 指数衰减 | α=1.0 | 均值趋近零,收敛快 | 指数计算开销大 |
五、Swish 与 GELU —— 现代自门控激活函数
近年来,研究人员发现引入自门控(self-gating)机制的激活函数在深层网络中表现更优。Swish 和 GELU 是最具代表性的两个。
5.1 Swish(SiLU)
Swish 由 Google 团队在 2017 年提出,定义为 x * Sigmoid(x)。它在 x=0 附近具有平滑的非单调特性。
Swish(x) = x * Sigmoid(x) = x / (1 + e-x)
Swish'(x) = Swish(x) + Sigmoid(x) * (1 - Swish(x))
def swish(x):
return x * sigmoid(x)
swish_layer = nn.SiLU()
x = torch.tensor([-3.0, -1.0, 0.0, 1.0, 3.0])
print(swish_layer(x))
Swish 的关键特性
- 非单调性: 在 x < 0 的区域先下降后上升,这种"非单调凹陷"有助于更好地建模复杂模式
- 自门控: Sigmoid(x) 作为门控信号,自动调节信息流,无需额外的门控网络
- 平滑性: 处处可导,比 ReLU 的导数在 0 处更连续,优化更稳定
5.2 GELU(高斯误差线性单元)
GELU 由 Dan Hendrycks 等在 2016 年提出,融合了 Dropout、Zoneout 和 ReLU 的思想,在 BERT、GPT 等 Transformer 架构中成为标准选择。
GELU(x) = x * Φ(x)
其中 Φ(x) 为标准正态分布的累积分布函数
近似公式:GELU(x) ≈ 0.5x * (1 + Tanh(√(2/π) * (x + 0.044715x3)))
from scipy.special import erf
def gelu_exact(x):
return x * 0.5 * (1.0 + erf(x / np.sqrt(2.0)))
def gelu_approx(x):
return 0.5 * x * (1.0 + np.tanh(
np.sqrt(2.0 / np.pi) * (x + 0.044715 * x ** 3)
))
gelu_layer = nn.GELU(approximate='tanh')
x = torch.randn(4)
print(x, gelu_layer(x), sep='\n')
| 特性 | Swish | GELU |
| 提出年份 | 2017 (Google) | 2016 (Hendrycks & Gimpel) |
| 数学形式 | x · σ(x) | x · Φ(x) |
| 非单调性 | 是 | 是 |
| 主要应用 | EfficientNet, MobileNet | BERT, GPT, ViT, LLaMA |
| 计算开销 | 中等(含指数) | 较高(含 erf 或 tanh) |
六、Softmax —— 多分类概率输出
Softmax 通常用于多分类神经网络的输出层,将 K 个实数映射为 K 个和为 1 的概率值。
数学定义
Softmax(xi) = exi / Σj=1K exj
Python 实现
def softmax(logits):
"""稳定的 Softmax 实现(减去最大值防止指数溢出)"""
shifted = logits - np.max(logits, axis=-1, keepdims=True)
exp_vals = np.exp(shifted)
return exp_vals / np.sum(exp_vals, axis=-1, keepdims=True)
logits = np.array([2.0, 1.0, 0.1])
probs = softmax(logits)
print("原始得分:", logits)
print("Softmax 概率:", probs)
print("概率之和:", np.sum(probs))
数值稳定性注意
在实现 Softmax 时,必须减去最大值防止指数运算溢出。例如,输入 [1000, 1001, 999] 时,e1001 会超出浮点数范围,而减去最大值后变为 [-1, 0, -2],计算安全。
Softmax 与交叉熵损失的梯度推导
Softmax 与交叉熵损失(Cross-Entropy Loss)的组合在反向传播中具有极其简洁的梯度形式,这是它被广泛使用的重要原因:
L = -Σi yi log(pi)
∂L/∂zi = pi - yi
criterion = nn.CrossEntropyLoss()
logits = torch.tensor([[2.0, 1.0, 0.1]])
target = torch.tensor([0])
loss = criterion(logits, target)
print(f"CrossEntropy Loss: {loss.item():.4f}")
七、激活函数全景对比
| 激活函数 | 输出范围 | 是否零中心 | 是否饱和 | 计算复杂度 | 导数最大值 | 主要问题 |
| Sigmoid | (0, 1) | 否 | 是(双侧饱和) | O(exp) | 0.25 | 梯度消失 |
| Tanh | (-1, 1) | 是 | 是(双侧饱和) | O(exp) | 1.0 | 梯度消失 |
| ReLU | [0, ∞) | 否 | 否(正侧无饱和) | O(1) | 1.0 | Dying ReLU |
| LeakyReLU | (-∞, ∞) | 近似 | 否 | O(1) | 1.0 | α 需手动设定 |
| ELU | (-α, ∞) | 近似 | 负侧软饱和 | O(exp) | 1.0 | 计算开销大 |
| Swish | (-0.28, ∞) | 否 | 负侧软饱和 | O(exp) | ≈1.1 | 计算开销较大 |
| GELU | (-0.17, ∞) | 否 | 负侧软饱和 | O(erf) | ≈1.0 | 计算开销大 |
| Softmax | (0, 1) | — | — | O(exp) | — | 输出层专用 |
选择指南
- 隐藏层首选: ReLU(通用场景)或 GELU / Swish(深层 Transformer 架构)
- 二分类输出层: Sigmoid
- 多分类输出层: Softmax
- RNN / LSTM: Tanh + Sigmoid(门控机制)
- 避免 Dying ReLU: 使用 LeakyReLU 或 PReLU,或调低学习率
八、梯度饱和与死亡神经元的深入分析
8.1 梯度消失在深层网络中的传播效应
考虑一个 10 层的全连接网络,每层使用 Sigmoid 激活。在反向传播中,损失函数 L 对第 l 层权重 W(l) 的梯度为:
∂L / ∂W(l) = (W(L) · σ'(z(L))) · ... · σ'(z(l)) · x(l-1)
由于每个 σ' ≤ 0.25,10 层后梯度衰减为原始的 (0.25)10 ≈ 9.5 × 10-7
np.random.seed(42)
n_layers = 15
n_neurons = 128
x = np.random.randn(n_neurons)
Ws = [np.random.randn(n_neurons, n_neurons) * 0.1 for _ in range(n_layers)]
activations_sig = [x]
for W in Ws:
z = W @ activations_sig[-1]
a = sigmoid(z)
activations_sig.append(a)
grad_sig = 1.0
for a in activations_sig[1:]:
grad_sig *= np.mean(sigmoid_derivative(a))
print(f"Sigmoid 网络平均梯度: {grad_sig:.10f}")
print("ReLU 在正区间梯度: 1.0(不衰减)")
8.2 Dying ReLU 的缓解策略
- 使用 LeakyReLU / PReLU / ELU: 从根本上避免死亡问题
- 较小的学习率: 防止权重大幅跳入负区域
- 合理的初始化: He 初始化(专为 ReLU 设计)比 Xavier 初始化更适合
- Batch Normalization: 将输入标准化,降低进入负区域的概率
def he_init(fan_in, fan_out):
std = np.sqrt(2.0 / fan_in)
return np.random.randn(fan_out, fan_in) * std
def xavier_init(fan_in, fan_out):
std = np.sqrt(1.0 / fan_in)
return np.random.randn(fan_out, fan_in) * std
九、激活函数在反向传播中的梯度流
理解激活函数如何影响梯度流是优化深度网络的关键。以下通过一个完整的前向-反向传播示例来展示不同激活函数的梯度行为。
完整的两层网络梯度流示例
np.random.seed(0)
n_input, n_hidden, n_output = 4, 8, 2
x = np.random.randn(n_input)
y_true = np.array([1.0, 0.0])
W1 = np.random.randn(n_hidden, n_input) * 0.1
b1 = np.zeros(n_hidden)
W2 = np.random.randn(n_output, n_hidden) * 0.1
b2 = np.zeros(n_output)
z1_sig = W1 @ x + b1
a1_sig = sigmoid(z1_sig)
z2_sig = W2 @ a1_sig + b2
a2_sig = sigmoid(z2_sig)
loss_sig = np.sum((a2_sig - y_true) ** 2)
dL_da2 = 2 * (a2_sig - y_true)
dL_dz2 = dL_da2 * sigmoid_derivative(z2_sig)
dL_dW2 = np.outer(dL_dz2, a1_sig)
dL_da1 = W2.T @ dL_dz2
dL_dz1 = dL_da1 * sigmoid_derivative(z1_sig)
dL_dW1 = np.outer(dL_dz1, x)
print("Sigmoid 隐藏层梯度均值:", np.mean(np.abs(dL_dz1)))
print("Sigmoid 输出层梯度均值:", np.mean(np.abs(dL_dz2)))
W1 = np.random.randn(n_hidden, n_input) * np.sqrt(2.0 / n_input)
z1_relu = W1 @ x + b1
a1_relu = np.maximum(0, z1_relu)
z2_relu = W2 @ a1_relu + b2
a2_relu = sigmoid(z2_relu)
dL_dz1_relu = (W2.T @ (2 * (a2_relu - y_true) * sigmoid_derivative(z2_relu)))
dL_dz1_relu[z1_relu <= 0] = 0
print("ReLU 隐藏层梯度均值:", np.mean(np.abs(dL_dz1_relu)))
关键发现
在上述对比中,ReLU 隐藏层的梯度均值通常比 Sigmoid 大 3~5 个数量级。这正是 ReLU 能够成功训练深层网络的根本原因——它保持了梯度在正区间的完整传导。这一特性使得数十层甚至上百层的网络训练成为可能。
十、现代架构中的激活函数趋势
随着深度学习架构的演进,激活函数的选择也在不断变化。以下是当前各大主流架构中的激活函数使用趋势:
| 架构类别 | 常用激活函数 | 代表模型 | 选择原因 |
| CNN(卷积网络) | ReLU / LeakyReLU | ResNet, VGG, YOLO | 计算高效,稀疏激活 |
| 轻量 CNN | Swish / ReLU6 | MobileNetV3, EfficientNet | 自门控提升精度,量化友好 |
| Transformer | GELU / ReLU | BERT, GPT, LLaMA, ViT | 平滑梯度,深层效果好 |
| RNN / LSTM | Tanh + Sigmoid | LSTM, GRU | 门控机制需要范围约束 |
| GAN(生成对抗) | LeakyReLU | DCGAN, StyleGAN | 避免死亡神经元,稳定训练 |
Transformer 为何选择 GELU?
以 GPT 系列和 BERT 为代表的 Transformer 架构普遍选择 GELU 而非 ReLU,主要原因有三:
- 梯度平滑性: GELU 在 0 附近平滑可导,避免了 ReLU 在 0 处不可导带来的优化不稳定性
- 正则化效果: GELU 的随机门控特性(输入乘以一个概率值)天然具有 Dropout 式的正则化效果
- 深层表现: 在 12 层以上的 Transformer 中,GELU 收敛更快、最终精度更高
from transformers.modeling_utils import get_activation
activation_fn = get_activation("gelu")
十一、核心要点总结
- 激活函数的本质: 为神经网络引入非线性,使其具备拟合任意复杂函数的能力
- Sigmoid → ReLU 的范式转变: ReLU 的简单性和梯度保持能力是深层网络训练的关键突破
- 梯度消失的根源: S 形激活函数的导数在饱和区趋近于零,多层连乘使梯度指数衰减
- Dying ReLU 的应对: LeakyReLU / PReLU / ELU 通过保留负区间梯度来避免神经元死亡
- 现代趋势: GELU 和 Swish 通过自门控机制在 Transformer 等深层架构中表现优异
- 输出层选择: 二分类用 Sigmoid,多分类用 Softmax,回归问题无需激活函数(或线性激活)
- 实践原则: 没有万能激活函数——选择需结合网络架构、任务类型和计算约束
十二、进一步思考与练习
实践练习
- 在 MNIST 数据集上训练一个 3 层全连接网络,分别使用 Sigmoid、Tanh、ReLU 和 GELU,比较收敛速度和最终准确率
- 统计训练过程中 ReLU 神经元的死亡比例,尝试用 LeakyReLU 能否降低这一比例
- 实现一个自定义激活函数并结合
nn.Module 集成到 PyTorch 模型中
- 可视化不同激活函数的梯度直方图,观察深度增加时梯度的分布变化
扩展阅读建议
- 《Deep Learning》Goodfellow et al. Ch.6 — 深度前馈网络中的激活函数
- 《Attention Is All You Need》Vaswani et al. 2017 — Transformer 中的 ReLU 到 GELU 的演进
- "Searching for Activation Functions" Ramachandran et al. 2017 — Swish 的提出论文
- "Gaussian Error Linear Units" Hendrycks & Gimpel 2016 — GELU 原始论文
- "Delving Deep into Rectifiers" He et al. 2015 — PReLU 和 He 初始化