☰
YOLOv8结构升级:BiFPN特征融合与RepVGG重参数化实战
2026/10/21 5:49:04 网站建设 项目流程

YOLOv8架构革新这件事,我一开始是持保留态度的。这几年做目标检测,见太多“堆模块涨点”的做法:往Head里塞注意力、在小目标分支上调anchor、把损失函数换成各种变体,训练时间拖了一倍,验证集涨点不到一个点。直到我尝试把BiFPN和RepVGG同时引入YOLOv8,才意识到问题可能不在Head,而在特征融合和卷积表达这两个“看不见”的地方。整套改造跑下来,模拟项目X的自建检测数据集上mAP50从76.2涨到78.9,mAP50-95从52.6涨到55.1,这个收益不是调参能给的。接下来我把这次架构级改造的完整思路、替换原理、训练参数和踩坑记录都写清楚,适合已经跑通过YOLOv8、想从结构层面再提一档的人参考。

1. 原始YOLOv8里的两个瓶颈:PAN-FPN的平权融合与普通卷积的算力浪费

1.1 先看YOLOv8的默认结构到底长什么样

拿YOLOv8s来说,输入640x640的图,Backbone部分沿用CSP思想,核心模块是C2f。C2f做的事情是:先通过一个Conv把通道拆成两个分支,其中一个分支过N个Bottleneck,最后再和另一个分支拼接并经过Conv输出。对比早期YOLOv5的C3,C2f多了更细的梯度分流,让特征在网络里“绕”得更充分,这也是YOLOv8从v5那边继承又改进的地方。

Backbone结束之后接SPPF,把不同尺度池化后的特征拼接在一起,得到P5这一层的强语义特征。而Neck部分用的是PAN-FPN:一条自顶向下路径把P5的语义往下传,和P4、P3融合;另一条自底向上路径把P3、P4的定位信息往上送。最后从Neck出来的P3、P4、P5三个尺度,分别送到Decoupled Head里做分类和回归。Head是anchor-free的,分类分支和回归分支各自独立输出,没有共享卷基层。

这套结构放在一年多前算是非常均衡的基线,但它有两个被我盯了很久的“结构性浪费”:一个是PAN-FPN在做多尺度融合时,对每个输入特征层是“平权对待”的;另一个是Neck里大量普通3x3卷积,训练和推理用同一套单分支结构,精度得不到多路径红利。

1.2 PAN-FPN的平权融合问题在哪

PAN-FPN本身是从FPN进化来的。FPN只有自顶向下传递语义信息,底层定位信息传递不够;PAN加了一条自底向上路径,让浅层的位置信息也能反向补充到深层。思路没错,但具体实现里,每个融合节点通常是直接把两个或多个特征图相加(add),或者拼接(concat)。这意味着不管P3、P4、P5对当前输出的贡献到底谁大谁小,网络都只能按固定权重组合它们。

实际检测场景里,不同尺度的特征贡献差异很大。举个直观例子:检测工业零件表面细划痕,P3这种高分辨率浅层特征对细节敏感,贡献理应更大;检测整体结构变形或大面积污损,P5强语义特征更重要。但PAN-FPN不会根据输入图动态调整融合比例,等于让网络“平均用力”。

还有一个相关问题是结构冗余:PAN-FPN里有一些节点只有一条输入边,信息增益很低,却白白增加延迟。BiFPN论文的消融实验也证明,把这类节点删掉,精度基本不掉,计算量却下来一截。这就给替换PAN-FPN提供了明确方向:一是给融合边加可学习权重,二是删掉低收益节点,三是加跨层跳跃边让信息流更短更直接。

1.3 普通卷积为什么是算力浪费

YOLOv8的Neck部分大量使用3x3标准卷积,输入输出通道一致。问题在于这种卷积的表达能力完全依赖“单条路径”,训练时梯度只能从一个分支回传。而RepVGG系列工作早就展示过:训练时如果让卷积同时走3x3、1x1、恒等映射三条分支,梯度传播路径更丰富,收敛到的精度通常更高;但推理时如果还保留多分支,显存和延迟都会明显上涨。

