《深度学习入门:基于Python的理论与实现》 卷积神经网络
[日]斋藤康毅
卷积神经网络(Convolutional Neural Network,CNN)。CNN被用于图像识别、语音识别等各种场合,在图像识别的比赛中,基于深度学习的方法几乎都以CNN为基础。
卷积神经网络整体结构
全连接(fully-connected):相邻层的所有神经元之间都有连接,例如第二层的第一个节点与第一层的所有神经元节点都有连接。
CNN的基本结构为Convolution -> ReLU -> Pooling -> Convolution -> ReLU -> Pooling -> Affine -> ReLU -> Affine -> Softmax
只在靠近输出的层中使用了之前的“Affine - ReLU”组合,最后的输出层中使用了之前的“Affine - Softmax”组合。
卷积层
全连接层存在什么问题呢?
那就是数据的形状被“忽视”了。比如,输入数据是图像时,图像通常是高、长、通道方向上的3维形状。但是,向全连接层输入时,需要将3维数据拉平为1维数据。实际上,前面提到的使用了MNIST数据集的例子中,输入图像就是1通道、高28像素、长28像素的(1, 28, 28)形状,但却被排成1列,以784个数据的形式输入到最开始的Affine层。
图像是3维形状,这个形状中应该含有重要的空间信息。比如,空间上邻近的像素为相似的值、RGB的各个通道之间分别有密切的关联性、相距较远的像素之间没有什么关联等,3维形状中可能隐藏有值得提取的本质模式。但是,因为全连接层会忽视形状,将全部的输入数据作为相同的神经元(同一维度的神经元)处理,所以无法利用与形状相关的信息。这也是注意力机制改进的地方,2017年google发布的attention is all you need,这本书是2016年出版的。
CNN中,有时将卷积层的输入输出数据称为特征图(feature map)。其中,卷积层的输入数据称为输入特征图(input feature map),输出数据称为输出特征图(output feature map)。卷积上可以看作是删减了全连接中的一些连接线的网络。
卷积运算
卷积运算相当于图像处理中的滤波器运算。输入数据是有高长方向的形状的数据,滤波器也一样,有高长方向上的维度。假设用(height, width)表示数据和滤波器的形状,则在本例中,输入大小是(4, 4),滤波器大小是(3, 3),输出大小是(2, 2)。另外,有的文献中也会用“卷积核”这个词来表示这里所说的“滤波器”。
对于输入数据,卷积运算以一定间隔滑动滤波器的窗口并应用,然后将各个位置上滤波器的元素和输入的对应元素相乘,然后再求和(有时将这个计算称为乘积累加运算)。然后,将这个结果保存到输出的对应位置。将这个过程在所有位置都进行一遍,就可以得到卷积运算的输出。
CNN中,滤波器的参数就是卷积层的参数,同时CNN中也存在偏置。
卷积运算的偏置只有1个,这个值会被加到应用了滤波器的所有元素上。
填充(Padding)
在进行卷积层的处理之前,有时要向输入数据的周围填入固定的数据(比如0等),这称为填充(padding),是卷积运算中经常会用到的处理。下图中,对大小为(4, 4)的输入数据应用了幅度为1的填充。“幅度为1的填充”是指用幅度为1像素的0填充周围。
通过填充,大小为(4, 4)的输入数据变成了(6, 6)的形状。然后,应用大小为(3, 3)的滤波器,生成了大小为(4, 4)的输出数据。填充的值也可以设置成2、3等任意的整数。如果将填充设为2,则输入数据的大小变为(8, 8);如果将填充设为3,则大小变为(10, 10)
使用填充主要是为了调整输出的大小。比如,对大小为(4, 4)的输入数据应用(3, 3)的滤波器时,输出大小变为(2, 2),相当于输出大小比输入大小缩小了2个元素。这在反复进行多次卷积运算的深度网络中会成为问题。为什么呢?因为如果每次进行卷积运算都会缩小空间,那么在某个时刻输出大小就有可能变为1,导致无法再应用卷积运算。为了避免出现这样的情况,就要使用填充。
步幅(stride)
应用滤波器的位置间隔称为步幅(stride)。之前的例子中步幅都是1,如果将步幅设为2,应用滤波器的窗口的间隔变为2个元素。
增大步幅后,输出大小会变小。而增大填充后,输出大小会变大。
输出大小计算
假设输入大小为(H,W),滤波器大小为(FH,FW),输出大小为(OH,OW),填充为P,步幅为S。输出大小为:
$$
OH = \frac{H+2P-FH}{S} +1 \
OW = \frac{W+2P-FW}{S} +1
$$
当输出大小无法除尽时(结果是小数时),需要采取报错等对策。顺便说一下,根据深度学习的框架的不同,当值无法除尽时,有时会向最接近的整数四舍五入,不进行报错而继续运行。
多维数据的卷积计算
对于彩色图像RGB三个颜色对应的三个通道,在进行卷积计算时,会按通道进行输入数据和滤波器的卷积运算,并将结果相加,从而得到输出。
通道方向上有多个特征图时,会按通道进行输入数据和滤波器的卷积运算,并将结果相加,从而得到输出。
输入数据和滤波器的通道数相同,每个通道的滤波器的值可以不同,但是每个通道的滤波器Shape要都相同。
把3维数据表示为多维数组时,书写顺序为(channel, height, width)。比如,通道数为C、高度为H、长度为W的数据的形状可以写成(C,H,W)。滤波器也一样,要按(channel, height, width)的顺序书写。比如,通道数为C、滤波器高度为FH(Filter Height)、长度为FW(Filter Width)时,可以写成(C,FH,FW)。
上图中3个通道的输入数据和三个通道的滤波器卷积计算后,数据输出是1张特征图,它的通道数为1。如果要在通道方向上也拥有多个卷积运算的输出,该怎么做呢?
为了可以让输出有多个通道,需要用到多个滤波器(权重)。通过应用FN个滤波器,输出特征图也生成了FN个。如果将这FN个特征图汇集在一起,就得到了形状为(FN,OH,OW)的方块,这个输出就可以作为下一层的输入了。
对于灰度图像,这里特征图的通道使用滤波器的个数来表示,每个滤波器表示一个特征维度,例如某一个滤波器表示是否是一个🍎的特征,而另一个滤波器表示是否是一只🐱的特征
所以滤波器是一个4维数据,它的权重数据要按(output_channel, input_channel, height, width)的顺序书写。
通过矩阵的批处理可以将N次的卷积滤波处理汇总成了1次进行。
池化层(Pooling)
池化是缩小高、长方向上的空间的运算。池化会吸收输入数据的偏差(根据数据的不同,结果有可能不一致)
上图的例子是按步幅2进行2×2的Max池化时的处理顺序。“Max池化”是获取最大值的运算,“2×2”表示目标区域的大小。Average池化则是计算目标区域的平均值,在图像识别领域,主要使用Max池化。
◆ 池化层和卷积层不同,没有要学习的参数。池化只是从目标区域中取最大值(或者平均值),所以不存在要学习的参数。
经过池化运算,输入数据和输出数据的通道数不会发生变化。池化计算是按通道独立进行的。
网络层实现
卷积层实现
以之前图像识别为例,输入数据为(批次大小,通道数量,图像高度,图像宽度)
,所以输入的数据是4维的。要对这个4维数据进行卷积运算,最直接的方法是通过for循环遍历每一个批次的每一个通道的数据,再进行实际的卷积计算,但这样的效率很低。
可以通过im2col(image to column)函数把多维的图像数据转换为2维的矩阵。对可以通过对一个批次中的一个3维的输入数据应用im2col
后,数据转换为2维矩阵(正确地讲,是把包含批数量的4维数据转换成了2维数据)。
当滤波器的应用区域重叠的情况下,使用im2col
展开后,展开后的元素个数会多于原方块的元素个数。因此,使用im2col
的实现存在比普通的实现消耗更多内存的缺点。但是,汇总成一个大的矩阵进行计算,对计算机的计算颇有益处。比如,在矩阵计算的库(线性代数库)等中,矩阵计算的实现已被高度最优化,可以高速地进行大矩阵的乘法运算。
使用矩阵行列分解的和组合的方式更容易理解这个计算过程,例如输入数据为(N, C, H, W)
,根据滤波器(FN, C, FH, FW)
计算出的输出的大小为(OH,OW)
。通过im2col
计算后输出的矩阵为(N*OH*OW, C*FH*FW)
,它的行是这个批次中数据数量个预期输出的大小的行,列是通道个数与滤波器大小的乘积,这个输出可以和(C*FH*FW, FN)
即FN个滤波器进行矩阵乘法,最终得到(N*OH*OW, FN)
,通过reshape重新展开,就得到(N, FN, OH, OW)
最终的输出。
im2col
的实现如下
1 | def im2col(input_data, filter_h, filter_w, stride=1, pad=0): |
卷积层代码
1 | class Convolution: |
reshape(FN,-1)
将参数指定为-1,这是reshape的一个便利的功能。通过在reshape时指定为-1,reshape函数会自动计算-1维度上的元素个数,以使多维数组的元素个数前后一致。比如,(10, 3, 5, 5)形状的数组的元素个数共有750个,指定reshape(10,-1)后,就会转换成(10, 75)形状的数组。
在进行卷积层的反向传播时,必须进行im2col的逆处理col2im函数来进行
池化层实现
池化的应用区域按通道单独展开。 然后,只需对展开的矩阵求各行的最大值,并转换为合适的形状即可
1 | class Pooling: |
CNN的实现
CNN的流程如下:
Convolution -> ReLU -> Pooling -> Convolution -> ReLU -> Pooling -> Affine -> ReLU -> Affine -> Softmax
1 | class SimpleConvNet: |
这次模型训练需要半个小时左右时间,20个批次最终输出测试集准确率为0.989,比之前非卷积网络的高上一些。保存的权重参数文件params.pkl
大小为3.31 MB (3,471,485 bytes)
CNN的可视化
学习前的滤波器是随机进行初始化的,所以在黑白的浓淡上没有规律可循,但学习后的滤波器变成了有规律的图像。通过学习,滤波器被更新成了有规律的滤波器,比如从白到黑渐变的滤波器、含有块状区域(称为blob)的滤波器等。
最开始的第一层中滤波器在“观察”边缘(颜色变化的分界线)和斑块(局部的块状区域)等。
CNN通过卷积层中提取的信息。第1层的神经元对边缘或斑块有响应,第3层对纹理有响应,第5层对物体部件有响应,最后的全连接层对物体的类别(狗或车)有响应。
随着层次加深,提取的信息也愈加复杂、抽象,这是深度学习中很有意思的一个地方。最开始的层对简单的边缘有响应,接下来的层对纹理有响应,再后面的层对更加复杂的物体部件有响应。也就是说,随着层次加深,神经元从简单的形状向“高级”信息变化。
具有代表性的CNN
AlexNet
叠有多个卷积层和池化层,最后经由全连接层输出结果.
第6章网络优化的相关代码
optimizer.py 权重参数更新优化
1 | import numpy as np |
trainer.py
1 | class Trainer: |