YOLOv5改进---添加注意力机制之CA

文章介绍了CoordAttention,一种为轻量级网络设计的注意力机制,它结合了通道注意力和位置信息,显著提升网络性能,特别在密集预测任务中表现优秀。文章还展示了如何在YOLOv5中添加CA模块以增强特征表示。
摘要由CSDN通过智能技术生成

参考文章:CoordAttention - 知乎 (zhihu.com)

一、CA注意力简介

        在轻量级网络上的研究表明,通道注意力会给模型带来比较显著的性能提升,但是通道注意力通常会忽略对生成空间选择性注意力图非常重要的位置信息。因此,新加坡国立大学的Qibin Hou等人提出了一种为轻量级网络设计的新的注意力机制,该机制将位置信息嵌入到了通道注意力中,称为coordinate attention(简称CoordAttention,下文也称CA),该论文已被CVPR2021收录。不同于通道注意力将输入通过2D全局池化转化为单个特征向量,CoordAttention将通道注意力分解为两个沿着不同方向聚合特征的1D特征编码过程。这样的好处是可以沿着一个空间方向捕获长程依赖,沿着另一个空间方向保留精确的位置信息。然后,将生成的特征图分别编码,形成一对方向感知和位置敏感的特征图,它们可以互补地应用到输入特征图来增强感兴趣的目标的表示。

        CoordAttention简单灵活且高效,可以插入经典的轻量级网络(如MobileNetV2)在几乎不带来额外计算开销的前提下,提升网络的精度。实验表明,CoordAttention不仅仅对于分类任务有不错的提高,对目标检测、实例分割这类密集预测的任务,效果提升更加明显。

        考虑到轻量级网络有限的计算能力,目前最流行的注意力机制仍然是SENet提出的SE Attention。遗憾的是,SE模块只考虑了通道间信息的编码而忽视了位置信息的重要性,而位置信息其实对于很多需要捕获目标结构的视觉任务至关重要。因此,后来CBAM等方法通过减少通道数继而使用大尺寸卷积来利用位置信息。然而,卷积仅仅能够捕获局部相关性,建模对视觉任务非常重要的长程依赖则显得有些有心无力。

         CA注意力机制,通过将位置信息嵌入到通道注意力中,使得轻量级网络能够在更大的区域上进行注意力,同时避免了产生大量的计算开销。为了缓解2D全局池化造成的位置信息丢失,论文作者将通道注意力分解为两个并行的1D特征编码过程,有效地将空间坐标信息整合到生成的注意图中。更具体来说,作者利用两个一维全局池化操作分别将垂直和水平方向的输入特征聚合为两个独立的方向感知特征图。然后,这两个嵌入特定方向信息的特征图分别被编码为两个注意力图,每个注意力图都捕获了输入特征图沿着一个空间方向的长程依赖。因此,位置信息就被保存在生成的注意力图里了,两个注意力图接着被乘到输入特征图上来增强特征图的表示能力。由于这种注意力操作能够区分空间方向(即坐标)并且生成坐标感知的特征图,因此将提出的方法称为坐标注意力(coordinate attention)。

Coordinate Attention

相比此前的轻量级网络上的注意力方法,coordinate attention存在以下优势。

  • 首先,它不仅仅能捕获跨通道的信息,还能捕获方向感知和位置感知的信息,这能帮助模型更加精准地定位和识别感兴趣的目标;
  • 其次,coordinate attention灵活且轻量,可以被容易地插入经典模块,如MobileNetV2提出的inverted residual block和MobileNeXt提出的 sandglass block,来通过强化信息表示的方法增强特征;
  • 最后,作为一个预训练模型,coordinate attention可以在轻量级网络的基础上给下游任务带来巨大的增益,特别是那些存在密集预测的任务(如语义分割)。

