前言:卷积神经网络是深度学习算法中一个重要组成部分,在深度学习图像识别技术的应用中起到了关键作用。卷积神经网络和循环神经网络(RNN)都是类似于传统的全连接神经网络(也叫深度神经网络,简称DNN),CNN属于编码了空间相关性的DNN,RNN属于编码了时间相关性的DNN。由于图像任务的不同,CNN的网络层也会有些许变动,但是基本上都会使用到卷积层、池化层以及非线性层。为了加深这方面理论知识的理解,本文将从多方面深入讲解CNN中的卷积操作、池化操作以及激活函数。
目录
1、卷积层
1.1 卷积计算
1.2 卷积层的特点
1.3 常用的卷积操作
2、池化层
2.1 池化的作用
2.2 常用的池化操作
3、非线性层
3.1 激活函数的作用
3.2 常用的激活函数
卷积层的作用是提取输入图片中的信息,这些信息被称为图像特征,这些特征是由图像中的每个像素通过组合或者独立的方式所体现,比如图片的纹理特征,颜色特征。
在讲解具体的卷积计算之前,我们先通过几张动图直观地感受一下不同维度的卷积操作。
一维卷积操作如下图所示:
二维卷积操作如下图所示:
三位卷积操作如下图所示:
卷积核做的是线性运算,核上的每个值与它滑动到的对应位置上的值相乘,然后把这些值相加。以二维卷积为例讲解Conv2d如何进行卷积计算,在讲解卷积计算之前,我们需要明白几点重要的概念,这对理解卷积运算有很大的帮助:
①二维卷积中的二维并不是指卷积核是二维的,它与卷积核的维度无关,而是指卷积核只在两个维度上滑动。同理,一维卷积和三维卷积分别指卷积核在一个维度或者三个维度上滑动,正如上面3个图所示;
②卷积核通道数(或者叫卷积核个数,一组卷积核中有通道数个卷积核)=输入层通道数;
③输出层通道数(即特征图通道数或特征图个数)=卷积核组数,也就是说1组卷积核对输入进行卷积计算后只能得到1个特征图,特征图有n个通道则说明需要用n组卷积核对输入进行卷积计算。
理解了上面几点概念后,现在我们来通过一个实例来具体感受一下二维卷积是如何计算的。
如上图所示,假设输入是RGB3个通道的二维图像,那么一组卷积核中包含3个二维卷积核,即卷积核也必须是3个通道的。这3个卷积核分别在输入图像的3个通道上滑动,比如R通道上,每滑动一次,对应元素相乘再相加就可以得到一个数,3个卷积核滑动一次就会得到3个数,将这3个数相加并且加上一个偏置即可得到特征图上的一个值,该组卷积核在输入图像上全部滑动结束,那么就可以得到一个完整的特征图,这个特征图代表从输入图像中提取出来的一种特征。而一般在输入图像上只提取一种特征是完全不够的,往往需要在输入图像上获取更多的特征信息,即要获得多个特征图,那么就需要多组卷积核在图像上进行卷积计算。下图中使用了两组卷积核计算得到了2个特征图。
二维卷积操作在Pytorch中通过下面函数实现:
#二维卷积
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
#参数介绍:
#in_channels:输入的通道数
#out_channels:输出的通道数
#kernel_size:卷积核的大小
#stride:卷积核滑动的步长,默认是1
#padding:怎么填充输入图像,此参数的类型可以是int , tuple或str , optional 。默认padding=0,即不填充。
#dilation:设置膨胀率,即核内元素间距,默认是1。即如果kernel_size=3,dilation=1,那么卷积核大小就是3×3;如果kernel_size=3,dilation=2,那么卷积核大小为5×5
#groups:通过设置这个参数来决定分几组进行卷积,默认是1,即默认是普通卷积,此时卷积核通道数=输入通道数
#bias:是否添加偏差,默认true
#padding_mode:填充时,此参数决定用什么值来填充,默认是'zeros',即用0填充,可选参数有'zeros', 'reflect', 'replicate'或'circular'
假设输入的尺寸是,卷积后输出的尺寸是,那么:
①权值共享
用同一组参数去遍历整张图像,用于提取整张图像中具有某种共性的特征信息,比如纹理特征等,不同卷积核用于提取图像在不同方面具有共性的特征信息,即卷积操作后得到的一个特征图代表提取的一种图像特征。权值共享是深度学习的一个重要思想,它在减少网络参数的同时依然可以保持很好的网络容量。卷积神经网络在空间上权值共享,而循环神经网络在时间上权值共享。
②局部连接
卷积层是由全连接层演变而来的,在全连接层中,每个输出通过权值与所有输入相连。而在视觉识别任务中,关键性的图像特征、边缘、角点等只占据了整张图像的一小部分,图像中相距较远的的两个像素有相互影响的可能性较小。因此,在卷积层中,每个输出神经元在通道上保持全连接,而在空间上只和邻域的一小部分输入神经元相连。
1)分组卷积
分组卷积和普通卷积的区别在于普通卷积在通道上保持全连接,而在空间上只和邻域的一小部分输入神经元局部连接,而分组卷积在通道和空间上都是局部连接,那么不难发现分组卷积可以在普通卷积的基础上进一步减少参数,但是效果可能会比普通卷积效果差点。如下图所示,可以直观地观察到分组卷积和普通卷积的区别。
举个例子,假设输入的shape为(1,12,24,24),卷积核的kernal_size=3,要求输出的通道数为64,如果是普通卷积,那么权值参数的数量为3×3×12×64=6912;而如果采用分组卷积的话,假定分4组,那么权值参数的数量为3×3×3×16×4=1728,分组卷积的参数量降低到普通卷积参数量的四分之一。分组卷积在代码实现上也很简单,只需将torch.nn.Conv2d()函数中的groups参数的值修改一下即可,分几组就修改成几。
2)深度可分离卷积
深度可以分离卷积是讲卷积过程分为Depthwise Convolution与Pointwise Convolution进行。Depthwise Convolution其实就是groups=输入通道数的分组卷积,这样做就完全孤立了像素点在通道上的相互影响,没有有效的利用不同通道在相同空间位置上的feature信息,因此需要Pointwise Convolution来将这些特征图在通道上进行线性组合生成新的特征图。Depthwise Convolution和Pointwise Convolution操作过程分别如下图所示。
Depthwise Convolution
Pointwise Convolution
需要注意的一点是,Depthwise Convolution完成后的特征图通道数与输入层的通道数相同,也就是说Depthwise Convolution不改变通道数,而Pointwise Convolution不仅可以使每个像素点在不同channels上进行线性组合,还可以改变通道数。
简单来说,深度可分离卷积在代码实现上可以当成分组数等于输入通道数的分组卷积与1×1卷积的组合,一般来说深度可分离卷积的效果要比分组卷积的效果好,和普通卷积的效果差不多,因为深度可分离卷积和普通卷积一样在通道上实现全连接,在空间上局部连接,这更符合图像像素相互作用的特点,但是使用深度可分离卷积,其参数量会比普通卷积减少很多。举个例子,假设输入的shape为(1,12,24,24),卷积核的kernal_size=3,要求输出的通道数为64,如果是普通卷积,那么权值参数的数量为3×3×12×64=6912;如果使用深度可分离卷积,那么权值参数的数量为3×3×1×1×12+1×1×12×64=876。
池化层的引入是仿照人的视觉系统对视觉输入对象进行降维和抽象,它主要有以下几种作用:
①特征不变性:池化操作使模型更加关注图像是否存在某些特征而不管这些特征以什么形式出现,比如特征的位置,大小等。其中特征不变性主要包括平移不变性和尺度不变性等。平移不变性是指输出结果对输入的平移基本保持不变,例如,假设输入为(4,1,3,7,2),最大池化将会取7,如果将输入左移一位得到(1,3,7,2,0),输出结果仍将为7;对于尺度不变性,池化操作就相当于图像的resize,平时一张狗的图像被缩小了一倍我们还能认出这是一张狗的照片,这说明这张图像中仍保留着狗最重要的特征,我们一看就能判断图像中画的是一只狗,图像压缩时去掉的信息只是一些无关紧要的信息,而留下的信息则是具有尺度不变性的特征,是最能表达图像的特征。
②特征降维(下采样):我们知道一幅图像含有的信息是很大的,特征也很多,但是有些信息对于我们做图像任务时没有太多用途或者有重复,我们可以把这类冗余信息去除,把最重要的特征抽取出来,这也是池化操作的一大作用。
③池化层会不断地减小数据的空间大小,因此参数的数量和计算量也会下降,这在一定程度上也控制了过拟合。
④实现非线性(类似relu)。
⑤扩大了感受野。
1)最大池化、平均池化、全局最大池化(GMP)、全局平均池化(GAP)
最大池化:选取图像中池化核区域的最大值作为该区域池化后的值。
平均池化:计算图像中池化核区域的平均值作为该区域池化后的值。
那么根据什么来决定到底是用最大池化还是平均池化呢?在回答这个问题之前,我们需要明白网络在对图像进行特征提取时产生的误差主要来自于两方面:①邻域大小受限意味着此部分区域中的数据信息不够全面,从而造成估计值方差增大;②卷积层参数的误差造成估计值偏差增大(正则化那篇文章详细讲了误差可以表示为偏差,方差,噪声之和,即误差=偏差+方差+噪声)。一般来说,平均池化能减小第一种误差,更多的保留图像的背景信息;而最大池化能减小第二种误差,更多的保留图像的纹理信息。如果还不能理解怎么选择最大池化还是平均池化,那换一种解释,就是当特征图中的信息都应该对模型预测结果有所贡献的时候用平均池化,比如图像分割中常用全局平均池化来获取全局上下文关系,再例如图像分类任务最后往往是对特征图进行平均池化而不是最大池化,是因为网络深层的高级语义信息一般来说都能帮助分类器分类;另外为了减少无用信息的影响时使用最大池化,比如网络浅层常常见到最大池化,因为浅层对图像而言包含了较多无用信息。总结一句话就是网络浅层一般使用最大池化,深层多使用平均池化。
代码实现:
#1、最大池化
torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
#参数解释
#kernel_size:池化核的大小
#stride:池化核滑动的步长,默认大小是kernel_size
#padding:在输入图像的两边进行填充,默认是0,即不填充,另外填充值默认是0
#dilation:设置核的膨胀率,默认 dilation=1,如果kernel_size =3,那么核的大小就是3×3。如果 dilation = 2,kernel_size =3×3,那么每列数据与每列数据,每行数据与每行数据中间都再加一行或列数据,数据都用0填充,那么核的大小就变成5×5。
#return_indices:这个参数用来控制要不要返回最大值的索引位置,如果为true那么要记住最大池化后最大值的所在索引位置,后面上采样可能要用上,为false则不用记住位置。
#ceil_mode:它决定的是在计算输出结果形状的时候,是使用向上取整还是向下取整。
#2、平均池化
torch.nn.AvgPool2d(kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True, divisor_override=None)
#参数讲解,与最大池化相同的参数代表的意思一样
#count_include_pad:为True时表示平均计算时零填充也包含在内
#divisor_override:如果指定,它将用作除数,否则将使用池化区域的大小
#3、全局最大池化
torch.nn.AdaptiveMaxPool2d(output_size, return_indices=False)
#这个函数用来自适应最大池化
#output_size:决定将输入图像分几个区域池化,如果output_size=1,就代表全局最大池化
#全局平均池化
torch.nn.AdaptiveAvgPool2d(output_size)
#这个函数代表自适应平均池化
#output_size:决定输入图像分几个区域进行平均池化,output_size=1,就代表全局平均池化
2)重叠池化
就是相邻池化窗口之间有重叠区域,此时一般kernel_size > stride
3)空间金字塔池化(SPP)
它将一个pooling变成了多个scale的pooling。用不同大小池化窗口作用于上层的特征图。如下图所示,对输入特征图进行了3次不同窗口大小的池化操作,然后送入全连接层。
这样的结构设计可以保证即使带有全连接层的卷积网络也可以处理不同尺寸的图像。为什么全连接层不能处理不同尺寸的图像呢?这是因为全连接层两边的神经元个数必须是固定的,而不同尺寸的输入图像必定神经元的个数是不同的。其代码使用上面提到的自适应最大池化或自适应平均池化即可实现。由SPP发展出了很多类似结构,比如ASPP、ROI Pooling等,这里就不一一列举了。
激活函数的引入可以为神经网络带来非线性能力,这一点是非常重要的,因为世界上的数据大部分都是非线性的,而线性网络是无法学习和模拟图像、音频等非线性数据的。另外,神经网络中没有引入非线性层,那么神经网络就变成了线性层的简单堆叠, 而多层线性网络的简单堆叠本质上可以用一个线性函数来表示,这就使得神经网络的深度失去了其本来的意义。最后,激活函数还可以把数据从非线性空间映射到线性空间,让数据可以更好地被分类。
1)sigmoid激活函数
sigmoid函数将值的范围压缩到(0,1),刚好符合概率分布的特点,可以在用于概率预测的输出层中使用。其优点是连续且处处可导,缺点是函数值接近0和1时函数梯度较小容易造成梯度消失现象,而且该函数输出值恒为正,不是以0为中心,这会导致权值只往一个方向更新,从而影响收敛速度。
2)Tanh激活函数
tanhx激活函数又叫双曲正切激活函数,该函数将值的范围压缩到(-1,1),其优点是输出值以0为中心,解决了sigmoid函数中权值只能朝一个方向更新的问题,其缺点是也会造成梯度消失并且计算量巨大。
3)ReLU激活函数
ReLU激活函数又叫修正线性单元或线性整流函数,是神经网络中非常常用的一种激活函数。其优点是ReLU具有稀疏性,计算量小,收敛速度快且在区域,不会出现梯度消失,缺点就是输出不以0为中心并且的这部分神经元的权值永远不会得到更新。
4)Leaky ReLU激活函数
该激活函数很好地解决了ReLU激活函数中的这部分神经元的权值永远不会得到更新
的问题。