pytorch 实战教程之 SPP(SPPNet---Spatial Pyramid Pooling)空间金字塔池化网络代码实现 和 SPPF (Spatial Pyramid Pooling Fast)详解​​

原文作者:aircraft

原文链接:pytorch 实战教程之 SPP(SPPNet---Spatial Pyramid Pooling)空间金字塔池化网络代码实现 和 SPPF (Spatial Pyramid Pooling Fast)详解​​

 

 

 

      学习YOLOv5前的准备就是学习DarkNet53网络,FPN特征金字塔网络,PANet路径聚合网络结构,(从SPP到SPPF)SPPF空间金字塔池化等。本篇讲从SPP到SPPF网络结构。。。(其他几篇已经发布在历史博客里------基本YOLO网络的前置学习就在这篇讲的差不多了,后面应该写YOLO目标检测了。。。。)

 

 

 

SPPNet(空间金字塔池化网络)详解​

SPPNet(Spatial Pyramid Pooling Network)是何凯明团队在2014年提出的创新架构,核心是​​空间金字塔池化(SPP)层​​,解决了传统卷积神经网络必须固定输入尺寸的限制。以下从技术原理到实践应用进行详解:

 

​一、SPPNet的诞生背景​

  1. ​传统CNN的痛点​​:

    • 全连接层需固定输入维度(如AlexNet强制缩放到227×227的图像大小)
    • 图像裁剪/变形导致信息丢失(如长宽比失真)---- 想象一下你的小仙女女朋友拍了很多美美哒的照片,这些照片的构图都不同,最后都要缩放到一个固定尺寸,那么每张照片还好看吗???
    • 多尺度特征难以有效融合
  2. ​SPP的核心突破​​:

    • 允许卷积层输出​​任意尺寸的特征图​
    • 通过SPP层将​​动态尺寸特征​​转换为​​固定维度向量​
    • 全连接层无需修改即可处理不同输入尺寸

 

二、空间金字塔池化(SPP)层原理​

1. ​​结构设计​

  • ​多级空间分箱(Spatial Bins)​​:
    • 预设金字塔层级:如 [4×4, 2×2, 1×1] 三级网格
    • 每级网格将特征图划分为n×n个子区域
  • ​自适应最大池化​​:
    • 每个子区域执行最大池化,输出1个值
    • 池化窗口尺寸自动计算:win_size = ⌈输入尺寸/n⌉
    • 步长(stride)自动计算:stride = ⌊输入尺寸/n⌋

2. ​​数学形式​

  • 输入特征图尺寸:W×H×C(宽×高×通道)
  • 第k级金字塔输出维度:n_k × n_k × C
  • 总输出维度:(Σ n_k²) × C(固定长度)

​示例​​:

  • 三级金字塔 [4×4, 2×2, 1×1]
  • 输出维度:(16 + 4 + 1) × C = 21C

 

 具体流程:

       1、特征图分割:SPP 层将输入的特征图分割成多个不同大小的网格,这些网格的大小通常是 1x1、2x2、4x4 、8x8 等等,形成一个金字塔结构,每个网格的大小决定了池化操作的感受野;

       2、池化操作:对每个网格进行池化操作,通常使用最大池化(Max Pooling);最大池化会选择每个网格中的最大值作为输出。这样,每个网格都会生成一个固定大小的特征表示,需要注意的是这个池化操作是一个并发的过程;

       3、特征拼接:将所有网格的特征表示拼接起来,形成一个固定长度的特征向量;这个特征向量随后可以作为后续全连接层的输入;

 

为什么很多带全连接层的网络结构都需要固定归一化图像的大小呢(如AlexNet强制缩放到227×227的图像大小)?举个例子就是我在全连接层输出的网络结构是固定大小的,比如是21*512这个大小:

 

在最后一层卷积层我们已经固定用了512个卷积核,那么输入到全连接层的数据就是 w * h * 512 (特征图像的长宽乘通道数),那么在通道数固定的情况下,图像大小变化的话就会和全连接层对应不上。接口对接不起来!!!

 

拿全连接层所需数据大小21*512举例在卷积层输出的通道数固定为512的情况下,我们构建1x1、2x2、4x4的三层空间金字塔池化:

 