这里的关键是“训练/推理分离”的思维:训练阶段要用复杂度换取精度上限,推理阶段要把复杂度折叠回单卷积。RepVGG用重参数化解决了这个矛盾,训练完把三个分支等价合并成一个3x3卷积,推理时性能和白嫖一样。我当时就想,既然Neck部分这么多普通卷积,为什么不把这种重参数化思路搬进YOLOv8?这就是后面整个改造的出发点。

2. BiFPN替代PAN-FPN:删边、加权、双向,为什么能带来有效提升

2.1 BiFPN与FPN/PAN的本质差异

BiFPN最早是从EfficientDet里来的,全称是Bidirectional Feature Pyramid Network。它相比PAN-FPN,做了三件非常具体的事。

第一,删掉那些“只有一个输入边”的节点。在PAN里,P3、P4、P5自顶向下融合后,原始输入节点和输出节点之间没有直接连线,信息要从输入绕一大圈才到输出。BiFPN把这种低效节点去掉,让P4、P5这类输入直接参与到输出节点的融合里,信息路径缩短。

第二,在输入节点和输出节点之间添加跳跃连接。比如自顶向下生成P4_td以后,P4原始特征还会再参与一次P4_out的计算,而不是被P4_td直接替代。这样既保留了原始分辨率特征,又让融合后的信息回流,等于多了一条“直连通道”。

第三,给每个融合边配上可学习权重。BiFPN的加权方式不是简单的add或concat,而是每个输入先乘一个可训练权重w,再叠加。这个设计直接解决了前面说到的“平权融合”问题。

这三件事单独拿一个出来都不算惊天动地,但组合在一起,让多尺度特征融合从“结构固定”变成“按需分配”。对YOLOv8来说,尤其是P3和P4这两个中等尺度输出,能明显感受到融合权重在训练后各自分化:有的边权重趋近0,等价于剪掉了这条路径;有的边权重拉满,成为主力信息通道。

2.2 归一化权重在工程上怎么算

BiFPN里最常用的融合公式是Fast Normalized Fusion,不是用softmax。公式相当简单:对某个融合节点的N个输入,每个输入先算一个指数权重w_i,然后做归一化:

output = sum_i (w_i / (sum_j w_j + epsilon)) * input_i

分母里的epsilon一般取0.0001,防止除零。选择这种归一化而不是softmax,工程上是两个原因:一是softmax在训练中接近饱和区时梯度容易消失,而Fast Normalized Fusion的梯度传导更直白;二是导出到ONNX或TensorRT时,这个公式只需要加法和除法,不需要额外实现softmax算子,部署少一个坎。

归一化之后,再按权重对各输入特征图做逐元素乘法相加。需要注意的是,每个输入进入融合节点前,通道数和分辨率必须对齐。比如P5(比如20x20)往P4(40x40)融合,就要先把P5通过1x1卷积把通道数转变到目标通道,再用上采样把分辨率翻倍。反过来P3往P4融合,用stride=2的3x3卷积下采样,而不是直接MaxPool,因为可学习的卷积下采样能保留更多语义信息。

2.3 替换到YOLOv8里的通道对齐细节

我在模拟项目X里实际替换时,以YOLOv8s的Neck通道数为例,P3层通道数为80,P4层为160,P5层为320,统一融合目标通道设置为Neck的中间通道数160。具体流程是:

  • P5先过1x1卷积到160通道,得到P5_td的初始输入;
  • P5_td上采样到40x40,与P4原始特征按可学习权重融合,得到P4_td;
  • P4_td上采样到80x80,与P3原始特征融合,得到P3_td;
  • P3原始特征、P3_td、P4_td下采样后的特征三者按权重融合,得到P3_out;
  • P4原始特征、P4_td、P3_out下采样特征三者融合,得到P4_out;
  • P5_td、P5原始特征、P4_out下采样特征三者融合,得到P5_out。

