共计 1763 个字符,预计需要花费 5 分钟才能阅读完成。
背景介绍
手写数字识别是计算机视觉领域的基础任务,在银行支票识别、快递单号录入等场景有广泛应用。传统方法依赖特征工程,而神经网络能自动学习特征。MNIST 数据集包含 6 万张 28×28 灰度手写数字图,是理想的入门数据集。

原理详解
前向传播
神经网络通过加权求和与激活函数逐层计算输出:
$$ z^{l} = w^{l}a^{l-1} + b^{l} $$
$$ a^{l} = \sigma(z^{l}) $$
其中 $\sigma$ 常用 ReLU 或 Sigmoid 函数。输出层用 Softmax 得到概率分布。
反向传播
通过链式法则计算梯度:
-
输出层误差:
$$ \delta^{L} = \nabla_a C \odot \sigma'(z^{L}) $$ -
隐藏层误差:
$$ \delta^{l} = ((w^{l+1})^T \delta^{l+1}) \odot \sigma'(z^{l}) $$ -
参数梯度:
$$ \nabla_w C = \delta^{l}(a^{l-1})^T $$
$$ \nabla_b C = \delta^{l} $$
代码实现
import numpy as np
from sklearn.datasets import fetch_openml
from sklearn.preprocessing import OneHotEncoder
class NeuralNetwork:
def __init__(self, layer_sizes):
self.weights = [np.random.randn(y, x)/np.sqrt(x)
for x, y in zip(layer_sizes[:-1], layer_sizes[1:])]
self.biases = [np.random.randn(y, 1) for y in layer_sizes[1:]]
def forward(self, x):
for w, b in zip(self.weights, self.biases):
x = sigmoid(np.dot(w, x) + b)
return x
def train(self, X, y, epochs=10, lr=0.1, batch_size=32):
for _ in range(epochs):
for i in range(0, len(X), batch_size):
X_batch, y_batch = X[i:i+batch_size], y[i:i+batch_size]
self.update_batch(X_batch, y_batch, lr)
def update_batch(self, X_batch, y_batch, lr):
# 反向传播实现代码...
pass
def sigmoid(x):
return 1/(1 + np.exp(-x))
模型训练
- 数据预处理:
- 像素值归一化到[0,1]
-
标签进行 one-hot 编码
-
超参数设置:
- 网络结构:784(input)-128(hidden)-10(output)
- 学习率:初始 0.1,每 5 轮衰减 10%
-
Batch 大小:64
-
训练过程:
mnist = fetch_openml('mnist_784') X, y = mnist.data / 255.0, mnist.target encoder = OneHotEncoder() y_onehot = encoder.fit_transform(y.reshape(-1, 1)).toarray() nn = NeuralNetwork([784, 128, 10]) nn.train(X, y_onehot, epochs=20, lr=0.1)
性能优化
- 学习率:过大导致震荡,过小收敛慢。建议使用学习率衰减
- Batch 大小:较小 batch 带来更多噪声,可能帮助跳出局部最优
- 激活函数:隐藏层用 ReLU 比 Sigmoid 训练更快
- 权重初始化:Xavier 初始化避免梯度消失
避坑指南
- 梯度爆炸:
- 现象:loss 突然变成 NaN
-
解决:梯度裁剪 / 权重正则化
-
模型不收敛:
- 检查数据预处理是否正确
-
确认反向传播梯度计算无误
-
过拟合:
- 添加 Dropout 层
- 使用 L2 正则化
扩展思考
- 尝试增加隐藏层数量观察性能变化
- 用交叉验证选择最优超参数
- 实现卷积神经网络 (CNN) 对比效果
- 在自定义手写数字数据集上测试
通过这个项目,我们不仅理解了 BP 神经网络的数学原理,还掌握了从数据预处理到模型调优的完整流程。建议读者尝试修改网络结构,观察不同激活函数的效果,这是深入理解神经网络行为的最佳方式。
正文完
