AP(Average Precision)와 mAP(mean Average Precision)

  • 지난 글(정밀도(Precision)와 재현율(Recall) 내용 정리)에서 확장된 내용입니다.
  • 객체 검출(Object Detection) 문제를 풀 때 성능을 평가하는 대표적인 방법입니다.
  • AP 및 mAP를 이해하기 위해서는 정밀도&재현율 곡선(Precision & Recall Curve) 및 IoU(Intersection of Union)에 대한 이해가 필요합니다.

Precision & Recall Curve

Confusion Matrix

  • 분류 문제를 풀 때 오차 행렬(Confusion Matrix)를 많이 사용한다고 하였습니다. 정밀도와 재현율은 오차 행렬의 요소(TN, FP, FN, TP)로 계산이 가능합니다.
  • 정밀도 계산 방법

$$ Precision = \frac{TP}{TP + FP} $$

  • 재현율 계산 방법

$$ Recall = \frac{TP}{TP + FN} $$

  • 또한 TP(True Positive)를 결정하기 위해 결정 함수(Decision Function)를 사용하고, 결정 함수의 출력값(신뢰 점수, Confidence Score)과 임계값(Threshold)을 비교합니다. 즉 Confidence Score가 Threshold보다 높으면 TP, 반대로 낮으면 FP(False Positive)로 결정합니다.
  • 그렇다면 객체 검출에서 Confidence Score를 어떻게 계산할까요?

Object Detection의 Confidence Score

  • Object Classification과 Object Detection은 모두 객체를 검출하지만, 객체 검출은 이미지 영역 내에서 객체의 위치(Localization)까지도 검출합니다.
  • 컴퓨터 비전의 유명한 수업에서는(CS231n: Convolutional Neural Networks for Visual Recognition)에서는 아래 그림과 같이 정의하고 있습니다.

Object Detection

  • 그림에서 보이는 사각형의 박스를 바운딩 박스(Bounding Box)라고 부릅니다.
  • 객체 검출에서 Confidence Score를 결정하는 방법으로 IoU(Intersection of Union)를 많이 사용합니다. IoU는 그렇다면 어떻게 계산할까요? 아래 그림을 보겠습니다.

IoU

  • 여기서 GT(Ground Truth)는 정답 데이터이고, Prediction은 객체 검출기의 출력 결과입니다.
  • IoU를 계산하기 위해서는 GT와 Prediction의 전체 면적과 중복(교차) 면적의 비율로 계산합니다.

$$ IoU = \frac{A_{GT} ∩ A_{Prediction}}{A_{GT} ∪ A_{Prediction}} $$

  • 최종적으로 객체 검출의 Confidence Score는 대상 객체의 정답 레이블을 맞추는 확률과 IoU의 값을 곱한 결과를 사용합니다.
    • 객체 분류의 Confidence Score는 정답 레이블을 맞추는 클래스 확률 값(0 ~ 1)을 사용합니다.
    • 객체 검출의 경우 객체를 분류하고 위치를 찾는 문제이므로, 객체 검출의 Confidence Score와 위치의 정확도를 의미하는 IoU를 같이 사용합니다.

$$ OD_{Score} = \textbf{P}{r}(Class{i}) * IoU_{i} $$

샘플 데이터: Precision & Recall

  • Confidence Score의 개념을 알았으니, Recall과 Precision을 계산하기 위한 가상의 데이터를 사용하겠습니다. 총 15개의 데이터에 대해 객체 검출기는 10개의 결과를 출력하였습니다.
