共计 3287 个字符,预计需要花费 9 分钟才能阅读完成。
1. 为什么选择神经网络处理 MNIST?
传统机器学习方法(如 SVM)在处理 MNIST 这类图像数据时存在明显瓶颈:

- 特征依赖性强:需要手动设计特征(如 HOG、SIFT)
- 维度灾难:784 维像素点直接输入会导致计算复杂度陡增
- 非线性分割困难:手写数字的形态差异大,线性分类器效果有限
而全连接神经网络(FNN)通过以下优势成为更好的选择:
- 自动特征提取:网络自行学习像素间的组合关系
- 非线性表达能力:ReLU 激活函数可拟合复杂决策边界
- 端到端训练:从原始输入到分类结果一体化优化
2. 环境准备与数据加载
2.1 基础环境
确保安装 PyTorch 2.0+(推荐使用 conda 环境):
conda install pytorch torchvision -c pytorch
2.2 数据预处理
关键步骤分解:
- 标准化:将像素值从 [0,255] 缩放到 [0,1] 并做均值方差归一化
- 数据增强:虽然 MNIST 较简单,仍可添加随机旋转(后续对比实验用)
- 批量加载:利用 DataLoader 实现并行数据加载
完整实现代码:
import torch
from torchvision import datasets, transforms
# 定义转换管道
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)) # MNIST 的均值和标准差
])
# 加载数据集
train_set = datasets.MNIST('./data', train=True, download=True, transform=transform)
test_set = datasets.MNIST('./data', train=False, transform=transform)
# 创建数据加载器
train_loader = torch.utils.data.DataLoader(train_set, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=1000, shuffle=False)
3. 网络架构实现
3.1 网络结构设计
采用经典的三层前馈结构:
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(784, 512) # 输入层→隐藏层
self.fc2 = nn.Linear(512, 10) # 隐藏层→输出层
self.dropout = nn.Dropout(0.2) # 防过拟合
def forward(self, x):
x = x.view(-1, 784) # 展平输入 [batch, 1,28,28]→[batch,784]
x = F.relu(self.fc1(x)) # ReLU 激活
x = self.dropout(x) # 只在训练时生效
x = F.log_softmax(self.fc2(x), dim=1) # 输出概率分布
return x
3.2 关键组件解析
- ReLU 激活函数:解决梯度消失问题,计算高效
- LogSoftmax 输出:配合 CrossEntropyLoss 实现数值稳定
- Dropout 层:随机屏蔽神经元,增强泛化能力
4. 模型训练全流程
4.1 初始化配置
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = Net().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()
学习率选择经验:
- 常见范围:1e- 5 到 1e-3
- MNIST 较简单,可适当增大
- 使用 Adam 优化器对学习率不敏感
4.2 训练循环实现
def train(epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
if batch_idx % 100 == 0:
print(f'Train Epoch: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)}]\tLoss: {loss.item():.6f}')
4.3 验证与可视化
绘制准确率 /loss 曲线:
import matplotlib.pyplot as plt
def test():
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += criterion(output, target).item()
pred = output.argmax(dim=1, keepdim=True)
correct += pred.eq(target.view_as(pred)).sum().item()
test_loss /= len(test_loader.dataset)
accuracy = 100. * correct / len(test_loader.dataset)
# 记录用于绘图
test_losses.append(test_loss)
accuracies.append(accuracy)
print(f'Test set: Average loss: {test_loss:.4f}, Accuracy: {correct}/{len(test_loader.dataset)} ({accuracy:.0f}%)')
5. 避坑指南
5.1 张量维度问题
常见错误场景:
# 错误示例:未展平输入
x = torch.randn(64, 1, 28, 28)
out = model(x) # 报错:mat1 和 mat2 形状不匹配
# 正确做法:x = x.view(-1, 784) # 或使用 Flatten 层
5.2 过拟合识别
诊断方法:
- 训练准确率持续上升但测试准确率停滞
- 损失函数值差距逐渐增大
解决方案对比:
| 方法 | 验证准确率 | 训练时间 |
|---|---|---|
| Baseline | 97.8% | 2min |
| +Dropout | 98.1% | 2.2min |
| +DataAugment | 98.3% | 3min |
5.3 GPU 使用注意事项
设备转移三要素:
- 模型.to(device)
- 数据.to(device)
- 确保所有张量在同一设备
6. 延伸实验建议
6.1 网络结构调整
尝试不同架构:
# 加深网络
self.fc1 = nn.Linear(784, 1024)
self.fc2 = nn.Linear(1024, 512)
self.fc3 = nn.Linear(512, 10)
# 加宽网络
self.fc1 = nn.Linear(784, 2048)
6.2 与 CNN 的对比
性能对比表:
| 模型类型 | 参数量 | 测试准确率 | 训练时间 /epoch |
|---|---|---|---|
| FNN | 669K | 98.2% | 25s |
| CNN | 1.2M | 99.1% | 40s |
7. 完整代码获取
项目已开源在 GitHub:[项目链接] 包含:
- Jupyter Notebook 交互式教程
- 不同网络结构的对比实验
- 训练曲线可视化工具
通过本实践,你已掌握:
- PyTorch 基础开发流程
- 全连接神经网络构建方法
- 图像分类任务的标准处理流程
接下来可以尝试:
- 在 FashionMNIST 上测试模型泛化能力
- 实现动态学习率调整(如 ReduceLROnPlateau)
- 导出 ONNX 模型进行部署
正文完
发表至: 未分类
近一天内
