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

本文最后一次更新为 天前,文章中的某些内容可能已过时!