激活函数的作用#

为什么需要激活函数?#

问题:没有激活函数的神经网络只是线性变换的组合:

$$ 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. 引入非线性:使网络能够学习复杂模式
  2. 决定输出范围:限制输出值域
  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  5

2. 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  5

4. 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()

总结#

  1. 激活函数:引入非线性,使网络能够学习复杂模式
  2. Sigmoid/Tanh:传统激活函数,容易梯度消失
  3. ReLU:最常用,计算快速,但可能死亡
  4. Leaky ReLU/ELU:改进ReLU的变体
  5. GELU:现代架构常用
  6. 选择原则:隐藏层用ReLU,输出层根据任务选择

关键要点

  • 激活函数是神经网络非线性的来源
  • ReLU是最常用的隐藏层激活函数
  • 输出层激活函数取决于任务类型
  • 注意梯度消失和死亡神经元问题