BiFPN
在common.py中加入bifpn类
BiFPN(Bi-directional Feature Pyramid Network)是一种用于目标检测任务的特征金字塔网络结构。
在传统的目标检测算法中,通常使用特征金字塔网络(Feature Pyramid Network,简称FPN)来提取不同尺度的特征。FPN通过在网络中添加额外的侧分支和上采样操作来获得多尺度的特征图。然而,在FPN中,上采样和下采样操作之间的信息流只是单向的。
BiFPN通过在不同尺度的特征金字塔网络中引入双向连接,实现了跨层级的信息交流和融合。具体来说,BiFPN包含了以下几个步骤:
-
金字塔上采样(Upsampling):从较低层级的特征图开始,通过上采样操作生成具有更高分辨率的特征图。
-
金字塔下采样(Downsampling):从较高层级的特征图开始,通过下采样操作生成具有较低分辨率但更丰富语义信息的特征图。
-
双向连接(Bi-directional Connection):将上采样和下采样的特征图进行连接,从而实现双向的信息流动。
-
特征融合(Feature Fusion):通过融合相邻层级的特征图,使得不同尺度的特征能够互相补充,提高目标检测的精度。
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()
通过引入双向连接和特征融合,BiFPN能够更好地提取多尺度的特征,从而在目标检测任务中能够更好地处理大小不同的目标,并提高目标检测的性能和精度。
这段代码中的BiFPN_Add2
类继承自nn.Module
类。nn.Module
是PyTorch中定义神经网络模型的基类,所有自定义的模型都应该继承自该类。
在BiFPN_Add2
类的定义中,super(BiFPN_Add2, self).__init__()
表示调用父类nn.Module
的构造函数,确保正确地初始化模型。
然后在BiFPN_Add2
类的初始化函数__init__
中,首先定义了可学习的参数self.w
,这是使用nn.Parameter
将一个Tensor
转换成可以训练的parameter
类型,并设置为可以进行梯度更新requires_grad=True
。这样,在训练过程中,这个参数将会被优化器自动更新。
接下来,self.epsilon
定义了一个小的常数,用于数值稳定性。
然后,定义了一个卷积层self.conv
,用于对输入特征进行卷积操作。nn.Conv2d
是一个卷积层,kernel_size
表示卷积核大小,stride
表示卷积步长,padding
表示零填充的大小。
最后,定义了一个nn.SiLU
激活函数self.silu
,SiLU
是一种激活函数,它可以将输入的值进行缩放和平移,并保持非线性的特性。
回忆一下nn.moudle
在PyTorch中,nn.Module
是一个基类,用于定义神经网络模型。通过继承nn.Module
类,我们可以定义自己的神经网络模型,包括网络结构的定义和前向传播的实现。
nn.Module
类的主要功能包括:
-
模型的初始化:在模型的初始化函数
__init__
中,可以定义模型的各个层和可学习的参数。这些层可以是卷积层、池化层、全连接层等,以及一些非线性激活函数。通过在__init__
中定义这些层和参数,可以让PyTorch自动追踪模型的结构,并在模型的训练过程中自动更新这些参数的梯度。 -
前向传播函数的实现:在定义了模型的层和参数之后,需要实现前向传播函数
forward
。该函数描述了模型接收输入数据,并通过网络结构计算输出结果的过程。在这个函数中,可以使用模型定义的层和参数进行计算,并返回结果。 -
模型参数的管理:
nn.Module
类提供了一些方法来管理模型的参数,如parameters
方法可以获取模型中的所有可学习参数列表,named_parameters
方法可以获取参数列表以及参数的名称,to
方法可以将模型参数移动到指定的设备(如GPU)上。 -
模型的保存和加载:通过
state_dict
方法可以获取模型的当前状态,即包含了模型的参数和缓存的字典,可以将这个字典保存到文件中,以便后续恢复模型的状态。
在具体的代码中,我们通常通过继承nn.Module
类来定义自己的神经网络模型,然后重写__init__
函数来初始化模型的层和参数,并实现forward
函数来定义模型的前向传播过程。这样就能够方便地使用PyTorch提供的各种功能和工具来训练、评估和保存模型。
在yolo.py中添加bifpn_add结构
# 添加bifpn_add结构 elif m in [BiFPN_Add2, BiFPN_Add3]: c2 = max([ch[x] for x in f])
def parse_model(d, ch): # model_dict, input_channels(3)
LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10} {'module':<40}{'arguments':<30}")
anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']
na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors
no = na * (nc + 5) # number of outputs = anchors * (classes + 5)
layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out
for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args
m = eval(m) if isinstance(m, str) else m # eval strings
for j, a in enumerate(args):
try:
args[j] = eval(a) if isinstance(a, str) else a # eval strings
except NameError:
pass
这段代码位于Yolo的模型构建函数parse_model
中,其作用是解析模型定义字典,并构建模型的层和参数。
以下是对代码的详细解释:
-
anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']
:从模型定义字典d
中获取锚框、类别数、深度倍数和宽度倍数。 -
na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors
:计算锚框的数量。如果锚框是一个列表,就获取一个锚框的数量,否则直接使用提供的锚框数量。 -
no = na * (nc + 5)
:计算输出的通道数,公式为锚框数量乘以(类别数加5)。 -
layers, save, c2 = [], [], ch[-1]
:定义用于存储模型层的列表layers
,用于存储需要保存的层索引的列表save
,以及当前层的输出通道数c2
。 -
for i, (f, n, m, args) in enumerate(d['backbone'] + d['head'])
:遍历模型定义字典中的backbone
和head
列表中的每个元素,其中f
表示来源索引,n
表示重复次数,m
表示模块类型,args
表示参数列表。 -
m = eval(m) if isinstance(m, str) else m
:将模块类型字符串转为模块类型,如果m
是字符串类型。 -
for j, a in enumerate(args)
:遍历参数列表,将字符串类型的参数转换为实际的参数。
if m in [Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv, BottleneckCSP, C3, C3TR, C3SPP, C3Ghost]: c1, c2 = ch[f], args[0] if c2 != no: # if not output c2 = make_divisible(c2 * gw, 8) args = [c1, c2, *args[1:]] if m in [BottleneckCSP, C3, C3TR, C3Ghost]: args.insert(2, n) # number of repeats n = 1 elif m is nn.BatchNorm2d: args = [ch[f]] elif m is Concat: c2 = sum(ch[x] for x in f) # 添加bifpn_add结构 elif m in [BiFPN_Add2, BiFPN_Add3]: c2 = max([ch[x] for x in f]) elif m is Detect: args.append([ch[x] for x in f]) if isinstance(args[1], int): # number of anchors args[1] = [list(range(args[1] * 2))] * len(f) elif m is Contract: c2 = ch[f] * args[0] ** 2 elif m is Expand: c2 = ch[f] // args[0] ** 2 else: c2 = ch[f]
-
根据模型配置中的层参数构建模型:
- 当前层为卷积操作时,根据输入通道数和输出通道数调整输出通道数,确保输出通道数不为no(输出通道数)。
- 当前层为
nn.BatchNorm2d
时,将输入通道数作为参数。 - 当前层为
Concat
时,计算各输入通道的总和作为输出通道数。 - 当前层为
BiFPN_Add2
或BiFPN_Add3
时,将输入通道数的最大值作为输出通道数。 - 当前层为
Detect
时,将各输入通道数作为参数。如果参数args[1]
是一个整数,则表示锚点的数量,将其转换为对应的锚点列表。 - 当前层为
Contract
时,根据输入通道数和args[0]
的平方计算输出通道数。 - 当前层为
Expand
时,根据输入通道数和args[0]
的平方根计算输出通道数。
这段代码是对模型的结构进行解析和构建的过程。
首先,通过迭代d['backbone'] + d['head']
,遍历每个模块的信息,包括输入来源f
、模块的重复次数n
、模块的类型m
和参数args
。
然后,通过eval()
函数将类型为字符串的模块类型转换为实际的模块类。如果参数也是字符串类型,则将其转换为相应的类型。这样做是为了通过字符串来灵活地指定模块类型和参数。
接下来,根据深度因子gd
调整重复次数n
,如果n
大于1,则将其乘以gd
进行调整。如果n
等于1,则保持不变。
在对各个模块进行处理时,根据模块类型的不同,对输入通道数c1
和输出通道数c2
进行设置。如果模块类型是需要调整输出通道数的类型,则将c2
设置为no
(输出通道数,即输出特征图的通道数),并通过make_divisible
函数将其乘以gw
进行调整,保证输出通道数是8的倍数。
对于特定的模块类型(如BottleneckCSP
、C3
、C3TR
、C3Ghost
等),在参数列表args
中插入重复次数n
作为第二个参数,并将重复次数n
设置为1。这样可以在模块的参数中动态地插入重复次数。
通过以上的处理过程,可以解析并构建出符合模型定义的神经网络结构。
修改train.py
nbs = 64 # nominal batch size
accumulate = max(round(nbs / batch_size), 1) # accumulate loss before optimizing
hyp['weight_decay'] *= batch_size * accumulate / nbs # scale weight_decay
LOGGER.info(f"Scaled weight_decay = {hyp['weight_decay']}")
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)
首先,根据给定的批次大小和实际的批次大小之间的比例,计算出每个批次之前累积的损失数量。这样做是为了在优化之前累积损失,以便更好地优化模型。例如,如果实际批次大小为32,而给定的批次大小为64,则累积损失为2(64/32)。
接下来,通过将超参数weight_decay
缩放为实际批次大小和累积损失数量之间的比例,来调整权重衰减。这是为了根据实际的批次大小和累积损失数量进行适当的缩放,以便更好地控制权重衰减的影响。
然后,定义了三个优化器参数组:g0,g1和g2。其中,g0用于不进行权重衰减的权重(例如,批量归一化层的权重),g1用于进行权重衰减的权重(例如,卷积层的权重),g2用于偏置项(例如,卷积层的偏置项)。
最后,对模型的每个模块进行遍历,根据其属性和类型将其相应的参数添加到相应的优化器参数组中。具体来说,我们将具有偏置项且偏置项是可学习参数的模块的偏置项添加到g2中,将批量归一化层的权重(不进行权重衰减)添加到g0中,将具有权重且权重是可学习参数的模块的权重(进行权重衰减)添加到g1中。此外,如果遇到BiFPN_Add2
或BiFPN_Add3
类型的模块,并且该模块具有名为w
的可学习参数,则将其添加到g1中。
这样做可以将模型的不同类型的参数分组,并将它们分别添加到相应的优化器参数组中,以便在训练过程中使用不同的优化策略对它们进行处理。
将`Concat`全部换成`BiFPN_Add`
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)
]
模型深度倍数(depth_multiple),层通道倍数(width_multiple)
定义了YOLOv5的主干网络(backbone)。这里使用了一系列Conv、C3和SPPF模块来构建主干网络。每个列表项表示一个模块的配置,包括起始索引、模块数量、模块类型和参数。例如,[-1, 1, Conv, [64, 6, 2, 2]]
表示从索引为-1的层开始,使用1个Conv模块,参数为[64, 6, 2, 2]。
接下来,定义了YOLOv5的BiFPN头部(head)。这里使用了一系列Conv、Upsample、BiFPN_Add2、C3和Detect模块来构建头部网络。与主干网络类似,每个列表项表示一个模块的配置。例如,[[17, 20, 23], 1, Detect, [nc, anchors]]
表示使用Detect模块,参数为[nc, anchors],并将索引17、20和23的输出作为输入。
需要注意的是,将所有的Concat
模块都更换为了BiFPN_Add
模块,
Concat
是指将两个或多个张量在某个维度上拼接(拼接通常是在维度上进行)的操作。在深度学习中,Concat
通常用于融合不同层或通道的特征图。
具体来说,Concat
操作将多个输入张量按照指定的维度进行拼接。例如,如果有两个形状为(batch_size, C, H, W)
的张量,其中C
表示通道数,H
和W
表示高度和宽度,进行Concat
操作后,拼接后的张量形状将变为(batch_size, C1 + C2, H, W)
,其中C1
和C2
分别是两个输入张量的通道数。
在神经网络中,Concat
操作通常用于特征融合。例如,在图像分类任务中,可以将不同层的特征图进行拼接,从而将全局和局部特征结合起来。在目标检测任务中,可以将不同尺度的特征图进行拼接,用于检测不同大小的目标。
总而言之,Concat
模块是一种用于将多个张量在某个维度上进行拼接的操作,常用于将不同层或通道的特征图进行融合。
加入bifpn
原版yolov5s
结论还是原配的好