CoordConv:给卷积加上坐标,从而使其具备了空间感知能力
- 核心代码
-
- CoordConv代码
- common代码:
- yolo注册
- yaml文件:
- 测试
众所周知,深度学习里的卷积运算是具有平移等变性的,这样可以在图像的不同位置共享统一的卷积核参数,但是这样卷积学习过程中是不能感知当前特征在图像中的坐标的,论文中的实验证明如下图所示。通过该实验,作者证明了传统卷积在卷积核进行局部运算时,仅仅能感受到局部信息,并且是无法感受到位置信息的。CoordConv就是通过在卷积的输入特征图中新增对应的通道来表征特征图像素点的坐标,让卷积学习过程中能够一定程度感知坐标来提升检测精度。
反方向和第三个发现:监督式回归对CNN同样很难
所以为什么网络很难定位一个像素呢?是因为从小空间到大空间的所以为什么网络很难定位一个像素呢?是因为从小空间到大空间的转换很困难吗?如果朝一个方向会不会容易点呢?如果我们训练卷积网络将图像信息转换成标量坐标,是否与普通图像分类更相似呢?
结果模型在这种监督式回归的任务上同样表现得不好。在图10中,左边图中的点表示正确的像素坐标,中间图中的点表示模型的预测。模型在测试集上表现得不好,并且在训练集上也差强人意。
简单地说,方向根本不重要。
所以,这一看似简单的坐标转换任务在卷积网络身上主要有两个问题:从笛卡尔空间转换到one-hot像素空间及其他方式上。即使用监督式方法进行训练,即使只有一个像素,即使所有的训练案例很容易获得,卷积仍然学不会顺利转换。另外,表现最好的卷机模型体积巨大,训练耗时很长。
CoordConv
我们提出了解决这一难题的方法。
卷积是等变的,也就是说当每个过滤器应用到输入上时,它不知道每个过滤器在哪。我们可以帮助卷积,让它知道过滤器的位置。这一过程需要在输入上添加两个通道实现,一个在i坐标,另一个在j坐标。我们将这个图层成为CoordConv,如图所示:
提出的CoordConv图层是标准卷积层的简单扩展,其中卷积和坐标相对应。让卷积过滤器看到坐标其实违背了等变性原则,看起来不是个好方法,但是,等变性原则对卷积有好处吗?
我们认为,卷积获得成功的原因主要依靠三个元素:他运用相对较少的学习参数、在GPU上计算速度很快、他学习的函数时符合平移等变性的。
CoordConv保留了前两种特点——较少的参数和高效的计算。如果坐标的权重变为零,CoordConv就和普通卷积没什么区别。另一方面,如果平移依赖对下游任务有用的话,它也同样可以学习。
CoordConv与目前很多理念相关,例如局部连接层、复合模式生成网络(CPPN)以及语言建模中用到的位置嵌入。
核心代码
CoordConv代码
#CoordConv**************************************************
class AddCoords(nn.Module):
def __init__(self, with_r=False):
super().__init__()
self.with_r = with_r
def forward(self, input_tensor):
"""
Args:
input_tensor: shape(batch, channel, x_dim, y_dim)
"""
batch_size, _, x_dim, y_dim = input_tensor.size()
xx_channel = torch.arange(x_dim).repeat(1, y_dim, 1)
yy_channel = torch.arange(y_dim).repeat(1, x_dim, 1).transpose(1, 2)
xx_channel = xx_channel.float() / (x_dim - 1)
yy_channel = yy_channel.float() / (y_dim - 1)
xx_channel = xx_channel * 2 - 1
yy_channel = yy_channel * 2 - 1
xx_channel = xx_channel.repeat(batch_size, 1, 1, 1).transpose(2, 3)
yy_channel = yy_channel.repeat(batch_size, 1, 1, 1).transpose(2, 3)
ret = torch.cat([
input_tensor,
xx_channel.type_as(input_tensor),
yy_channel.type_as(input_tensor)], dim=1)
if self.with_r:
rr = torch.sqrt(torch.pow(xx_channel.type_as(input_tensor) - 0.5, 2) + torch.pow(yy_channel.type_as(input_tensor) - 0.5, 2))
ret = torch.cat([ret, rr], dim=1)
return ret
class CoordConv(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size=1, stride=1, with_r=False):
super().__init__()
self.addcoords = AddCoords(with_r=with_r)
in_channels += 2
if with_r:
in_channels += 1
self.conv = Conv(in_channels, out_channels, k=kernel_size, s=stride)
def forward(self, x):
x = self.addcoords(x)
x = self.conv(x)
return
common代码:
class Bottleneck_CoordConv(nn.Module):
# Standard bottleneck
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = CoordConv(c1, c_, 1, 1)
self.cv2 = CoordConv(c_, c2, 3, 1)
self.add = shortcut and c1 == c2
def forward(self, x):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
class C3_CoordConv(C3):
# C3 module with 结合位置编码,卷积加上坐标,从而使其具备了空间感知能力
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
super().__init__(c1, c2, n, shortcut, g, e)
c_ = int(c2 * e)
self.m = nn.Sequential(*(Bottleneck_CoordConv(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
yolo注册
for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args
m = eval(m) if isinstance(m, str) else m # eval strings
for j, a in enumerate(args):
with contextlib.suppress(NameError):
args[j] = eval(a) if isinstance(a, str) else a # eval strings
n = n_ = max(round(n * gd), 1) if n > 1 else n # depth gain
if m in {
Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,
BottleneckCSP, C3, C3f, C3TR, C3SPP, C3Ghost, nn.ConvTranspose2d, DWConvTranspose2d, C3x, C3CA ,
CA, C3ECA, C3CAAM, stem, MBConvBlock, FusedMBConv, MBConv, SPPCSPC, SPPA_CBAM, SE_SPPFCSPC, ASPP,
SCConv, nn.ConvTranspose2d, conv_bn_hswish, MobileNetV3_InvertedResidual,MobileNet_Block,
ShuffleNetV2_InvertedResidual, Conv_maxpool, InvertedResidual, ConvBNReLU, PatchEmbed,BasicStage, PatchMerging,
CTR3, C3STR, C3_DCNv2, CoordConv, CSPStage, C3SPPAF, C3_Faster, PConv, C3PConv, SCConv, C3_MDConv, C2f, SPPF_ATT, FEM, CBAMBlock2, GhostBottleneckv1, GhostBottleneckV2, ghostnetv2_model,
C3_Bottleneck_ATT, C3_ODConv, C3_CoordConv,
RFCAConv, RFCAConv2, CAConv, CAMConv, CBAMConv, NSTConv, GAMConv, PSAConv, MultiScaleCAConv, DyCAConv, NonCAConv, DeformCAConv, DeformCAConv2, PSA_sConv,
SKConv, CAConv2, CAConv3, DyMCAConv, C3_Res2, C3_Res2_2}:#添加模块
c1, c2 = ch[f], args[0]
if c2 != no: # if not output
c2 = make_divisible(c2 * gw, 8)
args = [c1, c2, *args[1:]]
if m in {BottleneckCSP, C3, C3TR,CTR3, C3Ghost, C3x,
C3CA, C3ECA, C3CAAM, C3STR, C3_DCNv2, CSPStage, C3SPPAF, C3PConv, C3_Bottleneck_ATT, C3_Res2, C3_ODConv, C3_Res2_2, C3_CoordConv}:#添加模块
args.insert(2, n) # number of repeats
n = 1
yaml文件:
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# yolov5s-CoordConv 结合位置编码,卷积加上坐标,从而使其具备了空间感知能力
#Conv YOLOv5s summary: 280 layers, 7273657 parameters, 7273657 gradients, 12.1 GFLOPs
# Parameters
nc: 80 # number of classes
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
anchors:
- [10,13, 16,30, 33,23] # P3/8
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32
# YOLOv5 v6.0 backbone
backbone:
# [from, number, module, args]
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
[-1, 3, C3_CoordConv, [128]],
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 6, C3_CoordConv, [256]],
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, C3_CoordConv, [512]],
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 3, C3_CoordConv, [1024]],
[-1, 1, SPPF, [1024, 5]], # 9
]
# YOLOv5 v6.0 head
head:
[[-1, 1, Conv, [512, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 6], 1, Concat, [1]], # cat backbone P4
[-1, 3, C3_CoordConv, [512, False]], # 13
[-1, 1, Conv, [256, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 4], 1, Concat, [1]], # cat backbone P3
[-1, 3, C3_CoordConv, [256, False]], # 17 (P3/8-small)
[-1, 1, Conv, [256, 3, 2]],
[[-1, 14], 1, Concat, [1]], # cat head P4
[-1, 3, C3_CoordConv, [512, False]], # 20 (P4/16-medium)
[-1, 1, Conv, [512, 3, 2]],
[[-1, 10], 1, Concat, [1]], # cat head P5
[-1, 3, C3_CoordConv, [1024, False]], # 23 (P5/32-large)
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
测试
卷积加上坐标,从而使其具备了空间感知能力,与CA注意力思想蛮类似的,或许可以提升行人检测的精度。