SilverIce Toolbox
Back to course

Stage 2 / Chapter 7

第7章:激活函数与多分类 | Chapter 7: Activation Functions & Multiclass Classification

阶段定位 | Stage: 第二阶段 — 神经网络与进阶算法 预计学时 | Duration: 3~4 小时

---

学习目标 | Learning Objectives

中文:

  • 掌握 Sigmoid、Tanh、ReLU 及其变体的数学性质与适用场景
  • 理解梯度消失问题的成因与每种激活函数的缓解能力
  • 掌握 Softmax 的数学推导与数值稳定性实现
  • 理解多分类交叉熵损失与二分类的关系
  • 能根据任务需求选择合适的激活函数组合

English:

  • Master mathematical properties and use cases of Sigmoid, Tanh, ReLU and variants
  • Understand vanishing gradient causes and how each activation mitigates it
  • Master Softmax derivation and numerically stable implementation
  • Understand the relationship between multiclass and binary cross-entropy
  • Choose appropriate activation function combinations for different tasks

---

7.1 激活函数全景 | Activation Function Landscape

中文解释

Sigmoid

g(z) = 1 / (1 + e^(-z))
  • 输出范围:(0, 1)
  • 导数:g'(z) = g(z)(1-g(z))
  • 最大导数:0.25(在 z=0 处)
  • 问题:当 |z| > 5 时,梯度接近 0 → 梯度消失
  • 用途:输出层二分类

Tanh

g(z) = (e^z - e^(-z)) / (e^z + e^(-z))
  • 输出范围:(-1, 1),零中心化
  • 导数:g'(z) = 1 - g(z)²
  • 最大导数:1(在 z=0 处),比 Sigmoid 大 4 倍
  • 改进:零中心化让下一层输入均值为 0,收敛更快
  • 问题:仍有饱和,|z| > 3 时梯度消失
  • 用途:RNN 隐藏层(历史原因,现已被 ReLU 替代)

ReLU

g(z) = max(0, z)
  • 输出范围:[0, +∞)
  • 导数:z > 0 时为 1,z < 0 时为 0
  • 优点

- 计算极快(只需比较) - 正区间梯度恒为 1,永不饱和

  • 缺点

- Dead ReLU:若输入恒为负,梯度永远为 0,神经元"死亡" - 非零中心化

  • 用途隐藏层默认选择

Leaky ReLU

g(z) = max(αz, z)   # α 通常取 0.01
  • 负区间有一个小的斜率 α
  • 解决 Dead ReLU 问题

Swish / GELU

Swish(z) = z * sigmoid(z)
GELU(z) = z * Φ(z)   # Φ 是标准正态的 CDF
  • Transformer 时代的默认选择(GPT、BERT 用 GELU)
  • 平滑、处处可导、负区间有微小响应
  • 计算代价略高于 ReLU

English Explanation

FunctionRangeMax GradientVanishing?Default For
Sigmoid(0,1)0.25YesOutput binary
Tanh(-1,1)1.0YesLegacy RNN
ReLU[0,∞)1.0No (positive)Hidden layers
Leaky ReLU(-∞,∞)1.0/0.01NoAvoid dead ReLU
GELU(-∞,∞)~1.0NoTransformers

---

7.2 梯度消失问题详解 | Vanishing Gradient Deep Dive

中文解释

问题本质

在深层网络中,反向传播时梯度要逐层相乘:

∂L/∂W₁ = ∂L/∂a_L * ∂a_L/∂z_L * ... * ∂a_1/∂z_1 * ∂z_1/∂W₁

如果每一层的梯度都小于 1(如 Sigmoid 最大 0.25),乘积会指数级衰减:

(0.25)^10 ≈ 0.00000095

10 层网络后,第一层权重几乎不更新!

各激活函数的缓解能力

激活函数梯度特性能否缓解梯度消失
Sigmoid最大 0.25❌ 差
Tanh最大 1.0⚠️ 一般(仍会饱和)
ReLU正区间 = 1.0✅ 好(正区间永不饱和)
ResNet 跳跃连接旁路梯度✅ 极好(绕过乘法链)

梯度爆炸

如果权重初始化太大,梯度会指数级增长:

(1.5)^10 ≈ 57.7

解决:权重初始化规范化(Xavier / He 初始化)、梯度裁剪。

English Explanation

The Problem: Backpropagation multiplies gradients layer by layer. If each < 1, the product decays exponentially:

(0.25)^10 ≈ 0.00000095

Gradient Explosion: If weights are too large:

