BP神经网络反向传播计算实战:从数学推导到Python实现

1次阅读
没有评论

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

image.webp

神经网络基础回顾

在开始反向传播之前,我们需要明确几个基本概念。BP 神经网络通常由输入层、隐藏层和输出层组成,每层包含若干神经元。每个神经元接收前一层神经元的输出,经过加权求和和激活函数处理后,传递给下一层。

BP 神经网络反向传播计算实战:从数学推导到 Python 实现

神经网络的训练过程分为两个阶段:

  1. 前向传播:输入数据从输入层经过隐藏层传递到输出层,得到预测结果。
  2. 反向传播:根据预测结果与真实值的误差,从输出层向输入层反向传播,调整各层的权重和偏置。

反向传播之所以必要,是因为它提供了一种高效计算梯度的方法,使得神经网络能够通过梯度下降等优化算法进行参数更新。

反向传播数学推导

反向传播的核心是链式法则的应用。我们以一个三层神经网络(输入层、隐藏层、输出层)为例,推导反向传播的过程。

设:

  • $x_i$:输入层第 i 个神经元的输入
  • $w_{ij}^{(1)}$:输入层到隐藏层的权重
  • $b_j^{(1)}$:隐藏层的偏置
  • $z_j^{(1)}$:隐藏层的加权输入
  • $a_j^{(1)}$:隐藏层的输出(激活值)
  • $w_{jk}^{(2)}$:隐藏层到输出层的权重
  • $b_k^{(2)}$:输出层的偏置
  • $z_k^{(2)}$:输出层的加权输入
  • $a_k^{(2)}$:输出层的输出(预测值)
  • $y_k$:真实值
  • $L$:损失函数(如均方误差)

前向传播公式:

$$z_j^{(1)} = \sum_i w_{ij}^{(1)}x_i + b_j^{(1)}$$

$$a_j^{(1)} = \sigma(z_j^{(1)})$$

$$z_k^{(2)} = \sum_j w_{jk}^{(2)}a_j^{(1)} + b_k^{(2)}$$

$$a_k^{(2)} = \sigma(z_k^{(2)})$$

$$L = \frac{1}{2}\sum_k (y_k – a_k^{(2)})^2$$

反向传播时,我们需要计算损失函数对各参数的梯度。以输出层权重 $w_{jk}^{(2)}$ 为例:

$$\frac{\partial L}{\partial w_{jk}^{(2)}} = \frac{\partial L}{\partial a_k^{(2)}} \cdot \frac{\partial a_k^{(2)}}{\partial z_k^{(2)}} \cdot \frac{\partial z_k^{(2)}}{\partial w_{jk}^{(2)}}$$

计算各项:

$$\frac{\partial L}{\partial a_k^{(2)}} = -(y_k – a_k^{(2)})$$

$$\frac{\partial a_k^{(2)}}{\partial z_k^{(2)}} = \sigma'(z_k^{(2)})$$

$$\frac{\partial z_k^{(2)}}{\partial w_{jk}^{(2)}} = a_j^{(1)}$$

因此:

$$\frac{\partial L}{\partial w_{jk}^{(2)}} = -(y_k – a_k^{(2)}) \cdot \sigma'(z_k^{(2)}) \cdot a_j^{(1)}$$

类似地,我们可以推导出其他参数的梯度。

Python 代码实现

下面是一个使用 NumPy 实现的简单 BP 神经网络:

import numpy as np

class NeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size):
        # 初始化权重和偏置
        self.W1 = np.random.randn(input_size, hidden_size)
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size)
        self.b2 = np.zeros((1, output_size))

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

    def sigmoid_derivative(self, x):
        return x * (1 - 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
        self.a2 = self.sigmoid(self.z2)
        return self.a2

    def backward(self, X, y, output, learning_rate):
        # 反向传播
        error = y - output
        d_output = error * self.sigmoid_derivative(output)

        error_hidden = d_output.dot(self.W2.T)
        d_hidden = error_hidden * self.sigmoid_derivative(self.a1)

        # 更新权重和偏置
        self.W2 += self.a1.T.dot(d_output) * learning_rate
        self.b2 += np.sum(d_output, axis=0, keepdims=True) * learning_rate
        self.W1 += X.T.dot(d_hidden) * learning_rate
        self.b1 += np.sum(d_hidden, axis=0, keepdims=True) * learning_rate

    def train(self, X, y, epochs, learning_rate):
        for i in range(epochs):
            output = self.forward(X)
            self.backward(X, y, output, learning_rate)
            if i % 1000 == 0:
                loss = np.mean(np.square(y - output))
                print(f'Epoch {i}, Loss: {loss}')

# 示例使用
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([[0], [1], [1], [0]])

nn = NeuralNetwork(input_size=2, hidden_size=4, output_size=1)
nn.train(X, y, epochs=10000, learning_rate=0.1)

具体计算例题演示

让我们通过一个具体例子演示前向传播和反向传播的过程。考虑一个简单的神经网络:

  • 输入层:2 个神经元(x1, x2)
  • 隐藏层:2 个神经元
  • 输出层:1 个神经元

初始权重和偏置:

  • W1 = [[0.1, 0.2], [0.3, 0.4]]
  • b1 = [0.5, 0.5]
  • W2 = [[0.5], [0.6]]
  • b2 = [0.7]

输入 x = [1, 0.5],真实值 y = 1

前向传播:

  1. 计算隐藏层输入:
    z1 = [10.1 + 0.50.3 + 0.5, 10.2 + 0.50.4 + 0.5] = [0.75, 0.9]
  2. 计算隐藏层输出(使用 sigmoid):
    a1 = [σ(0.75), σ(0.9)] ≈ [0.679, 0.711]
  3. 计算输出层输入:
    z2 = 0.6790.5 + 0.7110.6 + 0.7 ≈ 1.424
  4. 计算输出:
    a2 = σ(1.424) ≈ 0.806

反向传播:

  1. 计算输出层误差:
    δ2 = (1 – 0.806) * σ'(0.806) ≈ 0.194 * 0.151 ≈ 0.029
  2. 更新 W2:
    ΔW2 = learning_rate * a1.T * δ2
  3. 计算隐藏层误差:
    δ1 = δ2 * W2 * σ'(a1) ≈ [0.0290.50.197, 0.0290.60.205] ≈ [0.0029, 0.0036]
  4. 更新 W1:
    ΔW1 = learning_rate * x.T * δ1

常见问题与优化建议

梯度消失 / 爆炸

梯度消失发生在深层网络中,当梯度通过多层传播时变得越来越小。解决方法:

  • 使用 ReLU 等激活函数
  • 使用 Batch Normalization
  • 使用残差连接

梯度爆炸则相反,梯度变得非常大。解决方法:

  • 梯度裁剪
  • 权重正则化

学习率选择

  • 初始学习率通常设为 0.1 或 0.01
  • 可以使用学习率衰减策略
  • 自适应优化器(如 Adam)可以自动调整学习率

激活函数选择

  • ReLU:最常用,计算简单
  • LeakyReLU:解决 ReLU 的 ” 死亡 ” 问题
  • Sigmoid:输出层用于二分类
  • Tanh:隐藏层有时使用

性能优化建议

  1. 向量化计算:使用 NumPy 等库进行矩阵运算
  2. GPU 加速:使用 CUDA 或深度学习框架
  3. 批量训练:使用 mini-batch 而非单个样本
  4. 并行化:多线程或多进程处理

总结与思考

本文详细介绍了 BP 神经网络的反向传播算法,从数学推导到 Python 实现。通过一个具体例子演示了前向传播和反向传播的计算过程,并讨论了实际应用中的常见问题和优化建议。

思考题:如何将反向传播算法应用到卷积神经网络中?答案在于理解卷积层的梯度计算和误差传播方式。卷积层的反向传播需要计算对卷积核的梯度,这可以通过互相关运算实现。池化层的反向传播则需要将误差分配到前一层相应的位置。

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