首页 智能家居

从零手撸:基于传统神经网络的 MNIST 手写数字识别项目实战

分类:智能家居
字数: (4424)
阅读: (6066)
内容摘要:从零手撸:基于传统神经网络的 MNIST 手写数字识别项目实战,

在机器学习的入门实践中,手写数字识别(MNIST)项目可谓经典。然而,许多初学者在直接使用高级框架(如 TensorFlow 或 PyTorch)时,往往忽略了底层原理,导致遇到问题时束手无策。例如,面对模型训练速度慢、准确率不高的情况,不知道如何优化,更别提将其部署到实际的生产环境中,利用如 Nginx 进行反向代理和负载均衡,提升并发连接数处理能力。

MNIST 数据集与基本原理

MNSIT 数据集包含了 60,000 个训练图像和 10,000 个测试图像,每个图像都是 28x28 像素的灰度图像,代表 0-9 十个数字。我们的目标是构建一个神经网络,能够准确地识别这些数字。

从零手撸:基于传统神经网络的 MNIST 手写数字识别项目实战

最简单的神经网络架构包括一个输入层、一个或多个隐藏层和一个输出层。输入层接收 28x28=784 个像素值,输出层包含 10 个神经元,分别对应 0-9 十个数字,输出值表示该数字的概率。

从零手撸:基于传统神经网络的 MNIST 手写数字识别项目实战

代码实现:从 NumPy 开始

为了彻底理解神经网络的运作方式,我们将使用 NumPy 从头开始实现一个简单的 MNIST 手写数字识别模型。避免直接使用宝塔面板一键部署,而是手动配置环境,可以更好地掌握依赖关系。

从零手撸:基于传统神经网络的 MNIST 手写数字识别项目实战
import numpy as np

# 定义 Sigmoid 激活函数
def sigmoid(x):
 return 1 / (1 + np.exp(-x))

# 定义 Sigmoid 函数的导数
def sigmoid_derivative(x):
 return x * (1 - x)

# 加载 MNIST 数据集(这里假设你已经有了 mnist.npz 文件)
def load_mnist(path='mnist.npz'):
 with np.load(path, allow_pickle=True) as f:
 x_train, y_train = f['x_train'], f['y_train']
 x_test, y_test = f['x_test'], f['y_test']
 return x_train, y_train, x_test, y_test

# 预处理数据
def preprocess_data(x_train, y_train, x_test, y_test):
 x_train = x_train.astype('float32') / 255 # 归一化
 x_test = x_test.astype('float32') / 255
 x_train = x_train.reshape((x_train.shape[0], 784)) # 展平图像
 x_test = x_test.reshape((x_test.shape[0], 784))

 # One-hot 编码
 def one_hot_encode(y):
 num_labels = len(np.unique(y))
 identity = np.eye(num_labels)
 return identity[y]

 y_train = one_hot_encode(y_train)
 y_test = one_hot_encode(y_test)

 return x_train, y_train, x_test, y_test


# 初始化权重
def initialize_weights(input_size, hidden_size, output_size):
 weights1 = np.random.randn(input_size, hidden_size) * 0.01 # 避免梯度消失
 biases1 = np.zeros((1, hidden_size))
 weights2 = np.random.randn(hidden_size, output_size) * 0.01
 biases2 = np.zeros((1, output_size))
 return weights1, biases1, weights2, biases2

# 前向传播
def forward_propagation(x, weights1, biases1, weights2, biases2):
 layer1 = sigmoid(np.dot(x, weights1) + biases1)
 output = sigmoid(np.dot(layer1, weights2) + biases2)
 return layer1, output

# 反向传播
def backward_propagation(x, y, layer1, output, weights2):
 output_error = output - y
 output_delta = output_error * sigmoid_derivative(output)

 layer1_error = output_delta.dot(weights2.T)
 layer1_delta = layer1_error * sigmoid_derivative(layer1)

 return output_delta, layer1_delta

# 更新权重
def update_weights(weights1, biases1, weights2, biases2, x, layer1, output_delta, layer1_delta, learning_rate):
 weights2 -= layer1.T.dot(output_delta) * learning_rate
 biases2 -= np.sum(output_delta, axis=0, keepdims=True) * learning_rate
 weights1 -= x.T.dot(layer1_delta) * learning_rate
 biases1 -= np.sum(layer1_delta, axis=0, keepdims=True) * learning_rate
 return weights1, biases1, weights2, biases2

