YOLOv5改进系列(四) 本文(2.5万字) | 更换Neck | BiFPN | AFPN | BiFusion |

142 篇文章 7 订阅

已下架不支持订阅

本文详细介绍了YOLOv5的改进,包括BiFPN、AFPN和BiFusion的原理、添加方法及优缺点。BiFPN通过跨尺度连接和加权特征融合提升特征融合效果,AFPN采用渐进架构和自适应空间融合优化多级特征提取。BiFusion Neck结合两者优点,实现高性能与低计算量的平衡。文章还提供了添加这些改进到YOLOv5的具体步骤。
摘要由CSDN通过智能技术生成

点击进入专栏:
《人工智能专栏》 Python与Python | 机器学习 | 深度学习 | 目标检测 | YOLOv5及其改进 | YOLOv8及其改进 | 关键知识点 | 各种工具教程


代码函数调用关系图(全网最详尽-重要)

因文档特殊,不能在博客正确显示,请移步以下链接!

图解YOLOv5_v7.0代码结构与调用关系(点击进入可以放大缩小等操作)

预览:
在这里插入图片描述


文章目录

  • BiFPN
    • 一、BiFPN介绍
      • 1.1 简介
      • 1.2 BiFPN
        • (1)跨尺度连接
        • (2)加权特征融合
      • 1.3 EfficientDet
        • (1)模型框架
        • (2)复合缩放
    • 二、添加方式1:Add操作
        • 第①步:在common.py中添加BiFPN模块
        • 第②步:在yolo.py文件里的parse_model函数加入类名
        • 第③步:创建自定义的yaml文件
        • 第④步:验证是否加入成功
        • 第⑤步:修改train.py
    • 三、添加方式2:Concat操作
        • 第①步:在common.py中添加BiFPN模块
        • 第②步:在yolo.py文件里的parse_model函数加入类名
        • 第③步:创建自定义的yaml文件
        • 第④步:验证是否加入成功
        • 第⑤步:修改train.py
  • Neck之AFPN
    • 一、AFPN介绍
      • img1.1 简介
      • 1.2 提取多级特征
      • 1.3 渐进架构
      • 1.4 自适应空间融合
      • 1.5 实验
    • 二、更换AFPN的方法
        • 第①步:在common.py中添加AFPN模块
        • 第②步:修改yolo.py文件
        • 第③步:创建自定义的yaml文件
  • BiFusion
        • YOLOv6贡献
        • BiFusion Neck 融合的原理
        • BiFusion Neck结构图
        • 参数量与计算量
        • YOLOv5 BiFusion Neck 配置文件


BiFPN

详细解读

一、BiFPN介绍

1.1 简介

EfficientDet 是继 2019 年推出 EfficientNet 模型之后,Google 人工智能研究小组Tan Mingxing等人为进一步提高目标检测效率,以 EfficientNet 模型和双向特征加权金字塔网络 BiFPN为基础,于2020 年创新推出的新一代目标检测模型,在COCO数据集上吊打其他方法。

EfficientDet = Backbone(EfficientNet) + Neck(BiFPN) + Head(class + box)

img


1.2 BiFPN

(1)跨尺度连接
  • 移除那些只有一条输入边的节点,这是因为如果一个节点只有一条输入边而没有特征融合,那么它对以融合不同特征为目标的特征网络的贡献就比较小。这可以简化双向网络。
  • 如果原始输入节点和输出节点处于同一水平,就在它们之间增加一条额外的边,以便在不增加太多成本的情况下融合更多的特征。
  • 与PANet只有一条自上而下和一条自下而上的路径不同,BiFPN将每条双向(自上而下&自下而上)路径视为一个特征网络层,并多次重复同一层,以实现更高级别的特征融合。

img


(2)加权特征融合
  • 无限融合:img
  • 基于Softmax的融合:img
  • 快速归一化融合: img

1.3 EfficientDet

(1)模型框架

img

  • **backbone:**EfficientNets
  • **特征网络:**BiFPN

(2)复合缩放

EfficientDet使用的是EfficientNet-B0到B7作为预训练模型,所以EfficientDet的系数ϕ的选择范围也是0~7。

Backbone network—主干网络

