BP神经网络的正向传播与反向传播:从数学原理到Python实现

1次阅读
没有评论

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

image.webp

神经网络基础回顾

BP 神经网络(Back Propagation Neural Network)是一种多层前馈神经网络,通过反向传播算法来调整网络权重。它由输入层、隐藏层和输出层组成,每一层包含若干神经元。神经元之间的连接权重决定了信号的传递强度。

BP 神经网络的正向传播与反向传播:从数学原理到 Python 实现

神经网络的训练过程分为两个阶段:正向传播和反向传播。正向传播用于计算网络的输出,反向传播用于根据误差调整权重。

正向传播的数学推导与实现

正向传播是指输入信号从输入层经过隐藏层最终到达输出层的过程。每一层的输出都是前一层输出的加权和经过激活函数的结果。

数学表达式如下:

  1. 输入层到第一隐藏层的计算:
    $$Z^1 = W^1 \cdot X + b^1$$
    $$A^1 = f(Z^1)$$

  2. 第 l 层到第 l + 1 层的计算:
    $$Z^{l+1} = W^{l+1} \cdot A^l + b^{l+1}$$
    $$A^{l+1} = f(Z^{l+1})$$

其中,$W$ 是权重矩阵,$b$ 是偏置向量,$f$ 是激活函数(如 sigmoid、ReLU 等)。

反向传播的梯度计算详解

反向传播是根据输出误差来调整网络权重的过程。它通过链式法则计算各层权重和偏置的梯度。

  1. 输出层的误差:
    $$\delta^L = \frac{\partial J}{\partial Z^L} = (A^L – Y) \odot f'(Z^L)$$

  2. 隐藏层的误差传播:
    $$\delta^l = (W^{l+1})^T \cdot \delta^{l+1} \odot f'(Z^l)$$

  3. 权重和偏置的梯度:
    $$\frac{\partial J}{\partial W^l} = \delta^l \cdot (A^{l-1})^T$$
    $$\frac{\partial J}{\partial b^l} = \delta^l$$

其中,$J$ 是损失函数,$Y$ 是真实标签,$\odot$ 表示元素乘法。

完整 Python 代码实现

以下是使用 NumPy 实现的 BP 神经网络代码:

import numpy as np

class BPNeuralNetwork:
    def __init__(self, layers, learning_rate=0.1):
        self.layers = layers
        self.learning_rate = learning_rate
        self.weights = []
        self.biases = []

        # 初始化权重和偏置
        for i in range(len(layers)-1):
            w = np.random.randn(layers[i+1], layers[i]) * 0.01
            b = np.zeros((layers[i+1], 1))
            self.weights.append(w)
            self.biases.append(b)

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

    def sigmoid_derivative(self, z):
        s = self.sigmoid(z)
        return s * (1 - s)

    def forward_propagation(self, X):
        A = X
        caches = []

        for i in range(len(self.weights)):
            Z = np.dot(self.weights[i], A) + self.biases[i]
            A = self.sigmoid(Z)
            caches.append((Z, A))

        return A, caches

    def backward_propagation(self, X, Y, caches):
        m = X.shape[1]
        grads = {}
        A_final = caches[-1][1]

        # 计算输出层误差
        dZ = A_final - Y

        for l in reversed(range(len(self.weights))):
            A_prev = X if l == 0 else caches[l-1][1]

            dW = np.dot(dZ, A_prev.T) / m
            db = np.sum(dZ, axis=1, keepdims=True) / m

            grads[f'dW{l+1}'] = dW
            grads[f'db{l+1}'] = db

            if l > 0:
                dZ = np.dot(self.weights[l].T, dZ) * self.sigmoid_derivative(caches[l-1][0])

        return grads

    def update_parameters(self, grads):
        for l in range(len(self.weights)):
            self.weights[l] -= self.learning_rate * grads[f'dW{l+1}']
            self.biases[l] -= self.learning_rate * grads[f'db{l+1}']

    def train(self, X, Y, epochs=1000):
        for i in range(epochs):
            # 正向传播
            A, caches = self.forward_propagation(X)

            # 计算损失
            loss = -np.mean(Y * np.log(A) + (1-Y) * np.log(1-A))

            # 反向传播
            grads = self.backward_propagation(X, Y, caches)

            # 更新参数
            self.update_parameters(grads)

            if i % 100 == 0:
                print(f'Epoch {i}, Loss: {loss}')

超参数调优建议

  1. 学习率:
  2. 过大可能导致震荡甚至发散
  3. 过小会导致收敛速度慢
  4. 建议从 0.01 开始尝试,根据训练效果调整

  5. 批量大小:

  6. 批量梯度下降(Batch GD):使用全部样本
  7. 小批量梯度下降(Mini-batch GD):常用 32/64/128
  8. 随机梯度下降(SGD):每次一个样本

  9. 隐藏层数量和神经元数量:

  10. 过于复杂的网络容易过拟合
  11. 建议从简单网络开始,逐步增加复杂度

常见错误与解决方案

  1. 梯度消失 / 爆炸问题:
  2. 使用 ReLU 等非饱和激活函数
  3. 使用 Batch Normalization
  4. 使用梯度裁剪

  5. 参数初始化问题:

  6. 避免全零初始化
  7. 使用 Xavier 或 He 初始化

  8. 过拟合问题:

  9. 使用 L1/L2 正则化
  10. 使用 Dropout
  11. 增加训练数据

延伸思考题

  1. 除了 sigmoid 函数,还有哪些激活函数可以使用?它们各有什么优缺点?
  2. 如何修改代码实现批量归一化(Batch Normalization)?
  3. 除了均方误差和交叉熵,还有哪些损失函数适合分类问题?
  4. 如何实现早停(Early Stopping)来防止过拟合?
  5. 如何可视化训练过程中权重和偏差的变化?

通过本文的学习,你应该已经掌握了 BP 神经网络的基本原理和实现方法。接下来可以尝试将这些知识应用到实际项目中,比如手写数字识别、房价预测等问题。在实践中不断调整和优化网络结构,积累经验。

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