6.2 PyTorch图像的多分类--手写字体识别

欢迎订阅本专栏:《PyTorch深度学习实践》
订阅地址:https://blog.csdn.net/sinat_33761963/category_9720080.html

  • 第二章:认识Tensor的类型、创建、存储、api等,打好Tensor的基础,是进行PyTorch深度学习实践的重中之重的基础。
  • 第三章:学习PyTorch如何读入各种外部数据
  • 第四章:利用PyTorch从头到尾创建、训练、评估一个模型,理解与熟悉PyTorch实现模型的每个步骤,用到的模块与方法。
  • 第五章:学习如何利用PyTorch提供的3种方法去创建各种模型结构。
  • 第六章:利用PyTorch实现简单与经典的模型全过程:简单二分类、手写字体识别、词向量的实现、自编码器实现。
  • 第七章:利用PyTorch实现复杂模型:翻译机(nlp领域)、生成对抗网络(GAN)、强化学习(RL)、风格迁移(cv领域)。
  • 第八章:PyTorch的其他高级用法:模型在不同框架之间的迁移、可视化、多个GPU并行计算。

LeNet网络,做图像的同学肯定超级熟悉,这是1998年提出的一种卷积神经网络,当时风靡于手写数字识别。

关于它的理论知识于网络结构,可以参看我的博客:,也可以在网上搜索,有非常多的文章来介绍这个经典的模型。

Letent的模型结构由卷积-池化-卷积-池化-全连接-全连接-高斯连接这几个步骤组成。现在我们来一起看看如何构建LeNet,并进行手写数字的识别。

6.2.1 准备数据集

MINST是一个手写数字数据库,官网地址为:http://yann.kecun.com/exdb/mnist, 他是非常经典的一个数据集,由60000张训练样本,10000张测试样本,每张图片的尺寸是28*28。

由于这是一个很经典的用于学习和练习的数据集,PyTorch提供了快速下载和加载NIMIST数据集的方法,无需我们自己去官网下载了。torchvision就是PyTorch准们便些的处理图像的工具包,其中包含了图像预处理,加载等的方法,还包括一些经过预训练的经典卷积神经网络模型。现在我们来用Torchvision来下载、加载、预处理MINIST数据集。

import torch
from torchvision import datasets, transforms
import torch.utils.data as Data

# 转换特征
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,),(0.3081,))
])

# 下载、加载、预处理数据
trainset = datasets.MNIST('data', train=True, download=True, transform=transform)
testset = datasets.MNIST('data', train=False, download=True, transform=transform)

# 转换成批量迭代器:DataLoader
trainloader = torch.utils.data.dataLoader(trainset, batch_size=4, shuffle=True, num_work=2)
testloader = torch.utils.data.dataLoader(testset, batch_size=4, shuffle=False, num_work=2)

解释:

(1)torchvision.transforms.Compose()是用来设置预处理的方法,其参数是一个list,list中是预处理方法。

(2)datasets.MNIST()中,第一个参数是数据的路径,参数train设置是否下载加载训练集,download指是否需要去下载,下载后会保存再第一个参数设置的数据路径里,若无需下载则会去该路径下读取数据,tansform是传入前文设置好的预处理方法。如此设置后,程序会去自动下载数据并加载进来,然后根据transform中预处理方法的顺序去做数据的处理。

运行以上代码,代码所在目录下会多一个目录’data’,里面包含processed和raw文件夹,raw路径下是MNIST原始数据集,有4份,分别是训练集、测试机的图像数据与标签数据。

(3)DataLoader是将dataset作为输入,设置batch_size,生成一个迭代器,每次都会输出一批batch_size大小的数据,shuffle参数可以设置是否将原来的dataset打乱后输出数据,num_work是加载数据的子进程数目。在实际项目中,基本上都会使用批量随机梯度下降,因此dataLoader()非常常用。

6.2.2 构建LeNet模型

因为Lenet由好多层构成,因此选择nn.Module的subclass方式去构建模型结构。

  • nn.Conv2d 构建二维卷积层,有3个参数需要输入:in_channels为输入图片的个数,out_channels输出特征图的数目(卷积核的个数),kernel_size为卷积核的大小,本网络中为5·5的矩阵,可以携程(5,5)也可以直接写5,其他参数有默认值:stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode=‘zeros’
  • nn.Linear 构建线性层,in_features是输入的维度, out_features是输出的维度, 还有一个默认值的参数bias=True,表示需要偏置项b
import torch.nn as nn

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.c1 = nn.Conv2d(1,6(5,5))
        self.c3 = nn.Conv2d(6,16,5) 
        self.fc1 = nn.Linear(16*4*4, 120)
        self.fc2 = nn.Linear(120,84)
        self.fc3 = nn.Linear(84,10)
    
    def forward(self, x):
        x = F.max_pool2d(F.relu(self.c1(x)), 2)
        x = F.max_pool2d(F.relu(self.c3(x)), 2)
        x = x.view(-1, self.num_flat_features(x))  # 拉平,使x变成2维矩阵,每个样本的特征拉平成一个维度,以方便之后的线性计算
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        
        return x
        
    def num_flat_features(self,x):
        "size的第一个维度是该批样本的数目,其他维度相乘为所有特征数目"
        size = x.size()[1:]
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

回顾构建nn.Moduel的subclass的要点:

  • 必须继承nn.Module父类
  • 在__init__初始函数中创建layer即submodule,注意这里创建的layer都是有参数的,初始化实例的时候,这些参数也会被初始化,且默认使开启自动梯度计算的;而没有参数的激活函数relu则是直接在forward函数中被调用。
  • 必须定义一个forward函数,其中为模型前向计算的过程
  • num_flat_features是自己定义的函数
  • 对于Conv2d, Linear以及其他很多的submodule,可以在官网找到它们的用法:https://pytorch.org/docs/stable/nn.html#linear-layers

