前言
在卷积神经网络中使用全连接层时,需要将特征图转换成固定的尺寸以便连接到全连接层中。这种固定尺寸的需求会导致卷积神经网络对输入图片的大小和比例变化非常敏感,而无法适应各种尺寸和比例的输入图片。SPP层的作用就是针对这种问题提供一种解决方案,允许卷积神经网络能够适应任意大小的输入图片,从而更好地完成图像分类、目标检测等任务。
原理
SPP层的原理可以简单概括为将不同尺寸的网格子图进行金字塔式的池化,将每个子图都映射到一个固定长度的向量中,再将这些向量级联在一起,最终形成一个固定长度的特征向量。
具体来说,SPP层将输入的特征图分成多个不同大小的子区域,对每个子区域进行池化操作,得到一个固定长度的向量表示该子区域的特征。不同大小的子区域可以通过金字塔式的分割来实现,即将原始特征图先分成2×2的小块,然后分成4×4的块,再分成8×8的块,以此类推。每个子区域的池化方式可以采用最大值池化或平均值池化等方式。
最后,将每个子区域的特征向量级联在一起,就可以得到输入特征图的固定长度的特征表示。这种特征表示可以适用于不同大小和比例的输入图片,从而增强了卷积神经网络对输入图片的鲁棒性和泛化能力。
ini
复制代码
import torch import torch.nn as nn import math def spatial_pyramid_pool(previous_conv, num_sample, previous_conv_size, out_pool_size): for i in range(len(out_pool_size)): h_wid = int(math.ceil(previous_conv_size[0] / out_pool_size[i])) w_wid = int(math.ceil(previous_conv_size[1] / out_pool_size[i])) h_pad = (h_wid * out_pool_size[i] - previous_conv_size[0] + 1) // 2 w_pad = (w_wid * out_pool_size[i] - previous_conv_size[1] + 1) // 2 maxpool = nn.MaxPool2d((h_wid, w_wid), stride=(h_wid, w_wid), padding=(h_pad, w_pad)) x = maxpool(previous_conv) if (i == 0): spp = x.view(num_sample, -1) else: spp = torch.cat((spp, x.view(num_sample, -1)), 1) return spp
SPP与传统池化层
传统的池化层采用固定大小的滑动窗口进行池化,它们的池化大小和步长都是固定的,所以对于不同大小的输入图像,需要通过修改网络结构或调整输入图像的大小来适应。这种方法在处理不同尺寸的输入图像时存在一定的局限性。
与传统的池化层相比,SPP层则可以适应不同大小的输入图像,不需要修改网络结构或调整输入图像的大小。通过金字塔分割和多层池化,SPP层可以从输入特征图中提取更加丰富和复杂的特征信息,与传统的池化层相比,可以提高网络的表达能力和分类准确率。
此外,SPP层还可以提高网络的运行效率。传统的池化层需要对每个池化区域进行池化操作,这会导致池化层的运算量非常大,而SPP层只需要在每个子区域中进行一次池化操作,同时将不同层次的子区域的特征向量连接在一起,运算量大大降低,从而提高了网络的速度和效率。
SPP层与传统的池化层相比,具有更好的鲁棒性、更强的特征表达能力和更高的运算效率。这也是SPP层在目标检测、图像分类等任务中得到广泛应用的重要原因之一。
实验
ini
复制代码
if __name__ == "__main__": input = torch.randn(5, 3, 22, 82) conv = nn.Sequential( nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1), nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1), nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1), nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1) ) # 假设我们要使用 SPP,在第四个卷积层的输出上应用池化操作,输出池化尺寸为 [1, 2, 4] out_pool_size = [1, 2, 4] previous_conv = conv(input) num_sample = previous_conv.size(0) previous_conv_size = previous_conv.size()[2:4] spp = spatial_pyramid_pool(previous_conv, num_sample, previous_conv_size, out_pool_size) print(previous_conv.shape) print(spp.shape) # 现在我们可以将 SPP 的输出馈送给全连接层 fc = nn.Linear(spp.size(1), 10) out = fc(spp) print(out.shape)