EfficientDet 目标检测实战:BiFPN 与复合缩放原理及调参指南

📅 发布时间:2026/10/15 12:38:56
EfficientDet 目标检测实战:BiFPN 与复合缩放原理及调参指南
1. 从一次模型选型的纠结说起目标检测这个方向每年都有新东西冒出来但真正能在工程里长期用下去的模型其实不多。我最早接触 EfficientDet 是在一个边缘设备部署的项目里当时的需求很明确要在算力有限的嵌入式板子上跑实时检测精度不能掉太多推理延迟还得压得住。那会儿试过几个主流方案要么精度够但跑不动要么速度快但小目标漏检严重来回折腾了好几轮。后来翻到 EfficientDet 这篇论文第一反应是它的思路挺克制的——没有堆花哨的模块而是把注意力放在两个最本质的问题上特征怎么融合以及网络怎么缩放。这两个问题听起来朴素但恰恰是目标检测里最影响最终效果的地方。EfficientDet 给出的答案分别是 BiFPN 和复合缩放compound scaling前者解决多尺度特征的高效融合后者解决 backbone、neck、head 和输入分辨率之间的配比问题。这篇笔记我想按自己读论文和复现时的思路来写不逐段翻译原文而是把里面几个关键设计拆开讲清楚为什么这么设计、和已有方案比强在哪、实际复现时哪些参数要特别注意。适合已经了解目标检测基础比如知道 FPN、anchor 机制的读者也适合正在做模型选型、想搞清楚 EfficientDet 到底值不值得用的工程同学。如果你只是想快速跑个 demo那直接调库就行但如果你想理解它为什么高效或者想在自己的数据集上做微调那这些细节绕不开。2. EfficientDet 的整体设计思路拆解2.1 它到底想解决什么问题目标检测模型发展到那个阶段精度高的模型普遍又大又慢轻量模型精度又上不去。EfficientDet 的出发点很直接在给定的计算资源FLOPs约束下尽可能把精度做到最高。这个给定资源求最优的思路其实和 EfficientNet 一脉相承只不过从分类任务搬到了检测任务上。检测任务比分类复杂的地方在于它多了 neck特征融合和 head检测头这两个部分而且输入分辨率对检测的影响比分类更大。所以 EfficientDet 要解决的核心矛盾是backbone、BiFPN、head、分辨率这四个维度到底怎么分配资源才最划算。这不是拍脑袋能定的需要一套可量化的缩放规则。2.2 两个核心贡献的定位整篇论文的骨架就两根柱子。第一根是BiFPN解决的是不同尺度的特征怎么融合才既有效又省算力。传统的 FPN 是单向的PANet 加了自顶向下和自底向上两条路径但结构冗余很多节点贡献很小。BiFPN 的做法是删掉那些只有一个输入的中间节点同时在同层之间加一条跳跃连接并且给不同输入配上可学习的权重。第二根是复合缩放解决的是四个维度怎么一起放大。单独放大某一个维度比如只加深 backbone收益会很快饱和必须按一定比例协同放大。EfficientDet 用一个简单的幂律公式把四个维度的缩放系数绑在一起用一个 φ 参数统一控制从 D0 一直到 D7 形成一系列模型。注意很多人第一次读会以为 BiFPN 只是个结构改进其实它和复合缩放是配套的。BiFPN 让 neck 部分变得可缩放且高效复合缩放才有空间去调 neck 的重复次数。2.3 和同期方案的横向对比读论文时我习惯把它放到当时的坐标系里看。和 EfficientDet 同期或稍早的方案大致分几类一类是追求极致精度的重型模型backbone 用很深的 ResNeXt 或更大的网络精度高但推理成本大一类是轻量方案用 MobileNet 做 backbone速度快但精度一般还有一类是 NAS 搜出来的结构效果好但搜索成本高、结构不透明。EfficientDet 的位置比较讨巧它用 EfficientNet 做 backbone本身就是在精度-效率上优化过的配上 BiFPN再用复合缩放统一调参。结果是它在同等 FLOPs 下精度普遍领先而且模型系列从 D0 到 D7 覆盖了从移动端到服务器端的整个区间。这种一个框架覆盖全场景的能力是它当年被大量工程采用的重要原因。方案类型代表思路优势短板重型高精度深 backbone 复杂 neck精度上限高推理慢难部署轻量方案轻 backbone 简单 neck速度快小目标、密集场景弱NAS 方案自动搜索结构结构较优搜索贵难复现EfficientDetEfficientNet BiFPN 复合缩放精度效率均衡系列完整训练调参有一定门槛3. BiFPN 的核心细节与实操要点3.1 为什么传统 FPN 和 PANet 不够用先回顾一下 FPN 的问题。FPN 是自顶向下的单向融合高层语义特征上采样后和低层特征相加。问题是低层的位置信息传到高层后没有再往下传回去导致浅层特征里缺少高层语义。PANet 补了一条自底向上的路径缓解了这个问题但它的结构是每个节点都保留包括那些只有一个输入的中间节点。这些单输入节点其实很尴尬它们没有做融合只是把特征透传了一下却占用了计算量。BiFPN 的洞察就在这里——如果一个节点只有一个输入边那它对特征融合没有贡献删掉它既不影响融合效果又能省算力。这个观察看起来很朴素但确实是实打实的效率提升点。3.2 BiFPN 的三个关键改动第一个改动是删节点。把 PANet 里那些只有一条输入边的中间节点去掉简化了网络结构。第二个改动是加跳跃连接。如果原始输入和输出节点在同一层级就在它们之间加一条边这样能在不增加太多成本的前提下融合更多特征。第三个改动是加权融合。加权融合是 BiFPN 最有意思的地方。传统 FPN 融合是直接相加或者 concat 后卷积。直接相加的问题是不同尺度的特征贡献不一样等权处理不合理。BiFPN 给每个输入配一个可学习的权重融合时做加权求和。但直接用原始权重会导致训练不稳定所以它用了fast normalized fusion权重先过 ReLU 保证非负再除以所有输入权重之和做归一化。公式大致是这样输出等于各输入乘以归一化权重后的加权和。归一化这一步很关键它避免了权重数值范围失控也让训练更稳。我复现时试过不加归一化loss 确实会抖加上之后就平稳多了。3.3 实操中的参数与注意事项复现 BiFPN 时有几个参数需要留意。重复次数num repeats是复合缩放里控制 neck 深度的关键D0 用的是 3 层往后逐级增加。特征层数方面BiFPN 通常用 P3 到 P7 五层P3 到 P5 来自 backboneP6、P7 通过对 P5 下采样得到。通道数在 BiFPN 内部是统一的由复合缩放里的 W_bifpn 决定。提示BiFPN 的通道数不要和 backbone 各层通道数混为一谈。backbone 输出的各层通道数可能不同进 BiFPN 前一般会先用 1x1 卷积统一到 W_bifpn。实操心得方面我踩过的一个坑是BiFPN 的权重初始化。如果权重初始化得太小前期融合几乎等于等权收敛会慢初始化太大又容易震荡。论文里没有特别强调这点但实际训练时用默认初始化配合 warmup 是比较稳的做法。另外BiFPN 里的卷积建议用深度可分离卷积这是它效率高的另一个原因但要注意深度可分离卷积在某些硬件上的实际加速比可能不如理论值部署前最好实测一下。4. 复合缩放的原理与参数计算4.1 为什么不能只放大一个维度复合缩放的核心逻辑用一句话概括就是四个维度要协同放大单独放大任何一个都会很快遇到瓶颈。这个结论在 EfficientNet 里已经被验证过EfficientDet 把它扩展到了检测任务。举个直观的例子。如果你只加深 backbone参数量上去了但输入分辨率没变小目标还是看不清如果你只提高分辨率计算量平方级增长但模型容量不够学不到更细的特征。所以必须按比例一起调。EfficientDet 用一组幂律公式把四个维度的缩放系数绑定到同一个 φ 上。4.2 四个维度的缩放公式四个维度分别是backbone 的宽度和深度由 EfficientNet 的缩放规则控制、BiFPN 的宽度 W_bifpn 和深度 D_bifpn、检测头的宽度和深度、输入分辨率。它们各自是 φ 的函数形式都是类似基准值乘以 φ 的某次方。以 BiFPN 宽度为例公式是 W_bifpn 64 × (1.35^φ)深度是 D_bifpn 3 φ。检测头的宽度和 BiFPN 宽度挂钩深度是 3 floor(φ/3)。分辨率则是 512 φ × 128。这些系数不是随便定的是论文作者通过网格搜索在小规模上试出来的经验值。模型φ输入分辨率BiFPN 宽度BiFPN 深度检测头深度D005126433D116408843D2276811253D3389616064D44102422474D55128028874D66128038485D771536384854.3 参数选择的实操建议实际用的时候选哪个 φ 主要看你的算力预算和精度要求。D0 到 D2 适合移动端或边缘设备D3 到 D5 适合常规服务器推理D6、D7 基本是刷榜用的实际部署很少见。我个人的经验是如果你的数据集和 COCO 差异较大不要盲目照搬论文的缩放系数。复合缩放的系数是在 COCO 上搜出来的换到别的数据集比如遥感、医学影像最优配比可能不一样。这时候更实际的做法是先固定一个中等规模比如 D3然后单独微调分辨率和 BiFPN 深度看哪个方向收益更大再决定往哪边倾斜。注意提高分辨率带来的计算量增长是平方级的而宽度增长是平方级、深度增长是线性级。所以在算力受限时优先加深度其次加宽度最后才考虑加分辨率。5. 完整训练流程与关键环节实现5.1 数据准备与增强策略EfficientDet 的训练用的是 COCO 格式的数据但实际项目里数据格式五花八门第一步肯定是转格式。我一般会写个脚本把标注统一成 COCO 的 json 结构包括 images、annotations、categories 三个字段。这一步看似简单但类别 id 的映射很容易出错建议转换后抽样可视化几张确认一下。数据增强方面论文用的是比较标准的组合随机水平翻转、随机缩放、随机裁剪。这里有个细节随机缩放的范围要和输入分辨率匹配。比如你用 D0 的 512 分辨率缩放范围设得太大会导致大量目标被裁掉或变得过小。我的做法是让缩放后的短边在 0.5 到 1.5 倍输入分辨率之间浮动这样既能增加尺度多样性又不会太极端。5.2 损失函数与训练配置EfficientDet 的损失函数由两部分组成分类损失和回归损失。分类用的是 focal loss这个在 RetinaNet 里已经验证过能缓解正负样本不平衡。回归用的是 smooth L1对异常值不那么敏感。两部分损失按一定权重相加权重需要根据数据集调。训练配置上优化器用 SGD 配合 momentum学习率用 cosine 衰减或者 step 衰减。batch size 受显存限制如果跑不了论文那么大的 batch可以用梯度累积模拟。warmup 阶段很重要尤其是 BiFPN 的权重前期需要小学习率慢慢调。# 训练配置示例伪代码参数需按实际情况调整 config { optimizer: SGD, momentum: 0.9, weight_decay: 4e-5, initial_lr: 0.08, lr_schedule: cosine, warmup_epochs: 5, total_epochs: 300, batch_size: 16, grad_accumulation: 2, # 显存不够时用 focal_loss_alpha: 0.25, focal_loss_gamma: 1.5, reg_loss_weight: 50.0, }5.3 训练过程中的监控与调优训练时我重点盯三个指标总 loss、分类 loss、回归 loss。正常情况下三者应该同步下降如果分类 loss 降但回归 loss 不降可能是回归权重设小了反过来则可能是分类权重不够。另外要监控验证集上的 mAP如果训练 loss 降但验证 mAP 停滞甚至下降说明过拟合了该加正则或减模型规模。一个容易被忽略的点是anchor 的设置。EfficientDet 用的是和 RetinaNet 类似的 anchor 机制anchor 的尺度和长宽比要根据数据集里目标的实际分布来调。如果你的数据集里小目标特别多anchor 尺度要相应调小否则正样本匹配率会很低训练效率差。我一般会先统计一下训练集里目标框的宽高分布再据此设 anchor。6. 常见问题与排查技巧实录6.1 训练不收敛或 loss 震荡这是最常见的问题原因可能有好几个。首先检查学习率是不是太大尤其是用了大 batch 的时候学习率要相应放大但也不能过头。其次看 BiFPN 的权重归一化有没有正确实现如果归一化写错了权重会失控。第三看数据增强是不是太激进比如随机裁剪把目标裁得只剩一小块这种样本对训练是负面的。排查顺序建议是先把数据增强关掉用最简单的配置跑几十个 epoch看能不能收敛。如果能收敛再逐步加回增强定位是哪个增强出的问题。如果关掉增强还不收敛那就是模型或损失的问题重点查 BiFPN 实现和损失权重。6.2 小目标检测效果差小目标一直是检测的难点。EfficientDet 用了 P3 到 P7 五层特征P3 负责小目标但如果输入分辨率不够P3 上的小目标特征本身就模糊。解决办法有两个一是提高输入分辨率让 P3 保留更多细节二是调整 anchor 尺度让小目标更容易匹配到正样本。还有一个技巧是在 BiFPN 里加强浅层特征的权重。因为浅层特征包含更多位置和细节信息对小目标更友好。可以通过观察训练后 BiFPN 学到的权重来判断如果浅层权重偏低可以手动调高或者用更大的数据集重新训。6.3 推理速度不达预期论文里报的 FLOPs 和实际推理速度往往有差距因为 FLOPs 不反映内存访问、算子融合等实际开销。如果推理速度慢先看瓶颈在哪用 profiling 工具测一下各部分的耗时通常 BiFPN 和检测头占大头。优化方向有几个把 BiFPN 里的普通卷积换成深度可分离卷积如果还没换用 TensorRT 之类的推理框架做算子融合和量化降低输入分辨率但要权衡精度。我实测下来量化到 FP16 通常能提速 30% 到 50%精度损失很小量化到 INT8 提速更多但需要校准精度损失要看数据集。问题现象可能原因排查方法解决方向loss 震荡学习率过大 / 归一化错误关增强跑基线调小 lr检查 BiFPN 归一化小目标漏检分辨率不足 / anchor 不匹配统计目标尺寸分布提高分辨率调 anchor推理慢算子未融合 / 分辨率高profiling 定位瓶颈量化换推理框架过拟合模型太大 / 数据少对比训练验证曲线加正则减规模增数据6.4 迁移到自定义数据集的坑在自定义数据集上微调时最容易出问题的是类别数和类别不平衡。EfficientDet 的检测头输出维度是按类别数定的改类别数时别忘了改这部分。类别不平衡的话focal loss 的 alpha 参数要调稀有类可以给更高权重。另一个坑是预训练权重的加载。backbone 部分可以加载 ImageNet 预训练权重但 BiFPN 和检测头是随机初始化的。加载时要注意层名匹配不同实现的命名可能不一样加载失败会静默跳过导致你以为加载了其实没有。建议加载后打印一下成功匹配的层数确认。7. 我个人的一些使用体会EfficientDet 这套东西我觉得它最大的价值不在于某个单点创新而在于它把效率这件事系统化了。BiFPN 解决 neck 的效率复合缩放解决全局配比的效率两者配合才让整个系列从 D0 到 D7 都能保持竞争力。这种系统性的思考方式比单纯堆模块更值得学。实际用下来D0 到 D3 是我用得最多的区间性价比高部署也方便。D4 以上除非精度要求特别苛刻否则不太划算。另外如果你的场景和 COCO 差异很大别指望直接套用就能出好结果复合缩放的系数、anchor 的设置、损失权重都得重新调。这个过程没有捷径就是多试多记录。最后分享一个小技巧训练时把每次实验的配置和结果都记下来哪怕只是改了一个参数。EfficientDet 的可调参数不少靠脑子记很容易乱。我一般用一个简单的表格记录跑了几十次之后回头看哪些参数敏感、哪些不敏感一目了然后面调参就有方向了。