yolo核心组件9:BiFPN 加权双向特征金字塔

BiFPN 加权双向特征金字塔

!abstract 论文信息

  • 论文标题: EfficientDet: Scalable and Efficient Object Detection
  • 作者: Mingxing Tan, Ruoming Pang, Quoc V. Le
  • 发表: CVPR 2020
  • 论文地址: https://arxiv.org/abs/1911.09070
  • 核心贡献: 提出带权重的双向特征金字塔网络 (BiFPN),通过可学习的特征权重实现更高效的多尺度特征融合

一、核心思想

BiFPN 的核心思想是通过可学习的权重来实现更有效的多尺度特征融合,并采用高效的双向(自顶向下 + 自底向上)跨层连接。

BiFPN 的三个关键改进:

  1. 加权特征融合:使用可学习的权重来平衡不同尺度特征的贡献
  2. 双向跨层连接:在 FPN 和 PANet 的基础上优化连接拓扑
  3. 重复堆叠:多次迭代融合以增强特征交互

BiFPN 的设计哲学:

  • 移除只有一个输入的节点(无特征融合)
  • 在同一层的输入和输出节点之间添加跳跃连接
  • 将单向融合替换为双向融合

二、模块结构

2.1 特征网络拓扑演变

复制代码
(a) FPN (自顶向下):      (b) PANet (双向):      (c) BiFPN (加权双向):

  P7 ──→ P7_out           P7 ──→ P7_out ──→      P7 ←──→ P7_out
    ↓                        ↓        ↑             ↑↓
  P6 ──→ P6_out           P6 ──→ P6_out ──→      P6 ←──→ P6_out
    ↓                        ↓        ↑             ↑↓
  P5 ──→ P5_out           P5 ──→ P5_out ──→      P5 ←──→ P5_out
    ↓                        ↓        ↑             ↑↓
  P4 ──→ P4_out           P4 ──→ P4_out ──→      P4 ←──→ P4_out
    ↓                        ↓        ↑             ↑↓
  P3 ──→ P3_out           P3 ──→ P3_out ──→      P3 ←──→ P3_out

(单向)                   (双向,等权)            (双向,加权)

2.2 BiFPN 融合节点

复制代码
以 P5 层为例:

  P5_in ──→ ┐
             │
  P6_td ──→ ├─── 快速归一化融合 ──→ P5_out
             │
  P4_out ──→ ┘
      ↑
   (下采样)

其中:
- P5_in: 来自Backbone的原始P5特征
- P6_td: 来自上层(P6)的自顶向下特征 (上采样)
- P4_out: 来自下层(P4)的自底向上特征 (下采样)

2.3 完整 BiFPN 结构 (以 EfficientDet-D0 为例)

复制代码
输入特征 (来自 Backbone):
  P3_in (1/8)
  P4_in (1/16)
  P5_in (1/32)
  P6_in (1/64)  ← 额外下采样
  P7_in (1/128) ← 额外下采样

第1层 BiFPN:
  自顶向下: P7→P6→P5→P4→P3
  自底向上: P3→P4→P5→P6→P7

第2层 BiFPN:
  重复上述过程 (共享权重或独立权重)

... (重复N次,N=3~7)

输出:
  P3_out, P4_out, P5_out, P6_out, P7_out

三、数学公式

3.1 标准融合方式

FPN 的简单相加:

Output=Resize(Feature1)+Feature2\text{Output} = \text{Resize}(\text{Feature}_1) + \text{Feature}_2Output=Resize(Feature1)+Feature2

3.2 加权融合策略

BiFPN 提出三种加权融合方式:

方式一:朴素的权重融合

Output=∑iwi⋅Resize(Featurei)\text{Output} = \sum_i w_i \cdot \text{Resize}(\text{Feature}_i)Output=i∑wi⋅Resize(Featurei)

问题:权重 wiw_iwi 无约束,可能导致训练不稳定。

方式二:基于 Softmax 的权重融合

Output=∑iewi∑jewj⋅Resize(Featurei)\text{Output} = \sum_i \frac{e^{w_i}}{\sum_j e^{w_j}} \cdot \text{Resize}(\text{Feature}_i)Output=i∑∑jewjewi⋅Resize(Featurei)

问题:Softmax 计算开销较大。

方式三:快速归一化融合 (Fast Normalized Fusion)

Output=∑iwiϵ+∑jwj⋅Resize(Featurei)\text{Output} = \sum_i \frac{w_i}{\epsilon + \sum_j w_j} \cdot \text{Resize}(\text{Feature}_i)Output=i∑ϵ+∑jwjwi⋅Resize(Featurei)

其中 wi≥0w_i \geq 0wi≥0,ϵ=10−4\epsilon = 10^{-4}ϵ=10−4 为小常数防止除零。这是 BiFPN 最终采用的方式,计算高效且效果好。

