BP神经网络公式推导:从数学原理到代码实现

1次阅读
没有评论

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

image.webp

背景介绍

BP(Backpropagation)神经网络是深度学习的基础,广泛应用于图像识别、自然语言处理等领域。它通过误差反向传播算法调整网络参数,使预测结果逼近真实值。理解 BP 算法的数学本质,能帮助开发者更好地设计网络结构和调参。

BP 神经网络公式推导:从数学原理到代码实现

数学推导

前向传播的矩阵表示

设神经网络有 $L$ 层,第 $l$ 层的权重矩阵为 $W^l$,偏置为 $b^l$,激活函数为 $\sigma(\cdot)$。前向传播过程可表示为:

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

其中 $a^0$ 即为输入数据。

损失函数定义

采用均方误差(MSE)作为损失函数:

$$
J = \frac{1}{2m}\sum_{i=1}^m (y_i – a^L_i)^2
$$

$m$ 为样本数量,$y$ 为真实标签。

链式法则求导

输出层梯度

首先计算输出层误差 $\delta^L$:

$$
\delta^L = \frac{\partial J}{\partial z^L} = (a^L – y) \odot \sigma'(z^L)
$$

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

隐藏层梯度

通过链式法则逐层反向传播误差:

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

参数更新

最终得到权重和偏置的梯度:

$$
\frac{\partial J}{\partial W^l} = \delta^l (a^{l-1})^T \
\frac{\partial J}{\partial b^l} = \delta^l
$$

Python 实现

import numpy as np

class BPNetwork:
    def __init__(self, layers, learning_rate=0.01):
        self.weights = [np.random.randn(y, x) * 0.1 
                        for x, y in zip(layers[:-1], layers[1:])]
        self.biases = [np.zeros((y, 1)) for y in layers[1:]]
        self.lr = learning_rate

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

    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):
        # 前向传播并保存中间结果
        activations = [x]
        zs = []
        for w, b in zip(self.weights, self.biases):
            z = np.dot(w, activations[-1]) + b
            zs.append(z)
            activations.append(self.sigmoid(z))

        # 反向传播
        delta = (activations[-1] - y) * activations[-1] * (1 - activations[-1])
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        nabla_b = [np.zeros(b.shape) for b in self.biases]

        nabla_w[-1] = np.dot(delta, activations[-2].T)
        nabla_b[-1] = delta

        for l in range(2, len(self.weights)+1):
            z = zs[-l]
            sp = self.sigmoid(z) * (1 - self.sigmoid(z))
            delta = np.dot(self.weights[-l+1].T, delta) * sp
            nabla_w[-l] = np.dot(delta, activations[-l-1].T)
            nabla_b[-l] = delta

        # 参数更新
        self.weights = [w - self.lr * nw 
                        for w, nw in zip(self.weights, nabla_w)]
        self.biases = [b - self.lr * nb 
                       for b, nb in zip(self.biases, nabla_b)]

实验分析

学习率影响

通过实验发现:

  1. 学习率过大(如 >0.1)会导致损失震荡不收敛
  2. 学习率过小(如 <0.001)会显著降低训练速度
  3. 自适应学习率策略(如 Adam)能平衡收敛速度和稳定性

激活函数对比

测试不同激活函数在 MNIST 数据集上的表现:

  • Sigmoid:容易出现梯度消失
  • ReLU:训练速度最快,但可能产生 ” 死亡神经元 ”
  • LeakyReLU:平衡了训练速度和神经元存活率

避坑指南

梯度消失问题

解决方案包括:

  1. 使用 ReLU 等非饱和激活函数
  2. 采用批归一化(BatchNorm)层
  3. 残差连接(ResNet 结构)

权重初始化

推荐方法:

  1. Xavier 初始化:$W \sim N(0, \sqrt{2/(n_{in}+n_{out})})$
  2. He 初始化:$W \sim N(0, \sqrt{2/n_{in}})$(适合 ReLU)

延伸思考

  1. 如何将手动实现的 BP 算法扩展到支持 GPU 加速?
  2. 在超大规模网络中,如何优化反向传播的内存消耗?
  3. 现代深度学习框架(如 PyTorch)是如何自动实现反向传播的?

理解 BP 算法的底层实现,是掌握深度学习框架工作原理的关键。建议读者尝试扩展本文代码,实现更复杂的网络结构,并对比不同优化算法的效果。

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