💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡
目标检测是计算机视觉中一个重要的下游任务。对于边缘盒子的计算平台来说,一个大型模型很难实现实时检测的要求。基于一系列消融实验,研究者提出了几个实用的网络设计效率指导原则。据此,提出了一种新的架构,称为ShuffleNetV2。在本文中,给大家带来的教程是将原来的主干网络中的特征提取网络替换为shufflenetv2网络。文章在介绍主要的原理后,将手把手教学如何进行模块的代码添加和修改,并将修改后的完整代码放在文章的最后,方便大家一键运行,小白也可轻松上手实践。以帮助您更好地学习深度学习目标检测YOLO系列的挑战。
专栏地址: YOLOv5改进+入门——持续更新各种有效涨点方法 点击即可跳转
1.原理
论文地址:ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design——点击即可跳转
官方代码:pytorch官方代码仓库——点击即可跳转
ShuffleNetV2 是一种高效的卷积神经网络架构,旨在优化计算资源的使用,尤其适用于移动设备和嵌入式系统。ShuffleNetV2 由 Megvii (Face++) 团队在 2018 年提出,是 ShuffleNet 的改进版本,主要通过优化信息流和减少内存访问成本来提升性能。
ShuffleNetV2 的设计理念
ShuffleNetV2 主要针对以下几个方面进行了优化:
-
模型计算复杂度:
通过减少冗余计算和优化卷积操作,降低计算复杂度,使得模型在计算资源有限的情况下仍能保持较高性能。 -
内存访问成本:
内存访问成本是影响模型效率的一个重要因素。ShuffleNetV2 通过优化特征图的传递和处理,减少内存带宽的占用。 -
并行度:
设计上考虑了硬件的并行计算能力,确保模型在并行处理器上的高效运行。
核心模块
-
Channel Split(通道分割):
输入特征图被分成两部分,一部分直接传递到下一层,另一部分经过复杂的卷积操作。这种设计减少了每一层的计算量,同时保留了原始信息。 -
Channel Shuffle(通道打乱):
在通道级别打乱特征图。通过这种操作,确保在网络的后续层中,各个通道之间的信息能够充分混合,提高特征的表达能力。 -
Pointwise Group Convolution(逐点分组卷积):
使用逐点分组卷积(1x1 卷积)来减少特征图通道数,同时分组卷积进一步降低计算量。 -
Depthwise Convolution(深度可分离卷积):
深度可分离卷积将标准卷积分解为深度卷积和逐点卷积两部分,从而减少计算量。 -
Element-wise Add(元素级加法):
使用元素级加法而非拼接操作,将原始输入与处理后的特征图结合,这样不仅减少了特征图的数量,还避免了高计算复杂度。
ShuffleNetV2 的性能优化
-
降低内存访问成本:
通过减少特征图的传递和拼接操作,减少了内存访问成本。这一点对资源受限的移动设备尤为重要。 -
提升计算效率:
通过优化卷积操作和特征图处理方式,ShuffleNetV2 能够在计算资源有限的情况下保持高效的计算性能。 -
增加并行度:
ShuffleNetV2 的设计充分考虑了硬件的并行计算能力,通过优化计算操作和数据流,提高了模型的并行计算效率。
总结
ShuffleNetV2 通过优化计算复杂度、内存访问成本和并行度,提供了一种高效的卷积神经网络架构。其设计理念和核心模块,如 Channel Split、Channel Shuffle 和 Depthwise Convolution 等,不仅降低了计算和内存开销,还提高了模型的性能和效率。这使得 ShuffleNetV2 成为一种非常适合在资源受限设备上部署的深度学习模型。
2. ShuffleNetv2的代码实现
2.1 将ShuffleNetv2添加到YOLOv5中
关键步骤一: 将下面代码粘贴到/projects/yolov5-6.1/models/common.py文件中
def channel_shuffle(x, groups):
batchsize, num_channels, height, width = x.data.size()
channels_per_group = num_channels // groups
# reshape
x = x.view(batchsize, groups,
channels_per_group, height, width)
x = torch.transpose(x, 1, 2).contiguous()
# flatten
x = x.view(batchsize, -1, height, width)
return x
class conv_bn_relu_maxpool(nn.Module):
def __init__(self, c1, c2): # ch_in, ch_out
super(conv_bn_relu_maxpool, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(c1, c2, kernel_size=3, stride=2, padding=1, bias=False),
nn.BatchNorm2d(c2),
nn.ReLU(inplace=True),
)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
def forward(self, x):
return self.maxpool(self.conv(x))
class Shuffle_Block(nn.Module):
def __init__(self, inp, oup, stride):
super(Shuffle_Block, self).__init__()
if not (1 <= stride <= 3):
raise ValueError('illegal stride value')
self.stride = stride
branch_features = oup // 2
assert (self.stride != 1) or (inp == branch_features << 1)
if self.stride > 1:
self.branch1 = nn.Sequential(
self.depthwise_conv(inp, inp, kernel_size=3, stride=self.stride, padding=1),
nn.BatchNorm2d(inp),
nn.Conv2d(inp, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(branch_features),
nn.ReLU(inplace=True),
)
self.branch2 = nn.Sequential(
nn.Conv2d(inp if (self.stride > 1) else branch_features,
branch_features, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(branch_features),
nn.ReLU(inplace=True),
self.depthwise_conv(branch_features, branch_features, kernel_size=3, stride=self.stride, padding=1),
nn.BatchNorm2d(branch_features),
nn.Conv2d(branch_features, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(branch_features),
nn.ReLU(inplace=True),
)
@staticmethod
def depthwise_conv(i, o, kernel_size, stride=1, padding=0, bias=False):
return nn.Conv2d(i, o, kernel_size, stride, padding, bias=bias, groups=i)
def forward(self, x):
if self.stride == 1:
x1, x2 = x.chunk(2, dim=1) # 按照维度1进行split
out = torch.cat((x1, self.branch2(x2)), dim=1)
else:
out = torch.cat((self.branch1(x), self.branch2(x)), dim=1)
out = channel_shuffle(out, 2)
return out
ShuffleNet 的主要流程涉及一系列精心设计的操作,以最大限度地提高计算效率,同时确保模型的表达能力。以下是 ShuffleNet 的主要流程:
1. 初始卷积层
-
输入处理:
-
输入图像首先通过一个标准的卷积层进行处理,这个卷积层的作用是提取初始特征。
-
-
特征图大小和通道数调整:
-
初始卷积层通常会调整特征图的大小和通道数,以便于后续层的处理。
-
2. 分组卷积(Grouped Convolution)
-
特征图分组:
-
特征图在通道维度上分成多个组。分组卷积将特征图分为若干小组,并对每一小组单独进行卷积操作。这种方式可以大幅减少计算量。
-
-
1x1 卷积(Pointwise Convolution):
-
使用 1x1 卷积调整每一组特征图的通道数。1x1 卷积主要用于减少通道数,以减小计算量。
-
3. 通道打乱(Channel Shuffle)
-
通道打乱操作:
-
为了确保不同组之间的信息可以相互交流,使用通道打乱操作。具体来说,将特征图的通道重新排列,以便在下一次分组卷积时,不同组之间的信息能够充分混合。
-
4. 深度可分离卷积(Depthwise Separable Convolution)
-
深度卷积(Depthwise Convolution):
-
在深度卷积中,每个卷积核只在单个通道上进行卷积操作,不改变通道数。这样可以显著减少计算量。
-
-
逐点卷积(Pointwise Convolution):
-
在深度卷积之后,使用逐点卷积(1x1 卷积)来调整通道数。这一步结合了所有通道的信息。
-
5. 残差连接(Residual Connection)
-
特征图合并:
-
将原始输入特征图与经过上述处理后的特征图通过元素级加法(或拼接操作)合并。这一步类似于残差网络(ResNet)中的残差连接,帮助梯度在网络中更好地传播。
-
6. 通道分割(Channel Split)
-
特征图分割:
-
在某些版本中(如 ShuffleNetV2),特征图被分成两部分,一部分直接传递到下一层,另一部分经过复杂操作后再与第一部分合并。这种设计进一步减少了计算量。
-
7. 多次重复上述模块
-
模块重复:
-
上述操作模块会被重复多次,以构建深层网络。这些模块的重复次数和具体配置会根据网络的设计需求而定。
-
8. 最终卷积层和分类层
-
全局池化(Global Pooling):
-
在网络的最后,通常会使用全局平均池化层,将特征图的空间维度压缩到单个数值。
-
-
全连接层:
-
最后,通过一个全连接层(或完全卷积层),将特征映射到分类标签空间,以完成分类任务。
-
2.2 新增yaml文件
关键步骤二:在下/projects/yolov5-6.1/models下新建文件 yolov5-shufflenetv2.yaml并将下面代码复制进去
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# Parameters
nc: 80 # number of classes
depth_multiple: 1.0 # model depth multiple
width_multiple: 1.0 # layer channel multiple
anchors:
- [10,13, 16,30, 33,23] # P3/8
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32
# YOLOv5 v6.0 backbone
backbone:
# [from, number, module, args]
# Shuffle_Block: [out, stride]
[[ -1, 1, conv_bn_relu_maxpool, [ 32 ] ], # 0-P2/4
[ -1, 1, Shuffle_Block, [ 128, 2 ] ], # 1-P3/8
[ -1, 3, Shuffle_Block, [ 128, 1 ] ], # 2
[ -1, 1, Shuffle_Block, [ 256, 2 ] ], # 3-P4/16
[ -1, 7, Shuffle_Block, [ 256, 1 ] ], # 4
[ -1, 1, Shuffle_Block, [ 512, 2 ] ], # 5-P5/32
[ -1, 3, Shuffle_Block, [ 512, 1 ] ], # 6
]
# YOLOv5 v6.0 head
head:
[[-1, 1, Conv, [256, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 4], 1, Concat, [1]], # cat backbone P4
[-1, 1, C3, [256, False]], # 10
[-1, 1, Conv, [128, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 2], 1, Concat, [1]], # cat backbone P3
[-1, 1, C3, [128, False]], # 14 (P3/8-small)
[-1, 1, Conv, [128, 3, 2]],
[[-1, 11], 1, Concat, [1]], # cat head P4
[-1, 1, C3, [256, False]], # 17 (P4/16-medium)
[-1, 1, Conv, [256, 3, 2]],
[[-1, 7], 1, Concat, [1]], # cat head P5
[-1, 1, C3, [512, False]], # 20 (P5/32-large)
[[14, 17, 20], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
温馨提示:本文只是对yolov5l基础上添加swin模块,如果要对yolov8n/l/m/x进行添加则只需要指定对应的depth_multiple 和 width_multiple。
# YOLOv5n
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.25 # layer channel multiple
# YOLOv5s
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
# YOLOv5l
depth_multiple: 1.0 # model depth multiple
width_multiple: 1.0 # layer channel multiple
# YOLOv5m
depth_multiple: 0.67 # model depth multiple
width_multiple: 0.75 # layer channel multiple
# YOLOv5x
depth_multiple: 1.33 # model depth multiple
width_multiple: 1.25 # layer channel multiple
2.3 注册模块
关键步骤三:在yolo.py中注册, 大概在260行左右分别添加 ‘ conv_bn_relu_maxpool, Shuffle_Block’
2.4 执行程序
在train.py中,将cfg的参数路径设置为yolov5-ShuffleNetv2.yaml的路径
建议大家写绝对路径,确保一定能找到
🚀运行程序,如果出现下面的内容则说明添加成功🚀
3. 完整代码分享
https://pan.baidu.com/s/1EKQQOCdwIUWrR3LcFaCk8A?pwd=aaus
提取码: aaus
4. GFLOPs
关于GFLOPs的计算方式可以查看:百面算法工程师 | 卷积基础知识——Convolution
未改进的GFLOPs
改进后的GFLOPs
5.总结
ShuffleNet的设计理念主要集中在提高计算效率和减少参数量,同时保持较高的准确性。为了实现这一目标,ShuffleNet引入了两个关键的操作:点卷积(Pointwise Convolution)和通道洗牌(Channel Shuffle)。首先,ShuffleNet采用了一种被称为深度可分离卷积(Depthwise Separable Convolution)的操作。这种操作将标准卷积分解为两个更简单的操作:深度卷积(Depthwise Convolution)和点卷积(Pointwise Convolution)。深度卷积在每个输入通道上独立执行卷积操作,而点卷积则用于将这些独立处理后的通道进行线性组合。通过这种分解方式,计算量和参数量都得到了大幅度的减少。在此基础上,ShuffleNet进一步引入了通道洗牌的概念。在常规的卷积操作中,不同通道之间的信息交流是有限的。为了增强通道间的信息交流,ShuffleNet将通道划分为若干组,并在每一组内进行卷积操作后,将通道重新排列(洗牌),使得下一组操作能够接收到来自不同组的通道信息。这样,通过多层的叠加和洗牌操作,网络能够更有效地捕捉不同通道之间的关联特征,从而提升模型的表现力。此外,ShuffleNet采用了分组卷积(Grouped Convolution)的策略,即将通道划分为多个组,每组独立进行卷积操作。结合通道洗牌,这种策略进一步降低了计算复杂度和参数量,适合在移动设备等资源受限的环境中应用。总的来说,ShuffleNet通过巧妙地设计卷积操作和通道处理方式,显著提升了计算效率和模型的轻量化程度,使得其在保持较高准确率的同时,能够在计算资源有限的设备上高效运行。这种创新的架构设计不仅为移动设备上的实时图像处理提供了可能,也为其他需要高效视觉处理的应用场景提供了有力支持。