计算机视觉实践-Task3 字符识别模型

3.1 学习目标

  1. 学习CNN基础和原理
  2. 使用Pytorch框架构建CNN模型,并完成训练

3.2 CNN介绍

卷积神经网络(简称CNN)是一类特殊的人工神经网络,是深度学习中重要的一个分支。CNN在很多领域都表现优异,精度和速度比传统计算学习算法高很多。特别是在计算机视觉领域,CNN是解决图像分类、图像检索、物体检测和语义分割的主流模型。

CNN每一层由众多的卷积核组成,每个卷积核对输入的像素进行卷积操作,得到下一次的输入。随着网络层的增加卷积核会逐渐扩大感受野,并缩减图像的尺寸。
CNN卷积神经网络通常包含以下几种层:

  • 卷积层(Convolutional layer),卷积神经网路中每层卷积层由若干卷积单元组成,每个卷积单元的参数都是通过反向传播算法优化得到的。卷积运算的目的是提取输入的不同特征,第一层卷积层可能只能提取一些低级的特征如边缘、线条和角等层级,更多层的网络能从低级特征中迭代提取更复杂的特征。
  • 线性整流层(Rectified Linear Units layer, ReLU layer),这一层神经的活性化函数(Activation function)使用线性整流(Rectified Linear Units, ReLU)。
  • 池化层(Pooling layer),通常在卷积层之后会得到维度很大的特征,将特征切成几个区域,取其最大值或平均值,得到新的、维度较小的特征。 全连接层( Fully-Connected layer), 把所有局部特征结合变成全局特征,用来计算最后每一类的得分。

具体的内容不详细介绍,下面进行Pytorch构建CNN模型的研究。

3.3 Pytorch构建CNN模型

这个CNN模型包括两个卷积层,最后并联6个全连接层进行分类,先放上全部代码:

import torch
torch.manual_seed(0)
torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = True

import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data.dataset import Dataset

# 定义模型
class SVHN_Model1(nn.Module):
    def __init__(self):
        super(SVHN_Model1, self).__init__()
        # CNN提取特征模块
        self.cnn = nn.Sequential(
            nn.Conv2d(3, 16, kernel_size=(3, 3), stride=(2, 2)),
            nn.ReLU(),  
            nn.MaxPool2d(2),
            nn.Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2)),
            nn.ReLU(), 
            nn.MaxPool2d(2),
        )
        # 
        self.fc1 = nn.Linear(32*3*7, 11)
        self.fc2 = nn.Linear(32*3*7, 11)
        self.fc3 = nn.Linear(32*3*7, 11)
        self.fc4 = nn.Linear(32*3*7, 11)
        self.fc5 = nn.Linear(32*3*7, 11)
        self.fc6 = nn.Linear(32*3*7, 11)
    
    def forward(self, img):        
        feat = self.cnn(img)
        feat = feat.view(feat.shape[0], -1)
        c1 = self.fc1(feat)
        c2 = self.fc2(feat)
        c3 = self.fc3(feat)
        c4 = self.fc4(feat)
        c5 = self.fc5(feat)
        c6 = self.fc6(feat)
        return c1, c2, c3, c4, c5, c6
    
model = SVHN_Model1()

训练代码:

# 损失函数
criterion = nn.CrossEntropyLoss()
# 优化器
optimizer = torch.optim.Adam(model.parameters(), 0.005)

loss_plot, c0_plot = [], []
# 迭代10个Epoch
for epoch in range(10):
    for data in train_loader:
        c0, c1, c2, c3, c4, c5 = model(data[0])
        loss = criterion(c0, data[1][:, 0]) + \
                criterion(c1, data[1][:, 1]) + \
                criterion(c2, data[1][:, 2]) + \
                criterion(c3, data[1][:, 3]) + \
                criterion(c4, data[1][:, 4]) + \
                criterion(c5, data[1][:, 5])
        loss /= 6
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        loss_plot.append(loss.item())
        c0_plot.append((c0.argmax(1) == data[1][:, 0]).sum().item()*1.0 / c0.shape[0])
        
    print(epoch)

为了追求精度,也可以使用在ImageNet数据集上的预训练模型,具体方法如下:

