BP神经网络Python实战:从数学原理到手写数字识别

1次阅读
没有评论

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

image.webp

前言

最近在入门机器学习,发现 BP 神经网络是个绕不开的话题。网上资料虽多,但要么过于理论,要么代码晦涩难懂。经过两周摸索,我终于用 Python+numpy 实现了完整的 BP 神经网络,并在 MNIST 手写数字识别上跑出了不错的效果。这篇笔记记录了我的实现过程和踩坑经验,特别适合有以下需求的同学:

  • 想彻底搞懂 BP 算法的数学原理
  • 拒绝调包,希望从零实现神经网络
  • 需要可复用的工业级代码模板

一、BP 神经网络基础

1.1 网络结构图解

先看最简单的三层网络结构:

 输入层 (784) → 隐层 (256) → 输出层 (10)

对于 MNIST 数据集,输入是 28×28=784 维的像素值,输出是 10 维的概率分布(对应 0 - 9 数字)。中间的隐层神经元数量需要自己试验确定。

1.2 核心数学公式

前向传播

隐层输出:
$$h = \sigma(W_1 x + b_1)$$

输出层结果:
$$y = \text{softmax}(W_2 h + b_2)$$

其中 $\sigma$ 是激活函数,softmax 用于多分类的概率归一化。

反向传播(链式求导)

输出层梯度:
$$\frac{\partial L}{\partial W_2} = (y – \text{label}) \cdot h^T$$

隐层梯度:
$$\frac{\partial L}{\partial W_1} = (W_2^T(y – \text{label})) \odot \sigma'(z) \cdot x^T$$

这里 $\odot$ 表示逐元素相乘,$\sigma’$ 是激活函数的导数。

二、Python 实现详解

2.1 网络初始化

class NeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size):
        # Xavier 初始化:根据输入输出维度调整初始权重范围
        self.W1 = np.random.randn(input_size, hidden_size) / np.sqrt(input_size)
        self.b1 = np.zeros(hidden_size)
        self.W2 = np.random.randn(hidden_size, output_size) / np.sqrt(hidden_size)
        self.b2 = np.zeros(output_size)

2.2 激活函数对比

测试发现 ReLU 在 MNIST 上效果最好:

def relu(x):
    return np.maximum(0, x)

def relu_derivative(x):
    return (x > 0).astype(float)

2.3 完整训练流程

for epoch in range(epochs):
    # 前向传播
    z1 = np.dot(X, self.W1) + self.b1
    a1 = relu(z1)
    z2 = np.dot(a1, self.W2) + self.b2
    probs = softmax(z2)

    # 计算 loss(交叉熵)loss = -np.sum(np.log(probs[np.arange(batch_size), labels])) / batch_size

    # 反向传播
    dz2 = probs
    dz2[np.arange(batch_size), labels] -= 1
    dz2 /= batch_size

    dW2 = np.dot(a1.T, dz2)
    db2 = np.sum(dz2, axis=0)

    dz1 = np.dot(dz2, self.W2.T) * relu_derivative(z1)
    dW1 = np.dot(X.T, dz1)
    db1 = np.sum(dz1, axis=0)

    # 梯度裁剪(防止爆炸)for grad in [dW1, db1, dW2, db2]:
        np.clip(grad, -5, 5, out=grad)

    # 参数更新
    self.W1 -= learning_rate * dW1
    self.b1 -= learning_rate * db1
    self.W2 -= learning_rate * dW2
    self.b2 -= learning_rate * db2

三、实战调优技巧

3.1 学习率动态调整

采用指数衰减策略效果显著:

initial_lr = 0.1
decay_rate = 0.95
learning_rate = initial_lr * (decay_rate ** epoch)

3.2 防止过拟合

  • 隐层神经元不宜过多(256 足够)
  • 添加 L2 正则化项
  • 早停法(验证集 loss 连续 3 次不降则终止)

3.3 BatchNorm 的妙用

在隐层激活前加入 BN 层,可使训练更稳定:

# 前向传播时
mu = np.mean(z1, axis=0)
var = np.var(z1, axis=0)
z1 = (z1 - mu) / np.sqrt(var + 1e-8)

四、最终效果

经过 20 个 epoch 训练后:

  • 测试集准确率:97.2%
  • Loss 曲线平稳下降

BP 神经网络 Python 实战:从数学原理到手写数字识别

总结

从数学推导到代码实现,BP 神经网络并没有想象中那么难。关键是要理解每个矩阵运算的物理意义,以及梯度是如何通过链式法则反向传播的。建议初学者一定要亲手实现一遍,这会比直接调用 TensorFlow/PyTorch 收获大得多。

完整代码已上传 GitHub,包含更多工程优化技巧(如向量化加速、多线程数据加载)。遇到问题欢迎在评论区交流,我会持续更新优化方案。

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