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

1次阅读
没有评论

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

image.webp

背景介绍

手写数字识别是计算机视觉领域的基础问题,广泛应用于银行支票处理、邮政分拣等场景。BP 神经网络因其强大的非线性拟合能力,成为解决这类问题的经典方法。相比传统算法,BP 网络能自动学习特征,且对图像变形具有一定鲁棒性。

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

数学原理

前向传播

神经网络第 $l$ 层的输出计算为:
$$\mathbf{z}^{(l)} = \mathbf{W}^{(l)}\mathbf{a}^{(l-1)} + \mathbf{b}^{(l)}$$
$$\mathbf{a}^{(l)} = \sigma(\mathbf{z}^{(l)})$$
其中 $\sigma$ 为激活函数,常用 Sigmoid:
$$\sigma(z) = \frac{1}{1+e^{-z}}$$

反向传播

通过链式法则计算梯度:
1. 输出层误差:
$$\delta^{(L)} = \nabla_a J \odot \sigma'(\mathbf{z}^{(L)})$$
2. 隐藏层误差:
$$\delta^{(l)} = (\mathbf{W}^{(l+1)})^T\delta^{(l+1)} \odot \sigma'(\mathbf{z}^{(l)})$$
3. 参数梯度:
$$\nabla_{\mathbf{W}^{(l)}}J = \delta^{(l)}(\mathbf{a}^{(l-1)})^T$$
$$\nabla_{\mathbf{b}^{(l)}}J = \delta^{(l)}$$

Python 实现

import numpy as np

class NeuralNetwork:
    def __init__(self, layers):
        """:param layers: 各层神经元数量,如 [784, 64, 10]"""
        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:]]

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

    def forward(self, x):
        """前向传播"""
        for w, b in zip(self.weights, self.biases):
            x = self.sigmoid(w @ x + b)
        return x

    def train(self, X, y, lr=0.1):
        """单次训练"""
        # 前向传播
        activations = [X]
        zs = []
        for w, b in zip(self.weights, self.biases):
            z = w @ activations[-1] + b
            zs.append(z)
            activations.append(self.sigmoid(z))

        # 反向传播
        delta = (activations[-1] - y) * activations[-1] * (1-activations[-1])
        nabla_w = [delta @ activations[-2].T]
        nabla_b = [delta]

        for l in range(2, len(self.weights)+1):
            delta = (self.weights[-l+1].T @ delta) * \
                    activations[-l] * (1-activations[-l])
            nabla_w.insert(0, delta @ activations[-l-1].T)
            nabla_b.insert(0, delta)

        # 参数更新
        for i in range(len(self.weights)):
            self.weights[i] -= lr * nabla_w[i]
            self.biases[i] -= lr * nabla_b[i]

模型训练

使用 MNIST 数据集时,建议配置:

  • 输入层:784 个神经元(28×28 图像展平)
  • 隐藏层:64-128 个神经元
  • 输出层:10 个神经元(对应 0 - 9 数字)
  • 学习率:初始 0.1,每 10 轮衰减 10%
  • Batch 大小:32-128

关键训练代码:

from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1)
X, y = mnist["data"], mnist["target"]
X = X / 255.0  # 归一化

def one_hot(y):
    res = np.zeros((10, len(y)))
    res[y.astype(int), np.arange(len(y))] = 1
    return res

y_onehot = one_hot(y)

# 初始化网络
nn = NeuralNetwork([784, 64, 10])

# 训练循环
for epoch in range(20):
    for i in range(0, len(X), 128):
        batch_X = X[i:i+128].T
        batch_y = y_onehot[:, i:i+128]
        nn.train(batch_X, batch_y, lr=0.1*(0.9**(epoch//10)))

性能优化

通过实验发现:

  • 隐藏层节点数:64 节点时测试集准确率约 92%,128 节点可达 94%
  • 学习率:大于 0.3 容易震荡,小于 0.01 收敛过慢
  • Batch 大小:32 比 128 收敛更快,但波动更大
  • 添加第二个隐藏层(如 [784,128,64,10])可提升至 95%

避坑指南

  1. 梯度消失
  2. 使用 ReLU 激活函数替代 Sigmoid
  3. 采用 Xavier 初始化权重

  4. 过拟合

  5. 添加 L2 正则化项
  6. 早停法(Early Stopping)

  7. 收敛慢

  8. 检查梯度计算是否正确
  9. 尝试动量法(Momentum)

延伸思考

  1. 引入卷积层(CNN)可显著提升准确率(可达 99%+)
  2. 使用 Adam 优化器替代 SGD
  3. 添加 Batch Normalization 层加速训练
  4. 数据增强(旋转 / 平移图像)提升泛化能力

通过这个项目,我们不仅理解了 BP 神经网络的底层原理,还掌握了参数调优的实用技巧。虽然现在业界更多使用现成框架,但亲手实现一次对理解深度学习本质大有裨益。

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