YOLOv8详解 网络结构+代码+实操

Posted zyw2002

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了YOLOv8详解 网络结构+代码+实操相关的知识,希望对你有一定的参考价值。

文章目录

YOLOv8 概述

YOLOv8 算法的核心特性和改动可以归结为如下:

  • 提供了一个全新的 SOTA 模型,包括 P5 640 和 P6 1280 分辨率的目标检测网络和基于 YOLACT 的实例分割模型。和 YOLOv5 一样,基于缩放系数也提供了 N/S/M/L/X 尺度的不同大小模型,用于满足不同场景需求

  • Backbone:
    骨干网络和 Neck 部分可能参考了 YOLOv7 ELAN 设计思想,将 YOLOv5 的 C3 结构换成了梯度流更丰富的 C2f 结构,并对不同尺度模型调整了不同的通道数。

属于对模型结构精心微调,不再是无脑一套参数应用所有模型,大幅提升了模型性能。不过这个 C2f 模块中存在 Split 等操作对特定硬件部署没有之前那么友好了

  • Head: Head部分较yolov5而言有两大改进:1)换成了目前主流的解耦头结构(Decoupled-Head),将分类和检测头分离 2)同时也从 Anchor-Based 换成了 Anchor-Free

  • Loss :1) YOLOv8抛弃了以往的IOU匹配或者单边比例的分配方式,而是使用了Task-Aligned Assigner正负样本匹配方式。2)并引入了 Distribution Focal Loss(DFL)

  • Train:训练的数据增强部分引入了 YOLOX 中的最后 10 epoch 关闭 Mosiac 增强的操作,可以有效地提升精度

从上面可以看出,YOLOv8 主要参考了最近提出的诸如 YOLOX、YOLOv6、YOLOv7 和 PPYOLOE 等算法的相关设计,本身的创新点不多,偏向工程实践,主推的还是 ultralytics 这个框架本身。

下面将按照模型结构设计、Loss 计算、训练数据增强、训练策略和模型推理过程共 5 个部分详细介绍 YOLOv8 目标检测的各种改进,实例分割部分暂时不进行描述。

模型结构

如下图, 左侧为 YOLOv5-s,右侧为 YOLOv8-s。
在暂时不考虑 Head 情况下,对比 YOLOv5 和 YOLOv8 的 yaml 配置文件可以发现改动较小。


Backbone和Neck的具体变化
a) 第一个卷积层的 kernel 从 6x6 变成了 3x3
b) 所有的 C3 模块换成 C2f,结构如下所示,可以发现多了更多的跳层连接和额外的 Split 操作

C3C2f
c)去掉了 Neck 模块中的 2 个卷积连接层

d) Backbone 中 C2f 的 block 数从 3-6-9-3 改成了 3-6-6-3
e) 查看 N/S/M/L/X 等不同大小模型,可以发现 N/S 和 L/X 两组模型只是改了缩放系数,但是 S/M/L 等骨干网络的通道数设置不一样,没有遵循同一套缩放系数。如此设计的原因应该是同一套缩放系数下的通道设置不是最优设计,YOLOv7 网络设计时也没有遵循一套缩放系数作用于所有模型


Head的具体变化
从原先的耦合头变成了解耦头,并且从 YOLOv5 的 Anchor-Based 变成了 Anchor-Free。

C3C2f

从上图可以看出,不再有之前的 objectness 分支,只有解耦的分类和回归分支,并且其回归分支使用了 Distribution Focal Loss 中提出的积分形式表示法。

Loss 计算

Loss 计算过程包括 2 个部分: 正负样本分配策略和 Loss 计算。


正负样本分配策略
现代目标检测器大部分都会在正负样本分配策略上面做文章,典型的如 YOLOX 的 simOTA、TOOD 的 TaskAlignedAssigner 和 RTMDet 的 DynamicSoftLabelAssigner,这类 Assigner 大都是动态分配策略,而 YOLOv5 采用的依然是静态分配策略。考虑到动态分配策略的优异性,YOLOv8 算法中则直接引用了 TOOD 的 TaskAlignedAssigner。
TaskAlignedAssigner 的匹配策略简单总结为: 根据分类与回归的分数加权的分数选择正样本。


Loss计算
Loss 计算包括 2 个分支: 分类和回归分支,没有了之前的 objectness 分支。
分类分支依然采用 BCE Loss
回归分支需要和 Distribution Focal Loss 中提出的积分形式表示法绑定,因此使用了 Distribution Focal Loss, 同时还使用了 CIoU Loss。3 个 Loss 采用一定权重比例加权即可。

训练数据增强

数据增强方面和 YOLOv5 差距不大,只不过引入了 YOLOX 中提出的最后 10 个 epoch 关闭 Mosaic 的操作。假设训练 epoch 是 500,其示意图如下所示:

考虑到不同模型应该采用的数据增强强度不一样,因此对于不同大小模型,有部分超参会进行修改,典型的如大模型会开启 MixUp 和 CopyPaste。数据增强后典型效果如下所示:

