深入解析BP反向传播算法:从数学原理到实现细节

1次阅读
没有评论

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

image.webp

BP 算法的核心地位

反向传播(Backpropagation,BP)是深度学习模型训练的基石算法。它通过高效计算梯度,使得多层神经网络的参数优化成为可能。没有 BP 算法,现代深度学习的爆发式发展将无从谈起。

深入解析 BP 反向传播算法:从数学原理到实现细节

数学原理推导

前向传播的矩阵表示

对于一个 L 层全连接网络,第 l 层的输出可表示为:

$$
\mathbf{h}^{(l)} = f(\mathbf{z}^{(l)}) = f(\mathbf{W}^{(l)}\mathbf{h}^{(l-1)} + \mathbf{b}^{(l)})
$$

其中 $\mathbf{W}^{(l)}$ 是权重矩阵,$\mathbf{b}^{(l)}$ 是偏置向量,$f$ 是激活函数。

损失函数的偏导推导

以均方误差损失为例,对权重 $W_{ij}^{(l)}$ 的偏导计算过程:

  1. 计算输出层误差:
    $$
    \delta^{(L)} = \frac{\partial L}{\partial \mathbf{z}^{(L)}} = (\mathbf{h}^{(L)} – \mathbf{y}) \odot f'(\mathbf{z}^{(L)})
    $$

  2. 反向传播误差(链式法则):
    $$
    \delta^{(l)} = ((\mathbf{W}^{(l+1)})^T \delta^{(l+1)}) \odot f'(\mathbf{z}^{(l)})
    $$

  3. 计算权重梯度:
    $$
    \frac{\partial L}{\partial W_{ij}^{(l)}} = h_j^{(l-1)}\delta_i^{(l)}
    $$

激活函数导数的作用

常用激活函数导数示例:

  • Sigmoid: $\sigma'(z) = \sigma(z)(1-\sigma(z))$
  • ReLU: $\text{ReLU}'(z) = \begin{cases} 1 & \text{if} z > 0 \ 0 & \text{otherwise} \end{cases}$

Python 实现

import numpy as np

class ThreeLayerNet:
    def __init__(self, input_size, hidden_size, output_size):
        # He 初始化
        self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(2/input_size)
        self.b1 = np.zeros(hidden_size)
        self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(2/hidden_size)
        self.b2 = np.zeros(output_size)

    def forward(self, x):
        self.z1 = np.dot(x, self.W1) + self.b1
        self.h1 = np.maximum(0, self.z1)  # ReLU
        self.z2 = np.dot(self.h1, self.W2) + self.b2
        self.h2 = 1/(1+np.exp(-self.z2))  # Sigmoid
        return self.h2

    def backward(self, x, y, lr=0.01):
        # 输出层误差
        d_z2 = (self.h2 - y) * self.h2 * (1 - self.h2)  # 含 Sigmoid 导数

        # 隐藏层误差
        d_h1 = np.dot(d_z2, self.W2.T)
        d_z1 = d_h1 * (self.z1 > 0)  # ReLU 导数

        # 更新参数
        self.W2 -= lr * np.dot(self.h1.T, d_z2)
        self.b2 -= lr * np.sum(d_z2, axis=0)
        self.W1 -= lr * np.dot(x.T, d_z1)
        self.b1 -= lr * np.sum(d_z1, axis=0)

工程实践

优化器比较

# SGD with Momentum
v_W = 0
beta = 0.9
v_W = beta * v_W + (1-beta) * dW
W = W - lr * v_W

# Adam 实现
m_W = 0
v_W = 0
beta1 = 0.9
beta2 = 0.999
eps = 1e-8

m_W = beta1*m_W + (1-beta1)*dW
v_W = beta2*v_W + (1-beta2)*(dW**2)
m_hat = m_W/(1-beta1**t)
v_hat = v_W/(1-beta2**t)
W = W - lr*m_hat/(np.sqrt(v_hat)+eps)

梯度裁剪

grad_norm = np.sqrt(sum(np.sum(g**2) for g in grads))
max_norm = 5.0
if grad_norm > max_norm:
    ratio = max_norm / grad_norm
    grads = [g*ratio for g in grads]

学习率衰减

initial_lr = 0.1
decay_rate = 0.95
epoch = 100
lr = initial_lr * (decay_rate ** epoch)

避坑指南

  1. 权重初始化
  2. 使用 Xavier/Glorot 初始化(Sigmoid)
  3. 使用 He 初始化(ReLU)

  4. 激活函数选择

  5. 隐藏层优先使用 ReLU/LeakyReLU
  6. 输出层根据任务选择(Sigmoid 二分类,Softmax 多分类)

  7. 批量归一化

    # 在激活函数前插入
    batch_mean = np.mean(z, axis=0)
    batch_var = np.var(z, axis=0)
    z_hat = (z - batch_mean) / np.sqrt(batch_var + eps)
    out = gamma * z_hat + beta

思考问题

  1. 当网络层数很深时,除了 ReLU 还有哪些方法可以缓解梯度消失问题?
  2. 为什么 Adam 优化器在实际应用中通常比 SGD 表现更好?背后的数学原理是什么?
  3. 在移动端部署模型时,如何在不显著影响精度的情况下简化 BP 计算过程?
正文完
 0
评论(没有评论)