天天看点

map平均准确率_mAP

最近在处理检测问题的时候,需要对网络参数进行评估,之前对mAP的详细细节没有深入了解,这里记录一下

-----------------------

准确率(accuracy),精确率(precision),召回率(recall)

MeanAverage Precision(平均精确率),即mAP

Precision=TP/(TP+FP)

 TP(检测为P, 实际为p ,检测正确,P->P)  TruePostive

 FP(检测为P,实际为N,检测错误,N->P)   False Postive

即Precision 为检测认为是P的样本里,真实为P的概率,即检测结果哩的真实率  

Recall =TP/(TP+FN)   

 TP(检测为P,检测正确,实际为正,P->P) True Postive

 FN(检测为N,检测错误,说明实际为正,被检测为负,即漏掉了P->N) False Negive

即Recall 为检测认为是P的样本哩占全部真实为P的概率,即反映了查全率

假如原图中有10张人脸,检测到12张人脸,其中正确的人脸为8张,错误的为4张,则漏检的2张。

TP= 8, FP=4, Precision=66.7%  期望Precision越高越好,即错检测的尽可能少

TP=8,  FN=2, Recall =80% 即检测的查全率为80% 希望越多越好。

Precision:根据上述方法,对于某个类别A,我们先计算每张图片中A类别TP和FP的数量,并遍历所有图像对其进行累加,即可得到类别A在整个数据集中TP和FP的数量,计算TP/(TP+FP)即可得到类别A的Precision (计算Precision的时候只需要用到TP和FP),但是会发现Precision的数值是受模型预测出的边界框的数量(上述计算式的分母部分,即我们认为的检测出的Postive的样本的数量)影响的,如果我们控制模型输出预测框的数量,就可以得到不同的Precision,所以我们可以设置不同的score阈值,最终得到不同数量的TP和FP。

Recall:对于某个类别A,按上述方法进行累加TP的数量,计算TP/(n_gt)即可得到Recall,其中n_gt表示类别A在所有图片中gt的数量之和。同理,如果控制模型输出的预测框的数量,就会改变TP的数量,也就会改变Recall的值

综上,想要得到PR曲线,可以通过改变score的阈值来控制模型输出的预测框数量,从而得到不同的TP、FP、FN。不过在实际操作中,并不需要手动来设置score的阈值,因为每个预测框都有一个score,我们只需要将其按从小到大进行排序,然后每次选择最后一个score作为阈值即可,这样如果类别A在所有图片中的预测框数量之和有100个,就可以计算100组类别A的Precision-Recall值。

假设现在数据集中一共有5张图片,

①第1张图片中有2个A类别的gt,有三个A类别的预测框,score分别为(0.3, 0.5, 0.9),按照上述计算TP的方法(按score从大到小的顺序匹配)发现score为0.3和0.9的与gt相匹配,则将这两个记为TP。建立用于计算PR曲线的数组metric和记录A类别gt总数的变量ngt,向数组中加入(0.3, 1), (0.5, 0), (0.9, 1)三组

数据(每组数据的第一个代表预测框的score,第二个代表这个预测框是否是TP),并将n_gt累加2;使用相同的方法计算接下来的几张图片,

(0.3,1)(0.5, 0)(0.9,1)     gt=2

②第2张图片中没有A类别的物体(gt数量为0),则n_gt+=0,但有一个关于A类别的预测框,score为0.45,则向metric中加入(0.45, 0);

(0.3,1)(0.5, 0)(0.9,1)(0.45, 0)     gt=2

③第3张图片中有1个A类别的物体,但没有预测框,则n_gt+=1;

(0.3,1)(0.5, 0)(0.9,1)(0.45, 0)     gt=3

④第4张图片中有3个A类别的物体,有5个预测框,其中有3个与gt相匹配,最终n_gt+=3,metric中加入(0.85, 1), (0.8, 1), (0.7, 1), (0.35, 0), (0.1, 0);

