创新模型结构
nc: 80
scales:
# [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
backbone:
# [from, repeats, module, args]
- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
- [-1, 2, C3k2, [256, False, 0.25]]
- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
- [-1, 2, C3k2, [512, False, 0.25]]
- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
- [-1, 2, C3k2, [512, True]]
- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
- [-1, 2, C3k2, [1024, True]]
- [-1, 1, SPPF, [1024, 5]] # 9
- [-1, 2, C2PSA, [1024]] # 10
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)
使用C2K2模块代替C2f模块
首先了解C2f模块的代码,这个模块结合了跨阶段部分连接(CSPNet)和 Bottleneck 结构,用于有效的特征提取。:
__init__方法:
- 初始化了两个卷积层 cv1和 cv2,并计算出隐藏通道数 self.c
- 使用 ModuleList 创建多个 Bottleneck 模块,作为中间的卷积操作
- 参数包括:输入通道数 c1、输出通道数 c2、Bottleneck 模块数 n、是否使用残差连接 shortcut、卷积分组数 g 和扩展因子 e。
forward方法:
- 通过 cv1 卷积层将输入特征分成两部分(使用 chunk() 方法),然后通过 Bottleneck 层处理后一部分特征。
- 最后使用 cv2 通过 1x1 卷积层将所有分支的输出拼接在一起,得到最终的输出。
forward_split方法:
- 与 forward 类似,但使用了 split() 函数来分割特征,而不是 chunk()。
其中split() 和 chunk()都是 PyTorch 中用于张量切分的函数,但它们的切分方式有所不同。具体来说:
chunk()根据指定的数量将张量沿着某个维度分成若干块,每块的大小大致相同(可能不是完全相等)。如果无法均匀分割,则有些块会比其他块略大。
torch.chunk(input, chunks, dim=0)
input:输入的张量。
chunks:将张量分割成多少块。
dim:在哪个维度上进行分割。
示例:
x = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
chunks = torch.chunk(x, 2, dim=0)
chunks 的结果是 [tensor([[1, 2, 3], [4, 5, 6]]), tensor([[7, 8, 9]])]
split() 根据指定的每个块的大小将张量沿着某个维度分割。你可以指定每个块的大小,因此它的切分方式比chunk()更灵活。
torch.split(input, split_size_or_sections, dim=0)
input:输入的张量。
split_size_or_sections:可以是一个整数,表示每块的大小,或者是一个列表,表示每块的大小。
dim:在哪个维度上进行分割。
x = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
splits = torch.split(x, [1, 2], dim=0)
splits 的结果是 [tensor([[1, 2, 3]]), tensor([[4, 5, 6], [7, 8, 9]])]
C2f模块实现代码:
import torch
import torch.nn as nn
class C2f(nn.Module):
“”“CSP Bottleneck with 2 convolutions.”“”
def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):
"""
初始化函数,定义C2f模块的参数和层次结构。
参数:
- c1: 输入通道数
- c2: 输出通道数
- n: Bottleneck模块的数量
- shortcut: 是否使用残差连接
- g: 卷积层中的组数(用于分组卷积)
- e: 扩展因子,用于计算 Bottleneck 中的隐藏通道数
"""
super().__init__()
self.c = int(c2 * e) # 计算隐藏通道数,通常是输出通道数的某个比例
self.cv1 = Conv(c1, 2 * self.c, 1, 1) # 1x1 卷积层,用于减少通道数,输出2倍隐藏通道数
self.cv2 = Conv((2 + n) * self.c, c2, 1) # 最终的1x1卷积层,恢复到原输出通道数,可能使用FReLU激活函数
# 创建多个 Bottleneck 模块,每个模块有一个卷积层,使用shortcut和分组卷积
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):
"""
前向传播函数,定义C2f模块的计算流程。
参数:
- x: 输入的张量(Tensor)
返回:
- 输出的张量(Tensor)
"""
# 使用1x1卷积将输入分成两部分,并沿通道维度进行分割
y = list(self.cv1(x).chunk(2, 1)) # 将输入切分为两块(沿通道维度)
# 对每个Bottleneck模块处理y的后一部分,并添加到列表中
y.extend(m(y[-1]) for m in self.m) # 将每个 Bottleneck 的输出添加到列表中
# 将所有分支的输出拼接,经过最后的1x1卷积生成最终输出
return self.cv2(torch.cat(y, 1)) # 在通道维度上拼接
def forward_split(self, x):
"""
前向传播的另一种方式,使用 split() 方法代替 chunk() 进行特征切分。
参数:
- x: 输入的张量(Tensor)
返回:
- 输出的张量(Tensor)
"""
# 使用1x1卷积将输入分成两部分,并使用 split() 进行分割
y = list(self.cv1(x).split((self.c, self.c), 1)) # 使用 split 按照指定尺寸切分
# 对每个Bottleneck模块处理y的后一部分,并添加到列表中
y.extend(m(y[-1]) for m in self.m) # 将每个 Bottleneck 的输出添加到列表中
# 将所有分支的输出拼接,经过最后的1x1卷积生成最终输出
return self.cv2(torch.cat(y, 1)) # 在通道维度上拼接
接下来了解C3k2模块的代码,C3k2模块是 CSP Bottleneck 的一种加速实现版本,它结合了卷积和分组卷积的特性,并允许选择使用自定义卷积核大小的 C3k模块,或者标准的 Bottleneck模块。它的主要设计目的是在保持高效特征提取的同时,提供更灵活的配置以适应不同的计算需求:
C3k2 模块
CSP Bottleneck 的加速版本:
CSP (Cross Stage Partial Network)网络最早是在 YOLOv4 中引入的,它通过减少冗余的梯度流动,使得模型的计算更加高效,同时保持了强大的特征表示能力。
C3k2模块继承了 CSP 的基本思想,通过使用 Bottleneck或者 C3k模块来进行特征提取。
两个卷积操作(2 convolutions):
C3k2的命名反映了它的结构,其中的 “k2” 代表两个卷积操作。相比传统的 CSP Bottleneck,它通过减少卷积操作的复杂度来加速特征提取过程。
自定义卷积核大小(C3k):
如果设置 C3k=True,C3k2模块会使用 C3k,这是一个具有可定制卷积核大小的 Bottleneck 模块。通过允许用户选择卷积核的大小,C3k2提供了更大的灵活性,可以针对不同任务和计算资源进行优化。
如果 C3k=False,则使用标准的 Bottleneck 模块。
扩展因子(Expansion Factor):
C3k2使用了扩展因子 e,这意味着模块内部的卷积通道数将根据输出通道数 c2 和 e 的值进行缩放。扩展因子通常用于控制瓶颈层中的特征维度,决定了特征的精简和扩展程度。
分组卷积(Group Convolution):
C3k2中的卷积层可以支持分组卷积(通过参数 g 控制),这种技术通过将卷积操作分成多个组,从而减少计算量并加速推理过程。这在处理高维特征时尤为重要,特别是在需要进行实时推理的应用中。
残差连接(Shortcut):
C3k2还支持残差连接(shortcut参数)。残差连接使得输入可以跳过卷积操作,直接加到输出上,从而避免梯度消失问题,并在深层网络中提高训练效果。
C3k2模块实现代码:
import torch
import torch.nn as nn
class C3k2(nn.Module):
“”"
C3k2 模块是带有 2 个卷积操作的加速版 CSP Bottleneck 模块,并且可以选择性地使用 C3k 块。
“”"
def __init__(self, c1, c2, n=1, c3k=False, e=0.5, g=1, shortcut=True):
"""
初始化 C3k2 模块。
参数:
- c1: 输入通道数
- c2: 输出通道数
- n: Bottleneck 模块的重复次数
- c3k: 是否使用 C3k 模块
- e: 扩展因子,决定隐藏通道数
- g: 分组卷积参数
- shortcut: 是否使用残差连接
"""
super(C3k2, self).__init__()
self.c = int(c2 * e) # 计算隐藏通道数
# 定义 1x1 卷积层,用于将输入调整到 2 倍的隐藏通道数
self.cv1 = nn.Conv2d(c1, 2 * self.c, kernel_size=1, stride=1, bias=False)
self.cv2 = nn.Conv2d(2 * self.c, c2, kernel_size=1, stride=1, bias=False)
# 定义模块列表,使用 C3k 或 Bottleneck 模块
self.m = nn.ModuleList(
C3k(self.c, self.c, 2, shortcut, g) if c3k else Bottleneck(self.c, self.c, shortcut, g) for _ in range(n)
)
def forward(self, x):
"""
前向传播函数,处理输入张量。
参数:
- x: 输入张量
返回:
- 经过 C3k2 模块处理后的输出张量
"""
# 将 cv1 的输出分成两部分
y = list(self.cv1(x).chunk(2, dim=1)) # 使用 chunk 操作将通道维度分成两部分
# 对每个模块列表中的模块进行处理,并将结果添加到 y 中
y.extend(m(y[-1]) for m in self.m)
# 将所有部分拼接并通过 cv2 恢复通道数
return self.cv2(torch.cat(y, dim=1))
class Bottleneck(nn.Module):
“”"
Bottleneck 模块,用于特征提取,包含 1x1 和 3x3 卷积层,并带有残差连接。
“”"
def __init__(self, in_channels, out_channels, shortcut=True, g=1, e=0.5):
"""
初始化 Bottleneck 模块。
参数:
- in_channels: 输入通道数
- out_channels: 输出通道数
- shortcut: 是否使用残差连接
- g: 分组卷积参数
- e: 扩展因子,控制 Bottleneck 中的隐藏通道数
"""
super(Bottleneck, self).__init__()
hidden_channels = int(out_channels * e)
# 1x1 卷积用于降维
self.conv1 = nn.Conv2d(in_channels, hidden_channels, kernel_size=1, stride=1, bias=False)
self.bn1 = nn.BatchNorm2d(hidden_channels)
# 3x3 卷积用于提取特征,使用自定义卷积核大小
self.conv2 = nn.Conv2d(hidden_channels, out_channels, kernel_size=3, stride=1, padding=1, groups=g, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels)
# 判断是否需要残差连接
self.shortcut = nn.Identity() if in_channels == out_channels and shortcut else None
def forward(self, x):
"""
前向传播函数,处理输入张量。
参数:
- x: 输入张量
返回:
- 经过 Bottleneck 模块处理后的输出张量
"""
residual = x
x = self.conv1(x)
x = self.bn1(x)
x = torch.relu(x)
x = self.conv2(x)
x = self.bn2(x)
if self.shortcut is not None:
x += residual
return torch.relu(x)
class C3k(nn.Module):
“”"
C3k 模块,提供自定义卷积核大小的 CSP Bottleneck 模块。
“”"
def __init__(self, c1, c2, n=1, shortcut=True, g=1, k=3):
"""
初始化 C3k 模块。
参数:
- c1: 输入通道数
- c2: 输出通道数
- n: Bottleneck 模块的重复次数
- shortcut: 是否使用残差连接
- g: 分组卷积参数
- k: 卷积核大小
"""
super(C3k, self).__init__()
hidden_channels = int(c2 * 0.5) # 默认扩展因子为 0.5
# 定义卷积层
self.cv1 = nn.Conv2d(c1, hidden_channels, kernel_size=1, stride=1)
self.cv2 = nn.Conv2d(c1, hidden_channels, kernel_size=1, stride=1)
self.cv3 = nn.Conv2d(2 * hidden_channels, c2, kernel_size=1, stride=1)
# 定义 Bottleneck 模块序列,使用自定义卷积核大小
self.m = nn.Sequential(
*[Bottleneck(hidden_channels, hidden_channels, shortcut, g=g, k=(k, k), e=1.0) for _ in range(n)]
)
def forward(self, x):
"""
前向传播函数,处理输入张量。
参数:
- x: 输入张量
返回:
- 经过 C3k 模块处理后的输出张量
"""
return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))
示例使用
if name == “main”:
# 假设输入张量形状为 [batch_size, channels, height, width]
x = torch.randn(1, 64, 128, 128)
model = C3k2(in_channels=64, out_channels=128, n=3, c3k=False, e=0.5, shortcut=True)
output = model(x)
print(output.shape) # 输出张量的形状应为 [1, 128, 128, 128]
其中CSP Bottleneck 模块通常包含以下几个部分:
分支机制:输入被分为两部分:一个直接传递,另一个通过 Bottleneck 进行处理
Bottleneck 单元:Bottleneck 是由 1x1 卷积层和 3x3 卷积层组成的结构,1x1 卷积层用于减少通道数(降低计算复杂度),3x3 卷积层用于提取局部特征
特征融合:最终通过拼接(concatenation)或逐元素相加(element-wise addition)将两条路径的特征融合在一起,作为输出。
Bottleneck 模块实现代码:
import torch
import torch.nn as nn
class Bottleneck(nn.Module):
“”"
ResNet 中 Bottleneck 模块的实现,包含 1x1、3x3 和 1x1 卷积层,并带有残差连接。
“”"
def init(self, in_channels, out_channels, bottleneck_channels=None, shortcut=True, groups=1, expansion=0.5):
“”"
初始化 Bottleneck 模块。
参数:
- in_channels: 输入通道数
- out_channels: 输出通道数
- bottleneck_channels: Bottleneck 中的隐藏通道数
- shortcut: 是否使用残差连接
- groups: 分组卷积参数
- expansion: 扩展因子,控制 Bottleneck 中的通道数
“”"
super().init()
bottleneck_channels = bottleneck_channels or int(out_channels * expansion)
# 1x1 卷积,降维操作
self.conv1 = nn.Conv2d(in_channels, bottleneck_channels, kernel_size=1, stride=1, bias=False)
# 3x3 卷积,特征提取
self.conv2 = nn.Conv2d(bottleneck_channels, bottleneck_channels, kernel_size=3, stride=1, padding=1, groups=groups, bias=False)
# 1x1 卷积,升维操作
self.conv3 = nn.Conv2d(bottleneck_channels, out_channels, kernel_size=1, stride=1, bias=False)
# Batch Normalization
self.bn1 = nn.BatchNorm2d(bottleneck_channels)
self.bn2 = nn.BatchNorm2d(bottleneck_channels)
self.bn3 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
self.shortcut = shortcut and in_channels == out_channels # 是否使用残差连接
def forward(self, x):
"""
前向传播函数。
参数:
- x: 输入的张量
返回:
- 经过 Bottleneck 处理后的输出张量
"""
residual = x # 原始输入,用于残差连接
# 通过 1x1 卷积进行降维
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
# 通过 3x3 卷积进行特征提取
x = self.conv2(x)
x = self.bn2(x)
x = self.relu(x)
# 通过 1x1 卷积升维
x = self.conv3
2.3.2 YOLO11在SPPF层后新增C2PSA层
C2PSA 模块是基于 PSA(Pyramid Squeeze Attention)注意力机制的卷积模块,负责处理输入张量并通过注意力机制增强特征表示。该模块涉及到卷积操作、分割和多头注意力机制。
PSA 模块:
PSA 模块实现代码:
import torch
import torch.nn as nn
import math
Squeeze-and-Excitation (SE) 模块,用于特征加权
class SEWeightModule(nn.Module):
“”"
Squeeze-and-Excitation (SE) 模块:
通过全局池化和两层全连接网络,对特征通道进行重新加权。
“”"
def init(self, channels, reduction=16):
“”"
初始化 SE 模块。
参数:
- channels: 输入的通道数
- reduction: 降维比率,通常是 16
“”"
super(SEWeightModule, self).init()
# 全局平均池化,将空间维度压缩为 1x1
self.avg_pool = nn.AdaptiveAvgPool2d(1)
# 第一个 1x1 卷积层用于减少通道数,通道数为 channels // reduction
self.fc1 = nn.Conv2d(channels, channels // reduction, kernel_size=1, padding=0)
self.relu = nn.ReLU(inplace=True)
# 第二个 1x1 卷积层用于恢复通道数
self.fc2 = nn.Conv2d(channels // reduction, channels, kernel_size=1, padding=0)
# 使用 sigmoid 激活函数生成注意力权重
self.sigmoid = nn.Sigmoid()
def forward(self, x):
"""
前向传播函数。
参数:
- x: 输入张量 (batch_size, channels, height, width)
返回:
- weight: 通道权重 (batch_size, channels, 1, 1)
"""
# 全局平均池化
out = self.avg_pool(x)
# 通过全连接层减少通道数
out = self.fc1(out)
out = self.relu(out)
# 恢复通道数并生成权重
out = self.fc2(out)
weight = self.sigmoid(out)
return weight
标准卷积定义函数
def conv(in_planes, out_planes, kernel_size=3, stride=1, padding=1, dilation=1, groups=1):
“”"
定义带有填充和分组卷积的标准卷积层。
参数:
- in_planes: 输入通道数
- out_planes: 输出通道数
- kernel_size: 卷积核大小
- stride: 步长
- padding: 填充
- dilation: 扩张
- groups: 分组卷积数
返回:
- 卷积层
“”"
return nn.Conv2d(in_planes, out_planes, kernel_size=kernel_size, stride=stride,
padding=padding, dilation=dilation, groups=groups, bias=False)
1x1 卷积定义
def conv1x1(in_planes, out_planes, stride=1):
“”"
定义 1x1 卷积层。
参数:
- in_planes: 输入通道数
- out_planes: 输出通道数
- stride: 步长
返回:
- 1x1 卷积层
“”"
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
PSA (Pyramid Squeeze Attention) 模块定义
class PSAModule(nn.Module):
“”"
Pyramid Squeeze Attention (PSA) 模块:
通过多种不同卷积核大小和分组卷积提取多尺度特征,并使用 SE 模块进行特征加权。
“”"
def init(self, inplans, planes, conv_kernels=[3, 5, 7, 9], stride=1, conv_groups=[1, 4, 8, 16]):
“”"
初始化 PSA 模块。
参数:
- inplans: 输入通道数
- planes: 输出通道数
- conv_kernels: 使用的卷积核大小列表
- stride: 卷积的步长
- conv_groups: 分组卷积数量列表
“”"
super(PSAModule, self).init()
# 定义四个不同卷积核大小和分组数的卷积层
self.conv_1 = conv(inplans, planes // 4, kernel_size=conv_kernels[0], padding=conv_kernels[0] // 2,
stride=stride, groups=conv_groups[0])
self.conv_2 = conv(inplans, planes // 4, kernel_size=conv_kernels[1], padding=conv_kernels[1] // 2,
stride=stride, groups=conv_groups[1])
self.conv_3 = conv(inplans, planes // 4, kernel_size=conv_kernels[2], padding=conv_kernels[2] // 2,
stride=stride, groups=conv_groups[2])
self.conv_4 = conv(inplans, planes // 4, kernel_size=conv_kernels[3], padding=conv_kernels[3] // 2,
stride=stride, groups=conv_groups[3])
# 使用 SE 模块进行加权
self.se = SEWeightModule(planes // 4)
self.split_channel = planes // 4
self.softmax = nn.Softmax(dim=1)
def forward(self, x):
"""
前向传播函数。
参数:
- x: 输入张量 (batch_size, channels, height, width)
返回:
- 加权后的输出张量
"""
batch_size = x.shape[0]
# 四个不同卷积核的特征提取
x1 = self.conv_1(x)
x2 = self.conv_2(x)
x3 = self.conv_3(x)
x4 = self.conv_4(x)
# 将四个分支特征拼接
feats = torch.cat((x1, x2, x3, x4), dim=1)
feats = feats.view(batch_size, 4, self.split_channel, feats.shape[2], feats.shape[3])
# 使用 SE 模块加权
x1_se = self.se(x1)
x2_se = self.se(x2)
x3_se = self.se(x3)
x4_se = self.se(x4)
# 将所有加权后的特征拼接
x_se = torch.cat((x1_se, x2_se, x3_se, x4_se), dim=1)
attention_vectors = x_se.view(batch_size, 4, self.split_channel, 1, 1)
attention_vectors = self.softmax(attention_vectors)
# 使用注意力权重重新加权特征
feats_weight = feats * attention_vectors
for i in range(4):
x_se_weight_fp = feats_weight[:, i, :, :, :]
if i == 0:
out = x_se_weight_fp
else:
out = torch.cat((out, x_se_weight_fp), 1)
return out
C2PSA 模块:
C2PSA 模块实现代码:
import torch
import torch.nn as nn
class C2PSA(nn.Module):
“”"
C2PSA 模块实现,结合了卷积和 Pyramid Squeeze Attention (PSA) 注意力机制,
用于增强特征表示能力。
“”"
def __init__(self, c1, c2, n=1, e=0.5):
"""
初始化 C2PSA 模块。
参数:
- c1: 输入通道数,确保与 c2 相等
- c2: 输出通道数
- n: 重复 PSABlock 的次数
- e: 扩展因子,控制隐藏通道数的大小
"""
super().__init__()
assert c1 == c2 # 确保输入通道数与输出通道数相同
self.c = int(c1 * e) # 计算隐藏通道数
# 1x1 卷积层,将输入通道数变为 2 倍的隐藏通道数
self.cv1 = Conv(c1, 2 * self.c, 1, 1)
# 最终的 1x1 卷积层,将通道数恢复到原始的输出通道数
self.cv2 = Conv(2 * self.c, c1, 1, 1)
# 使用 PSA Block 处理分割后的特征,定义一个包含多个 PSABlock 的序列
# PSA Block 中的注意力比率设置为 0.5,num_heads 控制多头注意力的数量
self.m = nn.Sequential(
*(PSABlock(self.c, attn_ratio=0.5, num_heads=self.c // 64) for _ in range(n))
)
def forward(self, x):
"""
前向传播函数,处理输入张量 x,通过 PSA blocks 后返回处理后的张量。
参数:
- x: 输入的张量
返回:
- 经过处理的输出张量
"""
# 输入张量 x 经过第一个 1x1 卷积层 self.cv1 处理
# 使用 split 将通道维度按隐藏通道数 self.c 分割为两个部分 a 和 b
a, b = self.cv1(x).split((self.c, self.c), dim=1)
# 通过 PSA blocks 处理 b 分支
b = self.m(b)
# 将经过处理的 b 分支和未处理的 a 分支在通道维度拼接
# 然后经过第二个 1x1 卷积层恢复原通道数
return self.cv2(torch.cat((a, b), dim=1))
init 方法:
参数:
c1:输入通道数。
c2:输出通道数,代码中通过 assert c1 == c2 确保输入输出通道数相等。
n:模块重复的次数。
e:扩展因子,用于计算隐藏通道数。
变量:
self.c:隐藏通道数,计算方式为 c1 * e。
self.cv1:1x1 卷积操作,用于将输入通道数变为 2 倍的隐藏通道数。该操作减少了特征维度。
self.cv2:最终的卷积操作,将通道数恢复到原始输出通道 c1。
PSA Block:
self.m 是通过 PSABlock 组成的序列,PSABlock 是 PSA 注意力机制的核心部分。每个 PSABlock 中,计算注意力比率 attn_ratio=0.5,以及 num_heads=self.c // 64,表示多头注意力的数量(每 64 个通道作为一组头部)。
forward 方法:
张量分割:
a, b = self.cv1(x).split((self.c, self.c), dim=1):输入的张量首先经过 cv1 处理,然后通过 split 函数将其在通道维度上分割成两部分,每部分具有 self.c 个通道。
PSA Block 处理:
b = self.m(b):通过 PSABlock 模块处理第二部分特征 b,这些模块包含注意力机制,用于增强特征表示。
拼接与输出:
torch.cat((a, b), dim=1):最后,将分割后的两个张量 a 和 b 在通道维度上拼接。
拼接后的张量通过 cv2 进行处理,恢复到输出通道数。