YOLO11改进|卷积篇|引入空间通道重组卷积ScConv

发布于:2024-10-09 ⋅ 阅读:(137) ⋅ 点赞:(0)

在这里插入图片描述

一、【SCConv】卷积

1.1【SCConv】卷积介绍

在这里插入图片描述

SCConv 模块提供了一种新的视角来看待CNNs的特征提取过程,提出了一种更有效地利用空间和通道冗余的方法,从而在减少冗余特征的同时提高模型性能。主要是结合空间和通道的重组卷积,此模块目标在于减少视觉任务中由于冗余特征提取而产生的计算成本。下面将简单介绍一下空间重组卷积和通道重组卷积

  • 空间重组卷积
    空间重组卷积的结构如下,下面是其工作流程
  1. 输入和分离阶段 (Separate):
    输入特征 X:输入的是一组特征图(如卷积层的输出)。
    Group Normalization (GN):首先对输入特征应用 GN(组归一化)操作,以标准化特征图,使模型能够更好地适应不同的批次。
    γi 权重计算:对归一化后的特征图,根据各通道的激活值,计算一组通道加权系数 γi。
    归一化后的通道权重 (wi):通过将 γi 归一化,得到权重 wi,用于控制各个通道的重要性。
    S(Sigmoid)和T(Threshold):该步骤包括 Sigmoid 函数和阈值控制,Sigmoid 函数平滑各个通道的输出,并通过阈值操作进一步筛选重要的空间信息。
  2. 重建阶段 (Reconstruct):
    W1 和 W2 加权矩阵:输入特征在不同权重矩阵 W1 和 W2 的作用下,生成不同分支的特征图。
    空间重构 (Spatial Reconstruction):每个分支将特征图进一步拆分和重构,生成多尺度的空间特征。比如图中的 𝑋11𝑤、𝑋21𝑤等是不同分支重构的结果。
    重构特征组合 (Summation & Concatenation):将不同分支的重构特征进行加和(⊕)和拼接(C),得到最终的空间重构特征 X^w。
  3. 模块优势:
    增强空间信息表达:通过对输入特征进行分离处理,权重自适应调节不同通道的响应,有效提升了模型对空间信息的表达能力。
    多尺度重建:通过不同加权矩阵的作用,模块能够生成多个尺度的特征图,有利于捕捉不同尺度的空间结构,从而更好地处理复杂场景中的细节信息。
    特征融合和选择性增强:通过多分支加权组合,能够有效地筛选并融合最重要的空间信息,提升了模型对边缘细节和局部特征的捕捉能力。

在这里插入图片描述 - [ ] 通道重组卷积
下面是通道卷积的结构图,以下是对其结构的分析

  1. 输入与分离阶段 (Split):
    输入特征 X^w:这是从前一个空间重构单元(Spatial Reconstruction Unit)输出的空间优化特征,具有 C 个通道。
    通道划分 (Channel Split):将输入特征通道数按比例 α 进行拆分,得到 αC 和 (1-α)C 个通道。这个划分是为了分别处理不同重要性的通道。
    每部分分别经过一个 1x1 卷积层,用来减少通道间的维度冗余并提高效率。
  2. 转换阶段 (Transform):
    GWC (Group-wise Convolution) 和 PWC (Point-wise Convolution):对划分后的通道分别应用 GWC 和 PWC 操作。
    GWC:将通道分组进行卷积运算,有助于局部上下文的捕捉。
    PWC:对每个通道独立进行卷积操作,从而保留全局信息并提高计算效率。
    生成特征 Y1 和 Y2:GWC 处理后的特征记作 X_up,PWC 处理后的特征记作 X_low,然后分别产生新的特征 Y1 和 Y2。
  3. 融合阶段 (Fuse):
    池化与权重生成:通过池化操作提取不同通道的重要性信息,生成两个权重分支 S1 和 S2,然后通过 SoftMax 进行归一化,得到权重 β1 和 β2。这些权重用于控制不同通道特征的贡献。
    通道融合:将权重 β1 和 β2 分别与特征 Y1 和 Y2 做逐元素相乘操作,控制它们对最终输出的影响。
    元素加和:将两个加权后的特征进行逐元素相加,得到最终的 通道优化特征 Y。
  4. 模块优势:
    通道自适应增强:通过对不同通道进行加权分离处理,该模块可以对高低频通道特征进行分别优化,提升通道信息的表达能力。
    高效计算:通过 GWC 和 PWC 的结合,既能捕捉到局部上下文,也能有效地保留全局信息,保证高效的计算同时提升特征表达的精确性。
    自适应权重分配:通过池化和 SoftMax 生成的权重动态调整各个通道的贡献,使得模型能够更智能地筛选出重要的通道信息,进一步提升特征质量。

在这里插入图片描述

1.2【SCConv】核心代码

