学号:17020110019 姓名:高少魁
【嵌牛导读】在深度学习领域,手写数字识别是一个较为基础的案例,笔者通过深度学习框架pytorch,使用卷积神经网络设计了一个基本的网络结构,使用MINST数据集作为实验数据集,训练了40个轮次,识别率(在训练集上的)达到了99.95%,最终识别率(在测试集上的)达到了99.13%,取得了比较良好的识别效果。
【嵌牛鼻子】深度学习 卷积神经网络 手写数字识别 pytorch
【嵌牛正文】
一、测试环境
笔者使用的操作系统为windows7,开发语言选择python,版本为3.7.3,使用开发工具IDE为pycharm。根据神经网路的要求,使用了深度学习框架pytorch。使用的相关工具与依赖库有pytorch(torch)、torchvision、matplotlib、time等。
考虑到笔者的设备性能对卷积神经网络的限制,本人在pycharm上进行网络设计与初步实现,对于更精确的、需要更多训练轮次的程序,使用了谷歌的深度学习工具colab,该平台可以以优异的性能运行深度学习代码,平台还提供了Tesla K80作为GPU加速,可以取得理想的实验结果。
二、基本原理
卷积神经网络(Convolutional Neural Networks, CNN)是一种包含卷积计算的,具有深度结构的前馈神经网络,是深度学习中具有代表性的一种神经网络,卷积神经网络可以对格点化特征(如图片的像素点)进行学习,具有稳定的效果,因此在计算机视觉领域应用十分广泛。卷积神经网络主要包括输入层、隐含层和输出层,如下:
输入层:CNN的输入层可以处理多维数据,在计算机视觉领域通常输入三维数组(即图像上的RGB像素值与二维像素点位置)。一般来说,其输入数据需要进行预处理,如标准化,将分布在[0,255]上的像素灰度值转化为[0,1]上的值,这样做有利于提高卷积神经网络的效率。
隐含层:隐含层通常主要包含卷积层、池化层和全连接层三种结构,其中
(1)卷积层(convolutional layer)可以对输入数据进行特征提取。它包含多个卷积核,卷积核的大小、步长、填充方式都决定了输出特征图像的尺寸,它在图像上进行扫描,可以提取不同的特征。对图像进行不断地卷积,可以得到边缘、线条甚至更加复杂的特征。
(2)线性整流层(Rectified Linear Units layer, ReLU layer),该层主要使用ReLU函数或其变形来作为激活函数,其中,ReLU函数为:f(x)=max(0,x)。使用ReLU函数主要是为了使神经网络非线性化。
(3)池化层(Pooling layer),该层主要是为了解决卷积层之后数据特征维度过大的问题,特征图送到池化层以进行信息过滤。池化层主要是将图中单个点或多点运算的一个结果替换为其邻域部分,池化层也有池化大小、步长、填充等参数。常见的池化有均匀池化(在池化区域内取均值)、极大池化(在区域内取极大值)、随机池化、混合池化等。经过池化之后,特征维度减小。
(4)全连接层(Fully-Connected layer),经过卷积层和池化层对输入数据进行的特征提取之后,全连接层主要是对特征进行非线性组合得到输出,即利用已有的高阶特征完成学习目标。
输出层:对于手写数字图像分类问题,输出层使用逻辑函数或归一化指数函数softmax输出分类标签。
三、数据集
笔者使用了MNIST数据集作为实验数据集。MNIST数据集是一个手写数字数据集,如下图。该数据集由四部分组成,分别是训练图片集、训练标签集、测试图片集和测试标签集,其中,训练集包含了60000张图片,测试集有10000张图片,数据集足够大,可以提高训练性能。
在MNIST数据集中的数字具有不同的形态,并且还有少部分残缺数据,这样有利于提高模型的鲁棒性与泛化能力。
四、神经网络设计
1、网络结构的设计如下
根据上图神经网络的结构可知,数据输入时,首先经过一个卷积核大小为3×3的卷积层,由于MNIST数据集是灰度图像,因此输入通道为1,输出通道设定为8,步长为1;之后经过一个池化层,大小为2×2,步长为2,使用的池化策略为极大池化;在激活函数上,使用了ReLU作为激活函数,以使网络非线性化;之后再通过一个输入8通道,输出16通道,卷积核为3×3,步长为1的卷积层以及一个与之前一模一样的池化层;该网络运算结束后通过一个ReLU激活函数并将其转化(reshape)为大小15×25的张量;接下来是一个全连接层,输入的张量大小是16×25,输出为1024,在经过一个ReLU激活函数后,使用Dropout网络来抑制过拟合,Dropout策略为0.2,即该层神经元在每次迭代训练后有20%的可能性被丢弃,不参与训练,最后是一个全连接层,由于手写数字有10类(0~9),因此输入张量大小为1024,输出大小为10,在网络的最后,由于该项目属于分类任务,使用softmax激活函数来将多个神经元的输出映射到[0,1]区间内,并且预测结果的概率累积和为1。
2、one-hot编码设计
基于卷积神经网络的手写数字识别,本质上是分类问题,而在神经网络中有大量的加权平均运算,如果使用图片标签编码的类别值,如数字“1”、“2”,那么模型的预测会有大量的误差,因此要使用one-hot编码对类别进行“二进制”操作。例如数字“3”,它的one-hot编码为“0001000000”。
3、数据预处理与加载
对于数据的预处理,笔者使用了torchvision中的transforms,它含有很多对数据与处理的函数,本人使用的是transforms.Normalize(mean=[0.5], std=[0.5]),即将[0,1]映射到[-1,1]中,由于MNIST中的数据为图片,因此使用了transforms.ToTensor()将其转化为张量。
对于数据集的定义,首先使用了torchvision中的datasets.MNIST()来进行MNIST数据集的加载,笔者定义了两组数据,即训练集和测试集,分别是data_train与data_test。之后使用torch.utils.data中的DataLoader来加载数据。
4、训练神经网络与测试
在神经网络定义结束后,就可以声明一个网络对象,将之前加载的数据标签进行one-hot编码,将数据传入网络进行计算。使用torch.argmax()可以得到数字标签的索引(由于使用one-hot编码,因此返回的结果非1即0,最大的数的索引即为数字标签),将通过网络训练的数字标签与真实的数字标签做对比,计算出正确个数,以此计算精确度。在优化器和损失函数方面,使用了optim.zero_grad()进行梯度归零,使用了二分类交叉熵损失函数(nn.BCELoss()),经过一次反向传播后使用optim.step()更新网络参数。笔者设计了40个训练轮次,每个轮次一次加载200个数据,MNIST数据集共有60000个,因此一个训练轮次要加载数据60000/200=300次,一共有12000次循环。在训练结束后,将测试集通过神经网络,进行手写数字的预测,通过预测标签与真实标签的对比,即可统计正确预测数量,以计算识别准确率。
为了使运行者可以看到实际识别的图片,笔者设计了另一个版本的程序,与之前程序不同的是,该程序在测试集测试部分,只加载了16个数据,使用matplotlib.pyplot中的方法将加载的16个数字图片与识别的标签输出,使运行者可以清楚的看到识别的效果。具体可见训练结果。
五、训练结果
结果分为两个部分,一个是40个轮次的训练,结果输出测试集识别手写数字的准确度,另一个部分只训练10个轮次,但着重于输出使用网络识别的16张手写数字图片。
1、识别准确度结果
在google的colab平台上进行测试,选择训练轮次为40轮,运行结果如下:
分析结果可知,在训练了40轮之后,训练集的识别精确度提高到了99.94%左右,损失函数也在不断的降低,可见该模型对于手写数字识别的准确率很高;在测试集上进行测试,得到的精确度为99.13%,已经可以满足日常生产生活需求;另外,程序总运行时间约为938.9秒,主要耗费在训练模型上,而在测试集上运行的时间仅仅只有2.467秒,由于测试集一共有10000张手写数字图片,平均识别一张图片仅需约0.25ms,识别速度非常快。
通过分析后两张图,可以直观的看到训练过程中一些指标的变化,识别精确度在前面的训练轮次增长速度非常快,而在后续的训练轮次,增长速度开始变得缓慢,并且最后基本稳定在一个精确度上;而对于损失函数,它的下降趋势和精确度的增长趋势基本上是互补的,也是不断地下降,最后稳定在0.01附近,但与识别精确度不同的是,损失函数的下降过程中有明显的波动。
2、手写数字识别结果实例
在pycharm上运行另一个程序,选择训练轮次为10轮,运行结果如下图:
根据第一张图的运行结果可以看出,精确度不断提高,在训练了10轮之后,精确度达到了99.52%,并稳定在99.52%左右,已经可以满足正常的使用。从第二张图可以看出,16张手写数字图片全部被识别出,一些形态各异的数字也能很好的识别,可见该神经网络可以实际应用到生产生活中去。