class SVHN_Model2(nn.Module):
    def __init__(self):
        super(SVHN_Model1, self).__init__()
                
        model_conv = models.resnet18(pretrained=True)
        model_conv.avgpool = nn.AdaptiveAvgPool2d(1)
        model_conv = nn.Sequential(*list(model_conv.children())[:-1])
        self.cnn = model_conv
        
        self.fc1 = nn.Linear(512, 11)
        self.fc2 = nn.Linear(512, 11)
        self.fc3 = nn.Linear(512, 11)
        self.fc4 = nn.Linear(512, 11)
        self.fc5 = nn.Linear(512, 11)
    
    def forward(self, img):        
        feat = self.cnn(img)
        # print(feat.shape)
        feat = feat.view(feat.shape[0], -1)
        c1 = self.fc1(feat)
        c2 = self.fc2(feat)
        c3 = self.fc3(feat)
        c4 = self.fc4(feat)
        c5 = self.fc5(feat)
        return c1, c2, c3, c4, c5

接下来分析代码:

torch.manual_seed(0)

官方文档给出的是:设定生成随机数的种子,并返回一个 torch._C.Generator 对象。
作用是:
在神经网络中,参数默认是随机初始化的,而不同的初始化参数会导致结果不同,一些好的结果我们希望能够复现,随机种子便可以实现。
原理具体而言可以参考下面解释:
计算机视觉实践-Task3 字符识别模型_第1张图片
截图原评论:https://blog.csdn.net/youhuakongzhi/article/details/90572969

torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = True

作用:torch.backends.cudnn.benchmark = true是为了提高卷积神经网络的运行速度。

设置 torch.backends.cudnn.benchmark=True 将会让程序在开始时花费一点额外时间,为整个网络的每个卷积层搜索最适合它的卷积实现算法,进而实现网络的加速。适用场景是网络结构固定(不是动态变化的),网络的输入形状(包括 batch size,图片大小,输入的通道)是不变的,其实也就是一般情况下都比较适用。反之,如果卷积层的设置一直变化,将会导致程序不停地做优化,反而会耗费更多的时间。
————————————————
版权声明:本文为CSDN博主「AlanBupt」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/byron123456sfsfsfa/article/details/96003317

torch.backends.cudnn.deterministic = False作用是:

如果把我们这个 flag 置为True的话,每次返回的卷积算法将是确定的,即默认算法。如果配合上设置 Torch的随机种子为固定值的话,应该可以保证每次运行网络的时候相同输入的输出是固定的。

具体的可以去看这个文章:https://blog.csdn.net/byron123456sfsfsfa/article/details/96003317

下面是一些导入,先不管

import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data.dataset import Dataset
class SVHN_Model1(nn.Module):
    def __init__(self):
        super(SVHN_Model1, self).__init__()
        # CNN提取特征模块
        self.cnn = nn.Sequential(
            nn.Conv2d(3, 16, kernel_size=(3, 3), stride=(2, 2)),
            nn.ReLU(),  
            nn.MaxPool2d(2),
            nn.Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2)),
            nn.ReLU(), 
            nn.MaxPool2d(2),
        )
        # 
        self.fc1 = nn.Linear(32*3*7, 11)
        self.fc2 = nn.Linear(32*3*7, 11)
        self.fc3 = nn.Linear(32*3*7, 11)
        self.fc4 = nn.Linear(32*3*7, 11)
        self.fc5 = nn.Linear(32*3*7, 11)
        self.fc6 = nn.Linear(32*3*7, 11)
    
    def forward(self, img):        
        feat = self.cnn(img)
        feat = feat.view(feat.shape[0], -1)
        c1 = self.fc1(feat)
        c2 = self.fc2(feat)
        c3 = self.fc3(feat)
        c4 = self.fc4(feat)
        c5 = self.fc5(feat)
        c6 = self.fc6(feat)
        return c1, c2, c3, c4, c5, c6
    
model = SVHN_Model1()

以上是一个简单的自定义层,我们实现一个自定义层主要包含以下几个步骤:

(1)自定义一个类,继承自Module类,并且一定要实现两个基本的函数,第一是构造函数__init__,第二个是层的逻辑运算函数,即所谓的前向计算函数forward函数

(2)在构造函数_init__中实现层的参数定义。比如Linear层的权重和偏置,Conv2d层的in_channels,out_channels,kernel_size,stride=1,padding=0, dilation=1,groups=1,bias=True, padding_mode='zeros’这一系列参数;

