YOLOv8改进 | 注意力机制 | 正确的 Self-Attention 与 CNN 融合范式,性能速度全面提升【独家创新】

116 篇文章 46 订阅 ¥159.90 ¥299.90

💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡


专栏目录:《YOLOv8改进有效涨点》专栏介绍 & 专栏目录 | 目前已有40+篇内容,内含各种Head检测头、损失函数Loss、Backbone、Neck、NMS等创新点改进


卷积和自注意力是两种强大的表征学习技术,它们通常被认为是彼此不同的两种平行方法。ACmix模型通过结合卷积和自注意力的优势,旨在解决卷积神经网络和自注意力模型在表征学习中的各自局限性,提高模型性能。它解决了感受野的限制,提高了计算和参数效率,增强了模型的泛化能力,并提供了灵活的框架以适应不同的任务和数据。通过这种方式,ACmix在图像识别和下游任务上实现了性能提升。文章在介绍主要的原理后,将手把手教学如何进行模块的代码添加和修改,并将修改后的完整代码放在文章的最后,方便大家一键运行,小白也可轻松上手实践。以帮助您更好地学习深度学习目标检测YOLO系列的挑战。

专栏地址YOLOv8改进——更新各种有效涨点方法——点击即可跳转

目录

1. 简介

2. 原理

2.1 将自注意力与卷积联系起来

2.2 自注意力与卷积的整合

3. ACmix代码实现

3.1 将ACmix添加到YOLOv8中

3.2 更改init.py文件

3.3 添加yaml文件

3.4 在task.py中进行注册

3.5 执行程序

4. 完整代码分享

5. GFLOPs

6. 进阶

7. 总结


1. 简介

img

论文链接:https://arxiv.org/pdf/2111.14556.pdf

代码链接:https://github.com/Panxuran/ACmix

预训练模型:models: Models of MindSpore

Convolution和Self-Attention是两种强大的表征学习方法,它们通常被认为是两种彼此不同的方法。在本文中证明了它们之间存在着很强的潜在关系,因为这两个方法的大部分计算实际上是用相同的操作完成的。具体来说:

  • 首先,证明了具有卷积可以分解成个独立的卷积;

  • 然后,进行移位和求和操作;

  • 再然后,将Self-Attention模块中的query、key和value的投影解释为多个卷积,然后计算注意力权重和value的聚合。

因此,两个模块的第一阶段都包含了类似的操作。更重要的是,与第二阶段相比,第一阶段的计算复杂度(通道的平方)占主导地位。这个观察结果自然地导致了这两个看似不同的范式的优雅集成,即,一个混合模型,它既兼顾Self-Attention和Convolution的优点,同时与Convolution或Self-Attention对应的模型相比,具有更小的计算开销。大量的实验表明,本文方法在图像识别和下游任务上取得了持续改进的结果。

近年来,卷积和Self-Attention在计算机视觉领域得到了长足的发展。卷积神经网络广泛应用于图像识别、语义分割和目标检测,并在各种基准上实现了最先进的性能。最近,随着Vision Transformer的出现,基于Self-Attention的模块在许多视觉任务上取得了与CNN对应模块相当甚至更好的表现。

尽管这两种方法都取得了巨大的成功,但卷积和Self-Attention模块通常遵循不同的设计范式。传统卷积根据卷积的权值在局部感受野上利用一个聚合函数,这些权值在整个特征图中共享。固有的特征为图像处理带来了至关重要的归纳偏差。

相比之下,Self-Attention模块采用基于输入特征上下文的加权平均操作,通过相关像素对之间的相似函数动态计算注意力权重。这种灵活性使注意力模块能够适应地关注不同的区域,并捕捉更多的特征。

考虑到卷积和Self-Attention的不同和互补性质,通过集成这些模块,存在从两种范式中受益的潜在可能性。先前的工作从几个不同的角度探讨了Self-Attention和卷积的结合。

早期的研究,如SENet、CBAM,表明Self-Attention可以作为卷积模块的增强。最近,Self-Attention被提出作为独立的块来替代CNN模型中的传统卷积,如SAN、BoTNet。

另一种研究侧重于将Self-Attention和卷积结合在单个Block中,如 AA-ResNet、Container,而该体系结构限于为每个模块设计独立的路径。因此,现有的方法仍然将Self-Attention和卷积作为不同的部分,并没有充分利用它们之间的内在关系。

