第P1周:实现mnist手写数字识别

一、前期准备

1.设置GPU

代码知识点
torch.device(...):这是PyTorch中的一个函数,用于创建一个新的设备对象。这个对象可以用于指定张量(tensor)应该在哪个设备上进行计算。

Torchvision是PyTorch的一个子库,提供了丰富的功能以支持计算机视觉任务。主要可以划分为以下四个部分:

  1. torchvision.datasets: 提供常用的数据集,如MNIST、ImageNet、CIFAR-10等,并且设计上继承自torch.utils.data.Dataset接口。

  2. torchvision.models: 提供深度学习中各种经典的网络结构以及预训练好的模型,例如Alex-Net、VGG、ResNet、Inception等。

  3. torchvision.transforms: 提供图像转换的功能,包括常见的图像增强方法,如裁剪、旋转、翻转等。

  4. torchvision.utils: 提供一些实用的工具函数,例如保存和加载模型、可视化数据等。

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torchvision

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

device

输出
device(type=‘cuda’)

2.导入数据

代码知识点
'data': 这是数据集下载和保存的目录路径。在这个例子中,数据集将被下载到名为"data"的文件夹中。
train=True: 这个参数指定了我们想要加载的是训练数据集。如果设置为False,则加载的是测试数据集。
transform=torchvision.transforms.ToTensor(): 这个参数指定了在加载数据集之前对图像进行转换的操作。在这里,使用torchvision.transforms.ToTensor()将图像转换为张量(tensor)。
download=True: 这个参数指定了是否自动下载数据集。如果设置为True,则会检查是否存在已下载的数据集,如果不存在,则会从互联网上下载并保存到指定的目录中。

train_ds = torchvision.datasets.MNIST('data',
                                       train = True,
                                       transform=torchvision.transforms.ToTensor(),
                                       download = True)
test_ds = torchvision.datasets.MNIST('data',
                                       train = False,
                                       transform=torchvision.transforms.ToTensor(),
                                       download = True)

代码知识点
torch.utils.data.DataLoader是Pytorch自带的一个数据加载器,结合了数据集和取样器,并且可以提供多个线程处理数据集。
参数说明:
dataset(string) :加载的数据集
batch_size (int,optional) :每批加载的样本大小(默认值:1)
shuffle(bool,optional) : 如果为True,每个epoch重新排列数据。
sampler (Sampler or iterable, optional) :定义从数据集中抽取样本的策略。 可以是任何实现了 len 的 Iterable。 如果指定,则不得指定 shuffle 。
batch_sampler (Sampler or iterable, optional) : 类似于sampler,但一次返回一批索引。与 batch_size、shuffle、sampler 和 drop_last 互斥。
num_workers(int,optional) : 用于数据加载的子进程数。 0 表示数据将在主进程中加载(默认值:0)。
pin_memory (bool,optional) : 如果为 True,数据加载器将在返回之前将张量复制到设备/CUDA 固定内存中。 如果数据元素是自定义类型,或者collate_fn返回一个自定义类型的批次。
drop_last(bool,optional) : 如果数据集大小不能被批次大小整除,则设置为 True 以删除最后一个不完整的批次。 如果 False 并且数据集的大小不能被批大小整除,则最后一批将保留。 (默认值:False)
timeout(numeric,optional) : 设置数据读取的超时时间 , 超过这个时间还没读取到数据的话就会报错。(默认值:0)
worker_init_fn(callable,optional) : 如果不是 None,这将在步长之后和数据加载之前在每个工作子进程上调用,并使用工作 id([0,num_workers - 1] 中的一个 int)的顺序逐个导入。 (默认:None)

batch_size = 16

train_dl = torch.utils.data.DataLoader(train_ds,
                                       batch_size = batch_size,
                                       shuffle = True)
test_dl = torch.utils.data.DataLoader(test_ds,
                                       shuffle = True)

代码知识点
iter(train_dl)返回一个迭代器,next()函数则用于获取迭代器的下一个元素。
imgs,labels = next(iter(train_dl))这行代码将获取到的数据分解为两部分:imgs和labels。

imgs,labels = next(iter(train_dl))
imgs.shape

输出
torch.Size([32, 1, 28, 28])

3.数据可视化

代码知识点

  1. plt.figure(figsize=(20, 5)): 创建一个大小为20x5英寸的图形窗口。plt是Matplotlib库的常用别名,用于绘图。
  2. for i, imgs in enumerate(imgs[:20]):: 使用enumerate()函数遍历imgs列表的前20个元素。enumerate()函数返回一个枚举对象,其中包含索引和对应的值。在这里,索引表示图像的位置,值表示图像本身。
  3. npimg = np.squeeze(imgs.numpy()): 将图像转换为NumPy数组,并使用np.squeeze()函数删除数组中的单维度条目。这样可以确保图像数据以正确的形状进行处理。