(0.3,1)(0.5, 0)(0.9,1)(0.45, 0) (0.85, 1), (0.8, 1), (0.7, 1), (0.35, 0), (0.1, 0)   gt=6

⑤第5张图片中没有A类别的物体,也没有A类别的预测框。

(0.3,1)(0.5, 0)(0.9,1)(0.45, 0) (0.85, 1), (0.8, 1), (0.7, 1), (0.35, 0), (0.1, 0)   gt=6

对score进行从大到小排序,之所以使用由大向小排列是因为判断条件为>=score,是因为小的阈值向上兼容大的阈值

       accTP    accFP      precition    recall

(0.9,  1)        1                0        1/1        1/6 

    #score  =0.9   正确分类1个(0.9,  1),错分0个.  

     precision=1/(1+0)=1,  recall =1/6

(0.85,1)        2        0        2/2        2/6 

    #score  =0.85   正确分类2个(0.85,1)(0.9,1),错分0个. 

    precision=2/(2+0)=1,  recall =2/6

(0.8,  1)        3        0        3/3        3/6 

    #score  =0.80   正确分类3个(0.85,1)(0.9,1),(0.8,1),错分0个.

     precision=3/(3+0)=1,  recall =3/6

(0.7,  1)        4        0        4/4        4/6 

     #score  =0.70   正确分类4个(0.85,1)(0.9,1),(0.8,1)(0.7,1),错分0个.

     precision=4/(4+0)=1,  recall =4/6

(0.5,  0)        4        1        4/5        4/6 

     #score  =0.50   正确分类4个(0.85,1)(0.9,1),(0.8,1)(0.7,1),错分1个

     (0.5, 0).  precision=4/(4+1)=4/5,  recall =4/6

(0.45,0)        4        2        4/6        4/6 

     #score  =0.45   正确分类4个(0.85,1)(0.9,1),(0.8,1)(0.7,1),错2个

     (0.5,0),(0.45,0).  precision=4/(4+2)=4/6,  recall =4/6

(0.35,0)        4        3        4/7        4/6 

     #score  =0.35   正确分类4个(0.85,1)(0.9,1),(0.8,1)(0.7,1),错3个

     (0.5,0),(0.45,0),(0.35,0). precision=4/(4+3)=4/7,  recall=4/6

(0.3,  1)        5        3        5/8        5/6 

     #score  =0.3   正确分类4个(0.85,1)(0.9,1),(0.8,1)(0.7,1)(0.3,1),错3个

     (0.5,0),(0.45,0),(0.35,0). precision=5/(5+3)=5/8,  recall=5/6

(0.1   0)        5        4        5/9        5/6  

      #score  =0.1   正确分类4个(0.85,1)(0.9,1),(0.8,1)(0.7,1)(0.3,1),错3个

      (0.5,0),(0.45,0),(0.35,0),(0.1,0). precision=5/(5+4)=5/9,  recall=5/6

在计算面积的时候,我们使用均匀分片的方法,用样条面积相加生成最终的面积,因此,合适的样条,以Recall固定间隔进行采样,以上面数据为例子则分别为

[0-1/6],[1/6-2/6],[2/6-3/6],[3/6-4/6],[4/6-5/6],[5/6-6/6], 由于是离散的点,则分别取recall=1/6, 2/6, 3/6, 4/6, 5/6, 6/6的对应的对应得precision,由于计算得是面积,

因此取每个Recall值对应的最大Precision进行计算即可

map平均准确率_mAP

因为A类别一共有6个gt,所以Recall的值应该是从1/6~6/6共6个,也就是要取6组PR值计算平均Precision,因为这个例子中没有出现Recall=6/6的情况,

所以R=6/6时的Precision算作0,即类别A的AP=(1/1 + 2/2 + 3/3 + 4/4+ 5/8 + 0) / 6 = 0.7708