在这篇论文中,作者试图揭示Self-Attention和卷积之间更为密切的关系。通过分解这两个模块的操作表明它们在很大程度上依赖于相同的1×1卷积操作。作者基于这一观察结果开发了一个混合模型,名为ACmix,并以最小的计算开销优雅地集成了Self-Attention和卷积。

具体地说:

  • 首先,通过使用1×1卷积对输入特征进行映射,获得丰富的中间特征集;

  • 然后,按照不同的模式(分别以Self-Attention方式和卷积方式)重用和聚合中间特征。

通过这种方式,ACmix既享受了两个模块的优点,又有效地避免了两次昂贵的投影操作。

主要贡献:

  1. 揭示了Self-Attention和卷积之间强大的潜在关系,为理解两个模块之间的联系提供了新的视角,并为设计新的学习范式提供了灵感;

  2. 介绍了Self-Attention和卷积模块的一个优雅集成,它享受这两者的优点。经验证据表明,混合模型始终优于其纯卷积或Self-Attention对应模型。

2. 原理

2.1 将自注意力与卷积联系起来

前面介绍了对自注意力和卷积模块的分解,从多个角度揭示了更深层次的关系。首先,这两个阶段的作用非常相似。阶段一是一个特征学习模块,两种方法通过执行个卷积来将特征投射到更深的空间,从而共享相同的操作。另一方面,第二阶段对应的是特征聚合的过程。

从计算的角度来看,卷积模块和自注意力模块在第一阶段进行的1 × 1卷积都需要理论浮点数和通道大小参数的二次复杂度C。相比较而言,在第二阶段,两个模块都是轻量级的或几乎不需要计算。

综上所示,上述分析表明:

  1. Convolution和self-attention在通过1×1 convolutions投影输入feature map的操作上实际上是相同的,这也是两个模块的计算开销;

  2. 虽然对于捕获语义特征至关重要,但第二阶段的聚合操作是轻量级的,不需要获取额外的学习参数。

2.2 自注意力与卷积的整合

上述的观察自然带来了卷积和自注意力的完美结合。由于两个模块共享相同的1×1卷积操作,因此只能执行一次投影,并将这些中间特征映射分别用于不同的聚合操作。于是本文作者提出了如图2(c)所示的混合模块ACmix。

图2(c)

具体来说,ACmix依旧包括两个阶段:

  1. 在第一阶段:通过3个1×1卷积对输入特征进行投影,然后reshape为N个Pieces。因此,获得了包含3×N特征映射的一组丰富的中间特征。

  2. 在第二阶段:它们遵循不同的范例。对于自注意力路径,将中间特征集合到N组中,每组包含3个特征,每个特征来自1×1卷积。对应的三个特征图分别作为query、key和value,遵循传统的多头自注意力模块。对于kernel size为k的卷积路径,采用轻全连接层,生成个特征映射。因此,通过对生成的特征进行移位和聚合,对输入特征进行卷积处理,并像传统的一样从局部感受野收集信息。

最后,将两个路径的输出相加,其强度由两个可学习标量控制:

img

3. ACmix代码实现

3.1 将ACmix添加到YOLOv8中

关键步骤一: 将下面代码粘贴到在/ultralytics/ultralytics/nn/modules/conv.py中,并在该文件的__all__中添加“ACmix”

