一、项目背景:为什么选择手写数字识别?​

在深度学习领域,手写数字识别就像 “Hello World” 一样,是入门的必经之路。它的任务很简单:让计算机识别出手写的 0-9 这 10 个数字。​

为什么说它适合入门呢?主要有三个原因:​

  1. 数据集成熟:MNIST 数据集是深度学习领域的 “标杆数据集”,包含 60000 张训练图片和 10000 张测试图片,每张图片是 28×28 像素的灰度图,标签明确,无需额外处理。​
  1. 任务难度适中:数字识别的分类任务相对简单,不需要复杂的模型结构,用基础的 FNN 就能达到不错的效果,容易获得成就感。​
  1. 覆盖核心流程:从数据加载、预处理,到模型搭建、训练、评估,再到结果可视化,整个过程完整覆盖了深度学习项目的核心环节,能帮我们快速熟悉开发流程。

    二、准备工作:环境与工具​

    在开始之前,我们需要准备好开发环境。这里我选择 Python 作为开发语言,搭配以下常用库:​

  2. TensorFlow/PyTorch:深度学习框架,用于搭建和训练模型(本文以 TensorFlow 为例,PyTorch 版本会在文末补充)。​
  3. NumPy:用于数值计算,处理数据矩阵。​
  4. Matplotlib:用于数据可视化,展示图片和训练曲线。​
  5. Scikit-learn:用于简单的数据预处理(如归一化)。​
  6. 安装命令(以 TensorFlow 为例):pip install tensorflow numpy matplotlib scikit-learn

    三、步骤拆解:从数据到模型​

    1. 加载并探索 MNIST 数据集​

    首先,我们用 TensorFlow 自带的接口加载 MNIST 数据集。加载后的数据会自动分成训练集和测试集,并且像素值范围在 0-255 之间。

    import tensorflow as tf

    from tensorflow.keras.datasets import mnist

    import matplotlib.pyplot as plt

    import numpy as np

    # 加载数据集

    (x_train, y_train), (x_test, y_test) = mnist.load_data()

    # 探索数据形状

    print("训练集图片形状:", x_train.shape) # (60000, 28, 28):60000张28×28的图片

    print("训练集标签形状:", y_train.shape) # (60000,):60000个标签

    print("测试集图片形状:", x_test.shape) # (10000, 28, 28)

    print("测试集标签形状:", y_test.shape) # (10000,)

    # 可视化一张图片

    plt.imshow(x_train[0], cmap='gray')

    plt.title(f"标签:{y_train[0]}")

    plt.axis('off')

    plt.show()

    运行代码后,我们会看到一张手写数字 “5” 的图片(因为 x_train [0] 的标签是 5),这就是我们要让模型学习识别的数据。​

    2. 数据预处理:让模型 “更好学”​

    原始数据无法直接输入 FNN,我们需要做两步预处理:​

  7. 扁平化(Flatten):FNN 的输入是一维向量,而 MNIST 图片是 28×28 的二维矩阵,所以需要把每张图片转换成 784(28×28)个元素的一维向量。​
  8. 归一化(Normalization):将像素值从 0-255 缩放到 0-1 之间。这样做可以加快模型的训练速度,避免因数值过大导致的梯度爆炸问题。

    # 1. 扁平化:将28×28的二维矩阵转为784维的一维向量

    x_train_flatten = x_train.reshape(x_train.shape[0], -1) # (60000, 784)

    x_test_flatten = x_test.reshape(x_test.shape[0], -1) # (10000, 784)

    # 2. 归一化:像素值从0-255转为0-1

    x_train_normal = x_train_flatten / 255.0

    x_test_normal = x_test_flatten / 255.0

    # 3. 标签独热编码(One-Hot Encoding)

    # 因为是10分类任务,需要将标签转为10维向量(如标签5转为[0,0,0,0,0,1,0,0,0,0])

    y_train_onehot = tf.keras.utils.to_categorical(y_train, 10)

    y_test_onehot = tf.keras.utils.to_categorical(y_test, 10)

    print("预处理后训练集输入形状:", x_train_normal.shape) # (60000, 784)

    print("预处理后训练集标签形状:", y_train_onehot.shape) # (60000, 10)

    3. 搭建 FNN 模型:核心结构解析​

    FNN 的核心是 “输入层 - 隐藏层 - 输出层” 的全连接结构,每一层的神经元都与上一层的所有神经元相连。​

  9. 输入层:784 个神经元(对应扁平化后的图片向量)。​隐藏层:2 层,第一层 128 个神经元,第二层 64 个神经元,激活函数用 ReLU(常用的非线性激活函数,能解决梯度消失问题)。​

  10. 输出层:10 个神经元(对应 0-9 这 10 个数字),激活函数用 Softmax(将输出转为概率分布,总和为 1,方便判断类别)。

    from tensorflow.keras.models import Sequential

    from tensorflow.keras.layers import Dense

    # 搭建模型

    model = Sequential([

    # 输入层(可省略,模型会自动根据输入数据推断)

    # Dense(128, input_shape=(784,), activation='relu'),

    # 隐藏层1:128个神经元,ReLU激活

    Dense(128, activation='relu'),

    # 隐藏层2:64个神经元,ReLU激活

    Dense(64, activation='relu'),

    # 输出层:10个神经元,Softmax激活

    Dense(10, activation='softmax')

    ])

    # 查看模型结构

    model.summary()

    运行model.summary()后,我们可以看到模型的各层参数:​

  11. 隐藏层 1:784(输入)×128(神经元) + 128(偏置)= 100480 个参数。​
  12. 隐藏层 2:128×64 + 64 = 8256 个参数。​
  13. 输出层:64×10 + 10 = 650 个参数。​
  14. 总参数约 10.9 万,对于 FNN 来说,这个规模非常适合入门。​

    4. 编译与训练模型:让模型 “学会” 识别​

    模型搭建好后,需要先 “编译”,指定训练时的优化器、损失函数和评估指标;然后用训练集 “训练” 模型,让它从数据中学习规律。

    # 编译模型

    model.compile(

    optimizer='adam', # 优化器:Adam(常用且高效)

    loss='categorical_crossentropy', # 损失函数:适用于多分类的交叉熵

    metrics=['accuracy'] # 评估指标:准确率

    )

    # 训练模型

    history = model.fit(

    x_train_normal, y_train_onehot, # 训练数据和标签

    epochs=10, # 训练轮次:整个训练集遍历10次

    batch_size=32, # 批次大小:每次训练32张图片

    validation_split=0.1 # 验证集比例:从训练集中划分10%作为验证集

    )

  15. epochs:训练轮次。轮次太少会导致模型 “学不会”(欠拟合),轮次太多会导致 “学过头”(过拟合),这里先设为 10。​
  16. batch_size:批次大小。每次用 32 张图片更新一次模型参数,平衡训练速度和稳定性。​
  17. validation_split:验证集用于在训练过程中监控模型性能,避免过拟合。​
  18. 5. 模型评估:看看模型 “学得怎么样”​

    训练完成后,我们需要用测试集评估模型的泛化能力(即对新数据的识别能力),同时可视化训练过程中的准确率和损失变化。

    # 用测试集评估模型

    test_loss, test_acc = model.evaluate(x_test_normal, y_test_onehot)

    print(f"测试集准确率:{test_acc:.4f}") # 通常能达到97%以上

    # 可视化训练过程

    plt.figure(figsize=(12, 4))

    # 1. 准确率曲线

    plt.subplot(1, 2, 1)

    plt.plot(history.history['accuracy'], label='训练准确率')

    plt.plot(history.history['val_accuracy'], label='验证准确率')

    plt.title('准确率变化')

    plt.xlabel('epoch')

    plt.ylabel('accuracy')

    plt.legend()

    # 2. 损失曲线

    plt.subplot(1, 2, 2)

    plt.plot(history.history['loss'], label='训练损失')

    plt.plot(history.history['val_loss'], label='验证损失')

    plt.title('损失变化')

    plt.xlabel('epoch')

    plt.ylabel('loss')

    plt.legend()

    plt.show()

    从结果中我们可以看到:​

  19. 训练准确率和验证准确率都在不断上升,说明模型在持续学习。​
  20. 如果验证准确率在后期开始下降,而训练准确率还在上升,说明出现了过拟合,需要调整模型(如增加正则化)。​
  21. 测试集准确率通常能达到 97% 以上,对于一个简单的 FNN 来说,这个效果已经很不错了。​
  22. 6. 模型预测:用新数据测试​

    最后,我们用测试集中的图片来测试模型的预测效果,看看它能不能正确识别手写数字。

    # 随机选择5张测试图片进行预测

    np.random.seed(42) # 固定随机种子,保证结果可复现

    random_idx = np.random.choice(x_test_normal.shape[0], 5)

    test_imgs = x_test_normal[random_idx]

    test_labels = y_test[random_idx]

    # 预测

    predictions = model.predict(test_imgs)

    # 取概率最大的索引作为预测结果

    predict_labels = np.argmax(predictions, axis=1)

    # 可视化预测结果

    plt.figure(figsize=(10, 4))

    for i in range(5):

    plt.subplot(1, 5, i+1)

    # 还原为28×28的图片

    plt.imshow(test_imgs[i].reshape(28, 28), cmap='gray')

    # 标题:真实标签 vs 预测标签

    plt.title(f"真实:{test_labels[i]}\n预测:{predict_labels[i]}")

    plt.axis('off')

    plt.show()

    四、模型优化:如何让准确率更高?​

    如果想进一步提升模型性能,可以尝试以下优化方法:​

  23. 增加隐藏层或神经元数量:但要注意避免过拟合。​
  24. 添加正则化:在 Dense 层中加入kernel_regularizer=tf.keras.regularizers.l2(0.001),抑制参数过大。​
  25. 使用 dropout:在隐藏层后加入Dropout(0.2),随机 “关闭” 部分神经元,防止过拟合。​
  26. 调整超参数:比如将 epochs 改为 15,batch_size 改为 64,或更换优化器(如 SGD)。​
  27. 数据增强:虽然 MNIST 数据简单,但也可以通过旋转、平移等方式生成更多训练数据。​
  28. 优化后的模型测试准确率通常能达到 98% 以上,甚至接近 99%。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