1、前言
BiFPN即“双向特征金字塔网络”**有效的双向交叉尺度连接和加权特征融合。**它扩展了特征金字塔网络(FPN),通过在金字塔级别之间引入双向连接,使信息能够在网络中同时进行自底向上和自顶向下的流动。
以下是BiFPN的工作原理概述:
- 特征金字塔生成:最初,网络通过从骨干网络(通常是ResNet等卷积神经网络)的多个层中提取特征来生成特征金字塔。
- 双向连接:与传统FPN不同,BiFPN在特征金字塔相邻级别之间引入了双向连接。这意味着信息可以从更高级别的特征流向更低级别的特征(自顶向下路径),也可以从更低级别的特征流向更高级别的特征(自底向上路径)。
- 特征整合:双向连接允许在两个方向上整合来自特征金字塔不同级别的信息。这种整合有助于有效地捕获多尺度特征。
- 加权特征融合:BiFPN采用加权特征融合机制,将不同级别的特征进行组合。融合的权重在训练过程中学习,确保了最佳的特征整合。
BiFPN中的双向连接有助于更好地在不同尺度上捕获特征表示,提高了网络处理不同尺寸和复杂度对象的能力。这在目标检测任务中尤为重要,因为图像中的对象大小可能差异显著。
BiFPN是在EfficientDet: Scalable and Efficient Object Detection这篇论文中提出来的,传统的自上而下的 FPN 本质上受到单向信息流的限制。 为了解决这个问题,PANet 添加了一个额外的自下而上的路径聚合网络,如下图(b)所示。 NAS-FPN采用神经架构搜索来搜索更好的跨尺度特征网络拓扑,但在搜索过程中需要数千个GPU小时,并且发现的网络不规则并且难以解释或修改,如下图(c)所示
通过研究这三个网络的性能和效率,我们观察到 PANet 比 FPN 和 NAS-FPN 实现了更好的精度,但代价是更多的参数和计算量加粗样式。为了提高模型效率,论文作者提出了几种跨尺度连接的优化,也就是BiFPN网络,如图(d):
- 第一,删除那些只有一个输入边的节点。 我们的直觉很简单:如果一个节点只有一个输入边,没有特征融合,那么它对旨在融合不同特征的特征网络的贡献就会较小,这导致了一个简化的双向网络;
- 第二,如果原始输入和输出节点处于同一水平,我们在原始输入和输出节点之间增加一条额外的边,以便在不增加太多成本的情况下融合更多的特征;
- 第三,与PANet只有一条自顶向下和一条自底向上的路径不同,我们将每条双向(自顶向下和自底向上)路径视为一个特征网络层,并多次重复同一层,以实现更高级的特征融合。(在yolo当中PANet都是只有一条的,但是在EfficientDet论文中,作者将BiFPN这个特征融合层设置多次重复,可以融合更加高级的特征)
- 第四,使用了加权特征融合,像P3~P7这几个stage特征融合的时候设置的有不同权重参数,这样可以保证网络集中在目标特征上面,同时这个权重参数也是通过训练得到的。
通过不同特征网络的比较实验数据对比可以看出,论文中的的加权BiFPN以更少的参数和FLOPs达到了最好的精度。
2、BiFPN代码实现
2.1、common.py
在models/common.py
的加入BiFPN
类
# 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]))
2.2、yolo.py
在models/yolo.py
的parse_model函数
,elif m is nn.BatchNorm2d: 语句 ,添加BiFPN_Add2, BiFPN_Add3
模块
# 添加bifpn_add结构
elif m in [BiFPN_Add2, BiFPN_Add3]:
c2 = max([ch[x] for x in f])
2.3、修改train.py文件
(1)在train.py文件
,首先引入我们刚刚的BiFPN_Add2模块还有BiFPN_Add3模块。
from models.common import BiFPN_Add2
from models.common import BiFPN_Add3
(2)找到Optimizer优化器设置这一部分,添加下面这段代码;
g0, g1, g2 = [], [], [] # optimizer parameter groups
for v in model.modules():
if hasattr(v, 'bias') and isinstance(v.bias, nn.Parameter): # bias
g2.append(v.bias)
if isinstance(v, nn.BatchNorm2d): # weight (no decay)
g0.append(v.weight)
elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter): # weight (with decay)
g1.append(v.weight)
# 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)
2.4、自定义yolov5s_BiFPN.yaml文件
将head配置文件中的所有concat连接都替换成BiFPN_Add2模块还有BiFPN_Add3模块,配置文件如下:
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# Parameters
nc: 6 # 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)
]
3、目标检测系列文章
- YOLOv5s网络模型讲解(一看就会)
- 生活垃圾数据集(YOLO版)
- YOLOv5如何训练自己的数据集
- 双向控制舵机(树莓派版)
- 树莓派部署YOLOv5目标检测(详细篇)
- YOLO_Tracking 实践 (环境搭建 & 案例测试)
- 目标检测:数据集划分 & XML数据集转YOLO标签
- DeepSort行人车辆识别系统(实现目标检测+跟踪+统计)
- YOLOv5参数大全(parse_opt篇)
- YOLOv5改进(一)-- 轻量化YOLOv5s模型
- YOLOv5改进(二)-- 目标检测优化点(添加小目标头检测)
- YOLOv5改进(三)-- 引进Focaler-IoU损失函数
- YOLOv5改进(四)–轻量化模型ShuffleNetv2
- YOLOv5改进(五)-- 轻量化模型MobileNetv3
- YOLOv5改进(六)–引入YOLOv8中C2F模块
- YOLOv5改进(七)–改进损失函数EIoU、Alpha-IoU、SIoU、Focal-EIOU
- YOLOv5改进(八)–引入Soft-NMS非极大值抑制