激活函数的作用#
为什么需要激活函数?#
问题:没有激活函数的神经网络只是线性变换的组合:
$$ y = W_3(W_2(W_1 x + b_1) + b_2) + b_3 = W'x + b' $$仍然是线性函数!
解决方案:在每层之间添加非线性激活函数:
$$ y = f_3(W_3 \cdot f_2(W_2 \cdot f_1(W_1 x + b_1) + b_2) + b_3) $$激活函数的作用#
- 引入非线性:使网络能够学习复杂模式
- 决定输出范围:限制输出值域
- 影响梯度流动:影响反向传播的梯度
常见激活函数#
1. Sigmoid函数#
公式#
$$ \sigma(x) = \frac{1}{1 + e^{-x}} $$特性#
- 输出范围:$(0, 1)$
- 导数:$\sigma'(x) = \sigma(x)(1 - \sigma(x))$
- 优点:
- 输出在0-1之间,适合概率
- 平滑可微
- 缺点:
- 梯度消失:在饱和区域梯度接近0
- 非零中心:输出均值不为0,影响梯度
- 计算较慢:涉及指数运算
图像#
1 | ╱╲
| ╱ ╲
0.5 | ╱ ╲
| ╱ ╲
0 |_╱________╲___
-5 0 52. Tanh函数#
公式#
$$ \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} $$特性#
- 输出范围:$(-1, 1)$
- 导数:$\tanh'(x) = 1 - \tanh^2(x)$
- 优点:
- 零中心:输出均值约为0
- 比Sigmoid梯度更大
- 缺点:
- 仍有梯度消失问题
- 计算较慢
与Sigmoid的关系#
$$ \tanh(x) = 2\sigma(2x) - 1 $$3. ReLU函数(Rectified Linear Unit)#
公式#
$$ \text{ReLU}(x) = \max(0, x) = \begin{cases} x & \text{if } x > 0 \\ 0 & \text{if } x \leq 0 \end{cases} $$特性#
- 输出范围:$[0, +\infty)$
- 导数:$\text{ReLU}'(x) = \begin{cases} 1 & \text{if } x > 0 \\ 0 & \text{if } x \leq 0 \end{cases}$
- 优点:
- 计算快速:只需比较和选择
- 缓解梯度消失:正区域梯度为1
- 稀疏激活:约50%神经元激活
- 缺点:
- 死亡ReLU:负区域梯度为0,神经元可能"死亡"
- 非零中心:输出均值大于0
图像#
| ╱
| ╱
| ╱
| ╱
0 |╱________
-5 0 54. Leaky ReLU#
公式#
$$ \text{LeakyReLU}(x) = \max(\alpha x, x) = \begin{cases} x & \text{if } x > 0 \\ \alpha x & \text{if } x \leq 0 \end{cases} $$其中 $\alpha$ 通常为 $0.01$ 。
特性#
- 优点:
- 解决死亡ReLU问题
- 负区域有小的梯度
- 缺点:
- 需要选择超参数
α
- 需要选择超参数
5. ELU(Exponential Linear Unit)#
公式#
$$ \text{ELU}(x) = \begin{cases} x & \text{if } x > 0 \\ \alpha(e^x - 1) & \text{if } x \leq 0 \end{cases} $$特性#
- 优点:
- 零中心输出(负区域)
- 平滑的负值处理
- 缺点:
- 计算较慢(涉及指数)
6. GELU(Gaussian Error Linear Unit)#
公式#
$$ \text{GELU}(x) = x \cdot \Phi(x) $$其中 $\Phi(x)$ 是标准正态分布的累积分布函数。
特性#
- 优点:
- 在Transformer中表现优异
- 平滑的非线性
- 缺点:
- 计算较复杂
激活函数选择#
隐藏层#
推荐:
- ReLU:最常用,适合大多数情况
- Leaky ReLU:如果担心死亡ReLU
- GELU:Transformer等现代架构
不推荐:
- Sigmoid/Tanh:深层网络容易梯度消失
输出层#
根据任务选择:
回归问题#
- 无激活函数 (线性)
- Sigmoid:输出在(0,1)
二分类问题#
- Sigmoid:输出概率
多分类问题#
- Softmax:输出概率分布
多标签分类#
- Sigmoid:每个类别独立
高级激活函数#
Swish函数#
公式#
$$ \text{Swish}(x) = x \cdot \sigma(x) = \frac{x}{1 + e^{-x}} $$特性#
- 在深度网络中表现优异
- 平滑且非单调
Mish函数#
公式#
$$ \text{Mish}(x) = x \cdot \tanh(\text{softplus}(x)) $$其中 $\text{softplus}(x) = \ln(1 + e^x)$ 。
特性#
- 在多个任务中表现优异
- 平滑且无界
Maxout#
公式#
$$ \text{Maxout}(x) = \max(w_1^T x + b_1, w_2^T x + b_2, \ldots, w_k^T x + b_k) $$特性#
- 学习激活函数
- 参数较多
代码实现#
PyTorch实现#
import torch
import torch.nn as nn
import torch.nn.functional as F
import matplotlib.pyplot as plt
import numpy as np
# 定义各种激活函数
x = torch.linspace(-5, 5, 100)
# Sigmoid
sigmoid = torch.sigmoid(x)
# 或
sigmoid = F.sigmoid(x)
# Tanh
tanh = torch.tanh(x)
# 或
tanh = F.tanh(x)
# ReLU
relu = F.relu(x)
# Leaky ReLU
leaky_relu = F.leaky_relu(x, negative_slope=0.01)
# ELU
elu = F.elu(x, alpha=1.0)
# GELU
gelu = F.gelu(x)
# Swish (需要手动实现)
swish = x * torch.sigmoid(x)
# Mish (需要手动实现)
mish = x * torch.tanh(F.softplus(x))可视化激活函数#
def plot_activation_functions():
x = torch.linspace(-5, 5, 100)
activations = {
'Sigmoid': torch.sigmoid(x),
'Tanh': torch.tanh(x),
'ReLU': F.relu(x),
'Leaky ReLU': F.leaky_relu(x, 0.01),
'ELU': F.elu(x),
'GELU': F.gelu(x),
}
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()
for idx, (name, y) in enumerate(activations.items()):
axes[idx].plot(x.numpy(), y.numpy())
axes[idx].set_title(name)
axes[idx].grid(True)
axes[idx].axhline(0, color='black', linewidth=0.5)
axes[idx].axvline(0, color='black', linewidth=0.5)
plt.tight_layout()
plt.show()
plot_activation_functions()在模型中使用#
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(784, 128)
self.fc2 = nn.Linear(128, 64)
self.fc3 = nn.Linear(64, 10)
self.activation = nn.ReLU() # 或 nn.LeakyReLU(), nn.GELU()等
def forward(self, x):
x = self.activation(self.fc1(x))
x = self.activation(self.fc2(x))
x = self.fc3(x) # 输出层通常不加激活(或加Softmax)
return x自定义激活函数#
class Swish(nn.Module):
def forward(self, x):
return x * torch.sigmoid(x)
class Mish(nn.Module):
def forward(self, x):
return x * torch.tanh(F.softplus(x))
# 使用
model = nn.Sequential(
nn.Linear(784, 128),
Swish(),
nn.Linear(128, 10)
)激活函数统计#
def analyze_activation(model, x):
"""分析激活函数的输出统计"""
activations = {}
def hook(name):
def hook_fn(module, input, output):
activations[name] = {
'mean': output.mean().item(),
'std': output.std().item(),
'min': output.min().item(),
'max': output.max().item(),
'dead_ratio': (output == 0).float().mean().item() # 对于ReLU
}
return hook_fn
# 注册hook
hooks = []
for name, module in model.named_modules():
if isinstance(module, (nn.ReLU, nn.LeakyReLU)):
hooks.append(module.register_forward_hook(hook(name)))
# 前向传播
_ = model(x)
# 打印统计
for name, stats in activations.items():
print(f"{name}:")
print(f" Mean: {stats['mean']:.4f}")
print(f" Std: {stats['std']:.4f}")
print(f" Range: [{stats['min']:.4f}, {stats['max']:.4f}]")
if 'dead_ratio' in stats:
print(f" Dead Ratio: {stats['dead_ratio']:.2%}")
# 移除hooks
for hook in hooks:
hook.remove()总结#
- 激活函数:引入非线性,使网络能够学习复杂模式
- Sigmoid/Tanh:传统激活函数,容易梯度消失
- ReLU:最常用,计算快速,但可能死亡
- Leaky ReLU/ELU:改进ReLU的变体
- GELU:现代架构常用
- 选择原则:隐藏层用ReLU,输出层根据任务选择
关键要点:
- 激活函数是神经网络非线性的来源
- ReLU是最常用的隐藏层激活函数
- 输出层激活函数取决于任务类型
- 注意梯度消失和死亡神经元问题