YOLO11 改进 – 注意力机制 _ CPCA(Channel prior convolutional attention)通道先验卷积注意力:轻量级设计破解权重分布难题,增强小目标显著性

前言

本文介绍了通道先验卷积注意力(CPCA)机制及其在YOLOv11中的结合应用。CPCA结合通道注意力与空间注意力,通过多尺度深度可分离卷积模块,有效提取空间关系并保留通道先验。通道注意力学习各通道重要性,空间注意力捕捉特征图位置关系。我们将CPCA模块集成进YOLOv11,替换部分原有模块。实验表明,基于CPCA的CPCANet在医学图像分割任务中,用较少计算资源实现了优于先进算法的分割性能,展现了CPCA在目标检测领域的应用潜力。

文章目录: YOLOv11改进大全:卷积层、轻量化、注意力机制、损失函数、Backbone、SPPF、Neck、检测头全方位优化汇总

专栏链接: YOLOv11改进专栏

文章目录

[TOC]

介绍

摘要

医学图像普遍呈现低对比度特征及显著的器官形态变异特性,现有注意力机制往往难以有效适应医学图像分割的性能提升需求。本文提出了一种高效的通道优先卷积注意力机制(CPCA),该机制能够在通道与空间维度上实现动态注意力权重分配。通过引入多尺度深度卷积模块,CPCA在有效提取空间关系的同时保持了通道优先的设计原则,具备关注信息丰富通道及关键区域的能力。基于CPCA架构,我们构建了专门针对医学图像分割任务的深度神经网络CPCANet。该网络在两个公开医学图像数据集上进行了系统性验证,实验结果表明,相较于当前最先进的算法,CPCANet在显著减少计算资源需求的条件下实现了分割性能的实质性提升。相关实现代码已在https://github.com/Cuthbert-Huang/CPCANet平台开源发布。

文章链接

论文地址: 论文地址

代码地址: 代码地址

基本原理

通道先验卷积注意力(Channel Prior Convolutional Attention,CPCA)是一种用于增强特征表示和动态分配注意力权重的注意力机制。CPCA结合了通道注意力和空间注意力,通过多尺度深度可分离卷积模块有效地提取空间关系并保留通道先验。下面是CPCA的详细技术原理:

  1. 通道注意力(Channel Attention)

    • 通道注意力模块用于动态学习每个通道的重要性,以提高特征的表征能力。通道注意力通过以下步骤实现:

      • 对输入特征进行全局平均池化和全局最大池化,得到两个不同的特征表示。

      • 将这两个特征表示分别通过两个卷积层和激活函数处理,以提取通道之间的关系。

      • 将处理后的特征通过Sigmoid函数生成通道注意力权重,用于动态调整每个通道的重要性。

  2. 空间注意力(Spatial Attention)

    • 空间注意力模块用于捕捉特征图中不同位置之间的关系,以提高空间信息的表征。空间注意力通过多尺度深度可分离卷积模块实现,可以有效地提取空间关系。

    • 多尺度深度可分离卷积模块使用不同大小的卷积核来捕获多尺度信息,从而更好地理解特征图的空间结构。

  3. CPCA的整体原理

    • CPCA结合了通道注意力和空间注意力,通过多尺度深度可分离卷积模块实现动态分配注意力权重,并保留通道先验。这种结合可以帮助网络更好地捕捉重要的特征,并提高特征的表征能力。

通道注意力模块

**通道注意力: 沿袭了 CBAM 的方法 ,**采用平均池和最大池操作,从特征映射中聚集空间信息,然后再输入共享的 MLP 中:

𝐶𝐴(𝐹)=σ(𝑀𝐿𝑃(𝐴𝑣𝑔𝑃𝑜𝑜𝑙(𝐹))+𝑀𝐿𝑃(𝑀𝑎𝑥𝑃𝑜𝑜𝑙(𝐹)))

其中 σ 是 sigmoid。

具体来说,通道注意力模块的代码实现包括以下步骤:

  1. 对输入特征进行平均池化和最大池化操作,得到两个池化后的特征表示。

  2. 将两个池化后的特征表示分别通过两个卷积层和ReLU激活函数处理,以提取特征之间的关系。

  3. 将处理后的特征通过 torch.sigmoid 函数生成通道注意力权重,用于动态调整每个通道的重要性。

  4. 最终将通道注意力权重应用到输入特征上,得到经过通道注意力处理后的特征表示。