假设我们有一张 448×448 的输入图像,通过卷积层后得到特征图 56×56×512;接下来,我们使用 SPP 层进行处理:

    1.特征图分割:
        1x1 网格:整个特征图作为一份数据
        2x2 网格:特征图被均匀分割成 4 份数据
        4x4 网格:特征图被均匀分割为16份数据

   2. 池化操作:
        1x1 网格:最大池化后的特征图尺寸为 1×1×512;
        2x2 网格:最大池化后的特征图尺寸为 2×2×512=4×512=4×512;
        4x4 网格:最大池化后的特征图尺寸为 4×4×512=16×512=16×512;

   3. 特征拼接:
    拼接后的特征向量长度为:21 x 512  =10752    不管你输入图像的大小是多少227x227 ,512x512,318x318也好,经过三层的空间金字塔网络后提取的特征数目都是21份,加上通道数就是 21x512份。这样就不用在意输入图像数据的大小了。

下图就非常鲜明了,厚度就是通道数:

 

 

 

大概代码实现(任意输入图像尺寸,输出的大小一致):

import torch
import torch.nn as nn

class SpatialPyramidPooling(nn.Module):
    def __init__(self, levels=[4, 2, 1]):
        super().__init__()
        self.levels = levels

    def forward(self, x):
        N, C, H, W = x.size()
        outputs = []
        for level in self.levels:
            kh = H // level
            kw = W // level
            pool = nn.MaxPool2d(kernel_size=(kh, kw), stride=(kh, kw))
            outputs.append(pool(x).view(N, -1))
        return torch.cat(outputs, dim=1)

# 使用示例
spp = SpatialPyramidPooling(levels=[4, 2, 1])
input = torch.randn(1, 256, 13, 13)  # 任意尺寸输入
output = spp(input)                   # 输出固定维度: (1, 21 * 256)

 

代码的隐藏条件​

代码​​隐式要求输入尺寸必须能被所有level整除​​,否则实际输出网格数会偏离预设level:

  • ​反例​​:输入10×10,level=4
    • 池化核:10//4=2,步长2×2
    • 输出尺寸:(10-2)/2 +1 =5 → ​​5×5​​(而非预设的4×4)

​若想不管这个隐藏条件,还可以这样改,使用自适应池化:

class SpatialPyramidPooling(nn.Module):
    def __init__(self, levels=[4, 2, 1]):
        super().__init__()
        self.pools = nn.ModuleList([
            nn.AdaptiveMaxPool2d((level, level)) for level in levels
        ])

    def forward(self, x):
        N, C, _, _ = x.size()
        outputs = [pool(x).view(N, -1) for pool in self.pools]
        return torch.cat(outputs, dim=1)

AdaptiveMaxPool2d直接强制输出目标尺寸(如4×4),无需计算核尺寸

 

 

局限性​

  1. ​池化信息损失​​:

    • 最大池化丢弃非最大值信息
    • 后续的ROI Align改用双线性插值缓解此问题
  2. ​计算资源消耗​​:

    • 多级池化增加内存占用
    • 实时性弱于纯全卷积网络(FCN)

总结​

SPPNet通过空间金字塔池化,首次实现了CNN对任意尺寸输入的处理,奠定了多尺度特征融合的基础。其核心思想被后续众多模型(如Fast R-CNN、Mask R-CNN)继承发展,是深度学习发展史上的重要里程碑。理解SPP机制对掌握现代目标检测和图像分类模型至关重要。

 

 

 

 

SPPF(Spatial Pyramid Pooling Fast)详解​

SPPF(空间金字塔快速池化)是SPPNet的改进版本,由YOLO系列(如YOLOv5、YOLOv7)引入,旨在保持多尺度特征融合能力的同时显著提升计算效率。其核心思想是通过​​串行重复池化操作​​替代传统金字塔并行池化,减少计算冗余。以下是详细解析:

 

 

 

 

​一、SPPF与SPP的结构对比​

模块池化方式计算路径参数量输出特征维度
SPP 并行多级池化(如4×4, 2×2, 1×1) 独立分支 多级特征拼接
SPPF 串行重复池化(如三次5×5池化) 单链叠加 等效金字塔融合

 

 

