# 深度学习入门之池化层
池化层是当前卷积神经网络中常用组件之一,它最早见于`LeNet`[^1]一文,称之为`Subsample`。自`AlexNet`[^2]之后采用Pooling命名。池化层是模仿人的视觉系统对数据进行降维,用更高层次的特征表示图像。
实施池化的目的:(1) 降低信息冗余;(2) 提升模型的尺度不变性、旋转不变性;(3) 防止过拟合。
池化层的常见操作包含以下几种:最大值池化,均值池化,随机池化,中值池化,组合池化等。
## 最大值池化
最大值池化是最常见、也是用的最多的池化操作。最大值池化的核心代码可以描述为:
```c++
// 摘选自caffe并稍加修改.
top_data = -FLT_MAX;
for (int h = hstart; h < hend; ++h) {
for (int w = wstart; w < wend; ++w) {
const int index = h * width_ + w;
if (bottom_data[index] > top_data[pool_index]) {
top_data = bottom_data[index];
}
}
}
```
在前向过程,选择图像区域中的最大值作为该区域池化后的值;在后向过程中,梯度通过前向过程时的最大值反向传播,其他位置的梯度为0.
在实际应用时,最大值池化又分为:重叠池化与非重叠池化。如`AlexNet/GoogLeNet`系列中采用的重叠池化,`VGG`中采用的非重叠池化。但是,自`ResNet`之后,池化层在分类网络中应用逐渐变少,往往采用`stride=2`的卷积替代最大值池化层。

最大值池化的优点在于它能学习到图像的边缘和纹理结构。
## 均值池化
均值池化的核心代码可以描述如下:
```c++
// 摘选自caffe并稍加修改.
top_data = 0;
for (int h = hstart; h < hend; ++h) {
for (int w = wstart; w < wend; ++w) {
top_data[ph * pooled_width_ + pw] += bottom_data[h * width_ + w];
}
}
top_data[ph * pooled_width_ + pw] /= pool_size;
```
在前向传播过程中,计算图像区域中的均值作为该区域池化后的值;在反向传播过程中,梯度特征分均配到各个位置。
在实际应用中,均值池化往往以全局均值池化的形式出现。常见于SE模块以及分类模块中。极少见于作为下采样模块用于分类网络中。

均值池化的优点在于可以减小估计均值的偏移,提升模型的鲁棒性。
## 随机池化
随机池化是ICLR2013的一篇论文[Stochastic Pooling](https://arxiv.org/abs/1301.3557),提出的一种池化策略,另有CVPR2017的一篇论文[S3Pool](https://arxiv.org/abs/1611.05138)提出一种随机位置池化策略。
随机池化的方法非常简单,只需对特征区域元素按照其概率值大小随机选择,元素值大的被选中的概率也大。随机位置池化则集成了随机池化与最大值池化两者。示例图如下:

## 中值池化
中值池化是参考图像处理中的中值滤波而引申的一种池化方式。在目前CNN架构中极为少见,仅发现一篇论文:[基于卷积神经网络和中值池化的人脸识别](http://www.doc88.com/p-3397641011170.html),不确定是否为水文。
在前向与反向传播过程中,中值池化类似于最大值池化,故不再赘述。
中值池化同样具有学习边缘和纹理结构的特性,同时具有抗噪性。代码描述参考:
```python
# 代码摘自开源项目:pytorch-image-models
class MedianPool2d(nn.Module):
""" Median pool (usable as median filter when stride=1) module.
Args:
kernel_size: size of pooling kernel, int or 2-tuple
stride: pool stride, int or 2-tuple
padding: pool padding, int or 4-tuple (l, r, t, b) as in pytorch F.pad
same: override padding and enforce same padding, boolean
"""
def __init__(self, kernel_size=3, stride=1, padding=0, same=False):
super(MedianPool2d, self).__init__()
self.k = _pair(kernel_size)
self.stride = _pair(stride)
self.padding = _quadruple(padding) # convert to l, r, t, b
self.same = same
def _padding(self, x):
if self.same:
ih, iw = x.size()[2:]
if ih % self.stride[0] == 0:
ph = max(self.k[0] - self.stride[0], 0)
else:
ph = max(self.k[0] - (ih % self.stride[0]), 0)
if iw % self.stride[1] == 0:
pw = max(self.k[1] - self.stride[1], 0)
else:
pw = max(self.k[1] - (iw % self.stride[1]), 0)
pl = pw // 2
pr = pw - pl
pt = ph // 2
pb = ph - pt
padding = (pl, pr, pt, pb)
else:
padding = self.padding
return padding
def forward(self, x):
x = F.pad(x, self._padding(x), mode='reflect')
x = x.unfold(2, self.k[0], self.stride[0]).unfold(3, self.k[1], self.stride[1])
x = x.contiguous().view(x.size()[:4] + (-1,)).median(dim=-1)[0]
return x
```
## 分数阶最大值池化
分数阶最大值池化(Fractional Max Pooling)见诸于[ARXIV](https://arxiv.org/pdf/1412.6071.pdf)。本文查阅Pytorch代码时发现的,先前未曾了解过,也未曾用过。感兴趣者可以参见原文或者用pytorch代码试玩几把,这里提供一个pytorch试玩deme:
```python
import torch
import torch.nn as nn
inputs = torch.rand(20, 16, 50, 32)
fmp = nn.FractionalMaxPool2d(3, output_ratio=(0.8, 0.8))
output = fmp(inputs)
print(output.size())
# 此时output的尺寸为:20X16X40X25.
```
摘自论文的分数阶池化效果图。

## 组合池化
组合池化则是同时利用最大值池化与均值池化两种的优势而引申的一种池化策略。常见组合策略有两种:Cat与Add。其代码描述如下:
```python
def add_avgmax_pool2d(x, output_size=1):
x_avg = F.adaptive_avg_pool2d(x, output_size)
x_max = F.adaptive_max_pool2d(x, output_size)
return 0.5 * (x_avg + x_max)
def cat_avgmax_pool2d(x, output_size=1):
x_avg = F.adaptive_avg_pool2d(x, output_size)
x_max = F.adaptive_max_pool2d(x, output_size)
return torch.cat([x_avg, x_max], 1)
```
---
[^1]: LeNet: Gradient based Learning Applied to Document Recognition.
[^2]: AlexNet: ImageNet Classification with Deep Convolutional Nerural Networks.