深入理解bp反向传播梯度:从数学原理到Python实现

1次阅读
没有评论

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

image.webp

反向传播(Backpropagation)是神经网络训练的基石,它通过高效计算梯度来指导参数更新。理解反向传播的关键在于掌握链式求导法则和矩阵运算的维度对齐。本文将从数学推导到代码实现,带你一步步攻克这个核心算法。

深入理解 bp 反向传播梯度:从数学原理到 Python 实现

数学原理拆解

链式求导示例(Sigmoid 函数)

假设神经网络使用 Sigmoid 激活函数:

$$\sigma(z) = \frac{1}{1+e^{-z}}$$

其导数为:

$$\sigma'(z) = \sigma(z)(1-\sigma(z))$$

当计算损失函数 $L$ 对权重 $w$ 的梯度时,需要逐层反向求导:

$$\frac{\partial L}{\partial w} = \frac{\partial L}{\partial a} \times \frac{\partial a}{\partial z} \times \frac{\partial z}{\partial w}$$

其中 $a=\sigma(z)$ 是激活值,$z=w^Tx+b$ 是线性变换结果。

矩阵求导维度对齐

在矩阵运算中,梯度维度必须与参数维度一致。例如:

  • 权重矩阵 $W$ 的维度为 $(n_{in}, n_{out})$
  • 梯度 $\frac{\partial L}{\partial W}$ 必须保持相同维度
  • 通过 $\delta^{l} = (W^{l+1})^T \delta^{l+1} \odot \sigma'(z^l)$ 反向传播误差

Python 实现详解

核心代码结构

import numpy as np

class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size):
        self.W1 = np.random.randn(input_size, hidden_size) * 0.01
        self.b1 = np.zeros(hidden_size)
        self.W2 = np.random.randn(hidden_size, output_size) * 0.01
        self.b2 = np.zeros(output_size)

    def sigmoid(self, x):
        return 1 / (1 + np.exp(-x))

    def forward(self, X):
        self.z1 = np.dot(X, self.W1) + self.b1
        self.a1 = self.sigmoid(self.z1)
        self.z2 = np.dot(self.a1, self.W2) + self.b2
        return self.z2

    def backward(self, X, y, output, learning_rate):
        m = X.shape[0]

        # 输出层梯度
        dz2 = output - y
        dW2 = np.dot(self.a1.T, dz2) / m
        db2 = np.sum(dz2, axis=0) / m

        # 隐藏层梯度
        dz1 = np.dot(dz2, self.W2.T) * self.a1 * (1 - self.a1)
        dW1 = np.dot(X.T, dz1) / m
        db1 = np.sum(dz1, axis=0) / m

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

梯度检查实现

def gradient_check(net, X, y, epsilon=1e-7):
    params = {'W1':net.W1, 'b1':net.b1, 'W2':net.W2, 'b2':net.b2}
    grads = {}

    # 计算数值梯度
    for key in params:
        param = params[key]
        grad = np.zeros_like(param)

        for i in range(param.shape[0]):
            for j in range(param.shape[1]):
                # 正向扰动
                param[i,j] += epsilon
                loss_plus = compute_loss(net.forward(X), y)

                # 负向扰动
                param[i,j] -= 2*epsilon
                loss_minus = compute_loss(net.forward(X), y)

                # 恢复原值
                param[i,j] += epsilon

                # 计算梯度
                grad[i,j] = (loss_plus - loss_minus) / (2*epsilon)

        grads[f'd{key}'] = grad

    return grads

避坑指南

梯度消失 / 爆炸处理

  • 现象:梯度指数级缩小 / 增大
  • 解决方案:
  • 使用 ReLU 等改进的激活函数
  • 初始化时控制权重范围(如 Xavier 初始化)
  • 添加梯度裁剪(Gradient Clipping)

学习率设置经验

  • 常用基准值:0.001~0.1
  • 监控训练损失曲线:
  • 如果损失波动大,适当降低学习率
  • 如果下降过慢,可尝试增大学习率
  • 进阶技巧:
  • 学习率衰减(Learning Rate Decay)
  • 自适应优化器(Adam 等)

思考与延伸

  1. 如何修改代码实现批量归一化(BatchNorm)?需要考虑哪些额外参数?
  2. 比较 ReLU、LeakyReLU、Tanh 等激活函数的梯度计算差异
  3. 如果使用二阶优化算法(如 L -BFGS),反向传播的实现需要做哪些调整?

通过这个完整的实现案例,相信你已经掌握了反向传播的核心要义。接下来可以尝试扩展到更复杂的网络结构,或者探索不同的优化策略。

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