Pytorch神器(6)

作者介绍:高扬,奇点大数据创始人。技术畅销书《白话大数据与机器学习》、《白话深度学习与Tensorflow》、《数据科学家养成手册》著书人。重庆工商大学研究生导师。真传X《深度学习实战60小时》金牌讲师。擅长用简单的语言把复杂的技术问题讲明白。

正文:

上回我们讲到使用一层的全连接网络来实现一个手写数字识别的功能。训练模型的大致步骤如下,我们复习一下:

(1)把样本从28*28处理成为1*784

(2)让1*784的样本通过一个500个节点的神经元(隐藏层)

(3)让上一层的输出结果通过一个10节点的神经元(输出层)

(4)定义损失函数为交叉熵损失函数

这样Pytorch就能够通过梯度下降得的方式逐步求出模型的神经元中的权重参数取得什么值的时候会有比较好的分类正确率了。

今天我们学习Pytorch的卷积网络的分类模型怎么定义。

请确认路径位置:

使用vim命令打开main.py

我们来解析一下这个文件的内容,30行之前的部分几乎可以不解释了,这在上一次的案例中已经做了解释:导入库,设置超参数,使用官方提供的API获取MNIST数据的训练集和测试集。

核心的部分在这里,请注意:

定义一个CNN类。所谓CNN类就是指卷积神经网络(Convolutional Neural Network),叫它卷积网络是因为在网络中有卷积核(ConvolutionKernel)这样一个功能组件。

卷积网络通常在工程中有比较好的表现效果,所以在近年来的网络模型中几乎都离不开卷积核。那么卷积核究竟是什么,有什么作用呢?在这里做一个插入的解释。

卷积核通常是一个矩形的方框,尺寸可以是1*1、2*2、3*3等,当然在一些特殊场景下也可以是非正方形的。

卷积核用来做的事情就是把前一层输入的数据做特征提取。所谓特征提取就是通过一个线性计算映射和一个激励函数(可选),让它在后一层成为一个数值的激励值结果。例如图上这样,如果输入了这样一个5*5的图形,我用一个3*3的卷积核对它进行y=wx+b的这样一个卷积(线性计算),其中

那么卷积核在图片上进行特征提取的时候,就像图上所示那样进行按位置的相乘关系,再相加,即:

补充说明:

第一、这不是矩阵乘法,而是两个向量在做点积,向量对应位置的值相乘再相加。

第二、这个例子中b=0

第三、在一次扫描后,卷积核会从左到右从上到下扫描整张图片,并在每一次计算中产生对应的结果值放到后面的Feature Map中去(图中所示的Convolved Feature),在这个例子中,应该会产生一个3*3的Feature Map。

第四、根据需要,如果一个Feature Map需要跟前面输入的图片尺寸保持一致的情况下,通常需要在边界上补,这种操作叫做Padding。如图所示,一个32*32的图片,Padding操作处理成了36*36的大小,而周围就是在边界上补了2个。这种补的目的一个是可以保证卷积操作后得到的Feature Map和输入的图片尺寸一样大,也可以保证边界的数据可以被扫描的次数增加。

第五、在第三项所提到的扫描中,是可以做“跳跃”的,也就是说可以一次移动1个像素再扫描,也可以移动2个或更多。移动1个像素,就叫做Striding=1,这是扫描最为密集的方式;Striding可以为2或者更多,越多则跳过的信息越多,扫描速会变快,但是会牺牲扫描提取的信息量(Feature Map的尺寸也会根据Striding的量收到影响,Striding越大,Feature Map尺寸越小)。

在Pytorch中,我们如果需要用卷积核对一个图片进行扫描,通常我们会定义卷积核的大小和数量。大小就是指卷积核的尺寸;数量指的是用多少个这样的卷积核来对图片进行扫描,卷积核的数量也叫做通道数。训练的时候就是在让Pytorch框架帮我们用梯度下降的方式找出各个卷积核的w各自取什么值能够满足让模型由足够好的表现。一般来说卷积网络中所需要的参数的数量比全连接网络要少得多,训练起来速度也会更快,泛化能力也会更好。

我们再来看一下模型的定义。

