当前位置: 首页 > article >正文

YOLOv10改进 | YOLOv10引入AKConv(轻量)

1、AKConv介绍

论文摘要:基于卷积运算只的神经网络 口在深度学习只领域取得了令人瞩目的成果,但标准卷积运算存在两个固有的缺陷。 一方面,卷积运算仅限于局部窗口,无法捕获其他位置的信息,并且其采样形状是固定的。 另一方面,卷积核的大小固定为kxk,是一个固定的正方形,参数的数量往往随大小呈平方增长。 很明显,不同数据集和不同位置的目标的形状和大小是不同的。 具有固定样本形状和正方形的卷积核不能很好地适应不断变化的目标。 针对上述问题,本工作探索了可改变核卷积(AKConv),它赋予卷积核任意数量的参数和任意采样形状,为网络开销和性能之间的权衡提供更丰富的选择。 在 AKConv 中,我们通过新的坐标生成算法定义任意大小的卷积核的初始位置。为了适应目标的变化,我们引入了偏移量来调整每个位置的样本形状。此外,我们通过使用具有相同大小和不同初始采样形状的AKCony 来探索神经网络的效果。 AKCony 通过不规则卷积运算完成高效特征提取的过程,为卷积采样形状带来更多探索选择。 在代表件数据集 COCO2017、VOC 7+12和 VisDrone-DET2021上进行的物体检测实验充分展示了 AKCony 的优势。AKCony可以作为即插即用的卷积运算来替代卷积运算来提高网络性能。

官方论文地址:2311.11587v2

AKConv模块结构图如下:

说明:

  • 输入:输入图像(C,H,W)。
  • 初始采样形状:给出卷积核的初始采样形状。
  • 卷积操作:使用Conv2d对输入图像执行卷积操作。
  • 偏移:通过学习得到的偏移量来调整初始采样形状。AKConv的关键,允许卷积核形状动态调整以适应图像的特征。
  • 重采样:根据调整后的采样形状对特征图进行重采样。
  • 输出管道:重采样后的特征图经过重塑、再次卷积、标准化,最后通过激活函数QSiLU输出最终结果。

2、AKConv模块代码

import torch.nn as nn
import torch
from einops import rearrange
import math
 
 
class AKConv(nn.Module):
    def __init__(self, inc, outc, num_param, stride=1, bias=None):
        super(AKConv, self).__init__()
        self.num_param = num_param
        self.stride = stride
        self.conv = nn.Sequential(nn.Conv2d(inc, outc, kernel_size=(num_param, 1), stride=(num_param, 1), bias=bias),
                                  nn.BatchNorm2d(outc),
                                  nn.SiLU())  # the conv adds the BN and SiLU to compare original Conv in YOLOv5.
        self.p_conv = nn.Conv2d(inc, 2 * num_param, kernel_size=3, padding=1, stride=stride)
        nn.init.constant_(self.p_conv.weight, 0)
        self.p_conv.register_full_backward_hook(self._set_lr)
 
    @staticmethod
    def _set_lr(module, grad_input, grad_output):
        grad_input = (grad_input[i] * 0.1 for i in range(len(grad_input)))
        grad_output = (grad_output[i] * 0.1 for i in range(len(grad_output)))
 
    def forward(self, x):
        # N is num_param.
        offset = self.p_conv(x)
        dtype = offset.data.type()
        N = offset.size(1) // 2
        # (b, 2N, h, w)
        p = self._get_p(offset, dtype)
 
        # (b, h, w, 2N)
        p = p.contiguous().permute(0, 2, 3, 1)
        q_lt = p.detach().floor()
        q_rb = q_lt + 1
 
        q_lt = torch.cat([torch.clamp(q_lt[..., :N], 0, x.size(2) - 1), torch.clamp(q_lt[..., N:], 0, x.size(3) - 1)],
                         dim=-1).long()
        q_rb = torch.cat([torch.clamp(q_rb[..., :N], 0, x.size(2) - 1), torch.clamp(q_rb[..., N:], 0, x.size(3) - 1)],
                         dim=-1).long()
        q_lb = torch.cat([q_lt[..., :N], q_rb[..., N:]], dim=-1)
        q_rt = torch.cat([q_rb[..., :N], q_lt[..., N:]], dim=-1)
 
        # clip p
        p = torch.cat([torch.clamp(p[..., :N], 0, x.size(2) - 1), torch.clamp(p[..., N:], 0, x.size(3) - 1)], dim=-1)
 
        # bilinear kernel (b, h, w, N)
        g_lt = (1 + (q_lt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_lt[..., N:].type_as(p) - p[..., N:]))
        g_rb = (1 - (q_rb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_rb[..., N:].type_as(p) - p[..., N:]))
        g_lb = (1 + (q_lb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_lb[..., N:].type_as(p) - p[..., N:]))
        g_rt = (1 - (q_rt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_rt[..., N:].type_as(p) - p[..., N:]))
 
        # resampling the features based on the modified coordinates.
        x_q_lt = self._get_x_q(x, q_lt, N)
        x_q_rb = self._get_x_q(x, q_rb, N)
        x_q_lb = self._get_x_q(x, q_lb, N)
        x_q_rt = self._get_x_q(x, q_rt, N)
 
        # bilinear
        x_offset = g_lt.unsqueeze(dim=1) * x_q_lt + \
                   g_rb.unsqueeze(dim=1) * x_q_rb + \
                   g_lb.unsqueeze(dim=1) * x_q_lb + \
                   g_rt.unsqueeze(dim=1) * x_q_rt
 
        x_offset = self._reshape_x_offset(x_offset, self.num_param)
        out = self.conv(x_offset)
 
        return out
 
    # generating the inital sampled shapes for the AKConv with different sizes.
    def _get_p_n(self, N, dtype):
        base_int = round(math.sqrt(self.num_param))
        row_number = self.num_param // base_int
        mod_number = self.num_param % base_int
        p_n_x, p_n_y = torch.meshgrid(
            torch.arange(0, row_number),
            torch.arange(0, base_int), indexing='xy')
        p_n_x = torch.flatten(p_n_x)
        p_n_y = torch.flatten(p_n_y)
        if mod_number > 0:
            mod_p_n_x, mod_p_n_y = torch.meshgrid(
                torch.arange(row_number, row_number + 1),
                torch.arange(0, mod_number), indexing='xy')
 
            mod_p_n_x = torch.flatten(mod_p_n_x)
            mod_p_n_y = torch.flatten(mod_p_n_y)
            p_n_x, p_n_y = torch.cat((p_n_x, mod_p_n_x)), torch.cat((p_n_y, mod_p_n_y))
        p_n = torch.cat([p_n_x, p_n_y], 0)
        p_n = p_n.view(1, 2 * N, 1, 1).type(dtype)
        return p_n
 
    # no zero-padding
    def _get_p_0(self, h, w, N, dtype):
        p_0_x, p_0_y = torch.meshgrid(
            torch.arange(0, h * self.stride, self.stride),
            torch.arange(0, w * self.stride, self.stride), indexing='xy')
 
        p_0_x = torch.flatten(p_0_x).view(1, 1, h, w).repeat(1, N, 1, 1)
        p_0_y = torch.flatten(p_0_y).view(1, 1, h, w).repeat(1, N, 1, 1)
        p_0 = torch.cat([p_0_x, p_0_y], 1).type(dtype)
 
        return p_0
 
    def _get_p(self, offset, dtype):
        N, h, w = offset.size(1) // 2, offset.size(2), offset.size(3)
 
        # (1, 2N, 1, 1)
        p_n = self._get_p_n(N, dtype)
        # (1, 2N, h, w)
        p_0 = self._get_p_0(h, w, N, dtype)
        p = p_0 + p_n + offset
        return p
 
    def _get_x_q(self, x, q, N):
        b, h, w, _ = q.size()
        padded_w = x.size(3)
        c = x.size(1)
        # (b, c, h*w)
        x = x.contiguous().view(b, c, -1)
 
        # (b, h, w, N)
        index = q[..., :N] * padded_w + q[..., N:]  # offset_x*w + offset_y
        # (b, c, h*w*N)
 
        index = index.contiguous().unsqueeze(dim=1).expand(-1, c, -1, -1, -1).contiguous().view(b, c, -1)
        index = index.clamp(min=0, max=x.shape[-1] - 1)
 
        x_offset = x.gather(dim=-1, index=index).contiguous().view(b, c, h, w, N)
 
        return x_offset
 
    @staticmethod
    def _reshape_x_offset(x_offset, num_param):
        b, c, h, w, n = x_offset.size()
        x_offset = rearrange(x_offset, 'b c h w n -> b c (h n) w')
        return x_offset