def ap_per_class(tp, conf, pred_cls, target_cls):

    """ Compute the average precision, given the recall and precision curves.

    Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.

    # Arguments

        tp:    True positives (list).

        conf:  Objectness value from 0-1 (list).

        pred_cls: Predicted object classes (list).

        target_cls: True object classes (list).

    # Returns

        The average precision as computed in py-faster-rcnn.

    """

    # Sort by objectness

    i = np.argsort(-conf)#np.argsort为从小到大排序,使用-conf,即等于对conf从大到小排序

    tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]#基于conf从大到小的排序结果对 tp, conf,pred_cls进行调整顺序

    # Find unique classes

    unique_classes = np.unique(target_cls) #对类别进行去重,并按从小到大排序,因为计算AP是针对每类进行

    # Create Precision-Recall curve and compute AP for each class

    ap, p, r = [], [], []

    for c in tqdm.tqdm(unique_classes, desc="Computing AP"):

        i = pred_cls == c #对于类别c,找到预测类别为c的所有的索引,索引记为i(i的0,1组成的序列)

        n_gt = (target_cls == c).sum()  # Number of ground truth objects 真实的类别为c的数目,即c类的gt数目

        n_p = i.sum()  # Number of predicted objects 即预测为c类的总的数目 n_p 即为fp和np的和

        if n_p == 0 and n_gt == 0: #本身没有c类,也没有被预测为c类的样本,则跳过该类别

            continue

        elif n_p == 0 or n_gt == 0:

            ap.append(0) 

            r.append(0) #r为recall的容器

            p.append(0) #p为precision的容器

            #本身没有c类,或者没有被预测为c类,两者必有之一,

            #如果没有c类,则p=0, r=0,ap=0

            #如果没有被预测到c类,则tp一定为0,则p=0,r=0,ap=0

        else:

            # Accumulate FPs and TPs

            fpc = (1 - tp[i]).cumsum()

            #i的值为(1,0),列表记录着索引对应位置是否是c类别框,i为1 则表示该点被预测为c,tp里面保存的为是否true-postive, tp[i]=0,则为false-postive, fpc则为计算累加的fp的总数目

            tpc = (tp[i]).cumsum()

            #i的值为(1,0),列表记录着索引对应位置是否是c类别框,i为1 则表示该点被预测为c,tp里面保存的为是否true-postive, tp[i]=1,则为true-postive, tpc则为计算累加的tp的总数目

            # Recall

            recall_curve = tpc / (n_gt + 1e-16)#计算recall的值

            r.append(recall_curve[-1])#recall值放入到r中,r为recall的容器

            # Precision

            precision_curve = tpc / (tpc + fpc)#计算precision值

            p.append(precision_curve[-1])#precision值放入到p中,p为precision的容器

            # AP from recall-precision curve

            ap.append(compute_ap(recall_curve, precision_curve))

    # Compute F1 score (harmonic mean of precision and recall)

    p, r, ap = np.array(p), np.array(r), np.array(ap)

    f1 = 2 * p * r / (p + r + 1e-16)

    return p, r, ap, f1, unique_classes.astype("int32")

def compute_ap(recall, precision):

    """ Compute the average precision, given the recall and precision curves.

    Code originally from https://github.com/rbgirshick/py-faster-rcnn.

    # Arguments

        recall:    The recall curve (list).

        precision: The precision curve (list).

    # Returns

        The average precision as computed in py-faster-rcnn.

    """

    # correct AP calculation

    # first append sentinel values at the end

    mrec = np.concatenate(([0.0], recall, [1.0]))

    mpre = np.concatenate(([0.0], precision, [0.0]))

    # compute the precision envelope

    for i in range(mpre.size - 1, 0, -1):

        mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])

    # to calculate area under PR curve, look for points

    # where X axis (recall) changes value

    i = np.where(mrec[1:] != mrec[:-1])[0]

    # and sum (\Delta recall) * prec

    ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])

    return ap