BP神经网络梯度下降链式法则:从数学原理到Python实现

1次阅读
没有评论

共计 2539 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

1. 前向传播与反向传播的必要性

神经网络的核心是通过前向传播计算预测值,再通过反向传播调整参数。对于一个单隐藏层的全连接网络,前向传播公式为:

BP 神经网络梯度下降链式法则:从数学原理到 Python 实现

$$
\begin{aligned}
z^{[1]} &= W^{[1]}X + b^{[1]} \
a^{[1]} &= \sigma(z^{[1]}) \
z^{[2]} &= W^{[2]}a^{[1]} + b^{[2]} \
a^{[2]} &= \sigma(z^{[2]})
\end{aligned}
$$

其中 $\sigma$ 表示 Sigmoid 激活函数。反向传播的目标是通过链式法则计算损失函数 $L$ 对各个参数的梯度,从而更新参数。

2. 链式法则的数学推导

以单样本的平方误差损失 $L = \frac{1}{2}(y – a^{[2]})^2$ 为例,推导关键梯度:

  1. 输出层梯度:
    $$ \frac{\partial L}{\partial z^{[2]}} = -(y – a^{[2]}) \cdot \sigma'(z^{[2]}) $$

  2. 隐藏层梯度:
    $$ \frac{\partial L}{\partial z^{[1]}} = W^{[2]T} \frac{\partial L}{\partial z^{[2]}} \odot \sigma'(z^{[1]}) $$

  3. 参数梯度:
    $$
    \begin{aligned}
    \frac{\partial L}{\partial W^{[2]}} &= \frac{\partial L}{\partial z^{[2]}} a^{[1]T} \
    \frac{\partial L}{\partial b^{[2]}} &= \frac{\partial L}{\partial z^{[2]}} \
    \frac{\partial L}{\partial W^{[1]}} &= \frac{\partial L}{\partial z^{[1]}} X^T \
    \frac{\partial L}{\partial b^{[1]}} &= \frac{\partial L}{\partial z^{[1]}}
    \end{aligned}
    $$

其中 $\odot$ 表示逐元素相乘。

3. Python 实现代码

import numpy as np

# Sigmoid 激活函数及其导数
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sigmoid_derivative(x):
    s = sigmoid(x)
    return s * (1 - s)

# 网络参数初始化
input_size = 3
hidden_size = 4
output_size = 1

W1 = np.random.randn(hidden_size, input_size) * 0.01
b1 = np.zeros((hidden_size, 1))
W2 = np.random.randn(output_size, hidden_size) * 0.01
b2 = np.zeros((output_size, 1))

# 前向传播
def forward(X):
    Z1 = np.dot(W1, X) + b1
    A1 = sigmoid(Z1)
    Z2 = np.dot(W2, A1) + b2
    A2 = sigmoid(Z2)
    return Z1, A1, Z2, A2

# 反向传播
def backward(X, y, Z1, A1, Z2, A2):
    m = X.shape[1]

    # 输出层梯度
    dZ2 = (A2 - y) * sigmoid_derivative(Z2)
    dW2 = np.dot(dZ2, A1.T) / m
    db2 = np.sum(dZ2, axis=1, keepdims=True) / m

    # 隐藏层梯度
    dZ1 = np.dot(W2.T, dZ2) * sigmoid_derivative(Z1)
    dW1 = np.dot(dZ1, X.T) / m
    db1 = np.sum(dZ1, axis=1, keepdims=True) / m

    return dW1, db1, dW2, db2

# 训练循环
learning_rate = 0.1
for epoch in range(1000):
    # 假设 X, y 是训练数据
    Z1, A1, Z2, A2 = forward(X)
    dW1, db1, dW2, db2 = backward(X, y, Z1, A1, Z2, A2)

    # 参数更新
    W1 -= learning_rate * dW1
    b1 -= learning_rate * db1
    W2 -= learning_rate * dW2
    b2 -= learning_rate * db2

4. 避坑指南

梯度消失问题

当网络层数较深时,Sigmoid 函数的导数最大值仅为 0.25,多次连乘会导致梯度指数级减小。可以通过以下方法识别:

  • 监控各层梯度范数
  • 使用 ReLU 等替代激活函数

学习率设置

推荐采用以下策略:

  1. 初始学习率从 0.01 开始尝试
  2. 使用学习率衰减策略
  3. 结合验证集表现调整

数值梯度检验

实现梯度检查函数,比较解析梯度与数值梯度的差异:

def gradient_check(X, y, params, eps=1e-7):
    grad_approx = []
    for param in params:
        param_shape = param.shape
        param_flat = param.flatten()
        grad_flat = np.zeros_like(param_flat)

        for i in range(len(param_flat)):
            old_val = param_flat[i]

            param_flat[i] = old_val + eps
            loss_plus = compute_loss(X, y)

            param_flat[i] = old_val - eps
            loss_minus = compute_loss(X, y)

            grad_flat[i] = (loss_plus - loss_minus) / (2 * eps)
            param_flat[i] = old_val

        grad_approx.append(grad_flat.reshape(param_shape))
    return grad_approx

5. 思考题

  1. 卷积层的链式法则需要考虑局部连接和权值共享特性,如何修改梯度计算公式?
  2. ReLU 激活函数的导数为分段函数,这对梯度计算和网络训练有什么影响?
  3. 在批量训练时,如何有效并行化梯度计算过程?

通过本文的推导和实现,读者应该能够深入理解 BP 算法的核心机制,并具备实现基本神经网络的能力。建议动手实现代码并尝试不同的网络结构和超参数,以加深理解。

正文完
 0
评论(没有评论)