YOLOv8目标检测创新改进与实战案例专栏
专栏目录: YOLOv8有效改进系列及项目实战目录 包含卷积,主干 注意力,检测头等创新机制 以及 各种目标检测分割项目实战案例
专栏链接: YOLOv8基础解析+创新改进+实战案例
介绍
摘要
在ShuffleNet v2的文章中作者指出现在普遍采用的FLOPs评估模型性能是非常不合理的,因为一批样本的训练时间除了看FLOPs,还有很多过程需要消耗时间,例如文件IO,内存读取,GPU执行效率等等。作者从内存消耗成本,GPU并行性两个方向分析了模型可能带来的非FLOPs的行动损耗,进而设计了更加高效的ShuffleNet v2。ShuffleNet v2的架构和DenseNet[4]有异曲同工之妙,而且其速度和精度都要优于DenseNet。
文章链接
论文地址:论文地址
代码地址代码地址
参考代码:代码地址
基本原理
ShuffleNet V2是一种新颖的卷积神经网络(CNN)架构,旨在实现高效和准确的图像分类和目标检测任务。
构建模块:ShuffleNet V2的架构由构建模块组成,这些模块被堆叠起来构建整个网络。这些构建模块被设计为高效,允许使用更多的特征通道和更大的网络容量[T2]。
空间下采样:在ShuffleNet V2中,通过修改单元并将输出通道数量加倍来实现空间下采样。这种修改增强了网络的效率,同时保持准确性[T2]。
感受野增强:为了改善ShuffleNet V2在检测任务上的性能,通过在每个构建模块的逐点卷积之前引入额外的3x3深度卷积来扩大网络的感受野。这种增强被标记为ShuffleNet V2*,可以在几乎不增加计算成本的情况下提高准确性[T1]。
ShuffleNet v2结构
观察 (c) 和 (d) 对网络的改进,我们发现了以下几点:
- 在 (c) 中,ShuffleNet v2 使用了通道分割(Channel Split)操作。这个操作将 (c) 个输入特征分成 (c - c') 和 (c') 两组,一般情况下 (c' = \frac{c}{2})。这种设计目的是为了尽量控制分支数,满足 G3。
- 分割后的两个分支中,左侧是一个直接映射,右侧是一个输入通道数和输出通道数均相同的深度可分离卷积,以满足 G1。
- 右侧的卷积中,1×1 卷积没有使用分组卷积,以满足 G2。
- 最后在合并时,使用拼接操作,以满足 G4。
- 在堆叠 ShuffleNet v2 时,通道拼接、通道洗牌和通道分割可以合并成一个 element-wise 操作,这也是为了满足 G4。
最后,当需要降采样时,通过不进行通道分割的方式来实现通道数量的加倍,如图 6(d) 所示,这个方法非常简单。
核心代码
# 定义 ShuffleNetV2 模块
class ShuffleNetV2(nn.Module):
def __init__(self, inp, oup, stride): # inp: 输入通道数, oup: 输出通道数, stride: 步长
super().__init__()
self.stride = stride
# 计算每个分支的通道数
branch_features = oup // 2
# 确保步长为1时输入通道数是分支通道数的两倍
assert (self.stride != 1) or (inp == branch_features << 1)
if self.stride == 2:
# 定义 branch1,当步长为2时
self.branch1 = nn.Sequential(
# 深度卷积,输入通道数等于输出通道数,步长为2
nn.Conv2d(inp, inp, kernel_size=3, stride=self.stride, padding=1, groups=inp),
nn.BatchNorm2d(inp),
# 1x1 卷积,输出通道数等于 branch_features
nn.Conv2d(inp, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(branch_features),
nn.ReLU(inplace=True))
else:
# 步长为1时,branch1 为空
self.branch1 = nn.Sequential()
# 定义 branch2
self.branch2 = nn.Sequential(
# 1x1 卷积,步长为1,输出通道数等于 branch_features
nn.Conv2d(inp if (self.stride == 2) else branch_features, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(branch_features),
nn.ReLU(inplace=True),
# 深度卷积,步长为 stride,输出通道数等于 branch_features
nn.Conv2d(branch_features, branch_features, kernel_size=3, stride=self.stride, padding=1, groups=branch_features),
nn.BatchNorm2d(branch_features),
# 另一个 1x1 卷积,步长为1
nn.Conv2d(branch_features, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(branch_features),
nn.ReLU(inplace=True),
)
def forward(self, x):
if self.stride == 1:
# 当步长为1时,将输入在通道维度上分成两部分
x1, x2 = x.chunk(2, dim=1)
# 连接 x1 和 branch2 处理后的 x2
out = torch.cat((x1, self.branch2(x2)), dim=1)
else:
# 当步长为2时,连接 branch1 和 branch2 的输出
out = torch.cat((self.branch1(x), self.branch2(x)), dim=1)
# 进行通道混洗
out = self.channel_shuffle(out, 2)
return out
def channel_shuffle(self, x, groups):
# 获取输入张量的形状信息
N, C, H, W = x.size()
# 调整张量的形状,并交换通道维度
out = x.view(N, groups, C // groups, H, W).permute(0, 2, 1, 3, 4).contiguous().view(N, C, H, W)
return out
task与yaml配置
详见:https://blog.csdn.net/shangyanaf/article/details/139655578