第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
| Function | Range | Max Gradient | Vanishing? | Default For |
|---|---|---|---|---|
| Sigmoid | (0,1) | 0.25 | Yes | Output binary |
| Tanh | (-1,1) | 1.0 | Yes | Legacy RNN |
| ReLU | [0,∞) | 1.0 | No (positive) | Hidden layers |
| Leaky ReLU | (-∞,∞) | 1.0/0.01 | No | Avoid dead ReLU |
| GELU | (-∞,∞) | ~1.0 | No | Transformers |
---
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.0000009510 层网络后,第一层权重几乎不更新!
各激活函数的缓解能力
| 激活函数 | 梯度特性 | 能否缓解梯度消失 |
|---|---|---|
| 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.00000095Gradient Explosion: If weights are too large:
(1.5)^10 ≈ 57.7Solutions: 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, ..., Kz = W·x + b,W是K × na_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
代码案例
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
中文解释
| 层位置 | 推荐激活 | 备选 |
|---|---|---|
| 隐藏层 | ReLU | Leaky ReLU, GELU |
| 输出层(二分类) | Sigmoid | — |
| 输出层(多分类) | Softmax | — |
| 输出层(回归) | 线性(无激活) | — |
| Transformer | GELU | Swish |
| 生成对抗网络 | Leaky ReLU | — |
一个常见错误
在隐藏层使用 Sigmoid:
- 深层网络中梯度会迅速消失
- 收敛极慢或完全停滞
- 绝对不要在隐藏层用 Sigmoid
English Explanation
| Layer | Recommended | Alternatives |
|---|---|---|
| Hidden | ReLU | Leaky ReLU, GELU |
| Output (binary) | Sigmoid | — |
| Output (multiclass) | Softmax | — |
| Output (regression) | Linear | — |
| Transformers | GELU | Swish |
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
- Dead ReLU 实验:生成数据使得某神经元输入恒为负,观察该神经元权重是否永远不再更新。
- 梯度传播模拟:假设 10 层网络,每层用 Sigmoid(最大梯度 0.25)vs ReLU(梯度 1.0)。计算反向传播到第一层时的梯度缩放因子。
- Softmax 温度:实现带温度参数 T 的 Softmax:
a_j = e^(z_j/T) / Σ e^(z_k/T)。观察 T→0 和 T→∞ 时概率分布的变化。
- GELU 实现:用
scipy.stats.norm.cdf实现 GELU,与 ReLU 在相同输入上对比输出曲线。
- 输出层设计:为一个"预测房价(连续值)+ 预测房型(3 类)+ 预测是否精装(2 类)"的多任务网络,设计输出层的结构、激活函数和损失函数组合。