3.3 BiFPN 节点计算

自顶向下路径 (中间层):

P6td=Conv(w1⋅P6in+w2⋅Resize(P7in)w1+w2+ϵ)P_6^{td} = \text{Conv}\left(\frac{w_1 \cdot P_6^{in} + w_2 \cdot \text{Resize}(P_7^{in})}{w_1 + w_2 + \epsilon}\right)P6td=Conv(w1+w2+ϵw1⋅P6in+w2⋅Resize(P7in))

自底向上路径 (输出层):

P6out=Conv(w1′⋅P6in+w2′⋅P6td+w3′⋅Resize(P5out)w1′+w2′+w3′+ϵ)P_6^{out} = \text{Conv}\left(\frac{w_1' \cdot P_6^{in} + w_2' \cdot P_6^{td} + w_3' \cdot \text{Resize}(P_5^{out})}{w_1' + w_2' + w_3' + \epsilon}\right)P6out=Conv(w1′+w2′+w3′+ϵw1′⋅P6in+w2′⋅P6td+w3′⋅Resize(P5out))


四、代码实现

4.1 快速归一化融合

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F

class FastNormalizedFusion(nn.Module):
    """快速归一化特征融合"""
    def __init__(self, num_inputs, eps=1e-4):
        super().__init__()
        self.eps = eps
        self.weights = nn.Parameter(torch.ones(num_inputs))

    def forward(self, inputs):
        """
        Args:
            inputs: 特征图列表, 需要对齐尺寸
        """
        # 对齐尺寸
        target_size = inputs[0].shape[2:]
        aligned = []
        for x in inputs:
            if x.shape[2:] != target_size:
                x = F.interpolate(x, size=target_size, mode='nearest')
            aligned.append(x)

        # 快速归一化加权
        w = F.relu(self.weights)
        w_sum = w.sum() + self.eps
        output = sum(w[i] / w_sum * aligned[i] for i in range(len(inputs)))
        return output

4.2 BiFPN 融合块

python 复制代码
class BiFPNBlock(nn.Module):
    """单层 BiFPN 块"""
    def __init__(self, channels, eps=1e-4):
        super().__init__()
        # 自顶向下融合 (中间节点)
        self.td_fuse_p6 = FastNormalizedFusion(2, eps)
        self.td_fuse_p5 = FastNormalizedFusion(2, eps)
        self.td_fuse_p4 = FastNormalizedFusion(2, eps)

        # 自底向上融合 (输出节点)
        self.out_fuse_p4 = FastNormalizedFusion(3, eps)  # P4_in, P4_td, P3_out
        self.out_fuse_p5 = FastNormalizedFusion(3, eps)
        self.out_fuse_p6 = FastNormalizedFusion(3, eps)
        self.out_fuse_p7 = FastNormalizedFusion(3, eps)

        # 深度可分离卷积
        self.conv_p6_td = DepthwiseSeparableConv(channels)
        self.conv_p5_td = DepthwiseSeparableConv(channels)
        self.conv_p4_td = DepthwiseSeparableConv(channels)
        self.conv_p4_out = DepthwiseSeparableConv(channels)
        self.conv_p5_out = DepthwiseSeparableConv(channels)
        self.conv_p6_out = DepthwiseSeparableConv(channels)
        self.conv_p7_out = DepthwiseSeparableConv(channels)

    def forward(self, features):
        p3_in, p4_in, p5_in, p6_in, p7_in = features

        # 自顶向下
        p6_td = self.conv_p6_td(self.td_fuse_p6([p6_in, self._up(p7_in)]))
        p5_td = self.conv_p5_td(self.td_fuse_p5([p5_in, self._up(p6_td)]))
        p4_td = self.conv_p4_td(self.td_fuse_p4([p4_in, self._up(p5_td)]))

        # 自底向上
        p4_out = self.conv_p4_out(
            self.out_fuse_p4([p4_in, p4_td, p3_in])
        )
        p5_out = self.conv_p5_out(
            self.out_fuse_p5([p5_in, p5_td, self._down(p4_out)])
        )
        p6_out = self.conv_p6_out(
            self.out_fuse_p6([p6_in, p6_td, self._down(p5_out)])
        )
        p7_out = self.conv_p7_out(
            self.out_fuse_p7([p7_in, self._down(p6_out)])
        )

        return [p3_in, p4_out, p5_out, p6_out, p7_out]

    def _up(self, x):
        return F.interpolate(x, scale_factor=2, mode='nearest')

    def _down(self, x):
        return F.max_pool2d(x, kernel_size=2, stride=2)


