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 的三个关键改进:
- 加权特征融合:使用可学习的权重来平衡不同尺度特征的贡献
- 双向跨层连接:在 FPN 和 PANet 的基础上优化连接拓扑
- 重复堆叠:多次迭代融合以增强特征交互
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]]
六、优缺点
优点
- 可学习的融合权重:不同特征的重要性通过数据自动学习,优于固定权重
- 高效的拓扑结构:移除了无融合的节点,添加了跳跃连接,信息流更高效
- 重复堆叠增强:多次迭代融合,增强特征交互
- 精度提升显著:在 COCO 等基准上以较低成本获得明显提升
- 可扩展性强:通过调整深度和宽度,适应不同计算预算
缺点
- 计算量较大:多次融合和额外的卷积增加了计算开销
- 设计复杂:拓扑结构和超参数(层数、通道数)需要仔细调优
- 训练难度:可学习权重可能需要更多训练数据和更细致的调参
- 延迟较高:相比简单的 FPN,推理延迟更高
- 内存占用大:重复融合需要缓存更多中间特征
参考
- Tan, M., Pang, R., & Le, Q.V. (2020). EfficientDet: Scalable and Efficient Object Detection. CVPR 2020.
- https://arxiv.org/abs/1911.09070
- https://github.com/google/automl/tree/master/efficientdet