卷积网络(convnet)
卷积网络(convolutional neural network,CNN),卷积神经网络是一种专门用来处理具有类似网络结构的数据的神经网络,卷积网络是指那些至少在网络的一层中使用卷积运算来替代一般的矩阵乘法运算的神经网络。例如时间序列数据(时间轴上有规律地采样形成的一维网格)、图像数据(二维像素网格)。
CNN新出现了卷积层(Convolution层)和池化层(Pooling层)
CNN层的连接顺序“Convolution - ReLU -(Pooling)”
一、卷积运算
1.卷积的基本知识
卷积是一种特殊的线性运算。
卷积网络的术语,卷积第一个参数x通常称为输入(input)(有时输出也被称为特征映射 feature map),第二个参数w称为核函数(kernel function)。
注:在机器学习的应用中,输入通常是多维数组的数据,而核通常是由学习算法优化得到的多维数组的参数,我们把这些多维数组叫做张量。
[1]滤波器
这个滤波器大小为(3,3),输出大小(2,2),有的时候也会用“核”来代替这里的滤波器。
[2]偏置
[3]填充
有的时候因为输出的时候要求不同,需要对输入的数据进行填充(扩充矩阵),在下图进行了幅度为1的填充(用幅度为1像素的0填充周围)
因为要输出4*4的矩阵,所以要进行填充。这里填充为1,输入数据大小变为了(6,6);如果填充设为2,则输入数据大小为(8,8);如果填充设为3,则输入数据大小为(10,10)。
使用填充主要是为了调整输出的大小。比如,对大小为(4, 4)的输入数据应用(3,3)的滤波器时,输出大小变为(2, 2),相当于输出大小比输入大小缩小了2个元素。这在反复进行多次卷积运算的深度网 络中会成为问题。为什么呢?因为如果每次进行卷积运算都会缩小空间,那么在某个时刻输出大小就有可能变为1,导致无法再应用 卷积运算。为了避免出现这样的情况,就要使用填充。在刚才的例子中,将填充的幅度设为1,那么相对于输入大小(4, 4),输出大小也保持为原来的(4, 4)。因此,卷积运算就可以在保持空间大小不变 的情况下将数据传给下一层。
[4]步幅
应用滤波器的位置间隔称为步幅(stride)。
补充说明:有的时候因为步幅、输入输出等的不同导致值无法除尽。当输出大小无法除尽时(结果是小数时),需要采取报错等对策。顺便说一下,根据深度学习的框架的不同,当值无法除尽时,有时会向最接近的整数四舍五入,不进行报错而继续运行。
[5]批处理
神经网络的处理中进行了将输入数据打包的批处理。之前的全连接神经网络的实现也对应了批处理,通过批处理,能够实现处理的高效化和学习时对mini-batch的对应。
我们希望卷积运算也同样对应批处理。为此,需要将在各层间传递的数据保存为4维数据。具体地讲,就是按(batch_num,channel,height,width) 的顺序保存数据。比如,将图7-12中的处理改成对N个数据进行批处理时,数据的形状如图7-13所示。
图7-13的批处理版的数据流中,在各个数据的开头添加了批用的维度。像这样,数据作为4维的形状在各层间传递。这里需要注意的是,网络间传递的是4维数据,对这N个数据进行了卷积运算。也就是说,批处理将N次的处理汇总成了1次进行。
在机器学习的应用中,输入通常是多维数组的数据,而核通常是由学习算法优化得到的多维数组的参数。我们把这些多维数组叫做张量。因为在输入与核中的每 一个元素都必须明确地分开存储,我们通常假设在存储了数值的有限点集以外,这些函数的值都为零。这意味着在实际操作中,我们可以通过对有限个数组元素的求和来实现无限求和。
2.卷积的可交换性
卷积运算可交换性的出现是因为我们将核相对输入进行了翻转,以m增大的角度看输入的索引在增大,但是核的索引在减小。我们将核翻转的唯一目的是实现可交换性。我们经常一次在多个维度上进行卷积运算。
例如:把一张二维的图像I作为输入,我们也许也想要使用一个二维的核K:
在这里有一个互相关函数(cross-correleation),和卷积运算几乎一样但是并没有对核进行翻转:
补充:这里的互相关是指对参数m、n,m+i、n+j,i-m、j-n。
目前把这两种运算都称为卷积。
注:在机器学习中,学习算法会在核合适的位置学得恰当的值,所以一个基于核翻转的卷积运算的学习算法所学得的核,是对未进行翻转的算法学得的核的翻转。
单独使用卷积运算在机器学习中很少见,通常与其他函数一起使用,无论卷积运算是否对它的核进行翻转,这些函数的组合通常是不可交换的。
对于单变量的离散卷积,卷积对应Toeplitz矩阵。Toeplitz矩阵(托普利兹矩阵):主对角线元素都相等,且平行于主对角线的元素也相等。
对于二维情况,卷积对应双重分块循环矩阵。分块循环矩阵:是一种特殊的Toeplitz矩阵,它的行向量是上一行向量向右移动一个单位。
二、动机
卷积运算通过三个重要的思想来帮助改进机器学习系统:
- 稀疏交互(sparse interactions)
- 参数共享(parameter sharing)
- 等变表示(equivariant representations)
另外,卷积提供了一种处理大小可变的输入的方法。我们下面依次介绍这些 思想。
1.稀疏交互
传统的神经网络使用矩阵乘法来建立输入与输出的连接关系。
其中,参数矩阵中每一个单独的参数都描述了一个输入单元与一个输出单元间的交互。这意味着每一个输出单元与每一个输入单元都产生交互。
然而,卷积网络具有稀疏交互(sparse interactions)(也叫做稀疏连接(sparse connectivity)或者稀疏权重 (sparse weights))的特征。
这是使核的大小远小于输入的大小来达到的。
举个例子,当处理一张图像时,输入的图像可能包含成千上万个像素点,但是我们可以通过只占用几十到上百个像素点的核来检测一些小的有意义的特征,例如图像的边缘。这意味着我们需要存储的参数更少,不仅减少了模型的存储需求,而且提高了它的统计效率。
如果有m个输入和n个输出,那么矩阵需要m×n个参数并且相应算法的时间复杂度为O(m×n)。如果我们限制每一个输出拥有的连接数为k,那么稀疏的连接方法只需要k×n个参数以及O(k×n)的运算时间。
在很多实际应用中,只需要保持k比m小几个数量级,就能在机器学习的任务中取得好的表现。下图第一张就是稀疏的表现。
2.参数共享
参数共享是指在一个模型的多个函数中使用相同的参数。在传统的神经网络中,当计算一层的输出时,权重矩阵的每一个元素只使用一次,当它乘以输入的一个元素后就再也不会用到了。
卷积运算中的参数共享保证了我们只需要学习一个参数集合,而不是对于每一位置都需要学习一个单独的参数集合。
这虽然每一改变前向传播的运行时间(仍然是O(k×n)),但它显著地把模型的存储需求降低至k个参数,并且k通常比m小很多个数量级。
参数共享如何实现的?见下图:
3.等变表示
对于卷积,参数共享的特殊形式使得神经网络层具有对平移等变(equivariance)的性质。如果一个函数满足输入改变,输出也以同样的方式改变这一性质,我们就说它是等变的。
特别地,如果函数f(g(x))=g(f(x)),我们就说f(x)对于变化g具有等变性。对于卷积来说,如果令g是输入的任意平移函数,那么卷积函数对于g具有等变性。
当处理多个输入位置时,一些作用在邻居像素的函数很有用的。相同的边缘或多或少地散落在图像的各处,所以应当对整个图像进行参数共享。但在某些情况下,我们并不希望对整幅图进行参数共享。例如,在处理已经通过裁减而使其居中的人脸图像时,我们可能想要提取不同位置上的不同特征(处理人脸上部的部分网络需要去搜寻眉毛,处理人脸下部的部分网络就需要去搜寻下巴了)。
卷积对其他的一些变换并不是天然等变的,例如对图像的放缩或者旋转变换,需要其他的一些机制来处理这些变换。
三、池化
卷积网络中一个典型层包含三级,见下图左侧
其中第一级Convolution stage(卷积级)并行地计算多个卷积产生一组线性激活响应;
在第二级Detector stage(探测级)每一个线性激活响应将会通过一个非线性的激活函数,例如整流线性激活函数;
在第三级中,使用池化函数(pooling function)来进一步调整这一层的输出。
1.池化函数
池化函数使用某一位置的相邻输出的总体统计特征来代替网络在该位置的输出
池化是缩小高、长方向上的空间运算。
图7-14的例子是按步幅2进行2×2的Max池化时的处理顺序。“Max 池化”是获取最大值的运算,“2×2”表示目标区域的大小。如图所示,从 2×2的区域中取出最大的元素。此外,这个例子中将步幅设为了2,所以 2×2的窗口的移动间隔为2个元素。另外,一般来说,池化的窗口大小会和步幅设定成相同的值。比如,3×3的窗口的步幅会设为3,4×4的窗口的步幅会设为4等。
1、最大池化(max pooling)函数:给出相邻矩形区域(目标区域)内的最大值。
2、其他常用的池化函数:平均池化函数计算相邻矩形区域内的平均值、L2 范数以及基于据中心像素距离的加权平均函数。
2.池化层特征
1、没有要学习的参数(只是从目标区域取最大、平均值)
2、通道数不发生变化(经过池化计算,输入数据和输出数据的通道数不会发生变化,计算都是按通道独立进行的)
3、对微小的位置变化具有鲁棒性(输入数据发生微小偏差时,池化仍会返回相同的结果)
不管采用什么样的池化函数,当输入作出少量平移时,池化能够帮助输入的表示近似不变(invariant)。
使用池化可以看作是增加了一个无限强的先验:这一层学得的函数必须具有对少量平移的不变性。当这个假设成立时,池化可以极大地提高网络的统计效率。
对空间区域进行池化产生了平移不变性,但当我们对分离参数的卷积的输出进行池化时,特征能够学得应该对于哪种变换具有不变性。
因为池化综合了全部邻居的反馈,这使得池化单元少于探测单元成为可能,我们可以通过综合池化区域的 k 个像素的统计特征而不是单个像素来实现。
在很多任务中,池化对于处理不同大小的输入具有重要作用。例如我们想对不同大小的图像进行分类时,分类层的输入必须是固定的大小,而这通常通过调整池 化区域的偏置大小来实现,这样分类层总是能接收到相同数量的统计特征而不管最初的输入大小了。例如,最终的池化层可能会输出四组综合统计特征,每组对应着图像的一个象限,而与图像的大小无关。
3.卷积层和池化层的实现
[1]4维数组
设置一个(10,1,28,28),10个高为28、长为28、通道为1的数据
import numpy as np
x=np.random.rand(10,1,28,28)
x.shape
#x[0]访问第一个数据
x[0].shape #(1,28,28)
x[1].shape #(1,28,28)
#如果要访问第1个数据的第1个通道的空间数据,可以写成下面这样。
x[0, 0] # 或者x[0][0]
像这样,CNN中处理的是4维数据,因此卷积运算的实现会看上起很复杂。但下面介绍im2col这个技巧,问题就会变得很简单。
[2]基于im2col的展开
im2col(”image to column”)从图像到矩阵
(不用关心im2col函数内部实现,它的实现在common/util.py)
import sys, os
sys.path.append(os.pardir)
from common.util import im2col
x1 = np.random.rand(1, 3, 7, 7)
col1 = im2col(x1, 5, 5, stride=1, pad=0)
print(col1.shape) # (9, 75)
x2 = np.random.rand(10, 3, 7, 7) # 10个数据
col2 = im2col(x2, 5, 5, stride=1, pad=0)
print(col2.shape) # (90, 75)
im2col函数会考虑滤波器大小、步幅、填充,将输入数据展开为2维数组
如果老老实实地实现卷积运算,估计要重复好几层的for语句。这样的 实现有点麻烦,而且,NumPy中存在使用for语句后处理变慢的缺点(NumPy 中,访问元素时最好不要用for语句)。这里,我们不使用for语句,而是使用im2col这个便利的函数进行简单的实现。
im2col是一个函数,将输入数据展开以适合滤波器(权重)。如图7-17所示, 对3维的输入数据应用im2col后,数据转换为2维矩阵(正确地讲,是把包含批数量的4维数据转换成了2维数据)。
im2col会把输入数据展开以适合滤波器(权重)。具体地说,如图7-18所示, 对于输入数据,将应用滤波器的区域(3维方块)横向展开为1列。im2col会在所有应用滤波器的地方进行这个展开处理。
在图7-18中,为了便于观察,将步幅设置得很大,以使滤波器的应用区 域不重叠。而在实际的卷积运算中,滤波器的应用区域几乎都是重叠的。在滤波器的应用区域重叠的情况下,使用im2col展开后,展开后的元素个数会多于原方块的元素个数。因此,使用im2col的实现存在比普通的实现消耗更多内存的缺点。但是,汇总成一个大的矩阵进行计算,对计算机的计算颇有益处。比如,在矩阵计算的库(线性代数库)等中,矩阵计算的实现已被高 度最优化,可以高速地进行大矩阵的乘法运算。因此,通过归结到矩阵计算上,可以有效地利用线性代数库。
使用im2col展开输入数据后,之后就只需将卷积层的滤波器(权重)纵向展开为1列,并计算2个矩阵的乘积即可(参照图7-19)。这和全连接层的 Affine层进行的处理基本相同。 如图7-19所示,基于im2col方式的输出结果是2维矩阵。因为CNN中数据会保存为4维数组,所以要将2维输出数据转换为合适的形状。以上就是卷积层的实现流程。
[3]卷积层的实现
使用im2col来实现卷积层。
卷积层的初始化方法将滤波器(权重)、偏置、步幅、填充作为参数接收。滤波器是(FN, C, FH, FW)的4维形状。另外,FN、C、FH、FW分别是Filter Number(滤波器数量)、Channel、Filter Height、Filter Width的缩写。
以上就是卷积层的forward处理的实现。通过使用im2col进行展开,基本上可以像实现全连接层的Affine层一样来实现(5.6节)。接下来是卷积层 的反向传播的实现,因为和Affine层的实现有很多共通的地方,所以就不再 介绍了。但有一点需要注意,在进行卷积层的反向传播时,必须进行im2col的逆处理。这可以使用本书提供的im2col函数(im2col的实现在common/util.py中)来进行。除了使用im2col这一点,卷积层的反向传播和Affine层的实现方式都一样。卷积层的反向传播的实现在common/layer.py中,有兴趣的读者可以参考。
[4]池化层的实现
池化层的实现和卷积层相同,都使用im2col展开输入数据。不过,池化 的情况下,在通道方向上是独立的,这一点和卷积层不同。具体地讲,如图 7-21所示,池化的应用区域按通道单独展开。
像这样展开之后,只需对展开的矩阵求各行的最大值,并转换为合适的形状即可(图7-22)。(一种转换思想,方便计算)
池化层的三个阶段:
1.展开输入数据
2.求各行的最大值
3.转换为合适的输出大小
以上就是池化层的forward处理的介绍。如上所述,通过将输入数据展开为容易进行池化的形状,后面的实现就会变得非常简单。 关于池化层的backward处理,之前已经介绍过相关内容,这里就不再介绍了。 另外,池化层的backward处理可以参考ReLU层的实现中使用的max的反向传播(5.5.1节)。池化层的实现在common/layer.py中,有兴趣的读者可以参考。
[5]CNN的实现
四、具有代表性的CNN
1、LeNet
和“现在的CNN”相比, LeNet有几个不同点。第一个不同点在于激活函数。LeNet中使用sigmoid函数,而现在的CNN中主要使用ReLU函数。 此外,原始的LeNet中使用子采样(subsampling)缩小中间数据的大小,而现在的CNN中Max池化是主流。综上,LeNet与现在的CNN虽然有些许不同,但差别并不是那么大。 LeNet是20多年前提出的最早的CNN。
2、AlexNet
AlexNet叠有多个卷积层和池化层,最后经由全连接层输出结果。虽然 结构上AlexNet和LeNet没有大的不同,但有以下几点差异。
• 激活函数使用ReLU。
• 使用进行局部正规化的LRN(Local Response Normalization)层。
• 使用Dropout(6.4.3节)。
LeNet和AlexNet是CNN的代表性网络。
卷积与池化作为一种无限强的先验
先验被认为是强或者弱取决于先验中概率密度的集中程度。
一个无限强的先验需要对一些参数的概率置零并且完全禁止对这些参数赋值,无论数据对于这些参数的值给出了多大的支持。
我们可以把卷积网络类比成全连接网络,但对于这个全连接网络的权重有一个无限强的先验。这个无限强的先验是说一个隐藏单元的权重必须和它邻居的权重相 同,但可以在空间上移动。这个先验也要求除了那些处在隐藏单元的小的空间连续的接受域内的权重以外,其余的权重都为零。总之,我们可以把卷积的使用当作是对网络中一层的参数引入了一个无限强的先验概率分布。这个先验说明了该层应该 学得的函数只包含局部连接关系并且对平移具有等变性。类似的,使用池化也是一个无限强的先验:每一个单元都具有对少量平移的不变性。
基本卷积函数的变体
因为卷积网络通常使用多通道的卷积,所以即使用了核翻转,也不一定保证网络的线性运算是可交换的。只有当其中的每个运算的输出和输入具有相同的通道数时,这些多通道的运算才是可交换的。
所以卷积函数需要变换。