import torch
import torch.nn.functional as F
import torch.nn as nn


class GroupBatchnorm2d(nn.Module):
    def __init__(self, c_num: int,
                 group_num: int = 16,
                 eps: float = 1e-10
                 ):
        super(GroupBatchnorm2d, self).__init__()
        assert c_num >= group_num
        self.group_num = group_num
        self.gamma = nn.Parameter(torch.randn(c_num, 1, 1))
        self.beta = nn.Parameter(torch.zeros(c_num, 1, 1))
        self.eps = eps

    def forward(self, x):
        N, C, H, W = x.size()
        x = x.view(N, self.group_num, -1)
        mean = x.mean(dim=2, keepdim=True)
        std = x.std(dim=2, keepdim=True)
        x = (x - mean) / (std + self.eps)
        x = x.view(N, C, H, W)
        return x * self.gamma + self.beta


class SRU(nn.Module):
    def __init__(self,
                 oup_channels: int,
                 group_num: int = 16,
                 gate_treshold: float = 0.5
                 ):
        super().__init__()

        self.gn = GroupBatchnorm2d(oup_channels, group_num=group_num)
        self.gate_treshold = gate_treshold
        self.sigomid = nn.Sigmoid()

    def forward(self, x):
        gn_x = self.gn(x)
        w_gamma = F.softmax(self.gn.gamma, dim=0)
        reweigts = self.sigomid(gn_x * w_gamma)
        # Gate
        info_mask = w_gamma > self.gate_treshold
        noninfo_mask = w_gamma <= self.gate_treshold
        x_1 = info_mask * reweigts * x
        x_2 = noninfo_mask * reweigts * x
        x = self.reconstruct(x_1, x_2)
        return x

    def reconstruct(self, x_1, x_2):
        x_11, x_12 = torch.split(x_1, x_1.size(1) // 2, dim=1)
        x_21, x_22 = torch.split(x_2, x_2.size(1) // 2, dim=1)
        return torch.cat([x_11 + x_22, x_12 + x_21], dim=1)


class CRU(nn.Module):
    '''
    alpha: 0<alpha<1
    '''

    def __init__(self,
                 op_channel: int,
                 alpha: float = 1 / 2,
                 squeeze_radio: int = 2,
                 group_size: int = 2,
                 group_kernel_size: int = 3,
                 ):
        super().__init__()
        self.up_channel = up_channel = int(alpha * op_channel)
        self.low_channel = low_channel = op_channel - up_channel
        self.squeeze1 = nn.Conv2d(up_channel, up_channel // squeeze_radio, kernel_size=1, bias=False)
        self.squeeze2 = nn.Conv2d(low_channel, low_channel // squeeze_radio, kernel_size=1, bias=False)
        # up
        self.GWC = nn.Conv2d(up_channel // squeeze_radio, op_channel, kernel_size=group_kernel_size, stride=1,
                             padding=group_kernel_size // 2, groups=group_size)
        self.PWC1 = nn.Conv2d(up_channel // squeeze_radio, op_channel, kernel_size=1, bias=False)
        # low
        self.PWC2 = nn.Conv2d(low_channel // squeeze_radio, op_channel - low_channel // squeeze_radio, kernel_size=1,
                              bias=False)
        self.advavg = nn.AdaptiveAvgPool2d(1)

    def forward(self, x):
        # Split
        up, low = torch.split(x, [self.up_channel, self.low_channel], dim=1)
        up, low = self.squeeze1(up), self.squeeze2(low)
        # Transform
        Y1 = self.GWC(up) + self.PWC1(up)
        Y2 = torch.cat([self.PWC2(low), low], dim=1)
        # Fuse
        out = torch.cat([Y1, Y2], dim=1)
        out = F.softmax(self.advavg(out), dim=1) * out
        out1, out2 = torch.split(out, out.size(1) // 2, dim=1)
        return out1 + out2


class ScConv(nn.Module):
    def __init__(self,
                 op_channel: int,
                 group_num: int = 16,
                 gate_treshold: float = 0.5,
                 alpha: float = 1 / 2,
                 squeeze_radio: int = 2,
                 group_size: int = 2,
                 group_kernel_size: int = 3,
                 ):
        super().__init__()
        self.SRU = SRU(op_channel,
                       group_num=group_num,
                       gate_treshold=gate_treshold)
        self.CRU = CRU(op_channel,
                       alpha=alpha,
                       squeeze_radio=squeeze_radio,
                       group_size=group_size,
                       group_kernel_size=group_kernel_size)

    def forward(self, x):
        x = self.SRU(x)
        x = self.CRU(x)
        return x



二、添加【SCConv】卷积

2.1STEP1

首先找到ultralytics/nn文件路径下新建一个Add-module的python文件包【这里注意一定是python文件包,新建后会自动生成_init_.py】,如果已经跟着我的教程建立过一次了可以省略此步骤,随后新建一个SCConv.py文件并将上文中提到的注意力机制的代码全部粘贴到此文件中,如下图所示在这里插入图片描述

2.2STEP2

在STEP1中新建的_init_.py文件中导入增加改进模块的代码包如下图所示在这里插入图片描述

2.3STEP3

找到ultralytics/nn文件夹中的task.py文件,在其中按照下图添加在这里插入图片描述

2.4STEP4

定位到ultralytics/nn文件夹中的task.py文件中的def parse_model(d, ch, verbose=True): # model_dict, input_channels(3)函数添加如图代码,【如果不好定位可以直接ctrl+f搜索定位】

在这里插入图片描述

三、yaml文件与运行

3.1yaml文件

以下是添加【SCConv】卷积在Backbone中的yaml文件

# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLO11 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=yolo11n.yaml' will call yolo11.yaml with scale 'n'
  # [depth, width, max_channels]
  n: [0.50, 0.25, 1024] # summary: 319 layers, 2624080 parameters, 2624064 gradients, 6.6 GFLOPs
  s: [0.50, 0.50, 1024] # summary: 319 layers, 9458752 parameters, 9458736 gradients, 21.7 GFLOPs
  m: [0.50, 1.00, 512] # summary: 409 layers, 20114688 parameters, 20114672 gradients, 68.5 GFLOPs
  l: [1.00, 1.00, 512] # summary: 631 layers, 25372160 parameters, 25372144 gradients, 87.6 GFLOPs
  x: [1.00, 1.50, 512] # summary: 631 layers, 56966176 parameters, 56966160 gradients, 196.0 GFLOPs

# YOLO11n 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, 2, C3k2, [256, False, 0.25]]
  - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  - [-1, 2, C3k2, [512, False, 0.25]]
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 2, C3k2, [512, True]]
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 2, C3k2, [1024, True]]
  - [-1, 1, ScConv,  []]
  - [-1, 1, SPPF, [1024, 5]] # 9
  - [-1, 2, C2PSA, [1024]] # 10

# YOLO11n head
head:
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 6], 1, Concat, [1]] # cat backbone P4
  - [-1, 2, C3k2, [512, False]] # 13

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

  - [-1, 1, Conv, [256, 3, 2]]
  - [[-1, 14], 1, Concat, [1]] # cat head P4
  - [-1, 2, C3k2, [512, False]] # 19 (P4/16-medium)

  - [-1, 1, Conv, [512, 3, 2]]
  - [[-1, 11], 1, Concat, [1]] # cat head P5
  - [-1, 2, C3k2, [1024, True]] # 22 (P5/32-large)

  - [[17, 20, 23], 1, Detect, [nc]] # Detect(P3, P4, P5)

以下是添加【SCConv】卷积在大目标检测层中的yaml文件

# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLO11 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=yolo11n.yaml' will call yolo11.yaml with scale 'n'
  # [depth, width, max_channels]
  n: [0.50, 0.25, 1024] # summary: 319 layers, 2624080 parameters, 2624064 gradients, 6.6 GFLOPs
  s: [0.50, 0.50, 1024] # summary: 319 layers, 9458752 parameters, 9458736 gradients, 21.7 GFLOPs
  m: [0.50, 1.00, 512] # summary: 409 layers, 20114688 parameters, 20114672 gradients, 68.5 GFLOPs
  l: [1.00, 1.00, 512] # summary: 631 layers, 25372160 parameters, 25372144 gradients, 87.6 GFLOPs
  x: [1.00, 1.50, 512] # summary: 631 layers, 56966176 parameters, 56966160 gradients, 196.0 GFLOPs

# YOLO11n 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, 2, C3k2, [256, False, 0.25]]
  - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  - [-1, 2, C3k2, [512, False, 0.25]]
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 2, C3k2, [512, True]]
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 2, C3k2, [1024, True]]
  - [-1, 1, SPPF, [1024, 5]] # 9
  - [-1, 2, C2PSA, [1024]] # 10

# YOLO11n head
head:
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 6], 1, Concat, [1]] # cat backbone P4
  - [-1, 2, C3k2, [512, False]] # 13

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

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

  - [-1, 1, Conv, [512, 3, 2]]
  - [[-1, 10], 1, Concat, [1]] # cat head P5
  - [-1, 2, C3k2, [1024, True]] # 22 (P5/32-large)
  - [-1, 1, ScConv,[]]

  - [[16, 19, 23], 1, Detect, [nc]] # Detect(P3, P4, P5)

以上添加位置仅供参考,具体添加位置以及模块效果以自己的数据集结果为准 ,你也可以加到小目标层下或者所有检测头下

3.2运行成功截图

在这里插入图片描述

OK 以上就是添加【ScConv】卷积的全部过程了,后续将持续更新尽情期待

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到