学习资源站

YOLOv11改进-Neck篇-SDI结合BiFPN全新的特征融合网络(全网独家创新)

一、本文介绍

本文给大家带来的最新改进机制是利用多层次 特征融合 模块 (SDI) 配上经典的加权 双向特征金字塔网络 Bi-FPN 形成一种全新的Neck网络结构,从而达到二次创新的效果,其中 (SDI) 模块的主要思想是通过整合编码器生成的层级特征图来增强图像中的语义信息和细节信息。 Bi-FPN 无需过多介绍其作为经典的特征金字塔网络其效果一直以来都是非常的不错,其中 Bi-FPN 的劣势主要是时间过于久远,但是 SDI 是一种全新的机制,所以我们将其融合在一起这就是一种全新的Neck,从而达到一个二次创新的形式,我们在书写论文的时候论文的结构图也比较好画,同时本文的 SDI 模块在分割领域涨点高效, 融合起来非常适用于目标分割

欢迎大家订阅我的专栏一起学习YOLO!

一、本文介绍

二、原理介绍

三、BiFPN代码

四、手把手教你修改BiFPN

4.1 修改一

4.2 修改二

4.3 修改三

五、SDI的核心代码

六、手把手教你添加SDI机制

6.1 修改一

6.2 修改二

6.3 修改三

6.4 修改四

七、融合后的yaml文件

八、运行截图

九、全文总结


二、原理介绍

论文地址: 论文官方地址

代码地址: 官方代码地址


2.1 BiFPN的基本原理

BiFPN(Bidirectional Feature Pyramid Network) ,双向特征金字塔网络是一种 高效的多尺度特征融合网络 ,它在传统特征金字塔网络(FPN)的基础上进行了优化。 主要特点 包括:

1. 高效的双向跨尺度连接: BiFPN通过在自顶向下和自底向上路径之间建立双向连接,允许不同尺度特征间的信息更有效地流动和融合。

2. 简化的网络结构: BiFPN通过删除只有一个输入边的节点、在同一层级的输入和输出节点间添加额外边,以及将每个双向路径视为一个特征网络层并重复多次,来优化跨尺度连接。

3. 加权特征融合: BiFPN引入了可学习的权重来确定不同输入特征的重要性,从而提高了特征融合的效果。

我们可以将其基本原理概括分为以下几点:

1. 双向特征融合: BiFPN允许特征在自顶向下和自底向上两个方向上进行融合,从而更有效地结合不同尺度的特征。

2. 加权融合机制: BiFPN通过为每个输入特征添加权重来优化特征融合过程,使得网络可以更加重视信息量更大的特征。

3. 结构优化: BiFPN通过移除只有一个输入边的节点、添加同一层级的输入输出节点之间的额外边,并将每个双向路径视为一个特征网络层,来优化跨尺度连接。

我将通过下图为大家对比展示BiFPN与 其他四种不同特征金字塔网络设计的不同 以及BiFPN如何 更有效地整合特征

​​

(a) FPN (Feature Pyramid Network): 引入了自顶向下的路径来融合从第3层到第7层(P3 - P7)的多尺度特征。
(b) PANet: 在FPN的基础上增加了自底向上的额外路径。
(c) NAS-FPN: 使用神经架构搜索(NAS)来找到不规则的特征网络拓扑,然后重复应用相同的块。
(d) BiFPN: 通过高效的双向跨尺度连接和重复的块结构,改进了准确度和效率之间的权衡。

我们可以看出BiFPN通过双向路径 允许特征信息在不同尺度间双向流动 ,这种双向流动可以看做是在不同尺度之间进行有效信息交换。这样的设计旨在通过 强化特征的双向流动 来提升特征融合的效率和有效性,从而提高目标检测的 性能


2.2 双向特征融合

双向特征融合 在BiFPN(双向特征金字塔网络)中指的是一种机制,它允许在特征网络层中的信息在自顶向下和自底向上两个方向上流动和融合。这种方法与传统的单向特征金字塔网络(如PANet)相比,能够 在不同层级之间更高效地融合特征,而无需增加显著的计算成本。

在BiFPN中,每一条双向路径(自顶向下和自底向上)被视作一个单独的特征网络层,然后这些层可以被重复多次,以促进更高级别的特征融合。这样做的结果是一个简化的双向网络,它增强了网络对特征融合的能力,使网络能够更有效地利用不同尺度的信息,从而提高目标检测的性能。

