赞
踩
相信刚上手yolov5的小伙伴,对train.py和val.py 中的评价指标很疑惑吧。
今天为大家讲解一下,争取用朴素的语言讲明白。
简单来说是判断这个类分的正确不正确的。那这个判断是不是有4种情况。
(1) 真实值是正确的,预测值也是正确的。称为TP
(2) 真实值是错误的,但是预测值是错误的。称为TN
(3) 真实值是正确的,预测值是错误的。称为FN
(4) 真实值是错误的,预测值是正确的。称为FP
这个也很好和英文名对应,真实值与预测值一致的话就是T,代表True。不一致就是F,代表False。如果预测值为负的,就是N,代表Negative。如果预测值为正,就是P,代表Positive。
这样子站位,横坐标为True,即真实值。纵坐标为Predict,即预测值。当TP和TN越大越好,分类效果越好。
下面几个公式很重要哦(一般来说,positive就是我们预测的指标哦)
(1)召回率:真实值为正确的时候,召回了多少正确预测值也正确的概率。
(2)精确率度:预测与真实一样时,要求的评价指标正确的概率。
(3)准确度:预测值与真实值一致时,占所有样本的概率。
以上三个当然是越高越好。一般yolov5给出的混淆矩阵都是经过归一化 的。看对角线的值越大越好。
我们主要讲解yolov5中有关混淆矩阵的代码
在val.py中,一共三次调用了confusion_matrix函数。
confusion_matrix = ConfusionMatrix(nc=nc)
接着进入ConfusionMatrix类中,构造这个类,并初始化。
- class ConfusionMatrix:
- # Updated version of https://github.com/kaanakan/object_detection_confusion_matrix
- def __init__(self, nc, conf=0.25, iou_thres=0.45):
- self.matrix = np.zeros((nc + 1, nc + 1))
- self.nc = nc # number of classes
- self.conf = conf
- self.iou_thres = iou_thres
- if plots:
- confusion_matrix.process_batch(predn, labelsn)
plots为run 的参数,本为ture。调用ConfusionMatrix类中的process_batch函数。
- def process_batch(self, detections, labels): #detections为预测的框,labels为真实的框
- """
- Return intersection-over-union (Jaccard index) of boxes.
- Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
- Arguments:
- detections (Array[N, 6]), x1, y1, x2, y2, conf, class
- labels (Array[M, 5]), class, x1, y1, x2, y2
- Returns:
- None, updates confusion matrix accordingly
- """
- detections = detections[detections[:, 4] > self.conf] # 如果预测框的之置信度大于设置的置信度,即上面初始化的置信度conf=0.25,时,保留这个预测框
- gt_classes = labels[:, 0].int() # 获取真实框
- detection_classes = detections[:, 5].int() #获取第几类标签
- iou = box_iou(labels[:, 1:], detections[:, :4]) #两者交叉的面积
-
- x = torch.where(iou > self.iou_thres) #相交的面积大于0.45
- if x[0].shape[0]:
- matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy()
- if x[0].shape[0] > 1: #获取最大阈值下的预测框
- matches = matches[matches[:, 2].argsort()[::-1]]
- matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
- matches = matches[matches[:, 2].argsort()[::-1]]
- matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
- else:
- matches = np.zeros((0, 3))
-
- n = matches.shape[0] > 0 # 满足条件的iou是否大于0个 bool
- m0, m1, _ = matches.transpose().astype(np.int16) #m0为真实正样本框的索引值,m1时预测正样本索引值
- for i, gc in enumerate(gt_classes):
- j = m0 == i
- if n and sum(j) == 1: # 简而言之,真实框预测到了。但是不一定是正样本类,为TP+TN
- self.matrix[detection_classes[m1[j]], gc] += 1 # correct
- else:
- self.matrix[self.nc, gc] += 1 # background FP 没预测到,成为了背景板
-
- if n:
- for i, dc in enumerate(detection_classes):
- if not any(m1 == i):
- self.matrix[dc, self.nc] += 1 # background FN 背景板加1
- if plots:
- confusion_matrix.plot(save_dir=save_dir, names=list(names.values()))
- callbacks.run('on_val_end')
这个是显示混淆矩阵的。
- def plot(self, normalize=True, save_dir='', names=()):
- try:
- import seaborn as sn
-
- array = self.matrix / ((self.matrix.sum(0).reshape(1, -1) + 1E-6) if normalize else 1) # normalize columns
- array[array < 0.005] = np.nan # don't annotate (would appear as 0.00)
-
- fig = plt.figure(figsize=(12, 9), tight_layout=True)
- sn.set(font_scale=1.0 if self.nc < 50 else 0.8) # for label size
- labels = (0 < len(names) < 99) and len(names) == self.nc # apply names to ticklabels
- with warnings.catch_warnings():
- warnings.simplefilter('ignore') # suppress empty matrix RuntimeWarning: All-NaN slice encountered
- sn.heatmap(array, annot=self.nc < 30, annot_kws={"size": 8}, cmap='Blues', fmt='.2f', square=True,
- xticklabels=names + ['background FP'] if labels else "auto",
- yticklabels=names + ['background FN'] if labels else "auto").set_facecolor((1, 1, 1))
- fig.axes[0].set_xlabel('True')
- fig.axes[0].set_ylabel('Predicted')
- fig.savefig(Path(save_dir) / 'confusion_matrix.png', dpi=250)
- plt.close()
- except Exception as e:
- print(f'WARNING: ConfusionMatrix plot failure: {e}')
具体代码原理:先归一化后画图,具体代码我们就不看了。
部分代码参考:【YOLOV5-5.x 源码解读】metrics.py_yolov5 metrics.py_满船清梦压星河HK的博客-CSDN博客
当想要同时控制风险 ( recall
) 和成本 ( precision
)怎么办?那就用 F1 Score
。
F1分数与置信度(x轴)之间的关系。F1分数是分类的一个衡量标准,是精确率和召回率的调和平均函数,介于0,1之间。越大越好。
表示准确率与置信度的关系图线,横坐标置信度。
由下图可以看出置信度越高,准确率越高。
PR曲线中的P代表的是precision(精准率),R代表的是recall(召回率),其代表的是精准率与召回率的关系。
其中越接近(1,1)效果越好。
召回率与置信度之间关系。
为什么置信度为0的时候,召回率一般不为1。
因为看召回率的公式,分母还有一个FN,还有可能判断为负样本。
置信程度越大,样本越接近真实目标,所以越接近(1,1)越好。
AP(average precision ):平均精度。虽然叫平均精度,但是代表着对一个类精度的判断。而且也不是对precision求平均,是计算PR图中PR线与坐标轴的面积。如果一个模型的AP越大,也就是说PR曲线与坐标轴围成的面积越大,Precision与Recall在整体上也相对较高。
MAP(mean of average precision):对所有类别的AP求平均值。AP反映的是对一个精度的判断,MPA反映对所有类精度的判断,当然越接近1越好啦。平时我们说的,某一目标检测算法的准确率达到了多少,这个准确率就泛指mAP。
想要解释后面两个就不得不提到IOU了。
iou:是交叉比,预测时预测框和真实标签框的交集比上并集。iou值越大说明预测的越准确。取值在【0,1】之间。
下面这个图可以形象化展示怎么计算的。
MAP@0.5:就是说mPA在iou>=0.5时计算的。也就是说,iou小于0.5时认为是负样本,不能判断出这个类别。大于0.5时才能画框展示出来类别。自然也是越大越好了。
mAP@[0.5:0.95]:是多个IOU阈值下的mAP,会在区间[0.5,0.95]内,以0.05为步长,取10个IOU阈值,分别计算这10个IOU阈值下的mAP,再取平均值。mAP@[0.5:0.95]越大,表示预测框越精准,因为它取到了更多IOU阈值大的情况。
部分参考:YOLO 模型的评估指标——IOU、Precision、Recall、F1-score、mAP_yolo评价指标_G.E.N.的博客-CSDN博客
在val.py中先初始化这几个参数。
- s = ('%20s' + '%11s' * 6) % ('Class', 'Images', 'Labels', 'P', 'R', 'mAP@.5', 'mAP@.5:.95')
- dt, p, r, f1, mp, mr, map50, map = [0.0, 0.0, 0.0], 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 #初始化
然后后面使用:
- if len(stats) and stats[0].any():
- p, r, ap, f1, ap_class = ap_per_class(*stats, plot=plots, save_dir=save_dir, names=names) #Plot precision-recall curve at mAP@0.5
- ap50, ap = ap[:, 0], ap.mean(1) # AP@0.5, AP@0.5:0.95
- mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
- nt = np.bincount(stats[3].astype(np.int64), minlength=nc)
我们进到ap_per_class函数中看看:
- #tp:整个数据集所有图片中所有预测框在每一个iou条件下(0.5~0.95)10个是否是TP
- # conf:整个数据集所有图片的所有预测框的置信度
- # pred_cls:预测类别
- # plot:是否演示
- def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, save_dir='.', names=()):
- """ Compute the average precision, given the recall and precision curves.
- Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
- # Arguments
- tp: True positives (nparray, nx1 or nx10).
- conf: Objectness value from 0-1 (nparray).
- pred_cls: Predicted object classes (nparray).
- target_cls: True object classes (nparray).
- plot: Plot precision-recall curve at mAP@0.5
- save_dir: Plot save directory
- # Returns
- The average precision as computed in py-faster-rcnn.
- """
- # 计算mAP 需要将tp按照conf降序排列
- # Sort by objectness 按conf从大到小排序 返回数据对应的索引
- i = np.argsort(-conf)
- tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]
- #一个shape为(n, 10)的的数组,其中n是测试集检测出的所有物体总和,10表示的是该物体在(0.5,0.05,0.95)
- # Find unique classes 对类别去重, 因为计算ap是对每类进行
- unique_classes = np.unique(target_cls) #target_cls统计类别和数量
- nc = unique_classes.shape[0] # number of classes, number of detections
-
- # Create Precision-Recall curve and compute AP for each class
- px, py = np.linspace(0, 1, 1000), [] # for plotting
- 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 = pred_cls == c # i: 记录着所有预测框是否是c类别框 是c类对应位置为True, 否则为False
- n_l = (target_cls == c).sum() # number of labels 召回率 真实的样本数量
- n_p = i.sum() # number of predictions 准确率,计算框有多少个
-
- if n_p == 0 or n_l == 0:
- continue
- else:
- # Accumulate FPs and TPs
- fpc = (1 - tp[i]).cumsum(0)
- tpc = tp[i].cumsum(0)
-
- # Recall
- recall = tpc / (n_l + 1e-16) # recall curve
- r[ci] = np.interp(-px, -conf[i], recall[:, 0], left=0) # negative x, xp because xp decreases
-
- # Precision
- precision = tpc / (tpc + fpc) # precision curve
- p[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个)下的mAP
- for j in range(tp.shape[1]):
- ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j])
- if plot and j == 0:
- py.append(np.interp(px, mrec, mpre)) # precision at mAP@0.5
-
- # Compute F1 (harmonic mean of precision and recall)
- f1 = 2 * p * r / (p + r + 1e-16)
- names = [v for k, v in names.items() if k in unique_classes] # list: only classes that have data
- names = {i: v for i, v in enumerate(names)} # to dict
- 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')
-
- i = f1.mean(0).argmax() # max F1 index
- return p[:, i], r[:, i], ap, f1[:, i], unique_classes.astype('int32')
train.py这样写的。
- fi = fitness(np.array(results).reshape(1, -1)) # weighted combination of [P, R, mAP@.5, mAP@.5-.95]
- if fi > best_fitness:
- best_fitness = fi
- log_vals = list(mloss) + list(results) + lr
- callbacks.run('on_fit_epoch_end', log_vals, epoch, best_fitness, fi)
只要有这个评价指标比最好的权重的评价指标的,就更新最好权重。
主要是在fitness函数中,综合评价这个指标的。
- def fitness(x):
- # Model fitness as a weighted combination of metrics
- w = [0.0, 0.0, 0.1, 0.9] # weights for [P, R, mAP@0.5, mAP@0.5:0.95]
- return (x[:, :4] * w).sum(1)
w 就是这几个权重的占比。
到这就把评价指标差不多将完了。欢迎收看专栏其他作品。
专栏指路:
YOLOv5评价指标:yolov5 评价指标_yolov5评价指标-CSDN博客
YOLOv5网络结构:yolov5 网络结构_yolov5头部网络-CSDN博客
YOLOv5主要流程:yolov5 主要流程_yolov5网络流程-CSDN博客
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。