PyTorch学习笔记——(7)使用pytorch实现手写数字识别,可以很好的练习pytorch

上一小节.
GitHub地址.

目录

  • 1、思路和流程分析
  • 2、准备训练集和测试集
    • 2.1 torchvision.transform的图形数据处理方法
      • (1) torchvision.transform.ToTensor
      • (2)torchvision.transforms.Normalize(mean, std)
      • (3)torchvision.transforms.Compose(transforms)
    • 2.2 准备MNIST数据集的Dataset和DataLoader
  • 3 模型的构建
    • 3.1 激活函数的使用:
    • 3.2 模型中数据的形状:
  • 4、模型的训练
  • 5、模型的保存和加载
    • 5.1 模型的保存
    • 5.2 模型的加载
  • 6、模型的评估
  • 7、完整代码:
    • 7.1 代码:
    • 7.2 结果:

1、思路和流程分析

流程:

  • 1.准备数据,这些需要准备DataLoader
  • 2.构建模型,这里可以使用torch构造一个深层的神经网络
  • 3.模型的训练
  • 4.模型的保存,保存模型,后续持续使用
  • 5.模型的评估,使用测试集,观察模型的好坏

2、准备训练集和测试集

准备数据集的方法上一节已经讲过数据加载Dataset和DataLoader的使用,在这里我们使用pytorch自带的mnist数据集来做,也就是说,我们不需要再去写自己的dataset类了,pytorch已经封装好了,我们只需要调用即可。API如下所示:

mnist = MNIST(path, train=True, download=True) # 是个dataset的实例

参数:
path:表示保存数据的路径;
train:训练集的数据;
download:是否下载,因为第一次使用要从官网下载,下载了在之后,就可以设置为False了。
后面还有参数之后再介绍。

但是,调用MNIST返回的结果中图形数据是一个mage对象需要对其进行处理。

为了进行数据的处理,接下来需要学习torchvision.transform的方法:

2.1 torchvision.transform的图形数据处理方法

(1) torchvision.transform.ToTensor

作用:把一个取值范围是[0,255]的PIL.Image或者shape为(H,W,C)的numpy.ndarray,转换成形状为[C,H,W] ,取值范围是[0,1. 0]的torch.FloatTensor

其中(H,W,C)意思为(高,宽,通道数),黑白图片的通道数只有1,其中每个像素点的取值为
[0,255],彩色图片的通道数为(R,G,B)每个通道的每个像素点的取值为[0,255],三个通道的颜色相互叠加,形成了各种颜色。

示例如下:

from torchvision.datasets import MNIST
from torchvision import transforms

mnist = MNIST("./data", train=True, download=False) # 是个dataset的实例

# mnist[0][0].show() # 由于mnist是一个实例化对象,则可以用[]方式取每一条数据,mnist[0]表示第一条数据,是个元组(image,label)

print(mnist[0]) # 第1条数据,是个元组(image,label)
ret = transforms.ToTensor()(mnist[0][0]) # 将mage对象转换成张量,[28,28,1]->[1,28,28]
print(ret.size())

运行结果:

(<PIL.Image.Image image mode=L size=28x28 at 0x211EE644198>, 5)
torch.Size([1, 28, 28])

注意:
transforms.ToTensor对象有个__call__方法,所以可以对其示例能够传入数据获取结果。

(2)torchvision.transforms.Normalize(mean, std)

作用:标准化张量
参数:
给定均值: mean注意: shape和图片的通道数相同(指的是每个通道的均值);
方差: std, 注意:和图片的通道数相同(指的是每个通道的方差),将会把Tensor规范化处理
即: Normalized_ image=(image-mean)/std.

例如:

from torchvision.datasets import MNIST
from torchvision import transforms

mnist = MNIST("./data", train=True, download=False) # 是个dataset的实例

# mnist[0][0].show() # 由于mnist是一个实例化对象,则可以用[]方式取每一条数据,mnist[0]表示第一条数据,是个元组(image,label)

print(mnist[0]) # 第1条数据,是个元组(image,label)
ret = transforms.ToTensor()(mnist[0][0])
print(ret.size())

norm_img = transforms.Normalize(mean=[0.1307], std=[0.3081])(ret)  # 进行规范化处理
print(norm_img)

运行结果:

PyTorch学习笔记——(7)使用pytorch实现手写数字识别,可以很好的练习pytorch_第1张图片

(3)torchvision.transforms.Compose(transforms)

作用:将多个transforms组合起来使用

例如:

transforms.Compose([transforms.ToTensor()(), # 先转化为Tensor
                   transforms.Normalize(mean, std)]) # 再进行正则化

2.2 准备MNIST数据集的Dataset和DataLoader

