一、前言

SPD-Conv是一种可以替换普通卷积层的卷积,依靠SPD层独特的特征处理方式对模型的性能进行提升。但是大家搜索SPD-Conv就会发现,清一色全都是YOLO的改进,即插即用等等之类的收费文章,真正讲SPD-Conv原理的文章所见甚少。如果不知道原理就去用这些模块的话往往不能够把论文给好,把故事讲好。虽然在改进的文章里会有一部分原理介绍,但那是远远不够的。对于想发高质量论文的,还是需要认认真真地把原理完全搞懂。

二、SPD-Conv

1、背景

首先,想要理解SPD-Conv的原理,我们得看看它是怎么提出的。

这篇论文就是提出SPD-Conv的论文,它最先是被应用在CNN网络中的。题目的意思是不再有更多的跨步卷积和池化:一种为了处理低分辨率和小物体的新的CNN构建模块

可以从标题中看出来啊,SPD-Conv提出的背景是为了解决低分辨率图像的问题。因此要写改进的话也应该从问题出发。因为应用场景往往只能提供提分辨率的图像,导致现有模型的效果不好,于是引入SPD-Conv……

论文发现了步长大于 1 的 Conv 和 pooling 层的缺点, 提出 No More Strided Convolutions or Pooling 的 SPD-Conv 结构来替代 Strided Conv 和 pooling,对低分辨率小目标场景有一定提升

2、结构和原理

这个图就是SPD-Conv的原理了,前面的bcd是SPD,e是Conv,连起来就是SPD-Conv

具体地,我们一步步来:

(1)a→b:

从a到b的一个过程实际上就是一个把图像分割的过程,我们可以看到b中的特征依然是(S,S,C1),说明并没有进行特殊的处理。实际上就是进行了划分,那么怎么划分的我们就要看它的公式原理了:

这就是它的公式了,可以看出来比较复杂了,不如图片那么简单。那是因为图片是假设scale=2的情况,而公式则是一般情况了。特征怎么取我们看中括号里的内容,第一个,f0,0=X [0:S:scale,0:S:scale],意思就是对图片第一维度从0开始,到S结束,步长为scale来进行截取,之后的也是一样。至于为什么用逗号隔开写两遍,那是表示x,y两个方向上。

就想这个图上标的x,y一样。但其实可以看到,无论如何中间的那个数都是S,这样才能确保之后的特征子图大小是一样的。

(2)b→c:

公式中,我们看到有着“f”,实际上就是代表一个子特征的意思,可以抽象理解为原本特征的几个儿子。有几个儿子就取决于scale的大小了,从公式中看,f要从0,0取到scale-1,scale-1,排列组合问题,应该会把通道数搞到scale^2个。

图中有scale=2,那么每两个取一个,总共出scale^2=4个;步长取得越大,在(c)中得到的子图就越多。

(3)c→d:

这就是一个特征融合的过程,可以看出来是就是把得到的子图叠在一起。可以想象,如果scale取得太大,这里的特征图就会变成一根金箍棒。。所以SPD-Conv用的scale就是2

(4)d→e:

SPD后接一个步长为1的卷积是为了减少特征图数量,卷积的步长为1,那么特征大小就不会变化,还是(s/2,s/2),但通道数变化了,应该是因为作用的卷积是多通道卷积。一可能是跟4C1不相等的通道数的卷积,二可能是相等的但是有C2个卷积。应该是前者,论文里并没有详细的描述,我也还没有对代码进行解析。

择日不如撞日,就今天来看一下代码吧

import torch

import torch.nn as nn

__all__ = ['SPDConv']

def autopad(k, p=None, d=1): # kernel, padding, dilation

"""Pad to 'same' shape outputs."""

if d > 1:

k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-size

if p is None:

p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad

return p

class SPDConv(nn.Module):

"""Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)."""

default_act = nn.SiLU() # default activation

def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):

"""Initialize Conv layer with given arguments including activation."""

super().__init__()

c1 = c1 * 4

self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)

self.bn = nn.BatchNorm2d(c2)

self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()

def forward(self, x):

x = torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)

"""Apply convolution, batch normalization and activation to input tensor."""

return self.act(self.bn(self.conv(x)))

def forward_fuse(self, x):

"""Perform transposed convolution of 2D data."""

x = torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)

return self.act(self.conv(x))

在SPD-Conv类的构造函数中,卷积层的初始化如下:

self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)

这个值由用户在创建 SPDConv 实例时传递给类的构造函数。例如,如果你实例化时写SPDConv(C1,64),则C2就是64,这意味着卷积操作后,输出的特征图的通道数为64。

也就是说这个C2输出通道数就是由我们来决定的,在合理范围内,想输出多少就写多少。

为什么用这样的卷积呢?

使用非跨行(步长为1)的原因是为了尽可能多地保留所有的判别特征信息。否则,例如,使用stride=3的3 × 3过滤器,特征地图将被“缩小”,但每个像素只采样一次;如果stride=2,将发生非对称采样,其中偶数和奇数行/列将在不同时间采样。

3、总结

总的来说,大概的过程就是这样,简单易懂

三、结语

SPD-Conv(Spatially Separated and Deformable Convolution)是一种针对低分辨率图像和小物体检测优化的卷积操作,其创新之处在于通过避免传统卷积中的步长大于1的卷积和池化操作,从而更好地保留图像的细节信息。SPD-Conv通过对输入图像进行空间划分,将其分割成多个子图,并通过特征融合的方式将这些子图合并,最终通过一个步长为1的卷积层进行进一步处理,从而获得更为精细的特征表示。这种结构特别适用于处理低分辨率图像,能够提升模型在小物体检测等任务中的表现。

通过对SPD-Conv的构造和原理的详细分析,可以看到其通过控制卷积操作的步长、融合不同尺度的特征以及利用多通道卷积,有效地提升了模型的性能,尤其是在低分辨率输入的情况下,避免了传统卷积和池化带来的信息丢失。总之,SPD-Conv是一种通过细粒度处理图像特征来改善传统卷积操作的有效方法,对于提升小物体检测和低分辨率图像处理具有重要意义。

Copyright © 2088 世界杯直播cctv5_世界杯阿根 - sunjianping.com All Rights Reserved.
友情链接
top