原文作者:aircraft
学习YOLOv5前的准备就是学习DarkNet53网络,FPN特征金字塔网络,PANet路径聚合网络结构,(从SPP到SPPF)SPPF空间金字塔池化等。本篇讲从SPP到SPPF网络结构。。。(其他几篇已经发布在历史博客里------基本YOLO网络的前置学习就在这篇讲的差不多了,后面应该写YOLO目标检测了。。。。)
SPPNet(空间金字塔池化网络)详解
SPPNet(Spatial Pyramid Pooling Network)是何凯明团队在2014年提出的创新架构,核心是空间金字塔池化(SPP)层,解决了传统卷积神经网络必须固定输入尺寸的限制。以下从技术原理到实践应用进行详解:
一、SPPNet的诞生背景
-
传统CNN的痛点:
- 全连接层需固定输入维度(如AlexNet强制缩放到227×227的图像大小)
- 图像裁剪/变形导致信息丢失(如长宽比失真)---- 想象一下你的小仙女女朋友拍了很多美美哒的照片,这些照片的构图都不同,最后都要缩放到一个固定尺寸,那么每张照片还好看吗???
- 多尺度特征难以有效融合
-
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),无需计算核尺寸
局限性
-
池化信息损失:
- 最大池化丢弃非最大值信息
- 后续的ROI Align改用双线性插值缓解此问题
-
计算资源消耗:
- 多级池化增加内存占用
- 实时性弱于纯全卷积网络(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池化) | 单链叠加 | 少 | 等效金字塔融合 |
特性 | SPP | SPPF |
---|---|---|
多尺度特征来源 | 离散分级(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的优势分析
-
计算效率提升:
- 减少分支操作,利用串行流水线加速
- YOLOv5中SPPF比SPP快2.5倍(相同硬件)
-
多尺度特征融合增强:
- 三次池化等效于13×13、9×9、5×5多级感受野
- 保留更精细的局部特征(对比SPP的跳跃式分级)
-
硬件友好性:
- 连续内存访问优化缓存命中率
- 适合部署到边缘设备(如Jetson系列)
-
模型兼容性:
- 输入输出尺寸一致,可直接替换SPP模块
- 无缝集成到ResNet、CSPNet等主流骨干网络
五、SPPF在YOLO中的实际应用
1. YOLOv5网络结构集成
Backbone
├── Focus
├── Conv
├── C3
└── SPPF # 替换原始SPP模块
2. 性能提升对比(COCO数据集)
模型 | mAP@0.5 | FPS (Tesla T4) | 参数量 (M) |
---|---|---|---|
YOLOv5s | 37.2 | 125 | 7.2 |
YOLOv5s+SPPF | 37.8 | 142 | 7.3 |
六、SPPF的局限性
-
小物体检测精度衰减:
- 多次池化可能模糊微小物体的细节特征
- 需配合FPN(特征金字塔)使用以缓解此问题
-
理论感受野与实际差异:
- 串行池化的等效感受野为线性增长,弱于SPP的指数级覆盖
-
通道膨胀问题:
- 输出通道数变为输入4倍,可能增加后续卷积计算量
七、改进变体(下面这些都是不断改进升级的版本)
-
SPPF+SE:
- 加入通道注意力机制(Squeeze-and-Excitation)
- 提升重要通道的权重分配
-
ASPPF(Atrous SPPF):
- 使用空洞池化(Dilated Pooling)扩大感受野
- 减少下采样带来的信息损失
-
轻量化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