核心代码

class ChannelAttention(nn.Module):

    def __init__(self, input_channels, internal_neurons):
        super(ChannelAttention, self).__init__()
        self.fc1 = nn.Conv2d(in_channels=input_channels, out_channels=internal_neurons, kernel_size=1, stride=1, bias=True)
        self.fc2 = nn.Conv2d(in_channels=internal_neurons, out_channels=input_channels, kernel_size=1, stride=1, bias=True)
        self.input_channels = input_channels

    def forward(self, inputs):
        x1 = F.adaptive_avg_pool2d(inputs, output_size=(1, 1))
        # print('x:', x.shape)
        x1 = self.fc1(x1)
        x1 = F.relu(x1, inplace=True)
        x1 = self.fc2(x1)
        x1 = torch.sigmoid(x1)
        x2 = F.adaptive_max_pool2d(inputs, output_size=(1, 1))
        # print('x:', x.shape)
        x2 = self.fc1(x2)
        x2 = F.relu(x2, inplace=True)
        x2 = self.fc2(x2)
        x2 = torch.sigmoid(x2)
        x = x1 + x2
        x = x.view(-1, self.input_channels, 1, 1)
        return x

class RepBlock(nn.Module):

    def __init__(self, in_channels, out_channels,
                 channelAttention_reduce=4):
        super().__init__()

        self.C = in_channels
        self.O = out_channels

        assert in_channels == out_channels
        self.ca = ChannelAttention(input_channels=in_channels, internal_neurons=in_channels // channelAttention_reduce)
        self.dconv5_5 = nn.Conv2d(in_channels,in_channels,kernel_size=5,padding=2,groups=in_channels)
        self.dconv1_7 = nn.Conv2d(in_channels,in_channels,kernel_size=(1,7),padding=(0,3),groups=in_channels)
        self.dconv7_1 = nn.Conv2d(in_channels,in_channels,kernel_size=(7,1),padding=(3,0),groups=in_channels)
        self.dconv1_11 = nn.Conv2d(in_channels,in_channels,kernel_size=(1,11),padding=(0,5),groups=in_channels)
        self.dconv11_1 = nn.Conv2d(in_channels,in_channels,kernel_size=(11,1),padding=(5,0),groups=in_channels)
        self.dconv1_21 = nn.Conv2d(in_channels,in_channels,kernel_size=(1,21),padding=(0,10),groups=in_channels)
        self.dconv21_1 = nn.Conv2d(in_channels,in_channels,kernel_size=(21,1),padding=(10,0),groups=in_channels)
        self.conv = nn.Conv2d(in_channels,in_channels,kernel_size=(1,1),padding=0)
        self.act = nn.GELU()

    def forward(self, inputs):
        #   Global Perceptron
        inputs = self.conv(inputs)
        inputs = self.act(inputs)

        channel_att_vec = self.ca(inputs)
        inputs = channel_att_vec * inputs

        x_init = self.dconv5_5(inputs)
        x_1 = self.dconv1_7(x_init)
        x_1 = self.dconv7_1(x_1)
        x_2 = self.dconv1_11(x_init)
        x_2 = self.dconv11_1(x_2)
        x_3 = self.dconv1_21(x_init)
        x_3 = self.dconv21_1(x_3)
        x = x_1 + x_2 + x_3 + x_init
        spatial_att = self.conv(x)
        out = spatial_att * inputs
        out = self.conv(out)
        return out

实验

脚本

import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLO

if __name__ == '__main__':
#     修改为自己的配置文件地址
    model = YOLO('/root/ultralytics-main/ultralytics/cfg/models/11/yolov11-CPCA.yaml')
#     修改为自己的数据集地址
    model.train(data='/root/ultralytics-main/ultralytics/cfg/datasets/coco8.yaml',
                cache=False,
                imgsz=640,
                epochs=10,
                single_cls=False,  # 是否是单类别检测
                batch=8,
                close_mosaic=10,
                workers=0,
                optimizer='SGD',
                amp=True,
                project='runs/train',
                name='CPCA',
                )

结果

THE END