天池大赛:街景字符编码识别——Part3:字符识别模型

街景字符编码识别

更新流程↓
Task01:赛题理解
Task02:数据读取与数据扩增
Task03:字符识别模型
Task04:模型训练与验证
Task05:模型集成
???
淦,飞碟
飞…飞车
比赛链接


Part3:字符识别模型





1. 卷积神经网络CNN

卷积神经网络(Convolutional Neural Networks, CNN)是一类包含卷积计算且具有深度结构的前馈神经网络(Feedforward Neural Networks),是深度学习(deep learning)的代表算法之一。卷积神经网络具有表征学习(representation learning)能力,能够按其阶层结构对输入信息进行平移不变分类(shift-invariant classification),因此也被称为“平移不变人工神经网络(Shift-Invariant Artificial Neural Networks, SIANN)”

  卷积神经网络的主要结构如下图所示,由输入层、卷积层、池化层、全连接层、输出层构成。
卷积神经网络的主要结构

 1.1. 输入层

  本次赛题CNN输入层代表了一张图片的像素矩阵。上图中最左侧三维矩阵代表一张图片,三维矩阵的维度代表了图像的 (高度h,宽度w,色彩通道数c)。三维矩阵的长、宽代表了图像的大小,深度代表了图像的色彩通道,RGB模式下图片深度为3,灰度模式图片深度为1。

 1.2. 卷积层(Convolutional layer)

  CNN 中最为重要的部分。与全连接层不同,卷积层中每一个节点的输入只是上一层神经网络中的一小块,这个小块常用的大小有 3×3 或者 5×5。一般来说,通过卷积层处理过的节点矩阵会变的更深。

  1.2.1. 卷积(Convolutions)

  卷积中含有以下几个参数
二维卷积、内核大小为 3、步幅为 1

二维卷积、内核大小为 3、步幅为 1
  • 内核大小(filter):卷积内核大小定义了卷积的视野。二维的常见选择是3——即3x3像素。
  • 卷积步长(stride):卷积步长定义了遍历图像时内核的步长。虽然它的默认值通常为1,但我们可以使用2的步长,类似于最大池化对图像进行下采样。
  • padding:padding定义样本的边框如何处理。一(半)个padding卷积将保持空间输出尺寸等于输入尺寸,而如果内核大于1,则不加卷积将消除一些边界。
  • 输入输出通道:卷积层需要一定数量的输入通道(I),并计算出特定数量的输出通道(O)。可以通过I * O * K来计算这样一层所需的参数,其中K等于内核中的值的数量。

  还有扩张卷积、转置卷积等等。更多卷积类型可以阅读参考资料[1]进行了解。


  1.2.2. 过滤器(filter)

  过滤器(filter),即卷积内核。若使用内核大小为3的卷积内核对一张5x5的图片进行卷积,会得到一个3x3的矩阵(Feature Map)。

在这里插入图片描述在这里插入图片描述

  内核可以将当前层神经网络上的一个子节点矩阵转化为下一层神经网络上的一个单位节点矩阵。单位节点矩阵制的是长和宽都是1,但深度不限的节点矩阵。

在这里插入图片描述
  常用的内核尺寸有 3×3 或 5×5,即上图黄色和橙色矩阵中的前两维,这个是人为设定的;内核的节点矩阵深度,即上图黄色和橙色矩阵中的最后一维(内核尺寸的最后一维),是由当前层神经网络节点矩阵的深度(RGB 图像节点矩阵深度为 3)决定的;卷积层输出矩阵的深度(也称为内核深度)是由该卷积层中内核的个数决定,该参数也是人为设定的,一般随着卷积操作的进行越来越大。上图中内核尺寸为 3×3×3,内核深度为 2。

  卷积操作中,一个 3×3×3 的子节点矩阵和一个 3×3×3 的 卷积内核对应元素相乘,得到的是一个 3×3×3 的矩阵,此时将该矩阵所有元素求和,得到一个 1×1×1 的矩阵,将其再加上卷积内核的偏斜量bias,经过激活函数得到最后的结果,将最后的结果填入到对应的输出矩阵中。输出矩阵中第一个元素 g ( 0 , 0 , 0 ) g(0, 0, 0) g(0,0,0)的计算如下所示:
g ( 0 , 0 , 0 ) = f ( ∑ x 3 ∑ y 3 ∑ z 3 a x , y , z × w x , y , z ( 0 ) + b ( 0 ) ) g(0, 0, 0) = f( \sum_{x}^3\sum_{y}^3\sum_{z}^3a_{x,y,z} × w_{x,y,z}^{(0)} + b^{(0)}) g(0,0,0)=f(x3y3z3ax,y,z×wx,y,z(0)+b(0))

  上式中, a x , y , z a_{x,y,z} ax,y,z 表示当前层的一个子节点矩阵,即 6×6×3 矩阵中左上角 3×3×3 部分; w x , y , z ( 0 ) w_{x,y,z}^{(0)} wx,y,z(0) 表示第一个卷积内核的权重,即第一个卷积内核每个位置的值; b ( 0 ) b^{(0)} b(0) 表示第一个卷积内核的偏置 bias,是一个实数; f f f 表示激活函数,如 ReLU 激活函数。

  “卷积层结构的前向传播过程就是通过将一个卷积内核从神经网络当前层的左上角移动到右下角,并且在移动中计算每一个对应的单位矩阵得到的。”
  在这里插入图片描述

