-NeoZng[neozng1@hnu.edu.cn]
5.2.2.卷积神经网络
在5.2.1中我们提到,可以把图像resize成一个1xn的特征向量当作输入投入一个网络当中进行训练和预测,但是这样做会出现很多的问题:首先是参数量过大,拿一张320x320的rgb图像举例,它是一个拥有320x320x3个维度的向量,若隐藏层也采用这样巨大的神经元规模,其参数量是不可想象的(利用你的排列组合知识计算一下)。另外,将图像resize成一维向量,在一定程度上丢失了两个维度之间的相关性(在映射的过程中有信息被压缩到原向量空间的零空间中了)因此,卷积神经网络就诞生了(LeNet5,1994)。但是由于算力的限制,直到到近年GPU的快速发展才得到广泛的应用。
卷积神经网络的设计思想是参数共享、空间信息的保存与合理的参数量下降。
如何用网络设计一个图片分类器?根据前面学习的知识,可以把图像看成一个width*height维的特征向量并把它输入到一个超大规模的神经网络中。在引言中提到这样做的参数量是随着分辨率的上升以阶乘级上涨的,我们显然无法处理这种规模的参数,并且在 5.2.1 也提到了过大的参数量容易让模型在训练过程中过拟合。
那还是看看造物主的杰作吧:计算机科学家从人类的视觉机制出发设计出了卷积神经网络。从认知心理学的角度来分析,人类的并不是一眼就能判断物体是什么,而是从其纹理、形状与轮廓这种低级特征开始鉴别一个物体,再上升到局部特征,最后通过组合局部特征得到对整体的判断。也就是说,我们没必要在“第一眼 ”就关注所有像素。
根据这个思想,我们改进了神经网络的运算方法、参数传递和激活方式:利用卷积运算替代全连接。即不必对每一层都进行全连接,全连接代表每个像素对之后的每一个神经元都有贡献,然而左上角的像素和右下角的像素可以说几乎没有任何相关性(其他大部分像素也是这样)。因此我们在刚开始只需要关注纹理和边缘,然后是纹理和边缘组成的局部特征,最后才是整体。这样,我们可以依据某种规则只让神经元之间进行部分连接,最后部分再进行全连接以充分利用高层次特征。
那要怎么样确定这种规则呢?接下来分别介绍CNN中最常见的三个构成模块:卷积层、池化层、全连接层。
-
卷积层
引子部分提到,如果把图像resize成一个一维向量,我们很可能就在此映射过程中把原来处于相邻像素之间的相关性给丢失了。想要保存这种相关性怎么办?这就要利用到“卷积”操作了。
一个3x3的图片,每个像素和周围的像素显然是有位置的相关关系的
把上图resize成1x9的向量,可以看到这里就丢失了原来的位置关系
学过信号与系统的同学应该已经对一维卷积熟悉,时域的卷积操作可以得到系统某个时刻由连续输入叠加产生地相应(一维情形),那对于图像信号该怎么做?显然图像在两个方向上延伸,图像信号就是坐标的函数(二元函数),我们直接把卷积的积分变量由时间改为坐标分量并进行离散化,那么二维卷积操作的结果就是系统在卷积核范围内的输出响应(即计算这个位置是否有相应的特征)。因为只需要采集局部的信息,我们不用把卷积核设置的太大(如果考虑最极端的情况,把卷积核半径设置得和图像一样大,那么就相当于采集了整幅图像的信息,和全连接无异了,只不过保留了位置信息)。来看几个动图:
二维卷积
如果没学过信号与系统或者还是不怎么理解卷积,可以参考知乎的这个问题:如何通俗易懂地解释卷积 。
若你学习过信号与系统,可以用我们所熟悉的一维卷积作为类比,二维情形一个大小有限的卷积核就相当于一个一维的只在某个区间有定义的、在其他地方的值为零的卷积函数(脉冲响应函数)。增大卷积核即增加感受野(在 5.2.3 中介绍),对比一维卷积函数来说就是扩大了使函数不为零的定义域定义域。
大小有限的卷积核与一维情形卷积函数的对比
对一维离散序列的卷积
图源知乎-palet
未学过信号与系统的同学可以忽略这个类比理解。那么卷积又是如何得到这些纹理等特征的?让我们看看提取图像中垂直边缘的例子就好了。以提取竖直边缘的特征为例,我们需要一个这样的卷积核:
用于提取垂直边缘的卷积核,当某处出现下图所示垂直边缘的时候,卷积操作会在对应位置得到较大的值
一张图片的像素表示,和他的显示效果(下图)
再回想一下卷积的含义,是某个系统在受到连续输入时响应的叠加,这里的“连续输入”指的是随着时间变化的输入信号。很显然图像信号是坐标的函数,那么在拍摄的那一刻所有信息就定格下来,这里的连续输入就变成了不同坐标的像素输入(x,y两个维度)。显然,在有垂直特征出现的时候,该区域的卷积结果就会很大,如果没有相应的特征出现,那么结果显然就会比较小。想要提取其他特征同理,这里给出提取水平边缘、角点的卷积核:
提取水平边缘的卷积核其实就是竖直边缘旋转90度,角点则是两个边缘的交点
因此,有怎么样的卷积核就会提取出怎么样的特征,不过到现在为止,我们还只是手动设计了物体的特征,比如上面提到的几个特征提取器(垂直、水平、角点),如何让机器自己学习特征?和标准的神经网络对比,这里的卷积核参数(即应该要学习到的特征,卷积核上每一个位置对应的参数值)其实也就是神经网络的权重参数w和b(隐藏层变成了卷积层)。这样,我们不必再自己设计特征(自己设计的特征不能涵盖所有情况并且特异性也不够好,学习得到的特征更能反映物体的本质),而是通过反向传播来让网络自己学习特征。
全连接神经网络和卷积神经网络参数(特征)的对比
现在,我们便有能替代全连接并且保持图像中像素的位置关系的同时还能提取特征的数学工具:卷积操作。通过网络的反向传播和梯度下降,我们便可以提取各种各样的特征。
此外我们还可以修改卷积的步长step(上面的例子中都是每隔一个像素都进行一次卷积计算),修改step就会改变每次卷积核移动的像素数,这样能够降低计算量、扩大感受野,对于大物体和高分辨率的图像常常使用3、5、7等大小的步长。
步长为2的卷积示例
对于图像的边缘,常使用“Padding”的技术来充分利用角落和边缘的像素(上面的例子中我们可以发现,每次卷积操作[step=1]得到的feature map都会比原图更小,那么我们可以通过在图像周围填充0或是对图像进行简单的复制,使得卷积核在运算时可以让中心保持对齐,这样在一些有特定需求的场景(比如对精度要求高、希望提升准确度)就可以生成和原图大小相同的feature map从而充分保留、突出图像的特征了。
padding示例
补零padding示例,复制padding则是通过一定的规则例如取边缘平均等方法填充外面一圈的值
是不是已经迫不及待想要训练一个CNN了呢?那么卷积神经网路的反向传播和普通神经网络有什么区别?我们在讲完池化层和全连接层后再来细说,先把前向传播的过程看完吧!
-
池化层
现在我们已经通过了一层卷积并且使用的是竖直边缘提取核,把有竖直特征的区域都保存了下来,那么这张特征图(feature map)里面数值大的点就代表着此区域很有可能有竖直的边缘存在。聪明的你大概已经想到我们可以通过设置阈值的方式来筛选这些特征,得到竖直边缘的强度大的区域(“够不够竖”)。不过这里有另一种方法,既能得到最“竖”的那一个特征,又能减少参数量——Pooling。
池化其实也是一种降采样的过程,早期对其的翻译也是只表意不表义非常生涩,笔者个人认为叫“汇集层”或“采集层”会更好理解一些,池化操作会提取feature map上最重要和显著的特征。常用的池化操作有最大池化和平均池化(平均池化其实用得非常少了),下图直观地展示了池化操作的过程:
最大池化操作会保留feature map上最显著的特征
平均池化则是更加中庸,对一个区域内的特征进行加权平均,一般加权系数相同
显然我们可以继续用卷积操作来提取feature map上的特征,但是池化不需要进行乘法和加法(最大池化不需要),在速度上远远超过了卷积。并且在用卷积提取了图像的特征之后,利用池化可以让被提取出的特征更有显著性、更凸出,以便之后的卷积操作会更有针对性,并且大大减少计算量。
使用池化相对于卷积来说还能提供额外的一些好处(当然是必须要配合卷积使用,只有卷积能提取特征,池化是让特征更明显,或者说是卷积+池化操作对比单纯卷积的好处):
-
抑制特征噪声,降低重复信息
当以两个邻近的像素为中心分别进行卷积操作的时候,得到的结果很可能非常接近,这样就带来了信息地冗余并且在之后的对此feature map进行的卷积操作(卷积核必然会同时覆盖到这两个像素)可能会产生不利的影响(过大或过小的结果)。利用最大池化或平均池化,就会把当前特征图上邻近的特征进行融合、取极值以降低冗余度。
-
提升模型的尺度不变性、旋转不变形、平移不变性
当一张图像发生轻微的旋转、平移和尺度缩放,池化操作对这些变化并不敏感,即使部分特征变换了位置或大小,也不会影响池化提取的结果或影响很小(从池化操作是如何进行的来理解)。
-
一定程度上防止过拟合
其实和第一个特点有相似之处,因为在进行池化操作的时候,随机噪声会被抑制,而局部样本不太明显的特征显然不如数据分布的相同特征那么显著,池化更加关注全局的特征而不是局部出现的特征,这在另一方面来说是对参数空间的降维,以防模型学习到一些不重要的特征维度上的信息。因此能够防止网络的过拟合。
所以,我们构建CNN的时候一般都是用1~3个卷积层+1个池化层作为一个基本的block(conv-pooling-block,CPB),然后用多个block进行串联得到网络的基本结构,最后加上全连接层完成整个网络的构建。
- 全连接层
在经过数个基本的block(conv+pooling)后,是不是感觉特征提取的差不多,网络已经知道这些特征对应的标签是什么了呢?这时候我们会选择把最后的feature map,resize成一个一维向量(最后的feature map上不同点的相关性其实已经很差,并且每一个点在此时都代表着某种局部特征,对于图像识别任务来说即使丢失位置信息了也不会对最终的结果产生太大的影)或是使用一个和特征图一样大的卷积核进行卷积或1x1卷积(也就是第二种方法,一些情况下也被称作全卷积Full Convolution Network),这样便会得到一个1xn的张量,本质上就是一个一维向量了(和resize得到的结果相同了)。一般在最后添加1~3个全连接的隐藏层并在输出时连接到一个神经元,对其使用softmax分类(二分类的推广),就可以得到一张图像的类别了。
以LeNet-5的结构为例,在数个CPB后增加了最后的全连接后,得到一个最简单的网络结构
刚刚说到全连接层会损失位置相关性,指的是多个FC串联会造成这种结果。但是一个有趣同时似乎也比较明显的结果的是:在CPB后直接跟上单层FC连接反而不会丢失像素间的位置关系并能够充分利用位置信息输出位置敏感的数据,有兴趣的读者可以思考一下两种连接方式的差异和造成这样结果的原因。
至此,一个完整的CNN就搭建好了,假设这是一个已经训练好的网络,这时候我们只需要投入一张图片,它就能够输出对应的分类了!可惜它还没训练好
下面让我们看一下CNN和标准的全连接神经网络的训练有什么差别。
-
CNN的反向传播
很多教程在这部分讲得非常模糊甚至根本没有提及这个过程,其实它的原理和普通神经网络是几乎一样的,只不过由于我们更应该去打破这种简单的黑箱子。
CNN在前向传播的时候,计算似乎比全连接网络要复杂,然而其本质都是线性组合+非线性化,即用上一层的输出向量通过分量乘以不同权重相加再投入一个激活函数(非线性过程)得到这一层的输出:
计算过程完全一致,只不过,有没有什么办法可以将CNN的前向传播也写成FC一样的矩阵运算
为了能够方便的计算出CNN的前向传播结果,充分利用矩阵运算加速库(如numpy等)并发挥GPU的威力,我们只需要将CNN的forward Propogation转化成矩阵形式:
只需要把上方的原图通过一点小trick转换成下方的矩阵,轻松的用矩阵相乘来得到输出了
图源深度之眼-Deepshare.net
这里我们只不过是把原来的一个正方形resize成一个一维向量,观察一下转换后feature map的下标很容易找到resize的规律。这里也回答了前面提出的“为什么单层FC能够保留位置信息”的问题。在此之后,我们也很容易写出反向传播的偏微分方程了。
当然,这里只是对一张单通道图片进行运算,那如果有多张图片,或者一个图片有多个通道该怎么做?小问题,把这些通道或图片全部resize成一个如上图所示的matrix,随后在行的方向上,把它们堆叠起来形成一个长长的矩阵(显然其列数还是保持不变,为卷积核的大小,故满足矩阵相乘的定义),再进行相同的运算即可。
卷积层已经搞定,再来想想池化操作,有了上面了例子是不是感觉也简单了不少?平均池化其实就相当于卷积核的参数固定为(1/卷积核大小)的卷积操作,这里不再展开说,直接上图看看最大池化吧:
用同样的方式根据卷积核的大小,把原feature map resize成x hat,再取每一行的最大值
图源深度之眼-Deepshare.net
池化层只会提取前层最大的特征,显然没有被提取到的特征点在反向传播中不会得到训练,分配到的误差为零,梯度为零:
至此,反向传播的基本介绍就到此结束。当然我们没有讲解使用batchnorm、layernorm的时候反向传播应该如何进行,不过原理都是完全一样的!最简单的办法就是先按照规律resize成一维向量,随后就和标准网络的FC层如出一辙!现在,你应该能够使用pytroch、caffe等网络框架搭建属于你自己的CNN图像识别网络了,快动手试试吧,马上复现一个LeNet-5问题应该不大。
如果你也对CV感兴趣或者也是一个RMer,觉得本文不错请给笔者点个赞或者帮忙转发给你的同学吧!后期将会把这个专栏的所有文章整合成pdf并建设一个wiki站或gitbook来维护、更新。