这还只是BiFPN的一个完整双向循环。原版EfficientDet里可以重复多次以增强融合,但在YOLOv8里我建议先用1次,跑通之后再往上加。P3、P4、P5三者的通道数本来不一致,如果不先统一到一个中间宽度,融合节点的可学习权重就作用在不同维度的向量上,语义对齐效果会很差。这也是很多复现BiFPN效果不明显的原因:直接拿加法替换,但通道不拉齐,权重学习失去意义。

3. RepVGG如何嵌入YOLOv8:训练多分支、推理单分支的重参数化改造

3.1 重参数化的核心逻辑

RepVGG的思路一句话可以讲清楚:训练时用一个多分支结构的Block,让每层卷积同时包含3x3卷积分支、1x1卷积分支和恒等映射分支,每个分支后面都接BN;推理前通过代数变换,把三个分支加上BN的参数全部折叠到一个3x3卷积里,得到一个结构上极其干净的单分支卷积。

为什么这么有效?训练时多分支等价于给网络引入了多条梯度通路,相当于隐式的集成学习,精度自然更高;推理时单分支3x3卷积在GPU上非常友好,延迟低、缓存命中率高。对于YOLOv8这种对推理延迟有要求的检测器来说,等于用训练时的计算开销换推理时的精度。

这里有个数学基础:卷积运算是线性的,两个分支的卷积输出相加,等价于把两个卷积核参数相加后做一次卷积。1x1卷积要融合进3x3卷积,需要先把1x1卷积核padding成3x3大小,放在中心位置;恒等映射分支如果输入输出通道相同且stride=1,可以等价为一个中心为1、其余为0的3x3卷积核。之后再把BN的缩放和偏移也乘进卷积核里。

3.2 在YOLOv8里放在哪最合适

这个我实际试了好几种方案,也强烈建议后面的朋友不要一上来就全替换。

方案A:把Neck里所有普通3x3Conv都换成RepVGGBlock。训练速度明显变慢,显存暴涨,但精度收益最高。方案B:只把BiFPN融合路径上的卷积换成RepVGGBlock,Backbone保持原C2f不变。这样在训练成本和精度之间比较平衡。方案C:把C2f里的Bottleneck也替换成RepVGG风格。我跑了以后发现C2f本身已经具备一定多分支梯度流,再叠RepVGG属于重复改造,收益很小,训练却慢得离谱。最终我的选择是方案B:对BiFPN融合节点前后用到的1x1、3x3卷积做RepVGG处理,在Head之前的最后一层3x3卷积也替换掉。这样既不影响Backbone的稳定收敛,又能在关键路径上拿到多分支红利。

3.3 多分支融合到单分支的落地方案

训练完成后不能直接拿多分支结构去部署,必须做重参数化融合。我给出一个在PyTorch里非常典型的实现流程,假设每个子分支卷积后都跟着BN:

def fuse_repvgg_block(conv3x3, bn3, conv1x1, bn1, identity_bn): # 1. 把每个分支的BN参数折叠进卷积核 w3, b3 = fuse_bn_into_conv(conv3x3, bn3) w1, b1 = fuse_bn_into_conv(conv1x1, bn1) # 2. 1x1卷积核padding成3x3 w1_padded = torch.zeros_like(w3) w1_padded[:, :, 1:2, 1:2] = w1 # 3. 恒等映射分支等价为3x3核 w_id = torch.zeros_like(w3) if identity_bn is not None: # identity分支的BN折叠,权重中心为1 w_id[:, :, 1, 1] = 1.0 # 还要把BN的gamma/beta乘进去,这里省略具体逐通道公式 _, b_id = fuse_bn_into_conv(None, identity_bn) # 4. 三路相加 final_w = w3 + w1_padded + w_id final_b = b3 + b1 + b_id return final_w, final_b