特性SPPSPPF
多尺度特征来源 离散分级(4×4, 2×2, 1×1) 连续叠加(5→9→13感受野)
计算效率 低(多分支并行池化) 高(单链串行池化,GPU优化)
硬件友好性 内存碎片化 连续内存操作
适用场景 分类网络(需全连接层) 检测网络(全卷积架构)

 

二、SPPF核心原理​

1. ​​串行池化设计​
  • ​池化核尺寸固定​​:通常使用5×5池化窗口,重复三次
  • ​步长固定为1​​:通过填充(padding)保持特征图尺寸不变
  • ​动态感受野叠加​​:每次池化扩大感受野,等效多尺度特征提取
2. ​​数学过程​

输入特征图尺寸:W×H×C

  • ​第一次池化​​:5×5池化 → 输出 W×H×C
  • ​第二次池化​​:5×5池化 → 输出 W×H×C
  • ​第三次池化​​:5×5池化 → 输出 W×H×C
  • ​特征拼接​​:原始输入 + 三次池化输出 → 最终维度 4C    而SPPF中的W×H始终不变,SPP则是通道数和金字塔后输出的数据不变

​等效感受野​​:

  • 三次5×5池化 ≈ 单次13×13池化(5+(5-1)x2 ) = 13
3. ​​计算效率优化​
  • ​参数共享​​:三次池化使用相同核尺寸,无需额外参数
  • ​内存连续性​​:串行操作减少内存碎片,提升GPU利用率
  • ​FLOPs对比​​(以256×13×13输入为例):
    • SPP:约 1.2 GFLOPs
    • SPPF:约 0.8 GFLOPs (​​速度提升33%​​)

 

具体示例演示​

​Case 1:输入尺寸 13×13×256​

  • ​第一次池化​​:

    • 输入:13×13×256
    • 输出:13×13×256(保持尺寸)
    • ​感受野​​:5×5(覆盖输入中5×5区域)
  • ​第二次池化​​:

    • 输入:13×13×256
    • 输出:13×13×256
    • ​感受野​​:9×9(叠加两次5×5池化)
  • ​第三次池化​​:

    • 输入:13×13×256
    • 输出:13×13×256
    • ​感受野​​:13×13(叠加三次池化覆盖全图)
  • ​拼接结果​​:

    • 输出尺寸:13×13×(256×4) = ​​13×13×1024​

 

虽然SPPF输出的空间尺寸 (W×H) 仍与输入相关,但后续网络通过 ​​全局池化(Global Pooling)​​ 或 ​​自适应层(Adaptive Layers)​​ 将其转换为固定维度

 

 

SPPF中的感受野计算示例​

以YOLOv5的SPPF模块为例(三次5×5池化,步长=1):

层数操作核尺寸(k)步长(stride)感受野计算累计感受野
1 输入 - - 初始感受野=1 1×1
2 第一次池化 5×5 1 1 + (5-1)*1 = ​​5​ 5×5
3 第二次池化 5×5 1 5 + (5-1)*1 * 1 = ​​9​ 9×9
4 第三次池化 5×5 1 9 + (5-1)*1 * 1 * 1 = ​​13​ 13×13

 

 

 

  • 不强制固定维度​​:保留空间信息以支持目标检测中的位置敏感任务。
  • ​与后续卷积层兼容​​:YOLO的检测头(Head)直接处理可变尺寸特征图。

 

 

  • 错误理解​​:将SPPF用于分类任务(需全连接层)时,才需要额外添加全局池化。
  • ​正确场景​​:在YOLO等检测网络中,SPPF天然适配全卷积结构,​​无需任何后续约束​

 

 

YOLO中SPPF的实际应用​​:

 

在YOLOv5/v7中,SPPF模块后​​直接连接卷积层​​,无需全局池化:

 

# YOLOv5的C3模块(包含SPPF)
class C3(nn.Module):
    def __init__(self, c1, c2):
        super().__init__()
        self.sppf = SPPF(c1)            # 输出H×W×4C1
        self.conv = nn.Conv2d(4*c1, c2, 1)  # 压缩通道到C2

    def forward(self, x):
        x = self.sppf(x)     # H×W×4C1 → 空间维度保留
        x = self.conv(x)     # H×W×C2 → 输入检测头
        return x

 

 

 

 

 

