赞
踩
目录
我们将从目标分类网络的基石开始我们的深度学习的旅程。今天要讲的内容来自《Gradient-based learning applied to document recognition》,一篇1998年发表的论文。这篇论文统共有47页,内容非常多,其中最经典的内容莫过于LeNet的提出,以及其设计思路,这也是我们今天要介绍的。
首先我们先把论文中LeNet的结构摆出来。LeNet是作者设计的一个目标分类网络,用于手写数字数据集MNIST的分类。就是说我将一张图片(上面是手写的数字)输入这个网络,它会输出这张图片上的数字是多少。这就是一个分类任务。
图片来自论文
我们再回到这张图上,我们可以看到这张图上有这么几个要素:Convolutions(卷积)、Subsampling(下采样)、Full connection(全连接)、Gaussian connections(高斯连接)。我们先从全连接开始讲起。
首先全连接层就像下图这样,上一层的每个神经元都与下一层的每一个神经元有连接,所以这叫全连接。
那全连接层要如何计算呢?根据下面这张图我们可以看到,每一个神经元的计算都是由上一层每一个神经元的输出乘上一个权重然后全部想加,最后减去一个阈值。
比如在这个Y1神经元上的计算就是:
其中W是指两个神经元之间的连接权,b是这个神经元的阈值,Y2与Y3的计算也一样。这个W和b就是神经网络要学习的参数,这幅图上W有3*4=12个,而b有4个,总共16个参数要学习。如果你曾经看过机器学习西瓜书或者学习过神经网络的一些基础,肯定对这个不陌生。以前的神经网络就是通过堆砌这些层,来实现一定的效果。
作者在论文里也说过,单纯用全连接层组合而成的神经网络也能完成手写数字分类的任务,而且也能获得一定的效果。但是!这样子是会出现问题的。麦当劳的咖啡不知道大家有没有喝过,它那里有一种扁平的棕色长棒,大家都把它当成吸管来使用,但实际人家那是搅拌棒。但把它当成吸管其实也能用,只是吸得很费劲,不太合适。我们使用纯全连接的神经网络进行图像分类任务就像使用搅拌棒当吸管,不能说不行,只是不太合适。那为什么会不合适呢?论文中阐述了几个问题。
首先是图片的输入问题,我们要怎么把图片作为数据输进去纯全连接的网络呢?众所周知,图像其实是有若干像素组成的,每个像素都存储了颜色信息,放大了看就像一个个小格子涂了颜色。比较常见的方法是把图像每个像素的数值作为输入。
但其实这样做问题很大,一般的图片分辨率都不小,假设一张图片是640*640的分辨率,那它的输入神经元,也就是神经网络的第一层就要有409600个,这还只是灰度图(一个通道),如果它是彩色图片,那么它会有三个通道,那么输入神经元就要有640*640*3=1228800个。
这还不是最要命的,最要命的是我们的神经网络实际是在学习网络中的参数,又因为全连接层的神经元是上一层与下一层的神经元两两相连,每个连接都有一个权重参数,假设第二层有100个神经元,要学习的权重参数都有409600*100(单通道)个,这还没算阈值参数或者其他层呢,毕竟你要好的分类效果,这层数不可能只有这么两层吧。
反正如果想要用纯全连接的网络,首先要面临的就是参数多的问题。
参数多就会引发第二个问题:因为参数多,所以我们需要大量的数据集去学习这些参数。中学我们学了解方程,我们都知道,要解二元一次方程,我们要有两个等式,如果我们要解三元一次方程,我们要有三个等式。类似的道理,我们参数这么多,不得需要大量的数据集去迭代学习他们吗?
纵使给你准备好了许多数据集,但是你又会面临下一个问题,训练时间的问题。要知道,数据集越大,训练所需要的时间便长。即使你以多张图片为一批来进行训练,马上又会面对另外一个问题,显卡显存不够。我们训练网络,为了提高速度,会使用显卡进行训练,而要使用显卡,我们需要先把数据放到显存来存着,想要每批存的图像多,就要更大的显存。因此可以说硬件与资源限制了纯全连接层网络的规模,同时也变相限制性能。
第三个问题,也是最重要的问题:纯全连接层网络对于输入的平移或局部失真没有内置的不变性。这是论文原话的翻译,此话怎么理解呢?
我有个比较通俗的理解,就是像素点对不齐神经元。在神经网络的训练中,某些神经元不自觉地承担了检测某些特征的检测。
如下图是我们输入训练的图以及我们的网络,其中图上第一个神经元主要负责学习猪耳朵的特征,另外一个神经元负责学习猪尾巴的特征。
但如果我们的猪猪在图片上的位置发生了移动或者我将耳朵遮起来,再输入网络,负责检测猪耳朵的神经元就检测不到东西了,就有可能导致结果发生错误。
那纯全连接网络如何解决这个问题呢?
一个比较简单的思路就是有多个神经元都负责检测同一个特征。
由于他们检测的都是相同的特征,所以他们的权重大体是相同的,所以在不同位置放置相似的权重可以应对特征发生移位的问题。
原文是这样说的:“学习这样一项任务可能会导致在输入中的不同位置放置具有相似权重模式的多个单元,以便在输入中出现的任何地方检测不同特征。”
最后一个问题,由于图片在二维上是一个整体,如果我们像全连接层输入那样压平它,就会破坏它在二维上的特征。
为了解决这个问题,我们的论文作者使用了卷积神经网络。卷积神经网络就是有一个小框框,扫描了整张图,卷积的算法如下图:
图片来自网络
一个卷积核就是这么一个小框框,它扫描了整张图后,便会形成一个尺寸比原图更小的图,这个图我们称为特征图。
图片来自https://mlnotebook.github.io/post/CNN1/
这个卷积我们可以理解为从原图提取更高维的隐含特征。由于从始至终都是这个卷积核在扫,那么其实我们学习到的就是这个卷积核每一位的权重以及他们共同的偏置。比如一个3*3的卷积核,我们要学习到的参数就是3*3+1=10个参数,这不比刚才说的成千上万的参数要少巨多?
而且卷积解决了特征移位的问题。还是刚才的那个猪猪的例子,我使用一个检测猪尾巴的卷积核在图上扫,不管你挪到哪里去了,我都能找到。
我只需要知道猪尾巴和猪耳朵的相对位置就够了,也不需要知道他们的绝对位置,这就是卷积的好处。
论文原文是这么评价这种特性的:“卷积层的一个有趣特性是,如果输入图像移位,特征映射输出将移位相同的量,否则将保持不变。该特性是卷积网络对输入位移和失真鲁棒性的基础。”
当然这个特性不全是好事,文中有这么一段话:“这些特征的精确位置不仅与识别模式无关,而且可能有害,因为这些位置可能因角色的不同实例而不同。降低特征图中不同特征位置编码精度的简单方法是降低特征图的空间分辨率。这可以通过所谓的子采样层实现,该层执行局部平均和子采样,从而降低特征图的分辨率,并降低输出对偏移和失真的敏感性。”
这段话引出了下采样层的作用。使用下采样层是为了降低特征图的分辨率并降低输出对偏移和失真的敏感性,从而模糊特征的精确位置。
我对这段话的理解是,我们大概知道猪耳朵在猪尾巴上面就行了,我们不需要精确地知道猪耳朵在猪尾巴上面多少米,因为不同猪这个距离是不同的,如果我们规定得太详细就会导致过于敏感,会出现“猪耳朵一定要在猪尾巴上面0.6米,不然不是猪”的情况。
而我们的下采样通常是池化层,池化层和卷积层差不多,都是有个框在扫描图像。只不过它做的运算不同。比如这个动图展现的是最大池化层,就是在3*3的区域里找出最大的那个值作为结果。
图片来自https://mlnotebook.github.io/post/CNN1/
虽说卷积相比于全连接有这么多好处,但实际上卷积网络是在特征提取这部分有优于全连接的特性。
换句话说,卷积更适合用于提取特征。所以我们可以看到LeNet的前面使用卷积来做特征提取器,获取到的尺寸更小的特征图才给后面的全连接层进行分类。这种思想就是用搅拌棒搅拌,用吸管喝,而不是搅拌棒又搅拌又当吸管。这篇论文也确定了这种“前面卷积提取特征,后面全连接分类”的模式,后面几篇我们要讲的网络也是这个模式。
到此,我们总结一下每一个层的作用和特点,随后我们将进行实战部分:
下采样层:降低特征图的分辨率并降低输出对偏移和失真的敏感性。
全连接层:利用前面提取的特征进行分类。
下面将演示如何用pytorch搭建一个LeNet网络,顺便补充论文里的一些细节。
其实pytorch的官方demo里就搭建了一个LeNet,但是与论文里的不太一样,这里照搬一下。
- # 相关的引入
- import torch.nn as nn
- import torch.nn.functional as F
-
- # 继承至父类nn.Module
- class LeNet(nn.Module):
- def __init__(self):
- super(LeNet, self).__init__() # 调用父类方法
- # 定义LeNet的若干个层
- self.conv1 = nn.Conv2d(3, 16, 5) # 输入图片是三个通道的
- self.pool1 = nn.MaxPool2d(2, 2)
- self.conv2 = nn.Conv2d(16, 32, 5)
- self.pool2 = nn.MaxPool2d(2, 2)
- self.fc1 = nn.Linear(32*5*5, 120)
- self.fc2 = nn.Linear(120, 84)
- self.fc3 = nn.Linear(84, 10) # 最后输出分类结果为10种
-
- def forward(self, x):
- x = F.relu(self.conv1(x)) # input(3, 32, 32) output(16, 28, 28)
- x = self.pool1(x) # output(16, 14, 14)
- x = F.relu(self.conv2(x)) # output(32, 10, 10)
- x = self.pool2(x) # output(32, 5, 5)
- # 将特征图压扁成一行,用fatten也行
- x = x.view(-1, 32*5*5) # output(32*5*5)
- x = F.relu(self.fc1(x)) # output(120)
- x = F.relu(self.fc2(x)) # output(84)
- x = self.fc3(x) # output(10)
- return x
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
它与论文里不一样的地方在哪里呢,首先激活函数不一样。论文中使用的是tanh函数,而pytorch官方的例子用的是ReLU函数,两个函数在收敛速度方面有着很大区别,这个按下不表,我们将在下一篇AlexNet网络中讲解。而我们使用激活函数的原因是为网络引入非线性的变化。
x = F.relu(self.conv1(x)) # 用的ReLU
第二个不同的地方是输出层,原论文中使用的是高斯连接层,需要计算RBF。我们本篇文章主要是为了领悟卷积层与全连接层的使用逻辑以及学习前卷积后全连接的模式,同时简单地实现网络,所以我们就不介绍这个高斯连接层了,也如同pytorch的例子一样将其替换成全连接层,方便理解也方便训练。
self.fc3 = nn.Linear(84, 10) # 最后输出分类结果为10种
我们最后这一层的神经元是10个,是因为我们的分类结果有10种可能(数字0-9)。我们主要看这最后一层(下图蓝色区域),看这10个神经元哪个的数值最高,就代表哪一类的可能性最高。为了输出的数值更直观,我们还会把这10个数字输入softmax函数,输出的结果就是每个分类的概率是多少。
但为啥这个网络没有体现softmax呢,那是因为之后的训练中使用的损失函数包含了一个更高效的softmax,所以这里不需要使用。这部分看不懂没关系,后面我们会出一个pytorch训练模板,讲讲搭建完网络后如何进行训练以及训练的套路。
第三个不同的地方就是这个输入了,论文中使用的数据集是MNIST,是只有一个通道的灰度图,而pytorch采用的数据集是彩色图,有三个通道,所以它的输入是3。
self.conv1 = nn.Conv2d(3, 16, 5) # 输入图片是三个通道的
最后一个不同的地方是,pytorch里使用的下采样层是最大池化层,也就是一个框扫整个特征图,选择框框里最大那个值。但论文里是这样操作的:首先用一个2*2的框框,以2的步长扫描输入的特征图,把框框里的四个值相加,然后乘一个可训练的系数,再加上一个可训练的偏置。
self.pool1 = nn.MaxPool2d(2, 2) # 用的最大池化层
通过对比两个LeNet网络的差别,我们可以知道,纵使我对网络进行了改动,但基本的框架是没有变化的,同样有下采样,同样有激活函数,同样有10个输出,但我换成了最大池化、relu函数、全连接+softmax输出。这样操作后,我们最后依然能实现分类功能,但在最终的精度和训练的速度上有所不同。
就像凳子和沙发都是用来坐的,我无论使用哪个都有坐的功能,但用沙发会比凳子更舒服一点。
所以我们在实际应用这些网络时,不一定要照搬原网络,我们可以在不改变框架和基本原则的情况下,适当修改,来适配自己的任务。至于激活函数用哪些,池化层选择什么好,这些就是大家在研究的东西。我们通过看大量论文,可以学习到使用不同配置的效果。
作者:公|众|号【荣仙翁】
内容同步在其中,想看更多内容可以关注我哦。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。