# 1、准备数据集
def get_dataloader(train=True):
    # 准备数据集,其中0.1307, 0.3081为MNIST数据集的均值和标准差,这样操作能够对其进行标准化
    # 因为MNIST只有一个通道(黑白色),所以列表中只有一个
    transforms_fn = transform=Compose([ToTensor(),  # 先转化为Tensor
                                       Normalize(mean=[0.1307], std=[0.3081])]) # 再进行正则化

    dataset = MNIST("./data", train=train, download=False, transform=transforms_fn)

    # 准备数据迭代器
    data_loader = DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True)

    return data_loader

注意:要是首次,需要download=True

3 模型的构建

补充:全连接层:当前一层的神经元和前一层的神经元相互连接,其核心操作就是 y = w x y = wx y=wx,即矩阵的乘法,实现对前一层的数据的变换模型的构建使用了一个三层的神经网络,其中包括两个全连接层和一个输出层,第一个全连接层会经过激活函数的处理,将处理后的结果交给下一个全连接层,进行变换后输出结果那么在这个模型中有两个地方需要注意:

  • 1.激活函数如何使用
  • 2.每一层数据的形状
  • 3.模型的损失函数

3.1 激活函数的使用:

常用的激活函数为Relu激活函数,他的使用非常简单,Relu激活函数由import torch.nn . functional as F提供,F.relu(x) 即可对x进行处理

例如:

In [30]: b
out[30]: tensor([-2, -1, 0, 1, 2])

In [31]: import torch.nn.functiona1 as F

In [32]: F.re1u(b)
out[32]: tensor([0, 0, 0, 1, 2])

3.2 模型中数据的形状:

1、原始输入数据为的形状: [batch_size ,1,28,28]
2、进行形状的修改: [batch_size,28*28] ,(全连接层是在进行矩阵的乘法操作);
3、第一个全连接层的输出形状: [batch_size,28],这里的28是个,人设定的,你也可以设置为别的;
4、激活函数不会修改数据的形状;
5、第二个全连接层的输出形状: [batch_size , 10] ,因为手写数字有10个类别。

构建模型的代码如下:

# 2、构建模型
class MnistModel(nn.Module):
    def __init__(self):
        super(self, MnistModel).__init__()
        self.fc1 = nn.Linear(1*28*28, 28)
        self.fc2 = nn.Linear(28, 10)

    def forward(self, input):
        """
        :param x:[batch_size, 1, 28, 28]
        :return:
        """
        # 1、修改形状
        x = input.view(input.size(0), 1*28*28)
        # input = input.view(-1, 1*28*28)

        # 2、进行全连接操作
        x = self.fc1(x)

        # 3、进行激活函数的处理,形状不会变
        F.relu(x)

        # 4、输出层
        out = self.fc2(x)

        return F.log_softmax(out)

4、模型的训练

训练的流程:

  • 1.实例化模型,设置模型为训练模式
  • 2.实例化优化器类,实例化损失函数
  • 3.获取,遍历dataloader
  • 4.梯度置为0
  • 5.进行向前计算
  • 6.计算损失
  • 7.反向传播
  • 8.更新参数
def train(epoch):
    """实现训练过程"""
    mode = True
    model.train(mode=mode ) # 模型设置为训练模式

    data_loader = get_dataloader()
    for idx, (input, target) in enumerate(data_loader): # 每一轮里面的数据进行遍历
        optimizer.zero_grad() # 梯度清零
        output = model(input) # 调用模型,得到预测值
        loss = F.nll_loss(output, target) # 得到损失
        loss.backward() # 反向传播
        optimizer.step() # 梯度更新
        if idx % 10 == 0:
            print("epoch:",epoch," idx:",idx, " loss:",loss.item())

if __name__ == "__main__":
    for i in range(3): # 训练三轮
        train(i)

5、模型的保存和加载

5.1 模型的保存

