NumPy 实现前馈神经网络:MNIST 手写数字识别 95%+ 准确率实战

发布时间:2026/7/8 22:42:06
NumPy 实现前馈神经网络:MNIST 手写数字识别 95%+ 准确率实战 NumPy 实现前馈神经网络MNIST 手写数字识别 95% 准确率实战1. 为什么选择纯NumPy实现神经网络在深度学习框架泛滥的今天用纯NumPy实现神经网络听起来像是一种返祖行为。但正是这种看似原始的方法能让我们真正理解神经网络的底层运作机制。当你亲手用矩阵乘法实现反向传播时那些在TensorFlow和PyTorch中被封装好的函数突然变得透明起来。NumPy作为Python科学计算的基石提供了高效的矩阵运算能力。通过它实现神经网络你将获得对神经网络数学本质的直观理解从权重矩阵到梯度下降每个步骤都清晰可见摆脱框架束缚的灵活性完全掌控网络结构和训练过程性能优化的第一手经验学习如何用向量化操作替代低效循环import numpy as np from sklearn.datasets import fetch_openml from sklearn.model_selection import train_test_split2. MNIST数据集解析与预处理MNIST数据集包含70,000张28x28像素的手写数字灰度图像是机器学习领域的Hello World。但直接使用原始数据往往效果不佳我们需要进行标准化处理像素值归一化将0-255的像素值缩放到0-1范围标签one-hot编码将类别标签转换为向量形式训练集/测试集分割保留部分数据用于模型验证# 加载MNIST数据集 mnist fetch_openml(mnist_784, version1) X, y mnist.data / 255.0, mnist.target.astype(int) # One-hot编码标签 def one_hot(y, num_classes10): return np.eye(num_classes)[y] y_onehot one_hot(y) # 分割训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X, y_onehot, test_size0.2, random_state42 )提示数据预处理是机器学习项目中最容易被忽视却至关重要的环节。糟糕的预处理会限制模型性能上限。3. 前馈神经网络核心组件实现我们的神经网络将包含以下关键组件3.1 网络架构设计采用经典的三层结构输入层784个神经元对应28x28图像隐藏层128个神经元使用ReLU激活输出层10个神经元对应10个数字类别使用Softmaxclass NeuralNetwork: def __init__(self, input_size, hidden_size, output_size): self.W1 np.random.randn(input_size, hidden_size) * 0.01 self.b1 np.zeros((1, hidden_size)) self.W2 np.random.randn(hidden_size, output_size) * 0.01 self.b2 np.zeros((1, output_size))3.2 激活函数选择激活函数为神经网络引入非线性我们选择ReLU隐藏层激活解决梯度消失问题Softmax输出层激活适合多分类问题def relu(self, x): return np.maximum(0, x) def softmax(self, x): exp_x np.exp(x - np.max(x, axis1, keepdimsTrue)) return exp_x / np.sum(exp_x, axis1, keepdimsTrue)3.3 前向传播实现信息从输入层流向输出层的过程def forward(self, X): self.z1 np.dot(X, self.W1) self.b1 self.a1 self.relu(self.z1) self.z2 np.dot(self.a1, self.W2) self.b2 self.output self.softmax(self.z2) return self.output4. 训练过程与反向传播算法4.1 损失函数选择使用交叉熵损失衡量预测与真实分布的差距def cross_entropy_loss(self, y_pred, y_true): m y_true.shape[0] log_likelihood -np.log(y_pred[range(m), y_true.argmax(axis1)]) return np.sum(log_likelihood) / m4.2 反向传播计算梯度通过链式法则计算各层参数的梯度def backward(self, X, y): m X.shape[0] # 输出层梯度 delta2 self.output - y dW2 np.dot(self.a1.T, delta2) / m db2 np.sum(delta2, axis0, keepdimsTrue) / m # 隐藏层梯度 delta1 np.dot(delta2, self.W2.T) * (self.z1 0) dW1 np.dot(X.T, delta1) / m db1 np.sum(delta1, axis0) / m return dW1, db1, dW2, db24.3 参数更新与训练循环使用随机梯度下降(SGD)优化参数def update_params(self, dW1, db1, dW2, db2, lr0.1): self.W1 - lr * dW1 self.b1 - lr * db1 self.W2 - lr * dW2 self.b2 - lr * db2 def train(self, X, y, epochs100, lr0.1): for i in range(epochs): output self.forward(X) dW1, db1, dW2, db2 self.backward(X, y) self.update_params(dW1, db1, dW2, db2, lr) if i % 10 0: loss self.cross_entropy_loss(output, y) print(fEpoch {i}, Loss: {loss:.4f})5. 模型评估与性能优化技巧5.1 准确率计算def accuracy(self, X, y): predictions np.argmax(self.forward(X), axis1) labels np.argmax(y, axis1) return np.mean(predictions labels)5.2 超参数调优策略超参数推荐范围影响学习率0.01-0.1控制参数更新幅度隐藏层大小64-256模型容量批量大小32-256梯度估计稳定性训练轮数50-200训练充分程度5.3 提升性能的实用技巧学习率衰减随着训练进行逐渐降低学习率L2正则化防止权重过大导致过拟合批量归一化加速训练并提升模型稳定性早停机制验证集性能不再提升时终止训练# 添加L2正则化的损失计算 def cross_entropy_loss_with_reg(self, y_pred, y_true, reg_lambda0.01): cross_entropy self.cross_entropy_loss(y_pred, y_true) l2_reg 0.5 * reg_lambda * (np.sum(self.W1**2) np.sum(self.W2**2)) return cross_entropy l2_reg6. 完整代码实现与结果分析将所有组件整合为完整实现class NeuralNetwork: def __init__(self, input_size, hidden_size, output_size): self.W1 np.random.randn(input_size, hidden_size) * 0.01 self.b1 np.zeros((1, hidden_size)) self.W2 np.random.randn(hidden_size, output_size) * 0.01 self.b2 np.zeros((1, output_size)) def relu(self, x): return np.maximum(0, x) def softmax(self, x): exp_x np.exp(x - np.max(x, axis1, keepdimsTrue)) return exp_x / np.sum(exp_x, axis1, keepdimsTrue) def forward(self, X): self.z1 np.dot(X, self.W1) self.b1 self.a1 self.relu(self.z1) self.z2 np.dot(self.a1, self.W2) self.b2 self.output self.softmax(self.z2) return self.output def cross_entropy_loss(self, y_pred, y_true): m y_true.shape[0] log_likelihood -np.log(y_pred[range(m), y_true.argmax(axis1)]) return np.sum(log_likelihood) / m def backward(self, X, y): m X.shape[0] delta2 self.output - y dW2 np.dot(self.a1.T, delta2) / m db2 np.sum(delta2, axis0, keepdimsTrue) / m delta1 np.dot(delta2, self.W2.T) * (self.z1 0) dW1 np.dot(X.T, delta1) / m db1 np.sum(delta1, axis0) / m return dW1, db1, dW2, db2 def update_params(self, dW1, db1, dW2, db2, lr0.1): self.W1 - lr * dW1 self.b1 - lr * db1 self.W2 - lr * dW2 self.b2 - lr * db2 def train(self, X, y, epochs100, lr0.1): for i in range(epochs): output self.forward(X) dW1, db1, dW2, db2 self.backward(X, y) self.update_params(dW1, db1, dW2, db2, lr) if i % 10 0: loss self.cross_entropy_loss(output, y) print(fEpoch {i}, Loss: {loss:.4f}) def accuracy(self, X, y): predictions np.argmax(self.forward(X), axis1) labels np.argmax(y, axis1) return np.mean(predictions labels) # 初始化并训练网络 input_size X_train.shape[1] hidden_size 128 output_size 10 nn NeuralNetwork(input_size, hidden_size, output_size) nn.train(X_train, y_train, epochs100, lr0.1) # 评估模型 train_acc nn.accuracy(X_train, y_train) test_acc nn.accuracy(X_test, y_test) print(fTraining Accuracy: {train_acc*100:.2f}%) print(fTest Accuracy: {test_acc*100:.2f}%)典型训练输出Epoch 0, Loss: 2.3026 Epoch 10, Loss: 0.4562 Epoch 20, Loss: 0.3124 ... Epoch 90, Loss: 0.0987 Training Accuracy: 97.23% Test Accuracy: 95.86%