优化改进YOLOv8算法之AKConv(可改变核卷积),即插即用的卷积,效果秒杀DSConv

本文主要是介绍优化改进YOLOv8算法之AKConv(可改变核卷积),即插即用的卷积,效果秒杀DSConv,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1 AKConv原理

1.1 Define the initial sampling position

1.2 Alterable convolutional operation

1.3 Extended AKConv

2 YOLOv8中加入AKConv模块

2.1 AKConv.py文件配置

2.2 task.py配置

2.3 创建添加优化点模块的yolov8-AKConv.yaml

2.4 训练 


1 AKConv原理

AKConv: Convolutional Kernel with Arbitrary Sampled Shapes andArbitrary Number of Parameters

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

1.1 Define the initial sampling position

定义初始采样位置卷积神经网络基于卷积运算,通过规则采样网格将特征定位在相应位置。在规则采样网格中给出了3×3卷积运算。设R表示采样网格,则R表示如下:

然而,采样网格是规则的,而AKConv的目标是不规则形状的卷积核。因此,为了允许不规则卷积核具有采样网格,我们创建了一种任意大小卷积的算法,该算法生成卷积核Pn的初始采样坐标。首先,我们将采样网格生成为规则采样网格,然后为剩余的采样点创建不规则网格,最后,我们将它们缝合以生成整体采样网格。伪代码如算法1所示。

如图2所示,它表明初始采样坐标是为任意大小的卷积生成的。正则卷积的采样网格以(0,0)点为中心。虽然不规则卷积在许多大小上都没有中心,但为了适应所使用的卷积的大小,我们在算法中设置左上角(0,0)点作为采样原点。 

在定义了不规则卷积的初始坐标Pn之后,位置P0处的相应卷积运算可以定义如下:

 这里,w表示卷积参数。然而,不规则的卷积运算是不可能实现的,因为不规则的采样坐标不能与相应大小的卷积运算相匹配,例如,大小为5、7和13的卷积。聪明的是,我们提出的AKConv实现了这一点。

1.2 Alterable convolutional operation

很明显,标准卷积采样位置是固定的,这导致卷积只能提取当前窗口的局部信息,而不能捕获其他位置的信息。可变形Conv通过卷积运算来学习偏移,以调整初始规则模式的采样网格。该方法在一定程度上弥补了卷积运算的不足。然而,标准卷积和可变形卷积是规则采样网格,不允许具有任意数量参数的卷积核。此外,随着卷积核的大小增加,它们的卷积参数的数量往往会增加一个平方,这对硬件环境来说是不友好的。因此,我们提出了一种新的可变卷积运算(AKConv)。如图3所示,它展示了尺寸为5的AKConv的整体结构。

与可变形Conv类似,在AKConv中,首先通过卷积运算获得相应核的偏移量,卷积运算的维数为(B,2N,H,W),其中N是卷积核的大小。以图3为例,N=5。然后,通过对偏移和原始坐标(P0+Pn)求和来获得修改后的坐标。最后通过插值和重采样获得相应位置的特征。很难提取与不规则卷积核的采样位置相对应的特征。为了解决这个问题,经过深入思考,我们发现有很多方法可以解决。在可变形Conv和RFAConv中,他们在空间维度上堆叠了3×3卷积特征。然后,使用步长为3的卷积运算来提取特征。但是,此方法针对方形采样形状。因此,可以将特征堆叠在行或列上,以使用列卷积或行卷积来提取与不规则采样形状相对应的特征。提取特征以使用适当大小和步长的卷积核。此外,我们可以将特征转换为四个维度(C,N,H,W),然后使用具有步长和卷积大小(N,1,1)的Conv3d来提取特征。当然,我们也可以将通道维度上的特征叠加到(CN,H,W),然后使用1×1卷积将维度降到(C,H,W)。因此,上述所有方法都可以提取与不规则采样形状相对应的特征。只需要重塑特征并使用相应的卷积运算。因此,在图3中,最后的“重塑”和“Conv”表示上述任何方法。此外,为了清楚地显示AKConv的过程,在图3中重新采样后,我们将卷积对应的特征的维度放在第三个维度中。然而,当代码被实现时,它位于最后一个维度。

根据RFAConv和Deformable Conv,我们在列方向上堆叠重新采样的特征,然后使用大小为(N,1)和步长为(N,1)的行卷积。因此,AKConv可以完美地完成不规则卷积特征提取过程。AKConv通过不规则卷积完成特征提取过程,可以根据偏移量灵活调整样本形状,为卷积采样形状带来更多探索选择。与标准卷积和可变形卷积不同,它们受到规则卷积核思想的限制。

1.3 Extended AKConv

我们认为AKConv的设计是一种新颖的设计,它实现了从不规则和任意采样的形状卷积核中提取特征的壮举。即使不使用可变形卷积中的偏移思想,AKConv仍然可以制作各种卷积核形状。因为,AKConv可以使用初始坐标进行重新采样,以显示各种更改。如图4所示,我们为大小为5的卷积设计了各种初始采样形状。在图4中,我们只展示了一些尺寸为5的示例。然而,AKConv的大小可以是任意的,因此,随着大小的增加,AKConv的初始卷积采样形状变得更加丰富甚至无限。考虑到目标形状在数据集之间变化,设计与采样形状相对应的卷积运算至关重要。AKConv通过根据相位特定域设计具有相应形状的卷积运算来完全实现这一点。它也可以类似于可变形Conv,通过添加可学习的偏移来动态适应对象的变化。对于特定的任务,卷积核的初始采样位置的设计是一个重要的问题,因为它是一个先验知识。与Qi等人一样,他们为细长管状结构分割任务提出了具有相应形状的采样坐标,但他们的形状选择仅适用于细长管状结构。

