BP神经网络入门指南:从数学原理到Python实战

1次阅读
没有评论

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

image.webp

为什么需要 BP 神经网络?

在开始理论推导前,先看两个典型场景:

BP 神经网络入门指南:从数学原理到 Python 实战

  • 手写数字识别 :银行支票上的手写金额识别,本质是将 28×28 像素的图片分类为 0 - 9 的数字。传统算法难以处理这种非线性关系
  • 商品销量预测 :基于历史销量、季节、促销活动等数十个因素预测下月销量,变量间存在复杂交互作用

这类问题的共性是:输入与输出间存在多层非线性关系,这正是 BP(Backpropagation)神经网络的用武之地。

数学原理三步走

1. 前向传播的矩阵表示

假设三层网络结构(输入层 2 节点,隐藏层 3 节点,输出层 1 节点):

# 权重矩阵维度说明
W1 = np.random.randn(2, 3)  # 输入层→隐藏层
W2 = np.random.randn(3, 1)  # 隐藏层→输出层 

前向传播公式:

  1. 隐藏层输入:$Z_1 = X W_1 + b_1$
  2. 隐藏层输出:$A_1 = \sigma(Z_1)$(σ 为激活函数)
  3. 输出层结果:$\hat{Y} = \sigma(Z_2) = \sigma(A_1 W_2 + b_2)$

2. 损失函数选择

分类问题常用交叉熵损失(Cross-Entropy):

$$ L = -\frac{1}{N}\sum_{i=1}^N [y_i\ln(\hat{y}_i) + (1-y_i)\ln(1-\hat{y}_i)] $$

回归问题可用均方误差(MSE)。前者能更好处理概率输出。

3. 反向传播推导(核心)

关键是通过链式法则计算权重梯度:

  1. 输出层梯度:
    $$ \frac{\partial L}{\partial W_2} = \frac{\partial L}{\partial \hat{Y}} \cdot \frac{\partial \hat{Y}}{\partial Z_2} \cdot \frac{\partial Z_2}{\partial W_2} = (\hat{Y}-Y) \cdot A_1^T $$

  2. 隐藏层梯度:
    $$ \frac{\partial L}{\partial W_1} = (\hat{Y}-Y) W_2^T \odot \sigma'(Z_1) \cdot X^T $$
    (⊙表示逐元素乘法)

最终权重更新公式:
$$ W \leftarrow W – \eta \cdot \frac{\partial L}{\partial W} $$
(η 为学习率)

Python 实战实现

网络结构定义

import numpy as np

class NeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size):
        self.W1 = np.random.randn(input_size, hidden_size) * 0.01
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size) * 0.01
        self.b2 = np.zeros((1, output_size))

Sigmoid 激活函数

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

def sigmoid_derivative(self, x):
    return x * (1 - x)  # 注意此处 x 应为 sigmoid 输出值 

训练过程

def train(self, X, y, epochs=1000, lr=0.1):
    for epoch in range(epochs):
        # 前向传播
        Z1 = np.dot(X, self.W1) + self.b1
        A1 = self.sigmoid(Z1)
        Z2 = np.dot(A1, self.W2) + self.b2
        y_pred = self.sigmoid(Z2)

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

        # 反向传播
        dy_pred = (y_pred - y) / (y_pred * (1 - y_pred + 1e-9))  # 防止除零
        dW2 = np.dot(A1.T, dy_pred * self.sigmoid_derivative(y_pred))
        db2 = np.sum(dy_pred * self.sigmoid_derivative(y_pred), axis=0)

        dhidden = np.dot(dy_pred * self.sigmoid_derivative(y_pred), self.W2.T)
        dW1 = np.dot(X.T, dhidden * self.sigmoid_derivative(A1))
        db1 = np.sum(dhidden * self.sigmoid_derivative(A1), axis=0)

        # 更新权重
        self.W1 -= lr * dW1
        self.b1 -= lr * db1
        self.W2 -= lr * dW2
        self.b2 -= lr * db2

MNIST 实战测试

加载数据并训练:

from sklearn.datasets import fetch_openml
from sklearn.preprocessing import MinMaxScaler

mnist = fetch_openml('mnist_784')
X = mnist.data.astype('float32') / 255.0
y = pd.get_dummies(mnist.target).values  # one-hot 编码

# 划分训练测试集
X_train, X_test = X[:60000], X[60000:]
y_train, y_test = y[:60000], y[60000:]

# 创建网络
nn = NeuralNetwork(784, 128, 10)  # 输入 784 像素,隐藏层 128 节点,输出 10 类
nn.train(X_train, y_train, epochs=50, lr=0.1)

常见问题及解决方案

梯度消失问题

现象:深层网络训练时,前面层的权重更新幅度极小
解决方案:

  • 使用 ReLU 激活函数替代 Sigmoid
  • 采用 Batch Normalization
  • 残差连接(ResNet 思路)

过拟合应对

识别方法:训练集准确率持续上升但测试集开始下降
应对策略:

  • 添加 L2 正则化项
  • 使用 Dropout 随机屏蔽神经元
  • 早停法(Early Stopping)

延伸思考

  1. 与 CNN/RNN 对比
  2. CNN 通过卷积核提取空间特征,适合图像
  3. RNN 具有记忆单元,适合序列数据
  4. 全连接 BP 网络更适合结构化数据

  5. Mini-Batch 改造
    将训练数据分批次(如 batch_size=64):

    for epoch in range(epochs):
        for i in range(0, len(X), batch_size):
            X_batch = X[i:i+batch_size]
            y_batch = y[i:i+batch_size]
            # 在该 batch 上执行前向 / 反向传播 

通过这个实现,你会发现神经网络不再神秘。建议尝试调整隐藏层节点数、更换激活函数,观察对模型性能的影响,这是理解深度学习的最佳途径。

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