下图展示的是 EfficientDet架构的具体细节 ,其中包含了EfficientNet作为骨干网络(backbone),以及BiFPN作为特征网络的使用。在这个架构中,BiFPN层通过其双向特征融合的能力,从EfficientNet骨干网络接收多尺度的输入特征,然后生成用于对象分类和边框预测的富有表现力的特征。

在BiFPN层中,我们可以看到不同尺度的特征(P2至P7)如何通过 上下双向路径进行融合 。这种结构设计的目的是在保持计算效率的同时最大化特征融合的效果,以提高对象检测的整体性能。图中还显示了类别预测网络和边框预测网络,这些是在BiFPN特征融合后用于预测对象类别和定位对象边界框的网络部分。


2.3 加权融合机制

加权融合机制 是BiFPN中用于 改进特征融合效果 的一种技术。在传统的特征金字塔网络中,所有输入特征通常在没有区分的情况下等同对待,这意味着不同分辨率的特征被简单地相加在一起,而不考虑它们对输出特征的不同贡献。然而,在BiFPN中,观察到由于不同的输入特征具有不同的分辨率,它们通常 对输出特征的贡献是不等的

为了解决这个问题,BiFPN提出了 为每个输入添加一个额外的权重 ,并让网络学习每个输入特征的重要性:

O = \sum_i w_i \cdot I_i

其中 w_{i} ​ 是一个可学习的权重,可以是标量(每个特征),向量(每个通道)或多维张量(每个像素)。这些权重是可学习的,可以是标量(针对每个特征),向量(针对每个通道),或者多维张量(针对每个像素)。这种加权融合方法可以在最小化计算成本的同时实现与其他方法可比的准确度。


2.4 结构优化

结构优化 是为了在不同的资源约束下,通过复合缩放方法确定不同的层数,从而在保持效率的同时提高准确性。我们通过分析观察BiFPN的设计, 其结构优化包括:

1. 简化的双向网络: 通过优化结构,减少了网络中的节点数,特别是移除了那些只有一个输入边的节点。这种简化的直觉是如果一个节点没有进行特征融合,即它只有一个输入边,那么它对于融合不同特征的特征网络的贡献会更小。

2. 增加额外的边缘 :在相同层级的原始输入和输出节点之间增加了额外的边缘,以便在不显著增加成本的情况下融合更多的特征。

3. 重复使用双向路径: 与只有单一自顶向下和自底向上路径的PANet不同,BiFPN将每条双向(自顶向下和自底向上)路径视为一个特征网络层,并重复多次,以实现更高级别的特征融合。


论文地址: 论文官方地址

代码地址: 代码官方地址


2.5 SDI 的基本原理

SDI(Semantic and Detail Infusion) 模块是UNetV2 模型 的一个组成部分。UNetV2包含三个主要模块:编码器、SDI模块和解码器。在SDI模块中,首先应用空间和通道注意机制对编码器生成的每个层级的特征进行处理。

SDI模块的主要思想是通过整合编码器生成的层级特征图来增强图像中的语义信息和细节信息。具体来说:

1. 特征提取和整合: 首先,编码器针对输入图像生成多层级的特征。然后,通过空间和通道注意机制处理每个层级的特征,以便特征能够整合局部空间信息和全局通道信息。

2. 高级特征和低级特征的融合: 对于每个层级的特征图,SDI模块将包含更多语义信息的高级特征和捕捉更精细细节的低级特征进行融合。这通过简单的哈达玛积(Hadamard product)操作来实现,从而增强了每个层级特征的语义和细节。

3. 特征传递和分割: 经过精炼的特征随后传递给解码器,用于解析重构和 图像分割 。SDI模块可以无缝集成到任何编码器-解码器网络中。该方法已在多个公开的医学图像分割数据集上进行了验证,包括皮肤病变分割和息肉分割,展示了其在这些分割任务中相比于现有方法的优越性,同时保持了计算和内存效率。