DetectionConfidence ScoreTP or FP
10.97TP
20.86TP
30.78TP
40.73TP
50.64TP
60.53FP
70.47TP
80.23TP
90.13FP
100.07FP
  • 객체 검출의 결과로 Confidence Score를 출력하는데, Threshold 0.5를 기준으로 TP와 FP를 결정하였습니다. 여기서 TP = 7, FP = 3입니다. FN(False Negative)의 경우 15개의 Positive GT가 존재하는데 7개의 TP가 결정되었으므로 FN = 8 입니다. TPFN의 합이 GT의 수와 같습니다.
  • 전체 결과에 대한 Precision은 \( \frac{TP}{TP + FP} \)으로 계산하므로 \( \frac{7}{10} \)은 \( 0.7 \)입니다.
  • 또한 Recall은 \( \frac{TP}{TP + FN} \)으로 계산하므로 \( \frac{7}{15} \)은 \( 0.466 \)입니다.

샘플 데이터: Precision & Recall Curve

  • Precision & Recall Curve를 계산하기 위해 결과를 누적된 결과를 하나씩 살펴보도록 하겠습니다.
DetectionConfidence ScoreTP or FP누적 TP누적 FPPrecisionRecall
10.97TP10\( \frac{1}{1} = 1 \)\( \frac{1}{15} = 0.0666 \)
20.86TP20\( \frac{2}{2} = 1 \)\( \frac{2}{15} = 0.1333 \)
30.78TP30\( \frac{3}{3} = 1 \)\( \frac{3}{15} = 0.2 \)
40.73TP40\( \frac{4}{4} = 1 \)\( \frac{4}{15} = 0.2666 \)
50.64TP50\( \frac{5}{5} = 1 \)\( \frac{5}{15} = 0.3333 \)
60.53FP51\( \frac{5}{6} = 0.8333 \)\( \frac{5}{15} = 0.3333 \)
70.47TP61\( \frac{6}{7} = 0.8571 \)\( \frac{6}{15} = 0.4 \)
80.23TP71\( \frac{7}{8} = 0.875 \)\( \frac{7}{15} = 0.4666 \)
90.13FP72\( \frac{7}{9} = 0.7777 \)\( \frac{7}{15} = 0.4666 \)
100.07FP73\( \frac{7}{10} = 0.7 \)\( \frac{7}{15} = 0.4666 \)
  • Confidence Score는 객체 검출기가 스스로 부여한 자신의 신뢰 점수이기 때문에 낮은 Confidence Score에도 TP가 나올 수 있습니다. 그래서 Threshold를 사용해서 비교적 불확실한 출력 결과에 대해 한 번 필터를 적용하는 것입니다. 위 데이터의 결과는 Threshold를 적용하지 않은 결과임을 가정하였습니다.

  • 출력된 결과를 가지고 Precision과 Recall에 대해 그래프를 그릴 수 있습니다.

  • 사용된 코드

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
TOTAL_DATASET = 15

tps = [1, 2, 3, 4, 5, 5, 6, 7, 7, 7]
fps = [0, 0, 0, 0, 0, 1, 1, 1, 2, 3]

precisions = []
recalls = []
for i, (tp, fp) in enumerate(zip(tps, fps), 1):
    precision = tp / i
    recall = tp / TOTAL_DATASET

    precisions.append(precision)
    recalls.append(recall)
  • 결과 사진

Precision & Recall Curve

샘플 데이터: AP

  • AP를 계산하는 방법은 간단합니다. Precision & Recall Curve의 그래프 아래 면적의 넓이와 같은데요. 정밀도와 재현율 모두 0 ~ 1 사이의 값을 갖기 때문에 AP의 값도 0 ~ 1의 값을 갖습니다.

  • 다만 위의 Precision & Recall Curve의 그래프를 보면 면적을 계산하기 불편하게 되어있습니다. 따라서 일반적으로 계산의 편의를 위해 정밀도의 값을 보간(Interpolation)합니다. 상대적으로 높은 재현율에서 정밀도가 더 높다면, 높은 정밀도의 수치로 보간하는 방법입니다. 이는 글보다 그림으로 이해하는 것이 쉽기 때문에 그림으로 확인하도록 하겠습니다.

  • 사용된 코드

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
precisions_rev = precisions[::-1]
precisions_inter = []
init_precision = precisions_rev[0]
precisions_inter.append(init_precision)
for i in range(len(precisions_rev) - 1):

    if not precisions_rev[i] > precisions_rev[i+1]:
        init_precision = precisions_rev[i+1]

    precisions_inter.append(init_precision)
