原论文摘要
引入了可变形卷积 v4 (DCNv4),这是一种为广泛视觉应用设计的高效且有效的操作算子。DCNv4通过两项关键增强解决了其前身DCNv3的局限性:1. 移除空间聚合中的softmax归一化,以增强其动态特性和表达能力;2. 优化内存访问以最小化冗余操作,从而加速计算。这些改进使得DCNv4相比DCNv3显著加快了收敛速度,并且处理速度大幅提升,前向传播速度超过三倍。DCNv4在多个任务中表现出色,包括图像分类、实例和语义分割,特别是在图像生成方面表现突出。当将DCNv4集成到生成模型(如潜在扩散模型中的U-Net)中时,它超越了基线模型,凸显了其提升生成模型性能的潜力。在实际应用中,将DCNv3替换为DCNv4并应用于InternImage模型创建FlashInternImage,速度提高高达80%,并且在无需进一步修改的情况下性能进一步提升。DCNv4在速度和效率方面的进步,加上其在多种视觉任务中的强大表现,展示了其作为未来视觉模型基础构建块的潜力。
DCNv4介绍
DCNv4在DCNv3中移除了softmax归一化,将介于0和1之间的调制标量转换为类似于卷积的无界动态权重。如图所示,这种改变进一步增强了DCN的动态性质,其中其他运算符具有一定的限制,例如有界值范围或具有输入无关聚合权的固定聚合窗口(卷积)。
(a) Attention 和 (b) DCNv3 使用动态权重在范围为0到1之间进行空间特征聚合。Attention的窗口(采样点集)对于每个位置是相同的,而DCNv3为每个位置使用一个专用窗口。
© 卷积 具有更灵活的无界值范围来聚合权重,并为每个位置使用专用的滑动窗口,但窗口形状和聚合权重与输入无关。
(d) DCNv4 结合了它们的优势,使用自适应聚合窗口和具有无界值范围的动态聚合权重。
在DCNv4中,使用一个线程来处理同一组中共享采样偏移和聚合权重的多个通道。这样可以减少内存读取和双线性插值系数计算等工作负载,并且可以合并多个内存访问指令。如下图所示:
DCNv4理论详解可以参考链接:论文地址
DCNv4代码可在这个链接找到:代码地址
本文在YOLOv10中引入DCNv4(可变性卷积),在windows下编译成功,并且解决各种报错,代码已经整理好了,跟着文章复制粘贴,即可直接运行
目录
🎓一、YOLOv10原始版本代码下载
如果之前有在我的网盘下载的YOLOv10源码的就不需要重新下载了,没有下载从我的网盘下载,链接: YOLOv10原始版本源码下载
提取码: js2i
注意注意注意:如果在我之前的文章下载过YOLOv10源码,不用重新下载了,没有特殊说明都是用同一个版本的源码
🍀🍀1.YOLOv10模型结构图
根据yolov10n.yaml画出yolo整体结构图,如下图所示
🍀🍀2.环境配置
环境配置参考教程链接:链接: 环境配置链接,如果已经配置好环境可以忽略此步骤
🎓二、DCNv4下载与编译,并与C2f结构融合
🍀🍀1.DCNv4源码下载
DCNv4源码下载链接: https://github.com/OpenGVLab/DCNv4
解压源码
解压完成之后把DCNv4源码复制到YOLOv10根目录下
🍀🍀2.DCNv4编译
电脑必须有英伟达显卡,才能编译成功,接下来手把手教你们编译,并且解决编译遇到的问题
1)在PyCharm终端,cd进入DCNv4-main/DCNv4_op文件夹
2)在终端输入:python setup.py build install
如果你电脑没有安装cuda和cudnn就会报错,如图所示
报错1:
raise NotImplementedError(‘Cuda is not available’)
NotImplementedError: Cuda is not available
解决方法:
安装cuda和cudnn,手把手安装教程: cuda和cudnn的安装教程(全网最详细保姆级教程)
之后重新在终端输入:python setup.py build install
安装完cuda和cudnn之后,重新编译,如果没有C++环境的话,会报错,没有报错不用管,从报错信息来看,需要你安装visual studio工具
报错2:
error: Microsoft Visual C++ 14.0 or greater is required. Get it with “Microsoft C++ Build
解决方法:
安装visual studio 2019工具,手把手安装教程: Visual Studio 2019安装教程(全网最详细教程)
接下来重新输入命令编译,重新在终端输入:python setup.py build install
输出下面信息代表编译成功
编译成功之后,也可以使用pip list命令查看自己虚拟环境是否有DCNv4
可以看到自己虚拟环境已经有DCNv4了,编译DCNv4大功告成,此时此刻心里成就感满满
之后,复制DCNv4_op文件夹到ultralytics文件夹下,操作如下:
🍀🍀3.DCNv4与C2f融合
DCNv4融合YOLOv10的C2f结构代码我已经写好,代码如下
# -*- coding: utf-8 -*-
"""
@Auth : 挂科边缘
@File :DCNv4.py
@IDE :PyCharm
@Motto:学习新思想,争做新青年
@Email :179958974@qq.com
@qq :179958974
"""
import torch
from torch import nn
from ultralytics.DCNv4_op.DCNv4 import DCNv4
import torch.nn as nn
import torch
import torch.nn.functional as F
def autopad(k, p=None, d=1): # kernel, padding, dilation
"""Pad to 'same' shape outputs."""
if d > 1:
k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-size
if p is None:
p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
return p
class Conv(nn.Module):
"""Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)."""
default_act = nn.SiLU() # default activation
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
"""Initialize Conv layer with given arguments including activation."""
super().__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)
self.bn = nn.BatchNorm2d(c2)
self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()
def forward(self, x):
"""Apply convolution, batch normalization and activation to input tensor."""
return self.act(self.bn(self.conv(x)))
def forward_fuse(self, x):
"""Perform transposed convolution of 2D data."""
return self.act(self.conv(x))
class Bottleneck(nn.Module):
"""Standard bottleneck."""
def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):
"""Initializes a bottleneck module with given input/output channels, shortcut option, group, kernels, and
expansion.
"""
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, k[0], 1)
self.cv2 = DCNv4(c2)
self.add = shortcut and c1 == c2
def forward(self, x):
"""'forward()' applies the YOLO FPN to input data."""
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
class C2f_DCNv4(nn.Module):
"""Faster Implementation of CSP Bottleneck with 2 convolutions."""
def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):
"""Initialize CSP bottleneck layer with two convolutions with arguments ch_in, ch_out, number, shortcut, groups,
expansion.
"""
super().__init__()
self.c = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, 2 * self.c, 1, 1)
self.cv2 = Conv((2 + n) * self.c, c2, 1) # optional act=FReLU(c2)
self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=(3, 3), e=1.0) for _ in range(n))
def forward(self, x):
"""Forward pass through C2f layer."""
x = self.cv1(x)
x = x.chunk(2, 1)
y = list(x)
y.extend(m(y[-1]) for m in self.m)
return self.cv2(torch.cat(y, 1))
def forward_split(self, x):
"""Forward pass using split() instead of chunk()."""
y = list(self.cv1(x).split((self.c, self.c), 1))
y.extend(m(y[-1]) for m in self.m)
return self.cv2(torch.cat(y, 1))
🎓三、添加方法
🍀🍀1.在modules目录下添加第二章的代码
(1).在ultralytics/nn/modules目录下,新建一个文件名,我这里取名为DCNv4.py,操作如以下截图:
(2).把第二章的代码复制进去,如下图所示:
🍀🍀2.在__init__.py文件进行导入
文件路径为:ultralytics/nn/modules/init.py
(1)在__init__.py开头注释下面代码,这个步骤注释了以后可以跳过这个步骤
(2)之后开头导入iRMB,导入截图所示
from .DCNv4 import C2f_DCNv4
🍀🍀3.在tasks.py文件进行注册
(1)在tasks.py文件开头导入所有模块,该文件路径为:ultralytics/nn/tasks.py
在这个文件修改导入方法,注释掉下图红色圈出来的部分,改成*号导入,以后无需手动一个个导入新的模块,方便很多,一次性导完,这个步骤改完了,往后的文章就可以跳过这个步骤了,添加如截图所示
from ultralytics.nn.modules import *
(2)之后在这个文件的parse_model方法,添加DCNv4,添加如截图所示
看到这里已经成功把改进的模块添加进YOLOv10源码了,接下来配置yaml文件调用改进的模块就行了
🎓四、yaml文件修改
在ultralytics/cfg/models/v10目录下,新建一个yaml文件,复制yolov10n.yaml文件,然后取名为yolov10n-iRMB.yaml
🍀🍀1.第一种添加方法
yaml全部代码如下:
# 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]
# 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_DCNv4, [256, True]]
- [-1, 1, SCDown, [512, 3, 2]] # 5-P4/16
- [-1, 6, C2f_DCNv4, [512, True]]
- [-1, 1, SCDown, [1024, 3, 2]] # 7-P5/32
- [-1, 3, C2f_DCNv4, [1024, True]]
- [-1, 1, SPPF, [1024, 5]] # 9
- [-1, 1, PSA, [1024]] # 10
# YOLOv8.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)
🍀🍀2.第二种添加方法
yaml全部代码如下:
# 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]
# 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, SCDown, [512, 3, 2]] # 5-P4/16
- [-1, 6, C2f, [512, True]]
- [-1, 1, SCDown, [1024, 3, 2]] # 7-P5/32
- [-1, 3, C2f, [1024, True]]
- [-1, 1, SPPF, [1024, 5]] # 9
- [-1, 1, PSA, [1024]] # 10
# YOLOv8.0n head
head:
- [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- [[-1, 6], 1, Concat, [1]] # cat backbone P4
- [-1, 3, C2f_DCNv4, [512]] # 13
- [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- [[-1, 4], 1, Concat, [1]] # cat backbone P3
- [-1, 3, C2f_DCNv4, [256]] # 16 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 13], 1, Concat, [1]] # cat head P4
- [-1, 3, C2f_DCNv4, [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)
🎓五、训练文件修改
🍀🍀1.新建训练文件
在根目录新建一个python文件,取名为:train.py,如果之前看过我的文章,已经新建过就不用重新新建了
🍀🍀2.修改训练文件
YOLOv10训练方式跟YOLOv5是有区别的,但是训练数据集格式跟YOLOv5一样的,你只需把处理好的数据集就行,这里就不在阐述了,废话不多说。
把训练代码复制到train.py文件,如果之前看过我的文章,已经复制过了就不用重新复制了,只需修改参数就行,训练的代码如下:
# -*- coding: utf-8 -*-
"""
@Auth : 挂科边缘
@File :train.py
@IDE :PyCharm
@Motto:学习新思想,争做新青年
@Email :179958974@qq.com
@qq :179958974
"""
import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLOv10
if __name__ == '__main__':
# model.load('yolov8n.pt') # 加载预训练权重,改进或者做对比实验时候不建议打开,因为用预训练模型整体精度没有很明显的提升
model = YOLOv10(model=r'D:\2-Python\1-YOLO\YOLOv10\yolov10-main\ultralytics\cfg\models\v10\yolov10n.yaml')
model.train(data=r'data.yaml',
imgsz=640,
epochs=200,
batch=4,
workers=8,
device='',
optimizer='SGD',
close_mosaic=10,
resume=False,
project='runs/train',
name='exp',
single_cls=False,
cache=False,
)
根据你训练需求修改指定参数就行,其中圈起来的参数需要你修改的,其他参数根据自己需求选择改或者不改就行。
训练代码的参数解释,标蓝色的参数为常用参数:
1.model参数:该参数填入模型配置文件的路径,改进的话建议不需要预训练模型权重来训练
2.data参数:该参数可以填入训练数据集配置文件的路径
3.imgsz参数:该参数代表输入图像的尺寸,指定为 640x640 像素
4.epochs参数:该参数代表训练的轮数
5.batch参数:该参数代表批处理大小,电脑显存越大,就设置越大,根据自己电脑性能设置
6.workers参数:该参数代表数据加载的工作线程数,出现显存爆了的话可以设置为0。默认填8,可以加快训练
7.device参数:该参数代表用哪个显卡训练,留空表示自动选择可用的GPU或CPU
8.optimizer参数:该参数代表优化器类型
9.close_mosaic参数:该参数代表在多少个 epoch 后关闭 mosaic 数据增强
10.resume参数:该参数代表是否从上一次中断的训练状态继续训练。设置为False表示从头开始新的训练。如果设置为True,则会加载上一次训练的模型权重和优化器状态,继续训练。这在训练被中断或在已有模型的基础上进行进一步训练时非常有用。
11.project参数:该参数代表项目文件夹,用于保存训练结果
12.name参数:该参数代表命名保存的结果文件夹
13.single_cls参数:该参数代表是否将所有类别视为一个类别,设置为False表示保留原有类别
14.cache参数:该参数代表是否缓存数据,设置为False表示不缓存。
训练报错常见的错误:
训练显存报错,出现以下报错情况,把workers参数改为0
(1)OSError: [WinError 1455] 页面文件太小,无法完成操作。 Error loading “D:\1-ProgramFiles\Anaconda\envs\torch\lib\site-packages\torch\lib\caffe2_detectron_ops_gpu.dll” or one of its dependencies.
(2)RuntimeError: CUDA out of memory. Tried to allocate 2.00 MiB (GPU 0; 8.00 GiB total capacity; 530.52 MiB already allocated; 4.87 GiB free; 558.00 MiB reserved in total by PyTorch) If reserved memory is >> allocated memory try setting max_split_size_mb to avoid fragmentation. See documentation for Memory Management and PYTORCH_CUDA_ALLOC_CONF
🍀🍀3.训练报错解决方法
测试一下训练,会报各种错误,接一下一一来解决
1)报错1:
报错:
assert _d_per_group % 16 == 0
AssertionError
报错的原因是因为代码中对_d_per_group进行了一个硬性限制,即它必须是16的倍数。如果你的模型配置导致_d_per_group不是16的倍数,便会触发这个错误
解决方法:
找到dcnv4.py文件,注释掉这行代码:assert _d_per_group % 16 == 0
文件路径:ultralytics/DCNv4_op/DCNv4/modules/dcnv4.py
2)报错2:
报错:
N, L, C = input.shape
ValueError: too many values to unpack (expected 3)
报错的原因是input是一个4D张量 (N, H, W, C),代码期望input是一个3D张量 (例如,形状为 (N, L, C))
解决方法:
调整张量并转换顺序,修改如下:
# print(input.shape)
# 将input的形状从(N, C, H, W)转换为(N,L, C)
input = input.reshape((input.size(0), input.size(1), input.size(2) * input.size(3))).transpose(2, 1)
之后在该文件末尾,修改一下输出值就行了,代码如下:
x = x.view(N, H, W, -1)
if not self.without_pointwise:
x = self.output_proj(x)
x = x.permute(0, 3, 1, 2).contiguous() # 将张量的维度重新排列 (N, C, H, W)
return x
3)报错3:
报错:
output = ext.dcnv4_forward(*args)
RuntimeError: Not implemented on the CPU
报错原因是说我们的DCNv4在一些操作中用到CPU来训练,但是我们是要他在GPU上跑才行
解决方法:
代码如下:
try:
m.stride = torch.tensor([s / x.shape[-2] for x in forward(torch.zeros(1, ch, s, s))]) # forward
except RuntimeError:
try:
self.model.to(torch.device('cuda'))
m.stride = torch.tensor([s / x.shape[-2] for x in forward(torch.zeros(1, ch, s, s).to(torch.device('cuda')))])
except RuntimeError as error:
raise error
测试一下训练,打印出来的YOLOv8结构可以看到添加改进的模块成功
总结
请在我提供的YOLOv10代码修改,把环境配置好,数据集处理好,训练基本能成功,创作不易,请帮忙点一个爱心,谢谢观看