深度学习:torch实战,使用神经网络识别手写数字
2026/8/29 20:36:48 网站建设 项目流程

今天开始使用 PyTorch 完成一个经典的深度学习项目——MNIST 手写数字识别。MNIST 数据集包含 60000 张训练图片和 10000 张测试图片,每张图片都是 28×28 像素的灰度图,标签为 0 到 9 的数字。

这个项目的主要流程是:

加载数据,转换为 Tensor, 使用 DataLoader 分批读取,构建神经网络,前向传播,计算损失,反向传播,更新参数,测试模型准确率

一、导入环境并查看版本

import torch import torchvision import torchaudio print(torch.__version__) print(torchvision.__version__) print(torchaudio.__version__)

如果需要使用代理下载数据集,可以设置环境变量:

import os os.environ["HTTP_PROXY"] = "http://127.0.0.1:7877" os.environ["HTTPS_PROXY"] = "http://127.0.0.1:7877"

这里需要注意,版本属性应该写成__version__,前后各有两个下划线。

注:如果是在Linux环境下或者服务器部署,我们也可以从https://download.pytorch.org/whl/这个pytorch官方网站上获取,下载名称中包含Linux和whl的文件,然后转移到Linux系统里。

二、加载 MNIST 数据集

PyTorch 提供了封装好的 MNIST 数据集,可以直接下载和读取:

from torchvision import datasets from torchvision.transforms import ToTensor training_data = datasets.MNIST( root="data", train=True, download=True, transform=ToTensor() ) test_data = datasets.MNIST( root="data", train=False, download=True, transform=ToTensor() )

参数含义如下:

1.root="data":数据集保存位置;

2.train=True:加载训练集;

3.train=False:加载测试集;

4.download=True:本地没有数据时自动下载;

5.transform=ToTensor():将图片转换为 Tensor。

原始图片不能直接输入神经网络,需要先转换为 Tensor。转换之后,图像的形状通常为:

[通道数, 高度, 宽度]

MNIST 是灰度图,因此通道数为 1,单张图片的形状为:

[1, 28, 28]

Tensor 与 NumPy 数组类似,都可以保存多维数据,但 Tensor 可以直接参与 PyTorch 的自动求导,也可以放到 GPU 上运行。

三、查看数据集中的图片

为了确认数据是否加载正确,可以使用 Matplotlib 显示部分训练图片:

from matplotlib import pyplot as plt figure = plt.figure() for i in range(9): #取九个数字 img, label = training_data[i] figure.add_subplot(3, 3, i + 1) #3*3的画板 plt.title(label) plt.axis("off") plt.imshow(img.squeeze(), cmap="gray") #.squeeze:去除通道维 plt.show()

img的形状是[1, 28, 28],而 Matplotlib 显示灰度图片时通常只需要[28, 28],因此使用squeeze()去掉通道维度。

最后显示出以上图像。

四、使用 DataLoader 分批读取

训练神经网络时,通常不会一次性把全部数据输入模型,而是将数据划分成多个批次。这样做主要有两个好处:一是可以降低单次计算的内存占用,避免显存或内存溢出;二是每个批次相当于一次“小规模抽样”,能让梯度更新更平稳,训练过程也更稳定。

from torch.utils.data import DataLoader train_dataloader = DataLoader( training_data, batch_size=64 ) test_dataloader = DataLoader( test_data, batch_size=64 )

这里设置batch_size=64,表示每次读取 64 张图片。

可以查看一个批次的数据形状:

for x, y in test_dataloader: print(f"shape of x: {x.shape}") print(f"shape of y: {y.shape}") print(f"type of y: {y.dtype}") break

输出结果大致为:

shape of x: torch.Size([64, 1, 28, 28]) shape of y: torch.Size([64]) type of y: torch.int64

其中x是图片数据,y是对应的数字标签。

五、选择运行设备

为了让模型能够在 GPU 上运行,可以根据当前环境自动选择设备:

device = torch.device( "cuda:0" if torch.cuda.is_available() else "cpu") print(device)

如果电脑安装了 CUDA 并且 PyTorch 能够识别显卡,就会使用 CUDA;否则使用 CPU。

模型和数据必须放在同一个设备上,否则会出现设备不一致的错误。

六、构建神经网络

本次使用一个简单的全连接神经网络:

from torch import nn class NeuralNetwork(nn.Module): def __init__(self): super().__init__() self.flatten = nn.Flatten() self.hidden1 = nn.Linear(28 * 28, 128) self.hidden2 = nn.Linear(128, 256) self.output = nn.Linear(256, 10) 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.output(x) return x

网络结构可以表示为:

28×28 图片 → Flatten 展平为 784 个特征 → Linear(784, 128) → Sigmoid → Linear(128, 256)
→ Sigmoid → Linear(256, 10) → 输出十个类别分数

Flatten()会将[1, 28, 28]展平为长度为 784 的向量。因为全连接层要求输入是一维特征,所以需要先进行展平。