图中的SDI模块部分 (b) 展示了该模块是如何对第三层级的特征(l=3)进行精细化处理的。我们可以从以下几个步骤来理解SDI模块的工作原理:

  1. 上采样(UpSample) :通过上采样过程,SDI模块将来自更低层级的特征图(l=2)的尺寸增加,使其与当前层级的尺寸匹配。这有助于将更细节的信息带入当前的特征图中。

  2. 身份映射(IdentityMap) :这通常表示特征图在不经任何修改的情况下直接传递到下一个操作。在这里,它可能表示第三层级的特征图在没有任何变化的情况下,直接传递到SDI模块进行处理。

  3. 下采样(DownSample) :与上采样相反,这一步将更高层级的特征图(l=4)的尺寸减小,以匹配第三层级的尺寸。这有助于将更高层次的语义信息带入当前层级。

  4. 注意力的应用 :应用空间和通道注意机制对编码器生成的每个层级的特征进行处理。这个过程使得特征能够整合局部空间信息和全局通道信息

总结: 这一机制我觉得大家可以理解成一种融合了注意力机制的Concat操作

三、BiFPN代码

添加方法看章节四!

  1. import torch.nn as nn
  2. import torch
  3. class swish(nn.Module):
  4. def forward(self, x):
  5. return x * torch.sigmoid(x)
  6. class Bi_FPN(nn.Module):
  7. def __init__(self, length):
  8. super().__init__()
  9. self.weight = nn.Parameter(torch.ones(length, dtype=torch.float32), requires_grad=True)
  10. self.swish = swish()
  11. self.epsilon = 0.0001
  12. def forward(self, x):
  13. weights = self.weight / (torch.sum(self.swish(self.weight), dim=0) + self.epsilon) # 权重归一化处理
  14. weighted_feature_maps = [weights[i] * x[i] for i in range(len(x))]
  15. stacked_feature_maps = torch.stack(weighted_feature_maps, dim=0)
  16. result = torch.sum(stacked_feature_maps, dim=0)
  17. return result


四、手把手教你修改BiFPN

4.1 修改一

第一还是建立文件,我们找到如下 ultralytics /nn文件夹下建立一个目录名字呢就是'Addmodules'文件夹( 用群内的文件的话已经有了无需新建) !然后在其内部建立一个新的py文件将核心代码复制粘贴进去即可。


4.2 修改二

第二步我们在该目录下创建一个新的py文件名字为'__init__.py'( 用群内的文件的话已经有了无需新建) ,然后在其内部导入我们的检测头如下图所示。

​​


4.3 修改三

第三步我门中到如下文件'ultralytics/nn/tasks.py'进行导入和注册我们的模块( 用群内的文件的话已经有了无需重新导入直接开始第四步即可)

​​


4.4 修改四

按照我的添加在ultralytics/nn/tasks.py文件下的parse_model里添加即可。

  1. elif m is Bi_FPN:
  2. length = len([ch[x] for x in f])
  3. args = [length]


五、SDI的核心代码

代码的使用方式看章节六!

  1. import torch
  2. import torch.nn as nn
  3. import torch.nn.functional as F
  4. __all__ = ['SDI']
  5. class SDI(nn.Module):
  6. def __init__(self, channel):
  7. super().__init__()
  8. self.convs = nn.ModuleList(
  9. [nn.Conv2d(c, channel[0], kernel_size=3, stride=1, padding=1) for c in channel])
  10. def forward(self, xs):
  11. ans = torch.ones_like(xs[0])
  12. target_size = xs[0].shape[-2:]
  13. for i, x in enumerate(xs):
  14. if x.shape[-1] > target_size[0]:
  15. x = F.adaptive_avg_pool2d(x, (target_size[0], target_size[1]))
  16. elif x.shape[-1] < target_size[0]:
  17. x = F.interpolate(x, size=(target_size[0], target_size[1]),
  18. mode='bilinear', align_corners=True)
  19. ans = ans * self.convs[i](x)
  20. return ans


六、手把手教你添加SDI机制

这个添加方式和之前的变了一下,以后的添加方法都按照这个来了,是为了和群内的文件适配。


6.1 修改一

第一还是建立文件,我们找到如下ultralytics/nn文件夹下建立一个目录名字呢就是'Addmodules'文件夹( 用群内的文件的话已经有了无需新建) !然后在其内部建立一个新的py文件将核心代码复制粘贴进去即可。


6.2 修改二

第二步我们在该目录下创建一个新的py文件名字为'__init__.py'( 用群内的文件的话已经有了无需新建) ,然后在其内部导入我们的检测头如下图所示。