# 训练模型
def train(x_train, y_train, hidden_size, learning_rate, epochs):
 input_size = x_train.shape[1]
 output_size = y_train.shape[1]

 weights1, biases1, weights2, biases2 = initialize_weights(input_size, hidden_size, output_size)

 for epoch in range(epochs):
 layer1, output = forward_propagation(x_train, weights1, biases1, weights2, biases2)
 output_delta, layer1_delta = backward_propagation(x_train, y_train, layer1, output, weights2)
 weights1, biases1, weights2, biases2 = update_weights(weights1, biases1, weights2, biases2, x_train, layer1, output_delta, layer1_delta, learning_rate)

 if epoch % 10 == 0:
 loss = np.mean(np.square(output - y_train)) # 均方误差
 print(f'Epoch {epoch}, Loss: {loss}')

 return weights1, biases1, weights2, biases2

# 预测
def predict(x_test, weights1, biases1, weights2, biases2):
 layer1, output = forward_propagation(x_test, weights1, biases1, weights2, biases2)
 predictions = np.argmax(output, axis=1)
 return predictions

# 评估模型
def evaluate(x_test, y_test, weights1, biases1, weights2, biases2):
 predictions = predict(x_test, weights1, biases1, weights2, biases2)
 y_true = np.argmax(y_test, axis=1)
 accuracy = np.mean(predictions == y_true)
 print(f'Accuracy: {accuracy}')

# 主函数
if __name__ == '__main__':
 x_train, y_train, x_test, y_test = load_mnist()
 x_train, y_train, x_test, y_test = preprocess_data(x_train, y_train, x_test, y_test)

 hidden_size = 64
 learning_rate = 0.1
 epochs = 100

 weights1, biases1, weights2, biases2 = train(x_train, y_train, hidden_size, learning_rate, epochs)
 evaluate(x_test, y_test, weights1, biases1, weights2, biases2)

训练与优化

训练过程: 在训练过程中,我们需要调整隐藏层的大小(hidden_size)、学习率(learning_rate)和训练轮数(epochs)等超参数。合适的超参数能够帮助模型更快地收敛并达到更高的准确率。

从零手撸:基于传统神经网络的 MNIST 手写数字识别项目实战

优化技巧:

  • 数据预处理: 归一化数据可以加速训练过程,防止梯度爆炸。可以使用 sklearn.preprocessing 中的 StandardScalerMinMaxScaler
  • 权重初始化: 使用 Xavier 或 He 初始化方法可以更好地初始化权重,避免梯度消失或梯度爆炸。
  • 激活函数: ReLU 激活函数通常比 Sigmoid 函数更有效,可以加快训练速度。
  • 优化器: 使用 Adam 或 RMSprop 优化器通常比梯度下降算法更有效。
  • 正则化: L1 或 L2 正则化可以防止过拟合。

实战避坑:常见问题与解决方案

  1. 梯度消失/爆炸: 检查学习率是否过大,尝试使用更小的学习率或使用 ReLU 激活函数。同时可以考虑Batch Normalization。
  2. 过拟合: 增加训练数据,使用正则化,或者使用 Dropout。
  3. 训练速度慢: 优化代码,使用 GPU 加速,或者减少模型复杂度。
  4. 准确率低: 检查数据预处理是否正确,调整超参数,或者尝试更复杂的模型结构。排查数据质量问题。

通过以上步骤,我们可以使用 NumPy 从头开始实现一个简单的 MNIST 手写数字识别模型。虽然这个模型比较简单,但它能够帮助我们理解神经网络的基本原理。在实际应用中,我们可以使用更高级的框架和更复杂的模型结构来提高准确率。

总结与展望

虽然现在深度学习框架已经封装了许多底层细节,但理解传统神经网络的原理仍然至关重要。只有掌握了底层原理,才能更好地理解和使用这些框架,并解决实际问题。对于高并发场景,例如大型电商平台,在模型部署时,除了算法优化,还需要考虑 Nginx 的配置,比如调整 worker 进程数、连接超时时间等,以及配合 Redis 等缓存服务,才能保证系统的稳定性和性能。

从零手撸:基于传统神经网络的 MNIST 手写数字识别项目实战

转载请注明出处: 代码一只喵

本文的链接地址: http://m.acea5.store/blog/080788.SHTML

本文最后 发布于2026-04-04 05:45:15,已经过了23天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 煎饼果子 5 天前
    NumPy 实现虽然看起来代码量大,但的确能帮助理解神经网络的本质。
  • 格子衫青年 6 天前
    NumPy 实现虽然看起来代码量大,但的确能帮助理解神经网络的本质。
  • 夏天的风 5 天前
    请问大佬,如果将这个模型部署到服务器上,使用 Flask 搭建 API,然后用 Nginx 做反向代理,需要注意哪些问题?