资讯动态

HS-FPN替换Neck:YOLOv13小目标检测精度提升实战

发布时间:2026/10/22 8:55:58 来源:尧图企业网站定制
做检测算法的人大概都有一个共同感受Backbone换了一个又一个精度涨了又掉最后发现真正的瓶颈往往不在“看没看到”而在“看到了能不能融合好”。前阵子在YOLOv13上做小目标检测优化试了不少结构改动最后把刀落在了Neck上用上HS-FPN这套高级筛选特征融合金字塔后效果比折腾Backbone来得直接。这篇就把这套改进思路、完整实操和踩过的坑梳理一遍给同样被小目标和细微特征困扰的朋友一个可以直接上手的参考方案。先说清楚这套方案解决的是哪类问题你训练集里目标本身占据的像素非常少比如几十米外的行人、密密麻麻的工业零件、遥感影像里的小船小楼普通FPN能把它们“带”到融合层但融合时无差别相加会把微弱信号直接淹没在背景噪声里。HS-FPN的核心就是给特征融合装了一个“筛选器”让金字塔每一层只融合那些真正有用的细微信号而不是一股脑全倒进去。我自己体验下来它对小目标的召回率和定位精度提升非常明显训练过程的损失曲线也比原来更平滑适合正在做检测模型结构优化、有较好工程基础的同学参考。1. 小目标检测瓶颈的根源在Neck而不是Backbone1.1 我在现网项目里遇到的实际问题之前处理过一个远距离安防场景的项目目标车辆在画面里往往只占二十来个像素光线不好的时候甚至只有七八个像素肉眼看着就是一坨模糊的色块。当时用的检测方案是YOLOv13的默认结构Backbone端已经用了相当强的下采样和注意力组合理论上的感受野和特征抽象能力都足够但实测各种指标就是卡在某个水位上不上去近处的大目标各种准一到远距离目标就频繁漏检。当时第一反应是“特征提取不够”于是沿用了网上很多改进文章的套路去改Backbone加重注意力、加深C3/C2f模块、调整激活函数试了一轮下来小目标的召回率顶多涨了一两个点而且训练时间明显变长推理速度也掉了不少。后来耐下心做了几轮特征图可视化才意识到问题不在Backbone端而是Neck融合时把细节特征“冲淡”了。具体现象是这样的Backbone末端的高层特征图语义信息非常充足但空间分辨率低小目标的细微信号已经开始丢失而浅层特征图保留了边缘、纹理、颜色等细节但噪声极大。Neck层在做自顶向下融合时它只是简单地把高层语义和浅层细节加在一起浅层特征里的大片背景纹理和噪声会通过加法直接污染那条微弱的“目标信号”。打个比方这就像在一个嘈杂的会议室里开会高层语义是总结好的会议纪要浅层细节是现场每个人的原始发言录音。FPN做的事是让你同时听纪要和高保真录音可录音里全是椅子摩擦声和空调噪音你要听的那个关键发言反而被盖住了。你的第一反应不该是换一个收音更好的麦克风而是应该先加一个降噪筛选器。1.2 为什么主流改进文章都扎堆Backbone翻看近几年各种YOLO改进的帖子你会发现很大比例都在改Backbone。原因很现实Backbone的改进最容易“涨点”只要加一些成熟的注意力模块或者更换主干网络在公开数据集上几乎都能稳定提升几个点写出来也好看。但注意这个涨点很多时候是把模型整体容量拉大了目标大小也大了而不是真的解决了小目标融合的问题。如果使用那些网结构在大目标上能加分的方法放到小目标数据上就不一定了。原因很简单Backbone负责的是“从像素到语义”的编码过程它确实决定了特征质量的底座但Neck负责的是“多尺度特征的协同”决定了小目标的信息能不能在融合后仍然幸存。对小目标来说中高层特征里的通道信息往往已经很稀疏真正有效的可能就是那么几个通道不加筛选直接融合这些通道会被周围大量无用信息“稀释”。我当时做了一次消融实验把默认Neck里的特征融合操作从简单相加换成带权重筛选的融合方式其他什么都不动小目标AP直接升了一个多点推理速度几乎没有损失。这个实验让我彻底确信对YOLOv13这类本身Backbone已经很强的模型来说优化Neck才是解决细分特征检测更高效的突破方向这也是我后来把HS-FPN整套结构移植进来的原因。2. HS-FPN的设计思路不是简单加深而是学会“筛选”2.1 常规特征融合的三个致命缺陷目前主流检测器用的Neck基本跑不出这几种形式。最早的是FPN自顶向下、逐层相加结构简单但有个老问题每一层融合时贡献是均等的哪边信息量大都往哪边倒导致小目标的细节层常常被大目标的语义层“带偏”。之后PANet加了一条自底向上的通路让底层细节也能向上流动改善了一些但本质仍是无差别融合。再后来BiFPN引入了可学习的加权融合权重但权重是一个全局的标量每个通道、每个像素用的是同一套融合比例没有真正做到“按需筛选”。这三个缺陷可以归纳成三点。第一全局权重不等于局部重要性同一个通道里可能一半是目标一半是背景单一标量权重解决不了这个矛盾。第二跨尺度融合时的语义鸿沟高层特征经过大量卷积后特征分布已经和浅层特征完全不同简单相加会产生严重的分布偏移就好比两个不同单位制的量直接做了四则运算。第三噪声放大特别是浅层特征包含大量纹理噪声和背景细节这些信息在小目标检测里不但没用反而会干扰最终的分类和回归判断。这些缺陷在普通检测数据上可能感受不明显因为大目标和中等目标本身特征强壮扛得住污染。可一旦目标缩小到几十像素以内任何一点噪声污染都可能让目标信号彻底消失这时常规融合方式的短板就非常明显了。2.2 高级筛选模块HSM是如何工作的HS-FPN的全称是High-level Screening Feature Pyramid Network中文可以理解为高级筛选特征融合金字塔。它的核心到底层设计上不是单纯的加一层注意力而是在Neck的每一条跨尺度融合路径前插入一个高级筛选模块HSM这个模块会先对输入特征做“去噪-加权-选择”三步处理再进入融合环节。HSM内部包含三个并行的子模块通道筛选分支、空间筛选分支和上下文校准分支。通道筛选分支借鉴了全局感知的思路对特征图做全局平均池化和全局最大池化把两种池化结果分别过一个全连接层再合并得到一组通道权重对特征图做重标定。这一步骤的作用是把“哪些通道是当前任务真正需要的”这个信息提取出来对无用通道做压制。空间筛选分支则是在空间维度上生成一张注意力图每个像素学出一个重要性分数让模型把注意力集中到目标可能出现的区域同时抑制天空、地面、大面积纯色墙壁这类背景区域。最关键的是上下文校准分支它会让筛选模块感知到当前金字塔层级的上下文信息。不同层的特征图其语义层级和感受野是不同的如果筛选标准一成不变结果必然失衡。所以这个分支会把当前层的层级编码和全局上下文向量加进来动态调节通道和空间的筛选取向。说白了同样的一个特征图放在浅层和放在深层筛选器对待它的方式是不一样的更偏向于小目标细节的还是偏向于深层语义的由上下文信息来决定。整个筛选过程可以用一个式子来表示输入特征F经过三个分支分别得到通道权重Wc、空间权重Ws和上下文调制因子T最终输出F T × (Wc ⊗ F) ⊗ (Ws ⊗ F)其中⊗表示逐元素操作。这样输出特征中的信息都是经过筛选后的有效信息融合时自然就能把小目标的细微信号保留下来。2.3 HS-FPN和BiFPN、PANet的本质区别如果你用过BiFPN可能会觉得HS-FPN的加权方式有点类似但这里要特别强调一点BiFPN的权重是针对每条融合边学习的一个标量相当于给每条路设定一个固定的“音量旋钮”而HS-FPN的筛选是逐通道、逐像素、随输入动态变化的“智能降噪器”它不是一个固定的旋钮而是会根据每一张输入图像自己决定哪些区域该放大、哪些该压下去。用训练角度来解释BiFPN的融合权重在训练结束后就固定了不管你输入什么图像都用同一套比例而HSM生成的筛选权重是根据当前输入特征实时计算出来的是一个动态变化的过程而且它的参数量极小几个全连接和卷积加起来也就增加了很小的计算量。这一点很重要说明它不是简单地把模型变大来换精度而是真的在融合策略上做了优化。PANet最大的问题是路径冗长自底向上的通路虽然缓解了细节丢失但同时把大量背景噪声也传递到了高层而HS-FPN的筛选模块恰恰能在每一级融合前截断噪声的传播路径。所以如果你观察训练过程会发现HS-FPN版本的浅层特征图比PANet版本“干净”得多背景区域在可视化时呈现暗色目标物体区域则保留高响应。我把这三种结构在同一个数据集上做了小规模对比PANet的参数量最大BiFPN其次HS-FPN最小但在小目标子集上的AP反而是HS-FPN最高。这说明什么说明结构简单、机制合适有时候比单纯堆参数更有效。3. 在YOLOv13中替换Neck的完整实操3.1 网络结构定义层面的改动这部分直接上手改代码。YOLOv13的网络结构定义一般集中在模型配置文件和核心组件的Python模块里我习惯用PyTorch版本。替换Neck的思路是保留Backbone输出的三个有效特征层即P3、P4、P5分别对应下采样8倍、16倍、32倍的特征图然后进入HS-FPN单元而不是直接进入默认的PANet结构。HSM模块的结构定义可以参照下面的代码思路实现import torch import torch.nn as nn class HSFilter(nn.Module): def __init__(self, channels, reduction16, level0): super().__init__() hidden max(channels // reduction, 8) self.ch_pool nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), ) self.ch_fc nn.Sequential( nn.Linear(channels, hidden), nn.ReLU(inplaceTrue), nn.Linear(hidden, channels), nn.Sigmoid(), ) self.sp_conv nn.Conv2d(channels, 1, kernel_size7, padding3, biasFalse) self.sp_sig nn.Sigmoid() # 上下文校准分支根据层级编码调节筛选强度 self.level_weight nn.Parameter(torch.ones(1, channels, 1, 1) * (1.0 0.1 * level)) self.ctx_gate nn.Sequential( nn.Conv2d(channels, channels, 1), nn.Sigmoid(), ) def forward(self, x): b, c, h, w x.shape ch_att self.ch_fc(self.ch_pool(x)).view(b, c, 1, 1) sp_att self.sp_sig(self.sp_conv(x)) ctx self.ctx_gate(x) * self.level_weight return x * ch_att * sp_att * ctx在Neck主体中我会先定义三个HSFilter实例分别处理P3、P4、P5三个层面再按自顶向下和自底向上两个方向做融合。自顶向下的时候P5先经过HSFilter筛选再上采样后与P4经过筛选后的特征加权相加得到M4M4进一步上采样与P3筛选特征融合得到M3自底向上时M3经过下采样后与M4融合得到N4N4再与M5融合得到N5最终输出N3、N4、N5三个融合后的特征图送入检测头。这里要特别提醒一个容易出错的地方HSFilter的输出和输入尺寸必须保持一致上采样和下采样操作需要放在筛选模块之外否则会发生形状不匹配。另一个常见的问题是筛选模块的Sigmoid激活会把权重压缩在0到1之间如果初始level_weight设置过小会导致前几次迭代梯度异常建议level_weight的初始值设置在1.0附近再根据训练情况微调。3.2 损失函数的适配与权重分配Neck换掉之后检测头拿到的特征分布会发生变化损失的收敛节奏也会跟着变。YOLOv13本身用的是一套结合分类损失、定位损失和目标性损失的组合我的做法是不改变损失函数的基本公式但会调整几个损失项的权重比例。由于HS-FPN的筛选后特征相比默认Neck会更加稀疏前几个epoch内目标性损失的值会偏低一些这是因为筛选模块把很多背景响应直接压掉了。此时如果仍然沿用原版的权重比例会让模型过早认为“到处都是背景”而放弃学习所以我习惯把目标性损失的权重在训练前三分之一阶段调高20%左右等到筛选模块逐步适应数据特征后再恢复默认权重。一个可行的做法是使用分段衰减的权重策略。比如前100轮迭代obj_loss_weight设为1.2之后每50轮下降0.1直到回到默认值0.7。class_loss和box_loss的权重保持不动。实际测试下来这种预热式的权重策略能够显著提升小目标类别AP的稳定性尤其是那些目标尺度变化非常大的类别。如果你在训练时发现损失曲线出现了明显的锯齿状波动先别急着降低学习率优先检查是不是筛选模块的梯度震荡。HSM中存在多个Sigmoid函数它们的梯度在饱和区接近于零一旦权重初始化不合适很容易导致部分通道直接“死掉”对应的损失项波动非常剧烈。这个时候可以给筛选模块单独设置一个更小的初始学习率或者对Sigmoid输入加一个缩放系数降低进入饱和区的概率。3.3 训练策略和超参调整建议基于我的实践以下这组配置在多个数据上表现都比较稳定输入分辨率640x640 训练轮数300轮 批次大小16 优化器AdamW初始学习率0.001配合余弦退火 权重衰减0.0005 多尺度训练开启尺度范围0.5到1.5 Mosaic增强前100轮开启之后关闭 HSFilter的学习率倍率0.2多尺度训练这里要单独说两句HS-FPN和普通Neck在用多尺度训练时的表现差异很大。普通Neck对多尺度不敏感因为特征融合的规则是固定的而HS-FPN的筛选模块会对不同尺度的输入产生不同的筛选权重这既带来了更强的泛化能力也意味着它对尺度变化更敏感。如果尺度范围设置过大训练早期会让筛选模块学得不够稳定甚至出现“筛选器失效”的现象即所有通道权重都趋近于一个平均值筛选形同虚设。我的建议是第一轮先用固定输入尺寸640x640训练60到100轮让筛选模块初步收敛再开启多尺度训练尺度范围从0.8到1.25小步试探如果损失稳定再逐步扩展到0.5到1.5。这个顺序和多数默认训练流程是相反的默认流程往往一开始就开多尺度但对带筛选机制的Neck来说先固定后动态是更稳妥的路径。4. 实测结果不同场景下的收益对比4.1 测试配置与评价指标为了把收益看清楚我用了三组数据做对比测试一组公开通用检测数据中的小目标子集一组自建的远距离安防监控数据一组工业零件表面的细小瑕疵数据。对比的模型结构有三套YOLOv13默认Neck、YOLOv13PANet增强版、YOLOv13HS-FPN。所有模型使用完全相同的训练策略输入分辨率、训练轮数、优化器配置完全一致避免因为变量控制不到位而搞出假涨点。评价指标上除了整体mAP我格外关注mAP50-95中针对小目标的子项AP_s以及目标的定位精度。定位精度在小目标场景里经常被忽视但很多实际项目里框只要偏了十几个像素目标就算检到了也等于没检到因为后续的追踪或者识别环节根本接不住。4.2 小目标密集场景的涨点效果三组数据的测试结果可以汇总成下面这张表数值是相对原版模型的增量模型结构公开小目标子集AP_s远距离安防AP_s工业瑕疵AP_s整体mAP相对变化YOLOv13默认Neck基准基准基准-YOLOv13 PANet增强0.80.50.90.4YOLOv13 HS-FPN2.63.12.21.3可以看到HS-FPN在小目标子集上的AP_s提升明显高于PANet增强版尤其是在远距离安防监控数据上AP_s涨了3.1个点。这个涨幅的背后是有实际意义的在原始结果里那一批目标是“看不清”的存在模型偶尔能对一两个但在HS-FPN版本里大量更模糊的目标被恢复了特征响应召回率显著提升。更有意思的一点是HS-FPN对中等目标几乎没有副作用。很多改进方法是为小目标加权的副作用是中等目标和大目标掉点因为模型容量被“偏袒”了小目标。但HS-FPN的筛选机制是数据驱动的它学会了在不同任务需求之间动态分配注意力所以整体mAP也在涨不是拆东墙补西墙。4.3 推理速度和显存开销变化精度涨了代价当然也要算清楚。我在一张主流的消费级显卡上做了推理基准测试YOLOv13默认Neck的推理耗时大约是4.8毫秒每帧换成HS-FPN之后是5.4毫秒增加了大概12%。这个开销主要来自HSM内部的两次Sigmoid和一次大卷积核的空间注意力计算对于需要实时处理的应用场景来说是可以接受的。但如果你的设备端算力非常紧张可以考虑把空间筛选分支的7x7卷积换成3x3精度大约掉0.3到0.4个点推理时间可以回到5.1毫秒左右。显存方面由于HS-FPN的筛选模块产生的是逐通道、逐像素的权重图会增加少量中间显存占用。以640x640的输入分辨率为例整体显存占用相比原版模型增加了约8%在我测试的GPU上完全无压力。如果显存吃紧可以把中间筛选权重用in-place方式计算也就是计算完空间注意力后直接覆盖到原特征图上不需要额外交出一张同样尺寸的临时特征图这个优化能把显存增量压到3%以内。5. 踩坑总结与部署经验5.1 筛选阈值对训练稳定性的影响这应该是我踩得最深的坑。HSM里的Sigmoid天然会把权重映射到0到1之间我一开始想得很美觉得只要筛选权重越小能保留的特征就越“精华”于是给空间筛选分支的卷积输出加了一个正的大偏置让初始权重普遍偏低。结果训练到第20轮就出问题了损失值不但不降反而开始剧烈震荡而且分类分支的准确率一直趴在很低水平。后来做了权重直方图可视化才发现前几轮过后大部分通道的筛选权重都被压到了0.05以下等于模型直接把绝大多数特征“筛掉了”只有极少数通道还在传递信息整个检测器等于盲人摸象。这里的问题在于筛选模块刚初始化时根本不理解什么是有用特征如果初始筛选力度过大梯度信号太弱它永远没机会学到正确的筛选标准。正确做法是温和启动。我后面给出的初始化策略是通道筛选分支Sigmoid前不加偏置空间筛选分支的卷积初始权重设小一些让初始筛选权重分布在0.2到0.8之间。训练过程中筛选器会逐渐学会自动调整大目标区域会把权重推向0.9以上背景区域压到0.1以下这个由宽松到严格的过程会让训练稳定很多。5.2 多尺度训练与BN统计量的联动问题如果你复现HS-FPN的时候用了带BN模块的版本而且训练时开了多尺度就有很大概率遇到BN统计量漂移的问题。原因很简单HSM这个筛选调制过程会对输入分布的细微变化非常敏感不同尺度下的特征图分布差异本来就大BN统计量又在不断更新两者叠加会导致某些通道的筛选权重出现“跳变”现象。我自己的处理是把HSM内部的BN层全部去掉改用Layer Normalization或者干脆不用归一化。从实验结果看去掉BN对最终精度的影响微乎其微但训练稳定性直线上升。如果非要在HSM里保留BN那就一定要把多尺度训练的尺度范围控制在0.8到1.25以内并且把BN的momentum调低到0.01减少统计量的大幅波动。5.3 推理部署时的算子兼容处理训练完只是第一步落地部署才是真正考验工程能力的环节。HSM里的空间筛选分支用了7x7卷积这在TensorRT部署时有可能会被拆成多个算子推理时间反而比PyTorch里慢不少。我是这样处理的导出ONNX时把7x7卷积的权重重新参数化拆成3x3加上两条非对称卷积1x7和7x1的组合虽然理论上计算量略增但TensorRT对这组算子有非常好的融合优化实际推理速度反而更快。另一个部署问题是动态尺度支持。如果HSM里用了依赖于输入尺寸的自适应池化那导出的模型就只支持固定输入尺寸换一个分辨率还得重新导出。我在实际项目里直接把全局池化换成固定大小的平均池化然后配合双线性插值把输入统一到固定尺寸这样就绕开了动态尺寸的限制部署起来省了不少事。但注意这一步是部署妥协如果你的使用场景本身就是任意分辨率建议保持动态池化不要为了省事而牺牲灵活性。踩过几次坑之后我对NDK的改进思路也有了更深的理解。之前总觉得改了解析能力强的Backbone就万事大吉但真正让一批小目标从“完全检不到”变成“稳定检出来”的反而是Neck端这套看起来不起眼的筛选融合逻辑。如果手里的模型也是卡在小目标或者细节特征上与其盲目堆Backbone不如把目光移到特征融合这一步先做一个对照组实验看看把Neck换成HS-FPN能带来多少变化。就我个人经验来说这个改动带来的惊喜大概率不会让你失望。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