卷积神经网络的简单理解
文章目录
前面的神经网络我们使用的都是全连接网络,不管是感知机还是函数逼近,都使用的仿射(Affine)变换,今天介绍卷积神经网络(Convolutional Neural Network,CNN)。CNN被用于图像识别、语音识别等各种场合,在图像识别的比赛中,基于深度学习的方法几乎都以CNN为基础。
网上谈卷积神经网络的文章不计其数,本篇的目的是帮助你快速理解其作用和关键点。
1、为什么需要卷积
我认为有以下几个作用:
1、通过共享权重,降低学习的参数 举个例子,在MNIST手写数字识别中,一张图片是由像素点构成,每个像素点有一个通道,代表RGB颜色,那么,如果一个图像的尺寸是(28,28,1),即代表这个图像的是一个长宽均为28,channel为1的图像(channel也叫depth,此处1代表灰色图像)。如果使用全连接的网络结构,即,网络中的神经与与相邻层上的每个神经元均连接,那就意味着我们的网络有28 * 28 =784个神经元,hidden层采用了15个神经元,那么简单计算一下,我们需要的参数个数(w和b)就有:个(解释:, 15个隐藏层和10个输出层偏置),这个参数太多了,随便进行一次反向传播计算量都是巨大的,从计算资源和调参的角度都不建议用传统的神经网络。这还只是一个 Hello World 级的实例。
2、特征提取 通过卷积核或叫做滤波器的不同设置可提取图像不同特征值。比如下图里的边界检查中,可以使用水平和垂直方向的滤波器,检测水平方向和垂直方向的边界特征。CNN中,有时将卷积层的输入输出数据称为特征图(feature map)。其中,卷积层的输入数据称为输入特征图(input feature map),输出数据称为输出特征图(output feature map)。
3、保形输出 之前介绍的全连接的神经网络中使用了全连接层(Affine层)。在全连接层中,相邻层的神经元全部连接在一起,输出的数量可以任意决定。全连接层存在什么问题呢?那就是数据的形状被“忽视”了。比如,输入数据是图像时,图像通常是高、长、通道方向上的3 维形状。但是,向全连接层输入时,需要将3 维数据拉平为1 维数据。实际上,前面提到的使用了MNIST数据集的例子中,输入图像就是1 通道、高28 像素、长28 像素的(1, 28, 28)形状,但却被排成1列,以784 个数据的形式输入到最开始的Affine层。图像是3 维形状,这个形状中应该含有重要的空间信息。比如,空间上邻近的像素为相似的值、RBG的各个通道之间分别有密切的关联性、相距较远的像素之间没有什么关联等,3 维形状中可能隐藏有值得提取的本质模式。但是,因为全连接层会忽视形状,将全部的输入数据作为相同的神经元(同一维度的神经元)处理,所以无法利用与形状相关的信息。而卷积层可以保持形状不变。当输入数据是图像时,卷积层会以3 维数据的形式接收输入数据,并同样以3 维数据的形式输出至下一层。
2、整体结构
首先,来看一下CNN的网络结构,了解CNN的大致框架。
CNN和之前介绍的神经网络一样,可以像乐高积木一样通过组装层来构建。不过,CNN中新出现了卷积层(Convolution 层)和池化层(Pooling 层)。
之前介绍的神经网络中,相邻层的所有神经元之间都有连接,这称为全连接(fully-connected)。我们用Affine层实现了全连接层。如果使用这个Affine层,一个5 层的全连接的神经网络就可以通过图1-1 所示的网络结构来实现。
如图1-1 所示,全连接的神经网络中,Affine层后面跟着**函数ReLU层(或者Sigmoid 层)。这里堆叠了4 层“Affine-ReLU”组合,然后第5 层是Affine层,最后由Softmax层输出最终结果(概率)。
图1-1
那么,CNN会是什么样的结构呢?图1-2 是CNN的一个例子。
图1-2
可以看出,在组建网络结构的方法上,没啥差别。在CNN中新增了Convolution 层和Pooling 层。CNN 的层的连接顺序是“Convolution-ReLU-(Pooling)”。这可以理解为之前的“Affine-ReLU”连接被替换成了“Convolution-ReLU-(Pooling)”连接。还需要注意的是,在图1-2 的CNN中,靠近输出的层中使用了之前的“Affine-ReLU”组合。此外,最后的输出层中使用了之前的“Affine =-Softmax”组合。这些都是一般的CNN中比较常见的结构.
3、卷积层
CNN中出现了一些特有的术语,比如填充、步幅等。此外,各层中传递的数据是有形状的数据(比如,3 维数据),这与之前的全连接网络不同,因此刚开始学习CNN时可能会感到难以理解。本节我们将花点时间,认真学习一下CNN中使用的卷积层的结构.
3.1 卷积运算
卷积层进行的处理就是卷积运算。卷积运算相当于图像处理中的“滤波器运算”。在介绍卷积运算时,我们来看一个具体的例子(图1-3)。
图1-3
如图1-3 所示,卷积运算对输入数据应用滤波器。在这个例子中,输入数据是有高长方向的形状的数据,滤波器也一样,有高长方向上的维度。假设用(height, width)表示数据和滤波器的形状,则在本例中,输入大小是(4, 4),滤波器大小是(3, 3),输出大小是(2, 2)。另外,有的文献中也会用“核”这个词来表示这里所说的“滤波器”。
现在来解释一下图1-3 的卷积运算的例子中都进行了什么样的计算。图1-4中展示了卷积运算的计算顺序。
对于输入数据,卷积运算以一定间隔滑动滤波器的窗口并应用。这里所说的窗口是指图1-4 中灰色的3 × 3 的部分。如图1-4 所示,将各个位置上滤波器的元素和输入的对应元素相乘,然后再求和(有时将这个计算称为乘积累加运算)。然后,将这个结果保存到输出的对应位置。将这个过程在所有位置都进行一遍,就可以得到卷积运算的输出。
图1-4
注意滤波器操作不是点积,二是如下操作,示例:
>>> import numpy as np
>>> a = np.array([[1,2,3],[0,1,2],[3,0,1]])
>>> a
array([[1, 2, 3],
[0, 1, 2],
[3, 0, 1]])
>>> b = np.array([[2,0,1],[0,1,2],[1,0,2]])
>>> b
array([[2, 0, 1],
[0, 1, 2],
[1, 0, 2]])
>>> c = a * b # 不是np.dot(a,b)
>>> c
array([[2, 0, 3],
[0, 1, 4],
[3, 0, 2]])
>>> print(c.sum())
15
在全连接的神经网络中,除了权重参数,还存在偏置。CNN中,滤波器的参数就对应之前的权重。并且,CNN中也存在偏置。图1-3 的卷积运算的例子一直展示到了应用滤波器的阶段。包含偏置的卷积运算的处理流如图1-5 所示。如图1-5 所示,向应用了滤波器的数据加上了偏置。偏置通常只有1 个(1 × 1)(本例中,相对于应用了滤波器的4 个数据,偏置只有1 个),这个值会被加到应用了滤波器的所有元素上。
图1-5
3.2 Padding填充
在进行卷积层的处理之前,有时要向输入数据的周围填入固定的数据(比如0 等),这称为填充(padding),是卷积运算中经常会用到的处理。比如,在图1-6 的例子中,对大小为(4, 4) 的输入数据应用了幅度为1的填充。“幅度为1的填充”是指用幅度为1 像素的0填充周围。
图1-6 卷积运算的填充处理:向输入数据的周围填入0(图中用虚线表示填充,并省略了填充的内容“0”)
如图1-6 所示,通过填充,大小为(4, 4) 的输入数据变成了(6, 6) 的形状。然后,应用大小为(3, 3) 的滤波器,生成了大小为(4, 4) 的输出数据。这个例子中将填充设成了1,不过填充的值也可以设置成2、3 等任意的整数。在图7-5的例子中,如果将填充设为2,则输入数据的大小变为(8, 8);如果将填充设为3,则大小变为(10, 10)。
使用填充主要是为了调整输出的大小。比如,对大小为(4, 4) 的输入数据应用(3, 3) 的滤波器时,输出大小变为(2, 2),相当于输出大小比输入大小缩小了2 个元素。这在反复进行多次卷积运算的深度网络中会成为问题。为什么呢?因为如果每次进行卷积运算都会缩小空间,那么在某个时刻输出大小就有可能变为1,导致无法再应用卷积运算。为了避免出现这样的情况,就要使用填充。在刚才的例子中,将填充的幅度设为1,那么相对于输入大小(4, 4),输出大小也保持为原来的(4, 4)。因此,卷积运算就可以在保持空间大小不变的情况下将数据传给下一层。
3.3 步幅
应用滤波器的位置间隔称为步幅(stride)。之前的例子中步幅都是1,如果将步幅设为2,则如图1-7 所示,应用滤波器的窗口的间隔变为2个元素。
图1-7 步幅为2 的卷积运算的例子
在图1-7 的例子中,对输入大小为(7, 7) 的数据,以步幅2 应用了滤波器。通过将步幅设为2,输出大小变为(3, 3)。像这样,步幅可以指定应用滤波器的间隔。综上,增大步幅后,输出大小会变小。而增大填充后,输出大小会变大。
如果将这样的关系写成算式,会如何呢?接下来,我们看一下对于填充和步幅,如何计算输出大小。
这里,假设输入大小为(H,W),滤波器大小为(FH, FW),输出大小为(OH,OW),填充为P,步幅为S。此时,输出大小可通过式(1.1) 进行计算。
3.4 3维数据的卷积运算
之前的卷积运算的例子都是以有高、长方向的2维形状为对象的。但是,图像是3维数据,除了高、长方向之外,还需要处理通道方向。这里,我们按照与之前相同的顺序,看一下对加上了通道方向的3维数据进行卷积运算的例子。
图1-8 对3维数据进行卷积运算的例子
图1-8 是卷积运算的例子,图1-9 是计算顺序。这里以3 通道的数据为例,展示了卷积运算的结果。和2 维数据时(图7-3 的例子)相比,可以发现纵深方向(通道方向)上特征图增加了。通道方向上有多个特征图时,会按通道进行输入数据和滤波器的卷积运算,并将结果相加,从而得到输出。
需要注意的是,在3 维数据的卷积运算中,输入数据和滤波器的通道数要设为相同的值。在这个例子中,输入数据和滤波器的通道数一致,均为3。滤波器大小可以设定为任意值(不过,每个通道的滤波器大小要全部相同)。
这个例子中滤波器大小为(3, 3),但也可以设定为(2, 2)、(1, 1)、(5, 5) 等任意值。
再强调一下,通道数只能设定为和输入数据的通道数相同的值。
3.5 结合方块思考
将数据和滤波器结合长方体的方块来考虑,3 维数据的卷积运算会很容易理解。方块是如图1-10 所示的3 维长方体。把3 维数据表示为多维数组时,书写顺序为(channel, height, width)。比如,通道数为C、高度为H、长度为W的数据的形状可以写成(C,H,W)。滤波器也一样,要按(channel,height, width)的顺序书写。比如,通道数为C、滤波器高度为FH(Filter Height)、长度为FW(Filter Width)时,可以写成(C, FH, FW)。
图1-10 结合方块思考卷积运算。请注意方块的形状
在这个例子中,数据输出是1 张特征图。所谓1 张特征图,换句话说,就是通道数为1 的特征图。那么,如果要在通道方向上也拥有多个卷积运算的输出,该怎么做呢?为此,就需要用到多个滤波器(权重)。用图表示的话,如图1-11 所示。
图1-11 基于多个滤波器的卷积运算的例子
图1-11 中,通过应用FN个滤波器,输出特征图也生成了FN个。如果将这FN个特征图汇集在一起,就得到了形状为(FN, OH,OW) 的方块。将这个方块传给下一层,就是CNN的处理流。如图1-11 所示,关于卷积运算的滤波器,也必须考虑滤波器的数量。因此,作为4 维数据,滤波器的权重数据要按(output_channel, input_channel, height, width) 的顺序书写。比如,通道数为3、大小为5 × 5 的滤波器有20 个时,可以写成(20, 3, 5, 5)。卷积运算中(和全连接层一样)存在偏置。在图1-11 的例子中,如果进一步追加偏置的加法运算处理,则结果如下面的图1-12 所示。图1-12 中,每个通道只有一个偏置。这里,偏置的形状是(FN, 1, 1),滤波器的输出结果的形状是(FN, OH,OW)。这两个方块相加时,要对滤波器的输出结果(FN, OH,OW) 按通道加上相同的偏置值。另外,不同形状的
方块相加时,可以基于NumPy的广播功能轻松实现。
图1-12 卷积运算的处理流(追加了偏置项)
3.6 批处理
神经网络的处理中进行了将输入数据打包的批处理。之前的全连接神经网络的实现也对应了批处理,通过批处理,能够实现处理的高效化和学习时对mini-batch 的对应。我们希望卷积运算也同样对应批处理。为此,需要将在各层间传递的数据保存为4 维数据。具体地讲,就是按(batch_num, channel, height, width)的顺序保存数据。比如,将图1-12 中的处理改成对N个数据进行批处理时,数据的形状如图1-13 所示。图1-13 的批处理版的数据流中,在各个数据的开头添加了批用的维度。像这样,数据作为4 维的形状在各层间传递。这里需要注意的是,网络间传递的是4 维数据,对这N个数据进行了卷积运算。也就是说,批处理将N次的处理汇总成了1 次进行。
图1-13 卷积运算的处理流(批处理)
4、池化层
池化是缩小高、长方向上的空间的运算。比如,如图1-14 所示,进行将2 × 2 的区域集约成1 个元素的处理,缩小空间大小。
图1-14 Max池化的处理顺序
图1-14 的例子是按步幅2 进行2 × 2 的Max池化时的处理顺序。“Max池化”是获取最大值的运算,“2 × 2”表示目标区域的大小。如图所示,从2 × 2 的区域中取出最大的元素。此外,这个例子中将步幅设为了2,所以2 × 2 的窗口的移动间隔为2 个元素。另外,一般来说,池化的窗口大小会和步幅设定成相同的值。比如,3 × 3 的窗口的步幅会设为3,4 × 4 的窗口的步幅会设为4 等。
除了Max 池化之外,还有Average 池化等。相对于Max 池化是从目标区域中取出最大值,Average 池化则是计算目标区域的平均值。在图像识别领域,主要使用Max 池化。因此,本书中说到“池化层”时,指的是Max 池化。
池化层的特征
没有要学习的参数 池化层和卷积层不同,没有要学习的参数。池化只是从目标区域中取最大值(或者平均值),所以不存在要学习的参数。
通道数不发生变化 经过池化运算,输入数据和输出数据的通道数不会发生变化。