在Yolov5中,使用BiFPN

BiFPN

在common.py中加入bifpn类

BiFPN(Bi-directional Feature Pyramid Network)是一种用于目标检测任务的特征金字塔网络结构。

在传统的目标检测算法中,通常使用特征金字塔网络(Feature Pyramid Network,简称FPN)来提取不同尺度的特征。FPN通过在网络中添加额外的侧分支和上采样操作来获得多尺度的特征图。然而,在FPN中,上采样和下采样操作之间的信息流只是单向的。

BiFPN通过在不同尺度的特征金字塔网络中引入双向连接,实现了跨层级的信息交流和融合。具体来说,BiFPN包含了以下几个步骤:

  1. 金字塔上采样(Upsampling):从较低层级的特征图开始,通过上采样操作生成具有更高分辨率的特征图。

  2. 金字塔下采样(Downsampling):从较高层级的特征图开始,通过下采样操作生成具有较低分辨率但更丰富语义信息的特征图。

  3. 双向连接(Bi-directional Connection):将上采样和下采样的特征图进行连接,从而实现双向的信息流动。

  4. 特征融合(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.siluSiLU是一种激活函数,它可以将输入的值进行缩放和平移,并保持非线性的特性。

回忆一下nn.moudle

在PyTorch中,nn.Module是一个基类,用于定义神经网络模型。通过继承nn.Module类,我们可以定义自己的神经网络模型,包括网络结构的定义和前向传播的实现。

nn.Module类的主要功能包括:

  1. 模型的初始化:在模型的初始化函数__init__中,可以定义模型的各个层和可学习的参数。这些层可以是卷积层、池化层、全连接层等,以及一些非线性激活函数。通过在__init__中定义这些层和参数,可以让PyTorch自动追踪模型的结构,并在模型的训练过程中自动更新这些参数的梯度。

  2. 前向传播函数的实现:在定义了模型的层和参数之后,需要实现前向传播函数forward。该函数描述了模型接收输入数据,并通过网络结构计算输出结果的过程。在这个函数中,可以使用模型定义的层和参数进行计算,并返回结果。

  3. 模型参数的管理:nn.Module类提供了一些方法来管理模型的参数,如parameters方法可以获取模型中的所有可学习参数列表,named_parameters方法可以获取参数列表以及参数的名称,to方法可以将模型参数移动到指定的设备(如GPU)上。

  4. 模型的保存和加载:通过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中,其作用是解析模型定义字典,并构建模型的层和参数。

以下是对代码的详细解释:

  1. anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']:从模型定义字典d中获取锚框、类别数、深度倍数和宽度倍数。

  2. na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors:计算锚框的数量。如果锚框是一个列表,就获取一个锚框的数量,否则直接使用提供的锚框数量。

  3. no = na * (nc + 5):计算输出的通道数,公式为锚框数量乘以(类别数加5)。

  4. layers, save, c2 = [], [], ch[-1]:定义用于存储模型层的列表layers,用于存储需要保存的层索引的列表save,以及当前层的输出通道数c2

  5. for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']):遍历模型定义字典中的backbonehead列表中的每个元素,其中f表示来源索引,n表示重复次数,m表示模块类型,args表示参数列表。

  6. m = eval(m) if isinstance(m, str) else m:将模块类型字符串转为模块类型,如果m是字符串类型。

  7. 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]

  8. 根据模型配置中的层参数构建模型:

  9. 当前层为卷积操作时,根据输入通道数和输出通道数调整输出通道数,确保输出通道数不为no(输出通道数)。
  10. 当前层为nn.BatchNorm2d时,将输入通道数作为参数。
  11. 当前层为Concat时,计算各输入通道的总和作为输出通道数。
  12. 当前层为BiFPN_Add2BiFPN_Add3时,将输入通道数的最大值作为输出通道数。
  13. 当前层为Detect时,将各输入通道数作为参数。如果参数args[1]是一个整数,则表示锚点的数量,将其转换为对应的锚点列表。
  14. 当前层为Contract时,根据输入通道数和args[0]的平方计算输出通道数。
  15. 当前层为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的倍数。

对于特定的模块类型(如BottleneckCSPC3C3TRC3Ghost等),在参数列表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_Add2BiFPN_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表示通道数,HW表示高度和宽度,进行Concat操作后,拼接后的张量形状将变为(batch_size, C1 + C2, H, W),其中C1C2分别是两个输入张量的通道数。

在神经网络中,Concat操作通常用于特征融合。例如,在图像分类任务中,可以将不同层的特征图进行拼接,从而将全局和局部特征结合起来。在目标检测任务中,可以将不同尺度的特征图进行拼接,用于检测不同大小的目标。

总而言之,Concat模块是一种用于将多个张量在某个维度上进行拼接的操作,常用于将不同层或通道的特征图进行融合。

加入bifpn

原版yolov5s


结论还是原配的好

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
YOLOv5 添加 BiFPN(Bi-directional Feature Pyramid Network)可以提高目标检测的性能。BiFPN 是在 FPN(Feature Pyramid Network)的基础上进行了改进,它增加了横向和纵向的连接来增强特征金字塔的信息传递和融合能力。 要在 YOLOv5 添加 BiFPN,可以按照以下步骤进行: 1. 在 YOLOv5 的网络结构,找到特征提取网络部分。一般来说,YOLOv5 使用的是 CSPDarknet53 或 CSPDarknetLite 作为特征提取网络。 2. 在特征提取网络的最后一层之后,添加 BiFPN 模块。BiFPN 模块由多个 BiFPN 层组成,每个 BiFPN 层由两个阶段构成:上采样和下采样。 3. 上采样阶段:将较低层级的特征金字塔通过上采样操作增加分辨率,使其与较高层级的特征金字塔具有相同的尺寸。 4. 下采样阶段:将较高层级的特征金字塔通过下采样操作减小分辨率,使其与较低层级的特征金字塔具有相同的尺寸。 5. 在上采样和下采样之间,使用融合操作将不同层级的特征金字塔进行融合。常见的融合操作有加法、乘法或者是使用卷积操作。 6. 重复上述步骤,直到达到所需的特征金字塔的层数。 添加 BiFPN 后,可以将其连接到 YOLOv5 的检测头部,然后进行目标检测的训练和推理。 需要注意的是,具体的实现细节可能因 YOLOv5 的版本和代码库而有所不同。因此,在实际操作,你可能需要参考 YOLOv5 的代码库和相关文档来了解如何添加 BiFPN

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值