第 5 课
← 返回系列列表

第5周:神经网络的学习

吴恩达机器学习 (2014) — 交互式学习笔记

反向传播算法、梯度检验、参数随机初始化、神经网络训练流程。

第5周:神经网络的学习

课程简介

反向传播算法、梯度检验、参数随机初始化、神经网络训练流程。

🎬 本课程视频:吴恩达机器学习 (2014) — 交互式学习笔记


一、神经网络的代价函数

1.1 记号回顾

在详细讨论反向传播之前,先统一记号:
- L:神经网络的总层数
- s_l:第 l 层的神经元个数(不计数偏置单元)
- K:输出层神经元个数(分类类别数)
- h_Θ(x)_k:第 k 个输出神经元的预测值
- y:标签向量,y_k ∈ {0, 1} 表示是否属于第 k 类

1.2 代价函数定义

对于一个多分类神经网络,代价函数是逻辑回归中交叉熵损失的推广:

$$J(\Theta) = -\frac{1}{m} \sum_{i=1}^{m} \sum_{k=1}^{K} [y_k^{(i)} \log (h_\Theta(x^{(i)})k) + (1-y_k^{(i)}) \log (1 - h\Theta(x^{(i)})k)] + \frac{\lambda}{2m} \sum{l=1}^{L-1} \sum_{i=1}^{s_l} \sum_{j=1}^{s_{l+1}} (\Theta_{ji}^{(l)})^2$$

这个公式虽然看起来复杂,但可以分解理解:
1. 求和符号 Σᵢ 遍历所有 m 个训练样本
2. 求和符号 Σₖ 遍历所有 K 个输出类别
3. 内部是每个输出节点的交叉熵损失
4. 最后一项是正则化项——对所有层、所有权重的平方和

二、反向传播算法

2.1 反向传播的核心思想

反向传播(Backpropagation)是神经网络学习最核心的算法。它的目标是高效地计算代价函数对所有参数的偏导数。

核心思想:通过链式法则,将输出层的误差信号逐层向后传播,每一层利用后一层传来的误差计算本层参数的梯度。

直观理解:如果输出层第 k 个神经元预测错误,这个错误信号会沿着网络连接反向传播——告诉前一层你给我的输入不准确,需要调整——层层递推直到输入层。

2.2 反向传播的四个核心公式

定义 δ_j^{(l)} 为第 l 层第 j 个神经元的误差。

输出层误差:

$$\delta^{(L)} = a^{(L)} - y$$

对于 K 类分类,这是一个 K 维向量——每个元素是预测值与真实值的差。

反向传播误差:

$$\delta^{(l)} = (\Theta^{(l)})^T \delta^{(l+1)} \odot g'(z^{(l)})$$

其中 ⊙ 是逐元素乘法,g'(z^{(l)}) 是激活函数的导数。对于 Sigmoid 函数:g'(z^{(l)}) = a^{(l)} ⊙ (1 - a^{(l)})。

梯度计算:

$$\frac{\partial}{\partial \Theta_{ij}^{(l)}} J(\Theta) = a_j^{(l)} \delta_i^{(l+1)}$$

2.3 反向传播的算法步骤

  1. 初始化 Δ_{ij}^{(l)} = 0(所有梯度累加器)
  2. 对于每个训练样本 t = 1 到 m:
    a. 设 a^{(1)} = x^{(t)}
    b. 执行前向传播,计算每层的 a^{(l)}
    c. 计算输出层误差 δ^{(L)} = a^{(L)} - y^{(t)}
    d. 从后向前逐层计算 δ^{(L-1)}, δ^{(L-2)}, ..., δ^{(2)}
    e. 累加梯度:Δ_{ij}^{(l)} += a_j^{(l)} δ_i^{(l+1)}
  3. 计算正则化梯度

三、梯度检验

反向传播的实现非常容易出错。一个微小的 bug 可能导致梯度计算错误,而错误计算的梯度仍然能让代价函数下降——只是训练出来的模型性能很差。

梯度检验(Gradient Checking)提供了一个简单的验证方法:用数值方法近似计算梯度。

利用导数的定义:

$$\frac{d}{d\theta} J(\theta) \approx \frac{J(\theta + \epsilon) - J(\theta - \epsilon)}{2\epsilon}$$

其中 ε 是一个非常小的数(通常取 10⁻⁴)。双边差分方法比单边差分更精确。

def check_gradient(theta, epsilon=1e-4):
    num_grad = np.zeros_like(theta)
    for i in range(len(theta)):
        theta_plus = np.copy(theta)
        theta_plus[i] += epsilon
        theta_minus = np.copy(theta)
        theta_minus[i] -= epsilon
        num_grad[i] = (cost_func(theta_plus) - cost_func(theta_minus)) / (2*epsilon)
    return num_grad

对比数值梯度与反向传播梯度,检查相对误差。如果 diff < 10⁻⁹,说明反向传播实现正确。

重要警告:梯度检验非常慢——因为需要为每个参数计算两次代价函数。验证正确后一定要关闭梯度检验。

四、参数的随机初始化

4.1 为什么不能全零初始化

如果所有权重初始化为零,同一层所有神经元在每次迭代中都会计算完全相同的梯度——它们学到的特征永远相同。这就是对称性问题。无论网络有多少个神经元,都退化为只有一个神经元的网络。

4.2 随机初始化的策略

解决方案是对权重进行随机初始化,打破对称性。

