这一节主要开始构建一个字符识别模型,基于赛题理解,本章将构建一个定长多字符分类模型
工欲善其事必先利其器,首先来了解下pytorch与TensorFlow。(本比赛主要用到pytorch框架用于解决这个问题)

PyTorch基本结构

字符识别深度学习算法_卷积

pytorch主要分为以下几个模块来训练模型:

  • tensor:tensor为基本结构,可以直接创建,从list创建以及由numpy数组得到,torch还提供一套运算以及shape变换的方法
  • Variable:自动求导机制,利用Variable包括tensor后,便可以使用其求导的功能了,有点像装饰器
  • nn:nn模块是整个pytorch的核心,自己设计的NEt(),继承nn.Model后可以提取模型参数,进行前向forward()运算(自己设计的),以及后向运算(自动),nn提供基本网络结构单元,例如nn.Linear(),nn.Conv2d()等,还提供了基本损失函数nn.CrossEntropyLoss等
  • torch.optim:该模块提供自动求导更新参数等功能,用它来封装模型参数nn.parameter()后,loss求导后,可以用.step来更新整个参数。
  • torch.utils.data.DataSet:该模块提供加载数据初始化的方式,需要实现完善__getitem__()和__len__()的接口,便可以使用DataLoader多进程加载数据。
    参考:

Tensorflow 处理结构及基本应用

处理结构

Tensorflow的设计理念主要体现在两个方面

  1. 将图定义和图运算完全分开
  2. TensorFlow 中涉及的运算都要放在图中,而图的运行只发生在会话(session)中。开启会话后,就可以用数据去填充节点,进行运算;关闭会话后,就不能进行计算了。因此,会话提供了操作运行和 Tensor 求值的环境。
    Tensorflow 首先要定义神经网络的结构,然后再把数据放入结构种运算和training

字符识别深度学习算法_字符识别深度学习算法_02

基本应用

  • 使用图(graph)来计算任务
  • 在被称为会话(Session)的上下文(context)执行图
  • 使用tensor表示数据
  • 通过变量(Variable)维护状态
  • 使用feed和fetch可以为任意的操作(arbitrary operation)赋值或从其中获取数据
    参考:https://www.jianshu.com/p/2b06b5630852,TensorFlow入门教程

CNN

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

输入图片经过卷积后所得特征图大小的计算公式
输入图片大小:W x W
Filter大小:F x F
步长:S
padding的像素数:P
输出图片大小N x N ,计算公式:
N = (W-F + 2P)/S + 1
参考:

字符识别深度学习算法_卷积_03

CNN 是一种层次模型,输入的是原始的图像的像素数据。CNN通过卷积(converlution)、池化(pooling)、非线性激活函数(non-linear activation function)和全连接层(fully connected layer)构成。
如下所示,是非常经典的字符识别模型--LeNet网络结构。它包括两个卷积层,两个池化层,两个全连接层。卷积核都为5 x 5,stride= 1,池化层使用最大池化。

字符识别深度学习算法_数据_04

通过多次卷积和池化,CNN的最后一层将输入的图像像素映射为具体的输出。如在分类任务中会转换成不同类别的概率输出,然后计算真实标签与CNN模型的预测结果的差异,并通过反向传播更新每层的参数,并在更新完成后再次前向全波,如此反复直到训练完成。

比较典型的网络结构

LeNet--5(1998)

字符识别深度学习算法_2d_05

AlexNet(2012)

字符识别深度学习算法_字符识别深度学习算法_06

VGG-16(2014)

字符识别深度学习算法_卷积_07

Inception-v1(2014)

字符识别深度学习算法_2d_08

ResNet-50(2015)

字符识别深度学习算法_卷积_09

PyTorch构建CNN模型

在pytorch中构建CNN模型只需要定义好模型的参数和正向传播,pytorch会根据正向传播自动计算反向传播
本模型包括两层卷积层,最后并联6个全连接层用于分类。
导入模块

import os,sys,glob,shutil,json

import cv2
from PIL import Image
import numpy as np
from tqdm import tqdm,tqdm_notebook

import torch
import torchvision.datasets as datasets
from torchvision import transforms
import torchvision.models as models
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.nn = 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.nn(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()

对super()的理解:(参考:https://www.runoob.com/python/python-func-super.html)

super()函数用于调用父类(超类)的一个方法,它用于解决多重继承的问题。
python3.x和python2.x的一个区别就是:python3可以直接使用super().xxx 代替super(class,self).xxx
举例:

字符识别深度学习算法_字符识别深度学习算法_10

torch.nn.Sequential()(参考:https://pytorch-cn.readthedocs.io/zh/latest/package_references/torch-nn/#torchnn)

torch.nn.Sequential(*args):是一个时序容器,Models会以传入的顺序被添加到容器中。
举例:

字符识别深度学习算法_卷积_11

关于卷积后图像尺寸大小的计算,可参考前面的公式,以及举例链接:https://www.jianshu.com/p/45a26d278473

模型的训练

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

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

字符识别深度学习算法_2d_12

plot 画的的loss以及c0_plot不知道是训练次数少的缘故,似乎得到的结果有点不对劲

也可以使用pytorch提供的模型(在ImageNet数据集上的预训练模型)

class SVHN_Model2(nn.Module):
    def __init__(self):
        super(SVHN_Model2,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)
        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

对children()的理解

返回当前模型 子模块的迭代器

字符识别深度学习算法_字符识别深度学习算法_13