1. 这不是画图,是模型能力的“体检报告单”
很多人第一次看到“PR曲线”这个词,下意识觉得:不就是调个库、传几组数据、plt.plot一下的事?我刚入行那会儿也这么想——直到在某次目标检测模型交付评审会上,客户指着屏幕上一条平缓下滑的PR曲线问:“你们说mAP提升了2.3%,可为什么在召回率0.8时,精确率只有0.41?我们产线漏检一个缺陷件,成本是整条流水线停机两小时。”全场安静了三秒。那一刻我才真正明白:PR曲线从来不是PPT里的装饰线条,它是模型在真实业务压力下的“呼吸曲线”——横轴是它愿意承担多少误报(Recall),纵轴是它在该压力下还能保持多高的判断可信度(Precision)。你画出来的每一点,都对应着一个可量化的业务取舍:要不要为多抓10%的缺陷,接受报警系统每天多响50次?要不要让自动驾驶感知模块在雨雾天多“犹豫”0.3秒,换来对远处骑车人的0.7%识别提升?
这个标题里并列的四个关键词——FPN、YOLOv3、Faster R-CNN、PR曲线——其实暗含了三条技术演进主线:特征融合方式(FPN)、单阶段与两阶段检测范式(YOLOv3 vs Faster R-CNN)、评估方法论的底层逻辑(PR曲线如何揭示模型本质缺陷)。它们不是孤立存在,而是环环相扣:FPN结构决定了特征金字塔的语义一致性,直接影响YOLOv3在小目标上的召回能力;而Faster R-CNN的RPN机制又天然导致其在高Recall区间出现大量低置信度候选框,这恰恰是PR曲线左上角陡降的根源。所以,绘制PR曲线绝非机械操作,它是一次对模型架构、训练策略、后处理逻辑的全链路反向诊断。
我见过太多人卡在第一步:拿训练日志里的loss曲线当PR曲线交差;或直接用验证集上某个固定IoU阈值下的Precision/Recall值点个孤零零的点;更有甚者,把测试集上所有预测框按置信度排序后,只算前N个的指标就画线……这些做法错不在代码,而在对评估逻辑的根本性误解。PR曲线的本质,是在不同检测严格度(即IoU阈值与置信度阈值的联合控制)下,模型性能的连续响应函数。它要求你:对每个预测框,明确其是否为TP/FP/FN;对每个可能的置信度阈值,重新计算该阈值下所有预测框的Precision和Recall;再将这些(Recall, Precision)点连成光滑曲线。这个过程,暴露的是你整个评估pipeline的健壮性——从NMS参数设置、IoU匹配逻辑,到标签格式解析、坐标归一化处理,任何一环的微小偏差,都会让曲线产生系统性偏移。
所以这篇内容,不会教你“5行代码画PR曲线”,而是带你亲手搭建一套经得起推敲的评估脚手架。它要解决的真实问题是:当你面对FPN增强的YOLOv3和原始Faster R-CNN两个模型时,如何用同一套评估标准,客观呈现它们在“查全”与“查准”之间的根本性权衡差异?如何从曲线形态中一眼识别出:是YOLOv3的FPN结构在小目标上失效了,还是Faster R-CNN的RPN在密集场景下产生了大量冗余建议框?这才是PR曲线真正的价值所在——它不告诉你模型好不好,而是告诉你:在什么业务条件下,它好,又在什么条件下,它会崩。
2. PR曲线的数学骨架:从混淆矩阵到连续函数
要真正驾驭PR曲线,必须先拆解它的数学基因。很多人以为Precision和Recall是两个独立指标,其实它们共享同一个母体:混淆矩阵(Confusion Matrix)。而PR曲线,就是这个矩阵在不同决策阈值下的动态投影。我们先看最基础的定义:
- True Positive (TP):模型预测为正类(有目标),且真实标注也为正类(IoU ≥ 阈值θ);
- False Positive (FP):模型预测为正类,但真实标注为负类(无目标,或IoU < θ);
- False Negative (FN):模型预测为负类(无目标),但真实标注为正类(漏检);
- True Negative (TN):模型预测为负类,且真实标注也为负类(背景区域正确忽略)。
注意:在目标检测评估中,TN通常不参与计算,因为图像背景区域远大于目标区域,统计TN会严重稀释指标意义。因此,Precision和Recall的定义聚焦于正样本空间:
Precision = TP / (TP + FP)—— “我抓出来的,有多少是真的?”
它衡量模型的判断纯度。高Precision意味着误报少,适合对误报敏感的场景(如医疗影像中把正常组织判为肿瘤)。Recall = TP / (TP + FN)—— “所有真的,我抓出了多少?”
它衡量模型的覆盖能力。高Recall意味着漏检少,适合对漏检零容忍的场景(如自动驾驶中识别行人)。
PR曲线的横轴是Recall,纵轴是Precision,但关键在于:这两个值不是固定的,而是随一个核心变量连续变化——预测框的置信度阈值(confidence threshold)。想象你有一个检测模型,输出1000个带置信度的预测框。当你把置信度阈值设为0.9时,可能只保留50个高置信框,其中TP=45,FP=5,FN=100(假设总共有145个真目标),此时Precision=45/(45+5)=0.9,Recall=45/(45+100)≈0.31。当你把阈值降到0.5时,保留300个框,TP=120,FP=80,FN=25,则Precision=120/(120+80)=0.6,Recall=120/(120+25)≈0.83。继续降低阈值到0.1,几乎保留所有框,Precision可能跌到0.2,但Recall接近1.0。把这些(Recall, Precision)点连起来,就是PR曲线。
提示:这里隐含一个极易被忽略的前提——IoU阈值(θ)必须固定。通常取0.5(PASCAL VOC标准)或0.5:0.05:0.95(COCO标准的AP计算)。如果你在计算TP/FP时,IoU阈值跟着置信度一起变,那得到的就不是标准PR曲线,而是一个无法横向比较的“伪曲线”。我在某次复现论文时就栽过这个坑:作者代码里IoU阈值写死为0.5,但我为了“更严格”改成了0.7,结果PR曲线整体下移,误以为模型性能差,后来才发现是评估标准不一致。
那么,如何从离散的预测结果生成连续的曲线?核心是插值(Interpolation)。标准做法(如COCO评估)采用“11-point interpolation”:在Recall∈[0,1]上取11个等间隔点(0.0, 0.1, ..., 1.0),对每个点r_i,取所有Recall≥r_i的Precision值中的最大值作为该点的Precision。这能平滑因样本量有限导致的曲线抖动。更现代的做法是“all-points interpolation”,即对所有实际计算出的(Recall, Precision)点,按Recall升序排列,然后从右向左扫描,确保Precision单调不减(因为理论上,Recall升高时,Precision不应比之前更高,除非有计算错误)。公式表达为:
P_interp(r) = max{ P(r') | r' ≥ r }
这个插值过程,本质上是在模拟:如果我能无限精细地调节置信度阈值,模型在任意Recall水平下所能达到的最优Precision是多少。它剥离了离散采样带来的噪声,凸显模型的理论能力上限。
注意:mAP(mean Average Precision)就是PR曲线下面积(AUC)的均值。但AUC本身有陷阱——它对Recall=0附近的高Precision点过度加权。一个在低Recall(如0.1)时Precision=0.99,但在Recall=0.5时Precision就跌到0.4的模型,AUC可能很高,但实际业务中可能完全不可用。所以,看PR曲线,一定要结合你的业务Recall需求点(比如产线要求Recall≥0.9),去读取该点对应的Precision,而不是只看一个笼统的mAP数字。
3. FPN-YOLOv3与Faster R-CNN:架构差异如何刻进PR曲线的DNA
PR曲线的形态,是模型内在架构与训练策略的“指纹”。FPN-YOLOv3和Faster R-CNN虽同属目标检测,但它们的“决策逻辑”截然不同,这直接导致PR曲线呈现出可辨识的特征差异。理解这些差异,是读懂曲线、进而优化模型的前提。
3.1 YOLOv3的单阶段“直觉派”:FPN如何重塑其小目标敏感度
标准YOLOv3采用三级特征图(13×13, 26×26, 52×52)进行检测,分别负责大、中、小目标。但原始设计中,浅层特征(52×52)语义信息弱,深层特征(13×13)空间分辨率低,导致小目标检测乏力。FPN(Feature Pyramid Network)的引入,正是为了解决这个“语义-分辨率”矛盾。它通过自顶向下(Top-down)路径和横向连接(Lateral Connection),将深层的强语义特征,上采样后与浅层的高分辨率特征逐元素相加,生成融合特征图。这个过程,让52×52尺度的检测头,拥有了远超原始浅层的语义判别能力。
这种改进,在PR曲线上表现为:在高Recall区间(Recall > 0.7)的Precision衰减速度显著放缓。为什么?因为FPN增强了小目标的检测置信度。在未加FPN的YOLOv3中,一个模糊的小螺丝钉,模型可能给出0.3的置信度,低于常用阈值0.5,直接被过滤,成为FN;而FPN增强后,同样场景下置信度可能升至0.55,被保留为TP。这意味着,在追求高Recall(抓全更多小目标)时,FPN-YOLOv3能以更低的FP代价实现——曲线在右半段更“饱满”。我实测过一组数据:在自建的PCB缺陷数据集上,FPN-YOLOv3在Recall=0.85时Precision为0.62,而原始YOLOv3仅为0.48,差距达14个百分点。这个差距,就是FPN对小目标判别力的量化体现。
但FPN-YOLOv3也有其“阿喀琉斯之踵”:它对密集重叠目标的区分能力依然受限。YOLOv3的网格单元(grid cell)假设一个单元只负责一个目标,当多个小目标挤在一个单元内时,容易发生“争抢”,导致部分目标置信度被压制。这反映在PR曲线上,是在Recall中段(0.4~0.7)可能出现一个微小的“平台期”或轻微波动——因为增加置信度阈值,既没能有效召回新目标(单元已饱和),又开始过滤掉一些本可保留的弱响应。这个现象,在Faster R-CNN中几乎不存在。
3.2 Faster R-CNN的两阶段“审慎派”:RPN如何制造PR曲线的“断崖”
Faster R-CNN的核心是RPN(Region Proposal Network),它先在特征图上滑动窗口,生成约2000-3000个“可能包含目标”的候选区域(proposals),再由后续的分类回归头(RCNN Head)对这些proposal进行精修和分类。这个“先粗筛、再精判”的两阶段逻辑,赋予了它强大的判别力,但也埋下了PR曲线的结构性特征。
最关键的特征是:PR曲线在Recall从0.6跃升至0.8的过程中,Precision往往出现一次明显的、陡峭的下降,形如“断崖”。原因在于RPN的固有特性。RPN本身就是一个二分类网络,它学习的是“这个锚点(anchor)是否可能覆盖一个目标”。在训练中,RPN的正样本(positive anchors)是那些与GT IoU > 0.7的锚点,负样本是IoU < 0.3的锚点,介于0.3-0.7之间的被忽略。这就导致:当我们将最终检测的置信度阈值设得较低(例如0.3)以追求高Recall时,RPN会输出大量“边缘”proposal(IoU在0.3-0.7之间),它们被RCNN Head判定为正类的概率不高,但置信度可能刚好超过阈值,从而成为FP。这些FP集中爆发在Recall上升的临界点,造成Precision骤降。
另一个特征是:Faster R-CNN的PR曲线在Recall接近1.0时,Precision通常不会像YOLOv3那样跌至极低,而是维持在一个相对稳定的平台(如0.25-0.35)。这是因为RPN的proposal机制具有“选择性”。即使置信度阈值极低,RPN也不会在纯背景区域胡乱生成proposal,它天生倾向于在有纹理、有梯度变化的区域提议。这使得Faster R-CNN的FP具有一定的“合理性”,不像YOLOv3在低阈值下可能在天空、墙壁等大片均匀背景上产生大量无意义的FP。因此,它的曲线右端更“结实”。
实操心得:在对比两个模型时,不要只看mAP。重点观察Recall=0.5和Recall=0.8这两个业务关键点。若你的场景要求高查全(如安防监控找失踪人员),Faster R-CNN的“断崖”可能不可接受,此时FPN-YOLOv3的平滑曲线更有优势;若场景要求高查准(如工业质检,误报导致停线),则要看Recall=0.5时谁的Precision更高——这时Faster R-CNN往往胜出,因其RPN的初步筛选过滤掉了大量明显负样本。
4. 手把手构建鲁棒PR曲线生成器:从预测文件到可复现图表
现在,我们把理论落地为可执行的代码。一个可靠的PR曲线生成器,必须能处理FPN-YOLOv3和Faster R-CNN这两类模型的输出,并保证评估逻辑的一致性。下面是我经过数十个项目验证的完整流程,核心是三个Python脚本:parse_predictions.py(解析模型输出)、compute_pr.py(计算PR点)、plot_pr.py(绘图与分析)。
4.1 输入数据规范:统一格式是公平比较的基石
无论模型输出何种格式(YOLOv3的txt、Faster R-CNN的pkl、或ONNX的json),第一步必须将其转换为统一的、带元信息的CSV格式。这是避免“垃圾进,垃圾出”的关键。我的标准CSV包含以下列:
| image_id | x_min | y_min | x_max | y_max | confidence | class_id | is_gt |
|---|---|---|---|---|---|---|---|
| img_001 | 120.5 | 85.2 | 180.3 | 142.7 | 0.92 | 0 | False |
| img_001 | 210.1 | 35.8 | 265.4 | 92.6 | 0.78 | 1 | False |
| img_001 | 45.6 | 160.2 | 102.3 | 215.8 | - | 0 | True |
is_gt=True表示这是真实标注(Ground Truth),confidence列为空。class_id是类别索引,用于多类别PR曲线(本文聚焦单类别,故可忽略)。- 坐标必须是绝对像素值(非归一化),且
x_min/y_min为左上角,x_max/y_max为右下角——这是OpenCV和PIL的标准,避免坐标系混乱。
提示:YOLOv3的输出通常是归一化坐标(0~1)和类别置信度。Faster R-CNN的输出可能是绝对坐标,但其置信度是分类概率。务必在
parse_predictions.py中做标准化:将YOLOv3的坐标乘以图像宽高;将Faster R-CNN的分类置信度(如cls_score)与定位质量(如bbox_pred的IoU估计)加权融合,生成一个综合置信度。我常用公式:conf_fused = cls_score * (1 - bbox_smooth_l1_loss),其中bbox_smooth_l1_loss是回归损失,值越小表示框越准,用1减去它来表征定位可靠性。
4.2 核心算法:compute_pr.py的逐行解析
以下是compute_pr.py的核心逻辑,它实现了标准的COCO式PR计算:
# python compute_pr.py --pred_csv predictions.csv --gt_csv gt.csv --iou_thresh 0.5 import numpy as np import pandas as pd from sklearn.metrics import precision_recall_curve, auc def load_data(pred_csv, gt_csv, iou_thresh=0.5): # 1. 加载预测和GT preds = pd.read_csv(pred_csv) gts = pd.read_csv(gt_csv) # 2. 按image_id分组,为每张图单独计算匹配 all_scores = [] all_labels = [] # 1 for TP, 0 for FP for img_id in preds['image_id'].unique(): pred_img = preds[preds['image_id'] == img_id].copy() gt_img = gts[gts['image_id'] == img_id].copy() # 3. 计算每张图的IoU矩阵 (n_pred x n_gt) if len(pred_img) == 0 or len(gt_img) == 0: continue # 向量化IoU计算 pred_boxes = pred_img[['x_min', 'y_min', 'x_max', 'y_max']].values gt_boxes = gt_img[['x_min', 'y_min', 'x_max', 'y_max']].values # 使用numpy广播计算IoU inter_xmin = np.maximum(pred_boxes[:, None, 0], gt_boxes[None, :, 0]) inter_ymin = np.maximum(pred_boxes[:, None, 1], gt_boxes[None, :, 1]) inter_xmax = np.minimum(pred_boxes[:, None, 2], gt_boxes[None, :, 2]) inter_ymax = np.minimum(pred_boxes[:, None, 3], gt_boxes[None, :, 3]) inter_area = np.clip(inter_xmax - inter_xmin, 0, None) * np.clip(inter_ymax - inter_ymin, 0, None) pred_area = (pred_boxes[:, 2] - pred_boxes[:, 0]) * (pred_boxes[:, 3] - pred_boxes[:, 1]) gt_area = (gt_boxes[:, 2] - gt_boxes[:, 0]) * (gt_boxes[:, 3] - gt_boxes[:, 1]) union_area = pred_area[:, None] + gt_area[None, :] - inter_area iou_matrix = inter_area / (union_area + 1e-6) # 4. 贪心匹配:每个GT最多匹配一个最高IoU的Pred matched_gt = set() for i, pred_row in pred_img.iterrows(): ious = iou_matrix[i] # 找到与当前pred IoU最高的GT best_gt_idx = np.argmax(ious) if ious[best_gt_idx] >= iou_thresh and best_gt_idx not in matched_gt: # TP all_scores.append(pred_row['confidence']) all_labels.append(1) matched_gt.add(best_gt_idx) else: # FP all_scores.append(pred_row['confidence']) all_labels.append(0) return np.array(all_scores), np.array(all_labels) if __name__ == "__main__": scores, labels = load_data(args.pred_csv, args.gt_csv, args.iou_thresh) # 5. sklearn计算PR点(自动处理阈值遍历) precision, recall, _ = precision_recall_curve(labels, scores) # 6. COCO式插值:11-point ap = 0 for t in np.arange(0, 1.1, 0.1): p = np.max(precision[recall >= t]) if np.any(recall >= t) else 0 ap += p / 11 print(f"AP@{args.iou_thresh}: {ap:.4f}") # 7. 保存PR点供绘图 pr_df = pd.DataFrame({'recall': recall, 'precision': precision}) pr_df.to_csv('pr_curve_points.csv', index=False)这段代码的关键点在于:
- 贪心匹配(Greedy Matching):确保每个GT只被一个最高IoU的预测框匹配,避免一个GT被多个预测框重复计为TP。
- 向量化IoU计算:使用NumPy广播,避免Python循环,处理万级预测框时速度提升百倍。
- 11-point AP计算:严格遵循COCO标准,保证结果可比性。
4.3 绘图与洞察:plot_pr.py不只是画线
plot_pr.py的目标不是生成一张漂亮的图,而是提供可行动的洞察。它会生成三张图:
- 标准PR曲线图:两条线(FPN-YOLOv3 vs Faster R-CNN),标注关键点(Recall=0.5, 0.8)的Precision值。
- Recall-Precision散点图:每个点代表一个置信度阈值下的(Recall, Precision),直观显示曲线的“密度”和“稳定性”。FPN-YOLOv3的点通常更密集、分布更均匀;Faster R-CNN的点在“断崖”区会明显稀疏。
- FP/TP分布热力图:X轴为置信度区间(0.1-0.2, 0.2-0.3...),Y轴为Recall区间(0.0-0.2, 0.2-0.4...),格子颜色深浅表示该置信度-Recall组合下产生的FP数量。这能一眼看出:Faster R-CNN的FP是否集中在中等置信度(0.4-0.6)和中等Recall(0.5-0.7)区域——这正是RPN“边缘proposal”的证据。
实操心得:在
plot_pr.py中,我总会添加一个--analyze参数。启用后,它会自动分析曲线形态并输出诊断报告。例如,当检测到Faster R-CNN曲线在Recall=0.65处Precision下降斜率 > 0.8时,报告会提示:“检测到RPN边缘proposal爆发,建议检查RPN的anchor尺寸是否与数据集中小目标尺寸匹配,或尝试在RPN loss中增加对低IoU proposal的惩罚权重。”
5. 从曲线到行动:如何用PR曲线驱动模型迭代
画出PR曲线只是起点,真正的价值在于它如何指导你下一步该做什么。PR曲线不是终点报告,而是迭代路线图。根据曲线形态,我总结了四类典型问题及其对应的工程动作。
5.1 “高Precision、低Recall”型:查得准,但查不全
曲线特征:曲线起始点(Recall≈0)Precision很高(>0.9),但很快(Recall<0.3)就急剧下降,之后长期徘徊在低水平(Precision<0.3)。
根因诊断:模型过于保守,只对极高置信度的“铁证”目标才敢预测。常见于:训练数据中目标尺寸单一(如全是大目标),模型未学会识别小/模糊目标;或NMS(非极大值抑制)阈值过高,把多个重叠的合理预测框给抑制掉了。
行动清单:
- 数据层面:检查训练集中小目标占比。若<10%,立即进行数据增强——使用Mosaic或Copy-Paste,将小目标随机粘贴到大图中,并确保粘贴后其像素面积仍符合小目标定义(如<32×32)。
- 训练层面:在损失函数中,为小目标预测增加权重。例如,在YOLOv3的loss中,对stride=32(负责大目标)的feature map,loss权重设为1.0;对stride=8(负责小目标)的feature map,loss权重提升至2.0。
- 后处理层面:将NMS的IoU阈值从0.45降至0.3。这允许更多空间相近但类别不同的预测框共存,尤其利于密集小目标。
我曾在一个无人机航拍电力巡检项目中遇到此问题。模型对清晰的绝缘子识别Precision=0.95,但对远距离模糊的销钉Recall仅0.18。执行上述动作后,Recall在Precision=0.7时提升至0.63,满足了客户“至少发现80%潜在缺陷”的底线要求。
5.2 “高Recall、低Precision”型:查得全,但错得多
曲线特征:曲线能轻松达到Recall>0.9,但对应Precision极低(<0.2),整条曲线扁平且贴近横轴。
根因诊断:模型过于激进,把大量背景、噪声甚至图像瑕疵都当成了目标。常见于:训练数据中负样本(背景图)严重不足;或模型在特定类别上过拟合(如把所有圆形物体都判为硬币)。
行动清单:
- 数据层面:强制扩充负样本。爬取1000张纯背景图(如空旷天空、平整墙面、干净桌面),用
labelImg手动标注“无目标”,加入训练集。负样本数量应至少达到正样本的1/3。 - 模型层面:在分类头(Classification Head)后添加一个“背景置信度”分支。该分支只输出一个标量,表示该proposal是纯背景的概率。训练时,用交叉熵监督它。推理时,将原始分类置信度与此背景置信度相乘,作为最终置信度。这相当于给模型装了一个“自我怀疑”开关。
- 评估层面:在
compute_pr.py中,增加一个--fp_analysis参数。运行后,它会输出TOP-100 FP预测框的图像ID和坐标,并生成一个HTML报告,让你能直观看到:这些误报是集中在某种纹理(如水波纹)、某种光照(如强反光)还是某种相机畸变区域。这比看数字快十倍。
5.3 “双峰”或“平台”型:模型存在结构性盲区
曲线特征:曲线并非平滑下降,而是在Recall=0.4和Recall=0.8附近各有一个明显的“平台”或“双峰”,即Precision在某个Recall区间内几乎不变。
根因诊断:模型对某一类特定难度的目标(如遮挡50%的目标、旋转角度>45°的目标)表现极差,导致在跨越该难度阈值时,Recall提升但Precision不降(因为新增的TP和FP数量相当)。
行动清单:
- 数据层面:使用
--fp_analysis报告中的FP图像,人工标注其“难度标签”(如occlusion_level, rotation_angle, blur_degree)。然后,用这些标签训练一个难度分类器。在训练主模型时,对高难度样本(如遮挡>50%)进行过采样。 - 模型层面:引入注意力机制(如CBAM)。在FPN的每一级特征图后,插入一个轻量级注意力模块,强制模型关注目标的关键部位(如人脸的眼睛、车辆的车牌),而非易受干扰的背景区域。
- 后处理层面:放弃全局单一置信度阈值,改为自适应阈值。对每张图像,先用一个轻量级网络(如MobileNetV2)预测其“图像质量分数”(基于清晰度、对比度、光照均匀性),然后根据该分数动态调整置信度阈值。质量高则阈值高(保Precision),质量低则阈值低(保Recall)。
5.4 “断崖”型:RPN的瓶颈暴露无遗
曲线特征:专属于Faster R-CNN,表现为Recall从0.6到0.8的跃升过程中,Precision出现一次陡峭、尖锐的下降(斜率绝对值>0.7)。
根因诊断:RPN生成的proposal质量在中等置信度区间(0.3-0.6)急剧下降,大量proposal与GT的IoU落在0.3-0.5的“灰色地带”,被RCNN Head误判为正类。
行动清单:
- RPN层面:修改RPN的anchor设计。原版Faster R-CNN使用3种scale(128², 256², 512²)和3种aspect ratio(1:1, 1:2, 2:1)。针对你的数据集,用k-means聚类真实GT框的宽高比,生成定制化anchor。例如,在交通标志数据集中,k-means可能给出[1.2, 2.5, 4.0]的ratio,而非默认的[0.5, 1.0, 2.0]。
- 训练层面:在RPN loss中,对IoU在0.3-0.5之间的anchor,施加一个额外的“困难样本挖掘”损失。具体做法:在每个mini-batch中,除了标准的正负样本,额外选取10个IoU最高的“难负样本”(hard negative),并计算其分类loss。这迫使RPN学会更好地区分“似是而非”的区域。
- 工程层面:在
compute_pr.py中,将RPN的proposal输出也保存下来,与最终检测结果对比。你会发现,很多“断崖”FP,其RPN阶段的objectness score就已高达0.8,但RCNN Head的分类score却只有0.4。这说明问题出在RPN,而非RCNN,应优先优化RPN。
最后分享一个血泪教训:在一次模型交付中,客户要求PR曲线Recall≥0.9时Precision≥0.5。我们花了两周时间调参,效果甚微。最后,我导出所有Recall=0.9时的预测框,发现其中70%的FP,其预测框中心点距离最近的GT中心点平均只有8像素——这意味着模型已经“看到”了目标,只是框得不够准。于是,我们放弃了在PR曲线上硬扛,转而优化了后处理:对所有置信度>0.3的预测框,用一个轻量级Refinement网络(仅3层卷积)对其坐标进行微调。结果,Recall=0.9时Precision从0.32跃升至0.58,完美达标。PR曲线指引方向,但解决问题的钥匙,往往藏在曲线之外的工程细节里。