单维度条目,是指数组中形状为1的维度。例如,对于一个一维数组,只有一个0轴,对于一个二维数组(shape(2,2)),有0轴和1轴,对于三维数组(shape(2,2,3)),有0, 1, 2轴。在NumPy库中,np.squeeze()函数就是用来删除这些单维度条目的。如果数组的形状中有一个单维度条目,np.squeeze()函数就会返回一个维度较小的新数组;如果该数组没有单维度条目,那么这个数组就不会发生变化。同时,我们可以通过指定axis参数来删除特定轴上的单维度条目。

  1. plt.subplot(2, 10, i+1): 创建一个子图,将其放置在2行10列的网格中,当前位置由i+1确定。这里,2表示行数,10表示列数,i+1表示当前图像在网格中的位置。
  2. plt.imshow(npimg, cmap=plt.cm.binary): 使用imshow()函数显示图像。npimg是要显示的图像数据,cmap=plt.cm.binary指定了颜色映射,这里使用了二值颜色映射。
  3. plt.axis('off'): 关闭坐标轴。这可以使得图像看起来更加简洁。
import numpy as np

plt.figure(figsize=(20, 5)) 
for i, imgs in enumerate(imgs[:20]):
   
    npimg = np.squeeze(imgs.numpy())
  
    plt.subplot(2, 10, i+1)
    plt.imshow(npimg, cmap=plt.cm.binary)
    plt.axis('off')
     

输出
在这里插入图片描述

二、构建简单的CNN网络

代码知识点
import torch.nn.functional as F
这行代码导入了PyTorch库中的函数模块torch.nn.functional,它包含了一些常用的激活函数和损失函数。
x = torch.flatten(x, start_dim = 1)
这行代码将卷积层的输出结果展平,以便后续进行全连接层的计算。start_dim=1表示从第二个维度开始展平。
x = F.relu(self.fcl(x)) x = self.fc2(x)
这两行代码分别对展平后的输出结果进行了两个全连接层的计算。第一个全连接层使用ReLU激活函数,第二个全连接层不使用激活函数.

import torch.nn.functional as F

num_classes = 10

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1,32,kernel_size = 3)
        self.pool1 = nn.MaxPool2d(2)
        self.conv2 = nn.Conv2d(32,64,kernel_size = 3)
        self.pool2 = nn.MaxPool2d(2)
        #分类网络
        self.fcl = nn.Linear(1600,64)
        self.fc2 = nn.Linear(64, num_classes)
        #前向传播
        
    def forward(self,x):
        x = self.pool1(F.relu(self.conv1(x)))
        x = self.pool2(F.relu(self.conv2(x)))
        
        x = torch.flatten(x, start_dim = 1)
        
        x = F.relu(self.fcl(x))
        x = self.fc2(x)
        
        return x
from torchinfo import summary
model = Model( ).to(device)

summary(model)

输出
在这里插入图片描述

三、训练模型

1.设置超参数

代码知识点
这段代码是使用PyTorch库定义了一个神经网络模型的损失函数、学习率和优化器。下面是逐行解释:

  1. loss_fn = nn.CrossEntropyLoss(): 这行代码创建了一个交叉熵损失函数对象,用于计算模型预测结果与真实标签之间的损失。

  2. learn_rate = 0.3e-2: 这行代码定义了学习率为0.3乘以10的负2次方,即0.003。学习率决定了模型参数更新的速度。

  3. opt = torch.optim.SGD(model.parameters(),lr = learn_rate): 这行代码创建了一个随机梯度下降(SGD)优化器对象,用于更新模型的参数。model.parameters()表示模型的所有参数,lr = learn_rate指定了学习率为之前定义的0.003。

总结起来,这段代码定义了一个交叉熵损失函数、一个学习率和一个随机梯度下降优化器,用于训练神经网络模型。

loss_fn = nn.CrossEntropyLoss()
learn_rate = 0.3e-2
opt = torch.optim.SGD(model.parameters(),lr = learn_rate)

2.编写训练函数

代码知识点
1.optimizer.zero_grad()函数会遍历模型的所有参数,通过内置方法截断反向传播的梯度流,再将每个参数的梯度值设为0,即上一次的梯度记录被清空。

