# 手写数字识别:使用PyTorch构建MNIST分类器
手写数字识别:使用PyTorch构建MNIST分类器
在这篇文章中,我将引导你通过使用PyTorch框架构建一个简单的神经网络模型,用于识别MNIST数据集中的手写数字。MNIST数据集是一个经典的机器学习数据集,包含了60,000张训练图像和10,000张测试图像,每张图像都是28x28像素的灰度手写数字。
数据加载与预处理
我们首先加载MNIST数据集,并将图像转换为PyTorch张量格式,以便模型可以处理。
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor'''下载训练数据集(包含训练图片+标签)'''
training_data = datasets.MNIST( #跳转到函数的内部源代码,pycharm 按下ctrl+鼠标点击 training_data:Datasetroot="data",#表示下载的手写数字 到哪个路径。60000train=True, #读取下载后的数据 中的 训练集download=True,#如果你之前已经下载过了,就不用再下载transform=ToTensor(), #张量,图片是不能直接传入神经网络模型
) #对于pytorch库能够识别的数据一般是tensor张量。'''下载测试数据集(包含训练图片+标签)'''
test_data = datasets.MNIST(root="data",train=False,download=True,transform=ToTensor()
)
print(len(training_data))
数据可视化
为了更好地理解数据,我们可以展示一些手写数字图像。
''展示手写字图片,把训练数据集中的前59000张图片展示一下'''from matplotlib import pyplot as plt
figure = plt.figure()
for i in range(9):img, label = training_data[i+59000] #提取第59000张图片figure.add_subplot(3, 3, i+1) #图像窗口中创建多个小窗口,小窗口用于显示图片plt.title(label) # 设置每个小窗口的标题为对应的标签plt.axis("off") ##关闭坐标轴的显示plt.imshow(img.squeeze(), cmap="gray") # 显示图片,squeeze()函数用于去除图片张量中长度为1的维度,cmap="gray"表示以灰度模式显示a = img.squeeze()# img.squeeze()从张量img中去掉维度为1的。如果该维度的大小不为1则张量不会改变。#cmap="gray
plt.show()
运行结果
创建DataLoader
为了高效地加载数据,我们使用DataLoader
来批量加载数据。
# '"创建数据DataLoader(数据加载器)开'
# 'batch_size:将数据集分成多份,每一份为batch_size个数据'
# '优点:可以减少内存的使用,提高训练速度。train_dataloader = DataLoader(training_data, batch_size=64) #64张图片为一个包,train_dataloader:<torch
test_dataloader = DataLoader(test_data, batch_size=64)'''判断当前设备是否支持GPU,其中mps是苹果m系列芯片的GPU。'''
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")
模型定义
接下来,我们定义一个简单的神经网络模型,包含两个隐藏层和一个输出层。
'''定义神经网络类的继承这种方式'''
class NeuralNetwork(nn.Module): # 定义一个名为 NeuralNetwork 的类,继承自 PyTorch 的 nn.Module 类# 通过继承 nn.Module,NeuralNetwork 类可以使用 PyTorch 提供的模块化网络结构和功能def __init__(self): # 类的构造函数,当创建 NeuralNetwork 类的实例时调用super().__init__() # 调用父类(nn.Module)的构造函数,确保父类的初始化被正确执行self.flatten = nn.Flatten() # 创建一个 Flatten 层,用于将多维输入一维化self.hidden1 = nn.Linear(28*28, 128) # 创建第一个隐藏层,输入特征为 28*28(即 784),输出特征为 128self.hidden2 = nn.Linear(128, 256) # 创建第二个隐藏层,输入特征为 128,输出特征为 256self.out = nn.Linear(256, 10) # 创建输出层,输入特征为 256,输出特征为 10(假设有 10 个类别)def forward(self, x): # 定义前向传播函数,x 为输入数据x = self.flatten(x) # 将输入数据 x 通过 Flatten 层展开x = self.hidden1(x) # 将展开后的数据通过第一个隐藏层x = torch.relu(x) # 对第一个隐藏层的输出应用 ReLU 激活函数x = self.hidden2(x) # 将激活后的数据通过第二个隐藏层x = torch.relu(x) # 对第二个隐藏层的输出再次应用 ReLU 激活函数x = self.out(x) # 将数据通过输出层return x # 返回最终的输出结果model = NeuralNetwork().to(device) # 创建 NeuralNetwork 类的实例,并将其移动到指定的设备(CPU 或 GPU)
print(model) # 打印模型的结构,以查看各层的参数和连接方式
训练与测试
我们定义训练和测试函数,使用交叉熵损失函数和随机梯度下降优化器。
def train(dataloader, model, loss_fn, optimizer):# 将模型设置为训练模式,这将影响某些层(如Dropout和BatchNorm)的行为model.train() batch_size_num = 1 # 初始化批次计数器for X, y in dataloader: # 遍历数据加载器,获取每个批次的数据和标签X, y = X.to(device), y.to(device) # 将数据和标签移动到指定的设备(CPU或GPU)pred = model.forward(X) # 执行模型的前向传播,计算预测结果loss = loss_fn(pred, y) # 计算预测结果和真实标签之间的损失# 反向传播和优化步骤optimizer.zero_grad() # 清除旧的梯度loss.backward() # 计算当前梯度optimizer.step() # 根据梯度更新模型参数# 提取损失值并打印loss_value = loss.item() # 从张量中提取损失值if batch_size_num % 100 == 0: # 每100个批次打印一次损失值和批次编号print(f"loss: {loss_value:>7f} [number:{batch_size_num}]")batch_size_num += 1 # 递增批次计数器def test(dataloader, model, loss_fn):size = len(dataloader.dataset) # 获取测试数据集的大小num_batches = len(dataloader) # 获取测试数据加载器中的批次数量model.eval() # 将模型设置为评估模式,这将影响某些层(如Dropout)的行为test_loss, correct = 0, 0 # 初始化测试损失和正确预测计数器with torch.no_grad(): # 关闭梯度计算for X, y in dataloader: # 遍历测试数据加载器X, y = X.to(device), y.to(device) # 将数据和标签移动到指定的设备(CPU或GPU)pred = model.forward(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 Accuracy: {(100*correct)}%, Avg loss: {test_loss}")
训练模型
最后,我们训练模型并测试其性能。
loss_fn = nn.CrossEntropyLoss() #创建交叉熵损失函数对象,因为手写字识别中一共有10个数字,输出会有10个结果optimizer = torch.optim.SGD(model.parameters(), lr=0.01) #创建一个优化器,SGD为随机梯度下降算法
# #params:要训练的参数,一般我们传入的都是model.parameters()# #lr:learning_rate学习率,也就是步长#loss表示模型训练后的输出结果与,样本标签的差距。如果差距越小,就表示模型训练越好,越逼近干真实的模型。# train(train_dataloader, model, loss_fn, optimizer)
# test(test_dataloader, model, loss_fn)epochs = 30
for t in range(epochs):print(f"Epoch {t+1}\n")train(train_dataloader, model, loss_fn, optimizer) #调用train函数开始一个训练周期,使用训练数据加载器、模型、损失函数和优化器。
print("Done!") #在所有训练周期完成后,打印"Done!"表示训练结束。
test(test_dataloader, model, loss_fn)
运行结果
结论
通过这篇文章,我们成功构建了一个简单的神经网络模型来识别MNIST数据集中的手写数字。这个模型展示了如何使用PyTorch进行数据处理、模型定义、训练和测试。希望这能帮助你开始自己的深度学习项目!