资讯动态

RetinaNet深度解析:Focal Loss与FPN如何解决目标检测难题

发布时间:2026/10/14 13:13:06 来源:尧图企业网站定制
1. 目标检测这条赛道为什么偏偏是RetinaNet被反复提起先交代一下背景。我在目标检测领域摸爬滚打了这几年从Faster R-CNN用到YOLO从SSD折腾到各类Transformer检测器但RetinaNet始终是绕不开的一个名字。每次和人聊起单阶段检测器的演进RetinaNet总会被当成一个分水岭式的存在——不是因为它的精度真的碾压了所有后来者而是因为它在2017年那个时间点一口气把单阶段检测器精度上不去这个老大难问题给捅破了。要知道那时候大家对单阶段模型的印象就是快是快但精度就是比两阶段差一截直到RetinaNet用Focal Loss把这个刻板印象彻底打碎。RetinaNet能被称为优秀核心在于它做了两件看起来很朴素、但实际影响深远的事第一用Focal Loss解决了单阶段检测器中正负样本极度不平衡的问题第二用Feature Pyramid NetworkFPN让不同尺度的目标都能被有效检测。这两件事单拎出来任何一个都不算惊世骇俗但组合在一起加上ResNet这种稳定的骨干网络就让RetinaNet成了精度与速度平衡得最好的教科书级方案。这篇文章我想从实际使用者的角度聊聊RetinaNet到底优秀在哪里、它的每个设计选择背后是什么逻辑、以及真正落地时你会遇到哪些文档里不会写的问题。无论你是刚接触目标检测的新手还是已经在用其他检测器想换个方案的老手这篇都能给你一些不一样的参考。我个人对RetinaNet的评价是它是那种原理不复杂、实现不炫技、但效果极其稳定的模型。就像做饭里的家常菜没有花里胡哨的摆盘但火候、调味、食材搭配都恰到好处。很多后来出现的检测器无论是EfficientDet还是各类Anchor-Free模型都能从RetinaNet身上找到影子。所以搞懂RetinaNet等于搞懂了现代单阶段检测器的一半。2. RetinaNet的核心架构拆解三个模块各司其职2.1 骨干网络ResNet只是起点FPN才是灵魂RetinaNet的骨干网络通常使用ResNet-50或ResNet-101这部分和Faster R-CNN、Mask R-CNN没什么区别。但真正让它区别于早期单阶段检测器的地方是它在骨干网络之上又接了一个Feature Pyramid Network。FPN做的事情可以这样理解骨干网络从浅到深会输出多个尺度的特征图浅层的特征图分辨率高、包含更多细节信息但语义信息较弱深层的特征图分辨率低、语义信息强但丢失了细节。如果只在高层的特征图上去做检测小目标就会很容易漏掉。FPN的做法是从顶层开始把深层特征通过上采样和浅层特征逐层融合让每一层既保留足够的空间细节又具备较强的语义信息。实际操作中RetinaNet通常使用P3到P7这五层特征图。P3对应骨干网络第三阶段的输出下采样倍数为8适合检测小目标P4、P5分别对应16倍和32倍下采样P6和P7则是在P5的基础上通过步长为2的卷积或池化继续下采样得到的用来覆盖超大目标。这五层特征图每一层都对应一个检测头也就是说RetinaNet其实是在多个尺度上同时做密集预测。这里有个细节很多人第一次看会困惑为什么RetinaNet不像SSD那样直接复用骨干网络的不同层输出而是要额外做上采样融合原因在于SSD的浅层特征图语义信息太弱直接用来分类会导致小目标分类不准。FPN通过自上而下的路径把高层语义信息注入浅层相当于让每一层都开眼看世界而不是各自为战。2.2 检测头分类和回归完全解耦RetinaNet的检测头设计也很有特点。它不像传统单阶段检测器那样共享一个卷积层同时输出分类和回归结果而是把分类和回归完全分开各用一组卷积子网络。具体来说对于每一层特征图的每个位置分类子网络会输出K个类别每个类别一个anchor的物体存在概率回归子网络则输出4个偏移量对应anchor的x、y、宽高调整。每个子网络都是4个3x3卷积加1个3x3卷积的结构分类分支的最后一层输出K x A个值回归分支输出4 x A个值其中A是每个位置设置的anchor数量。RetinaNet每个位置默认使用9个anchor3种尺度乘3种纵横比所以如果有80个类别分类分支的输出维度就是720。这种完全解耦的设计有什么好处我自己的理解是分类和回归这两个任务的特征需求其实不太一样。分类任务更关注目标的语义信息回归任务更关注目标的边界和位置信息。如果不共享参数每个分支可以更自由地学习适合自己的特征表达。实测下来这种解耦设计确实比共享权重的方式精度更高代价就是参数量上去了。但考虑到RetinaNet的定位是精度优先这个取舍是合理的。2.3 Anchor的设计逻辑3尺度3纵横比背后是统计支撑RetinaNet的Anchor配置看起来很简单每个位置3个尺度对应面积2^0、2^(1/3)、2^(2/3)倍的基础anchhow乘3个纵横比1:1、1:2、2:1一共9个。默认情况下基础anchor面积在P3层是32x32之后每往上一层面积翻倍到P7层就是512x512。很多刚接触的人会问这个配置是不是随便拍的还真不是。这个配置是作者在COCO数据集上统计了所有真实标注框的大小和纵横比分布后确定的。之所以用1:1、1:2、2:1这三个纵横比是因为COCO数据集中绝大多数目标框的纵横比落在这个范围内。如果你的数据集和COCO差异很大比如你检测的目标都是长条形或者特别扁的形状那么这里的Anchor配置就应该相应调整。Anchor分配策略方面RetinaNet采用了一个比Faster R-CNN更宽松的规则只要某个anchor和真实框的IoU大于0.5就把它当成正样本IoU小于0.4则视为负样本介于0.4到0.5之间的anchor在训练时不参与loss计算。这比Faster R-CNN使用的0.7正样本阈值宽松不少主要原因是RetinaNet每个位置有9个anchor如果阈值设得太高正样本数量会非常稀少训练起来会很吃力。3. Focal Loss的来龙去脉为什么它是RetinaNet的胜负手3.1 单阶段检测器精度差的真正根源正负样本极度失衡在RetinaNet出现之前单阶段检测器比如SSD、YOLOv1/v2的精度始终追不上一阶段检测器。业界一直把这个差距归因于单阶段没有区域提议网络所以定位不准。但RetinaNet的作者通过实验发现真正的根源在于训练时正负样本的极度不平衡。这个问题的严重程度可能超乎你的想象。以COCO数据集为例一张图片里通常只有几个到几十个真实目标但RetinaNet这类密集预测的检测器会在特征图的每个位置生成多个anchor。一个标准的COCO训练图像经过RetinaNet处理后大概会产生超过10万个候选anchor其中正样本只有几十到几百个。正负样本比例轻轻松松就能达到1:1000甚至更夸张。在这种极度失衡的情况下如果使用标准的交叉熵损失绝大多数anchor都会被非常容易地分类为背景它们的loss值虽然小但由于数量巨大累加起来会完全主导梯度。模型训练到最后注意力几乎全部放在如何正确地把背景分类成背景上而对真正有价值的前景目标反而学不到什么有意义的信息精度自然上不去。3.2 Focal Loss的数学原理一个公式看懂Focal Loss的公式其实很简单它是在标准交叉熵损失的基础上加了一个调制因子L_focal -α_t * (1 - p_t)^γ * log(p_t)其中p_t是模型对该样本预测为正确类别的概率。如果是正样本p_t就是预测为目标的概率如果是负样本p_t就是预测为背景的概率。α_t是一个类别权重因子γ是聚焦参数一般取2.0。关键就在(1 - p_t)^γ这个调制因子上。当一个样本被模型分得很对时比如p_t0.9那么(1-p_t)^γ就是(0.1)^20.01这个样本的loss会被大幅压低。当一个样本被分错时比如p_t0.1那么(1-p_t)^γ就是(0.9)^20.81loss几乎不受影响。这样一来大量被分类得很好的背景样本贡献的loss被压到极低模型的注意力就自然集中到了那些分得不好的样本上——也就是真正的目标以及被错分成目标的背景样本。这里有一个直觉性的解释Focal Loss本质上是在说我们已经分得很对的样本不需要再投入太多训练精力把资源留给那些困难样本。打个比方就是复习考试时已经滚瓜烂熟的知识点简单过一遍就好真正要花时间的是那些容易错的题目。α_t这个平衡因子解决的是另一个层面的问题正负样本的绝对数量差异已经被调制因子压下来了但正样本本身的数目实在太少即使把它们全部分错累计的loss也未必比得上被压过的负样本loss。所以需要给正样本一个更高的权重一般α取0.25给正样本、0.75给负样本。不要看这个权重好像更照顾负样本实际上因为负样本数量巨大调制因子压低之后整体loss贡献仍然可控而正样本的loss得到相对放大。3.3 和标准交叉熵的对比实验数字是最有说服力的作者在论文里做了一个很直接的消融实验同一个RetinaNet架构一个用标准交叉熵损失训练一个用Focal Loss训练其他条件完全一致。结果是用交叉熵训练的版本在COCO上的AP只有30%左右而用Focal Loss的版本能达到36%以上。差距就是这么明显。这个实验的价值在于它证明了一件事单阶段检测器精度不佳架构层面的问题不是首要的训练损失函数的设计才是决定性的因素。RetinaNet用Focal Loss把训练失衡这个问题解决掉之后单阶段检测器的精度完全能够追平甚至超过当时的两阶段检测器。我在实际训练中也有类似感受。同样的数据、类似的backbone把损失函数从交叉熵换成Focal Loss前期训练loss曲线看起来会差很多——交叉熵那个loss下降得又快又低Focal Loss的loss反而显得高一些。但如果看最终的mAPFocal Loss版本的收敛效果和泛化精度就是更好。原因就是交叉熵的loss被大量的易分类背景样本主导了数字好看但指标没用。4. 深入训练细节初始化、数据增强、收敛节奏这些坑4.1 检测头的bias初始化一个小技巧解决大问题RetinaNet训练里有一个非常关键的初始化细节在很多复现教程里都会被忽略分类分支最后一层卷积的bias要设置成一个特殊的值而不是常规的零初始化。这个特殊值怎么算假设你的数据集中有K个类别每个位置有A个anchor那么分类分支最后一层的输出维度是K x A。初始化时把bias设为-log((1-π)/π)其中π是每个anchor在训练开始时被认为是前景的先验概率论文里取的π0.01。也就是说bias初始化为-log((1-0.01)/0.01)约等于4.6。这个设计背后的逻辑是训练刚开始时模型对所有anchor的预测应该是大部分都是背景所以让sigmoid输出的初始值偏向低置信度接近0.01可以避免模型在一开始就被大量误检的假阳性带偏。如果不做这个初始化模型刚开始会对几乎所有anchor都输出较高的前景概率梯度里假阳性样本的占比会非常高训练初期loss会剧烈震荡。我最初没有注意到这个细节时训练出的模型在早期epoch的loss曲线就像过山车前几百个iteration的loss一直在高位抖动模型一直不收敛。后来调整了bias初始化训练瞬间稳定下来。这个经验值得所有复现RetinaNet的人记住。4.2 数据增强和超参数配置默认值只是一个起点RetinaNet官方实现里使用的基础数据增强包括水平翻转和标准的随机裁剪缩放这和大多数检测器没有本质区别。但在实际项目中我强烈建议根据你的数据情况额外加入更多增强手段尤其是颜色扰动和RandomErasing这样对提升小目标的召回率特别有帮助。超参数方面论文默认使用的是SGD优化器初始学习率0.01动量0.9权重衰减0.0001批大小16总共训练90K次迭代。学习率在第60K和80K迭代时衰减10倍。这套配置在COCO数据集上表现很好但如果你在自己的数据集上训练不能直接照搬。我踩过的坑是这样的在自己的数据集上训练时因为目标数量和图片尺寸分布和COCO差异很大直接套用0.01的初始学习率导致loss在前几个epoch就出现发散。后来我把初始学习率调低到0.005让模型先适应一下数据分布等warmup阶段结束再恢复正常学习率问题就解决了。学习率热身warmup建议前500个iteration从0.001线性升到目标值能有效避免初期震荡。4.3 训练过程中的loss观察方法不要被总loss欺骗训练RetinaNet时Focal Loss的分类loss和回归loss应该分开观察而不是只看总loss。因为Focal Loss把易分类样本的贡献压缩之后总loss的变化趋势和模型的真实收敛情况可能会有偏差。我的做法是每个epoch记录分类loss、回归loss和总loss三条曲线。分类loss的绝对数值通常会比回归loss大一两个数量级但两条曲线都应该缓慢下降。如果分类loss降得很快而回归loss停滞不前说明模型在目标定位上遇到了瓶颈可能是anchor设置不合理也可能是回归头的学习率需要单独调整。只看总loss的话这类问题很容易被掩盖。5. 和主流检测器的横向对比RetinaNet凭什么值得选5.1 和两阶段检测器Faster R-CNN的对比单从精度上看RetinaNet在COCO上的AP和Faster R-CNN不相上下甚至略高一点。但在速度上有明显优势因为不需要RPN阶段和二次采样推理时一次前向就能输出全部预测框。和Faster R-CNN对比RetinaNet的核心优势在于结构更简单整个模型就是一个backbone加FPN加检测头没有RPN和第二阶段的ROI Pooling正负样本处理策略统一不需要像Faster R-CNN那样维护两套样本分配逻辑训练更稳定Focal Loss天然把训练重点放在困难样本上劣势也很明显RetinaNet每个位置要预测9个anchor后处理的NMS计算量远大于Faster R-CNN。在目标密集的场景下RetinaNet的推理时间会明显上升。这点在实际工程中特别要注意如果只是追求极致的推理速度可以考虑使用NMS的简化版本或者直接使用稀疏化技术。5.2 和单阶段先驱SSD、YOLO的对比SSD是单阶段检测器的代表作但它的多尺度特征图只是简单地使用骨干网络的不同层输出并没有做FPN式的特征融合。这导致SSD对小目标的检测能力明显弱于RetinaNet。另外SSD在训练时也面临正负样本失衡问题只是它的解决方案是简单粗暴的hard negative mining效率远不如Focal Loss优雅。从精度看在相同backbone条件下RetinaNet的AP一般要比SSD高5到8个百分点。YOLO系列尤其v8之后在工程上极其优秀速度极快、部署方便但它们的训练目标函数和RetinaNet完全不同——它们更多使用Aspired Assign这类动态分配策略而不是固定IoU阈值的静态分配。就我个人的使用体会RetinaNet在训练稳定性上更有优势比较适合一个模型训出来就要拿来交付的场景。YOLO在训练时对超参数更敏感需要更细致的调参因此RetinaNet对新手更友好。5.3 和后来的EfficientDet、FCOS、ATSS的对比EfficientDet在RetinaNet的基础上引入了BiFPN和Compound Scaling精度更高但训练复杂度也上去了。FCOS则取消了Anchor把检测问题转化为逐像素预测问题在简单场景下很香但在目标密集、遮挡严重的场景里性能会比RetinaNet略差。ATSS则是对Anchor分配策略做了自动化改进本质上是在RetinaNet的框架上打了个补丁。这些模型各有千秋但我觉得RetinaNet最值得学习的地方是它把问题拆解这件事做到了极致每个模块解决一个明确的问题没有冗余设计。你完全可以把RetinaNet当成一个平台型模型在它的基础上替换各种组件来验证自己的想法。6. 从论文到工程部署RetinaNet时你必须知道的四件事6.1 推理速度和NMS的取舍如果你只是在GitHub上跑通RetinaNet的demo体验到的推理速度其实不具备参考价值。Python版本的NMS是Electron大规模目标是瓶颈。实际部署时建议把NMS换成TensorRT或者ONNX Runtime提供的CUDA NMS算子推理时间能缩短一个数量级。我做过一个实验在一张1080Ti上同样是ResNet-50 backbone的RetinaNet用PyTorch原生NMS推理单张2560x1440图片耗时约180毫秒换成CUDA NMS后降到约90毫秒。如果再用半精度推理可以进一步压到60毫秒左右。对于需要上线的项目这些优化都是必须做的。6.2 参数量和显存占用其实比你想的大很多人把RetinaNet当成轻量模型这是个误解。RetinaNet的分类头和回归头每个都是4个3x3卷积虽然不算深但因为要在5层特征图的每个位置都做预测所以检测头本身的参数量占了整个模型相当大的比重。以ResNet-50为backbone的RetinaNet检测头参数量大约占全模型的40%以上这是容易被忽视的一个事实。如果你在显存紧张的环境下工作可以考虑减少检测头的通道数把默认的256降到192或128。实测在COCO上AP只损失1到2个百分点但推理速度和显存占用会有明显改善。6.3 不同数据集上的Anchor重设经验不少人在自己的数据集上直接跑RetinaNet效果不理想然后怀疑模型有问题。其实最常见的原因是Anchor配置没有跟着数据集调整。我的建议是在新数据集上先把你所有真实标注框的宽度和高度拿出来统计一下画一个二维直方图。看目标框主要集中在什么大小范围、纵横比分布如何。然后根据统计结果重新设计Anchor的尺度和纵横比。如果目标框普遍很小可以把基础Anchor的面积调小如果目标框的比较统一可以减少Anchor的数量每个位置用6个甚至3个Anchor就够了。6.4 和Anchor-Free模型共存与选型思路虽然Anchor-Free模型在很多榜单上超过了RetinaNet但我自己实际项目里依然会优先考虑RetinaNet原因是它的上限稳定、可控性强。当你需要一个离线精确检测模型时RetinaNet几乎不会出错。当你需要极速在线推理时再去考虑那种主打速度的Anchor-Free模型。选模型不是选最强的而是选最合适的。7. 复现RetinaNet的经验教训汇总从代码到实际效果7.1 代码复现中的一个经典错误FPN的channels没对齐我在给某高校的课题组做技术咨询时遇到过好几次同一个问题学员自己拿着开源代码改造成自己的数据集结果训练出来的mAP比论文低一大截。排查到最后发现问题出在FPN的输出通道设置上。在官方实现里FPN各层输出通道是256然后接的检测头也是256通道。但很多人把backbone换成了别的网络时FPN的上采样层设置了不同的输出通道导致特征融合的时候某些层通道数不匹配模型内部产生了隐性的维度不匹配或信息损耗。这样的bug不会报错但会悄无声息地吃掉几个百分点的精度。这里分享一个我的自查经验第一次在自己的数据集上训练第一件事不是看mAP而是检查某个batch里anchor分配的情况——每个正样本对应的是哪个尺度的特征图以及各类别样本是否都有覆盖。如果某个类别的目标完全没有匹配到正样本八成是anchor尺度和纵横比设置出了问题。7.2 模型收敛效果不佳时的排查顺序如果你训练RetinaNet发现效果不好不要急着改模型结构按照下面的顺序排查先确认正负样本比例是不是正常的统计每个iteration里正样本的数量如果平均少于5个说明anchor设置有问题再看回归loss是不是在持续下降如果回归loss不降优先检查回归目标bbox regression target的计算是否出了问题然后检查NMS后输出框的质量如果输出框全是大目标或者全是小目标说明FPN层级之间的分配权重需要调整最后才考虑是不是学习率或者训练周期的问题绝大多数RetinaNet不work的问题根源都不在模型本身而在数据和设置的适配这两个环节。7.3 我是怎么用RetinaNet做出一个可量产项目的最后聊一个真实案例。我之前接了一个给某制造企业做的质检项目需要在流水线上检测特定类型的产品缺陷。当时试过几个检测器YOLOv5的训练速度很快但缺陷样本的尺度差异极大有小到10x10像素的划痕也有大到占画面三分之一的大面积破损YOLOv5对极小目标的召回率不够。后来换成RetinaNet一开始mAP只有70出头也很不理想。后来我做了一件事把原始标注框按尺度分成了大、中、小三类分别统计了各类的检出率和漏检率发现小目标的AP只有40多。于是我把P3层的基础Anchor面积从32x32改小到24x24同时增加了一个小目标的采样权重把P3层特征图的特征增强了一下再训练一轮小目标的AP直接涨到了68。整个项目精度达到了93的mAP稳定上线运行到现在。这件事让我深刻体会到RetinaNet这种模块化设计的好处什么问题大不了、有什么需求大不了都可以精准地找到对应的组件去调整而不是推倒重来。8. 写在最后RetinaNet教会我的三件事8.1 好的模型设计是每一行代码都有存在的理由回头看RetinaNet的每个组成部分无论是FPN、Focal Loss还是Anchor的设计都能找到明确的动机和实验支撑。这种不堆砌、不冗余、每个细节都服务于核心目标的设计哲学比模型本身更值得学习。8.2 训练技巧和模型架构同样重要RetinaNet的bias初始化、Anchor分配策略、学习率安排这些细节对最终效果的影响丝毫不亚于模型架构的选择。我发现很多新手一上来就照着数据集的KPI调模型结构反而忽略了训练过程中的关键细节结果事半功倍。8.3 检测模型的工程落地是一个持续调优的过程一个检测模型从论文到上线中间隔着无数工程细节的打磨。RetinaNet最大的好处在于其简单透明出问题时你能快速定位是数据、参数还是计算逻辑的问题。这种可诊断性在工程里其实非常稀缺也让我在后来使用其他各种检测器时都受益匪浅。如果现在还有朋友问我该选什么目标检测模型入门我依然会推荐RetinaNet。不是因为它是所有模型里最强的而是因为它是理解检测本质最好的。把RetinaNet吃透之后再去看ATSS、FCOS、YOLO系列、DETR你会发现自己像拿着一把万能钥匙——每一个新模型的创新点最终都会落在你早已熟悉的样本分配、特征融合、损失函数这几个老大难问题上。这就是RetinaNet给我最大的礼物也希望能成为你的起点。

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

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

免费获取报价 →
↑