Pytorch实现手写数字识别

1、流程分析

  1. 准备数据,需要准备DataLoader
  2. 构建模型,可以使用torch构造一个深层的神经网络
  3. 模型的训练
  4. 模型的保存
  5. 模型的评估

2、准备数据集和测试集

使用torch中自带的MNIST数据集,调用MNIST返回的结果中图形数据是一个Image对象,需要对其进行处理,为了进行数据的处理,接下来学习torchvision.transforms的方法

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

torchvision.transforms.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 import transforms
import numpy as np

data = np.random.randint(0,255,size=12)
img = data.reshape(2,2,3)
print(img.shape)
img_tensor = transforms.ToTensor()(img)	# 转换成tensor
print(img_tensor)
print(img_tensor.shape)
torchvision.transforms.Normalize(mean,std)

给定均值:mean,shape和图片的通道数相同(指的是每个通道的均值),方差:std,和图片的通道数相同(指的是每个通道的方差),将会把Tensor规范化处理。
即:Normalized_image=(image-mean)/std
例如:

from torchvision import transforms
import numpy as np
import torchvision
data = np.random.randint(0,255,size=12)
img = data.reshape(2,2,3)
img = transforms.ToTensor()(img)	# 转换成tensor
print(img)

norm_img = transform.Normalize((10,10,10),(1,1,1))(img) # 进行规范化处理
print(norm_img)

torchvision.transforms.Compose(transforms)

将多个transform组合起来使用。
如:

transforms.Compose([
	torchvision.transforms.Totensor(),# 先转化为Tensor
	torchvision.transforms.Normalize(mean,std)# 再进行正则化,均值和标准差的形状和通道数相同
])

准备MNIST数据集的Dataset和DataLoader

准备数据集

from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torchvision.transforms import Compose,ToTenser,Normalize

BATCH_SIZE = 128

#1. 准备数据
def get_dataloader(train=True):
    transform_fn = Compose([
        ToTenser(),
        Normalize(mean=(0.1307,),std=(0.3081,)) # mean和std的行传个通道数相同,它们两个分别是均值和方差
    ])
    dataset = MNIST('./data',train = train,trandorm = transform_fn)
    data_loader = DataLoader(dataset,batch_size=BATCH_SIZE,shuffle=True)
    return data_loader

3、构建模型

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

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

3.1、激活函数的使用

前面介绍了激活函数的作用,常用的激活函数为Relu激活函数,它是有import torch.nn.functional as F提供,F.relu(x)即可对x进行处理。
如:x = F.relu(x)

3.2、模型中数据的形状

  1. 原始输入数据的形状为:[batch_size,1,28,28]
  2. 进行形状的修改:[batch_size,28*28](全连接层是在进行矩阵的乘法操作)
  3. 第一个全连接层的输出形状:[batch_size,28],这里的28是个人设定的,可以修改为其他值。
  4. 激活函数不会修改数据的形状
  5. 第二个全连接层的输出形状:[batch_size,10],以为手写数据有10个类别。
    构建模型的代码如下:
import torch
from torch import nn
import torch.nn.functional as F
#2. 模型搭建
class MnistNet(nn.Module):
    def __init__(self):
        super(MnistNet,self).__init__()
        self.fc1 = nn.Linear(28*28,28)  # 参数是输入和输出特征的大小
        self.fc2 = nn.Linear(28, 10)

    def forward(self, input):
        """
        :param input: [batch_size,1,28,28]
        :return:
        """
        # 1.修改形状
        x = input.view(input.size(0),1*28*28)
        # 2.进行全连接操作
        x = self.fc1(x)
        # 3.进行激活函数的处理,形状没有变化
        x = F.relu(x)
        # 4.输出层
        out = self.fc2(x)
        return out

3.3、模型的损失函数

首先,我们需要明确,手写数字识别是一个多分类的问题。对比之前在逻辑回归中,使用sigmoid进行计算对数似然损失定义2分类的损失。
在这里插入图片描述
那么在多分类的过程中我们应该怎么做呐?

  • 多分类和2分类中唯一的区别使我们不能够再使用sigmoid函数来计算当前样本属于某个类别的概率,而应该使用softmax函数。
  • softmax和sigmoid的区别在于我们需要去计算样本属于某个类别的概率,需要计算多次,而sigmoid只需要计算一次
    softmax函数的公式如下:
    在这里插入图片描述

例如下图:
Pytorch实现手写数字识别_第1张图片
Pytorch实现手写数字识别_第2张图片

我们把softmax概率传入对数似然损失得到的损失函数称为交叉熵损失
在pytorch中有两种方法实现交叉熵损失
方法一:

criterion = nn.CrossEntropyLoss()
loss = criterion(input,target)

方法二:

# 1.对输出值计算softmax和取对数
output = F.log_softmax(x,dim=-1)
# 2.使用torch中带权损失
loss =  F.null_loss(output,target)

在这里插入图片描述

4、训练模型

训练的流程:

  1. 实例化模型,设置模型为训练模式
  2. 实例化优化器类,实例化损失函数
  3. 获取,遍历dataloader
  4. 梯度置为0
  5. 进行向前计算
  6. 计算损失
  7. 反向传播
  8. 更新参数
    代码如下:
model = MnistNet()  # 实例化模型
optimizer = Adam(model.parameters(),lr = 0.001) # 实例化优化器
criterion = nn.CrossEntropyLoss()      # 实例化损失函数

def train(epoch):
    """
    训练模型
    :param epoch:
    :return:
    """
    dataloader = get_dataloader()
    for idx,(data,target) in enumerate(dataloader):
        optimizer.zero_grad()# 将梯度置为0
        output = model(data) # 调用模型,得到预测值
        loss = criterion(output,target) # 计算损失
        loss.backward() # 反向传播
        optimizer.step()    # 梯度更新
        if idx%10 == 0:
            print("epoch{},idx{},loss{:.6f}".format(epoch,idx,loss.item()))

5、模型的保存和加载

5.1、模型的保存

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

5.2、模型的加载

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"))

6、模型的评估

注意:

  • 不是对计算进行追踪:with torch.no_grad()
  • 损失:和训练相同
  • 准确率:
    1. 获取预测值 : tensor.max(dim=-1)[-1]
    2. tensor.eq(tensor2).float().mean()
def test():
    loss_list = []
    acc_list = []
    testloader = get_dataloader(train=False)
    for idx,(input,target) in enumerate(testloader):
        with torch.no_grad():
            output = model(input)
            cur_loss = criterion(output,target)
            loss_list.append(cur_loss)
            # 计算准确度
            # output:[batch_size,10] target:[batch_size]
            # max方法可以获取指定维度中数据的最大值,dim=-1指获取行的最大值,dim=0指获取列的最大值
            pred = output.max(dim=-1)[-1]
            acc = pred.eq(target).float().mean()
            acc_list.append(acc)

    print("平均准去率,平均损失:",np.mean(acc_list),np.mean(loss_list))

你可能感兴趣的:(深度学习,pytorch,人工智能,pytorch)