PyTorch实战:实现MNIST手写数字识别


前言

PyTorch可以说是三大主流框架中最适合初学者学习的了,相较于其他主流框架,PyTorch的简单易用性使其成为初学者们的首选。这样我想要强调的一点是,框架可以类比为编程语言,仅为我们实现项目效果的工具,也就是我们造车使用的轮子,我们重点需要的是理解如何使用Torch去实现功能而不要过度在意轮子是要怎么做出来的,那样会牵扯我们太多学习时间。以后就出一系列专门细解深度学习框架的文章,但是那是较后期我们对深度学习的理论知识和实践操作都比较熟悉才好开始学习,现阶段我们最需要的是学会如何使用这些工具。

深度学习的内容不是那么好掌握的,包含大量的数学理论知识以及大量的计算公式原理需要推理。且如果不进行实际操作很难够理解我们写的代码究极在神经网络计算框架中代表什么作用。不过我会尽可能将知识简化,转换为我们比较熟悉的内容,我将尽力让大家了解并熟悉神经网络框架,保证能够理解通畅以及推演顺利的条件之下,尽量不使用过多的数学公式和专业理论知识。以一篇文章快速了解并实现该算法,以效率最高的方式熟练这些知识。


博主专注数据建模四年,参与过大大小小数十来次数学建模,理解各类模型原理以及每种模型的建模流程和各类题目分析方法。此专栏的目的就是为了让零基础快速使用各类数学模型、机器学习和深度学习以及代码,每一篇文章都包含实战项目以及可运行代码。博主紧跟各类数模比赛,每场数模竞赛博主都会将最新的思路和代码写进此专栏以及详细思路和完全代码。希望有需求的小伙伴不要错过笔者精心打造的专栏。

一文速学-数学建模常用模型


一、数据集加载

MNIST(Modified National Institute of Standards and Technology)是一个手写数字数据集,通常用于训练各种图像处理系统。

它包含了大量的手写数字图像,这些数字从0到9。每个图像都是一个灰度图像,大小为28x28像素,表示了一个手写数字。

MNIST数据集分成两部分:训练集和测试集。训练集通常包含60,000张图像,用于训练模型。测试集包含10,000张图像,用于评估模型的性能。

MNIST数据集是一个非常受欢迎的数据集,被用于测试和验证各种机器学习和深度学习模型,特别是在图像识别任务中。大家可以直接访问官网下载或者是在程序中使用torchvision下载数据集。

官网:THE MNIST DATABASE

一共4个文件,训练集、训练集标签、测试集、测试集标签:

文件名称 大小 内容
train-labels-idx1-ubyte.gz 9,681 kb 55000张训练集,5000张验证集
train-labels-idx1-ubyte.gz 29 kb 训练集图片对应的标签
t10k-images-idx3-ubyte.gz 1,611 kb 10000张测试集
t10k-labels-idx1-ubyte.gz 5 kb 测试集图片对应的标签

 程序加载MNIST数据集:

from torch.utils.data import DataLoader
import torchvision.datasets as dsets

transform = transforms.Compose([
    transforms.Grayscale(num_output_channels=1),  # 将图像转为灰度
    transforms.ToTensor(),  # 将图像转为张量
    transforms.Normalize((0.1307,), (0.3081,))
])

#MNIST dataset
train_dataset = dsets.MNIST(root = '/ml/pymnist',  #选择数据的根目录
                            train = True,  #选择训练集
                            transform = transform,  #不考虑使用任何数据预处理
                            download = True  #从网络上下载图片
                           )
test_dataset = dsets.MNIST(root = '/ml/pymnist',#选择数据的根目录
                           train = False,#选择测试集
                           transform = transform, #不考虑使用任何数据预处理
                           download = True #从网络上下载图片
                          )
#加载数据
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                           batch_size = batch_size,
                                           shuffle = True #将数据打乱
                                          )
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                           batch_size = batch_size,
                                           shuffle = True
                                         )

图片展示:

import matplotlib.pyplot as plt
digit = train_dataset.train_data[0]
plt.imshow(digit,cmap=plt.cm.binary,interpolation='none')
plt.title("Labels: {}".format(train_dataset.train_labels[0]))
plt.show()

 之后需要切分数据集,分为训练集和测试集,MNIST数据集已经做好了直接使用就好了:

print("train_data:",train_dataset.train_data.size())
print("train_labels:",train_dataset.train_labels.size())
print("test_data:",test_dataset.test_data.size())
print("test_labels:",test_dataset.test_labels.size())

train_data: torch.Size([60000, 28, 28])
train_labels: torch.Size([60000])
test_data: torch.Size([10000, 28, 28])
test_labels: torch.Size([10000])

还需要确定批次的尺寸,在神经网络训练中,batch_size 是指每次迭代训练时,模型同时处理的样本数量。它在训练过程中起到了几个重要作用:

  • 加速训练过程:通过同时处理多个样本,利用了现代计算机的并行计算能力,可以加速训练过程,尤其在使用GPU时。
  • 减少内存消耗:一次性加载整个训练集可能会占用大量内存,而将数据分批次加载可以降低内存消耗,使得在内存受限的环境中也能进行训练。
  • 提高模型泛化能力:在训练过程中,模型会根据每个batch的数据调整权重,而不是依赖于整个训练集。这样可以提高模型对不同样本的泛化能力。
  • 避免陷入局部极小值:随机选择的小批量样本可以帮助模型避免陷入局部极小值。
  • 增加噪声鲁棒性:在每次迭代中,模型只看到了一个小样本,这可以看作一种随机噪声,有助于提高模型的鲁棒性。
  • 方便在线学习:对于在线学习任务,可以动态地加载新的数据批次,而不需要重新训练整个模型。

