YOLO11改进|卷积篇|引入轻量级自适应提取卷积LAE
目录
一、【LAE】卷积
1.1【LAE】卷积介绍
下图是【LAE】卷积的结构图,让我们简单分析一下运行过程和优势
- 处理过程分析:
- 输入张量:输入的特征图尺寸为 ℎ×𝑤×𝑐,表示图像的高度、宽度和通道数。
- 轻量级提取(Lightweight Extraction):
分组卷积:输入特征图经过分组卷积(groups=c/16),将输入通道分成多个小组进行卷积操作。这种操作减少了计算成本,使得特征提取更加高效。
维度映射(Dimension Mapping):卷积后的特征经过重新排列操作(R),即将分组卷积的结果重新排列为不同的空间维度进行特征表示。这有助于捕捉更复杂的空间关系。
输出特征图:最终得到的轻量级提取结果仍然包含丰富的空间信息,但计算代价较低。 - 自适应提取(Adaptive Extraction):
全局特征聚合:首先,特征图经过自适应平均池化(AP),在高度和宽度维度上聚合特征信息。池化操作能够压缩输入的空间维度,使模型更关注全局特征。
1x1 卷积:池化后的特征图通过1x1卷积操作进一步压缩和调整通道数,这可以用较少的计算量学习到通道间的重要特征。
维度映射和Softmax:特征图经过重新排列操作(R)和Softmax归一化(S),使得每个通道的权重可以自适应地调整,进一步提升网络的表达能力。 - 融合与输出:
特征融合:轻量级提取和自适应提取的结果通过乘法运算融合,即使用自适应提取的权重调整轻量级提取的特征图。
残差连接与输出:融合后的特征图与输入特征进行相加操作(Sum),通过残差连接确保信息流畅通,并输出尺寸为 ℎ/2×𝑤/2×𝑐的最终特征图。 - 优势分析:
- 计算效率高:通过轻量级的分组卷积,LAE模块显著降低了计算成本。分组卷积有效减少了参数量和计算量,适合在资源受限的设备或大规模任务中使用。
- 灵活的特征提取:结合了轻量级提取和自适应提取两种机制,既保留了全局上下文信息的提取能力,也能根据特定任务动态调整特征权重。自适应机制通过Softmax调整每个通道的重要性,提升了网络的灵活性。
- 信息融合机制:轻量级提取捕捉了丰富的空间信息,而自适应提取则强化了通道间的特征互补。二者通过乘法运算和残差连接进行融合,确保了信息的有效传播与增强。
- 适应不同分辨率:自适应池化操作能够适应不同输入分辨率,使得该模块可以灵活应用于不同大小的输入数据,适用于各种图像处理任务。
1.2【LAE】核心代码
import torch
import torch.nn as nn
from einops import rearrange
__all__ = ['LAE']
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 LAE(nn.Module):
# Light-weight Adaptive Extraction
def __init__(self, ch, group=16) -> None:
super().__init__()
self.softmax = nn.Softmax(dim=-1)
self.attention = nn.Sequential(
nn.AvgPool2d(kernel_size=3, stride=1, padding=1),
Conv(ch, ch, k=1)
)
self.ds_conv = Conv(ch, ch * 4, k=3, s=2, g=(ch // group))
def forward(self, x):
# bs, ch, 2*h, 2*w => bs, ch, h, w, 4
att = rearrange(self.attention(x), 'bs ch (s1 h) (s2 w) -> bs ch h w (s1 s2)', s1=2, s2=2)
att = self.softmax(att)
# bs, 4 * ch, h, w => bs, ch, h, w, 4
x = rearrange(self.ds_conv(x), 'bs (s ch) h w -> bs ch h w s', s=4)
x = torch.sum(x * att, dim=-1)
return x
class MatchNeck_Inner(nn.Module):
def __init__(self, channels) -> None:
super().__init__()
self.gap = nn.Sequential(
nn.AdaptiveAvgPool2d((1, 1)),
Conv(channels, channels)
)
self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
self.pool_w = nn.AdaptiveAvgPool2d((1, None))
self.conv_hw = Conv(channels, channels, (3, 1))
self.conv_pool_hw = Conv(channels, channels, 1)
def forward(self, x):
_, _, h, w = x.size()
x_pool_h, x_pool_w, x_pool_ch = self.pool_h(x), self.pool_w(x).permute(0, 1, 3, 2), self.gap(x)
x_pool_hw = torch.cat([x_pool_h, x_pool_w], dim=2)
x_pool_h, x_pool_w = torch.split(x_pool_hw, [h, w], dim=2)
x_pool_hw_weight = x_pool_hw.sigmoid()
x_pool_h_weight, x_pool_w_weight = torch.split(x_pool_hw_weight, [h, w], dim=2)
x_pool_h, x_pool_w = x_pool_h * x_pool_h_weight, x_pool_w * x_pool_w_weight
x_pool_ch = x_pool_ch * torch.mean(x_pool_hw_weight, dim=2, keepdim=True)
return x * x_pool_h.sigmoid() * x_pool_w.permute(0, 1, 3, 2).sigmoid() * x_pool_ch.sigmoid()
class MatchNeck(nn.Module):
def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):
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.MN = MatchNeck_Inner(c2)
def forward(self, x):
return x + self.MN(self.cv2(self.cv1(x))) if self.add else self.MN(self.cv2(self.cv1(x)))
class MSFM(nn.Module):
def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):
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)
self.m = nn.ModuleList(MatchNeck(self.c, self.c, shortcut, g, k=(3, 3), e=1.0) for _ in range(n))
def forward(self, x):
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):
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))
if __name__ == "__main__":
# Generating Sample image
image_size = (1, 64, 224, 224)
image = torch.rand(*image_size)
# Model
model = MSFM(64, 64)
out = model(image)
print(out.size())
二、添加【LAE】卷积机制
2.1STEP1
首先找到ultralytics/nn文件路径下新建一个Add-module的python文件包【这里注意一定是python文件包,新建后会自动生成_init_.py】,如果已经跟着我的教程建立过一次了可以省略此步骤,随后新建一个LAE.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文件
以下是添加【LAE】卷积添加在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, 6, 2,2]] # 0-P1/2
- [-1, 1, LAE, []] # 1-P2/4
- [-1, 2, C3k2, [256, False, 0.25]]
- [-1, 1, LAE, []] # 3-P3/8
- [-1, 2, C3k2, [512, False, 0.25]]
- [-1, 1, LAE, []] # 5-P4/16
- [-1, 2, C3k2, [512, True]]
- [-1, 1, LAE, []] # 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)
- [[16, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)
以上添加位置仅供参考,具体添加位置以及模块效果以自己的数据集结果为准
3.2运行成功截图
OK 以上就是添加【LAE】卷积的全部过程了,后续将持续更新尽情期待
原文地址:https://blog.csdn.net/A1983Z/article/details/142751105
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!