第 T1 周:实现 MNIST 手写数字识别
🔗 声明
- 本文为「365 天深度学习训练营」内部学习记录。
- 本文参考 K 同学啊课程内容完成,仅用于个人学习与交流。
- MNIST 为公开数据集,请勿将本文用于商业传播。
- 本篇为个人在 T1 关卡上的实践记录。
第 T1 周:实现 MNIST 手写数字识别
这是 TensorFlow 的第一篇教案,也是深度学习的入门案例。本周不追求一次吃透所有原理,先把程序当黑盒跑通,再逐步建立直觉。
🍺 本周任务
- 跑通程序
- 了解「深度学习」是什么(先有感性认识即可)
👉 建议学习步骤
- 第 1 遍:整体把握每一部分代码在做什么(数据 → 模型 → 训练 → 预测)。
- 第 2 遍:把握每一行语句的含义,知道实现的是什么功能即可,不求甚解。
- 第 3 遍:动手跟着教案敲一遍代码,把程序跑通。
📑 备注
如果你之前没有接触过深度学习,第一篇学起来可能比较蒙,不要着急。等到第 3、4 篇教案时,你会逐步理解,不要尝试一下就弄明白。现阶段任务是:把程序当黑盒,先学会使用,再理解原理。
🏡 我的环境
- 语言环境:Python 3.9.13
- 编译器:Jupyter Lab
- 深度学习环境:TensorFlow 2.10.0
一、前期工作:设置 GPU
若使用 CPU,可跳过本步。
import tensorflow as tf
gpus = tf.config.list_physical_devices("GPU")
if gpus:
gpu0 = gpus[0] # 如果有多个 GPU,仅使用第 0 个 GPU
tf.config.experimental.set_memory_growth(gpu0, True) # 显存按需增长
tf.config.set_visible_devices([gpu0], "GPU")
print(gpus)
二、导入数据
import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt
import numpy as np
# 导入 MNIST:训练集图片/标签、测试集图片/标签
(train_images, train_labels), (test_images, test_labels) = datasets.mnist.load_data()
说明:
- 首次运行会自动下载数据,默认缓存目录一般为
~/.keras/datasets/。 - 本地
data.zip为课程备用包;本教案使用load_data()即可,无需手动解压到data/文件夹。
三、归一化
数据归一化的作用:
- 使不同量纲的特征处于同一数值量级,减少方差大的特征的影响,使模型更准确。
- 加快学习算法的收敛速度。
# 灰度图像素范围 0~255,除以 255 归一化到 [0, 1]
train_images, test_images = train_images / 255.0, test_images / 255.0
train_images.shape, test_images.shape, train_labels.shape, test_labels.shape
四、数据可视化
plt.figure(figsize=(20, 10))
for i in range(20):
plt.subplot(2, 10, i + 1)
plt.xticks([])
plt.yticks([])
plt.grid(False)
plt.imshow(train_images[i], cmap=plt.cm.binary)
plt.xlabel(train_labels[i])
plt.show()
五、调整数据格式
卷积层需要输入形状为 (高, 宽, 通道),因此把 (60000, 28, 28) 调整为 (60000, 28, 28, 1)。
train_images = train_images.reshape((60000, 28, 28, 1))
test_images = test_images.reshape((10000, 28, 28, 1))
train_images.shape, test_images.shape, train_labels.shape, test_labels.shape
期望输出类似:
((60000, 28, 28, 1), (10000, 28, 28, 1), (60000,), (10000,))
六、创建卷积神经网络
本文使用的是最简单的 CNN 结构(类似 LeNet-5 思路):
- 卷积层:通过卷积操作对输入图像进行降维和特征抽取。
- 池化层:非线性下采样,压缩数据与参数数量,减轻过拟合,提高鲁棒性。
- 全连接层:在若干卷积与池化之后,完成高级推理。
model = models.Sequential([
layers.Conv2D(32, (3, 3), activation="relu", input_shape=(28, 28, 1)),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(64, (3, 3), activation="relu"),
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dense(64, activation="relu"),
layers.Dense(10),
])
model.summary()
ReLU 作为激活函数可增强非线性,且通常能让训练更快,而对泛化影响相对温和。
七、编译与训练
model.compile() 用于配置训练时的优化器、损失函数和评测指标。
model.compile(
optimizer="adam",
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=["accuracy"],
)
history = model.fit(
train_images,
train_labels,
epochs=10,
validation_data=(test_images, test_labels),
)
关于 model.fit() 的更多参数说明,可参考:Keras model.fit 详解。
训练曲线
from datetime import datetime
current_time = datetime.now()
acc = history.history["accuracy"]
val_acc = history.history["val_accuracy"]
loss = history.history["loss"]
val_loss = history.history["val_loss"]
epochs_range = range(1, len(acc) + 1)
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label="Train Accuracy")
plt.plot(epochs_range, val_acc, label="Test Accuracy")
plt.legend(loc="lower right")
plt.title("Training and Validation Accuracy")
plt.xlabel(current_time)
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label="Train Loss")
plt.plot(epochs_range, val_loss, label="Test Loss")
plt.legend(loc="upper right")
plt.title("Training and Validation Loss")
plt.show()
八、模型预测
可以简单理解为:输入一张图片,模型输出 10 个数,分别对应数字 0~9 的「得分」(logits,并非概率)。数值越大,模型越倾向于该类别。
# 部分 matplotlib 版本会报 Invalid shape (28, 28, 1),可改为 reshape(28, 28)
plt.imshow(test_images[1].reshape(28, 28), cmap=plt.cm.binary)
plt.axis("off")
plt.show()
pre = model.predict(test_images) # 对所有测试图片预测
pre[1] # 第一张测试图的 10 维输出
np.argmax(pre[1]) # 预测类别:0~9 中得分最大的索引
可与真实标签对比:
print("预测数字:", np.argmax(pre[1]))
print("真实标签:", test_labels[1])
九、知识点详解
若是第一次接触深度学习,建议顺序:先跑通 → 再对照结构理解。
MNIST 手写数字数据集
MNIST 来源于美国国家标准与技术研究所(NIST),是经典公开数据集之一。数据由 250 名不同职业者手写绘制,官方页面:http://yann.lecun.com/exdb/mnist/。
- 共 70000 张 28×28 灰度图:训练集 60000、测试集 10000。
- 每张图可拉平为长度 784 的向量,像素值归一化后约在 0~1。
若把训练集看成张量,形状可理解为 [60000, 784]:第一维是图片索引,第二维是像素。
各层的作用
| 层级 | 作用 |
|---|---|
| 输入层 | 将数据送入网络 |
| 卷积层 | 用卷积核提取局部特征 |
| 池化层 | 下采样,用更高层抽象表示图像 |
| Flatten 层 | 将多维特征展平,连接卷积与全连接 |
| 全连接层 | 进一步组合特征 |
| 输出层 | 输出 10 类 logits |
十、常见问题汇总
1. data 文件夹放在哪里?
本教案使用 datasets.mnist.load_data(),不需要手动把 MNIST 放到项目 data/ 目录。首次运行会自动下载到用户目录下的 Keras 缓存路径(常见为 ~/.keras/datasets/mnist.npz)。
若课程提供了 data.zip,那是备用数据包;与 load_data() 二选一即可,不要混用导致路径困惑。
2. plt.imshow 报 Invalid shape (28, 28, 1)
将显示代码改为:
plt.imshow(test_images[1].reshape(28, 28), cmap=plt.cm.binary)
3. 没有 GPU 能跑吗?
可以。跳过 GPU 设置单元,TensorFlow 会自动使用 CPU,MNIST 规模较小,CPU 也能在可接受时间内完成训练。
4. from_logits=True 是什么意思?
最后一层未接 Softmax 时,损失函数内部会做数值稳定的处理;训练时通常更稳定。预测时用 np.argmax 取最大 logit 即可得到类别。
—
十一、本周总结
本周完成了 TensorFlow 入门链路:加载 MNIST → 归一化与可视化 → 搭建 CNN → 编译训练 → 单张/批量预测。
核心收获:
- 建立了「数据预处理 → 模型定义 →
compile/fit→predict」的完整流程; - 理解了归一化、通道维度
reshape、卷积/池化/全连接的分工; - 知道现阶段以跑通黑盒为主,原理可在后续周次逐步加深。
后续可尝试:减少 epochs 做快速实验、对比 CPU/GPU 速度、或改用 PyTorch 版本(如训练营 P1)对照两种框架的写法差异。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)