一、前言
MNIST 手写数字数据集是深度学习入门经典数据集,包含 60000 张训练集、10000 张测试集灰度手写数字图片(0~9)。本文基于 PyTorch 分别实现全连接神经网络(FC) 和 卷积神经网络(CNN) 完成手写数字分类,完整包含数据集加载、数据可视化、模型搭建、训练、测试全流程,适合深度学习新手入门学习。
环境依赖
python
运行
torch
torchvision
torchaudio
matplotlib
二、完整项目结构与整体思路
- 导入相关库,加载并解析 MNIST 官方数据集
- 数据可视化,查看样本图片与标签
- 使用
DataLoader批量封装数据,提升训练效率 - 自动适配 CPU/GPU/MPS 设备,加速运算
- 搭建全连接神经网络,完成训练与评估
- 搭建卷积神经网络 CNN,完成训练与评估
- 对比两种网络在手写数字任务上的效果
三、代码实现与逐行讲解
3.1 库导入与数据集加载
首先导入 PyTorch 核心库、视觉工具库、数据加载工具,并下载加载 MNIST 训练集和测试集。
python
运行
# 导入深度学习相关库
import torch
import torchvision
import torchaudio
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
from matplotlib import pyplot as plt
# 加载训练数据集:60000张手写数字图片
training_data = datasets.MNIST(
root="data", # 数据集存放路径
train=True, # 标记为训练集
download=True, # 自动在线下载数据集
transform=ToTensor()# 将PIL图像转为Tensor张量(模型可识别格式)
)
# 加载测试数据集:10000张手写数字图片
test_data = datasets.MNIST(
root="data",
train=False, # 标记为测试集
download=True,
transform=ToTensor()
)
# 打印训练集样本总数
print(f"训练集样本数量:{len(training_data)}")
核心参数说明
root:数据集本地存储目录,首次运行会自动创建data文件夹;train:True加载训练集,False加载测试集;ToTensor():将像素值归一化到[0,1],同时转换为 PyTorch 张量。
3.2 数据集可视化
通过 matplotlib 展示前 9 张手写数字图片,直观查看数据样式:
python
运行
# 创建画布,展示9张样本图片
figure = plt.figure(figsize=(6, 6))
for i in range(9):
img, label = training_data[i]
# 3行3列子图
figure.add_subplot(3, 3, i + 1)
plt.title(f"Label: {label}")
plt.axis("off") # 隐藏坐标轴
# squeeze() 去除维度为1的通道,灰度图使用灰度配色
plt.imshow(img.squeeze(), cmap="gray")
plt.show()
运行代码会弹出窗口,展示 9 张 28×28 手写数字灰度图,并标注对应真实标签。
3.3 数据加载器 DataLoader
数据集单张读取效率低,DataLoader 可以分批打包数据(batch),降低内存占用、加速训练:
python
运行
# 批次大小:每一批加载64张图片
batch_size = 64
# 训练集、测试集批量加载器
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)
# 查看数据维度格式 [N, C, H, W]
for X, y in test_dataloader:
print(f"数据形状 [批次, 通道, 高度, 宽度]:{X.shape}")
print(f"标签形状 & 数据类型:{y.shape} {y.dtype}")
break
输出解释
N:批次大小(64);C:通道数(灰度图 = 1);H/W:图片高宽(28×28);- 标签
y为一维张量,代表每张图片对应的数字类别(0~9)。
3.4 设备自动适配(CPU/GPU/MPS)
优先使用 NVIDIA CUDA 显卡,其次苹果 M 系列芯片 MPS,最后使用 CPU:
python
运行
# 自动判断可用设备
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"当前使用设备:{device}")
模型和数据都需要迁移到对应设备,才能使用硬件加速。
3.5 方案一:全连接神经网络(FC)实现分类
全连接网络(多层感知机 MLP)是最基础的神经网络,将 28×28 图片展平为一维向量,通过多层线性层提取特征。
3.5.1 定义全连接网络模型
python
运行
# 自定义全连接神经网络,继承nn.Module
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
# 展平层:将 1*28*28 图片转为一维向量 784
self.flatten = nn.Flatten()
# 第一层全连接:784 -> 128 神经元
self.hidden1 = nn.Linear(28 * 28, 128)
# 第二层全连接:128 -> 256 神经元
self.hidden2 = nn.Linear(128, 256)
# 输出层:256 -> 10 (对应0-9共10个分类)
self.out = nn.Linear(256, 10)
# 前向传播(数据流转逻辑,函数名固定为forward)
def forward(self, x):
x = self.flatten(x)
x = self.hidden1(x)
x = torch.sigmoid(x) # 激活函数
x = self.hidden2(x)
x = torch.sigmoid(x)
x = self.out(x)
return x
# 初始化模型并迁移到对应设备
fc_model = NeuralNetwork().to(device)
print("全连接网络结构:")
print(fc_model)
3.5.2 训练、测试通用函数
封装训练函数和测试函数,复用代码:
python
运行
# 训练函数
def train(dataloader, model, loss_fn, optimizer):
model.train() # 切换为训练模式(启用权重更新)
batch_num = 1
for X, y in dataloader:
# 数据、标签迁移到设备
X, y = X.to(device), y.to(device)
pred = model(X) # 前向传播预测
loss = loss_fn(pred, y) # 计算损失
# 反向传播 + 参数更新
optimizer.zero_grad() # 清空历史梯度
loss.backward() # 反向传播计算梯度
optimizer.step() # 根据梯度更新权重
# 每100个batch打印损失
if batch_num % 100 == 0:
print(f"批次:{batch_num} 损失值: {loss.item():.6f}")
batch_num += 1
# 测试/评估函数
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
model.eval() # 切换为评估模式(冻结权重)
test_loss, correct = 0, 0
# 关闭梯度计算,节省算力
with torch.no_grad():
for X, y in dataloader:
X, y = X.to(device), y.to(device)
pred = model(X)
test_loss += loss_fn(pred, y).item()
# argmax(1) 获取预测类别,统计预测正确数量
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
# 计算平均损失和准确率
test_loss /= num_batches
acc = (correct / size) * 100
print(f"测试集准确率: {acc:.2f}% 平均损失: {test_loss:.6f}\n")
3.5.3 损失函数、优化器 + 启动训练
python
运行
# 损失函数:交叉熵(分类任务标准损失)
loss_fn = nn.CrossEntropyLoss()
# 优化器:Adam 自适应学习率优化器
optimizer = torch.optim.Adam(fc_model.parameters(), lr=0.005)
# 迭代训练10轮
epochs = 10
for t in range(epochs):
print(f"========== 第 {t+1} 轮训练 ==========")
train(train_dataloader, fc_model, loss_fn, optimizer)
test(test_dataloader, fc_model, loss_fn)
全连接网络特点
- 结构简单、易理解;
- 缺点:丢失图像空间特征,对图片类任务精度有限。
3.6 方案二:CNN 卷积神经网络实现分类
卷积神经网络(CNN)专门针对图像任务设计,依靠卷积层、池化层提取图像局部特征、纹理、轮廓,分类效果远优于全连接网络。
3.6.1 定义 CNN 卷积模型
python
运行
# 自定义卷积神经网络
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
# 卷积块1:卷积+激活+池化
self.conv1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5, stride=1, padding=2),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2) # 最大池化,下采样
)
# 卷积块2:双层卷积+激活+池化
self.conv2 = nn.Sequential(
nn.Conv2d(16, 32, 5, 1, 2),
nn.ReLU(),
nn.Conv2d(32, 32, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(2)
)
# 卷积块3:单层卷积+激活
self.conv3 = nn.Sequential(
nn.Conv2d(32, 64, 5, 1, 2),
nn.ReLU()
)
# 全连接输出层:64*7*7 -> 10分类
self.out = nn.Linear(64 * 7 * 7, 10)
# 前向传播
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
# 展平:batch维度保留,其余维度合并
x = x.view(x.size(0), -1)
output = self.out(x)
return output
# 初始化CNN模型并迁移设备
cnn_model = CNN().to(device)
print("CNN卷积网络结构:")
print(cnn_model)
CNN 层参数说明
Conv2d:二维卷积,in_channels输入通道,out_channels卷积核数量;kernel_size=5:5×5 卷积核;padding=2补零,保证卷积后尺寸不变;MaxPool2d:池化下采样,缩小特征图尺寸、减少参数量。
3.6.2 CNN 模型训练与测试
复用上方已定义的 train()、test() 函数,仅替换模型、优化器即可:
python
运行
# 重新定义优化器(CNN模型参数)
optimizer = torch.optim.Adam(cnn_model.parameters(), lr=0.001)
# 单轮训练+测试演示,可自行增加轮数
print("========== CNN 模型训练开始 ==========")
train(train_dataloader, cnn_model, loss_fn, optimizer)
test(test_dataloader, cnn_model, loss_fn)
四、运行结果分析
-
全连接网络
- 训练收敛较慢,最终测试准确率一般在
96%~98%左右; - 无法利用图像空间信息,复杂手写数字识别效果一般。
- 训练收敛较慢,最终测试准确率一般在
-
CNN 卷积网络
- 收敛速度更快,单轮训练即可达到
98%+准确率; - 依靠卷积提取图像局部特征,是图像分类任务的主流方案。
- 收敛速度更快,单轮训练即可达到
五、知识点总结
- MNIST 数据集:入门级手写数字数据集,28×28 灰度图,10 分类任务;
- DataLoader:批量加载数据,深度学习标准数据读取方式;
- 设备适配:PyTorch 支持 CUDA、MPS、CPU 自动切换,充分利用硬件加速;
- 全连接网络:将图像转为一维向量,结构简单,不适合图像特征提取;
- CNN 卷积网络:卷积 + 池化提取图像空间特征,图像分类首选;
- 训练流程固定模板:前向传播 → 计算损失 → 梯度清零 → 反向传播 → 参数更新。
六、拓展优化方向
- 增加训练轮数
epochs,进一步提升模型精度; - 加入
Dropout层防止过拟合; - 调整学习率、批次大小、网络层数做调参实验;
- 保存训练好的模型权重,实现离线预测单张图片。