训练策略

YOLOv8 的训练策略和 YOLOv5 没有啥区别,最大区别就是模型的训练总 epoch 数从 300 提升到了 500,这也导致训练时间急剧增加。以 YOLOv8-S 为例,其训练策略汇总如下:

模型推理过程

YOLOv8 的推理过程和 YOLOv5 几乎一样,唯一差别在于前面需要对 Distribution Focal Loss 中的积分表示 bbox 形式进行解码,变成常规的 4 维度 bbox,后续计算过程就和 YOLOv5 一样了。

其推理和后处理过程为:

(1) bbox 积分形式转换为 4d bbox 格式
对 Head 输出的 bbox 分支进行转换,利用 Softmax 和 Conv 计算将积分形式转换为 4 维 bbox 格式

(2) 维度变换
YOLOv8 输出特征图尺度为 80x80、40x40 和 20x20 的三个特征图。Head 部分输出分类和回归共 6 个尺度的特征图。 将 3 个不同尺度的类别预测分支、bbox 预测分支进行拼接,并进行维度变换。为了后续方便处理,会将原先的通道维度置换到最后,类别预测分支 和 bbox 预测分支 shape 分别为 (b, 80x80+40x40+20x20, 80)=(b,8400,80),(b,8400,4)。

(3) 解码还原到原图尺度
分类预测分支进行 Sigmoid 计算,而 bbox 预测分支需要进行解码,还原为真实的原图解码后 xyxy 格式。

(4) 阈值过滤
遍历 batch 中的每张图,采用 score_thr 进行阈值过滤。在这过程中还需要考虑 multi_label 和 nms_pre,确保过滤后的检测框数目不会多于 nms_pre。

(5) 还原到原图尺度和 nms
基于前处理过程,将剩下的检测框还原到网络输出前的原图尺度,然后进行 nms 即可。最终输出的检测框不能多于 max_per_img。

有一个特别注意的点:YOLOv5 中采用的 Batch shape 推理策略,在 YOLOv8 推理中暂时没有开启,不清楚后面是否会开启,在 MMYOLO 中快速测试了下,如果开启 Batch shape 会涨大概 0.1~0.2。

网络模型解析

卷积神经单元(model.py)

ultralytics/nn/modules.py文件中定义了yolov8网络中的卷积神经单元。


autopad

  • 功能: 返回pad的大小,使得padding后输出张量的大小不变。
  • 参数:
    • k: 卷积核(kernel)的大小。类型可能是一个int也可能是一个序列
    • p: 填充(padding)的大小。默认为None
    • d: 扩张率(dilation rate)的大小, 默认为1 。普通卷积的扩张率为1,空洞卷积的扩张率大于1。

假设k为原始卷积核大小,d为卷积扩张率(dilation rate),加入空洞之后的实际卷积核尺寸与原始卷积核尺寸之间的关系:k =d(k-1)+1

通常,如果我们添加 p h p_h ph行填充(大约一半在顶部,一半在底部)和 p w p_w pw列填充(大约一半在左侧,一半在右侧),则输出的形状为
( n h − k h + p h + 1 ) × ( n w − k w + p w + 1 ) (n_h-k_h+p_h+1)\\times (n_w-k_w+p_w+1) (nhkh+ph+1)×(nwkw+pw+1)
当设置 p h = k h − 1 p_h=k_h-1 ph=kh1 p w = k w − 1 p_w=k_w-1 pw=kw1时,输入和输出具有相同的高度和宽度。
假设p为填充(padding)的大小(通常, p h = p w = p 2 p_h=p_w=\\fracp2 ph=pw=2p )。一般来说 k h = k w = k k_h=k_w=k kh=kw=k,且为奇数。
则当p=k//2时,padding后输出张量的大小不变。

def autopad(k, p=None, d=1):  # kernel(卷积核), padding(填充), dilation(扩张)
    # 返回pad的大小,使得padding后输出张量的shape不变
    if d > 1: # 如果采用扩张卷积,则计算扩张后实际的kernel大小
        k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k]  # 
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # 自动pad
    return p

Conv

  • 功能: 标准的卷积
  • 参数:输入通道数(c1), 输出通道数(c2), 卷积核大小(k,默认是1), 步长(s,默认是1), 填充(p,默认为None), 组(g, 默认为1), 扩张率(d,默认为1), 是否采用激活函数(act ,默认为True, 且采用SiLU为激活函数)

激活函数采用的是SiLU SiLU ⁡ ( x ) = x ( 1 1 + e − x ) \\operatornameSiLU(x)=x\\left(\\frac11+e^-\\mathrmx\\right) SiLU(x)=x(1+ex1)