nn.Linear()表示全连接层。第一层将 784 个像素特征映射为 128 个隐藏特征,第二层将 128 个特征映射为 256 个特征,最后输出 10 个数字类别对应的分数。

输出层有 10 个节点,分别对应数字 0 到 9。输出值还不是概率,而是每个类别的预测分数,通常称为 logits。

创建模型并放到指定设备(GPU):

model = NeuralNetwork().to(device) print(model)

七、训练模型

训练函数的代码如下:

def train(dataloader, model, loss_fn, optimizer): model.train() batch_size_num = 1 for x, y in dataloader: x = x.to(device) y = y.to(device) pred = model(x) loss = loss_fn(pred, y) optimizer.zero_grad() loss.backward() optimizer.step() loss_value = loss.item() if batch_size_num % 100 == 0: print(f"loss: {loss_value:.4f}") batch_size_num += 1

训练过程主要包含四个步骤。

第一步是前向传播:

pred = model(x)

模型接收输入图片,并计算得到预测结果。

第二步是计算损失:

loss = loss_fn(pred, y)

损失函数用于衡量预测结果与真实标签之间的差距。损失越小,说明模型预测得越准确。

第三步是清空梯度:

optimizer.zero_grad()

PyTorch 默认会累积梯度,因此每次更新参数前都要先清空上一轮的梯度。

第四步是反向传播并更新参数:

loss.backward() optimizer.step()

loss.backward()根据损失函数自动计算每个参数的梯度,optimizer.step()根据梯度更新网络参数。

八、测试模型

测试阶段不需要更新参数,只需要计算模型在测试集上的表现:

def test(dataloader, model, loss_fn): size = len(dataloader.dataset) num_batches = len(dataloader) model.eval() test_loss = 0 correct = 0 with torch.no_grad(): for x, y in dataloader: x = x.to(device) y = y.to(device) pred = model(x) test_loss += loss_fn( pred, y ).item() correct += ( pred.argmax(1) == y ).type(torch.float).sum().item() test_loss /= num_batches correct /= size print( f"Test result:\n" f"Accuracy: {correct * 100:.0f}%\n" f"Avg loss: {test_loss:.8f}\n" )

model.eval()会将模型切换到测试模式。

torch.no_grad()表示测试阶段不计算梯度,可以减少内存占用并提高运行速度。

模型输出的形状为:

[批次大小, 10]

每一行对应一张图片的十个类别分数。使用下面的代码可以得到预测类别:

pred.argmax(1)

这里将预测出来的类别与真实标签做对比,预测对为True,预测错为False

.type(torch.float)

这里是将布尔类型的张量转换为浮点类型的

argmax(1)表示在类别维度上寻找最大值的位置。例如:

[1.2, 0.3, 5.8, 0.6, ...]

最大值出现在下标 2,因此模型预测该图片是数字 2。

九、损失函数与优化器

本次使用交叉熵损失:

loss_fn = nn.CrossEntropyLoss()

交叉熵损失常用于多分类任务。MNIST 有 10 个类别,因此非常适合使用这个损失函数。

需要注意,使用CrossEntropyLoss()时,模型最后一层不需要手动添加 Softmax。因为交叉熵损失内部已经包含了相关计算,直接输入 logits 即可。

优化器使用 Adam:

optimizer = torch.optim.Adam( model.parameters(), lr=0.01 )

model.parameters()表示将模型中的所有可训练参数交给优化器管理,lr=0.01是学习率,用于控制每次参数更新的步长。

十、完整训练流程

设置训练轮数并开始训练:

epochs = 10 for i in range(epochs): print(f"epoch [{i + 1}/{epochs}]") train( train_dataloader, model, loss_fn, optimizer ) print("Done") test( test_dataloader, model, loss_fn )

一个 epoch 表示模型完整遍历一次训练集。训练 10 轮后,模型会反复学习训练图片中的像素特征和数字结构。

随着训练进行,损失一般会逐渐下降,测试准确率逐步提高。不过学习率、网络结构、激活函数和训练轮数都会影响最终效果。

总结

今天通过 MNIST 手写数字识别,完整体验了 PyTorch 的基本训练流程。首先使用datasets.MNIST加载数据,再通过ToTensor()将图片转换为 Tensor,使用DataLoader按批次读取数据。

模型部分使用了三个全连接层,中间加入 Sigmoid 激活函数,将 28×28 的图片转换为 10 个数字类别的预测结果。训练时,通过前向传播计算预测值,再使用交叉熵损失衡量误差,最后通过反向传播和 Adam 优化器更新模型参数。

这次代码虽然结构比较简单,但已经包含了深度学习项目中最核心的几个环节:

数据集,模型,损失函数,优化器,前向传播,反向传播,模型评估

后续还可以继续尝试修改网络层数、隐藏层节点数、激活函数和学习率,观察不同参数对准确率和训练速度的影响。也可以将全连接网络改成卷积神经网络,进一步提升 MNIST 图片识别效果。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询