torch.save(mnist_net.state_dict(), "mode1/mnist_net.pt") #保存模型参数
torch.save(optimizer.state_dict()'results/mnist_optimizer.pt' #保存优化器参数

5.2 模型的加载

mnist_net.1oad_state_dict(torch.1oad("mode1/mnist_net.pt")) # 加载模型的参数
optimizer.1oad_state_dict(torch.1oad("resu1ts/mnist_optimizer.pt")) # 加载优化器的参数

6、模型的评估

评估的过程和训练的过程相似,但是:

  • 1、不需要计算梯度
  • 2、需要收集损失和准确率,用来计算平均损失和平均准确率
  • 3、损失的计算和训练时候损失的计算方法相同
  • 4、准确率的计算:
    • 模型的输出为[batch_size,10]的形状
    • 其中最大值的位置就是其预测的目标值(预测值进行过sotfmax后为概率, sotfmax中分母
      都是相同的,分子越大,概率越大)
    • 最大值的位置获取的方法可以使用torch.max ,返回最大值和最大值的位置(这个要注意,torch的新版本的max,返回的是两个张量,第一个是值,第二个是对应的索引,因此加[-1]表示取索引,和argmax一样)
    • 返回最大值的位置后,和真实值( [batch_size])进行对比,相同表示预测成功
def test():
    loss_list = []
    acc_list = []

    test_dataloader = get_dataloader(train=False)
    for inx, (input, target) in enumerate(test_dataloader):
        with torch.no_grad():
            output = model(input)
            cur_loss = F.nll_loss(output, target)
            loss_list.append(cur_loss)
            # print(type(output))
            # 计算准确率
            # output [batch_size, 10], target [batch_size]
            pred = output.max(dim=-1)[-1] # tensor的max/min返回两个值:第一个是值,第二个是对应的索引,因此加[-1]表示取索引,和argmax一样
            # print(pred)
            cur_acc = pred.eq(target).float().mean()
            acc_list.append(cur_acc)
    print("平均准确率:", np.mean(acc_list), "平均损失:", np.mean(loss_list))

7、完整代码:

7.1 代码:

import os
import torch
import numpy as np
from torchvision.transforms import Compose, ToTensor, Normalize
import torch.nn as nn
import torch.nn.functional as F
from torch.optim import Adam
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST

BATCH_SIZE = 128

# 1、准备数据集
def get_dataloader(train=True):
    # 准备数据集,其中0.1307, 0.3081为MNIST数据集的均值和标准差,这样操作能够对其进行标准化
    # 因为MNIST只有一个通道(黑白色),所以列表中只有一个
    transforms_fn = transform=Compose([ToTensor(),  # 先转化为Tensor
                                       Normalize(mean=[0.1307], std=[0.3081])]) # 再进行正则化

    dataset = MNIST("./data", train=train, download=False, transform=transforms_fn)

    # 准备数据迭代器
    data_loader = DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True)

    return data_loader

# 2、构建模型
class MnistModel(nn.Module):
    def __init__(self):
        super(MnistModel, self).__init__()
        self.fc1 = nn.Linear(1*28*28, 28)
        self.fc2 = nn.Linear(28, 10)

    def forward(self, input):
        """
        :param x:[batch_size, 1, 28, 28]
        :return:
        """
        # 1、修改形状
        x = input.view(input.size(0), 1*28*28)
        # input = input.view(-1, 1*28*28)

        # 2、进行全连接操作
        x = self.fc1(x)

        # 3、进行激活函数的处理,形状不会变
        F.relu(x)

        # 4、输出层
        out = self.fc2(x)

        return F.log_softmax(out, dim=-1)

model = MnistModel()
optimizer = Adam(model.parameters(), lr=0.001)

if os.path.exists("./model/model.pkl"):
    # 加载模型参数
    model.load_state_dict(torch.load("./model/model.pkl"))
    # 加载优化器的参数
    optimizer.load_state_dict(torch.load("./model/optimizer.pkl"))

def train(epoch):
    """实现训练过程"""
    mode = True
    model.train(mode=mode ) # 模型设置为训练模式

    data_loader = get_dataloader()
    for idx, (input, target) in enumerate(data_loader): # 每一轮里面的数据进行遍历
        optimizer.zero_grad() # 梯度清零
        output = model(input) # 调用模型,得到预测值
        loss = F.nll_loss(output, target) # 得到损失
        loss.backward() # 反向传播
        optimizer.step() # 梯度更新
        if idx % 10 == 0:
            print("epoch:",epoch," idx:",idx, " loss:",loss.item())

        # 模型的保存
        if idx % 100 == 0:
            torch.save(model.state_dict(), "./model/model.pkl")
            torch.save(optimizer.state_dict(), "./model/optimizer.pkl")

def test():
    loss_list = []
    acc_list = []

    test_dataloader = get_dataloader(train=False)
    for inx, (input, target) in enumerate(test_dataloader):
        with torch.no_grad():
            output = model(input)
            cur_loss = F.nll_loss(output, target)
            loss_list.append(cur_loss)
            # print(type(output))
            # 计算准确率
            # output [batch_size, 10], target [batch_size]
            pred = output.max(dim=-1)[-1] # tensor的max/min返回两个值:第一个是值,第二个是对应的索引,因此加[-1]表示取索引,和argmax一样
            # print(pred)
            cur_acc = pred.eq(target).float().mean()
            acc_list.append(cur_acc)
    print("平均准确率:", np.mean(acc_list), "平均损失:", np.mean(loss_list))


if __name__ == "__main__":
    # for i in range(3): # 训练三轮
    #     train(i)
    test()

7.2 结果:

平均准确率: 0.9193038 平均损失: 0.2860677

你可能感兴趣的:(PyTorch)