总的来说,合理选择合适的batch_size可以使训练过程更加高效、稳定,并且能够提高模型的泛化能力。然而,过大的batch_size可能会导致内存溢出或训练速度变慢,过小的batch_size可能会导致模型收敛困难。因此,选择合适的batch_size需要在实践中进行调试和优化。

print("批次的尺寸:",train_loader.batch_size)
print("load_train_data:",train_loader.dataset.train_data.shape)
print("load_train_labels:",train_loader.dataset.train_labels.shape)
批次的尺寸: 100
load_train_data: torch.Size([60000, 28, 28])
load_train_labels: torch.Size([60000])

 从输出结果中,可以看到原始数据集和数据打乱按照批次读取的数据集的总行数是一样的,实际操作中train_loader以及test_loader将作为神经网络的输入数据源。

二、定义神经网络

在前面的文章中已经带着大家搭建过好几遍神经网络了,注意初始化网络和对应的输入层,隐藏层和输出层。

import torch.nn as nn
import torch

input_size = 784 #mnist的像素为28*28
hidden_size = 500
num_classes = 10#输出为10个类别分别对应于0~9

#创建神经网络模型
class Neural_net(nn.Module):
#初始化函数,接受自定义输入特征的维数,隐含层特征维数以及输出层特征维数
    def __init__(self,input_num,hidden_size,out_put):
        super(Neural_net,self).__init__()
        self.layer1 = nn.Linear(input_num,hidden_size) #从输入到隐藏层的线性处理
        self.layer2 = nn.Linear(hidden_size,out_put) #从隐藏层到输出层的线性处理
        
    def forward(self,x):
        x = self.layer1(x) #输入层到隐藏层的线性计算
        x = torch.relu(x) #隐藏层激活
        x = self.layer2(x) #输出层,注意,输出层直接接loss
        return x
        
net = Neural_net(input_size,hidden_size,num_classes)
print(net)
        
Neural_net(
  (layer1): Linear(in_features=784, out_features=500, bias=True)
  (layer2): Linear(in_features=500, out_features=10, bias=True)
)

 super(Neural_net, self).init() 是 Python 中用于调用父类的方法或属性的一种方式。在这里,Neural_net 是你定义的神经网络模型的类名,它继承了 nn.Module 类,而 nn.Module 是 PyTorch 中用于构建神经网络模型的基类。也就是说,你的神经网络模型会继承 nn.Module 的所有属性和方法,这样你可以在 Neural_net 类中使用 nn.Module 中定义的各种功能,比如添加神经网络层、指定损失函数等。

三、训练模型

只有要注意一下Variable,之前的文章中又提到过Variable。

Variable是PyTorch早期版本(0.4版本之前)中用于构建计算图的抽象,它包含了data、grad和grad_fn等属性,可以用于构建计算图,并在反向传播时自动计算梯度。但从PyTorch 0.4版本开始,Variable被官方废弃,而Tensor直接支持了自动求导功能,不再需要显式地创建Variable。

因此,Autograd是PyTorch实现自动求导的核心机制,而Variable是早期版本中用于构建计算图的一种抽象,现在已经被Tensor所取代。 Autograd会自动追踪Tensor上的操作,并在需要时计算梯度,从而实现反向传播。

#optimization
import numpy as np
from torchvision import transforms

learning_rate = 1e-3 #学习率
num_epoches = 5
criterion =nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(),lr = learning_rate) #随机梯度下降

for epoch in range(num_epoches):
    print('current epoch = %d' % epoch)
    for i ,(images,labels) in enumerate(train_loader,0):
        images=images.view(-1,28*28)
        outputs = net(images) #将数据集传入网络做前向计算
        labels = torch.tensor(labels, dtype=torch.long)
        
        loss = criterion(outputs, labels) #计算loss
        
        optimizer.zero_grad() #在做反向传播之前先清楚下网络状态
        loss.backward() #Loss反向传播
        optimizer.step() #更新参数
        
        if i % 100 == 0:
            print('current loss = %.5f' % loss.item())
            
print('finished training')
current epoch = 1
current loss = 0.27720
current loss = 0.23612
current loss = 0.39341
current loss = 0.24683
current loss = 0.18913
current loss = 0.31647
current loss = 0.28518
current loss = 0.18053
current loss = 0.34957
current loss = 0.31319
current epoch = 2
current loss = 0.15138
current loss = 0.30887
current loss = 0.24257
current loss = 0.46326
current loss = 0.30790
current loss = 0.17516
current loss = 0.32319
current loss = 0.32325
current loss = 0.32066
current loss = 0.24271

 四、准确度测试

各层的权重通过随机梯度下降法更新Loss之后,针对测试集数字分类的准确率:

#prediction
total = 0
correct =0 
acc_list_test = []
for images,labels in test_loader:
    images=images.view(-1,28*28)
    outputs = net(images) #将数据集传入网络做前向计算
    
    _,predicts = torch.max(outputs.data,1)
    total += labels.size(0)
    correct += (predicts == labels).sum()
    acc_list_test.append(100 * correct / total)
    
print('Accuracy = %.2f'%(100 * correct / total))
plt.plot(acc_list_test)
plt.xlabel('Epoch')
plt.ylabel('Accuracy On TestSet')
plt.show()

 

点关注,防走丢,如有纰漏之处,请留言指教,非常感谢

以上就是本期全部内容。我是fanstuck ,有问题大家随时留言讨论 ,我们下期见。


猜你喜欢

转载自blog.csdn.net/master_hunter/article/details/132964457