三、SPPF的具体实现​

1. ​​PyTorch代码示例​:
import torch
import torch.nn as nn

class SPPF(nn.Module):
    def __init__(self, in_channels):
        super().__init__()
        self.pool = nn.MaxPool2d(kernel_size=5, stride=1, padding=2)
    
    def forward(self, x):
        x1 = self.pool(x)
        x2 = self.pool(x1)
        x3 = self.pool(x2)
        return torch.cat([x, x1, x2, x3], dim=1) 

# 输入示例:batch=1, channels=256, size=13x13
input = torch.randn(1, 256, 13, 13)
sppf = SPPF(256)
output = sppf(input)  # 输出维度:[1, 1024, 13, 13]

 

2. ​​关键参数配置​
  • ​核尺寸​​:通常为5×5(平衡感受野与计算量)
  • ​填充策略​​:padding=kernel_size//2(保持尺寸不变)
  • ​拼接方式​​:沿通道维度拼接原始输入与池化结果

 

四、SPPF的优势分析​

  1. ​计算效率提升​​:

    • 减少分支操作,利用串行流水线加速
    • YOLOv5中SPPF比SPP快2.5倍(相同硬件)
  2. ​多尺度特征融合增强​​:

    • 三次池化等效于13×13、9×9、5×5多级感受野
    • 保留更精细的局部特征(对比SPP的跳跃式分级)
  3. ​硬件友好性​​:

    • 连续内存访问优化缓存命中率
    • 适合部署到边缘设备(如Jetson系列)
  4. ​模型兼容性​​:

    • 输入输出尺寸一致,可直接替换SPP模块
    • 无缝集成到ResNet、CSPNet等主流骨干网络

 

五、SPPF在YOLO中的实际应用​

1. ​​YOLOv5网络结构集成​
Backbone
├── Focus
├── Conv
├── C3
└── SPPF  # 替换原始SPP模块

 

2. ​​性能提升对比(COCO数据集)​
模型mAP@0.5FPS (Tesla T4)参数量 (M)
YOLOv5s 37.2 125 7.2
YOLOv5s+SPPF ​37.8​ ​142​ 7.3

 

 

​六、SPPF的局限性​

  1. ​小物体检测精度衰减​​:

    • 多次池化可能模糊微小物体的细节特征
    • 需配合FPN(特征金字塔)使用以缓解此问题
  2. ​理论感受野与实际差异​​:

    • 串行池化的等效感受野为线性增长,弱于SPP的指数级覆盖
  3. ​通道膨胀问题​​:

    • 输出通道数变为输入4倍,可能增加后续卷积计算量

 

七、改进变体​(下面这些都是不断改进升级的版本)

  1. ​SPPF+SE​​:

    • 加入通道注意力机制(Squeeze-and-Excitation)
    • 提升重要通道的权重分配
  2. ​ASPPF(Atrous SPPF)​​:

    • 使用空洞池化(Dilated Pooling)扩大感受野
    • 减少下采样带来的信息损失
  3. ​轻量化SPPF​​:

    • 采用深度可分离卷积(Depthwise Separable Conv)
    • 适用于移动端部署

还有SimSPPF,SPPCSPC,SPPFCSPC+都可以去了解一下都是YOLO不断升级中改进出来的。

 

八、总结​

SPPF通过巧妙的串行池化结构,在保持多尺度特征融合能力的同时,显著提升了计算效率。其设计体现了“​​简单即有效​​”的优化哲学,成为实时目标检测模型的标配模块。理解SPPF的工作原理对于优化模型速度和精度平衡至关重要,特别是在边缘计算和实时视频分析场景中具有重要价值。

 

 

 

参考博客:https://blog.csdn.net/CITY_OF_MO_GY/article/details/143897303

        https://developer.aliyun.com/article/1509544

        https://blog.csdn.net/weixin_38346042/article/details/131796263

 

From:https://www.cnblogs.com/DOMLX/p/18832576
aircraft
100+评论
captcha