Pooling 运算是深度学习中比较常见的类型,主要作用是保留部分特征,使维度降低。看到一些文章中经常将 Pooling 翻译为“池化”,总感觉词不达意。将 ThreadPool 翻译为“线程池”是比较容易理解的,但 Pooling 应该类比 Convolving,是个动名词,笔者认为不应该翻译成“池”,通过查阅牛津词典 Pool 的释义,其中“ 采集、抽取 ”更符合此处的物理意义。为了避免阅读障碍,本文后面仍写作 “Pooling 运算”。
Pooling 根据计算方式可大体分为如下几类。
01
—
Max Pooling
Max Pooling 运算过程可以见下图右侧效果。
上图中每种颜色代表不同窗口,计算所需的超参数为窗口尺寸 kernel_size = 2,输出间隔 stride = 2。
前向计算过程如下:首先计算特征图在每个窗口的最大值,将该值抽取出来,与其他窗口抽取结果按顺序组成新的特征图。
Pooling 运算在进行反向传播时需要将新特征图与原特征图对应位置记录下来,这样梯度可以沿每个窗口最大值的位置传递给前面一层。
当窗口尺寸大于步长(kernel_size > stride)时,称为 over-lapped pooling,相邻两个窗口会共用一部分原特征图。
在 TensorFlow 中调用 tf.nn.max_pool 实现 Max Pooling 运算。kernel_size、padding 与输入输出尺寸关系参考之前文章《TensorFlow 卷积 padding 策略详解》。
经典模型如 LeNet-5,AlexNet,VGG 等都使用了 Max Pooling。
02
—
Average Pooling
与 Max Pooling 类似,只是将每个窗口计算最大值改为计算平均值。反向传播时,梯度会平均分配到前一层窗口中每个元素。
Average Pooling 计算上等效于一个权重为常数 1/(k*k) 的 Depthwise Conv(k 为 kernel_size),其中二者 kernel_size 和 stride 保持一致。
在 TensorFlow 中调用 tf.nn.avg_pool 实现 Average Pooling 运算。kernel_size、padding 与输入输出尺寸关系参考之前文章《TensorFlow 卷积 padding 策略详解》。
经典模型如 Inception-V2 部分 Pooling 使用了 Average Pooling。如下表所示,最后一列标记为 “avg + xx” 的 inception module 使用了 Average Pooling。
03
—
Global Average Pooling
Global Average Pooling 简称 GAP,是一类特殊的 Average Pooling 运算,其输出空间维度始终等于 1x1,即最大程度降低空间维度,从而大幅缩减后面全连接层的权重数目。
以 AlexNet 为例,fc6 的输入特征维度为 256x6x6 = 9216,输出特征维度为 4096,需要的权重数量为 9216x4096 = 37,748,736 个,占整个模型全部权重数量的 62%!如果在 fc6 前先使用 GAP 做降维,这样输入特征维度变为 256x1x1 = 256,需要权重数量为 256x4096 = 1,048,576,比原始模型,fc6 单层权重压缩为原始模型的 1/36,模型总体压缩为原先的 40%。
在 TensorFlow 中调用 tf.nn.avg_pool 实现 GAP 运算,需要将 kernel_size 设置与 input height/width 相同,stride 设置为 1,padding 设置为 'VALID'。
GAP 运算最早出现在论文《Network in Network》,后来成为分类网络卷积层与用于分类的全连接层之间的标准配置,著名的 SqeezeNet、GoogLeNet、ResNet 中均能看到其身影。
04
—
ROI Pooling
在 R-CNN 系列目标检测方法中会用到一类特殊的 Max Pooling 计算,将特征图中位于感兴趣区域(Region of Interest, ROI)的一部分抽取出来,经过 Max Pooling 计算后输出空间维度固定为 HxW(例如 7x7)。实现时将 ROI 均匀分割为 HxW 个小窗口,每个小窗口计算最大值作为输出特征。反向传播时梯度需要根据每个窗口最大值位置传递回上一层。
如何判断一张特征图中哪些位置属于感兴趣区域?这依赖 Region Proposal 结果,原始图片中可能包含待检测目标的若干备选区域 bbox,将 bbox 投影到特征图(特征图尺寸小于原始图片,bbox 需要等比例缩小)对应位置即 ROI。
固定维度特征对后面分类、回归全连接层很重要,因为全连接层的输入维度必须是不变的。
ROI Pooling 最早是在论文《Fast R-CNN》中提出的,在后续论文《Faster R-CNN》中继续发挥余热。实际上 ROI Pooling 是更早论文《Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition》提出的 Spatial Pyramid Pooling (简称 SPP)简化形式。
SPP 同时抽取多组不同输出维度的特征,拼接为固定尺寸特征向量,然后送入全连接层做分类、回归,架构如下图所示。
TensorFlow 中没有直接支持 ROI Pooling,在 object detection api 中使用 tf.nn.max_pool 配合特定参数实现,其中 kernel_h = stride_h = roi_h/H, kernel_w = stride_w = roi_w/W。《TensorFlow 上基于 Faster RCNN 的目标检测》这篇文章中用到的代码通过编写自定义 OP 实现了 ROI Pooling,感兴趣的童鞋可以深入阅读,这里不再贴出。
05
—
PSROI Pooling
在 R-FCN 目标检测网络中出现了 Position Sensitive ROI Pooling (简称 PSROI Pooling)运算,可以看作 ROI Pooling 的进化。
PSROI Pooling 中,抽取得到的输出特征图不同位置元素来自输入特征图的不同 channel,以上图为例,输出特征图空间维度为 kxk,每个单元对应输入特征图的特定 channel(颜色相同即表示一一对应关系)。下面两张图更清楚地看到这个过程。
假设输出特征图尺寸为 3x3,那么 ROI 均匀分割为 3x3 个区域,左上角的元素完全由输入特征图的第一个 channel ROI 左上角区域计算得到,R-FCN 论文中均使用 Average 归约,但同时也表明 Max 归约也是可以的。
理解了上述过程,在 TensorFlow 中实现 PSROI Pooling 就比较简单了,先对输入特征图做 ROI Pooling 计算,然后每个 channel 提取对应的输出特征图位置元素,组合成新的特征图即可。
小结
—
Pooling 运算变化多端,但万变不离其宗,总能转换为 max_pool 或 avg_pool 这两种最基本的计算。如果你在项目中用到过更奇葩的 Pooling,欢迎留言交流。