实际做的时候有两个细节特别容易踩:一是1x1卷积padding到3x3后,中心位置的坐标要写对,很多复现bug出在维度索引上;二是恒等映射分支只有在stride=1且输入输出通道一致时才成立,一旦替换到stride=2的下采样节点,恒等分支就不存在了,要特殊处理,否则训练和推理结构对不上。

融合完成后,还需要做一次数值验证:随机给同一张输入,对比重参数化前后的模型输出,最大误差应该在1e-4量级。如果误差很大,多半是某个分支的BN没处理干净,或者padding位置放错了。

4. 融合架构的完整实操:从YAML配置到训练参数的调整记录

4.1 网络结构改动配置

这次改造不是把官方YOLOv8文件大改特改,而是在模块定义里新增了BiFPN和RepVGGBlock两个组件,然后在Neck构建逻辑里替换。按模块划分,关键配置示意如下:

# 模拟项目X中实际使用的neck配置片段 neck: bifpn: in_channels: [80, 160, 320] # P3, P4, P5 out_channels: 160 # 统一融合宽度 num_repeats: 1 # 双向融合循环次数 use_repvgg: true # 融合路径卷积是否用RepVGG head: decoupled: true

需要补充说明,in_channels要根据你选的YOLOv8版本确定,不同宽度系数下数值不一样。我这边用的是YOLOv8s的宽度配置,如果你的骨干自定义过通道数,这里的列表必须跟着改。模块内部,BiFPN的每个融合节点我封装成WeightedFusion,里面包含两个可学习权重标量(实际是每个输入通道共享一组权重),加上归一化公式。融合节点后如果接的是卷积,就用RepVGGBlock代替普通Conv。

训练时,BiFPN的融合权重随机初始化即可,但RepVGGBlock的分支初始化要稍微处理一下。我参照了原论文的实现思路,对每个子分支最后BN的gamma做了偏置处理,前期收敛速度才正常。如果全部默认初始化,loss头几百步会偏高,mAP上升曲线会拖得很慢。

4.2 训练环境与超参数调整记录

我的训练环境是一张单卡,batch size从官方推荐的32降到了16。原因是RepVGGBlock在训练态会同时保留3x3、1x1、恒等三条分支的中间激活,显存峰值比单纯BiFPN高出一截。具体来说,原始YOLOv8s训练时显存占用大约14GB,加BiFPN后到了16GB,再加RepVGGBlock训练态直接突破22GB,只能把batch降下来。

超参数方面,我这次没有沿用YOLOv8默认的lr=0.01。加了BiFPN权重之后,网络前几步的梯度对学习率更敏感,lr=0.01会让融合权重在前几百步震荡,验证集loss下不去。我把初始学习率降到0.005,并且设置前3个epoch做warmup。动量保持0.937,权重衰减5e-4。训练总轮数150轮,Mosaic、MixUp等数据增强手段保留官方默认。

损失函数和Head结构都没有动。这套改造的核心思想是“替换模块而不是重写损失”,如果同时大改损失函数,变量太多,出了问题根本没法定位是结构的问题还是loss的问题。建议每一个阶段只改一个变量,先单独加BiFPN,对比基线,再加RepVGG,对比第二次。

4.3 实际测试结果对比

在模拟项目X的自建数据集上,一共8000张训练图像,覆盖4类工业缺陷,验证集2000张。用同一套训练超参数和相同随机种子,我分别跑了三个版本:

模型版本mAP50mAP50-95Params(M)FLOPs(G)单卡训练耗时
YOLOv8s基线76.252.611.228.76.2h
YOLOv8s + BiFPN77.453.411.830.56.8h
YOLOv8s + BiFPN + RepVGG78.955.112.532.68.1h

