YOLOv5 中 metrics.py 之 ap_per_class compute_ap 学习记录
目录
参数部分:
tp(correct): shape=[25268, 10] bool
conf: shape=[25268]
pred_cls: shape=[25268]
target_cls: shape=[929]
unique_classes: shape=[71]
n_l && n_p: shape=254 && shape=4488
fpc: shape=[4488,10]
tpc: shape=[4488,10]
recall: shape=[4488,10]
precision: shape=[4488,10]
mpre: shape=[4490]
mrec: shape=[4490]
ap: shape=[71,10]
p: shape=[71,1000]
R: shape=[71,1000]
代码部分:
reference:
整整花了一天的时间在看,原本准备2小时搞懂(太天真了)
参数部分:
tp(correct): shape=[25268, 10] bool
含义:整个数据集所有图片中所有预测框在每一个iou条件下(0.5~0.95)10个是否是TP
old_view:
这里的tp中的预测框(每行)是按图片为单位进行存储的(比如说前300行属于第一张图片,300行到500行属于第二张图片)。
new_view:
这里利用np.argsort对conf进行排序(从大到小) 得到新的tp。
conf: shape=[25268]
含义:整个数据集所有图片的所有预测框的conf
old_view:
同理这里也是按图片为单位进行存储的。
new_view:
这是经过排序后的conf。
pred_cls: shape=[25268]
含义:整个数据集所有图片的所有预测框的类别
old_view:
同理这里也是按图片为单位进行存储的。
new_view:
同理,这是经过conf排序后的类别。
注意:这里的tp、conf、pred_cls是一一对应的
target_cls: shape=[929]
含义:整个数据集所有图片的所有gt框的类别
view:
unique_classes: shape=[71]
含义:整个数据集中一共包含了71个类别(CoCo128中)
view:
n_l && n_p: shape=254 && shape=4488
含义:整个数据集gt框中c类别的框数目;所有预测框中c类别的框数目
view:
fpc: shape=[4488,10]
含义:匹配tp中类别为c 顺序按置信度从大到小排列 截至到每一个预测框的各个iou阈值下FP个数 最后一行表示c类在该iou阈值下所有FP个数。
view:
由于使用np.cumsum(),所以对每一行进行累加操作,又由于conf从大到小排序,导致经过最后置信度低的预测框fp的个数基本都会增加。使得view呈现从左到右依次增加,从上到下依次增加的趋势。
tpc: shape=[4488,10]
含义:匹配tp中类别为c 顺序按置信度排列,截至到每一个预测框的各个iou阈值下TP个数 最后一行表示c类在该iou阈值下所有TP数。
view:
随着iou阈值的变大(同一行),对于同一个预测框检测的tp是逐步减少,又由于随着检测框的增多(同一列),检测到的tp会不断增多,使得view呈现从左到右依次减小,从上到下依次增加的趋势。
recall: shape=[4488,10]
含义:类别为c,顺序按置信度排列,截至每一个预测框的各个iou阈值下的召回率。
view:
就是将前面的tpc除以n_l(number of labels)。
precision: shape=[4488,10]
含义:类别为c,顺序按置信度排列,截至每一个预测框的各个iou阈值下的精确率
view:
precision = tpc / (tpc + fpc)
mpre: shape=[4490]
含义:该参数通过np.concatenate 返回 [开头 + 输入precision(排序后) + 末尾]
view:
mpre添加了开头和末尾从4488变为了4490,它取自于precision的c类别下的某一iou列,所以shape为[4490],这里原本经过np.concatenate会变为行向量,但后面经过排序又变为了列向量,这就是计算ap是的y坐标。
mrec: shape=[4490]
含义:该参数通过np.concatenate返回 开头 + 输入recall + 末尾
view:
同理mrec通过添加开头和末尾从4488变为了4490,它取自recall的c类别下的某一iou列,所以shape为[4490],经过np.concatenate变为了行向量,用于计算ap的x坐标。
ap: shape=[71,10]
含义:表示某类别在某个iou下的AP(指的是以mrec为X轴,mpre为Y轴,围成曲线的面积,也是PR图)
view:
shape[71,10]表示71个类别以及各iou(0.5-0.95总共10个),ap越大越好。
p: shape=[71,1000]
含义:指的是所有类别, 横坐标为conf(值为px=[0, 1, 1000] 0~1 1000个点)对应的precision值
view:
每一行代表每一个类别,利用np.interp取得在(0-1)之间1000的插值,用于绘制P-Confidence(注意,这里的Map取值可以手动设置,默认是Map=0.5)
R: shape=[71,1000]
含义:指的是所有类别, 横坐标为conf(值为px=[0, 1, 1000] 0~1 1000个点)对应的recall值
view:
每一行代表每一个类别,利用np.interp取得在(0-1)之间1000的插值,用于绘制P-Confidence(注意,这里的Map取值可以手动设置,默认是Map=0.5)
代码部分:
def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, save_dir='.', names=()):"""用于val.py中计算每个类的mAP计算每一个类的AP指标(average precision)还可以 绘制P-R曲线mAP基本概念: https://www.bilibili.com/video/BV1ez4y1X7g2Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.:params tp(correct): [pred_sum, 10]=[1905, 10] bool 整个数据集所有图片中所有预测框在每一个iou条件下(0.5~0.95)10个是否是TP:params conf: [img_sum]=[1905] 整个数据集所有图片的所有预测框的conf:params pred_cls: [img_sum]=[1905] 整个数据集所有图片的所有预测框的类别这里的tp、conf、pred_cls是一一对应的:params target_cls: [gt_sum]=[929] 整个数据集所有图片的所有gt框的class:params plot: bool:params save_dir: runs\train\exp30:params names: dict{key(class_index):value(class_name)} 获取数据集所有类别的index和对应类名:return p[:, i]: [nc] 最大平均f1时每个类别的precision:return r[:, i]: [nc] 最大平均f1时每个类别的recall:return ap: [71, 10] 数据集每个类别在10个iou阈值下的mAP:return f1[:, i]: [nc] 最大平均f1时每个类别的f1:return unique_classes.astype('int32'): [nc] 返回数据集中所有的类别index"""# Sort by objectness# np.argsort(将x中的元素从小到大排列,提取其对应的index(索引),这里添加负号其实是从大到小)# 计算mAP 需要将tp按照conf降序排列i = np.argsort(-conf)# 得到重新排序后对应的 tp, conf, pre_clstp, conf, pred_cls = tp[i], conf[i], pred_cls[i]# Find unique classes 对类别去重, 因为计算ap是对每类进行unique_classes = np.unique(target_cls)nc = unique_classes.shape[0] # number of classes, number of detections# Create Precision-Recall curve and compute AP for each class# px: [0, 1] 中间间隔1000个点 x坐标(用于绘制P-Conf、R-Conf、F1-Conf)# py: y坐标[] 用于绘制IOU=0.5时的PR曲线px, py = np.linspace(0, 1, 1000), [] # for plotting# 初始化 对每一个类别在每一个IOU阈值下 计算AP P R ap=[nc, 10] p=[nc, 1000] r=[nc, 1000]ap, p, r = np.zeros((nc, tp.shape[1])), np.zeros((nc, 1000)), np.zeros((nc, 1000))for ci, c in enumerate(unique_classes):# i: 记录着所有预测框是否是c类别框 是c类对应位置为True, 否则为Falsei = pred_cls == c# n_l: gt框中的c类别框数量n_l = (target_cls == c).sum()# n_p: 预测框中c类别的框数量# number of labelsn_p = i.sum() # number of predictions# 如果没有预测到 或者 ground truth没有标注 则略过类别cif n_p == 0 or n_l == 0:continueelse:# Accumulate FPs(False Positive) and TPs(Ture Positive) FP + TP = all_detections# tp[i] 可以根据i中的的True/False觉定是否删除这个数 所有tp中属于类c的预测框# 如: tp=[0,1,0,1] i=[True,False,False,True] b=tp[i] => b=[0,1]# a.cumsum(0) 会按照对象进行列方向累加操作# 一维按行累加如: a=[0,1,0,1] b = a.cumsum(0) => b=[0,1,1,2] 而二维则按列累加# fpc: 类别为c 顺序按置信度排列 截至到每一个预测框的各个iou阈值下FP个数 最后一行表示c类在该iou阈值下所有FP数# tpc: 类别为c 顺序按置信度排列 截至到每一个预测框的各个iou阈值下TP个数 最后一行表示c类在该iou阈值下所有TP数fpc = (1 - tp[i]).cumsum(0)tpc = tp[i].cumsum(0)# Recall=TP/(TP+FN) 加一个1e-16的目的是防止分母为0# n_l=TP+FN=num_gt: c类的gt个数=预测是c类而且预测正确+预测不是c类但是其实属于c类# recall: 类别为c 顺序按置信度排列 截至每一个预测框的各个iou阈值下的召回率recall = tpc / (n_l + 1e-16) # recall curve# 返回所有类别, 横坐标为conf(值为px=[0, 1, 1000] 0~1 1000个点)对应的recall值 r=[nc, 1000] 每一行从大到小# 这里 recall[:, 0] 指的是iou=0.5时的R-Confidencer[ci] = np.interp(-px, -conf[i], recall[:, 0], left=0) # negative x, xp because xp decreases# print(-conf[i])# print(-px)# Precision=TP/(TP+FP)# precision: 类别为c 顺序按置信度排列 截至每一个预测框的各个iou阈值下的精确率precision = tpc / (tpc + fpc) # precision curve# 返回所有类别, 横坐标为conf(值为px=[0, 1, 1000] 0~1 1000个点)对应的precision值 p=[nc, 1000]# 总体上是从小到大 但是细节上有点起伏 如: 0.91503 0.91558 0.90968 0.91026 0.90446 0.90506# 这里 precision[:, 0] 指的是iou=0.5时的P-Confidencep[ci] = np.interp(-px, -conf[i], precision[:, 0], left=1) # p at pr_score# AP from recall-precision curve# 对c类别, 分别计算每一个iou阈值(0.5~0.95 10个)下的mAPfor j in range(tp.shape[1]):# 这里执行10次计算ci这个类别在所有mAP阈值下的平均mAP ap[nc, 10]ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j])if plot and j == 0:# py: 用于绘制每一个类别IOU=0.5时的PR曲线py.append(np.interp(px, mrec, mpre)) # precision at mAP@0.5# 计算F1分数 P和R的调和平均值 综合评价指标# 我们希望的是P和R两个越大越好, 但是P和R常常是两个冲突的变量, 经常是P越大R越小, 或者R越大P越小 所以我们引入F1综合指标# 不同任务的重点不一样, 有些任务希望P越大越好, 有些任务希望R越大越好, 有些任务希望两者都大, 这时候就看F1这个综合指标了# 返回所有类别, 横坐标为conf(值为px=[0, 1, 1000] 0~1 1000个点)对应的f1值 f1=[nc, 1000]f1 = 2 * p * r / (p + r + 1e-16)# 画各种曲线图if plot:plot_pr_curve(px, py, ap, Path(save_dir) / 'PR_curve.png', names)plot_mc_curve(px, f1, Path(save_dir) / 'F1_curve.png', names, ylabel='F1')plot_mc_curve(px, p, Path(save_dir) / 'P_curve.png', names, ylabel='Precision')plot_mc_curve(px, r, Path(save_dir) / 'R_curve.png', names, ylabel='Recall')# f1=[nc, 1000] f1.mean(0)=[1000]求出所有类别在x轴每个conf点上的平均f1# .argmax(): 求出每个点平均f1中最大的f1对应conf点的indexi = f1.mean(0).argmax() # max F1 indexreturn p[:, i], r[:, i], ap, f1[:, i], unique_classes.astype('int32')def compute_ap(recall, precision):"""用于ap_per_class函数中计算某个类别在某个iou阈值下的mAPCompute the average precision, given the recall and precision curves:params recall: (list) [1635] 在某个iou阈值下某个类别所有的预测框的recall 一直从小到大(每个预测框的recall都是截至到这个预测框为止的总recall):params precision: (list) [1635] 在某个iou阈值下某个类别所有的预测框的precision总体上是从大到小 但是细节上有点起伏 如: 0.91503 0.91558 0.90968 0.91026 0.90446 0.90506(每个预测框的precision都是截至到这个预测框为止的总precision):return ap: Average precision 返回某类别在某个iou下的mAP(均值) [1]:return mpre: precision curve [1637] 返回 开头 + 输入precision(排序后) + 末尾:return mrec: recall curve [1637] 返回 开头 + 输入recall + 末尾"""# 在开头和末尾添加保护值 防止全零的情况出现 value Append sentinel values to beginning and end# np.concatenate 默认是按行进行拼接(0表示行,1表示列) 这里由列向量变成了行向量# https://blog.csdn.net/qq_38150441/article/details/80488800mrec = np.concatenate(([0.], recall, [recall[-1] + 0.01]))mpre = np.concatenate(([1.], precision, [0.]))# Compute the precision envelope np.flip翻转顺序# np.flip(mpre): 把一维数组每个元素的顺序进行翻转 第一个翻转成为最后一个# np.maximum.accumulate(np.flip(mpre)): 计算数组(或数组的特定轴)的累积最大值 令mpre是单调的 从小到大# np.flip(np.maximum.accumulate(np.flip(mpre))): 从大到小# 到这大概看明白了这步的目的: 要保证mpre是从大到小单调的(左右可以相同)# 我觉得这样可能是为了更好计算mAP 因为如果一直起起伏伏太难算了(x间隔很小就是一个矩形)# 而且这样做误差也不会很大 两个之间的数都是间隔很小的# 这里又从行向量变为列向量mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))# Integrate area under curvemethod = 'interp' # methods: 'continuous', 'interp'if method == 'interp':# 用一些典型的间断点来计算APx = np.linspace(0, 1, 101) # 101-point interp (COCO)# np.trapz(list,list) 计算两个list对应点与点之间四边形的面积 以定积分形式估算AP 第一个参数是y 第二个参数是x# 这里画个图就很清楚了(定积分思想)ap = np.trapz(np.interp(x, mrec, mpre), x) # integrateelse: # 'continuous'# 采用连续的方法计算AP# 通过错位的方式 判断哪个点当前位置到下一个位置值发生改变 并通过!=判断 返回一个布尔数组i = np.where(mrec[1:] != mrec[:-1])[0] # points where x axis (recall) changes# 值改变了就求出当前矩阵的面积 值没变就说明当前矩阵和下一个矩阵的高相等所有可以合并计算ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curvereturn ap, mpre, mrec
代码部分注释的很详细了,还有不懂得可以自行百度。
reference:
【YOLOV5-5.x 源码解读】metrics.py_满船清梦压星河HK的博客-CSDN博客_metrics.py
np.trapz(): np.trapz()的生动解释_鲁凡 Fan Lu的博客-CSDN博客_numpy trapz
np.interp(): numpy.interp()用法_MrLittleDog的博客-CSDN博客_np.interp
np.cumsum(): numpy.cumsum — NumPy v1.22 Manual
np.maximum.accumulate(): 【python numpy】a.cumsum()、np.interp()、np.maximum.accumulate()、np.trapz()_满船清梦压星河HK的博客-CSDN博客_np.maximum.accumulate用法
YOLOv5 中 metrics.py 之 ap_per_class compute_ap 学习记录相关推荐
- 12月29日--Java中有关类与对象的学习记录
1.12月29日第一课记录 Java中有关类与对象的学习记录 一.基本概念部分 1.类:具有相同.相似的属性.特征.行为方式以及功能的一类事物的总称 (举例:一类用户,如淘宝用户) 类是对象的模板 是 ...
- YOLOv5中autoanchor.py的def metric(k)的r = wh[:, None] / k[None]的理解
check_anchors()内metric()函数 def check_anchors(dataset, model, thr=4.0, imgsz=640):"""在 ...
- Python中ASCII转十六进制、C中BCD转十进制、十六进制学习记录
ASCII.BCD转十六进制 ASCII转十六进制 转换规则 Python实现 BCD转十进制.十六进制 BCD码的优点 BCD码分类 各种BCD码的特点 转换规则 ASCII转十六进制 转换规则 A ...
- 生存战争-中阶模拟量电路板视频学习记录
原视频地址 侵删 一. 生存战争电路教学:SR锁存器 SR锁存器(SR板)又称"置位/复位触发器"(Set/Reset).它具有两个稳定状态,分别为0和1. 如果没用外加触发信号, ...
- python 0x0101_Python中ASCII转十六进制、C中BCD转十进制、十六进制学习记录
ASCII.BCD转十六进制 ASCII转十六进制转换规则Python实现 BCD转十进制.十六进制BCD码的优点BCD码分类各种BCD码的特点转换规则 ASCII转十六进制 转换规则 ASCII(A ...
- 解决 YOLOV5中SyntaxError: Non-ASCII character ‘\xf0‘ in file detect.py
SyntaxError: Non-ASCII character '\xf0' in file detect.py 出现这种原因并不是编码的问题.(YOLOV5中) 首先说为什么不是编码的问题,由于我 ...
- YOLOv5的Tricks | 【Trick14】YOLOv5的val.py脚本的解析
如有问题,恳请指出. 这篇可能是这个系列最后的一篇了,最后把yolov5的验证过程大致的再介绍介绍,基本上把yolov5的全部内容就稍微过了一遍了,也是我自己对这个项目学习的结束.(补充一下,这里我介 ...
- 【YOLOV5-5.x 源码解读】metrics.py
目录 前言 0.导入需要的包 1.fitness 2.ap_per_class.compute_ap 2.1.ap_per_class 2.2.compute_ap 3.ConfusionMatrix ...
- python导入py文件-Python导入其他文件中的.py文件 即模块
python中__init__.py文件的作用 问题 在执行models.py时,报ImportError:No module named transwarp.db的错误,但明明transwarp下就 ...
最新文章
- mysql 8.14 rpm安装_centos8 安装 mysql8
- MSSQL2000 数据库文件迁移到 MSSQL2005 可能要用的一些命令
- IOS开发笔记(Swift):UITableView表格视图的静态使用
- python 读取txt
- Python 执行代码的两种方式
- qt制作一个画板_如何直接用Sketch制作动画|Sketch插件|
- 小米第二款5G手机是小米9?升级版小米9 配置强悍!
- Esper事件处理引擎 EPL语法-模式匹配
- 挂载本地目录到Virtualbox并解决[mounting failed with the error: Protocol error]错误
- lammps教程:nve/nvt/npt系综设置方法
- 关于Oracle性能分析中 自动工作量资料档案库(AWR)的管理(Oracle10个/11g的新特点) 文平...
- 悬浮窗一个怎么够?微信新版本满足你的一心多用
- 指纹识别属于计算机技术,指纹识别技术属于人工智能吗 指纹识别技术什么时候发明的-与非网...
- 姿态估计之2D人体姿态估计 - CPN(Cascaded Pyramid Network for Multi-Person Pose Estimation)
- 用Unity3D开发一个题库系统
- memwatch的使用(一)
- ❤️连续面试失败后,我总结了57道面试真题❤️,如果时光可以倒流...(附答案,建议收藏)
- 随性随笔_201606
- 28:Maximum sum
- restful 简单理解