class ACmix(nn.Module):
    def __init__(self, in_planes, kernel_att=7, head=4, kernel_conv=3, stride=1, dilation=1):
        super(ACmix, self).__init__()
        self.in_planes = in_planes
        self.out_planes = in_planes
        self.head = head
        self.kernel_att = kernel_att
        self.kernel_conv = kernel_conv
        self.stride = stride
        self.dilation = dilation
        self.rate1 = torch.nn.Parameter(torch.Tensor(1))
        self.rate2 = torch.nn.Parameter(torch.Tensor(1))
        self.head_dim = self.out_planes // self.head

        self.conv1 = nn.Conv2d(in_planes, self.out_planes, kernel_size=1)
        self.conv2 = nn.Conv2d(in_planes, self.out_planes, kernel_size=1)
        self.conv3 = nn.Conv2d(in_planes, self.out_planes, kernel_size=1)
        self.conv_p = nn.Conv2d(2, self.head_dim, kernel_size=1)

        self.padding_att = (self.dilation * (self.kernel_att - 1) + 1) // 2
        self.pad_att = torch.nn.ReflectionPad2d(self.padding_att)
        self.unfold = nn.Unfold(kernel_size=self.kernel_att, padding=0, stride=self.stride)
        self.softmax = torch.nn.Softmax(dim=1)

        self.fc = nn.Conv2d(3*self.head, self.kernel_conv * self.kernel_conv, kernel_size=1, bias=False)
        self.dep_conv = nn.Conv2d(self.kernel_conv * self.kernel_conv * self.head_dim, self.out_planes, kernel_size=self.kernel_conv, bias=True, groups=self.head_dim, padding=1, stride=stride)

        self.reset_parameters()
    
    def reset_parameters(self):
        init_rate_half(self.rate1)
        init_rate_half(self.rate2)
        kernel = torch.zeros(self.kernel_conv * self.kernel_conv, self.kernel_conv, self.kernel_conv)
        for i in range(self.kernel_conv * self.kernel_conv):
            kernel[i, i//self.kernel_conv, i%self.kernel_conv] = 1.
        kernel = kernel.squeeze(0).repeat(self.out_planes, 1, 1, 1)
        self.dep_conv.weight = nn.Parameter(data=kernel, requires_grad=True)
        self.dep_conv.bias = init_rate_0(self.dep_conv.bias)

    def forward(self, x):
        q, k, v = self.conv1(x), self.conv2(x), self.conv3(x)
        scaling = float(self.head_dim) ** -0.5
        b, c, h, w = q.shape
        h_out, w_out = h//self.stride, w//self.stride


        # ### att
        # ## positional encoding https://github.com/iscyy/yoloair
        pe = self.conv_p(position(h, w, x.is_cuda))

        q_att = q.view(b*self.head, self.head_dim, h, w) * scaling
        k_att = k.view(b*self.head, self.head_dim, h, w)
        v_att = v.view(b*self.head, self.head_dim, h, w)

        if self.stride > 1:
            q_att = stride(q_att, self.stride)
            q_pe = stride(pe, self.stride)
        else:
            q_pe = pe

        unfold_k = self.unfold(self.pad_att(k_att)).view(b*self.head, self.head_dim, self.kernel_att*self.kernel_att, h_out, w_out) # b*head, head_dim, k_att^2, h_out, w_out
        unfold_rpe = self.unfold(self.pad_att(pe)).view(1, self.head_dim, self.kernel_att*self.kernel_att, h_out, w_out) # 1, head_dim, k_att^2, h_out, w_out
        
        att = (q_att.unsqueeze(2)*(unfold_k + q_pe.unsqueeze(2) - unfold_rpe)).sum(1) # (b*head, head_dim, 1, h_out, w_out) * (b*head, head_dim, k_att^2, h_out, w_out) -> (b*head, k_att^2, h_out, w_out)
        att = self.softmax(att)

        out_att = self.unfold(self.pad_att(v_att)).view(b*self.head, self.head_dim, self.kernel_att*self.kernel_att, h_out, w_out)
        out_att = (att.unsqueeze(1) * out_att).sum(2).view(b, self.out_planes, h_out, w_out)

        ## conv
        f_all = self.fc(torch.cat([q.view(b, self.head, self.head_dim, h*w), k.view(b, self.head, self.head_dim, h*w), v.view(b, self.head, self.head_dim, h*w)], 1))
        f_conv = f_all.permute(0, 2, 1, 3).reshape(x.shape[0], -1, x.shape[-2], x.shape[-1])
        
        out_conv = self.dep_conv(f_conv)

        return self.rate1 * out_att + self.rate2 * out_conv

3.2 更改init.py文件

关键步骤二:修改modules文件夹下的__init__.py文件,先导入函数

然后在下面的__all__中声明函数

3.3 添加yaml文件

关键步骤三:在/ultralytics/ultralytics/cfg/models/v8下面新建文件yolov8_ACmix.yaml文件,粘贴下面的内容

# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLOv8 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect

# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov8n.yaml' will call yolov8.yaml with scale 'n'
  # [depth, width, max_channels]
  n: [0.33, 0.25, 1024] # YOLOv8n summary: 225 layers,  3157200 parameters,  3157184 gradients,   8.9 GFLOPs
  s: [0.33, 0.50, 1024] # YOLOv8s summary: 225 layers, 11166560 parameters, 11166544 gradients,  28.8 GFLOPs
  m: [0.67, 0.75, 768] # YOLOv8m summary: 295 layers, 25902640 parameters, 25902624 gradients,  79.3 GFLOPs
  l: [1.00, 1.00, 512] # YOLOv8l summary: 365 layers, 43691520 parameters, 43691504 gradients, 165.7 GFLOPs
  x: [1.00, 1.25, 512] # YOLOv8x summary: 365 layers, 68229648 parameters, 68229632 gradients, 258.5 GFLOPs

# YOLOv8.0n backbone
backbone:
  # [from, repeats, module, args]
  - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
  - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
  - [-1, 3, C2f, [128, True]]
  - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  - [-1, 6, C2f, [256, True]]
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 6, C2f, [512, True]]
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 3, C2f, [1024, True]]
  - [-1, 1, ACmix, [1024]]
  - [-1, 1, SPPF, [1024, 5]] # 9

