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
评论(没有评论)