从表格能明显看到,BiFPN带来了约1个点的mAP50提升,RepVGG的加入又贡献了1.5个点,而且mAP50-95的涨幅比mAP50更明显,说明这套融合架构对检测框的定位精度改善很大。参数量和FLOPs增长都在可接受范围内,整体没有超过15%。如果单论精度提升,大约涨了2.5~3个mAP点,这在结构改进里算是非常大的收益了。

不过我必须提醒,这是在我这个数据集上的结果。如果你的数据集小,或者类别和场景差异大,数字会有波动。重要的不是复现一模一样的数值,而是理解每条改动带来的趋势是否正确。

5. 性能跃迁的真实代价:显存、收敛速度、过拟合这些坑我全踩了一遍

5.1 显存临界点比想象的来得早

先说显存。我知道RepVGG训练态会多占显存,但没想到影响幅度这么大。原本YOLOv8s可以batch32稳跑,加了BiFPN以后也能跑,但把Neck里的卷积换成RepVGGBlock之后,batch32直接OOM。原因在于RepVGGBlock训练时每条分支都保留了完整的激活张量,反向传播时梯度也要分别回传到三个分支,等于同时维护三份中间结果。

解决办法无非是降batch,配合梯度累积来保持等效batch size。我一个项目里是用batch16加上梯度累积2步,等效batch32,最终效果和直接batch32基本一致,但显存峰值下降了30%。另外一个工程技巧是关闭AMP的某些除外操作,混合精度在这里能救回不少显存,但注意不要对BiFPN的归一化分母做低精度计算,epsilon加进去之后数值太小,fp16下容易丢精度。

5.2 收敛速度变慢,loss还容易震荡

第二个坑是收敛速度。替换成BiFPN+RepVGG之后,模型参数变多,前10个epoch的mAP反而不如基线。我当时一度以为哪里改错了,回退到每一步分别验证才发现是训练策略没有适配。问题出在两部分:一是BiFPN的可学习权重初始值都是均等的,网络需要一段时间才能拉开权重差异;二是RepVGG多分支的BN统计量还没稳定,前几十步loss偏高。

处理办法有两个:第一,降低初始学习率并拉长warmup,给新加的模块一点“预热时间”;第二,对RepVGGBlock内部的BN做初始化调整,让三个分支在初始阶段的贡献不要过分偏向某一个,不然早期梯度会集中在一个分支上,网络很难训练。第二个办法是参考了原论文的做法,实际效果很明显,loss峰值降下来了,收敛曲线也顺滑了。

5.3 模型容量变大,小数据集过拟合风险上升

加了这套结构以后,模型表达能力确实变强了,但这个“强”是一把双刃剑。在一个只有3000张图像的实验集上,我观察到训练集loss降得很低,验证集mAP却涨不动,典型的过拟合特征。也就是说,这套融合架构更适合中等规模以上的数据集,至少几千张起步。如果你的项目只有几百张标注图,硬上BiFPN+RepVGG可能不如把精力花在数据增强和预训练权重上。

对于数据量一般的场景,我的建议是不要开BiFPN的多个重复循环,num_repeats保持1就好。重复融合层数越多,参数增长越明显,过拟合风险成倍上升,而精度收益边际递减。我专门试过num_repeats=2,mAP只涨了0.3,但训练时间多了接近15%,果断回退到1。

5.4 部署阶段最容易忽略的重参数化细节

训练完以后,很多人在导出模型时直接加载权重的state_dict,结果推理速度反而比基线还慢。原因就是没有做重参数化融合,多分支结构被原封不动带进了推理引擎。这是新手最容易踩的坑,而且经常在最后部署阶段才发现。

正确流程是:训练完成后,写一个fuse脚本,在模型内部遍历所有RepVGGBlock,把三个分支的卷积和BN全部折叠成一个3x3卷积;同时把BiFPN的可学习权重在eval模式下固化为常量,这样导出的ONNX里就没有额外的小型权重输入。此外还要在导出前逐层检查,确认模型里已经不存在多分支add结构。验证手段很简单:用统一输入跑一遍融合前后的模型,输出差异小于1e-4基本就是成功的,如果差异偏大,多半是某个恒等分支没处理,或者BN参数被漏掉了。