2.loss.backward()PyTorch的反向传播(即tensor.backward())是通过autograd包来实现的,autograd包会根据tensor进行过的数学运算来自动计算其对应的梯度。

具体来说,torch.tensor是autograd包的基础类,如果你设置tensor的requires_grads为True,就会开始跟踪这个tensor上面的所有运算,如果你做完运算后使用tensor.backward(),所有的梯度就会自动运算,tensor的梯度将会累加到它的.grad属性里面去。
更具体地说,损失函数loss是由模型的所有权重w经过一系列运算得到的,若某个w的requires_grads为True,则w的所有上层参数(后面层的权重w)的.grad_fn属性中就保存了对应的运算,然后在使用loss.backward()后,会一层层的反向传播计算每个w的梯度值,并保存到该w的.grad属性中。

如果没有进行tensor.backward()的话,梯度值将会是None,因此loss.backward()要写在optimizer.step()之前。

  1. optimizer.step() step()函数的作用是执行一次优化步骤,通过梯度下降法来更新参数的值。因为梯度下降是基于梯度的,所以在执行optimizer.step()函数前应先执行loss.backward()函数来计算梯度。

注意:optimizer只负责通过梯度下降进行优化,而不负责产生梯度,梯度是tensor.backward()方法产生的。

3.编写测试函数

代码知识点
这段代码是一个用于测试神经网络模型的函数。下面是逐行解释:
这段代码定义了一个名为test的函数,它接受三个参数:dataloadermodelloss_fndataloader是一个数据加载器对象,用于从数据集中加载图像和标签;model是一个神经网络模型,用于对输入的图像进行预测;loss_fn是一个损失函数对象,用于计算预测结果与真实标签之间的损失。

函数首先获取测试集的大小(即总共有多少张图像),并计算批次数目(即总共有多少个批次)。然后,初始化测试损失和测试准确率为0。

接下来,使用with torch.no_grad()语句来停止梯度更新。这是因为在测试过程中,我们不需要计算梯度,因此可以节省计算内存消耗。

with torch.no_grad()语句块中,遍历数据加载器中的每个批次。对于每个批次,将图像和标签转移到指定的设备上(例如GPU或CPU),然后使用模型对图像进行预测。接着,使用损失函数计算预测结果与真实标签之间的损失,并将其累加到测试损失中。同时,计算预测结果中正确分类的数量,并将其累加到测试准确率中。

  1. for imgs, target in dataloader::这是一个循环,用于遍历数据加载器(dataloader)中的所有批次。在每个批次中,imgs表示图像数据,target表示对应的标签。

  2. imgs, target = imgs.to(device), target.to(device):这行代码将图像数据和标签转移到指定的设备上(例如GPU或CPU)。这样可以确保计算过程在相同的设备上进行,提高计算效率。

  3. target_pred = model(imgs):这行代码使用神经网络模型对输入的图像数据进行预测,得到预测结果target_pred

  4. loss = loss_fn(target_pred, target):这行代码计算预测结果与真实标签之间的损失。loss_fn是一个损失函数对象,它接受预测结果和真实标签作为输入,并返回一个标量值表示损失。

  5. test_loss += loss.item():这行代码将当前批次的损失累加到测试损失变量test_loss中。loss.item()将损失转换为Python标量值。

  6. test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item():这行代码计算预测结果中正确分类的数量,并将其累加到测试准确率变量test_acc中。首先,target_pred.argmax(1)找到预测结果中概率最大的类别索引,然后将其与真实标签进行比较,得到一个布尔值张量。接着,使用type(torch.float)将布尔值张量转换为浮点数张量,最后使用sum().item()计算正确分类
    的数量。

最后,将测试准确率除以测试集的大小,得到平均准确率;将测试损失除以批次数目,得到平均损失。函数返回平均准确率和平均损失作为输出。

def test (dataloader, model, loss_fn):
    size        = len(dataloader.dataset)  # 测试集的大小,一共10000张图片
    num_batches = len(dataloader)          # 批次数目,313(10000/32=312.5,向上取整)
    test_loss, test_acc = 0, 0
    
    # 当不进行训练时,停止梯度更新,节省计算内存消耗
    with torch.no_grad():
        for imgs, target in dataloader:
            imgs, target = imgs.to(device), target.to(device)
            
            # 计算loss
            target_pred = model(imgs)
            loss        = loss_fn(target_pred, target)
            
            test_loss += loss.item()
            test_acc  += (target_pred.argmax(1) == target).type(torch.float).sum().item()

    test_acc  /= size
    test_loss /= num_batches

    return test_acc, test_loss

4.正式训练

四、结果可视化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值