当前位置: 首页 > 后端技术 > Python

mAP和mIOU

时间:2023-03-26 16:14:08 Python

meanAveragePrecision(mAP)的网络评估在介绍mAP的概念之前,我们先回顾几个概念:TP:TruePositive,真实类,预测正类作为正类的数量。TN:TrueNegtive,真负类,预测负类为负类的个数。FP:FalsePositive,假阳性类,将阴性类预测为阳性类。FN:FalseNegtive,假负类,将正类预测为负类。positiveegtivetrueTPFPfalseFNTN根据以上内容,我们可以得到准确率(Accuracy)、精确率(precision)、召回率(recall)和F1-score。#所有样本中,正确分类样本的比例Accuracy=(TP+TN)/(TP+TN+FP+FN)#所有预测正样本中,正确预测精度的比例=TP/(TP+FP)#在所有正样本中,预测正样本的比例recall=TP/(TP+FN)#precision和recall的权衡F1=2*precision*recall/(precision+recall)mAP是目标检测中的常用评价标准任务,什么是mAP,为什么要用mAP。在目标检测任务中,需要判断一个预测的边界框是否正确。我们会计算预测边界框和真实框的iou,然后设置一个阈值。如果iou>threshold,那么它被认为是正确的。如果提高iou阈值,准确率会提高,召回率会降低。如果iou降低阈值,召回率会增加,准确率会降低。从这个角度来看,仅仅使用一个阈值来评估网络模型肯定是不够的,那么如何在precision和recall之间取得一个tradeoff呢。由于一个阈值不够,所以使用多个阈值来获得多个精度和召回率。这样就可以得到如下的precision-recall曲线,也称为PR曲线,PR曲线与坐标轴围成的面积为AP。Voc2010之前只要选择Recall取[0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]共11个值,对应11个点,然后计算PR曲线和坐标axis的封闭区域作为AP。在VOC2010及之后的版本中,对于每一个不同的Recall值(包括0和1),需要选择Precision大于等于这些Recall值时的最大值,然后计算PR曲线下的面积作为AP值。每个类别可以得到一条PR曲线,对应一个AP。将所有类别的AP取平均得到mAP。这里计算插值平均AP,还有另一种计算方法。它们的区别可以参考这里。下图显示了原始PR曲线(绿色)和插值后的PR曲线(蓝色虚线)。直接计算原始PR曲线与坐标轴围成的面积比较困难(需要积分计算),但蓝色虚线与坐标轴围成的面积的计算更为方便简单。插值法填充PR曲线的上升部分,保证PR曲线为下降曲线。计算步骤如下,计算每个类别的Precision和Recall。分别为每个类别插入PR曲线。分别计算每个类别插值后PR曲线的面积,得到每个类别的AP。将每个类别的AP取平均得到mAP。mAP计算代码如下:1.首先计算每个类别的TP和FP,得到每个类别的准确率和召回率。defcalc_detection_voc_prec_rec(pred_bboxes,pred_labels,pred_scores,gt_bboxes,gt_labels,gt_difficults=None,iou_thresh=0.5):"""PascalVoc数据集的评估代码,用于计算精度和召回率Args:pred_bboxes(list):预测框的可迭代列表,其中的每个元素都是一个数组。pred_labels(list):预测标签的可迭代列表。pred_scores(list):预测概率的可迭代列表。gt_bboxes(list):groundtruthboxes的可迭代列表。gt_labels(list):可迭代的groundtruthboxlabels列表。gt_difficults(list):groundtruthbox预测难度的可迭代列表。默认为None,表示难度级别低。iou_thresh(float):如果预测框对应于If的iou真实框大于阈值,则认为预测正确。返回:rec(list):数组列表,rec[l]表示第l类的召回率,如果第l类不存在,则设置toNone.pre(list):数组列表,pre[l]表示第l类的准确率,如果第l类不存在,则置为None。"""#将所有列表转换为可迭代对象pred_bboxes=iter(pred_bboxes)pred_labels=iter(pred_labels)pred_scores=iter(pred_scores)gt_bboxes=iter(gt_bboxes)gt_labels=iter(gt_labels)ifgt_difficultsisNone:gt_difficults=itertools.repeat(无)其他:gt=_iteric(gt_difficults)#每个类别级别是真实框的个数n_pos=defaultdict(int)#score=defaultdict(list)#表示每个预测框是否匹配真实框match=defaultdict(list)#pred_bbox,pred_label,pred_score,gt_bbox#gt_label,gt_difficult这6个list的长度是一样的#每次迭代相当于一个batchforpred_bbox,pred_label,pred_score,gt_bbox,gt_label,gt_difficultin\six.moves.zip(pred_bboxes,pred_labels,pred_scores,gt_bboxes,gt_labels,gt_difficults):ifgt_difficultisNone:gt_difficult=np.zeros(gt_bbox.shape[0],dtype=bool)#在np.unique(np.concatenate((pred_label,gt_label)).astype(int)):#取出属于l类的预测框和预测分数=pred_score_l.argsort()[::-1]pred_bbox_l=pred_bbox_l[order]pred_score_l=pred_score_l[order]#取出属于第l类的真实框,默认都是n_pos[l]+=np.logical_not(gt_difficult_l).sum()score[l].extend(pred_score_l)#如果没有预测框iflen(pred_bbox_l)==0:continue#如果真实框的个数为0,则没有匹配iflen(gt_bbox_l)==0:match[l].extend((0,)*pred_bbox_l.shape[0])continuepred_bbox_l[:,2:]+=1gt_bbox_l[:,2:]+=1#计算预测框和真实框的iouiou=bbox_iou(pred_bbox_l,gt_bbox_l)#得到每个预测框的iou最大的真实框的indexgt_index=iou.argmax(axis=1)#iou小于阈值,表示没有真实框对应的预测框,然后将索引设置为-1gt_index[iou.max(axis=1)=0:#如果对应真实框的难度级别高ifgt_difficult_l[gt_idx]:match[l].append(-1)else:#如果真实框box已经被匹配,如果没有则匹配selec[gt_idx]:match[l].append(1)else:match[l].append(0)#将索引gt_idx对应的预测框设置为匹配selec[gt_idx]=Trueelse:match[l].append(0)n_fg_class=max(n_pos.keys())+1prec=[None]*n_fg_classrec=[None]*n_fg_classforlinn_pos.keys():score_l=np.array(score[l])match_l=np.array(match[l],dtype=np.int8)#根据预测的类概率排序order=score_l.argsort()[::-1]match_l=match_l[order]tp=np.cumsum(match_l==1)fp=np.cumsum(match_l==0)#如果fp+tp为0,则Settoprec[l]isnanprec[l]=tp/(fp+tp)#如果n_pos[l]为0,设置rec[l]为None。ifn_pos[l]>0:rec[l]=tp/n_pos[l]返回prec,rec2,根据pre和rec计算每个类别的AP,对每个类别的AP求平均得到mAPdefcalc_detection_voc_ap(prec,rec,use_07_metric=False):"""Args:prec:arraylist.rec:arraylist.Returns:ap(array):每个类别的平均精度,shape->(len(n_fg_class),)"""n_fg_class=len(prec)ap=np.empty(n_fg_class)forlinsix.moves.range(n_fg_class):如果prec[l]为None或rec[l]为None:ap[l]=np.nancontinueifuse_07_metric:#11pointmetricap[l]=0fortinnp.arange(0.,1.1,0.1):ifnp.sum(rec[l]>=t)==0:p=0else:p=np.max(np.nan_to_num(prec[l])[rec[l]>=t])ap[l]+=p/11else:#Interpolation#在开头和结尾插入0确保最终PR曲线递减mpre=np.concatenate(([0],np.nan_to_num(prec[l]),[0]))mrec=np.concatenate(([0],rec[l],[1]))#np.maximum.accumulate沿指定轴,从第二个元素开始,与前一个元素比较,取最大值#从后往前比较,取最大值,填入PR曲线的上升部分#下面的代码等同于#foriinrange(mpre.size-1,0,-1):#mpre[i-1]=np.maximum(mpre[i-1],mpre[i])mpre=np.maximum.accumulate(mpre[::-1])[::-1]#从第二个位置开始,得到和前面一样的不等值的Indicesi=np.where(mrec[1:]!=mrec[:-1])[0]#计算面积ap[l]=np.sum((mrec[i+1]-mrec[i])*mpre[i+1])returnap在Coco数据集的检测任务中,经常会看到一些模型评价指标,比如AP50,AP75等,这里,AP50和AP75分别对应IOU阈值。对应PR曲线在0.5和0.75处计算出的APMeanIntersectionoverUnion(MIoU)miou是语义分割任务中的模型评价标准,miou是对每个类别的iou进行平均后得到的。iou的计算如下图所示,iou=overlap/union。miou计算代码如下:计算混淆矩阵defgen_matrix(gt_mask,pred_mask,class_num):"""gt_mask(ndarray):shape->(height,width),真正的分割图pred_mask(ndarray):shape->(height,width),预测分割结果class_num:类别数,不包括背景"""mask=(gt_mask>=0)&(gt_mask(class_num,class_num),混淆矩阵"""#mIou=np.diag(cf_mtx)/(np.sum(cf_mtx,axis=1)+\np.sum(cf_mtx,axis=0)-np.diag(cf_mtx))#对所有类别取平均值ioumIou=np.nanmean(mIou)returnmIouReferencehttps://github.com/chenyuntc/...https://github.com/dmlc/gluon...https://github.com/jfzhang95/...https://www.cnblogs.com/JZ-Se...