(3)在前向传播forward函数里面实现前向运算。这一般都是通过torch.nn.functional.***函数来实现,当然很多时候我们也需要自定义自己的运算方式。如果该层含有权重,那么权重必须是nn.Parameter类型,关于Tensor和Variable(0.3版本之前)与Parameter的区别请参阅相关的文档。简单说就是Parameter默认需要求导,其他两个类型则不会。另外一般情况下,可能的话,为自己定义的新层提供默认的参数初始化,以防使用过程中忘记初始化操作。

(4)补充:一般情况下,我们定义的参数是可以求导的,但是自定义操作如不可导,需要实现backward函数。

其中 super(Student,self).__init__()是对继承自父类的属性进行初始化。而且是用父类的初始化方法来初始化继承的属性。也就是说,子类继承了父类的所有属性和方法,父类属性自然会用父类方法来进行初始化。

那么如何继承继承nn.Module类来实现自定义,我将查到的部分内容引用在这里:

pytorch里面一切自定义操作基本上都是继承nn.Module类来实现的,我们在定义自已的网络的时候,需要继承nn.Module类,并重新实现构造函数__init__构造函数和forward这两个方法。

  • (1)一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数__init__()中,当然我也可以吧不具有参数的层也放在里面;
  • (2)一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中,也可不放在构造函数中,如果不放在构造函数__init__里面,则在forward方法里面可以使用nn.functional来代替;
  • (3)forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
nn.Conv2d(3, 16, kernel_size=(3, 3), stride=(2, 2))

nn.Conv2d 这些参数的意思:Torch.nn.Conv2d(in_channels,out_channels,kernel_size,stride=1,padding=0,dilation=1,groups=1,bias=True)

in_channels: 输入维度,这里我们是3纬
out_channels: 输出维度,这里我们是16纬
kernel_size: 卷积核大小,这里我们是(3,3)
stride: 步长大小,默认为1,当这个参数为tuple数组时候,tuple的第一维度表示height的数值,tuple的第二维度表示width的数值,当是数组时,计算时height使用索引为0的值,width使用索引为1的值。我们这里都是2。

这个nn.ReLU是非线形激活函数
计算机视觉实践-Task3 字符识别模型_第2张图片
这个nn.MaxPool2d是池化层class torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
相关的参数:

  • kernel_size(int or tuple) - max pooling的窗口大小
  • stride(int or tuple,optional) - max pooling的窗口移动的步长,默认值是kernel_size 。
  • padding(int or tuple, optional) - 输入的每一条边补充0的层数
  • dilation(int or tuple, optional) – 一个控制窗口中元素步幅的参数
  • return_indices-如果等于True,会返回输出最大值的序号,对于上采样操作torch.nn.MaxUnpool2d会有帮助
  • ceil_mode -如果等于True,计算输出信号大小的时候,会使用向上取整ceil,代替默认的向下取整floor的操作

最后这个,是并联6个全连接层进行分类:

   		self.fc1 = nn.Linear(32*3*7, 11)
        self.fc2 = nn.Linear(32*3*7, 11)
        self.fc3 = nn.Linear(32*3*7, 11)
        self.fc4 = nn.Linear(32*3*7, 11)
        self.fc5 = nn.Linear(32*3*7, 11)
        self.fc6 = nn.Linear(32*3*7, 11)

forward中feat = feat.view(feat.shape[0], -1)这句话一般出现在model类的forward函数中,具体位置一般都是在调用分类器之前。分类器是一个简单的nn.Linear()结构,输入输出都是维度为一的值,x = x.view(x.size(0), -1) 这句话的出现就是为了将前面多维度的tensor展平成一维。-1是自适应的意思,x.size(0)指batchsize的值,x = x.view(batchsize, -1)中batchsize指转换后有几行,而-1指在不告诉函数有多少列的情况下,根据原tensor数据和batchsize自动分配列数。

以上是对模型的学习,训练代码有一些资料还没看完,改天再说。

所参考的来源:

super(Student,self).__init__()
:http://www.imooc.com/qadetail/72165

pytorch教程之nn.Module类详解——使用Module类来自定义模型
https://blog.csdn.net/qq_27825451/article/details/90550890

pytorch教程之nn.Sequential类详解——使用Sequential类来自定义顺序连接模型https://blog.csdn.net/qq_27825451/article/details/90551513

pytorch 常用参数
https://www.cnblogs.com/wanghui-garcia/p/10775859.html

x = x.view(x.size(0), -1)
的理解https://blog.csdn.net/whut_ldz/article/details/78882532?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase

你可能感兴趣的:(自学)