3、在yolov10中添加AKConv模块

(1)在ultralytics /nn下新建Extramodule

     

     

(2)在Extramodule里创建AKConv.py文件,将上述代码放进AKConv.py文件。

     

(3)添加完AKConv代码后,在ultralytics/nn/Extramodule/init .py文件中引用

     

(4)在ultralytics/nn/tasks.py文件里引用Extramodule

     

(5)在tasks.py找到parse model(ctrl+f可以直接搜索parse model位置)
添加如下代码:

     

4、新建一个yolov10nAKConv.yaml文件

新建一个yolov10nAKConv.yaml文件后,在Backbone部分添加了AKConv模块,大家可以在其他地方多尝试。

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

# Parameters
nc: 2 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov10n.yaml' will call yolov10.yaml with scale 'n'
  # [depth, width, max_channels]
  s: [0.33, 0.50, 1024]

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

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

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

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

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

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

5、模型训练

    

6、模型结构打印,成功运行

    


http://www.kler.cn/a/514928.html

相关文章:

  • dp 凸优化
  • 快速入门Flink
  • Games104——渲染中光和材质的数学魔法
  • 寒假刷题记录
  • Java复习第四天
  • 【深度学习项目】语义分割-DeepLab网络(DeepLabV3介绍、基于Pytorch实现DeepLabV3网络)
  • YOLO 安装 并且命令行指定配置文件
  • 文件快递柜:匿名口令分享工具,轻松安全地存取文本与文件
  • Centos 修改历史读录( HISTSIZE)
  • 力扣-数组-414 第三大的数
  • 蓝桥杯备考:红黑树与map和set
  • 【Block总结】PConv风车卷积,更大的感受野,提高特征提取能力|即插即用
  • K8S中Service详解(一)
  • gesp(C++五级)(14)洛谷:B4071:[GESP202412 五级] 武器强化
  • docker安装elk6.7.1-搜集nginx-json日志
  • docker安装elk6.7.1-搜集java日志
  • SparkSQL函数综合实践
  • jinja2.exceptions.UndefinedError: ‘enumerate‘ is undefined
  • 汽车OEMs一般出于什么目的来自定义Autosar CP一些内容
  • 2501,进度控件
  • Unity3D项目开发中的资源加密详解
  • jenkins-pipeline 动态生成参数
  • Codeforces Round 1000 (Div. 2)(前三题)
  • Maven的下载安装配置
  • 每日一题--比较版本号
  • Qt中的Item Widget组控件:QListWidget、QTreeWidget 和 QTableWidget使用方法(详细图文教程)