36~40行,定义的内容是第一个卷积层。

37行:输入为1通道,输出为16通道(即输入了1张1通道的图片,有16个卷积核来扫描它),卷积核大小为5*5,Padding=2就是四面各补2个。

38行:对37行的结果进行批归一化(BatchNormalization),目的是为了防止过拟合。参数通常是给输出通道数就OK了。如果大家要想深入细致了解归一化的作用,可以查阅网上资料。简单说你这么理解就可以了,输入的数据维度那么多,其实你比较难保证输入这些数据都是多大的值,有的可能比较大,有的可能比较小。有的量纲可能是以几百几千计数的,有的可能是以小数零点几来计数的。这样的带来的缺点就是那些较大的数字比较容易引起网络的“注意”,会容易让网络产生过拟合现象,那么就需要把所有的输入的数值都想办法投射到一个小的统一的区间上去,比如到1之间。或者你理解这样一个例子,一个学校里的孩子做成绩排名,但是考试学科的分值不一样的,语文100分是满分,而数学、英语都是5分满分。这样你在直接进行3门功课加和排名的时候会明显发现几乎就是在对语文一门进行排名,因为量纲的问题导致最终排名对这个百分制的学科比较敏感。因此,在这种情况下,我们应该把语文,数学,英语都归一化到到100的区间去,让他们都用百分制进行评分,就会很大程度上消除这种问题。

39行:把38行输出的结果通过ReLU激励函数。

40行:把39行输出的结果做一个降采样(DownSampling),这里用的是Max Pooling。

所谓Max Pooling就是这样一个操作:

输入一个图片或者Feature Map,在相邻的n*n的方框里,挑选最大的一个做输出结果。如果所示,一个4*4的图片在过了2*2的MapPooling层之后就变成后面的2*2的图片了。

补充说明:

(1)这种降采样不仅可以取最大值,用Map Pooling,也可以取平均值,用Average Pooling。其目的都是通过用某一个值来代表前一层若干个邻近值的目的,相当于进一步进行特征提取和降维。

(2)Max Pooling或者AveragePooling的尺寸可以也可以是2*2,3*3,4*4等尺寸,根据业务需要来确定。

在我们这个例子中,用的是一个2*2的Map Pooling。

第二层:

16通道的输入,32通道的输出,5*5的卷积核,Padding=2。

仍然是做了批归一化,ReLU和2*2的MaxPooling。

所谓16通道的输入和32通道的输出,你就可以理解为前面卷积得到的16个再用32个卷积核去做卷积,每个前面的Feature Map被2个卷积核再做了卷积,得到32个不同的Feature Map就可以了。

最后一层:

一个线性全连接层,输入为7*7*32,输出为10个节点,其实就是将输出[1,7*7*32]尺寸的向量作为x,和一个[7*7*32,1]的w相乘,再+b,得到一个节点的输出,这样的结构有10个。

这个7*7*32怎么来的呢?我们想想看,一个MNIST图片大小是28*28,如果直接用5*5的卷积核扫描的话,仍然会得到28*28大小的Feature Map,再过2*2的Max Pooling,那就会得到14*14大小的Feature Map,得到16个。用5*5卷积和Padding=2处理14*14,仍然会得到14*14大小的Feature Map,过2*2的Max Pooling,得到7*7大小,而因为是32个卷积核,所以尺寸大小就是7*7*32,即1568个特征值。

整个网络的形式画出来就是这个样子:

损失函数就是交叉熵损失函数,这个和FC的网络没有区别:

而后面从61行到93行的部分就是训练,测试验证,模型保存的部分,由于在前面的实验中都有涉猎,所以我们就不再赘述了,大家读一下很快就能明白的。

最后执行一下该文件:

python main.py

可以在测试集上有98%的准确率,还是非常不错的。

只不过5*5尺寸的卷积核,计算量还是有点,大速度有点慢,大家可以考虑在实验中改成3*3的或者更小的卷积核试试看效果如何。

一个正确率可以达到98%的手写识别机器人只需要不到100行程序就能搞定,而且可读性极好,你还不赶紧上手试试看吗?

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180426G07I4T00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券