6.3 修改三

第三步我门中到如下文件'ultralytics/nn/tasks.py'进行导入和注册我们的模块( 用群内的文件的话已经有了无需重新导入直接开始第四步即可)

从今天开始以后的教程就都统一成这个样子了,因为我默认大家用了我群内的文件来进行修改!!


6.4 修改四

按照我的添加在parse_model里添加即可。

  1. elif m is SDI:
  2. args = [[ch[x] for x in f]]

到此就修改完成了,大家可以复制下面的yaml文件运行。


七、融合后的yaml文件

训练信息:YOLO11-SDI-BiFPN summary: 417 layers, 3,123,074 parameters, 3,123,058 gradients, 10.1 GFLOPs

  1. # Ultralytics YOLO 🚀, AGPL-3.0 license
  2. # YOLO11 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect
  3. # Parameters
  4. nc: 80 # number of classes
  5. scales: # model compound scaling constants, i.e. 'model=yolo11n.yaml' will call yolo11.yaml with scale 'n'
  6. # [depth, width, max_channels]
  7. n: [0.50, 0.25, 1024] # summary: 319 layers, 2624080 parameters, 2624064 gradients, 6.6 GFLOPs
  8. s: [0.50, 0.50, 1024] # summary: 319 layers, 9458752 parameters, 9458736 gradients, 21.7 GFLOPs
  9. m: [0.50, 1.00, 512] # summary: 409 layers, 20114688 parameters, 20114672 gradients, 68.5 GFLOPs
  10. l: [1.00, 1.00, 512] # summary: 631 layers, 25372160 parameters, 25372144 gradients, 87.6 GFLOPs
  11. x: [1.00, 1.50, 512] # summary: 631 layers, 56966176 parameters, 56966160 gradients, 196.0 GFLOPs
  12. # YOLO11n backbone
  13. backbone:
  14. # [from, repeats, module, args]
  15. - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
  16. - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
  17. - [-1, 2, C3k2, [256, False, 0.25]]
  18. - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  19. - [-1, 2, C3k2, [512, False, 0.25]]
  20. - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  21. - [-1, 2, C3k2, [512, True]]
  22. - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  23. - [-1, 2, C3k2, [1024, True]]
  24. - [-1, 1, SPPF, [1024, 5]] # 9
  25. - [-1, 2, C2PSA, [1024]] # 10
  26. # YOLO11n head
  27. head:
  28. - [4, 1, Conv, [256]] # 11-P3/8
  29. - [6, 1, Conv, [256]] # 12-P4/16
  30. - [10, 1, Conv, [256]] # 13-P5/32
  31. - [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 14 P5->P4
  32. - [[-1, 12], 1, SDI, []] # 15
  33. - [-1, 3, C3k2, [256, False]] # 16-P4/16
  34. - [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 17 P4->P3
  35. - [[-1, 11], 1, SDI, []] # 18
  36. - [-1, 3, C3k2, [256, False]] # 19-P3/8
  37. - [1, 1, Conv, [256, 3, 2]] # 20 P2->P3
  38. - [[-1, 11, 19], 1, SDI, []] # 21
  39. - [-1, 3, C3k2, [256, False]] # 22-P3/8
  40. - [-1, 1, Conv, [256, 3, 2]] # 23 P3->P4
  41. - [[-1, 12, 16], 1, SDI, []] # 24
  42. - [-1, 3, C3k2, [512, False]] # 25-P4/16
  43. - [-1, 1, Conv, [256, 3, 2]] # 26 P4->P5
  44. - [[-1, 13], 1, SDI, []] # 27
  45. - [-1, 3, C3k2, [1024, True]] # 28-P5/32
  46. - [[22, 25, 28], 1, Detect, [nc]] # Detect(P3, P4, P5)

八、运行截图


九、全文总结

到此本文的正式分享内容就结束了,在这里给大家推荐我的YOLOv11改进有效涨点专栏,本专栏目前为新开的平均质量分98分,后期我会根据各种最新的前沿顶会进行论文复现,也会对一些老的改进机制进行补充,目前本专栏免费阅读(暂时,大家尽早关注不迷路~),如果大家觉得本文帮助到你了,订阅本专栏,关注后续更多的更新~