$$\Theta_{ij}^{(l)} \in [-\epsilon_{init}, \epsilon_{init}]$$

选择一个 ε 值(如 0.12),在 [-ε, ε] 范围内均匀随机采样。

epsilon_init = 0.12
Theta1 = np.random.rand(hidden_size, input_size+1) * 2 * epsilon_init - epsilon_init
Theta2 = np.random.rand(num_labels, hidden_size+1) * 2 * epsilon_init - epsilon_init

五、神经网络的完整训练流程

  1. 选择网络结构:输入层单元数 = 特征维度,输出层单元数 = 类别数,隐藏层通常多于特征数
  2. 随机初始化权重:打破对称性
  3. 实现前向传播:计算 h_Θ(x) 和代价函数 J(Θ)
  4. 实现反向传播:计算偏导数
  5. 梯度检验:验证反向传播的正确性,然后关闭
  6. 使用优化器最小化 J(Θ):梯度下降或高级优化器
# TensorFlow/Keras 实现
from tensorflow import keras

model = keras.Sequential([
    keras.layers.Dense(25, activation='relu', input_shape=(n,)),
    keras.layers.Dense(25, activation='relu'),
    keras.layers.Dense(K, activation='sigmoid')
])

model.compile(optimizer='adam',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
model.fit(X, y, epochs=100, batch_size=32, validation_split=0.2)

第 5 周是课程中技术性最强、也最令人兴奋的部分。反向传播是神经网络学习的核心引擎——理解它,你就真正理解了神经网络是如何工作的。虽然现代深度学习框架自动处理了反向传播,但理解它的原理对于调试模型、设计新的网络结构至关重要。梯度检验是保护你免受实现 bug 困扰的安全网。而随机初始化则是打破对称性、让网络真正开始学习的触发器。

反向传播的详细推导

反向传播的核心是链式法则(Chain Rule)在神经网络中的系统化应用。对于单个训练样本,输出层第 k 个神经元的误差 δ_k^{(L)} = a_k^{(L)} - y_k 来源于代价函数对该神经元的偏导。

对于隐藏层,误差传播公式 δ^{(l)} = (Θ^{(l)})ᵀ δ^{(l+1)} ⊙ g'(z^{(l)}) 可以分解为:
1. (Θ^{(l)})ᵀ δ^{(l+1)}:将后一层的误差加权求和(按权重反向传播)
2. ⊙ g'(z^{(l)}):乘以激活函数的导数(考虑本层的梯度衰减)

忘记实现细节后,反向传播的直观理解就是:如果输出层的预测有误差,这个误差按照连接权重的大小比例,反向分配给前一层各神经元——谁的权重大谁就承担更多的责任。

参数更新计算示例

以一个 3 层网络(2 个输入、3 个隐藏、1 个输出)为例,前向传播和反向传播的计算过程:

前向传播:
1. z₁² = Θ₁₀¹·x₀ + Θ₁₁¹·x₁ + Θ₁₂¹·x₂,a₁² = g(z₁²)
2. z₂² = Θ₂₀¹·x₀ + Θ₂₁¹·x₁ + Θ₂₂¹·x₂,a₂² = g(z₂²)
3. z₃² = Θ₃₀¹·x₀ + Θ₃₁¹·x₁ + Θ₃₂¹·x₂,a₃² = g(z₃²)
4. z₁³ = Θ₁₀²·a₀² + Θ₁₁²·a₁² + Θ₁₂²·a₂² + Θ₁₃²·a₃²,a₁³ = σ(z₁³) = ŷ

反向传播:
1. δ₁³ = a₁³ - y
2. δ₁² = Θ₁₁²·δ₁³ · g'(z₁²)
3. δ₂² = Θ₁₂²·δ₁³ · g'(z₂²)
4. δ₃² = Θ₁₃²·δ₁³ · g'(z₃²)
5. ∂J/∂Θ₁₁² = a₁² · δ₁³

梯度检验的详细实现

梯度检验是反向传播正确性的必要保障。以下是一个完整的检验实现:

def gradient_checking(theta, X, y, epsilon=1e-4):
    # 计算反向传播梯度
    grad_bp = backpropagation(theta, X, y)

    # 计算数值梯度
    grad_num = np.zeros_like(theta)
    for i in range(len(theta)):
        theta_plus = theta.copy()
        theta_plus[i] += epsilon
        theta_minus = theta.copy()
        theta_minus[i] -= epsilon

        loss_plus = cost_function(theta_plus, X, y)
        loss_minus = cost_function(theta_minus, X, y)
        grad_num[i] = (loss_plus - loss_minus) / (2 * epsilon)

    # 计算相对误差
    diff = np.linalg.norm(grad_bp - grad_num) / (np.linalg.norm(grad_bp) + np.linalg.norm(grad_num))
    return diff

# 如果 diff < 1e-9,反向传播正确

梯度消失与梯度爆炸

梯度消失(Vanishing Gradient)和梯度爆炸(Exploding Gradient)是深度网络训练中的核心难题:
- 梯度消失:浅层梯度接近 0,参数几乎不更新。原因:激活函数饱和区梯度 < 1,连乘后指数衰减
- 梯度爆炸:浅层梯度极大,训练不稳定。原因:权重初始化过大

解决方案:合理的权重初始化(Xavier、He)、使用 ReLU 类激活函数、Batch Normalization、残差连接(ResNet)。

神经网络的超参数选择

延伸阅读

← 第4周:神经网络基础 第6周:应用机器学习的建议 →