SPD-Conv
- 提出背景
- SPD-Conv
- YOLO v5 小目标改进
- 定义 SPD-Conv
- 导入`SPD`模块
- 修改 .yaml 文件
- YOLO v7 小目标改进
- YOLO v8 小目标改进
提出背景
论文:https://arxiv.org/pdf/2208.03641v1.pdf
代码:https://github.com/labsaint/spd-conv
文章提出一个新的卷积神经网络(CNN)构建块,称为SPD-Conv,旨在替代传统CNN架构中的步长卷积和池化层,以提高在处理低分辨率图像和小对象时的性能。
- 问题: CNN在处理低分辨率图像和小对象时性能下降的问题,指出这一问题根源于使用步长卷积和池化层导致的细粒度信息丢失。
解法:SPD-Conv = SPD层 + 非步长卷积层:
-
空间到深度(SPD)层: 一个转换层,将输入图像的空间维度转换为深度维度,从而在不丢失信息的情况下增加特征图的深度。
之所以使用SPD层,是因为在处理低分辨率图像和小对象时,需要保留尽可能多的空间信息。
SPD层通过将空间维度的信息转换为深度维度,避免了传统步长卷积和池化操作中的信息丢失。
-
非步长卷积层: 在SPD转换后应用的卷积层,不使用步长,以保留细粒度信息。
在SPD层之后应用非步长卷积层,是因为非步长卷积能够在不减少特征图尺寸的情况下进行特征提取,进一步保持了图像的细粒度信息,这对于提高低分辨率图像和小对象的识别性能至关重要。
假设我们有一个低分辨率的图像,其中包含几个小的物体,我们需要对这些物体进行识别和分类。
在传统的CNN架构中,如果我们直接应用步长卷积和池化层,随着网络层次的加深,图像的空间分辨率会逐渐减少,导致小物体的细节信息丢失,从而使得网络难以准确识别这些小物体。
使用 SPD-Conv 代替 步长卷积和池化层:
-
空间到深度(SPD)层的应用:
- 初始特征图尺寸为 ( 32 × 32 ) (32 \times 32) (32×32),包含小物体的细节信息。
- 应用SPD层后,假设将空间分辨率降低为 ( 16 × 16 ) (16 \times 16) (16×16),同时将这部分减少的空间信息转移到通道维度上,从而通道数增加但没有信息丢失。
- 这样,原本在 ( 32 × 32 ) (32 \times 32) (32×32) 分辨率上分散的细节信息,现在被压缩和保留在更深的通道中。
-
非步长卷积层的应用:
- 经过SPD层处理后,特征图的尺寸变为 ( 16 × 16 ) (16 \times 16) (16×16),但通道数增加,假设从原来的64通道增加到128通道。
- 在这个增加通道数的特征图上应用非步长卷积层,这个卷积层不减少特征图的空间尺寸,而是通过学习这些增加的通道中的信息来提取重要特征。
- 这样,即使是在较小的空间分辨率上,模型也能有效捕捉到小物体的细节信息。
这种结合使用SPD层和非步长卷积层的方法,使得CNN能够更好地处理小物体和低分辨率图像中的挑战,提高了模型在这些复杂场景下的性能和鲁棒性。
SPD-Conv
当尺度为2时的SPD-Conv图:
- (a)显示了标准的特征图
- (b)展示了空间到深度操作,其中空间信息被重新排列到深度通道
- (c)显示了结果特征图的深度增加
- (d)表示在SPD操作之后应用的非步长卷积层
- (e)展示了经过步长为1的卷积后的输出特征图,该特征图保持了空间分辨率但改变了深度维度
-
空间到深度(SPD)层 - 特征图切片阶段:
- 从中间特征图 ( X ) 开始,大小为 S × S × C 1 S \times S \times C_1 S×S×C1。
- 使用
scale
因子进行下采样,将 ( X ) 划分为 scale 2 \text{scale}^2 scale2 个子特征图 f x , y f_{x,y} fx,y。每个子特征图是 ( X ) 的一部分,其中 X ( i , j ) X(i, j) X(i,j) 中的索引 ( i ) 和 ( j ) 被scale
整除。 - 这个阶段通过降低特征图的空间分辨率来增加特征图的深度(通道数)。
-
子图串联阶段:
- 这些子特征图沿通道维度合并,形成新的特征图 X 0 X_0 X0。
- X 0 X_0 X0 的空间维度是原来的 1 scale \frac{1}{\text{scale}} scale1,但通道维度增加了 scale 2 \text{scale}^2 scale2 倍。
- 这一阶段保持了原始特征图 ( X ) 中的全部信息,即使其空间分辨率减小。
-
非步长卷积层阶段:
- 接着对 X 0 X_0 X0 应用步长为1的卷积层,这意味着特征图的每个像素都会被卷积核覆盖,没有信息被跳过。
- 使用的过滤器数量 C 2 C_2 C2 少于 X 0 X_0 X0 的通道数 scale 2 C 1 \text{scale}^2 C_1 scale2C1,目的是提取重要的特征并降低通道维度。
- 结果得到特征图 X 00 X_{00} X00,其空间维度保持不变,但通道维度减少到 $C_2$。
假设我们有一个中间特征图 ( X ) 的大小为
8
×
8
×
3
8 \times 8 \times 3
8×8×3(宽、高、通道),scale
设定为2。
-
特征图切片(SPD层之前) - 我们将 ( X ) 切分为四个 4 × 4 × 3 4 \times 4 \times 3 4×4×3 的子特征图 f 0 , 0 , f 1 , 0 , f 0 , 1 , f 1 , 1 f_{0,0}, f_{1,0}, f_{0,1}, f_{1,1} f0,0,f1,0,f0,1,f1,1。
-
子图串联(SPD层) - 然后将这四个 4 × 4 × 3 4 \times 4 \times 3 4×4×3 的子特征图沿通道维度合并,形成一个新的特征图 X 0 X_0 X0 的大小为 4 × 4 × 12 4 \times 4 \times 12 4×4×12。
-
非步长卷积(非步长卷积层) - 在 X 0 X_0 X0 上应用步长为1的卷积层,假设使用8个过滤器,则最终输出特征图 KaTeX parse error: Expected '}', got 'EOF' at end of input: X_{00 的大小为 4 × 4 × 8 4 \times 4 \times 8 4×4×8。
通过这个链条,原始的特征图 ( X ) 经过一系列变换后,空间分辨率降低,但通道数增加并在非步长卷积后得到更有判别性的特征表示。
这种方法尤其适用于小对象检测和低分辨率图像分类,因为它通过增加通道深度来保留更多的信息,而非过减少像素点数来损失信息。
YOLO v5 SPD-Conv版:
- 红色框标出的部分是使用SPD-Conv构建块替换了原有步长为2的卷积层的位置。
- 结构被分为三个主要部分:骨干网络(Backbone)、颈部网络(Neck)和检测头(Head)。
- 骨干网络负责特征提取,颈部网络用于特征融合和处理,头部网络执行对象的分类和定位。
YOLOv5-SPD通过用SPD-Conv构建块替换YOLOv5中的步长为2的卷积层:
- SPD层替换: 在YOLOv5和ResNet中,将所有步长为2的卷积层替换为SPD-Conv构建块,因为这些步长卷积层导致特征图下采样,可能会丢失低分辨率图像和小尺寸物体的重要信息。
- 池化层移除: 对于低分辨率图像,移除最大池化层是合理的,因为在这些情况下,下采样可能不是必要的,且进一步的下采样可能导致更多信息的丢失。
- 可伸缩性设计: 通过调整非步长卷积层中的滤波器数量和C3模块的重复次数,可以根据需要轻松地放大或缩小模型,这是为了满足不同应用或硬件的需求。
YOLOv5-SPD-m能够检测到YOLOv5m漏检的被遮挡的长颈鹿。
)进一步展示了YOLOv5-SPD-m在检测非常小的对象(一个面孔和两个长凳)方面的能力,而YOLOv5m则未能检测到这些对象。
YOLO v5 小目标改进
改好的代码:https://github.com/labsaint/spd-conv
也可以自己调一下,有时候我们还得组合改进。
YOLO v5:https://github.com/ultralytics/yolov5
-
定义
SPD
模块:确保你已经在models/common.py
中定义了SPD`类。 -
导入
SPD
模块:在yolo.py
的顶部导入SPD
类。from models.common import SPD
-
修改
parse_model
函数:在parse_model
函数中处理SPD
模块的情况。找到
parse_model
函数,并在其中添加处理SPD
的逻辑。例如,如果你的模型配置文件中有指示使用
SPD
的地方,你需要检测它并相应地构造模块。 -
更新模型配置文件:你可能需要在YOLOv5的模型配置文件(通常是
.yaml
格式)中指明哪些卷积层应该被SPD
替换。例如,你可能会在配置文件中添加一个新的模块类型
- SPD
。 -
替换卷积层:在
parse_model
中添加逻辑,以便在解析模型时用SPD`模块替换步长为2的卷积层。
定义 SPD-Conv
在 yolov5/models/common.py
添加 SPD-Conv 代码
import torch
import torch.nn as nn
class SPD(nn.Module): # SPD 层
"""
这个模块实现了空间到深度的操作,它重新排列空间数据块到深度维度,
通过块大小增加通道数并减少空间维度。在卷积神经网络中常用此方法保持
下采样图像的高分辨率信息。
"""
def __init__(self, block_size=2):
"""
初始化 SPD 模块。
参数:
block_size (int): 每个块的大小。它定义了空间维度的下采样因子。
输出通道的数量将增加 block_size**2 倍。
"""
super(SPD, self).__init__()
self.block_size = block_size # 块大小
def forward(self, x):
"""
在输入张量上应用空间到深度操作。
参数:
x (torch.Tensor): 形状为 (N, C, H, W) 的输入张量。
返回:
torch.Tensor: 重新排列块后的输出张量。如果块大小为 2,
输出张量的形状将为 (N, C*4, H/2, W/2)。
"""
N, C, H, W = x.size() # 输入张量的维度
block_size = self.block_size # 块大小
# 确保高度和宽度可以被 block_size 整除
assert H % block_size == 0 and W % block_size == 0, \
f"空间维度必须能被 block_size 整除。得到的 H: {H}, W: {W}"
# 将空间块重新排列到深度
x_reshaped = x.view(N, C, H // block_size, block_size, W // block_size, block_size)
x_permuted = x_reshaped.permute(0, 3, 5, 1, 2, 4).contiguous()
out = x_permuted.view(N, C * block_size**2, H // block_size, W // block_size)
return out
在 YOLOv5 的项目结构中,yolov5/models/common.py
通常包含定义模型中使用的通用层和模块的 Python 代码。
这个文件是模型架构的一部分,它定义了可以在模型的不同部分重复使用的层,比如卷积层、上采样层、激活函数等。
例如,在 YOLOv5 的实现中,common.py
可能包含以下内容:
- 自定义卷积层类(有时为了特殊的初始化或行为)
- 激活函数(如 Mish 激活函数)
- 层组合(如 CSP 结构)
- SPP (Spatial Pyramid Pooling) 结构
- Focus 层(一种特殊的切片和重排层,用于改变输入特征图的空间分辨率)
在模型定义文件中(如 yolov5/models/yolov5s.py
),这些通用层会被实例化并组合成完整的神经网络。
这种模块化的方法可以让代码更加整洁,并且可以更容易地在不同的模型配置文件中重复使用相同的层定义。
导入SPD
模块
在yolo.py
的顶部导入SPD
类。
from models.common import SPD
在parse_model
中实现更改:
elif m is SPD:
c2 = 4 * ch[f]
# 将输入通道数 ch[f] 增加了四倍
# 4 是基于 SPD 的 block_size 为 2 的假设。如果 block_size 有所不同,这个数字应该是 block_size 的平方
# ch 是一个包含前面所有层输出通道数的列表,f 是指向前面某层的索引
修改 .yaml 文件
在 YOLOv5 的项目中,.yaml
文件通常用来定义模型的架构。
yaml 文件分为:
- 参数部分:nc(数据集类别数量)、
- 网络结构部分:from(从哪层获取输入), repeats(模块重复次数), module(模块名字), args
- 头部部分
文件名中的 yolov5s
, yolov5m
, yolov5l
, yolov5x
等分别代表了不同大小和复杂性的模型配置:
-
yolov5s.yaml - “s” 代表 “small”(小)。
这是 YOLOv5 系列中最小的模型,拥有最少的层数和参数,适用于速度要求较高或计算资源有限的环境。
-
yolov5m.yaml - “m” 代表 “medium”(中等)。
这个配置是介于小型和大型模型之间的中等大小的模型,它在速度和准确性之间提供了一个平衡。
-
yolov5l.yaml - “l” 代表 “large”(大)。
这个配置用于更大的模型,它具有更多的层数和参数,通常能够提供更高的准确性,但需要更多的计算资源。
-
yolov5x.yaml - “x” 代表 “extra large”(特大)。
这是 YOLOv5 系列中最大的模型,有最多的层数和参数,通常在准确性方面表现最好,但也是计算成本最高的。
-
yolov5n.yaml - “n” 代表 “nano”(纳米)。
这是一个非常小的模型,旨在在非常资源有限的设备上运行,比如在边缘计算设备或移动设备上。
这些文件中定义的参数包括了模型的层数、每一层的类型(如卷积层、上采样层等)、每一层的参数(如滤波器数量、步长、激活函数等),以及如何将这些层连接起来。
用户可以根据自己的需求和资源选择合适的模型大小。
例如,如果你需要在移动设备上进行实时物体检测,可能会选择 yolov5s
或 yolov5n
;如果你在服务器上进行高准确性的物体检测并且不太关心推理时间,可能会选择 yolov5l
或 yolov5x
。
我们选第一个:yolov5S.yaml
修改 backbone 部分:在 backbone 配置中,找到所有步长为2的 Conv 层。
例如,[-1, 1, Conv, [128, 3, 2]] 表示一个从上一个层(-1 表示上一层)接收输入的卷积层,具有128个输出通道,使用3x3的卷积核,步长为2。
backbone:
# [from, number, module, args]
[
[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
[-1, 1, SPD, [1]], # 1-P2/4 replaced Conv with SPD
[-1, 3, C3, [128]], # 2
[-1, 1, SPD, [1]], # 3-P3/8 replaced Conv with SPD
# ... [continue with other layers] ...
]
SPD 不需要像 Conv 那样指定过滤器的数量和大小,因为它是一个重新排列输入的操作,而不是学习过滤器权重的操作。
参数 [1] 可以表示 block_size,如果SPD 实现时接受这个参数的话。
调整后续层的通道数:由于 SPD 操作会增加通道数,你可能需要调整后续层以匹配新的通道数。这可能包括更改后面的 C3 层的通道数。
总结,YOLO v5 修改三步:
- 把 XXX模块 放入
common.py
yolo.py
放入类名- 修改
yolov5.yaml
YOLO v7 小目标改进
https://github.com/WongKinYiu/yolov7
逻辑同上。
YOLO v8 小目标改进
在 v8 文件夹下新建一个 xxx.yaml
;
把 SPD-Conv
代码添加到 ultralytics 文件中:
- 在 block.py 的 all = {各种模块},把 SPD 代码函数名字添加进来
- 在 block.py 下面添加 SPD 代码
- 在 modules/init.py 中 from .block import (各种模块),把 SPD 代码函数名字添加进来
- 在 task.py 中 from ultralyics.nm.modules import (各种模块),把 SPD 代码函数名字添加进来
将 SPD-Conv
函数名字加入到 ultralytics/nn/tasks.py 中;
修改 xxx.yaml
,用 SPD 构建 SPD-Conv 主干网络 ;
修改 ultralytics/yolo/cfg/default.yaml
文件的 ‘–model’ 参数,或使用指令训练。