news 2026/11/4 15:03:31

PyTorch入门:从零构建MNIST手写数字识别神经网络

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyTorch入门:从零构建MNIST手写数字识别神经网络

1. 项目概述

PyTorch作为当前最受欢迎的深度学习框架之一,以其动态计算图和直观的API设计赢得了大量开发者的青睐。我第一次接触PyTorch是在2017年,当时还在为TensorFlow的静态计算图调试而头疼,PyTorch的出现彻底改变了我的工作流程。今天,我将带你从零开始构建一个完整的神经网络,不仅涵盖基础操作,还会分享那些官方文档里找不到的实战技巧。

这个教程适合有一定Python基础但刚接触深度学习的开发者。我们将使用经典的MNIST手写数字数据集,构建一个能够识别0-9数字的简单神经网络。别被"神经网络"这个词吓到,实际上PyTorch让这个过程变得异常简单 - 我见过不少学生在两小时内就能完成第一个可运行的模型。

2. 环境准备与数据加载

2.1 PyTorch安装与验证

PyTorch的安装现在变得非常简单,但有几个关键点需要注意:

pip install torch torchvision

安装完成后,我强烈建议运行以下验证代码:

import torch print(torch.__version__) # 应该显示1.x.x版本 print(torch.cuda.is_available()) # 检查GPU是否可用

注意:如果你有NVIDIA显卡,建议安装对应CUDA版本的PyTorch以获得GPU加速。但初学者使用CPU版本也完全没问题 - 我们的第一个网络训练时间不会超过5分钟。

2.2 数据加载与预处理

MNIST是深度学习界的"Hello World",包含6万张28x28像素的手写数字图片。PyTorch的torchvision已经内置了这个数据集:

from torchvision import datasets, transforms # 定义数据转换 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) # 下载并加载数据 train_data = datasets.MNIST( root='data', train=True, download=True, transform=transform ) test_data = datasets.MNIST( root='data', train=False, transform=transform )

这里有几个值得解释的细节:

  1. ToTensor()将PIL图像转换为PyTorch张量,并自动将像素值从0-255缩放到0-1
  2. Normalize的参数(0.1307,)和(0.3081,)是MNIST数据集的全局平均值和标准差
  3. 我习惯将数据集保存在项目根目录的data文件夹,这样便于管理

2.3 创建数据加载器

数据加载器(DataLoader)是PyTorch中高效加载数据的利器:

from torch.utils.data import DataLoader train_loader = DataLoader( dataset=train_data, batch_size=64, shuffle=True ) test_loader = DataLoader( dataset=test_data, batch_size=1000, shuffle=False )

选择batch_size时需要考虑:

  • 较大的batch_size(如64,128)能利用GPU并行计算
  • 较小的batch_size(如16,32)可能带来更好的泛化性能
  • 对于MNIST这样的简单数据集,64是个不错的起点

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, 256) # 第一隐藏层到第二隐藏层 self.fc3 = nn.Linear(256, 10) # 第二隐藏层到输出层 def forward(self, x): x = x.view(-1, 784) # 展平图像(28x28=784) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) # 最后一层不用激活函数 return F.log_softmax(x, dim=1)

关键点解析:

  1. nn.Linear定义全连接层,参数为(输入特征数, 输出特征数)
  2. view(-1, 784)将二维图像展平为一维向量,-1表示自动计算batch大小
  3. 隐藏层使用ReLU激活函数,它能有效缓解梯度消失问题
  4. 输出层使用log_softmax而不是softmax,这能提高数值稳定性

3.2 模型初始化与GPU转移

创建模型实例后,我们可以将其转移到GPU(如果可用):

device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = Net().to(device) print(model) # 打印网络结构

实操技巧:在模型定义完成后打印它的结构是个好习惯。这能帮你确认各层的连接方式是否正确,参数数量是否符合预期。

4. 训练过程实现

4.1 定义损失函数与优化器