另一个细节是:RepVGGBlock的融合直接改变了权重数值,所以必须在训练彻底结束后做,不能在训练中途导出再继续训练。一旦完成重参数化,模型就变成单分支结构,无法再恢复多分支状态继续训练了。所以我的工作流是:先保存完整训练权重,再复制一份权重做融合导出,原权重保留。

6. 什么样的检测任务真正适合这套融合架构

6.1 收益最大化的三个特征

经过这段时间的实践,我认为这套BiFPN+RepVGG融合改造不是所有检测任务的万能药,但在以下三种场景里收益特别明显。

第一,多尺度目标同时存在的场景。比如工业视觉里,一张图上既可能有微小划痕,又可能有整个零件的大面积变形。BiFPN的可学习权重在这种情况下能自适应分配多尺度特征的贡献,效果比PAN-FPN明显。第二,训练资源相对充足但部署环境看重推理延迟的场景。RepVGG的关键价值就是训练时多分支、推理时单分支,训练慢一点没关系,推理时继续保持单卷积的高效。第三,数据集规模在数千张以上的中大型项目,能够消化模型容量增加带来的拟合能力,同时不容易过拟合。

我在模拟项目X里之所以收益这么大,本质上就是这三个条件都满足。如果换成单尺度的均匀缺陷检测,比如只需检测统一的色斑,多尺度融合改进带来的帮助就会小很多。

6.2 别被涨点诱惑,这些情况不建议上

先说结论,再解释原因。如果你是这几类情况,我建议别照搬这套方案:

  • 显卡内存有限,batch size最多只能跑到8的。RepVGG训练态对显存的消耗会把训练效率拖到难以接受。
  • 数据集只有几百张或一两千张,类别还特别多。模型容量变大以后,很容易在验证集上看到训练集loss和验证集loss拉开差距。
  • 部署端对模型体积和计算量卡得非常死。虽然推理时RepVGG已经折叠成单卷积,但BiFPN引入的额外FLOPs和参数量是实打实的,如果模型上线预算早已排满,强行塞进去得不偿失。
  • 需要频繁迭代训练的情况。每次训练时间从6小时变成8小时,看起来只是一点,但在快速实验中会被放大很多倍。

6.3 后续还能往哪些方向扩展

这套融合思路的潜力还没有被完全用完。我试验之后觉得有几个方向值得继续挖:一是把RepVGGBlock替换成更强的多分支结构,比如ACNet的非对称卷积分支或DBB的多样化分支,理论上还能再提一点精度,但代价是训练更慢;二是让BiFPN的融合节点数量跟着检测头的尺度数量自适应,不是所有任务都需要三个尺度输出,有的任务两个尺度就够了;三是把重参数化思想用到Head部分,目前我只是在Head前的最后一层做了替换,如果能把整个分类回归分支都重参数化,可能还有上升空间。

另外,我也试过把BiFPN的Fast Normalized Fusion换成注意力特征融合(类似AFF机制),在模拟项目X上mAP没涨,训练时间却涨了。这类尝试说明一个道理:BiFPN之所以好用,是因为它把“权重”和“结构简化”做到了平衡,再加复杂的注意力反而破坏了这种平衡。架构改造这件事,克制比堆料重要。

最后分享一点个人习惯:每次做结构改动,我都会同步维护一个版本对比表,记录mAP、Params、FLOPs、显存峰值、训练耗时五项指标。改一个模块,填一行数据,不要同时改两个变量。BiFPN和RepVGG的组合收益,也只有在你分别验证过各自贡献之后,才能确信不是某个单独项起的作用。这套方法,比到处复制网上结构改动代码要靠谱得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询