卷积操作流程
5×5×3矩阵,卷积步长为2,padding为1



  1.2.3. padding

  padding,顾名思义,就是在图像周围进行填充,一般常用zero padding,即用 0 来填充。当padding=1时,在图像周围填充一圈;当padding=2时,填充两圈。
  为什么要padding?两个原因,第一个是随着卷积操作的进行,图像会越来越小; 第二个是对图片边缘信息和内部信息的重视程度不一样,边缘信息卷积内核只经过一次,而内部信息会被经过多次。
  有效卷积和相同卷积的区别在于有效卷积: 无padding;相同卷积: padding使输出尺寸与输入尺寸相同。
  


  1.2.4. 卷积步长

  卷积步长(stride) 就是像在上图中卷积内核一次移动的格数,上图中 卷积步长S为2。
  卷积层输出矩阵的大小与输入图片大小 N N N、卷积内核的尺寸 F F F、padding 的大小 P P P、卷积步长 S S S 都有关。(假设输入的图片是方形的)
o u t p u t = ⌊ N + 2 P − F S ⌋ + 1 output = \lfloor \frac{N + 2P - F}{S} \rfloor + 1 output=SN+2PF+1
  当 S ≠ 1 S\not=1 S=1 时,可能存在 N + 2 P − F S \frac{N + 2P - F}{S} SN+2PF 不是整数的情况,这个时候对 N + 2 P − F S \frac{N + 2P - F}{S} SN+2PF 取下整或者使用整除。

  依据《TensorFlow实战Google深度学习框架》, o u t p u t output output 也可以写成如下形势:
o u t p u t = ⌈ N + 2 P − F + 1 S ⌋ output= \lceil \frac{N + 2P - F + 1 }{S} \rfloor output=SN+2PF+1
  上面两个公式最后的结果会是一样。



 1.3. 池化层(Pooling layer)

  池化层可以非常有效地缩小矩阵的尺寸(主要减少矩阵的长和宽,一般不会去减少矩阵深度),从而减少最后全连接层中的参数。“使用池化层既可以加快计算速度也有防止过拟合问题的作用。”

  与卷积层类似,池化层的前向传播过程也是通过一个类似卷积内核的结构完成的。不过池化层卷积内核中的计算不是节点的加权和,而是采用更加简单的最大值或者平均值运算。使用最大值操作的池化层被称为最大池化层(max pooling),这是使用最多的池化层结构。使用平均值操作的池化层被称为平均池化层(average pooling)

  与卷积层的卷积内核类似,池化层的卷积内核也需要人工设定卷积内核的尺寸、是否使用全 0 填充 以及卷积内核移动的步长等设置,而且这些设置的意义也是一样的。

  卷积层和池化层中卷积内核的移动方式是相似的,唯一的区别在于卷积层使用的卷积内核是横跨整个深度的,而池化层使用的卷积内核只影响一个深度上的节点。所以池化层的卷积内核除了在长和宽两个维度移动之外,它还需要在深度这个维度移动。也就是说,在进行 max 或者 average 操作时,只会在同一个矩阵深度上进行,而不会跨矩阵深度进行。
在这里插入图片描述
  上图中,池化层卷积内核尺寸为 2×2,即 F = 2 F = 2 F=2,padding 大小 P = 0 P = 0 P=0,卷积步长 S = 2 S = 2 S=2
  池化层一般不改变矩阵的深度,只改变矩阵的长和宽。

  池化层没有trainable参数,只有一些需要人工设定的超参数。



 1.4. 全连接层( Fully Connected layer)

  全连接层( Fully Connected layer,FC) 在整个卷积神经网络中起到分类器的作用。如果说卷积层、池化层和激活函数层等操作是将原始数据映射到隐层特征空间的话,全连接层则起到将学到的“分布式特征表示”映射到样本标记空间的作用。在实际使用中,全连接层可由卷积操作实现:
  1)对前层是全连接的全连接层可以转化为卷积核为1x1的卷积。
  2)对前层是卷积层的全连接层可以转化为卷积核为 h × w h × w h×w的全局卷积, h h h w w w分别为前层卷积结果的高度和宽度。

  全连接的核心操作就是矩阵向量乘积 y = W x y = Wx y=Wx。本质就是由一个特征空间线性变换到另一个特征空间。目标空间的任一维——也就是隐层的一个cell——都认为会受到源空间的每一维的影响。不考虑严谨,可以说,目标向量是源向量的加权和。在本文的第一张图中,最后一层全连接层激活函数使用 softmax

  在 CNN 中,全连接常出现在最后几层,用于对前面设计的特征做加权和。比如mnist,前面的卷积和池化相当于做特征工程,后面的全连接相当于做特征加权。(卷积相当于全连接的有意弱化,按照局部视野的启发,把局部之外的弱影响直接抹为零影响;还做了一点强制,不同的局部所使用的参数居然一致。弱化使参数变少,节省计算量,又专攻局部不贪多求全;强制进一步减少参数。少即是多) 在 RNN 中,全连接用来把 embedding 空间拉到隐层空间,把隐层空间转回 label 空间等。




2. Pytorch构建CNN模型

  在Pytorch中构建CNN模型非常简单,只需要定义好模型的参数正向传播即可,Pytorch会根据正向传播自动计算反向传播。

  在本章我们会构建一个非常简单的CNN,然后进行训练。这个CNN模型包括两个卷积层,最后并联六个全连接层进行分类。

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)
        for i range(1:7)
        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


参考资料

©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页