PyTorch提供了多种损失函数和优化器,对于分类问题我们通常这样选择:

import torch.optim as optim criterion = nn.NLLLoss() # 负对数似然损失 optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

为什么选择这些?

  1. NLLLoss与log_softmax配合使用是分类问题的标准组合
  2. SGD(随机梯度下降)虽然简单,但配合momentum参数效果很好
  3. 学习率lr=0.01对大多数问题是个安全的起点

4.2 训练循环实现

完整的训练循环包括前向传播、损失计算、反向传播和参数更新:

def train(model, device, train_loader, optimizer, 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)} ' f'({100. * batch_idx / len(train_loader):.0f}%)]\tLoss: {loss.item():.6f}')

重要细节:

  1. zero_grad()必须在每次迭代开始时调用,否则梯度会累积
  2. backward()自动计算所有参数的梯度
  3. step()根据梯度更新参数
  4. 每100个batch打印一次进度,这能让你了解训练是否在进行

4.3 测试函数实现

我们需要在测试集上评估模型性能:

def test(model, device, test_loader): 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) print(f'\nTest set: Average loss: {test_loss:.4f}, ' f'Accuracy: {correct}/{len(test_loader.dataset)} ' f'({100. * correct / len(test_loader.dataset):.0f}%)\n')

关键点:

  1. eval()将模型设为评估模式,这会影响某些层(如Dropout)的行为
  2. torch.no_grad()上下文管理器能显著减少内存使用
  3. argmax(dim=1)获取预测类别(输出中概率最大的那个)

5. 完整训练流程

现在我们可以将训练和测试组合起来:

epochs = 10 for epoch in range(1, epochs + 1): train(model, device, train_loader, optimizer, epoch) test(model, device, test_loader)

10个epoch通常足以让MNIST达到不错的效果。在我的测试中,这个简单网络能达到约97-98%的准确率。

经验分享:训练过程中如果发现损失不下降或准确率不升,可以尝试:

  1. 检查学习率(太大导致震荡,太小导致收敛慢)
  2. 确认数据预处理是否正确
  3. 检查模型结构是否有逻辑错误

6. 模型保存与加载

训练好的模型可以保存供后续使用:

torch.save(model.state_dict(), 'mnist_model.pth')

加载模型时需要先创��相同结构的网络实例:

loaded_model = Net().to(device) loaded_model.load_state_dict(torch.load('mnist_model.pth')) loaded_model.eval()

注意事项:state_dict只保存参数,不保存结构。因此加载时需要先创建相同结构的模型。

7. 模型预测示例

让我们用训练好的模型进行单张图片预测:

import matplotlib.pyplot as plt # 获取一个测试样本 data, target = next(iter(test_loader)) sample = data[0].to(device) # 预测 with torch.no_grad(): output = model(sample.unsqueeze(0)) prediction = output.argmax(dim=1).item() # 显示结果 plt.imshow(sample.cpu().numpy().reshape(28,28), cmap='gray') plt.title(f'Prediction: {prediction}') plt.show()

unsqueeze(0)是为样本添加一个batch维度(从28x28变为1x28x28),因为模型预期输入是有batch维度的。

8. 性能优化技巧

8.1 学习率调整

固定学习率可能不是最优选择。PyTorch提供了多种学习率调度器:

scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)

然后在每个epoch后调用:

scheduler.step()

这会使学习率每5个epoch乘以0.1,有助于后期精细调整参数。

8.2 使用验证集

更严谨的做法是从训练集中划分验证集:

train_size = int(0.8 * len(train_data)) val_size = len(train_data) - train_size train_dataset, val_dataset = torch.utils.data.random_split(train_data, [train_size, val_size])

然后在每个epoch后评估验证集性能,可以用于早停(early stopping)或模型选择。

8.3 添加正则化

防止过拟合的常用方法是添加Dropout层:

self.dropout = nn.Dropout(0.5) # 在__init__中添加 x = self.dropout(F.relu(self.fc1(x))) # 在forward中使用

