卷积神经网络汇聚层的简要介绍
卷积神经网络中的卷积层总结了输入图像中特征的存在。
输出要素地图的一个问题是它们对输入中要素的位置很敏感。解决这种敏感性的一种方法是对特征地图进行下采样。这具有使得到的下采样特征图对图像中的特征位置的改变更稳健的效果,技术短语“局部平移不变性”指的是该位置的改变。
通过汇总要素地图的补丁中存在的要素,池图层提供了一种向下采样要素地图的方法。两种常见的池化方法是平均池化和最大池化,它们分别汇总了特征的平均存在和特征的最活跃存在。
在本教程中,你将了解池化操作是如何工作的,以及如何在卷积神经网络中实现它。
完成本教程后,你将了解:
- 要对要素地图中的要素检测进行下采样,需要进行池操作。
- 如何在卷积神经网络中计算和实现平均池和最大池。
- 如何在卷积神经网络中使用全局池。
我们开始吧。
教程概述
本教程分为五个部分;它们是:
- 池化。
- 检测垂直线。
- 平均池化层。
- 最大池化层。
- 全局池化层。
池化图层
卷积神经网络中的卷积层系统地将学习的滤波器应用于输入图像,以便创建总结那些特征在输入中的存在的特征映射。
卷积层被证明是非常有效的,在深层模型中堆叠卷积层允许靠近输入的层学习低级特征(例如线),并且允许模型中更深的层学习高阶或更抽象的特征,如形状或特定对象。
卷积图层的要素地图输出的一个限制是它们记录输入中要素的精确位置。这意味着输入图像中特征位置的微小移动将导致不同的特征地图。对输入图像进行重新裁剪、旋转、平移和其他细微更改时,可能会发生这种情况。
从信号处理中解决此问题的一种常用方法称为下采样。这是创建输入信号的较低分辨率版本的地方,该输入信号仍然包含大的或重要的结构元素,没有可能对任务没有用处的精细细节。
通过改变图像上卷积的步幅,可以用卷积层实现向下采样。一种更健壮、更常见的方法是使用池层。
池化层是在卷积层之后添加的新层。具体地说,在将非线性(例如RELU)应用于卷积层输出的特征地图之后;例如,模型中的层可能如下所示:
- 输入图像。
- 卷积层。
- 非线性。
- 池化层。
在卷积层之后添加池层是用于对卷积神经网络内的层进行排序的常见模式,其可以在给定模型中重复一次或多次。
池化图层分别对每个要素地图进行操作,以创建相同数量的池化要素地图的新集合。
池化涉及选择池化操作,这与要应用于要素地图的过滤器非常相似。池操作或过滤器的大小小于特征地图的大小;具体地说,它几乎总是以2像素的跨度应用于2×2像素。
这意味着池化图层将始终将每个要素地图的大小减小2倍,例如,每个维度减半,将每个要素地图中的像素或值的数量减少到大小的四分之一。例如,应用于6×6(36像素)要素地图的池化图层将产生3×3(9像素)的输出池化要素地图。
池操作是指定的,而不是学习的。池化操作中使用的两个常见函数是:
- 平均池化:计算功能地图上每个补丁的平均值。
- 最大池化(或最大池化):计算功能地图的每个补丁的最大值。
使用池化图层并创建向下采样或池化要素地图的结果是输入中检测到的要素的汇总版本。它们非常有用,因为由卷积层检测到的输入中要素位置的微小更改将导致要素位于相同位置的池化要素地图。通过池添加的这种功能称为模型对本地转换的不变性。
在所有情况下,池化都有助于使表示变得与输入的小翻译近似不变。翻译的不变性意味着如果我们翻译少量的输入,大多数汇集的输出的值不会改变。
既然我们已经熟悉了池化层的必要性和好处,让我们来看一些具体的示例。
检测垂直线
在我们查看一些池化层的示例及其效果之前,让我们先开发一个输入图像和卷积层的小示例,稍后我们可以向其添加和评估池化层。
在此示例中,我们定义了一个输入图像或样本,它有一个通道,是一个8像素x 8像素的正方形,所有值均为0,中间有一条2像素宽的垂直线。
# define input data data = [[0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0]] data = asarray(data) data = data.reshape(1, 8, 8, 1)
接下来,我们可以定义一个模型,该模型期望输入样本具有形状(8,8,1),并且具有具有3像素乘3像素形状的单个过滤器的单个隐藏卷积层。
然后,将校正的线性激活函数(简称ReLU)应用于特征映射中的每个值。这是一种简单而有效的非线性,在这种情况下不会改变特征地图中的值,但之所以存在,是因为我们稍后将添加后续的池化层,并且在应用于特征地图的非线性之后添加池,例如,这是一种最佳实践。
# create model model = Sequential() model.add(Conv2D(1, (3,3), activation='relu', input_shape=(8, 8, 1))) # summarize model model.summary()
作为模型初始化的一部分,过滤器使用随机权重进行初始化。
取而代之的是,我们将硬编码我们自己的3×3过滤器来检测垂直线。也就是说,过滤器将在检测到垂直线时强烈激活,而在未检测到垂直线时弱激活。我们期望通过在输入图像上应用此过滤器,输出特征图将显示检测到垂直线。
# define a vertical line detector detector = [[[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]]] weights = [asarray(detector), asarray([0.0])] # store the weights in the model model.set_weights(weights)
接下来,我们可以通过调用模型上的Forecast()函数将过滤器应用于输入图像。
# apply filter to input data yhat = model.predict(data)
结果是一个具有一个批次、给定行数和列数以及一个过滤器或[批次、行、列、过滤器]的四维输出。我们可以在单个要素地图中打印激活,以确认检测到该线。
# enumerate rows for r in range(yhat.shape[1]): # print each column in the row print([yhat[0,r,c,0] for c in range(yhat.shape[2])])
将所有这些结合在一起,下面列出了完整的示例。
# example of vertical line detection with a convolutional layer from numpy import asarray from keras.models import Sequential from keras.layers import Conv2D # define input data data = [[0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0]] data = asarray(data) data = data.reshape(1, 8, 8, 1) # create model model = Sequential() model.add(Conv2D(1, (3,3), activation='relu', input_shape=(8, 8, 1))) # summarize model model.summary() # define a vertical line detector detector = [[[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]]] weights = [asarray(detector), asarray([0.0])] # store the weights in the model model.set_weights(weights) # apply filter to input data yhat = model.predict(data) # enumerate rows for r in range(yhat.shape[1]): # print each column in the row print([yhat[0,r,c,0] for c in range(yhat.shape[2])])
运行该示例首先总结了模型的结构。
值得注意的是,单个隐藏卷积层将获取8×8像素的输入图像,并将产生尺寸为6×6的特征地图。
我们还可以看到该层有10个参数:即9个用于过滤器的权重(3×3)和1个用于偏移的权重。
最后,打印单个要素地图。
通过查看6×6矩阵中的数字,我们可以看到,实际上手动指定的过滤器检测到了输入图像中间的垂直线。
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 6, 6, 1) 10 ================================================================= Total params: 10 Trainable params: 10 Non-trainable params: 0 _________________________________________________________________ [0.0, 0.0, 3.0, 3.0, 0.0, 0.0] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0]
现在,我们可以了解一些常用的池化方法,以及它们对输出要素地图的影响。
平均池化层
在二维特征地图上,通常在特征地图的2×2块中应用池化,步长为(2,2)。
平均池涉及计算要素地图的每个补丁的平均值。这意味着特征地图的每个2×2方块被下采样到方块中的平均值。
例如,上一节中的线路检测器卷积滤波器的输出是6×6特征图。我们可以手动查看如何将平均池操作应用于该功能地图的第一行。
输出要素映射的第一行(前两行六列)如下:
[0.0, 0.0, 3.0, 3.0, 0.0, 0.0] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0]
第一个池化操作按如下方式应用:
average(0.0, 0.0) = 0.0 0.0, 0.0
给定跨度为2的情况下,操作将沿两列向左移动,并计算平均值:
average(3.0, 3.0) = 3.0 3.0, 3.0
同样,操作向左移动两列,并计算平均值:
average(0.0, 0.0) = 0.0 0.0, 0.0
这就是池化操作的第一行。结果是平均池化操作的第一行:
[0.0, 3.0, 0.0]
给定(2,2)步幅,操作随后将向下移动两行并返回到第一列,过程继续。
由于下采样操作将每个维度减半,因此我们预计应用于6×6要素地图的合并输出将是新的3×3要素地图。在给定要素地图输入的水平对称性的情况下,我们预计每行具有相同的平均池值。因此,我们预计上一节检测到的线要素地图的结果平均池如下所示:
[0.0, 3.0, 0.0] [0.0, 3.0, 0.0] [0.0, 3.0, 0.0]
我们可以通过将上一节中的示例更新为使用平均池来确认这一点。
这可以通过使用AveragePooling2D层在Keras中实现。层的默认pool_size(例如,类似于内核大小或过滤器大小)是(2,2),并且默认跨度是None,这在本例中意味着使用pool_size作为跨度,它将是(2,2)。
# create model model = Sequential() model.add(Conv2D(1, (3,3), activation='relu', input_shape=(8, 8, 1))) model.add(AveragePooling2D())
下面列出了使用Average Pooling的完整示例。
# example of average pooling from numpy import asarray from keras.models import Sequential from keras.layers import Conv2D from keras.layers import AveragePooling2D # define input data data = [[0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0]] data = asarray(data) data = data.reshape(1, 8, 8, 1) # create model model = Sequential() model.add(Conv2D(1, (3,3), activation='relu', input_shape=(8, 8, 1))) model.add(AveragePooling2D()) # summarize model model.summary() # define a vertical line detector detector = [[[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]]] weights = [asarray(detector), asarray([0.0])] # store the weights in the model model.set_weights(weights) # apply filter to input data yhat = model.predict(data) # enumerate rows for r in range(yhat.shape[1]): # print each column in the row print([yhat[0,r,c,0] for c in range(yhat.shape[2])])
运行该示例首先对模型进行总结。
我们可以从模型摘要中看到,池化层的输入将是形状为(6,6)的单个要素地图,而平均池化层的输出将是单个要素地图,每个维度减半,形状为(3,3)。
应用平均合用会产生一个新的功能地图,它仍然会检测到线,尽管是以向下采样的方式,这与我们从手动计算操作中所期望的完全一样。
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 6, 6, 1) 10 _________________________________________________________________ average_pooling2d_1 (Average (None, 3, 3, 1) 0 ================================================================= Total params: 10 Trainable params: 10 Non-trainable params: 0 _________________________________________________________________ [0.0, 3.0, 0.0] [0.0, 3.0, 0.0] [0.0, 3.0, 0.0]
平均池化工作得很好,尽管使用最大池化更为常见。
最大池化层
最大池化或最大池化是计算每个要素地图的每个面片中的最大值或最大值的池化操作。
结果是向下采样或汇集的特征地图,它们突出显示修补程序中最当前的特征,而不是在平均汇集的情况下突出显示特征的平均存在。实践中发现,对于像图像分类这样的计算机视觉任务,这种方法比一般的池操作效果更好。
简而言之,原因在于,要素倾向于在要素地图(因此称为要素地图)的不同块上编码某个模式或概念的空间存在,并且查看不同要素的最大存在比它们的平均存在更具信息量。
我们可以通过将最大合并操作再次应用于线检测器卷积操作的输出特征图并手动计算合并特征图的第一行来使其具体化。
输出要素映射的第一行(前两行六列)如下:
[0.0, 0.0, 3.0, 3.0, 0.0, 0.0] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0]
第一个最大池化操作按如下方式应用:
max(0.0, 0.0) = 0.0 0.0, 0.0
给定跨度为2的情况下,操作将沿两列向左移动,并计算最大值:
max(3.0, 3.0) = 3.0 3.0, 3.0
同样,操作向左移动两列,并计算最大值:
max(0.0, 0.0) = 0.0 0.0, 0.0
这就是池化操作的第一行。
结果是最大池化操作的第一行:
[0.0, 3.0, 0.0]
同样,考虑到为池化提供的功能地图的水平对称性,我们期望池化的功能地图如下所示:
[0.0, 3.0, 0.0] [0.0, 3.0, 0.0] [0.0, 3.0, 0.0]
当使用平均池和最大池进行下采样时,所选线检测器图像和特征地图恰好产生相同的输出。
通过添加Keras API提供的MaxPooling2D层,可以将最大池化操作添加到工作示例中。
# create model model = Sequential() model.add(Conv2D(1, (3,3), activation='relu', input_shape=(8, 8, 1))) model.add(MaxPooling2D())
下面列出了使用最大值池进行垂直线检测的完整示例。
# example of max pooling from numpy import asarray from keras.models import Sequential from keras.layers import Conv2D from keras.layers import MaxPooling2D # define input data data = [[0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0]] data = asarray(data) data = data.reshape(1, 8, 8, 1) # create model model = Sequential() model.add(Conv2D(1, (3,3), activation='relu', input_shape=(8, 8, 1))) model.add(MaxPooling2D()) # summarize model model.summary() # define a vertical line detector detector = [[[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]]] weights = [asarray(detector), asarray([0.0])] # store the weights in the model model.set_weights(weights) # apply filter to input data yhat = model.predict(data) # enumerate rows for r in range(yhat.shape[1]): # print each column in the row print([yhat[0,r,c,0] for c in range(yhat.shape[2])])
运行该示例首先对模型进行总结。
正如我们现在可能预期的那样,我们可以看到,最大池化层的输出将是单个要素地图,每个维度减半,形状为(3,3)。
应用最大池化会产生一个新的功能地图,该地图虽然是以向下采样的方式,但仍能检测到该线。
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 6, 6, 1) 10 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 3, 3, 1) 0 ================================================================= Total params: 10 Trainable params: 10 Non-trainable params: 0 _________________________________________________________________ [0.0, 3.0, 0.0] [0.0, 3.0, 0.0] [0.0, 3.0, 0.0]
全局池化层
有时还会使用另一种类型的池,称为全局池。
全局向下汇集不是对输入要素地图的面片进行向下采样,而是将整个要素地图向下采样为单个值。这与将pool_size设置为输入要素地图的大小相同。
可以在模型中使用全局池来积极地总结图像中某个特征的存在。它有时也用于模型中,作为使用完全连接层从要素地图过渡到模型的输出预测的替代方案。
KERS分别通过GlobalAveragePooling2D和GlobalMaxPooling2D类支持全局平均池化和全局最大池化。
例如,我们可以将全局最大值合并添加到用于垂直线检测的卷积模型。
# create model model = Sequential() model.add(Conv2D(1, (3,3), activation='relu', input_shape=(8, 8, 1))) model.add(GlobalMaxPooling2D())
结果将是单个值,该值将总结输入图像中垂直线的最强激活或存在。
下面提供了完整的代码清单。
# example of using global max pooling from numpy import asarray from keras.models import Sequential from keras.layers import Conv2D from keras.layers import GlobalMaxPooling2D # define input data data = [[0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0]] data = asarray(data) data = data.reshape(1, 8, 8, 1) # create model model = Sequential() model.add(Conv2D(1, (3,3), activation='relu', input_shape=(8, 8, 1))) model.add(GlobalMaxPooling2D()) # summarize model model.summary() # # define a vertical line detector detector = [[[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]]] weights = [asarray(detector), asarray([0.0])] # store the weights in the model model.set_weights(weights) # apply filter to input data yhat = model.predict(data) # enumerate rows print(yhat)
运行该示例首先总结了模型。
我们可以看到,正如预期的那样,全局池层的输出是总结单个要素地图中存在的要素的单个值。
接下来,打印模型的输出,显示全局最大池对功能地图的影响,打印单个最大激活。
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 6, 6, 1) 10 _________________________________________________________________ global_max_pooling2d_1 (Glob (None, 1) 0 ================================================================= Total params: 10 Trainable params: 10 Non-trainable params: 0 _________________________________________________________________ [[3.]]
进一步阅读
如果你想深入了解,本节提供了更多关于该主题的资源。
文章
书籍
API接口
摘要
在本教程中,你了解了池操作的工作原理以及如何在卷积神经网络中实现它。
具体地说,你了解到:
- 要对要素地图中的要素检测进行下采样,需要进行池操作。
- 如何在卷积神经网络中计算和实现平均池和最大池。
- 如何在卷积神经网络中使用全局池。