深入解析BP神经网络反向传播原理:从数学推导到代码实现

1次阅读
没有评论

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

image.webp

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

BP 神经网络通过前向传播计算预测值,再通过反向传播调整权重参数。前向传播的公式为:

深入解析 BP 神经网络反向传播原理:从数学推导到代码实现

$$
z^{(l)} = W^{(l)}a^{(l-1)} + b^{(l)}\
a^{(l)} = \sigma(z^{(l)})
$$

其中 $l$ 表示层数,$\sigma$ 为激活函数。前向传播只能得到预测结果,无法自动优化权重,因此需要通过反向传播计算梯度来更新参数。

链式法则的梯度计算

反向传播的核心是链式法则。以均方误差损失函数为例:

$$
\frac{\partial L}{\partial W^{(l)}} = \frac{\partial L}{\partial a^{(l)}} \frac{\partial a^{(l)}}{\partial z^{(l)}} \frac{\partial z^{(l)}}{\partial W^{(l)}}
$$

具体推导过程:

  1. 输出层误差:
    $$
    \delta^{(L)} = \frac{\partial L}{\partial a^{(L)}} \odot \sigma'(z^{(L)})
    $$

  2. 隐藏层误差:
    $$
    \delta^{(l)} = (W^{(l+1)T}\delta^{(l+1)}) \odot \sigma'(z^{(l)})
    $$

  3. 参数梯度:
    $$
    \frac{\partial L}{\partial W^{(l)}} = \delta^{(l)}a^{(l-1)T}\
    \frac{\partial L}{\partial b^{(l)}} = \delta^{(l)}
    $$

激活函数对比

常见激活函数的导数特性:

  • Sigmoid:
    $$
    \sigma'(z) = \sigma(z)(1-\sigma(z))
    $$
    容易导致梯度消失

  • ReLU:
    $$
    \sigma'(z) = \begin{cases}
    1 & z > 0\
    0 & z \leq 0
    \end{cases}
    $$
    缓解梯度消失但可能导致神经元死亡

  • LeakyReLU:
    $$
    \sigma'(z) = \begin{cases}
    1 & z > 0\
    \alpha & z \leq 0
    \end{cases}
    $$
    解决了神经元死亡问题

Python 实现

import numpy as np

class NeuralNetwork:
    def __init__(self, layers):
        # 初始化权重
        self.weights = [np.random.randn(y, x)/np.sqrt(x) 
                        for x, y in zip(layers[:-1], layers[1:])]
        self.biases = [np.random.randn(y, 1) for y in layers[1:]]

    def forward(self, x):
        # 前向传播
        a = x
        for w, b in zip(self.weights, self.biases):
            z = np.dot(w, a) + b
            a = self.sigmoid(z)
        return a

    def backward(self, x, y):
        # 反向传播
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        nabla_b = [np.zeros(b.shape) for b in self.biases]

        # 前向计算各层激活值
        activation = x
        activations = [x]
        zs = []
        for w, b in zip(self.weights, self.biases):
            z = np.dot(w, activation) + b
            zs.append(z)
            activation = self.sigmoid(z)
            activations.append(activation)

        # 输出层误差
        delta = (activations[-1] - y) * self.sigmoid_prime(zs[-1])
        nabla_b[-1] = delta
        nabla_w[-1] = np.dot(delta, activations[-2].T)

        # 隐藏层误差
        for l in range(2, len(self.weights)+1):
            z = zs[-l]
            sp = self.sigmoid_prime(z)
            delta = np.dot(self.weights[-l+1].T, delta) * sp
            nabla_b[-l] = delta
            nabla_w[-l] = np.dot(delta, activations[-l-1].T)

        return nabla_w, nabla_b

    def sigmoid(self, z):
        return 1.0/(1.0+np.exp(-z))

    def sigmoid_prime(self, z):
        return self.sigmoid(z)*(1-self.sigmoid(z))

训练技巧

  1. 学习率选择:
  2. 太大导致震荡不收敛
  3. 太小导致训练缓慢
  4. 建议初始值为 0.01,使用学习率衰减

  5. 权重初始化:

  6. Xavier 初始化:$W \sim N(0, \sqrt{\frac{2}{n_{in}+n_{out}}})$
  7. He 初始化:$W \sim N(0, \sqrt{\frac{2}{n_{in}}})$

  8. 批量归一化:
    $$
    \hat{x} = \frac{x – E[x]}{\sqrt{Var[x] + \epsilon}}\
    y = \gamma \hat{x} + \beta
    $$
    有效缓解梯度消失 / 爆炸

梯度问题分析

梯度消失的原因:
– 深度网络中层间梯度连续相乘
– 使用 Sigmoid 等导数小于 1 的函数

梯度爆炸的原因:
– 权重初始化值过大
– 网络层间梯度连续相乘

解决方案:
1. 使用 ReLU 等激活函数
2. 合理的权重初始化
3. 梯度裁剪
4. 残差连接

思考题

如何将反向传播算法扩展到卷积神经网络?考虑:
1. 卷积核权重的梯度计算
2. 池化层的反向传播
3. 参数共享机制的影响
4. 感受野与梯度传播的关系

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