ReLU、Sigmoid、Tanh与现代激活函数
前言
激活函数为神经网络引入非线性,使网络能够学习复杂的模式。选择合适的激活函数对模型性能和训练稳定性有重要影响。
为什么需要激活函数
线性变换的局限
多层线性变换等价于单层线性变换:
\[\mathbf{W}_2(\mathbf{W}_1 \mathbf{x}) = (\mathbf{W}_2 \mathbf{W}_1) \mathbf{x} = \mathbf{W} \mathbf{x}\]import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
# 线性变换示例
W1 = np.array([[1, 2], [3, 4]])
W2 = np.array([[5, 6], [7, 8]])
x = np.array([1, 1])
# 两次线性变换
result_sequential = W2 @ (W1 @ x)
# 等价于一次线性变换
W_combined = W2 @ W1
result_combined = W_combined @ x
print(f"两次变换结果: {result_sequential}")
print(f"合并变换结果: {result_combined}")
print(f"等价: {np.allclose(result_sequential, result_combined)}")
Sigmoid函数
定义
\[\sigma(z) = \frac{1}{1 + e^{-z}}\]性质
- 输出范围:$(0, 1)$
- 单调递增
- 导数:$\sigma’(z) = \sigma(z)(1 - \sigma(z))$
def sigmoid(z):
return 1 / (1 + np.exp(-np.clip(z, -500, 500)))
def sigmoid_derivative(z):
s = sigmoid(z)
return s * (1 - s)
# 可视化
z = np.linspace(-10, 10, 100)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Sigmoid函数
ax = axes[0]
ax.plot(z, sigmoid(z), 'b-', linewidth=2)
ax.axhline(y=0.5, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
ax.set_xlabel('z')
ax.set_ylabel('σ(z)')
ax.set_title('Sigmoid函数')
ax.grid(True, alpha=0.3)
ax.set_ylim(-0.1, 1.1)
# 导数
ax = axes[1]
ax.plot(z, sigmoid_derivative(z), 'r-', linewidth=2)
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
ax.set_xlabel('z')
ax.set_ylabel("σ'(z)")
ax.set_title('Sigmoid导数')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print(f"最大导数值: {sigmoid_derivative(0):.4f}")
问题
| 问题 | 描述 |
|---|---|
| 梯度消失 | 饱和区域梯度接近0 |
| 非零中心 | 输出总为正 |
| 计算开销 | exp计算相对较慢 |
Tanh函数
定义
\[\tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}} = 2\sigma(2z) - 1\]性质
- 输出范围:$(-1, 1)$
- 零中心
- 导数:$\tanh’(z) = 1 - \tanh^2(z)$
def tanh(z):
return np.tanh(z)
def tanh_derivative(z):
return 1 - np.tanh(z) ** 2
# 比较Sigmoid和Tanh
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 函数值
ax = axes[0]
ax.plot(z, sigmoid(z), 'b-', label='Sigmoid', linewidth=2)
ax.plot(z, tanh(z), 'r-', label='Tanh', linewidth=2)
ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
ax.set_xlabel('z')
ax.set_ylabel('f(z)')
ax.set_title('Sigmoid vs Tanh')
ax.legend()
ax.grid(True, alpha=0.3)
# 导数
ax = axes[1]
ax.plot(z, sigmoid_derivative(z), 'b-', label='Sigmoid导数', linewidth=2)
ax.plot(z, tanh_derivative(z), 'r-', label='Tanh导数', linewidth=2)
ax.set_xlabel('z')
ax.set_ylabel("f'(z)")
ax.set_title('导数比较')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
ReLU函数
定义
\[\text{ReLU}(z) = \max(0, z)\]性质
- 输出范围:$[0, +\infty)$
- 计算简单
- 稀疏激活
def relu(z):
return np.maximum(0, z)
def relu_derivative(z):
return (z > 0).astype(float)
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
ax = axes[0]
ax.plot(z, relu(z), 'g-', linewidth=2)
ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
ax.set_xlabel('z')
ax.set_ylabel('ReLU(z)')
ax.set_title('ReLU函数')
ax.grid(True, alpha=0.3)
ax = axes[1]
ax.plot(z, relu_derivative(z), 'g-', linewidth=2)
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
ax.set_xlabel('z')
ax.set_ylabel("ReLU'(z)")
ax.set_title('ReLU导数')
ax.grid(True, alpha=0.3)
ax.set_ylim(-0.1, 1.5)
plt.tight_layout()
plt.show()
ReLU的优点
| 优点 | 原因 |
|---|---|
| 计算高效 | 只需比较和取max |
| 缓解梯度消失 | 正区域梯度恒为1 |
| 稀疏激活 | 负值输出为0 |
死亡ReLU问题
当输入持续为负时,神经元”死亡”。
# 演示死亡ReLU问题
np.random.seed(42)
# 模拟一个神经元的权重更新
weights = 0.5
learning_rate = 0.1
inputs = np.random.randn(100) - 2 # 偏负的输入
outputs = []
for x in inputs:
z = weights * x
a = relu(z)
outputs.append(a)
# 假设梯度为1
grad = relu_derivative(z)
weights -= learning_rate * grad * x # 如果grad=0,权重不更新
print(f"ReLU输出中的零值比例: {sum(np.array(outputs) == 0) / len(outputs):.2%}")
Leaky ReLU
定义
\[\text{LeakyReLU}(z) = \begin{cases} z & z > 0 \\ \alpha z & z \leq 0 \end{cases}\]通常 $\alpha = 0.01$
def leaky_relu(z, alpha=0.01):
return np.where(z > 0, z, alpha * z)
def leaky_relu_derivative(z, alpha=0.01):
return np.where(z > 0, 1, alpha)
# 比较ReLU和Leaky ReLU
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(z, relu(z), 'g-', label='ReLU', linewidth=2)
ax.plot(z, leaky_relu(z), 'b-', label='Leaky ReLU (α=0.01)', linewidth=2)
ax.plot(z, leaky_relu(z, 0.2), 'r-', label='Leaky ReLU (α=0.2)', linewidth=2)
ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
ax.set_xlabel('z')
ax.set_ylabel('f(z)')
ax.set_title('ReLU vs Leaky ReLU')
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_xlim(-5, 5)
ax.set_ylim(-1.5, 5)
plt.show()
ELU(指数线性单元)
定义
\[\text{ELU}(z) = \begin{cases} z & z > 0 \\ \alpha(e^z - 1) & z \leq 0 \end{cases}\]def elu(z, alpha=1.0):
return np.where(z > 0, z, alpha * (np.exp(z) - 1))
def elu_derivative(z, alpha=1.0):
return np.where(z > 0, 1, elu(z, alpha) + alpha)
# 可视化
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(z, relu(z), 'g-', label='ReLU', linewidth=2)
ax.plot(z, leaky_relu(z, 0.1), 'b-', label='Leaky ReLU', linewidth=2)
ax.plot(z, elu(z), 'r-', label='ELU', linewidth=2)
ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
ax.set_xlabel('z')
ax.set_ylabel('f(z)')
ax.set_title('ReLU vs Leaky ReLU vs ELU')
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_xlim(-5, 5)
ax.set_ylim(-1.5, 5)
plt.show()
ELU的优点
- 负值有非零输出
- 均值更接近零
- 光滑可微
SELU(自归一化指数线性单元)
定义
\[\text{SELU}(z) = \lambda \begin{cases} z & z > 0 \\ \alpha(e^z - 1) & z \leq 0 \end{cases}\]其中 $\lambda \approx 1.0507$,$\alpha \approx 1.6733$
def selu(z):
alpha = 1.6732632423543772
scale = 1.0507009873554805
return scale * np.where(z > 0, z, alpha * (np.exp(z) - 1))
# 可视化
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(z, elu(z), 'b-', label='ELU', linewidth=2)
ax.plot(z, selu(z), 'r-', label='SELU', linewidth=2)
ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
ax.set_xlabel('z')
ax.set_ylabel('f(z)')
ax.set_title('ELU vs SELU')
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_xlim(-5, 5)
ax.set_ylim(-2, 5)
plt.show()
Swish / SiLU
定义
\[\text{Swish}(z) = z \cdot \sigma(z) = \frac{z}{1 + e^{-z}}\]def swish(z, beta=1.0):
return z * sigmoid(beta * z)
# 可视化
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(z, relu(z), 'g-', label='ReLU', linewidth=2)
ax.plot(z, swish(z), 'b-', label='Swish', linewidth=2)
ax.plot(z, swish(z, 0.5), 'r--', label='Swish (β=0.5)', linewidth=2)
ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
ax.set_xlabel('z')
ax.set_ylabel('f(z)')
ax.set_title('ReLU vs Swish')
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_xlim(-5, 5)
ax.set_ylim(-1, 5)
plt.show()
GELU(高斯误差线性单元)
定义
\[\text{GELU}(z) = z \cdot \Phi(z)\]其中 $\Phi(z)$ 是标准正态分布的CDF。
近似形式: \(\text{GELU}(z) \approx 0.5z(1 + \tanh[\sqrt{2/\pi}(z + 0.044715z^3)])\)
from scipy.stats import norm
def gelu_exact(z):
return z * norm.cdf(z)
def gelu_approx(z):
return 0.5 * z * (1 + np.tanh(np.sqrt(2/np.pi) * (z + 0.044715 * z**3)))
# 可视化
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(z, relu(z), 'g-', label='ReLU', linewidth=2)
ax.plot(z, gelu_exact(z), 'b-', label='GELU', linewidth=2)
ax.plot(z, swish(z), 'r--', label='Swish', linewidth=2)
ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
ax.set_xlabel('z')
ax.set_ylabel('f(z)')
ax.set_title('ReLU vs GELU vs Swish')
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_xlim(-5, 5)
ax.set_ylim(-1, 5)
plt.show()
GELU 在 BERT、GPT 等 Transformer 模型中广泛使用。
Softmax函数
定义
用于多分类输出层:
\[\text{Softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}}\]def softmax(z):
exp_z = np.exp(z - np.max(z)) # 数值稳定
return exp_z / exp_z.sum()
# 示例
logits = np.array([2.0, 1.0, 0.1])
probs = softmax(logits)
print("Logits:", logits)
print("Softmax输出:", probs)
print("概率和:", probs.sum())
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
ax = axes[0]
ax.bar(range(3), logits, alpha=0.7)
ax.set_xlabel('类别')
ax.set_ylabel('Logit值')
ax.set_title('原始Logits')
ax.set_xticks(range(3))
ax = axes[1]
ax.bar(range(3), probs, alpha=0.7, color='orange')
ax.set_xlabel('类别')
ax.set_ylabel('概率')
ax.set_title('Softmax输出')
ax.set_xticks(range(3))
plt.tight_layout()
plt.show()
激活函数比较
# 综合比较
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()
activations = {
'Sigmoid': sigmoid,
'Tanh': tanh,
'ReLU': relu,
'Leaky ReLU': lambda z: leaky_relu(z, 0.1),
'ELU': elu,
'GELU': gelu_exact
}
for ax, (name, func) in zip(axes, activations.items()):
ax.plot(z, func(z), 'b-', linewidth=2)
ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
ax.set_xlabel('z')
ax.set_ylabel('f(z)')
ax.set_title(name)
ax.grid(True, alpha=0.3)
ax.set_xlim(-5, 5)
ax.set_ylim(-2, 5)
plt.tight_layout()
plt.show()
选择指南
| 场景 | 推荐激活函数 |
|---|---|
| 隐藏层(默认) | ReLU |
| 深层网络 | Leaky ReLU, ELU |
| Transformer | GELU |
| 二分类输出 | Sigmoid |
| 多分类输出 | Softmax |
| 回归输出 | 线性(无激活) |
在PyTorch中使用
try:
import torch
import torch.nn as nn
# PyTorch激活函数
activations_torch = {
'Sigmoid': nn.Sigmoid(),
'Tanh': nn.Tanh(),
'ReLU': nn.ReLU(),
'LeakyReLU': nn.LeakyReLU(0.1),
'ELU': nn.ELU(),
'GELU': nn.GELU(),
'SELU': nn.SELU(),
'SiLU': nn.SiLU() # Swish
}
x = torch.linspace(-3, 3, 100)
print("PyTorch激活函数示例:")
for name, activation in activations_torch.items():
y = activation(x)
print(f" {name}: 输出范围 [{y.min():.3f}, {y.max():.3f}]")
except ImportError:
print("PyTorch未安装")
常见问题
Q1: 为什么ReLU比Sigmoid流行?
- 计算更快
- 缓解梯度消失
- 实践效果更好
Q2: 什么时候用Sigmoid/Tanh?
- Sigmoid:二分类输出层
- Tanh:RNN的隐藏状态(历史用法)
Q3: Leaky ReLU的α如何选择?
常用值:0.01-0.3,也可作为可学习参数(PReLU)。
Q4: GELU为什么在Transformer中流行?
- 光滑、非单调
- 理论上与Dropout有联系
- 实验效果好
总结
| 函数 | 范围 | 优点 | 缺点 |
|---|---|---|---|
| Sigmoid | (0,1) | 输出为概率 | 梯度消失 |
| Tanh | (-1,1) | 零中心 | 梯度消失 |
| ReLU | [0,∞) | 快速、简单 | 死亡神经元 |
| Leaky ReLU | (-∞,∞) | 避免死亡 | 需调α |
| GELU | ≈(-0.17,∞) | 光滑、现代 | 计算稍慢 |
参考资料
- Nair, V. & Hinton, G. (2010). “Rectified Linear Units Improve Restricted Boltzmann Machines”
- Hendrycks, D. & Gimpel, K. (2016). “Gaussian Error Linear Units (GELUs)”
- Ramachandran, P. et al. (2017). “Searching for Activation Functions”
- Klambauer, G. et al. (2017). “Self-Normalizing Neural Networks”
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:《 机器学习基础系列——激活函数 》
本文链接:http://localhost:3015/ai/%E6%BF%80%E6%B4%BB%E5%87%BD%E6%95%B0.html
本文最后一次更新为 天前,文章中的某些内容可能已过时!