通常在全连接层后添加Dropout,保留概率0.5是个不错的起点。

9. 常见问题与解决方案

9.1 损失值不下降

可能原因:

  1. 学习率设置不当 - 尝试调整lr(如0.1,0.01,0.001)
  2. 数据预处理错误 - 检查Normalize参数
  3. 模型结构问题 - 确保各层维度匹配

9.2 GPU内存不足

解决方法:

  1. 减小batch_size
  2. 使用torch.cuda.empty_cache()清理缓存
  3. 检查是否有不需要的张量保留了梯度

9.3 预测结果随机

这通常表示:

  1. 模型没有正确训练(检查训练循环)
  2. 加载模型时忘记调用model.eval()
  3. 输入数据格式不正确

10. 进阶方向

一旦掌握了基础网络,你可以尝试:

  1. 使用卷积神经网络(CNN)提升图像识别性能
  2. 尝试更复杂的架构如ResNet
  3. 在其他数据集(如CIFAR-10)上测试
  4. 实现自定义数据集加载器

我在第一次成功运行MNIST分类器后,花了整整一周时间尝试各种改进 - 调整层数、改变激活函数、尝试不同的优化器。这个过程让我深刻理解了神经网络各个组件的作用。建议你也多做实验,这才是学习深度学习的最佳方式。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 15:03:21

AI工程从0到1:从模型训练到系统交付的完整路径

1. 从“会用模型”到“能交付系统”:AI工程这条路到底长什么样我见过很多人带着“学AI”的念头翻开教程,结果被数学公式劝退,或者被一系列零零散散的模型案例带偏,学了大半年还是只会跑通别人写好的notebook。如果你也有这种感觉&…

作者头像 李华
网站建设 2026/11/4 15:02:57

Meta开源Hindsight:让事故复盘从脑补到闭环,防止同类事故反复发生

"hindsight"这个词本身就很有意思——事后之明,事后复盘。做SRE和运维的朋友对“postmortem”肯定不陌生,但说实话,能把复盘真正做好的团队真不多。Hindsight就是Meta开源的一套事故复盘平台,专门用来把整个事故周期里的…

作者头像 李华
网站建设 2026/11/4 15:02:48

GROMACS分子动力学模拟:能量最小化与NVT/NPT平衡避坑指南

简介:围绕分子动力学模拟全流程整理的PDF文档,面向化学、物理、生物等方向初入MD模拟的研究生与科研人员,也适合需要把模拟流程规范写入论文方法部分的学习者。内容从体系构建、加溶剂与离子、能量最小化,到NVT/NPT预平衡、生产项…

作者头像 李华
网站建设 2026/11/4 15:01:48

RAG如何构建AI Agent可靠的知识获取管道

1. 这不是“加个检索”那么简单:RAG 为什么成了 AI Agent 的知识命脉 你有没有试过让一个 AI Agent 去查公司内部的销售合同模板?或者让它从上百份产品规格书里,精准定位某款芯片的散热参数?如果它张口就来、答得头头是道&#xf…

作者头像 李华
网站建设 2026/11/4 15:01:44

数据中心基础设施运维从入门到精通:核心技能与实战避坑指南

数据中心基础设施运维这个行当,外面的人看着觉得就是“看机房、插网线、重启服务器”,真正干过的人才知道这里面的水有多深。我从零基础入行到现在带过几个小团队,踩过的坑能写满一个笔记本。这篇内容就是把我这些年积累的东西系统性地梳理一…

作者头像 李华
网站建设 2026/11/4 15:00:02

SAP FICO成本中心结算:分配、分摊与作业分配机制详解

简介:面向具备一定ERP基础、尤其有SAP FI-CO模块实践经验的企业财务人员与管理人员,这份教程聚焦成本中心会计中的结算机制,帮助解决部门间成本归集与分配不透明、结转规则理解不清的问题。内容围绕成本中心间“推”与“拉”两种结转方式展开…

作者头像 李华