class DepthwiseSeparableConv(nn.Module):
    """深度可分离卷积"""
    def __init__(self, channels):
        super().__init__()
        self.dw = nn.Conv2d(channels, channels, 3, 1, 1,
                            groups=channels, bias=False)
        self.pw = nn.Conv2d(channels, channels, 1, bias=False)
        self.bn = nn.BatchNorm2d(channels)
        self.act = nn.SiLU(inplace=True)

    def forward(self, x):
        return self.act(self.bn(self.pw(self.dw(x))))

4.3 完整 BiFPN

python 复制代码
class BiFPN(nn.Module):
    """BiFPN: 带权重的双向特征金字塔"""
    def __init__(self, in_channels, out_channels, num_layers=3):
        super().__init__()
        # 通道统一
        self.lateral_convs = nn.ModuleList([
            nn.Conv2d(c, out_channels, 1) for c in in_channels
        ])
        # P6, P7 额外下采样
        self.p6_down = nn.Conv2d(out_channels, out_channels, 3, 2, 1)
        self.p7_down = nn.Conv2d(out_channels, out_channels, 3, 2, 1)

        # 堆叠 BiFPN 块
        self.bifpn_blocks = nn.ModuleList([
            BiFPNBlock(out_channels) for _ in range(num_layers)
        ])

    def forward(self, features):
        # 通道对齐
        aligned = [conv(f) for conv, f in zip(self.lateral_convs, features)]

        # 额外层
        p6 = self.p6_down(aligned[-1])
        p7 = self.p7_down(p6)
        aligned.extend([p6, p7])

        # 堆叠 BiFPN
        for block in self.bifpn_blocks:
            aligned = block(aligned)

        return aligned

五、在YOLO中的应用

5.1 适用场景

BiFPN 在以下场景中特别有效:

场景 原因
多尺度目标检测 加权融合能更好地平衡不同尺度特征
自动驾驶 远近目标尺度差异大
遥感检测 目标尺度变化剧烈
边缘部署 EfficientDet 系列针对移动端优化

5.2 与 FPN/PANet 对比

特性 FPN PANet BiFPN
信息流方向 自顶向下 双向 双向
融合权重 固定(相加) 固定(拼接) 可学习
跳跃连接
重复融合
计算量 中高

5.3 YOLO 配置示例

yaml 复制代码
# 使用 BiFPN 作为 Neck 的 YOLO 配置
neck:
  - [-1, 1, BiFPN, [[256, 512, 1024], 256, 3]]  # 3层BiFPN
head:
  - [[3, 4, 5], 1, Detect, [nc]]

六、优缺点

优点

  1. 可学习的融合权重:不同特征的重要性通过数据自动学习,优于固定权重
  2. 高效的拓扑结构:移除了无融合的节点,添加了跳跃连接,信息流更高效
  3. 重复堆叠增强:多次迭代融合,增强特征交互
  4. 精度提升显著:在 COCO 等基准上以较低成本获得明显提升
  5. 可扩展性强:通过调整深度和宽度,适应不同计算预算

缺点

  1. 计算量较大:多次融合和额外的卷积增加了计算开销
  2. 设计复杂:拓扑结构和超参数(层数、通道数)需要仔细调优
  3. 训练难度:可学习权重可能需要更多训练数据和更细致的调参
  4. 延迟较高:相比简单的 FPN,推理延迟更高
  5. 内存占用大:重复融合需要缓存更多中间特征

参考

  1. Tan, M., Pang, R., & Le, Q.V. (2020). EfficientDet: Scalable and Efficient Object Detection. CVPR 2020.
  2. https://arxiv.org/abs/1911.09070
  3. https://github.com/google/automl/tree/master/efficientdet
相关推荐
懷淰メ8 小时前
【AI赋能】基于PyQt+YOLO+DeepSeek水上漂浮物检测系统(详细介绍)
人工智能·yolo·目标检测·计算机视觉·pyqt·漂浮物·水上漂浮物
FL16238631299 小时前
非机动车闯红灯电动摩托车闯红灯检测数据集VOC+YOLO格式1754张3类别
人工智能·yolo·机器学习
星云_byto11 小时前
开付费专栏的意见收集
yolo·detr·多模态融合·rgb-d·多模态目标检测·rgb-ir·rgb-t
梦想三三2 天前
YOLOv5 口罩目标检测实战(一):项目整体介绍与数据准备
人工智能·yolo·目标检测
风逸尘_lz3 天前
面试:基于yolov5的头盔检测系统-项目概述(待补充)
人工智能·yolo·目标跟踪
羊羊小栈3 天前
化学生物实验室安全检测分析预警系统(YOLO检测_多模态大模型分析)
人工智能·安全·yolo·毕业设计·大作业
探物 AI3 天前
YOLO 目标检测中的特征是如何流动的
yolo·目标检测·目标跟踪
发光的小豆芽4 天前
记录第一次使用YOLOv8-seg做皮肤色素块分割
yolo
程序员正茂5 天前
Android工程中使用ncnn进行yolo识别
android·yolo·ncnn