CA模块通过精确的位置信息对通道关系和长程依赖进行编码,类似SE模块,也分为两个步骤:坐标信息嵌入(coordinate information embedding)和坐标注意力生成(coordinate attention generation),它的具体结构如下图。

二、YOLOv5添加CA注意力机制

1. 添加方法

YOLOv5改进---添加注意力机制之SE-CSDN博客

2. 在yolov5中添加CA模块的代码:

# CA
class h_sigmoid(nn.Module):
    def __init__(self, inplace=True):
        super(h_sigmoid, self).__init__()
        self.relu = nn.ReLU6(inplace=inplace)
    def forward(self, x):
        return self.relu(x + 3) / 6
class h_swish(nn.Module):
    def __init__(self, inplace=True):
        super(h_swish, self).__init__()
        self.sigmoid = h_sigmoid(inplace=inplace)
    def forward(self, x):
        return x * self.sigmoid(x)
 
class CoordAtt(nn.Module):
    def __init__(self, inp, oup, reduction=32):
        super(CoordAtt, self).__init__()
        self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
        self.pool_w = nn.AdaptiveAvgPool2d((1, None))
        mip = max(8, inp // reduction)
        self.conv1 = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0)
        self.bn1 = nn.BatchNorm2d(mip)
        self.act = h_swish()
        self.conv_h = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0)
        self.conv_w = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0)
    def forward(self, x):
        identity = x
        n, c, h, w = x.size()
        #c*1*W
        x_h = self.pool_h(x)
        #c*H*1
        #C*1*h
        x_w = self.pool_w(x).permute(0, 1, 3, 2)
        y = torch.cat([x_h, x_w], dim=2)
        #C*1*(h+w)
        y = self.conv1(y)
        y = self.bn1(y)
        y = self.act(y)
        x_h, x_w = torch.split(y, [h, w], dim=2)
        x_w = x_w.permute(0, 1, 3, 2)
        a_h = self.conv_h(x_h).sigmoid()
        a_w = self.conv_w(x_w).sigmoid()
        out = identity * a_w * a_h
        return out

3. 在C3模块中添加CA模块的代码:z

class CABottleneck(nn.Module):
    # Standard bottleneck
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5, ratio=32):  # ch_in, ch_out, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_, c2, 3, 1, g=g)
        self.add = shortcut and c1 == c2
        # self.ca=CoordAtt(c1,c2,ratio)
        self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
        self.pool_w = nn.AdaptiveAvgPool2d((1, None))
        mip = max(8, c1 // ratio)
        self.conv1 = nn.Conv2d(c1, mip, kernel_size=1, stride=1, padding=0)
        self.bn1 = nn.BatchNorm2d(mip)
        self.act = h_swish()
        self.conv_h = nn.Conv2d(mip, c2, kernel_size=1, stride=1, padding=0)
        self.conv_w = nn.Conv2d(mip, c2, kernel_size=1, stride=1, padding=0)

    def forward(self, x):
        x1 = self.cv2(self.cv1(x))
        n, c, h, w = x.size()
        # c*1*W
        x_h = self.pool_h(x1)
        # c*H*1
        # C*1*h
        x_w = self.pool_w(x1).permute(0, 1, 3, 2)
        y = torch.cat([x_h, x_w], dim=2)
        # C*1*(h+w)
        y = self.conv1(y)
        y = self.bn1(y)
        y = self.act(y)
        x_h, x_w = torch.split(y, [h, w], dim=2)
        x_w = x_w.permute(0, 1, 3, 2)
        a_h = self.conv_h(x_h).sigmoid()
        a_w = self.conv_w(x_w).sigmoid()
        out = x1 * a_w * a_h

        # out=self.ca(x1)*x1
        return x + out if self.add else out


class C3_CA(C3):
    # C3 module with CABottleneck()
    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)  # hidden channels
        self.m = nn.Sequential(*(CABottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))

注意将上面的两个类都添加到yolo.py里面parse_model函数中,如下图所示:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值