precisions_inter = precisions_inter[::-1]
  • 결과 사진

Precision & Recall Curve

  • 결과 사진에서 보이는 빨간색 점선 그래프의 아래 면적이 AP의 값입니다.

mAP 계산 방법

  • AP를 계산하게 되었다면 mAP는 정말 쉽게 계산할 수 있습니다.
  • 지금까지 살펴본 샘플 데이터는 하나의 클래스에 대해서만 다뤘습니다. mAP는 데이터셋에 존재하는 개별 클래스에 대한 AP를 평균낸 값입니다.
  • 그런데 AP를 보다 보면 \( \text{AP}_{50} \)와 같은 것을 볼 수 있습니다. 이건 어떤 의미일까요?
    • 앞서 설명한 내용에는 Confidence Score에 대해 Threshold를 적용하여 결과를 결정한다고 하였습니다. 하지만 예제에서는 사용하지 않았죠. 그렇다면 어떻게 사용하는 것일까요?
    • 만약 Confidence Score가 우리가 결정한 Threshold보다 낮으면, 해당 출력은 신뢰도가 낮은 결과로 판단하여 결과로 인정하지 않습니다. 즉 결과 데이터로 사용하지 않는다는 것이죠. 한 가지 예를 보겠습니다.
DetectionConfidence ScoreTP or FP
10.97TP
20.86TP
30.78TP
40.73TP
50.64TP
60.53FP
70.47TP
80.23TP
90.13FP
100.07FP
  • 앞서 우리가 사용한 결과 데이터 중에서 Threshold를 0.4로 잡아보겠습니다. 그러면 Confidence Score의 결과 중에서 0.4보다 낮은 값은 전부 무시됩니다. 바로 이렇게 말이죠.
DetectionConfidence ScoreTP or FP
10.97TP
20.86TP
30.78TP
40.73TP
50.64TP
60.53FP
70.47TP
  • Threshold를 적용하니 FP도 많이 줄었지만, TP도 같이 줄었습니다. 다시 한번 얘기하지만 Confidence Score는 객체 검출기가 스스로 판단한 신뢰 점수로 Confidence Score가 높다고 무조건 정답은 아닙니다. 높은 확신으로 정답이라 판단했지만, 실제로는 틀린 경우가 있을 수 있습니다. (시험볼 때 주로 그랬던 것 같습니다.) 따라서 Threshold 값에 따라 검출기의 성능이 달라지겠죠? 즉 \( \text{AP}_{50} \)는 Threshold를 적용하고, Threshold의 값을 의미합니다.

전체 코드

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import matplotlib.pyplot as plt
import numpy as np

TOTAL_DATASET = 15

tps = [1, 2, 3, 4, 5, 5, 6, 7, 7, 7]
fps = [0, 0, 0, 0, 0, 1, 1, 1, 2, 3]

precisions = []
recalls = []
for i, (tp, fp) in enumerate(zip(tps, fps), 1):
    precision = tp / i
    recall = tp / TOTAL_DATASET

    precisions.append(precision)
    recalls.append(recall)

precisions_rev = precisions[::-1]
precisions_inter = []
init_precision = precisions_rev[0]
precisions_inter.append(init_precision)
for i in range(len(precisions_rev) - 1):

    if not precisions_rev[i] > precisions_rev[i+1]:
        init_precision = precisions_rev[i+1]

    precisions_inter.append(init_precision)
precisions_inter = precisions_inter[::-1]


plt.plot(recalls, precisions, label='Precision & Recall')
plt.plot(recalls, precisions_inter, 'r--', label='Interpolation Precision')
plt.scatter(recalls, precisions)
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('Precision & Recall Curve')
plt.legend()
plt.show()