骨干网络采用和EfficientNet B0~B6相同的缩放系数,从而可以使用它们在ImageNet上的预训练模型。

BiFPN network—BiFPN 网络

对于BiFPN的深度 Dbifpn 采用线性变换的方式因为深度需要向下取整。对于宽度 Wbifpn采用指数变换的方式,采用网格搜索确定1.35作为宽度的缩放因子。

完整的缩放公式如下:

img

Box/class prediction network—Box/class预测网络

宽度固定为和BiFPN的宽度相等即 Wpred=Wbifpn ,深度按下式进行线性变换:

img

Input image resolution—输入图像分辨率

因为BiFPN中用到了level 3-7的特征,因此输入大小需要能被 2^7=128 除尽,因此输入分辨率按下式进行线性变换:

img


二、添加方式1:Add操作

第①步:在common.py中添加BiFPN模块

common.py后面加入如下代码:

# BiFPN 
# 两个特征图add操作
class BiFPN_Add2(nn.Module):
    def __init__(self, c1, c2):
        super(BiFPN_Add2, self).__init__()
        # 设置可学习参数 nn.Parameter的作用是:将一个不可训练的类型Tensor转换成可以训练的类型parameter
        # 并且会向宿主模型注册该参数 成为其一部分 即model.parameters()会包含这个parameter
        # 从而在参数优化的时候可以自动一起优化
        self.w = nn.Parameter(torch.ones(2, dtype=torch.float32), requires_grad=True)
        self.epsilon = 0.0001
        self.conv = nn.Conv2d(c1, c2, kernel_size=1, stride=1, padding=0)
        self.silu = nn.SiLU()
 
    def forward(self, x):
        w = self.w
        weight = w / (torch.sum(w, dim=0) + self.epsilon)
        return self.conv(self.silu(weight[0] * x[0] + weight[1] * x[1]))
 
 
# 三个特征图add操作
class BiFPN_Add3(nn.Module):
    def __init__(self, c1, c2):
        super(BiFPN_Add3, self).__init__()
        self.w = nn.Parameter(torch.ones(3, dtype=torch.float32), requires_grad=True)
        self.epsilon = 0.0001
        self.conv = nn.Conv2d(c1, c2, kernel_size=1, stride=1, padding=0)
        self.silu = nn.SiLU()
 
    def forward(self, x):
        w = self.w
        weight = w / (torch.sum(w, dim=0) + self.epsilon)  
        # Fast normalized fusion
        return self.conv(self.silu(weight[0] * x[0] + weight[1] * x[1] + weight[2] * x[2]))
 

如下图所示:

img


第②步:在yolo.py文件里的parse_model函数加入类名

再来修改yolo.py,在parse_model函数中找到 elif m is Concat: 语句,在其后面加上BiFPN_Add相关语句:

# 添加bifpn_add结构
elif m in [BiFPN_Add2, BiFPN_Add3]:
    c2 = max([ch[x] for x in f])

如下图所示:

img


第③步:创建自定义的yaml文件

这里的yaml文件将所有的Concat换成了BiFPN_Add

BiFPN_Add本质是add操作,不是concat操作,因此BiFPN_Add的各个输入层要求大小完全一致(通道数、feature map大小等)

yaml文件配置完整代码如下:

# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
 
# Parameters
nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # 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]
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 6, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, SPPF, [1024, 5]],  # 9
  ]
 
