Run this notebook online:\ |Binder| or Colab: |Colab| .. |Binder| image:: https://mybinder.org/badge_logo.svg :target: https://mybinder.org/v2/gh/deepjavalibrary/d2l-java/master?filepath=chapter_convolutional-neural-networks/pooling.ipynb .. |Colab| image:: https://colab.research.google.com/assets/colab-badge.svg :target: https://colab.research.google.com/github/deepjavalibrary/d2l-java/blob/colab/chapter_convolutional-neural-networks/pooling.ipynb .. _sec_pooling: 池化 ==== 通常,当我们处理图像时,我们希望逐渐 降低隐藏表示的空间分辨率, 聚合信息以便 我们在网络中的地位越高, 感受野越大(在输入端) 每个隐藏节点都对其敏感。 通常,我们的最终任务会问一些关于形象的全球性问题, e.g.,\ *里面有猫吗* 所以最后一层的节点通常是敏感的 到整个输入。 通过逐渐聚合信息,生成越来越粗糙的地图, 我们最终实现了学习全球代表性的目标, 同时在处理的中间层保留卷积层的所有优点。 此外,在检测较低级别的特征时,例如边缘 (如 :numref:`sec_conv_layer`\ )中所述, 我们经常希望我们的表达在某种程度上对翻译保持不变。 例如,如果我们拍摄图像 ``X`` 黑与白之间有着清晰的界限 将整个图像向右移动一个像素, 例如,\ ``Z[i, j] = X[i, j+1]`` , 那么新图像 ``Z`` 的输出可能会有很大的不同。 边缘将移动一个像素,并随之进行所有激活。 实际上,物体几乎不会完全出现在同一个地方。 事实上,即使有三脚架和固定物体, 快门移动导致相机振动 可能会让所有东西都偏移一个像素左右 (高端摄像头配备了特殊功能来解决这个问题)。 本节介绍池层, 有两个目的 降低卷积层对位置的敏感性 以及空间下采样表示。 最大池和平均池 -------------- 像卷积层,池操作符 由一个固定形状的滑动窗口组成 根据步幅输入的所有区域, 为每个经过的位置计算单个输出 通过固定形状窗口(有时称为\ *池窗口*\ )。 然而,与互相关计算不同 卷积层中的输入和内核, 池层不包含任何参数(没有\ *过滤器*\ )。 相反,池运算符是确定性的, 通常计算最大值或平均值 池窗口中的元素的。 这些操作称为\ *最大池*\ (\ *max pooling*\ 简称) 和\ *平均池*\ 。 在这两种情况下,与互相关算子一样, 我们可以考虑池窗口 从输入数组的左上角开始 从左到右,从上到下滑动输入数组。 在池窗口命中的每个位置, 它计算最大值或平均值 窗口中输入子数组的值 (取决于使用的是\ *最大*\ 还是\ *平均*\ 池)。 .. _fig_pooling: .. figure:: https://raw.githubusercontent.com/d2l-ai/d2l-en/master/img/pooling.svg Maximum pooling with a pooling window shape of :math:`2\times 2`. The shaded portions represent the first output element and the input element used for its computation: :math:`\max(0, 1, 3, 4)=4` 上面的:numref:\ ``fig_pooling``\ 中的输出数组的高度为2,宽度为2。 这四个元素来自 :math:`\text{max}`\ 的最大值: .. math:: \max(0, 1, 3, 4)=4,\\ \max(1, 2, 4, 5)=5,\\ \max(3, 4, 6, 7)=7,\\ \max(4, 5, 7, 8)=8.\\ 池窗口形状为 :math:`p \times q` 的池层 被称为\ :math:`p \times q` 池层。 池操作称为 :math:`p \times q` 池。 让我们回到物体边缘检测的例子 在本节开头提到。 现在我们将使用卷积层的输出 作为 :math:`2\times 2` 最大池的输入。 将卷积层输入设置为 ``X`` ,池层输出设置为 ``Y``\ 。无论 ``X[i, j]`` 和 ``X[i, j+1]`` 的值是否不同, 或者 ``X[i, j+1]`` 和 ``X[i, j+2]`` 是不同的, 池层输出都包括 ``Y[i, j]=1``\ 。 也就是说,使用 :math:`2\times 2` 最大池层, 我们仍然可以检测出卷积层是否识别出模式 在高度和宽度上最多移动一个元素。 在下面的代码中,我们实现了正向计算 ``pool2d`` 函数中池层的。 此函数类似于 ``corr2d`` 函数 in:numref:\ ``sec_conv_layer``\ 。 然而,在这里,我们没有计算输出的内核 作为输入中每个区域的最大值或平均值。 .. code:: java %load ../utils/djl-imports .. code:: java NDManager manager = NDManager.newBaseManager(); public NDArray pool2d(NDArray X, Shape poolShape, String mode){ long poolHeight = poolShape.get(0); long poolWidth = poolShape.get(1); NDArray Y = manager.zeros(new Shape(X.getShape().get(0) - poolHeight + 1, X.getShape().get(1) - poolWidth + 1)); for(int i=0; i < Y.getShape().get(0); i++){ for(int j=0; j < Y.getShape().get(1); j++){ if("max".equals(mode)){ Y.set(new NDIndex(i+","+j), X.get(new NDIndex(i + ":" + (i + poolHeight) + ", " + j + ":" + (j + poolWidth))).max()); } else if("avg".equals(mode)){ Y.set(new NDIndex(i+","+j), X.get(new NDIndex(i + ":" + (i + poolHeight) + ", " + j + ":" + (j + poolWidth))).mean()); } } } return Y; } 我们可以在上图中构造输入数组 ``X`` ,以验证二维最大池层的输出。 .. code:: java NDArray X = manager.arange(9f).reshape(3,3); pool2d(X, new Shape(2,2), "max"); .. parsed-literal:: :class: output ND: (2, 2) gpu(0) float32 [[4., 5.], [7., 8.], ] 同时,我们对平均池层进行了实验。 .. code:: java pool2d(X, new Shape(2,2), "avg"); .. parsed-literal:: :class: output ND: (2, 2) gpu(0) float32 [[2., 3.], [5., 6.], ] 填充和跨步 ---------- 与卷积层一样,池层 还可以更改输出形状。 和以前一样,我们可以改变操作以获得所需的输出形状 通过填充输入和调整步幅。 我们可以演示填充和跨步的使用 通过二维最大池层 ``maxPool2dBlock``\ 在池层中 在DJL的 ``Pool`` 模块中发货。 我们首先构造一个形状为 ``(1, 1, 4, 4)``\ 的输入数据, 其中前两个维度是批次和通道。 .. code:: java X = manager.arange(16f).reshape(1, 1, 4, 4); X .. parsed-literal:: :class: output ND: (1, 1, 4, 4) gpu(0) float32 [[[[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.], ], ], ] 下面,我们使用一个形状为 ``(3, 3)`` 的池窗口, 步幅形状为 ``(3, 3)`` .. code:: java // 定义块指定内核和步幅 Block block = Pool.maxPool2dBlock(new Shape(3, 3), new Shape(3, 3)); block.initialize(manager, DataType.FLOAT32, new Shape(1,1,4,4)); ParameterStore parameterStore = new ParameterStore(manager, false); // 因为池层中没有模型参数,所以我们不需要 // 调用参数初始化函数 block.forward(parameterStore, new NDList(X), true).singletonOrThrow(); .. parsed-literal:: :class: output ND: (1, 1, 1, 1) gpu(0) float32 [[[[10.], ], ], ] 步幅和填充可以手动指定。 .. code:: java // 重新定义内核形状、跨步形状和垫块形状 block = Pool.maxPool2dBlock(new Shape(3,3), new Shape(2,2), new Shape(1,1)); // block forward 方法 block.forward(parameterStore, new NDList(X), true).singletonOrThrow(); .. parsed-literal:: :class: output ND: (1, 1, 2, 2) gpu(0) float32 [[[[ 5., 7.], [13., 15.], ], ], ] 当然,我们可以指定一个任意的矩形池窗口 并分别为高度和宽度指定填充和跨步。 .. code:: java // 重新定义内核形状、跨步形状和垫块形状 block = Pool.maxPool2dBlock(new Shape(2,3), new Shape(2,3), new Shape(1,2)); block.forward(parameterStore, new NDList(X), true).singletonOrThrow(); .. parsed-literal:: :class: output ND: (1, 1, 3, 2) gpu(0) float32 [[[[ 0., 3.], [ 8., 11.], [12., 15.], ], ], ] 多通道 ------ 在处理多通道输入数据时, 池化层分别池化每个输入通道, 而不是一个通道一个通道地添加每个通道的输入 就像在卷积层中一样。 这意味着池层的输出通道数 与输入通道的数量相同。 下面,我们将把数组 ``X`` 和 ``X+1``\ 连接起来 在通道维度上,使用2个通道构造输入。 .. code:: java X = X.concat(X.add(1), 1); X .. parsed-literal:: :class: output ND: (1, 2, 4, 4) gpu(0) float32 [[[[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.], ], [[ 1., 2., 3., 4.], [ 5., 6., 7., 8.], [ 9., 10., 11., 12.], [13., 14., 15., 16.], ], ], ] 正如我们所看到的,在合并后,输出通道的数量仍然是2。 .. code:: java block = Pool.maxPool2dBlock(new Shape(3,3), new Shape(2,2), new Shape(1,1)); block.forward(parameterStore, new NDList(X), true).singletonOrThrow(); .. parsed-literal:: :class: output ND: (1, 2, 2, 2) gpu(0) float32 [[[[ 5., 7.], [13., 15.], ], [[ 6., 8.], [14., 16.], ], ], ] 总结 ---- - 对于池窗口中的输入元素,最大池操作将最大值指定为输出,平均池操作将平均值指定为输出。 - 池层的主要功能之一是减轻卷积层对位置的过度敏感性。 - 我们可以为池层指定填充和跨步。 - 最大池,再加上大于1的步幅,可以用来降低分辨率。 - 池层的输出通道数与输入通道数相同。 练习 ---- 1. 你能把平均池作为卷积层的一个特例来实现吗?如果是这样,那就去做。 2. 你能把最大池作为卷积层的特例来实现吗?如果是这样,那就去做。 3. 池层的计算成本是多少?假设池层的输入大小为 :math:`c\times h\times w`\ ,池窗口的形状为 :math:`p_h\times p_w` ,填充为 :math:`(p_h, p_w)` ,跨步为\ :math:`(s_h, s_w)` 。 4. 为什么您希望最大池和平均池的工作方式有所不同? 5. 我们需要一个单独的最小池层吗?你能换个运算吗? 6. 是否可以考虑平均和最大池之间的另一种操作(提示:recall the softmax)?为什么不那么受欢迎?