其他要点:

  • 卷积层、线性层中的参数一定要填正确,不理解的再去熟悉一下卷积、池化等计算过程,在其他实践中,一定要先手算一遍模型每层的参数维度,输入输出维度,以确保正确。
  • tensor.view()这个函数是用来变换张量维度的,-1表示在该维度上的值根据其他值自动计算,这个函数会经常被用到。
  • 这其实是一个多元分类的问题,既然是分类问题,最后一层全连接之后肯定要接一个softmax,但是上面的结构中却没有,而是直接返回了全连接的输出,别急,因为pytorch提供的交叉熵损失函数nn.CrossEntropyLoss()中包含了softamx这个步骤(4.6.2节中有单独讲解),等下会使用它。

6.2.3 实例化模型、损失、优化器

训练模型前的这三样东西缺一不可:

CUDA = torch.cuda.is_available()  # 查看是否有GPU

if cuda:
    lenet = LeNet().cuda()
else:
    lenet = LeNet
    
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(lenet.parameters(), rl=0.001, momentum=0.9)

要点讲解:

  • 判断是否有cuda,若有就要将model放到cuda上,以加速训练。建议大家在平时开发时也这样写代码,如此,无论在本机CPU开发还是放到GPU上训练,都无需改动代码。
  • nn.CrossEntropyLoss()这个损失函数,会在很多很多场合用到,无论时图像还是自然语言,所以pay attention.
  • torch.optim下还有很多优化器,你可以使用其他优化器做比较。

6.2.4 训练

好啦,万事俱备,只欠训练。训练还是老套路:

def train(model, criterion, optimizer, epochs=1):
    for epoch in epochs:
        running_loss = 0.0
        for i, data in enumerate(trainloader, 0):
            inputs, labels = data
            if CUDA:
                inputs, labels = inputs.cuda(), label.cuda()
                
            optimizer.zero_grad()
            
            outputs = model(inputs)
            
            loss = criterion(outputs, labels)
            loss.backward()
            
            running_loss += loss.item()
            if i % 999 == 0: # 每经过1000次batch的计算就打印一次平均损失
                print('epoch:%d, batch:%d, loss:%.3f' % (epoch+1, i+1, running_loss / 1000))
                
    print('finish training')
    
train(lenet, criterion, optimizer, epochs=2)
            

要点讲解:

  • 可以打印data出来看看是什么数据结构,其实是个二元组(inputs,labels)
  • 仍然使用了if CUDA的判断,若有GPU则将数据inputs, labels放到GPU上。记住,目前有两处是需要手动放到指定设备上的。

6.2.5存储与加载

在4.8节已经单独讲过模型的存储与加载的两种方法了。现在再来回顾一下:

(1)法1:直接存储加载模型
该方法直接又简洁,存储整个模型结构和参数,当模型很复杂的时候,这种方式会占用较大内存。

torch.save(lenet, 'model.pkl') # 存储
lenet = torch.load('model.pkl') # 加载

(2)法2:存储与加载模型参数
该方法直接保存参数,节省的空间,但它不存储模型结构,所以在加载时需要先构造好模型结构。不过这种方式时比较推荐大家使用的。

torch.save(lenet.state_dict(), 'model.pkl')
lenet.load_state_dict(torch.load('model.pkl'))

6.2.6测试

我们在4.4节讲解过训练过程中用验证集去做评估并且打印在训练集和验证集上的结果,以便动态实时查看模型的训练效果。在模型全部训练结束之后,或者已经保存之后,需要看看在额外测试集上的效果,就可以利用上面的方式加载模型进行测试。

6.2.1中已经加载了testset测试集了,也已经生成对应的dataloader了:

# 下载、加载、预处理数据
testset = datasets.MNIST('data', train=False, download=True, transform=transform)

# 转换成批量迭代器:DataLoader
testloader = torch.utils.data.dataLoader(testset, batch_size=4, shuffle=False, num_work=2)

现在开始测试与评估:

def test(testloader, model):
    correct = 0
    total = 0
    for data in testloader:
        inputs, labels = data
        if CUDA:
            inputs, labels = inputs.cuda(), labels.cuda()
        outputs = model(inputs)
        _, predicts = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicts == labels).sum()
    print('accurate on the testset: %d %%' % (100 * correct / total))
    
test(test_loader, lenet)
    

要点讲解:

  • 每个任务或模型都应该有一个既定的评估指标,从而量化模型的效果,这里采用的是准确率。关于各类量化指标的知识可以参考我的博客:https://blog.csdn.net/sinat_33761963/article/details/54910447
  • 当然咯,pytorch也提供了很多直接计算评估指标的函数,在之后会介绍。

总结

这一节讲CNN的经典模型LeNet的PyTorch实现。

  • 需要先具备卷积网络的知识,0基础的可以参看我的博客;https://blog.csdn.net/sinat_33761963/article/details/52893739,
    https://blog.csdn.net/sinat_33761963/article/details/80672556
  • 无需死记硬背代码,要掌握的是构建网络和训练的整体结构
  • 对于新出现的一些函数,要去理解它的功能,参数如何设置,为什么要这么做
  • 本文虽然讲解了很简单的LeNet模型,但完全可以举一反三,延伸到更复杂的模型的实现。
©️2020 CSDN 皮肤主题: 终极编程指南 设计师:CSDN官方博客 返回首页