# YOLOv5 v6.1 BiFPN head
head:
  [[-1, 1, Conv, [512, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, BiFPN_Add2, [256, 256]],  # cat backbone P4
   [-1, 3, C3, [512, False]],  # 13
 
   [-1, 1, Conv, [256, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 4], 1, BiFPN_Add2, [128, 128]],  # cat backbone P3
   [-1, 3, C3, [256, False]],  # 17 
 
   [-1, 1, Conv, [512, 3, 2]],  
   [[-1, 13, 6], 1, BiFPN_Add3, [256, 256]],  #v5s通道数是默认参数的一半
   [-1, 3, C3, [512, False]],  # 20 
 
   [-1, 1, Conv, [512, 3, 2]],
   [[-1, 10], 1, BiFPN_Add2, [256, 256]],  # cat head P5
   [-1, 3, C3, [1024, False]],  # 23 
 
   [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]


第④步:验证是否加入成功

yolo.py 文件里面配置改为我们刚才自定义的 yolov5s_BiFPN.yaml

img

img

然后运行yolo.py

img

我们可以看到,所有的Concat已被换成了BiFPN_Add


第⑤步:修改train.py

首先找到train.py文件里面的# Optimizer

然后将BiFPN_Add2BiFPN_Add3 函数中定义的w参数,加入g1

# BiFPN_Concat
 elif isinstance(v, BiFPN_Add2) and hasattr(v, 'w') and isinstance(v.w, nn.Parameter):
            g1.append(v.w)
 elif isinstance(v, BiFPN_Add3) and hasattr(v, 'w') and isinstance(v.w, nn.Parameter):
            g1.append(v.w)

如下图所示:

img

刚加入的时候会报错,莫慌~

img

这是没有导入包引起的啦~

我们可以直接导入 :

img

也可以加一个导包语句:

from models.common import BiFPN_Add3, BiFPN_Add2

然后就可以开始训练了

img

— 注意!—

因为我使用的还是6.1版本,是可以直接在train.py进行修改的,但是在看了一些后期改进的文章,发现在yolov5-v7.0版本中,这个部分作者加入了智能优化器(smart_optimizer)
img

我们ctrl+鼠标左键点击这个函数,进入之后可以发现optimizer这个函数进行了重构,之前的一重for循环被改成两重for。

img 另外,原来的 g[0] g[1] g[2] 被替换为g = [] [] []

  • 新版将这个地方关于weight的顺序翻转了一下,这样就导致一个问题,只要不是bias或者weight no decay,那么就全都归结于weight with decay上。
  • 与之前需要elif 进行判断Bi_FPN进行模型的添加相比,这里不在需要添加判断条件了,因为最后的else会把 剩余非bias 和非weight nodecay 部分全部加到weight with decay上。
  • 也就是说,添加其他Neck时,不需要额外对optimizer进行添加elif判断,也就实现了一个所谓智能的优化。

所以7.0版本无需对参数g的修改,直接略过即可,智能优化器会对多余的部分进行自动增加权重。

(以上解析来自:yolov5-7.0关于添加Bi_FPN的探讨_吃瓜太狼的博客-CSDN博客


三、添加方式2:Concat操作

第①步:在common.py中添加BiFPN模块

common.py后面加入如下代码:

# 结合BiFPN 设置可学习参数 学习不同分支的权重
# 两个分支concat操作
class BiFPN_Concat2(nn.Module):
    def __init__(self, dimension=1):
        super(BiFPN_Concat2, self).__init__()
        self.d = dimension
        self.w = nn.Parameter(torch.ones(2, dtype=torch.float32), requires_grad=True)
        self.epsilon = 0.0001
 
    def forward(self, x):
        w = self.w
        weight = w / (torch.sum(w, dim=0) + self.epsilon)  # 将权重进行归一化
        # Fast normalized fusion
        x = [weight[0] * x[0], weight[1] * x[1]]
        return torch.cat(x, self.d)
 
 
# 三个分支concat操作
class BiFPN_Concat3(nn.Module):
    def __init__(self, dimension=1):
        super(BiFPN_Concat3, self).__init__()
        self.d = dimension
        # 设置可学习参数 nn.Parameter的作用是:将一个不可训练的类型Tensor转换成可以训练的类型parameter
        # 并且会向宿主模型注册该参数 成为其一部分 即model.parameters()会包含这个parameter
        # 从而在参数优化的时候可以自动一起优化
        self.w = nn.Parameter(torch.ones(3, dtype=torch.float32), requires_grad=True)
        self.epsilon = 0.0001
 
    def forward(self, x):
        w = self.w
        weight = w / (torch.sum(w, dim=0) + self.epsilon)  # 将权重进行归一化
        # Fast normalized fusion
        x = [weight[0] * x[0], weight[1] * x[1], weight[2] * x[2]]
        return torch.cat(x, self.d)

如下图所示:

img


第②步:在yolo.py文件里的parse_model函数加入类名

再来修改yolo.py,在parse_model函数中找到 elif m is Concat: 语句,在其后面加上BiFPN_Concat相关语句:

# 添加bifpn_concat结构
elif m in [Concat, BiFPN_Concat2, BiFPN_Concat3]:
    c2 = sum(ch[x] for x in f)

如下图所示:

img


第③步:创建自定义的yaml文件

yaml文件配置完整代码如下

# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
 
# Parameters
nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # 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]
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 6, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, SPPF, [1024, 5]],  # 9
  ]
 
# YOLOv5 v6.0 BiFPN head
head:
  [[-1, 1, Conv, [512, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, BiFPN_Concat2, [1]],  # cat backbone P4 <--- BiFPN change
   [-1, 3, C3, [512, False]],  # 13
 
   [-1, 1, Conv, [256, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 4], 1, BiFPN_Concat2, [1]],  # cat backbone P3 <--- BiFPN change
   [-1, 3, C3, [256, False]],  # 17 (P3/8-small)
 
   [-1, 1, Conv, [256, 3, 2]],
   [[-1, 14, 6], 1, BiFPN_Concat3, [1]],  # cat P4 <--- BiFPN change
   [-1, 3, C3, [512, False]],  # 20 (P4/16-medium)
 
   [-1, 1, Conv, [512, 3, 2]],
   [[-1, 10], 1, BiFPN_Concat2, [1]],  # cat head P5 <--- BiFPN change
   [-1, 3, C3, [1024, False]],  # 23 (P5/32-large)
 
   [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]

第④步:验证是否加入成功

和上面方法一样,我们直接运行yolo.py

img

可以看到已经替换成功!


第⑤步:修改train.py

最后向优化器中添加BiFPN的权重参数,也和上面步骤一样。

# BiFPN_Concat
        elif isinstance(v, BiFPN_Concat2) and hasattr(v, 'w') and isinstance(v.w, nn.Parameter):
            g1.append(v.w)
        elif isinstance(v, BiFPN_Concat3) and hasattr(v, 'w') and isinstance(v.w, nn.Parameter):
            g1.append(v.w)

Neck之AFPN

详细解读

一、AFPN介绍

img1.1 简介

多尺度特征在目标检测任务中对具有尺度方差的目标进行编码时具有重要意义。多尺度特征提取的一种常见策略是采用经典的自上而下和自下而上的特征金字塔网络然而,这些方法遭受特征信息的丢失或退化,削弱了非相邻 Level 的融合效果。

本文使用了一种渐进特征金字塔网络AFPN来解决上述问题。在自下而上的特征提取过程中,首先通过组合两个具有不同分辨率的低层特征来启动融合过程。接着逐渐将高层特征纳入融合过程,最终融合主干网络的顶级特征。这种融合方式可以避免非相邻层次之间存在较大的语义差距

在此过程中,模型学习了空间滤波冲突信息来抑制不一致性的方法,针对有利用价值的信息保留后再加以组合自动地学习权重参数,以渐进的方式将低层特征与高层特征的语义信息和详细信息直接相互融合,避免了多级传输中的信息丢失或退化,提高特征尺度的不变性,并且引入的计算开销很小,实现简单。

img


1.2 提取多级特征

AFPN框架首先从主干网络的每个特征层提取最后一层特征,产生一组不同尺度的特征,表示为{C2, C3, C4, C5}。

接着将低层特征C2和C3输入到特征金字塔网络中,进行一次特征融合,然后添加C4,和之前添加的C2、C3一起做特征融合,最后添加C5,和之前添加的层继续做特征融合。最后产生一组多尺度特征{P2, P3, P4, P5}。

对于在Faster R-CNN框架上进行的实验,**作者将Stride为2的卷积应用于P5,然后再应用Stride为1的卷积来生成P6,这确保了统一的输出。**最后一组多尺度特征是{P2, P3, P4, P5, P6},对应的特征Stride为{4, 8, 16, 32, 64}个像素。


1.3 渐进架构

img

在主干网络自下而上的特征提取过程中,AFPN最初融合了低层特征,然后融合了深层特征,最后融合了最高层的特征,即最抽象的特征。

为了避免非相邻层次特征之间的语义差距大于相邻层次特征间的语义差距,导致的非相邻层次特征的融合效果较差问题,AFPN的架构采取渐进模式,每一层包含前一层的特征信息,这将使不同 Level 特征的语义信息在渐进融合过程中更加接近。

另外,为了对齐维度并为特征融合做准备,模型使用1×1卷积层双线性插值方法对特征进行上采样,并根据所需的下采样率使用不同的卷积核和步长来执行下采样

在特征融合之后,作者使用4个残差单元继续学习特征,这些残差单元类似于ResNet,每个残差单元包括2个3×3卷积。由于YOLO中只使用了3个 Level 的特征,因此没有8次上采样和8次下采样。


1.4 自适应空间融合

在多级特征融合过程中,作者利用ASFF为不同 Level 的特征分配不同的空间权重,与基于元素和或级联的多层次特征融合方法不同,其核心思想是自适应地学习各尺度特征映射融合的空间权重,通过学习权重参数的方式将不同层的特征融合到一起,可使网络自动学习过滤掉其他层的无用信息,保留有效信息,从而高效地融合特征。

img适应空间特征融合ASFF(Adaptive Spatial Feature Fusion)结构图

如上图所示,作者融合了3个层次的特征。让![{x_{ij}}^{n->l}](https://img-blog.csdnimg.cn/img_convert/5681efc23e7cc5faedb15353ddd163c1.png)表示从 Level n到 Level![l](https://img-blog.csdnimg.cn/img_convert/bdef0c72c419d2318a39615b60828b5d.png)的位置(i,j)处的特征向量。结果特征向量表示为[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YB7Glp2z-1706690017309)(https://latex.csdn.net/eq?l)],通过多级特征的自适应空间融合获得,并由特征向量的线性组合![{x_{ij}}^{1->l}](https://img-blog.csdnimg.cn/img_convert/f33ca84cc2499aee815f85debd637d65.png),![{x_{ij}}^{2->l}](https://img-blog.csdnimg.cn/img_convert/b9c8a83730be66a236defd2d1ce788ab.png)和![{x_{ij}}^{3->l}](https://img-blog.csdnimg.cn/img_convert/b28de5e2faa5c1d4a22bf9efab049f5c.png)如下:

img


1.5 实验

(1)表I与MS-COCO VAL2017上不同特征金字塔网络的比较。

img

(2)表II与MS-COCO测试开发中不同特征金字塔网络的比较。

img

(3)表III 与其他两级目标探测器的比较。

img

(4)表IV AFPN对YOLOV5的贡献。

img

(5)表V 消融实验。

img


二、更换AFPN的方法

第①步:在common.py中添加AFPN模块
 
class Upsample(nn.Module):
    """Applies convolution followed by upsampling."""
# ---1.渐进架构部分(融合前的准备)--- #
    def __init__(self, c1, c2, scale_factor=2):
        super().__init__()
        # self.cv1 = Conv(c1, c2, 1)
        # self.upsample = nn.Upsample(scale_factor=scale_factor, mode='nearest')  # or model='bilinear' non-deterministic
        if scale_factor == 2:
            self.cv1 = nn.ConvTranspose2d(c1, c2, 2, 2, 0, bias=True)  # 如果下采样率为2,就用Stride为2的2×2卷积来实现2次下采样
        elif scale_factor == 4:
            self.cv1 = nn.ConvTranspose2d(c1, c2, 4, 4, 0, bias=True)  # 如果下采样率为4,就用Stride为4的4×4卷积来实现4次下采样
 
    def forward(self, x):
        # return self.upsample(self.cv1(x))
        return self.cv1(x)
 
# ---2.自适应空间融合(ASFF)--- #
class ASFF2(nn.Module):
    """ASFF2 module for YOLO AFPN head https://arxiv.org/abs/2306.15988"""
 
    def __init__(self, c1, c2, level=0):
        super().__init__()
        c1_l, c1_h = c1[0], c1[1]
        self.level = level
        self.dim = c1_l, c1_h
        self.inter_dim = self.dim[self.level]
        compress_c = 8
       
#如果是第0层
        if level == 0:
# self.stride_level_1调整level-1出来的特征图,通道调整为和level-0出来的特征图一样大小
            self.stride_level_1 = Upsample(c1_h, self.inter_dim)
#如果是第1层
        if level == 1:
# self.stride_level_0通道调整为和level-1出来的特征图一样大小
            self.stride_level_0 = Conv(c1_l, self.inter_dim, 2, 2, 0)  # stride=2 下采样为2倍
 
 
# 两个卷积为了学习权重
        self.weight_level_0 = Conv(self.inter_dim, compress_c, 1, 1)
        self.weight_level_1 = Conv(self.inter_dim, compress_c, 1, 1)
# 用于调整拼接后的两个权重的通道
        self.weights_levels = nn.Conv2d(compress_c * 2, 2, kernel_size=1, stride=1, padding=0)
        self.conv = Conv(self.inter_dim, self.inter_dim, 3, 1)
 
    def forward(self, x):
        x_level_0, x_level_1 = x[0], x[1]
 
# 如果在第0层
# level-0出来的特征图保持不变
# 调整level-1的特征图,使得其channel、width、height与level-0一致
        if self.level == 0:
            level_0_resized = x_level_0
            level_1_resized = self.stride_level_1(x_level_1)
# 如果在第1层,同上
        elif self.level == 1:
            level_0_resized = self.stride_level_0(x_level_0)
            level_1_resized = x_level_1
 
# 将N*C*H*W的level-0特征图卷积得到权重,权重level_0_weight_v:N*256*H*W
        level_0_weight_v = self.weight_level_0(level_0_resized)
        level_1_weight_v = self.weight_level_1(level_1_resized)
 
# 将各个权重矩阵按照通道拼接
# levels_weight_v:N*3C*H*W
        levels_weight_v = torch.cat((level_0_weight_v, level_1_weight_v), 1)
 
# 将拼接后的矩阵调整,每个通道对应着不同的level_0_resized,level_1_resized的权重
        levels_weight = self.weights_levels(levels_weight_v)
 
# 在通道维度,对权重做归一化,也就是对于二通道tmp:tmp[0][0]+tmp[1][0]=1
        levels_weight = F.softmax(levels_weight, dim=1)
 
# 将levels_weight各个通道分别乘level_0_resized level_1_resized 
# 点乘用到了广播机制
        fused_out_reduced = level_0_resized * levels_weight[:, 0:1] + level_1_resized * levels_weight[:, 1:2]
        return self.conv(fused_out_reduced)
 
# ASFF3的运算流程同上
class ASFF3(nn.Module):
    """ASFF3 module for YOLO AFPN head https://arxiv.org/abs/2306.15988"""
 
    def __init__(self, c1, c2, level=0):
        super().__init__()
        c1_l, c1_m, c1_h = c1[0], c1[1], c1[2]
        self.level = level
        self.dim = c1_l, c1_m, c1_h
        self.inter_dim = self.dim[self.level]
        compress_c = 8
 
        if level == 0:
            self.stride_level_1 = Upsample(c1_m, self.inter_dim)
            self.stride_level_2 = Upsample(c1_h, self.inter_dim, scale_factor=4)
 
        if level == 1:
            self.stride_level_0 = Conv(c1_l, self.inter_dim, 2, 2, 0)  # downsample 2x
            self.stride_level_2 = Upsample(c1_h, self.inter_dim)
 
        if level == 2:
            self.stride_level_0 = Conv(c1_l, self.inter_dim, 4, 4, 0)  # downsample 4x
            self.stride_level_1 = Conv(c1_m, self.inter_dim, 2, 2, 0)  # downsample 2x
 
        self.weight_level_0 = Conv(self.inter_dim, compress_c, 1, 1)
        self.weight_level_1 = Conv(self.inter_dim, compress_c, 1, 1)
        self.weight_level_2 = Conv(self.inter_dim, compress_c, 1, 1)
 
        self.weights_levels = nn.Conv2d(compress_c * 3, 3, kernel_size=1, stride=1, padding=0)
        self.conv = Conv(self.inter_dim, self.inter_dim, 3, 1)
 
    def forward(self, x):
        x_level_0, x_level_1, x_level_2 = x[0], x[1], x[2]
 
        if self.level == 0:
            level_0_resized = x_level_0
            level_1_resized = self.stride_level_1(x_level_1)
            level_2_resized = self.stride_level_2(x_level_2)
 
        elif self.level == 1:
            level_0_resized = self.stride_level_0(x_level_0)
            level_1_resized = x_level_1
            level_2_resized = self.stride_level_2(x_level_2)
 
        elif self.level == 2:
            level_0_resized = self.stride_level_0(x_level_0)
            level_1_resized = self.stride_level_1(x_level_1)
            level_2_resized = x_level_2
 
        level_0_weight_v = self.weight_level_0(level_0_resized)
        level_1_weight_v = self.weight_level_1(level_1_resized)
        level_2_weight_v = self.weight_level_2(level_2_resized)
 
        levels_weight_v = torch.cat((level_0_weight_v, level_1_weight_v, level_2_weight_v), 1)
        w = self.weights_levels(levels_weight_v)
        w = F.softmax(w, dim=1)
 
        fused_out_reduced = level_0_resized * w[:, :1] + level_1_resized * w[:, 1:2] + level_2_resized * w[:, 2:]
        return self.conv(fused_out_reduced)

如下图所示:

img


第②步:修改yolo.py文件

再来修改yolo.py,在parse_model函数中找到 elif m is Concat: 语句,在其后面加上下面代码:

        elif m is ASFF2:
            c1, c2 = [ch[f[0]], ch[f[1]]], args[0]
            c2 = make_divisible(c2 * gw, 8)
            args = [c1, c2, *args[1:]]
        elif m is ASFF3:
            c1, c2 = [ch[f[0]], ch[f[1]], ch[f[2]]], args[0]
            c2 = make_divisible(c2 * gw, 8)
            args = [c1, c2, *args[1:]]

如下图所示:

img


第③步:创建自定义的yaml文件

yaml文件配置完整代码如下:

# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
 
# Parameters
nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # 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.1 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, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]], # 3-P3/8
   [-1, 6, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 6, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, SPPF, [1024, 5]],
  ]  # 9
 
# YOLOv5 v6.1 head
head:
  [[4, 1, Conv, [128, 1, 1]], # 10 downsample backbone P3
   [6, 1, Conv, [256, 1, 1]], # 11 downsample backbone P4
 
   [[10, 11], 1, ASFF2, [128, 0]], # 12
   [[10, 11], 1, ASFF2, [256, 1]], # 13
 
   [-2, 1, C3, [128, False]], # 14
   [-2, 1, C3, [256, False]], # 15
 
   [9, 1, Conv, [512, 1, 1]], # 16 downsample backbone P5
 
   [[14, 15, 16], 1, ASFF3, [128, 0]], # 17
   [[14, 15, 16], 1, ASFF3, [256, 1]], # 18
   [[14, 15, 16], 1, ASFF3, [512, 2]], # 19
 
   [17, 1, C3, [256, False]],  # 20 (P3/8-small)
   [18, 1, C3, [512, False]],  # 21 (P4/16-medium)
   [19, 1, C3, [1024, False]],  # 22 (P5/32-large)
  [[20, 21, 22], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]

OK!完成!


PS:

(1)本来没有准备放这个改进方法,一个是这个channel我研究了挺久,一直报错,后来直接借鉴大佬的了┗( T﹏T )┛。

再一个更重要的是,在我的数据集掉点了!┗|`O′|┛ 嗷~~w(Д)w。。。我的评价是不如BiFPN。同样,也有很多友友反应掉点问题。它更大的优点可能就是参数少叭~后来看到大家讨论,这个改进可能更适合小数据集(如果合适可以试试,我没有验证。)

(2)昨晚我一个突发奇想,我做了如下改进:

img

就是在这三个位置加了EMA注意力。

EMA

在我的数据集上比CA+BiFPN组合涨了0.2,并且参数量更小,很nice!


BiFusion

详细解读

在这里插入图片描述
YOLO 社区自前两次发布以来一直情绪高涨!随着中国农历新年2023兔年的到来,美团对YOLOv6进行了许多新的网络架构和训练方案改进。此版本标识为 YOLOv6 v3.0。对于性能,YOLOv6-N在COCO数据集上的AP为37.5%,通过NVIDIA Tesla T4 GPU测试的吞吐量为1187 FPS。YOLOv6-S以484 FPS的速度得到了超过45.0%的AP,超过了相同规模的其他主流检测器(YOLOv5-S、YOLOv8-S、YOLOX-S和PPYOLOE-S)。YOLOv6-M/L在相似的推理速度下也比其他检测器实现了更好的精度性能(分别为50.0%/52.8%)。此外,凭借扩展的Backbone和Neck设计,YOLOv6-L6实现了最先进的实时精度。


在这里插入图片描述

YOLOv6 3.0 结构


YOLOv6贡献

YOLOv6的新功能总结如下:

  1. 我们用双向级联(BiC)模块更新检测器的颈部,以提供更准确的定位信号。SPPF被简化为SimCSPSPF块,它带来了性能提高,速度下降可忽略不计。(SimCSPSPF和我之前提出的SPPFCSPC结构相同)
  2. 我们提出了一种锚辅助训练(AAT)策略,以在不影响推理效率的情况下,享受基于锚和无锚范例的优点。(这一点也比较有意思,v7作者在我的issue中是这么回答的issue)
  3. 我们深化 YOLOv6,使其在主干和颈部具有另一个阶段,这增强了它在 COCO 数据集上以高分辨率输入实现新的最先进性能。
  4. 我们采用了一种新的自蒸馏策略来提高 YOLOv6 小模型的性能,其中 DFL 的较重分支在训练期间被用作增强的辅助回归分支,并在推断时被移除,以避免显著的速度下降。

BiFusion Neck 融合的原理

BiFusion Neck 融合的原理其实并不是很难理解,有些博主解析的过于复杂,其实无非就是4步:

  1. 同尺度特征图使用 1×1 卷积降维;
  2. 大尺度特征图先使用 1×1 卷积降维,再使用 3×3 步长为 2 的卷积进行下采样
  3. 小尺度特征图 使用 2×2 的转置卷积进行上采样
  4. 然后将这三部分得到的特征图 Conca 拼接起来,使用 1×1 卷积再次降维;

BiFusion Neck结构图

我这里把YOLOv6的颈部网络完美移植到了YOLOv5


请添加图片描述

完整无水印高清结构图请关注博主本人公众号 深度之灵 回复 bif 领取;


参数量与计算量
模型参数量(parameters)计算量(GFLOPs)
yolov5s722588516.5
yolov5s BiFusion Neck739756517.5

很巧妙的改进~


代码修改方式:
yolo.py中加入nn.ConvTranspose2d

在这里插入图片描述
在这里插入图片描述


YOLOv5 BiFusion Neck 配置文件
# yolov5s-Bifusion.yaml
# Parameters
nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # 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]
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 6, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, SPPF, [1024, 5]],  # 9
  ]


# YOLOv5 v6.0 head
head:
  [[-1, 1, Conv, [512, 1, 1]], #10
   [-1, 1, nn.ConvTranspose2d, [512, 2, 2, 0, 0, 512]], #11
   [ 6, 1, Conv, [256, 1,1]],  #12
   [ 4, 1, Conv, [128, 1,1]],  #13
   [-1, 1, Conv, [128, 3,2]],  #14
   [[11, 12, 14], 1, Concat, [1]],  # cat backbone P4  #15

   [-1, 1, Conv, [512, 1,1]],    # 16
   [-1, 3, C3  , [512, False]],  # 17
   [-1, 1, Conv, [256, 1, 1]],   # 18
   [-1, 1, nn.ConvTranspose2d, [256, 2, 2, 0, 0, 256]], #19
   [ 4, 1, Conv, [ 128, 1,1 ] ],  #20
   [ 2, 1, Conv, [ 64, 1,1 ] ],   #21
   [-1, 1, Conv, [ 64, 3,2 ] ],   #22
   [[19, 20, 22], 1, Concat, [1]],  #23  cat backbone P3
   [-1, 1, Conv, [256, 1,1]],    #24
   [-1, 3, C3  , [256, False]],  #25 out

   [-1, 1, Conv, [256, 3, 2]],   #26
   [[-1, 18], 1, Concat, [1]],   #27  cat head P4
   [-1, 3, C3  , [512, False]],  #28 out (P4/16-medium)

   [-1, 1, Conv, [512, 3, 2]],   #29
   [[-1, 10], 1, Concat, [1]],   #30 cat head P5
   [-1, 3, C3  , [1024, False]], # 31 (P5/32-large)

   [[25, 28, 31], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]

[点击查看专栏全部文章]


已下架不支持订阅

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小酒馆燃着灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值