AKConv真正实现了对任意数量的任意形状进行卷积核运算的过程,它可以使卷积核呈现出各种形状。可变形卷积是为了弥补规则卷积的缺点而设计的。而DSConv是为特定的物体形状而设计的。他们没有探索任意大小的卷积和任意样本形状的卷积。AKConv的设计通过允许卷积运算通过Offset有效地提取不规则样本形状的特征来解决这些问题。AKConv允许卷积具有任意数量的卷积参数,并允许卷积呈现各种各样的形状。 

2 YOLOv8中加入AKConv模块

2.1 AKConv.py文件配置

下载YOLOv8官方代码,在ultralytics/nn文件夹新建extra_modules文件夹并新建AKConv.py文件

AKconv.py文件代码参考官方提供代码,如下

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.checkpoint as checkpoint
import math
import numpy as np
from einops import rearrangeclass AKConv(nn.Module):def __init__(self, inc, outc, num_param=5, stride=1, bias=None):super(AKConv, self).__init__()self.num_param = num_paramself.stride = strideself.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)@staticmethoddef _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 + 1q_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 pp = 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)# bilinearx_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_rtx_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_intmod_number = self.num_param % base_intp_n_x, p_n_y = torch.meshgrid(torch.arange(0, row_number),torch.arange(0, base_int))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))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-paddingdef _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))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_0def _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 + offsetreturn pdef _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)x_offset = x.gather(dim=-1, index=index).contiguous().view(b, c, h, w, N)return x_offset#  Stacking resampled features in the row direction.@staticmethoddef _reshape_x_offset(x_offset, num_param):b, c, h, w, n = x_offset.size()# using Conv3d# x_offset = x_offset.permute(0,1,4,2,3), then Conv3d(c,c_out, kernel_size =(num_param,1,1),stride=(num_param,1,1),bias= False)# using 1 × 1 Conv# x_offset = x_offset.permute(0,1,4,2,3), then, x_offset.view(b,c×num_param,h,w)  finally, Conv2d(c×num_param,c_out, kernel_size =1,stride=1,bias= False)# using the column conv as follow, then, Conv2d(inc, outc, kernel_size=(num_param, 1), stride=(num_param, 1), bias=bias)x_offset = rearrange(x_offset, 'b c h w n -> b c (h n) w')return x_offset

2.2 task.py配置

然后找到ultralytics/nn/tasks.py文件下里的parse_model函数,将类名加入进去,如下所示

2.3 创建添加优化点模块的yolov8-AKConv.yaml

# Ultralytics YOLO 🚀, GPL-3.0 license# Parameters
nc: 80  # number of classes
depth_multiple: 0.33  # scales module repeats
width_multiple: 0.25  # scales convolution channels# YOLOv8.0n backbone
backbone:# [from, repeats, module, args]- [-1, 1, Conv, [64, 3, 2]]  # 0-P1/2- [-1, 1, AKConv, [128, 6, 2]]  # 1-P2/4- [-1, 3, C2f, [128, True]]- [-1, 1, AKConv, [256, 6, 2]]  # 3-P3/8- [-1, 6, C2f, [256, True]]- [-1, 1, AKConv, [512, 6, 2]]  # 5-P4/16- [-1, 6, C2f, [512, True]]- [-1, 1, AKConv, [1024, 6, 2]]  # 7-P5/32- [-1, 3, C2f, [1024, True]]- [-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, AKConv, [256, 6, 2]]- [[-1, 12], 1, Concat, [1]]  # cat head P4- [-1, 3, C2f, [512]]  # 18 (P4/16-medium)- [-1, 1, AKConv, [512, 6, 2]]- [[-1, 9], 1, Concat, [1]]  # cat head P5- [-1, 3, C2f, [1024]]  # 21 (P5/32-large)- [[15, 18, 21], 1, Detect, [nc]]  # Detect(P3, P4, P5)

2.4 训练 

完成上述配置之后,可以对数据集进行训练

这篇关于优化改进YOLOv8算法之AKConv(可改变核卷积),即插即用的卷积,效果秒杀DSConv的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/590308

相关文章

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

一文详解SpringBoot响应压缩功能的配置与优化

《一文详解SpringBoot响应压缩功能的配置与优化》SpringBoot的响应压缩功能基于智能协商机制,需同时满足很多条件,本文主要为大家详细介绍了SpringBoot响应压缩功能的配置与优化,需... 目录一、核心工作机制1.1 自动协商触发条件1.2 压缩处理流程二、配置方案详解2.1 基础YAML

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

MySQL中慢SQL优化的不同方式介绍

《MySQL中慢SQL优化的不同方式介绍》慢SQL的优化,主要从两个方面考虑,SQL语句本身的优化,以及数据库设计的优化,下面小编就来给大家介绍一下有哪些方式可以优化慢SQL吧... 目录避免不必要的列分页优化索引优化JOIN 的优化排序优化UNION 优化慢 SQL 的优化,主要从两个方面考虑,SQL 语

MySQL中慢SQL优化方法的完整指南

《MySQL中慢SQL优化方法的完整指南》当数据库响应时间超过500ms时,系统将面临三大灾难链式反应,所以本文将为大家介绍一下MySQL中慢SQL优化的常用方法,有需要的小伙伴可以了解下... 目录一、慢SQL的致命影响二、精准定位问题SQL1. 启用慢查询日志2. 诊断黄金三件套三、六大核心优化方案方案

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、