# YOLOv8.0n head
head:
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 6], 1, Concat, [1]] # cat backbone P4
  - [-1, 3, C2f, [512]] # 12

  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 4], 1, Concat, [1]] # cat backbone P3
  - [-1, 3, C2f, [256]] # 15 (P3/8-small)

  - [-1, 1, Conv, [256, 3, 2]]
  - [[-1, 13], 1, Concat, [1]] # cat head P4
  - [-1, 3, C2f, [512]] # 18 (P4/16-medium)


  - [-1, 1, Conv, [512, 3, 2]]
  - [[-1, 10], 1, Concat, [1]] # cat head P5
  - [-1, 3, C2f, [1024]] # 21 (P5/32-large)
  # - [-1, 1, CBAM, [1024]]

  - [[17, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)

温馨提示:本文只是对yolov8基础上添加模块,如果要对yolov8n/l/m/x进行添加则只需要指定对应的depth_multiple 和 width_multiple。


# YOLOv8n
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.25  # layer channel multiple
 
# YOLOv8s
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # layer channel multiple
 
# YOLOv8l 
depth_multiple: 1.0  # model depth multiple
width_multiple: 1.0  # layer channel multiple
 
# YOLOv8m
depth_multiple: 0.67  # model depth multiple
width_multiple: 0.75  # layer channel multiple
 
# YOLOv8x
depth_multiple: 1.33  # model depth multiple
width_multiple: 1.25  # layer channel multiple

3.4 在task.py中进行注册

elif m in [ACmix,]:
            c1, c2 = ch[f], args[0]
            if c2 != nc:  # if not output
                c2 = make_divisible(c2 * width, 8)
            args = [c1, *args[1:]]

3.5 执行程序

关键步骤五: 在ultralytics文件中新建train.py,将model的参数路径设置为yolov8_ACmix.yaml的路径即可

from ultralytics import YOLO
 
# Load a model
# model = YOLO('yolov8n.yaml')  # build a new model from YAML
# model = YOLO('yolov8n.pt')  # load a pretrained model (recommended for training)
 
model = YOLO(r'/projects/ultralytics/ultralytics/cfg/models/v8/yolov8_ACmix.yaml')  # build from YAML and transfer weights
 
# Train the model
model.train(device = [3], batch=16)

🚀运行程序,如果出现下面的内容则说明添加成功🚀

报错解决指南:

RuntimeError: Input type (float) and bias type (c10::Half) should be the same

在/ultralytics/ultralytics/engine/validator.py中的116行处添加下面代码即可  

self.args.half = False

4. 完整代码分享

https://pan.baidu.com/s/1z25xBCG2jnN3zVRglIUJdQ?pwd=n2wt

提取码: n2wt 

5. GFLOPs

关于GFLOPs的计算方式可以查看百面算法工程师 | 卷积基础知识——Convolution

未改进的YOLOv8nGFLOPs

img

改进后的GFLOPs

6. 进阶

ACmix可以结合CBAM添加在head部分,快去试试吧

7. 总结

ACmix注意力机制是一种结合了通道注意力和空间注意力的新型方法,旨在提升神经网络对不同特征的重要性判断能力。ACmix将通道注意力和空间注意力分别进行处理,通过逐层的方式对特征图进行筛选和加权,以突出重要的特征信息。在通道注意力部分,它会对每个通道的特征图进行加权,突出重要的通道信息,而在空间注意力部分,它会对特征图的每个空间位置进行加权,增强关键的空间信息。通过这种混合注意力机制,ACmix能够更好地捕捉和利用多尺度、多层次的特征信息,从而提高模型的表达能力和性能。

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kay_545

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值