原论文摘要
注意力机制是计算机视觉中最广泛使用的组件之一,能够帮助神经网络突出重要元素并抑制不相关的部分。然而,大多数通道注意力机制只包含通道特征信息,忽略了空间特征信息,导致模型的表示效果较差或目标检测性能不佳,并且空间注意力模块往往复杂且代价高昂。为了在性能和复杂性之间取得平衡,本文提出了一种轻量级的混合局部通道注意力(MLCA)模块,以提高目标检测网络的性能。该模块能够同时结合通道信息和空间信息,以及局部信息和全局信息,从而提升网络的表示效果。
MLCA介绍
MLCA是一种即插即用的可扩展混合局部注意力机制,以在检测效果、速度和模型参数数量之间实现平衡,并使注意力机制同时包含通道信息、空间信息、局部通道信息和全局通道信息。
具体来说:
1.局部性:关注特征图的局部区域,而非全局特征,避免无关区域的干扰。
2.通道性:同时对不同的通道进行加权,以突出最相关的特征。
3.混合性:将局部通道注意力与全局注意力或其他类型的注意力结合,增强模型的表达能力。
混合局部信道注意(MLCA)结构图如下:
MLCA理论详解可以参考链接:论文地址
MLCA代码可在这个链接找到:代码地址
本文在YOLOv10中引入MLCA(混合局部信道注意),代码已经整理好了,跟着文章复制粘贴,即可直接运行
目录
🎓一、YOLOv10原始版本代码下载
如果之前有在我的网盘下载的YOLOv10源码的就不需要重新下载了,没有下载从我的网盘下载,链接: YOLOv10原始版本源码下载
提取码: js2i
注意注意注意:如果在我之前的文章下载过YOLOv10源码,不用重新下载了,没有特殊说明都是用同一个版本的源码
🍀🍀1.YOLOv10模型结构图
根据yolov10n.yaml画出yolo整体结构图,如下图所示
🍀🍀2.环境配置
环境配置参考教程链接:链接: 环境配置链接,如果已经配置好环境可以忽略此步骤
🎓二、MLCA代码
# -*- coding: utf-8 -*-
"""
@Auth : 挂科边缘
@File :MLCA.py
@IDE :PyCharm
@Motto:学习新思想,争做新青年
@Email :179958974@qq.com
@qq :179958974
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class MLCA(nn.Module):
def __init__(self, in_size, local_size=5, gamma=2, b=1, local_weight=0.5):
super(MLCA, self).__init__()
self.local_size = local_size
self.gamma = gamma
self.b = b
t = int(abs(math.log(in_size, 2) + self.b) / self.gamma) # eca gamma=2
k = t if t % 2 else t + 1
self.conv = nn.Conv1d(1, 1, kernel_size=k, padding=(k - 1) // 2, bias=False)
self.conv_local = nn.Conv1d(1, 1, kernel_size=k, padding=(k - 1) // 2, bias=False)
self.local_weight = local_weight
self.local_arv_pool = nn.AdaptiveAvgPool2d(local_size)
self.global_arv_pool = nn.AdaptiveAvgPool2d(1)
def forward(self, x):
local_arv = self.local_arv_pool(x)
global_arv = self.global_arv_pool(local_arv)
b, c, m, n = x.shape
b_local, c_local, m_local, n_local = local_arv.shape
# (b,c,local_size,local_size) -> (b,c,local_size*local_size)-> (b,local_size*local_size,c)-> (b,1,local_size*local_size*c)
temp_local = local_arv.view(b, c_local, -1).transpose(-1, -2).reshape(b, 1, -1)
temp_global = global_arv.view(b, c, -1).transpose(-1, -2)
y_local = self.conv_local(temp_local)
y_global = self.conv(temp_global)
# (b,c,local_size,local_size) <- (b,c,local_size*local_size)<-(b,local_size*local_size,c) <- (b,1,local_size*local_size*c)
y_local_transpose = y_local.reshape(b, self.local_size * self.local_size, c).transpose(-1, -2).view(b, c,
self.local_size,
self.local_size)
# y_global_transpose = y_global.view(b, -1).transpose(-1, -2).unsqueeze(-1)
y_global_transpose = y_global.view(b, -1).unsqueeze(-1).unsqueeze(-1) # 代码修正
# print(y_global_transpose.size())
# 反池化
att_local = y_local_transpose.sigmoid()
att_global = F.adaptive_avg_pool2d(y_global_transpose.sigmoid(), [self.local_size, self.local_size])
# print(att_local.size())
# print(att_global.size())
att_all = F.adaptive_avg_pool2d(att_global * (1 - self.local_weight) + (att_local * self.local_weight), [m, n])
# print(att_all.size())
x = x * att_all
return x
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 C2f_MLCA(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), (3, 3)), e=1.0) for _ in range(n))
def forward(self, x):
"""Forward pass through C2f layer."""
y = list(self.cv1(x).chunk(2, 1))
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))
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 = Conv(c_, c2, k[1], 1, g=g)
self.add = shortcut and c1 == c2
self.MLCA = MLCA(c2)
def forward(self, x):
"""'forward()' applies the YOLO FPN to input data."""
return x + self.MLCA(self.cv2(self.cv1(x))) if self.add else self.MLCA(self.cv2(self.cv1(x)))
🎓三、在YOLOv10添加MLCA代码
🍀🍀1.在modules目录下添加第二章的MLCA代码
(1).在ultralytics/nn/modules目录下,新建一个文件名,我这里取名为MLCA.py,操作如以下截图:
(2).把第二章的代码复制进去,如下图所示:
🍀🍀2.在__init__.py文件导入MLCA模块
文件路径为:ultralytics/nn/modules/init.py
(1)在__init__.py开头注释下面代码,这个步骤注释了以后可以跳过这个步骤
(2)之后开头导入iRMB,导入截图所示
from .MLCA import C2f_MLCA,MLCA
🍀🍀3.在tasks.py文件注册MLCA
(1)在tasks.py文件开头导入所有模块,该文件路径为:ultralytics/nn/tasks.py
在这个文件修改导入方法,注释掉下图红色圈出来的部分,改成*号导入,以后无需手动一个个导入新的模块,方便很多,一次性导完,这个步骤改完了,往后的文章就可以跳过这个步骤了,添加如截图所示
from ultralytics.nn.modules import *
(2)之后在这个文件的parse_model方法,添加MLCA,添加如截图所示
看到这里已经成功把改进的模块添加进YOLOv10源码了,接下来配置yaml文件调用改进的模块就行了
🎓四、yaml文件修改
在ultralytics/cfg/models/v10目录下,新建一个yaml文件,复制yolov10n.yaml文件,然后取名为yolov10n-MLCA.yaml
🍀🍀1.第一种添加方法
yolov10n-MLCA.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, [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)
- [-1, 1, MLCA, []] # 23
- [[16, 19, 23], 1, v10Detect, [nc]] # Detect(P3, P4, P5)
🍀🍀2.第二种添加方法
yolov10n-iRMB.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_MLCA, [128, True]]
- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
- [-1, 6, C2f_MLCA, [256, True]]
- [-1, 1, SCDown, [512, 3, 2]] # 5-P4/16
- [-1, 6, C2f_MLCA, [512, True]]
- [-1, 1, SCDown, [1024, 3, 2]] # 7-P5/32
- [-1, 3, C2f_MLCA, [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)
🎓五、训练文件修改
🍀🍀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
测试一下训练,打印出来的YOLOv10结构可以看到添加改进的模块成功
总结
请在我提供的YOLOv10代码修改,把环境配置好,数据集处理好,训练基本能成功,创作不易,请帮忙点一个爱心,谢谢观看