class Conv(nn.Module):
    # 标准的卷积 参数(输入通道数, 输出通道数, 卷积核大小, 步长, 填充, 组, 扩张, 激活函数)
    default_act = nn.SiLU()  # 默认的激活函数

    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False) # 2维卷积,其中采用了自动填充函数。
        self.bn = nn.BatchNorm2d(c2) # 使得每一个batch的特征图均满足均值为0,方差为1的分布规律
        # 如果act=True 则采用默认的激活函数SiLU;如果act的类型是nn.Module,则采用传入的act; 否则不采取任何动作 (nn.Identity函数相当于f(x)=x,只用做占位,返回原始的输入)。
        self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity() 

    def forward(self, x):  # 前向传播
        return self.act(self.bn(self.conv(x))) # 采用BatchNorm
    def forward_fuse(self, x): #  用于Model类的fuse函数融合 Conv + BN 加速推理,一般用于测试/验证阶段
        return self.act(self.conv(x)) # 不采用BatchNorm

DWConv
深度可分离卷积,继承自Conv
g=math.gcd(c1, c2) 分组数是输入通道(c1)和输出通道(c2)的最大公约数。(因为分组卷积时,分组数需要能够整除输入通道和输出通道)

class DWConv(Conv):
    # 深度可分离卷积
    def __init__(self, c1, c2, k=1, s=1, d=1, act=True):  # ch_in, ch_out, kernel, stride, dilation, activation
        super().__init__(c1, c2, k, s, g=math.gcd(c1, c2), d=d, act=act)

DWConvTranspose2d
带有深度分离的转置卷积,继承自nn.ConvTranspose2d
groups=math.gcd(c1, c2) 分组数是输入通道(c1)和输出通道(c2)的最大公约数。(因为分组卷积时,分组数需要能够整除输入通道和输出通道)

class DWConvTranspose2d(nn.ConvTranspose2d):
    # Depth-wise transpose convolution
    def __init__(self, c1, c2, k=1, s=1, p1=0, p2=0):  # 输入通道, 输出通道, 卷积核大小, 步长, padding, padding_out
        super().__init__(c1, c2, k, s, p1, p2, groups=math.gcd(c1, c2))

ConvTranspose
和Conv类似,只是把Conv2d换成了ConvTranspose2d

class ConvTranspose(nn.Module):
    # Convolution transpose 2d layer
    default_act = nn.SiLU()  # default activation

    def __init__(self, c1, c2, k=2, s=2, p=0, bn=True, act=True):
        super().__init__()
        self.conv_transpose = nn.ConvTranspose2d(c1, c2, k, s, p, bias=not bn)
        self.bn = nn.BatchNorm2d(c2) if bn else nn.Identity()
        self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()

    def forward(self, x):
        return self.act(self.bn(self.conv_transpose(x)))

DFL(Distribution Focal Loss)
本篇文章(https://ieeexplore.ieee.org/document/9792391)提出了GFL(了Generalized Focal Loss)。GFL具体又包括Quality Focal Loss(QFL)和Distribution Focal Loss(DFL),其中QFL用于优化分类和质量估计联合分支,DFL用于优化边框分支。

class DFL(nn.Module):
    # Integral module of Distribution Focal Loss (DFL) proposed in Generalized Focal Loss 
    def __init__(self, c1=16):
        super().__init__()
        self.conv = nn.Conv2d(c1, 1, 1, bias=False).requires_grad_(False)
        x = torch.arange(c1, dtype=torch.float)
        self.conv.weight.data[:] = nn.Parameter(x.view(1, c1, 1, 1))
        self.c1 = c1

    def forward(self, x):
        b, c, a = x.shape  # batch, channels, anchors
        return self.conv(x.view(b, 4, self.c1, a).transpose(2, 1).softmax(1)).view(b, 4, a)
        # return self.conv(x.view(b, self.c1, 4, a).softmax(1)).view(b, 4, a)

TransformerLayer

关于Transformer的理解和torch.nn.MultiheadAttention 的用法,请参考博客《详解注意力机制和Transformer》

论文《Attention is all you need》 中提出的Transformer架构。如下图,是Transformer中的Encoder部分。
单头注意力(Attention): Attention ⁡ ( q , k , v ) = softmax ⁡ ( q ⋅ k T k ⋅ d i m ) ⋅ v \\operatornameAttention(q, k, v)=\\operatornamesoftmax\\left(\\fracq \\cdot k^T\\sqrtk \\cdot d i m\\right) \\cdot v Attention(q,k,v)=softmax(kdim qkT)v
多头注意力(Multihead-Attention) q , k , v q, k, v q,k,v 均是长度 c c 以上是关于YOLOv8详解 网络结构+代码+实操的主要内容,如果未能解决你的问题,请参考以下文章

yolo v8 解决了 v5 的问题嘛?

yolo v8 解决了 v5 的问题嘛?

Yolov8详解与实战

YOLOYOLOv8实操:环境配置/自定义数据集准备/模型训练/预测

YOLOv8 Ultralytics:最先进的 YOLO 模型——简介+实战教程

CSDN独家原创《芒果YOLO改进高阶指南》适用YOLOv5YOLOv7YOLOv8等改进专栏,来自人工智能专家唐宇迪老师联袂推荐