最近在處理檢測問題的時候,需要對網絡參數進行評估,之前對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進行計算即可

因為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