(1.5)^10 ≈ 57.7

Solutions: Xavier/He initialization, gradient clipping.

---

7.3 Softmax 与多分类 | Softmax & Multiclass

中文解释

从二分类到多分类

二分类:输出一个概率 P(y=1|x) 多分类:输出 K 个概率,和为 1

Softmax 定义

a_j = e^(z_j) / Σ_k e^(z_k)    for j = 1, ..., K
  • z = W·x + bWK × n
  • a_j:类别 j 的概率
  • Σ a_j = 1

数值稳定性

直接计算 e^(1000) 会溢出。减最大值:

a_j = e^(z_j - max(z)) / Σ_k e^(z_k - max(z))

分子分母同除以 e^max(z),数学等价但数值稳定。

多分类交叉熵

L = -Σ_j y_j log(a_j)

其中 y 是 One-Hot 向量(只有一个位置为 1)。

与二分类的关系

当 K=2 时,Softmax 退化为 Sigmoid:

a_1 = e^(z_1) / (e^(z_1) + e^(z_2))
    = 1 / (1 + e^(z_2 - z_1))
    = sigmoid(z_1 - z_2)

English Explanation

Numerical Stability:

a_j = e^(z_j - max(z)) / Σ_k e^(z_k - max(z))

Connection to Binary: When K=2, Softmax reduces to Sigmoid:

a_1 = sigmoid(z_1 - z_2)

---

7.4 完整实现:激活函数可视化与 Softmax

代码案例

python
import numpy as np
import matplotlib.pyplot as plt

# ========== 1. 激活函数可视化 ==========
x = np.linspace(-5, 5, 200)

fig, axes = plt.subplots(2, 3, figsize=(14, 8))

# Sigmoid
axes[0,0].plot(x, 1/(1+np.exp(-x)), 'b-', linewidth=2)
axes[0,0].axhline(0.5, color='r', linestyle='--', alpha=0.5)
axes[0,0].set_title('Sigmoid: g(z)=1/(1+e^{-z})')
axes[0,0].set_ylim(-0.1, 1.1)
axes[0,0].grid(True, alpha=0.3)
# 标注饱和区
axes[0,0].axvspan(-5, -3, alpha=0.2, color='red', label='Vanishing zone')
axes[0,0].axvspan(3, 5, alpha=0.2, color='red')
axes[0,0].legend()

# Tanh
axes[0,1].plot(x, np.tanh(x), 'b-', linewidth=2)
axes[0,1].axhline(0, color='r', linestyle='--', alpha=0.5)
axes[0,1].set_title('Tanh: (-1, 1), zero-centered')
axes[0,1].set_ylim(-1.1, 1.1)
axes[0,1].grid(True, alpha=0.3)

# ReLU
axes[0,2].plot(x, np.maximum(0, x), 'b-', linewidth=2)
axes[0,2].set_title('ReLU: max(0, z)')
axes[0,2].set_ylim(-1, 5)
axes[0,2].grid(True, alpha=0.3)

# Leaky ReLU
axes[1,0].plot(x, np.where(x > 0, x, 0.01*x), 'b-', linewidth=2)
axes[1,0].set_title('Leaky ReLU: α=0.01')
axes[1,0].set_ylim(-1, 5)
axes[1,0].grid(True, alpha=0.3)

# 导数对比
sigmoid_deriv = lambda z: (1/(1+np.exp(-z))) * (1 - 1/(1+np.exp(-z)))
tanh_deriv = lambda z: 1 - np.tanh(z)**2
relu_deriv = lambda z: np.where(z > 0, 1, 0)

axes[1,1].plot(x, sigmoid_deriv(x), label='Sigmoid', linewidth=2)
axes[1,1].plot(x, tanh_deriv(x), label='Tanh', linewidth=2)
axes[1,1].plot(x, relu_deriv(x), label='ReLU', linewidth=2)
axes[1,1].set_title('Derivatives Comparison')
axes[1,1].set_ylim(-0.1, 1.1)
axes[1,1].legend()
axes[1,1].grid(True, alpha=0.3)

# Swish
def swish(z):
    return z * (1 / (1 + np.exp(-np.clip(z, -500, 500))))
axes[1,2].plot(x, swish(x), 'b-', linewidth=2)
axes[1,2].plot(x, np.maximum(0, x), 'r--', alpha=0.5, label='ReLU')
axes[1,2].set_title('Swish: z·sigmoid(z)')
axes[1,2].legend()
axes[1,2].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('ch07_activations.png')
print("激活函数可视化已保存")

