秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转
💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡
专栏目录 :《YOLOv8改进有效涨点》专栏介绍 & 专栏目录 | 目前已有100+篇内容,内含各种Head检测头、损失函数Loss、Backbone、Neck、NMS等创新点改进——点击即可跳转
为了加速神经网络,通常减少浮点运算(FLOPs),但FLOPs降低并不总是等同于延迟降低。这是因为运算效率低,尤其是深度可分离卷积频繁内存访问。本文介绍一种部分卷积(PConv)以高效提取特征,减少冗余计算和内存访问。基于PConv,构建了FasterNet神经网络,它在多种设备上运行更快,且机会不影响视觉任务的准确性。文章在介绍主要的原理后,将手把手教学如何进行模块的代码添加和修改,并将修改后的完整代码放在文章的最后,方便大家一键运行,小白也可轻松上手实践。以帮助您更好地学习深度学习目标检测YOLO系列的挑战。
目录
1. 原理
论文地址:Run, Don’t Walk: Chasing Higher FLOPS for Faster Neural Networks——点击即可跳转
官方代码: 官方代码仓库——点击即可跳转
FasterNet 神经网络,主要侧重于提高计算速度(FLOPS - 每秒浮点运算次数),同时保持浮点运算 (FLOP) 数量较低。核心思想是,由于许多现有方法的内存访问效率低下和 FLOPS 较低,减少 FLOP 并不总是意味着降低延迟或提高性能。
关键概念和创新
-
FLOP 与 FLOPS:
-
加速神经网络的传统方法侧重于降低 FLOP,这衡量了计算复杂度。然而,这并不一定会降低延迟,因为它忽略了实际的计算速度,即 FLOPS。
-
FLOPS 表示每秒可以执行多少次浮点运算,并直接影响网络的实际速度。作者认为,仅仅减少 FLOP 而不考虑 FLOPS 在某些情况下甚至可能会增加延迟。
内存访问瓶颈:
-
许多现有运算符,如深度卷积 (DWConv),都存在频繁内存访问的问题,这会降低整体计算速度。尽管这些运算符在 FLOP 方面很高效,但它们通常会导致较低的 FLOPS,从而增加延迟。
部分卷积 (PConv):
-
为了解决这些低效率问题,本文提出了一种称为部分卷积 (PConv) 的新运算符。PConv 通过将卷积滤波器仅应用于部分输入通道,而其余通道保持不变,从而减少了计算冗余。这种方法减少了 FLOP 并增加了 FLOPS,从而缩短了执行时间。
-
PConv 能够有效地提取空间特征,同时减少内存访问,这是提高 CPU 和 GPU 等设备的实际处理速度的关键因素。
FasterNet:
-
FasterNet 以 PConv 为基础,是一个新的神经网络系列,旨在在不牺牲准确性的情况下在各种设备上普遍更快。例如,较小的变体 FasterNet-T0 比 MobileViT-XXS 等竞争模型更快、更准确。
-
FasterNet 的设计考虑了实际部署,确保它在各种硬件配置(包括 GPU、CPU 和 ARM 处理器)上都能表现良好。
结论
FasterNet 代表了神经网络架构的进步,它解决了计算复杂度 (FLOP) 和实际计算速度 (FLOPS) 问题。通过引入 PConv 并强调高效的内存访问,它在不同设备上实现了更快的性能,同时与现有模型相比保持甚至提高了准确性。
2. 将C2f_Faster添加到yolov8网络中
2.1 C2f_Faster 代码实现
关键步骤一: 将下面代码粘贴到在/ultralytics/ultralytics/nn/modules/block.py中,并在该文件的__all__中添加“C2f_Faster”
from timm.models.layers import DropPath
class Partial_conv3(nn.Module):
def __init__(self, dim, n_div=4, forward='split_cat'):
super().__init__()
self.dim_conv3 = dim // n_div
self.dim_untouched = dim - self.dim_conv3
self.partial_conv3 = nn.Conv2d(self.dim_conv3, self.dim_conv3, 3, 1, 1, bias=False)
if forward == 'slicing':
self.forward = self.forward_slicing
elif forward == 'split_cat':
self.forward = self.forward_split_cat
else:
raise NotImplementedError
def forward_slicing(self, x):
# only for inference
x = x.clone() # !!! Keep the original input intact for the residual connection later
x[:, :self.dim_conv3, :, :] = self.partial_conv3(x[:, :self.dim_conv3, :, :])
return x
def forward_split_cat(self, x):
# for training/inference
x1, x2 = torch.split(x, [self.dim_conv3, self.dim_untouched], dim=1)
x1 = self.partial_conv3(x1)
x = torch.cat((x1, x2), 1)
return x
class Faster_Block(nn.Module):
def __init__(self,
inc,
dim,
n_div=4,
mlp_ratio=2,
drop_path=0.1,
layer_scale_init_value=0.0,
pconv_fw_type='split_cat'
):
super().__init__()
self.dim = dim
self.mlp_ratio = mlp_ratio
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
self.n_div = n_div
mlp_hidden_dim = int(dim * mlp_ratio)
mlp_layer = [
Conv(dim, mlp_hidden_dim, 1),
nn.Conv2d(mlp_hidden_dim, dim, 1, bias=False)
]
self.mlp = nn.Sequential(*mlp_layer)
self.spatial_mixing = Partial_conv3(
dim,
n_div,
pconv_fw_type
)
self.adjust_channel = None
if inc != dim:
self.adjust_channel = Conv(inc, dim, 1)
if layer_scale_init_value > 0:
self.layer_scale = nn.Parameter(layer_scale_init_value * torch.ones((dim)), requires_grad=True)
self.forward = self.forward_layer_scale
else:
self.forward = self.forward
def forward(self, x):
if self.adjust_channel is not None:
x = self.adjust_channel(x)
shortcut = x
x = self.spatial_mixing(x)
x = shortcut + self.drop_path(self.mlp(x))
return x
def forward_layer_scale(self, x):
shortcut = x
x = self.spatial_mixing(x)
x = shortcut + self.drop_path(
self.layer_scale.unsqueeze(-1).unsqueeze(-1) * self.mlp(x))
return x
class C3_Faster(C3):
def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):
super().__init__(c1, c2, n, shortcut, g, e)
c_ = int(c2 * e) # hidden channels
self.m = nn.Sequential(*(Faster_Block(c_, c_) for _ in range(n)))
class C2f_Faster(C2f):
def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):
super().__init__(c1, c2, n, shortcut, g, e)
self.m = nn.ModuleList(Faster_Block(self.c, self.c) for _ in range(n))
2.2 Faster_Block
的神经网络模块代码解析
1. 初始化与参数设定 (__init__
方法):
-
输入参数:
-
inc
: 输入特征图的通道数。 -
dim
: 模块中间层的通道数(也即输出的通道数)。 -
n_div
: 部分卷积(Partial_conv3
)的分割因子,用于控制部分卷积操作的分组数。 -
mlp_ratio
: MLP层的宽度倍率,决定了中间层的隐藏通道数。 -
drop_path
: DropPath概率,决定了跳跃连接中随机失活的比例。 -
layer_scale_init_value
: 层级缩放因子初始值,控制了残差连接中输出的缩放比例。 -
pconv_fw_type
: 部分卷积的前向传播类型。
-
-
内部层:
-
mlp
: 包含两个1x1卷积层的MLP层,用于通道之间的特征变换。 -
spatial_mixing
: 使用Partial_conv3
实现的部分卷积层,用于空间信息混合。 -
adjust_channel
: 当输入通道数inc
与模块的通道数dim
不一致时,用于调整通道数的一层1x1卷积。
-
2. 前向传播 (forward
方法):
-
通道调整:
-
如果输入通道数不等于目标通道数
dim
,则通过adjust_channel
调整输入通道数。
-
-
部分卷积 (
spatial_mixing
):-
将输入特征图通过
Partial_conv3
进行部分卷积操作,这一步通过分割因子n_div
将输入通道分为若干组,并对每组执行卷积操作。此步骤提取空间特征,同时减少了计算量。
-
-
MLP与残差连接:
-
将部分卷积后的特征图输入到MLP层,进行通道间的特征变换。
-
使用残差连接将输入的快捷路径(
shortcut
)与MLP的输出相加,这有助于在训练过程中稳定梯度传递。 -
如果启用了DropPath,则在加和之前随机失活一部分路径,以增加模型的鲁棒性。
-
3. 层级缩放 (forward_layer_scale
方法):
-
作用:
-
如果
layer_scale_init_value
大于0,使用层级缩放因子调整残差连接的输出权重。 -
在计算MLP输出时,残差连接中的输出会乘以一个可学习的缩放因子(
layer_scale
),以更好地控制不同层之间的特征信息。
-
-
步骤:
-
layer_scale
参数会按通道展开(使用unsqueeze
),与MLP输出相乘,之后再加到输入的快捷路径(shortcut
)上。
-
总结
Faster_Block
通过结合部分卷积和MLP层,在保持高效特征提取的同时减少了计算量和内存访问。残差连接确保了信息的有效传递,而可选的层级缩放提供了额外的模型灵活性,有助于提高训练和推理的稳定性与效率。
2.3 更改init.py文件
关键步骤二:修改modules文件夹下的__init__.py文件,先导入函数
然后在下面的__all__中声明函数
2.4 添加yaml文件
关键步骤三:在/ultralytics/ultralytics/cfg/models/v8下面新建文件yolov8_C2f_Faster.yaml文件,粘贴下面的内容
- OD【目标检测】
# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLOv8 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=yolov8n.yaml' will call yolov8.yaml with scale 'n'
# [depth, width, max_channels]
n: [0.33, 0.25, 1024] # YOLOv8n summary: 225 layers, 3157200 parameters, 3157184 gradients, 8.9 GFLOPs
s: [0.33, 0.50, 1024] # YOLOv8s summary: 225 layers, 11166560 parameters, 11166544 gradients, 28.8 GFLOPs
m: [0.67, 0.75, 768] # YOLOv8m summary: 295 layers, 25902640 parameters, 25902624 gradients, 79.3 GFLOPs
l: [1.00, 1.00, 512] # YOLOv8l summary: 365 layers, 43691520 parameters, 43691504 gradients, 165.7 GFLOPs
x: [1.00, 1.25, 512] # YOLOv8x summary: 365 layers, 68229648 parameters, 68229632 gradients, 258.5 GFLOPs
# YOLOv8.0n backbone
backbone:
# [from, repeats, module, args]
- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
- [-1, 3, C2f_Faster, [128, True]]
- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
- [-1, 6, C2f_Faster, [256, True]]
- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
- [-1, 6, C2f_Faster, [512, True]]
- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
- [-1, 3, C2f_Faster, [1024, True]]
- [-1, 1, SPPF, [1024, 5]] # 9
# YOLOv8.0n head
head:
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 6], 1, Concat, [1]] # cat backbone P4
- [-1, 3, C2f_Faster, [512]] # 12
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 4], 1, Concat, [1]] # cat backbone P3
- [-1, 3, C2f_Faster, [256]] # 15 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 12], 1, Concat, [1]] # cat head P4
- [-1, 3, C2f_Faster, [512]] # 18 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]]
- [[-1, 9], 1, Concat, [1]] # cat head P5
- [-1, 3, C2f_Faster, [1024]] # 21 (P5/32-large)
- [[15, 18, 21], 1, Detect, [nc]] # Detect(P3, P4, P5)
- Seg【语义分割】
# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLOv8 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=yolov8n.yaml' will call yolov8.yaml with scale 'n'
# [depth, width, max_channels]
n: [0.33, 0.25, 1024] # YOLOv8n summary: 225 layers, 3157200 parameters, 3157184 gradients, 8.9 GFLOPs
s: [0.33, 0.50, 1024] # YOLOv8s summary: 225 layers, 11166560 parameters, 11166544 gradients, 28.8 GFLOPs
m: [0.67, 0.75, 768] # YOLOv8m summary: 295 layers, 25902640 parameters, 25902624 gradients, 79.3 GFLOPs
l: [1.00, 1.00, 512] # YOLOv8l summary: 365 layers, 43691520 parameters, 43691504 gradients, 165.7 GFLOPs
x: [1.00, 1.25, 512] # YOLOv8x summary: 365 layers, 68229648 parameters, 68229632 gradients, 258.5 GFLOPs
# YOLOv8.0n backbone
backbone:
# [from, repeats, module, args]
- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
- [-1, 3, C2f_Faster, [128, True]]
- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
- [-1, 6, C2f_Faster, [256, True]]
- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
- [-1, 6, C2f_Faster, [512, True]]
- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
- [-1, 3, C2f_Faster, [1024, True]]
- [-1, 1, SPPF, [1024, 5]] # 9
# YOLOv8.0n head
head:
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 6], 1, Concat, [1]] # cat backbone P4
- [-1, 3, C2f_Faster, [512]] # 12
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 4], 1, Concat, [1]] # cat backbone P3
- [-1, 3, C2f_Faster, [256]] # 15 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 12], 1, Concat, [1]] # cat head P4
- [-1, 3, C2f_Faster, [512]] # 18 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]]
- [[-1, 9], 1, Concat, [1]] # cat head P5
- [-1, 3, C2f_Faster, [1024]] # 21 (P5/32-large)
- [[15, 18, 21], 1, Segment, [nc, 32, 256]] # Segment(P3, P4, P5)
温馨提示:因为本文只是对yolov8基础上添加模块,如果要对yolov8n/l/m/x进行添加则只需要指定对应的depth_multiple 和 width_multiple。不明白的同学可以看这篇文章: yolov8yaml文件解读——点击即可跳转
# YOLOv8n
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.25 # layer channel multiple
max_channels: 1024 # max_channels
# YOLOv8s
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
max_channels: 1024 # max_channels
# YOLOv8l
depth_multiple: 1.0 # model depth multiple
width_multiple: 1.0 # layer channel multiple
max_channels: 512 # max_channels
# YOLOv8m
depth_multiple: 0.67 # model depth multiple
width_multiple: 0.75 # layer channel multiple
max_channels: 768 # max_channels
# YOLOv8x
depth_multiple: 1.33 # model depth multiple
width_multiple: 1.25 # layer channel multiple
max_channels: 512 # max_channels
2.5 注册模块
关键步骤四:在task.py的parse_model函数中注册
2.6 执行程序
在train.py中,将model的参数路径设置为yolov8_C2f_Faster.yaml的路径
建议大家写绝对路径,确保一定能找到
from ultralytics import YOLO
import warnings
warnings.filterwarnings('ignore')
from pathlib import Path
if __name__ == '__main__':
# 加载模型
model = YOLO("ultralytics/cfg/v8/yolov8.yaml") # 你要选择的模型yaml文件地址
# Use the model
results = model.train(data=r"你的数据集的yaml文件地址",
epochs=100, batch=16, imgsz=640, workers=4, name=Path(model.cfg).stem) # 训练模型
🚀运行程序,如果出现下面的内容则说明添加成功🚀
from n params module arguments
0 -1 1 464 ultralytics.nn.modules.conv.Conv [3, 16, 3, 2]
1 -1 1 4672 ultralytics.nn.modules.conv.Conv [16, 32, 3, 2]
2 -1 1 3920 ultralytics.nn.modules.block.C2f_Faster [32, 32, 1, True]
3 -1 1 18560 ultralytics.nn.modules.conv.Conv [32, 64, 3, 2]
4 -1 2 22144 ultralytics.nn.modules.block.C2f_Faster [64, 64, 2, True]
5 -1 1 73984 ultralytics.nn.modules.conv.Conv [64, 128, 3, 2]
6 -1 2 87552 ultralytics.nn.modules.block.C2f_Faster [128, 128, 2, True]
7 -1 1 295424 ultralytics.nn.modules.conv.Conv [128, 256, 3, 2]
8 -1 1 240128 ultralytics.nn.modules.block.C2f_Faster [256, 256, 1, True]
9 -1 1 164608 ultralytics.nn.modules.block.SPPF [256, 256, 5]
10 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
11 [-1, 6] 1 0 ultralytics.nn.modules.conv.Concat [1]
12 -1 1 93184 ultralytics.nn.modules.block.C2f_Faster [384, 128, 1]
13 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
14 [-1, 4] 1 0 ultralytics.nn.modules.conv.Concat [1]
15 -1 1 23488 ultralytics.nn.modules.block.C2f_Faster [192, 64, 1]
16 -1 1 36992 ultralytics.nn.modules.conv.Conv [64, 64, 3, 2]
17 [-1, 12] 1 0 ultralytics.nn.modules.conv.Concat [1]
18 -1 1 68608 ultralytics.nn.modules.block.C2f_Faster [192, 128, 1]
19 -1 1 147712 ultralytics.nn.modules.conv.Conv [128, 128, 3, 2]
20 [-1, 9] 1 0 ultralytics.nn.modules.conv.Concat [1]
21 -1 1 272896 ultralytics.nn.modules.block.C2f_Faster [384, 256, 1]
22 [15, 18, 21] 1 897664 ultralytics.nn.modules.head.Detect [80, [64, 128, 256]]
YOLOv8_c2f_faster summary: 245 layers, 2452000 parameters, 2451984 gradients, 7.1 GFLOPs
3. 完整代码分享
https://pan.baidu.com/s/1MtbUlIK6Qdso2lwSfQMgvg?pwd=1bgi
提取码: 1bgi
4. GFLOPs
关于GFLOPs的计算方式可以查看:百面算法工程师 | 卷积基础知识——Convolution
未改进的YOLOv8nGFLOPs
改进后的GFLOPs
5. 进阶
可以与其他的注意力机制或者损失函数等结合,进一步提升检测效果
6. 总结
FasterNet的主要工作原理在于通过优化神经网络的计算效率来提高实际运行速度(FLOPS),而不仅仅是减少浮点运算次数(FLOPs)。传统方法常通过减少FLOPs来降低计算复杂度,但这并不总能带来实际延迟的减少,主要因为频繁的内存访问限制了实际计算速度。为了解决这一问题,FasterNet引入了部分卷积(PConv)这一新型操作符,通过仅对部分输入通道进行卷积操作,减少了冗余计算和内存访问需求,同时保持高效的空间特征提取能力。基于PConv,FasterNet设计了一系列神经网络,在各种设备上实现了显著更快的运行速度,并在图像分类等任务中保持了较高的准确性。总的来说,FasterNet通过兼顾FLOPs和FLOPS,达到了更低的延迟和更高的吞吐量,适用于GPU、CPU和ARM处理器等多种硬件平台。