BP神经网络实战:Python实现手写数字识别案例解析

1次阅读
没有评论

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

image.webp

背景痛点

在实现 BP 神经网络时,初学者常会遇到以下几个典型问题:

BP 神经网络实战:Python 实现手写数字识别案例解析

  • 梯度消失 :当使用 Sigmoid 激活函数时,反向传播过程中梯度会随着网络深度指数级衰减,导致浅层权重难以更新
  • 训练速度慢 :传统的全批量梯度下降需要遍历整个数据集才能更新一次参数,计算开销大
  • 过拟合 :网络在训练集上表现良好但在测试集上性能下降,常见于参数量较大的网络

技术对比:Sigmoid vs ReLU

两种激活函数的特性对比:

  • Sigmoid
  • 数学形式:$\sigma(z) = \frac{1}{1+e^{-z}}$
  • 优点:输出范围 (0,1),适合概率输出
  • 缺点:容易导致梯度消失,计算复杂度高

  • ReLU

  • 数学形式:$ReLU(z) = max(0,z)$
  • 优点:计算简单,缓解梯度消失
  • 缺点:可能出现神经元 ” 死亡 ” 现象

核心实现

网络结构设计

我们采用三层网络结构:
– 输入层:784 个神经元(对应 28×28 图像)
– 隐藏层:256 个神经元
– 输出层:10 个神经元(对应 0 - 9 数字分类)

Python 实现代码

import numpy as np
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split

# 数据加载与预处理
mnist = fetch_openml('mnist_784', version=1)
X = mnist.data / 255.0  # 归一化
Y = np.eye(10)[mnist.target.astype(int)]  # one-hot 编码

# 划分训练集和测试集
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2)

# 网络参数初始化
input_size = 784
hidden_size = 256
output_size = 10
learning_rate = 0.01

# 权重初始化(Xavier 初始化)W1 = np.random.randn(input_size, hidden_size) * np.sqrt(1./input_size)
b1 = np.zeros(hidden_size)
W2 = np.random.randn(hidden_size, output_size) * np.sqrt(1./hidden_size)
b2 = np.zeros(output_size)

# ReLU 激活函数
def relu(x):
    return np.maximum(0, x)

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

# Softmax 函数
def softmax(x):
    exps = np.exp(x - np.max(x, axis=1, keepdims=True))
    return exps / np.sum(exps, axis=1, keepdims=True)

# 交叉熵损失
def cross_entropy(y_pred, y_true):
    m = y_true.shape[0]
    log_likelihood = -np.log(y_pred[range(m), np.argmax(y_true, axis=1)])
    return np.sum(log_likelihood) / m

# 训练过程
for epoch in range(100):
    # 前向传播
    z1 = np.dot(X_train, W1) + b1
    a1 = relu(z1)
    z2 = np.dot(a1, W2) + b2
    y_pred = softmax(z2)

    # 计算损失
    loss = cross_entropy(y_pred, Y_train)

    # 反向传播
    m = X_train.shape[0]
    dz2 = y_pred - Y_train
    dW2 = (1/m) * np.dot(a1.T, dz2)
    db2 = (1/m) * np.sum(dz2, axis=0)

    dz1 = np.dot(dz2, W2.T) * relu_derivative(a1)
    dW1 = (1/m) * np.dot(X_train.T, dz1)
    db1 = (1/m) * np.sum(dz1, axis=0)

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

    if epoch % 10 == 0:
        print(f"Epoch {epoch}, Loss: {loss:.4f}")

案例演示:MNIST 识别

训练过程中观察损失变化:

Epoch 0, Loss: 2.3026
Epoch 10, Loss: 0.3562
Epoch 20, Loss: 0.2517
...
Epoch 90, Loss: 0.0983

测试集准确率可达约 92%,可视化部分识别结果:

# 测试集评估
z1_test = np.dot(X_test, W1) + b1
a1_test = relu(z1_test)
z2_test = np.dot(a1_test, W2) + b2
y_pred_test = softmax(z2_test)

accuracy = np.mean(np.argmax(y_pred_test, axis=1) == np.argmax(Y_test, axis=1))
print(f"Test Accuracy: {accuracy*100:.2f}%")

性能优化

学习率调整

  • 指数衰减 :随训练轮次逐步降低学习率
  • Adam 优化器 :自适应调整各参数学习率

Batch Normalization

# 在隐藏层添加 BN 层
class BatchNorm:
    def __init__(self, dim):
        self.gamma = np.ones(dim)
        self.beta = np.zeros(dim)
        self.running_mean = np.zeros(dim)
        self.running_var = np.ones(dim)

    def forward(self, x, train=True):
        if train:
            mean = np.mean(x, axis=0)
            var = np.var(x, axis=0)
            self.running_mean = 0.9 * self.running_mean + 0.1 * mean
            self.running_var = 0.9 * self.running_var + 0.1 * var
        else:
            mean = self.running_mean
            var = self.running_var

        x_norm = (x - mean) / np.sqrt(var + 1e-8)
        return self.gamma * x_norm + self.beta

避坑指南

权重初始化

  • Xavier 初始化 :适合 Sigmoid/Tanh
  • He 初始化 :适合 ReLU

防止梯度爆炸

  • 梯度裁剪 :限制梯度最大值
    max_grad_norm = 1.0
    grad_norm = np.linalg.norm(dW1)
    if grad_norm > max_grad_norm:
        dW1 = dW1 * max_grad_norm / grad_norm

早停法实现

best_loss = float('inf')
patience = 5
wait = 0

for epoch in range(100):
    # ... 训练过程...

    if loss < best_loss:
        best_loss = loss
        wait = 0
    else:
        wait += 1
        if wait >= patience:
            print("Early stopping")
            break

思考题

如何改进网络结构以提升识别准确率到 98% 以上?可以考虑以下方向:

  • 增加网络深度
  • 使用卷积神经网络 (CNN)
  • 添加 Dropout 层
  • 数据增强
  • 更复杂的优化器(如 AdamW)

希望这篇实战教程能帮助你掌握 BP 神经网络的核心实现技巧!在实际项目中,记得根据具体问题调整网络结构和超参数。

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