# ========== 2. Softmax 数值稳定性 ==========
def softmax_naive(z):
    """不稳定的 Softmax"""
    exp_z = np.exp(z)
    return exp_z / np.sum(exp_z)

def softmax_stable(z):
    """数值稳定的 Softmax"""
    exp_z = np.exp(z - np.max(z))
    return exp_z / np.sum(exp_z)

logits = np.array([1000.0, 1001.0, 1002.0])

print("\n=== Softmax 数值稳定性对比 ===")
print(f"输入 logits: {logits}")

try:
    naive = softmax_naive(logits)
    print(f"Naive Softmax: {naive}")
except OverflowError:
    print("Naive Softmax: 溢出!")

stable = softmax_stable(logits)
print(f"Stable Softmax: {stable}")
print(f"概率之和: {stable.sum():.4f}")
print(f"预测类别: {np.argmax(stable)}")

# ========== 3. 多分类交叉熵 ==========
# 3 类分类,真实类别是 1(One-Hot: [0, 1, 0])
logits_3 = np.array([2.0, 1.0, 0.1])
probs = softmax_stable(logits_3)
print(f"\n3 类 Softmax 概率: {probs.round(4)}")

y_true = np.array([0, 1, 0])
loss = -np.sum(y_true * np.log(probs + 1e-8))
print(f"多分类交叉熵损失: {loss:.4f}")

输出:

Naive Softmax: [inf inf inf]  ← 溢出!
Stable Softmax: [0.0900 0.2447 0.6652]
概率之和: 1.0000
预测类别: 2

3 类 Softmax 概率: [0.6590 0.2424 0.0986]
多分类交叉熵损失: 1.4170

---

7.5 激活函数选择指南 | Choosing Activations

中文解释

层位置推荐激活备选
隐藏层ReLULeaky ReLU, GELU
输出层(二分类)Sigmoid
输出层(多分类)Softmax
输出层(回归)线性(无激活)
TransformerGELUSwish
生成对抗网络Leaky ReLU

一个常见错误

在隐藏层使用 Sigmoid:

  • 深层网络中梯度会迅速消失
  • 收敛极慢或完全停滞
  • 绝对不要在隐藏层用 Sigmoid

English Explanation

LayerRecommendedAlternatives
HiddenReLULeaky ReLU, GELU
Output (binary)Sigmoid
Output (multiclass)Softmax
Output (regression)Linear
TransformersGELUSwish

Never use Sigmoid in hidden layers — it causes severe vanishing gradients.

---

本章总结 | Chapter Summary

中文:

  • Sigmoid 输出 (0,1),但最大梯度仅 0.25,深层网络中梯度消失严重
  • Tanh 输出 (-1,1) 零中心化,最大梯度 1.0,但仍会饱和
  • ReLU 计算快、正区间梯度恒为 1,是隐藏层默认选择
  • Dead ReLU 问题可用 Leaky ReLU 或 GELU 缓解
  • Softmax 是多分类的标准输出,必须减最大值保证数值稳定
  • 隐藏层默认 ReLU,输出层根据任务选 Sigmoid/Softmax/Linear

English:

  • Sigmoid outputs (0,1) but max gradient is 0.25 — severe vanishing in deep nets
  • Tanh is zero-centered with max gradient 1.0, but still saturates
  • ReLU is fast with constant gradient 1.0 in positive region — default for hidden layers
  • Dead ReLU solved by Leaky ReLU or GELU
  • Softmax is standard for multiclass; subtract max for numerical stability
  • Hidden: ReLU; Output: choose based on task

---

课后练习 | Homework

  1. Dead ReLU 实验:生成数据使得某神经元输入恒为负,观察该神经元权重是否永远不再更新。
  1. 梯度传播模拟:假设 10 层网络,每层用 Sigmoid(最大梯度 0.25)vs ReLU(梯度 1.0)。计算反向传播到第一层时的梯度缩放因子。
  1. Softmax 温度:实现带温度参数 T 的 Softmax:a_j = e^(z_j/T) / Σ e^(z_k/T)。观察 T→0 和 T→∞ 时概率分布的变化。
  1. GELU 实现:用 scipy.stats.norm.cdf 实现 GELU,与 ReLU 在相同输入上对比输出曲线。
  1. 输出层设计:为一个"预测房价(连续值)+ 预测房型(3 类)+ 预测是否精装(2 类)"的多任务网络,设计输出层的结构、激活函数和损失函数组合。