#YOLOv8分割-全网最新创新点改进系列:YOLOv8-分割改进ShuffleNetV2,手把手教学、保姆级实操、必须有效涨点!!!
所有改进代码均经过实验测试跑通!截止发稿时YOLOv8分割已改进40+!自己排列组合2-4种后,考虑位置不同后可排列组合上千万种!改进不重样!!专注AI学术,关注B站up主:Ai学术叫叫兽er!
购买相关资料后畅享一对一答疑!
# YOLOv8分割-全网最新创新点改进系列:YOLOv8-分割改进ShuffleNetV2,手把手教学、保姆级实操、必须有效涨点!!!
一 ShuffleNetV2简介(大概了解一下即可)
主干网络为ShuffleNetV2, 其主要结构Inverted_residual_unit的组成单元如下图 c、d所示:
(a): the basic ShuffleNet-V1 unit; (b) the ShuffleNet-V1 unit for spatial down sampling (2×); © ShuffleNet-V2 basic unit; (d) ShuffleNet-V2 unit for spatial down sampling (2×)
ShuffleNetV1结构的问题:
如上图(a)(b)在ShuffleNetv1的模块中,大量使用了1x1组卷积,这违背了G2原则,另外v1采用了类似ResNet中的瓶颈层(bottleneck layer),输入和输出通道数不同,这违背了G1原则。同时使用过多的组,也违背了G3原则。短路连接中存在大量的元素级Add运算,这违背了G4原则。
ShuffleNetV2改进:
为了改善v1的缺陷,v2版本引入了一种新的运算:channel split。具体来说,在开始时先将输入特征图在通道维度分成两个分支:通道数分别为c{'}和c-c{‘} ,实际实现时 c^{’}=c/2 。左边分支做同等映射,右边的分支包含3个连续的卷积,并且输入和输出通道相同,这符合G1。而且两个1x1卷积不再是组卷积,这符合G2,另外两个分支相当于已经分成两组。两个分支的输出不再是Add元素,而是concat在一起,紧接着是对两个分支concat结果进行channle shuffle,以保证两个分支信息交流。其实concat和channel shuffle可以和下一个模块单元的channel split合成一个元素级运算,这符合原则G4。
对于下采样模块,不再有channel split,而是每个分支都是直接copy一份输入,每个分支都有stride=2的下采样,最后concat在一起后,特征图空间大小减半,但是通道数翻倍。
在同等条件下,ShuffleNetv2相比其他模型速度稍快,而且准确度也稍好一点。同时作者还设计了大的ShuffleNetv2网络,相比ResNet结构,其效果照样具有竞争力。
二 改进ShuffleNetV2教学
2.1 第一步 替换yaml文件
直接上YAML文件
在YOLOv8包中找到YOLOv8.yaml文件后进行内容替换即可。
最新版的路径为:ultralytics/ultralytics/cfg/models/v8/yolov8.yaml
# Ultralytics YOLO 🚀, GPL-3.0 license
# Parameters
nc: 80 # number of classes
depth_multiple: 0.33 # scales module repeats
width_multiple: 0.50 # scales convolution channels
# YOLOv8.0n backbone
backbone:
# [from, repeats, module, args]
- [-1, 1, Conv_maxpool, [24]] # 0-P2/4
- [-1, 1, ShuffleNetV2, [116, 2]] # 1-P3/8
- [-1, 3, ShuffleNetV2, [116, 1]] # 2
- [-1, 1, ShuffleNetV2, [232, 2]] # 3-P4/16
- [-1, 7, ShuffleNetV2, [232, 1]] # 4
- [-1, 1, ShuffleNetV2, [464, 2]] # 5-P5/32
- [-1, 3, ShuffleNetV2, [464, 1]] # 6
- [-1, 1, SPPF, [1024, 5]] # 7
# YOLOv8.0n head
head:
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 4], 1, Concat, [1]] # cat backbone P4
- [-1, 3, C2f, [512]] # 12
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 2], 1, Concat, [1]] # cat backbone P3
- [-1, 3, C2f, [256]] # 15 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 10], 1, Concat, [1]] # cat head P4
- [-1, 3, C2f, [512]] # 18 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]]
- [[-1, 7], 1, Concat, [1]] # cat head P5
- [-1, 3, C2f, [1024]] # 21 (P5/32-large)
- [[13, 16, 19], 1, Detect, [nc]] # Detect(P3, P4, P5)
2.2 创建代码包
在nn文件夹中新建qihnet.py文件(路径为:ultralytics/ultralytics/nn)代码如下
import contextlib
from copy import deepcopy
from pathlib import Path
import torch
import torch.nn as nn
class Conv_maxpool(nn.Module):
def __init__(self, c1, c2): # ch_in, ch_out
super().__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 ShuffleNetV2(nn.Module):
def __init__(self, inp, oup, stride): # ch_in, ch_out, stride
super().__init__()
self.stride = stride
branch_features = oup // 2
assert (self.stride != 1) or (inp == branch_features << 1)
if self.stride == 2:
# copy input
self.branch1 = nn.Sequential(
nn.Conv2d(inp, inp, kernel_size=3, stride=self.stride, padding=1, groups=inp),
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))
else:
self.branch1 = nn.Sequential()
self.branch2 = nn.Sequential(
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),
nn.Conv2d(branch_features, branch_features, kernel_size=3, stride=self.stride, padding=1, groups=branch_features),
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),
)
def forward(self, x):
if self.stride == 1:
x1, x2 = x.chunk(2, dim=1)
out = torch.cat((x1, self.branch2(x2)), dim=1)
else:
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
2.3 修改tasks.py
修改nn文件中tasks.py
找到下面的代码(657行左右)
elif m in (HGStem, HGBlock):
c1, cm, c2 = ch[f], args[0], args[1]
args = [c1, cm, c2, *args[2:]]
if m is HGBlock:
args.insert(4, n) # number of repeats
n = 1
在上述代码下方添加以下代码:
elif m in [ShuffleNetV2, Conv_maxpool]:
c1, c2 = ch[f], args[0]
if c2 != nc: # if c2 not equal to number of classes (i.e. for Classify() output)
c2 = make_divisible(c2 * width, 8)
args = [c1, c2, *args[1:]]
2.4 跑通示例
至此全部修改成功。
各种改进网络,更详细教程请关注B站:AI学术叫叫兽,欢迎讨论交流!
AI学术叫叫兽在这!家人们,给我遥遥领先!!!
AI学术叫叫兽在这!家人们,给我遥遥领先!!!
[AI学术叫叫兽在这!家人们,给我遥遥领先!!!](https://space.bilibili.com/3546623938398505?plat_id=1&share_from=space&share_medium=android&share_plat=android&share_session_id=9ac46a33-0bcf-40d3-a9f8-3ff85e402116&share_source=COPY&share_tag=s_i×tamp=1710314219&unique