논문 읽기/Object Detection

[Object Detection] mAP(mean Average Precision)을 이해하고 파이토치로 구현하기

AI 꿈나무 2021. 1. 3. 16:30
반응형

 안녕하세요 mAP(mean Average Precision)는 Faster R-CNN, SSD와 같은 object detector의 정확도를 측정하는 유명한 평가지표 입니다.

 

 이번 포스팅에서는 mAP가 무엇인지 알아보고 파이토치로 구현해보도록 하겠습니다.

 

 mAP를 알아보기 전에 precision(정밀도), recall(재현율), IoU(intersection of union)에 대한 개념을 알아야 합니다.

 

Precision(정밀도)와 recall(재현율)

 Precision은 모델이 True라고 예측한 것 중 정답도 True인 것의 비율을 의미합니다.

 recall은 실제 정답이 True인 것중에서 모델이 True라고 예측한 것의 비율입니다.

 

 Precision과 recall의 공식은 다음과 같습니다.

 

 

 TP, TN, FP, FN은 무엇을 의미하는 걸까요??

 

 

 예를 들어, 이미지에 10개의 사과가 있고 모델이 6개의 사과를 검출하여 5개는 TP, 1개는 FP라고 가정해보겠습니다.

 그러면 모델이 검출하지 못한 4개의 사과는 FN이 됩니다.

 

 그러면 Precision = 5/6가 되고, Recall = 5/10이 됩니다.

 

 Precision을 봤을 때는 좋은 성능을 갖고 있는 것 같은데 Recall을 보면 안좋은 성능을 갖고 있는 것 같습니다.

 

 어느 한 값으로 알고리즘의 성능을 판단하기에는 불가능하고, 두 값을 종합해서 알고리즘을 평가하기 위한 것이 AP(Average Precision) 입니다.

 

 참고로 TP와 FP을 결정해주는 것은 IoU 입니다. IoU에 대한 내용은 여기서 확인하실 수 있습니다.

 

 

[Object Detection] IoU(Intersection over Union)를 이해하고 파이토치로 구현하기

 안녕하세요 이번 포스팅에서는 IoU에 대해 알아보도록 하겠습니다. IoU(Intersection over Union)은 무엇일까요?  Intersection over Union은 object detector의 정확도를 측정하는데 이용되는 평가 지표입니다..

deep-learning-study.tistory.com

 

AP(Average Precision)

 AP는 Precision-Recall 그래프의 아래 면적을 의미합니다.

 Precision-Recall 그래프가 무엇인지 알아보도록 하겠습니다.

 

 5개의 사과가 포함되어 있는 이미지에 Detector가 10개의 사과를 검출했다고 가정해보겠습니다.

 각각의 검출 결과를 confidence가 높은 순으로 정렬한 것입니다.

 

 

 이 recall은 서서히 증가하다가 rank 10에서 5번째 True때 1.0이 되었습니다.

 반면에 Precision은 들쑥날쑥 한 것을 확인할 수 있으며 5개의 사과중 10개의 사과를 검출했으므로 최종적으로 0.5의 precision가 되었습니다.

 

 Recall에 따른 Precision을 Plot으로 그려보겠습니다. 이것이 Precision-Recall 그래프입니다.

 

 

 

 이 곡선의 아래 면적이 AP(Average Precision) 입니다.

 그런데 보통 계산 전에 Precision-Recall 그래프를 바꿔줍니다.

 

 

 이렇게 바꾼 다음에 아래 면적을 구해서 AP를 구합니다.

 

 AP = (5x11 + 4x0.57 + 2x0.5) / 11이 됩니다.

 

mAP(mean Average Precision)

 mAP은 이름 그대로 각각의 클래스에 대한 AP의 평균을 의미합니다.

 이것이 Object Detection의 평가 지표가 되는 것입니다.

 

 

mAP를 파이토치로 구현하기

import torch
from collections import Counter
from IoU import intersection_over_union

def mean_average_precision(pred_boxes, true_boxes, iou_threshold=0.5, box_format='corners', num_classes=20):
    # pred_boxes (list) : [[train_idx, class_pred, prob_score, x1, y1, x2, y2], ... ]
    average_precisions = []
    epsilon = 1e-6

    # 각각의 클래스에 대한 AP를 구합니다.
    for c in range(num_classes):
        detections = []
        ground_truths = []

        # 모델이 c를 검출한 bounding box를 detections에 추가합니다.
        for detection in pred_boxes:
            if detection[1] == c:
                detections.append(detection)

        # 실제 c 인 bounding box를 ground_truths에 추가합니다.
        for true_box in true_boxes:
            if true_box[1] == c:
                ground_truths.append(true_box)

        # amount_bboxes에 class에 대한 bounding box 개수를 저장합니다.
        # 예를 들어, img 0은 3개의 bboxes를 갖고 있고 img 1은 5개의 bboxes를 갖고 있으면
        # amount_bboexs = {0:3, 1:5} 가 됩니다.
        amount_bboxes = Counter([gt[0] for gt in ground_truths])

        # class에 대한 bounding box 개수 만큼 0을 추가합니다.
        # amount_boxes = {0:torch.tensor([0,0,0]), 1:torch.tensor([0,0,0,0,0])}
        for key, val in amount_bboxes.items():
            amount_bboxes[key] = torch.zeros(val)

        # detections를 정확도 높은 순으로 정렬합니다.
        detections.sort(key=lambda x: x[2], reverse=True)

        TP = torch.zeros((len(detections)))
        FP = torch.zeros((len(detections)))
        total_true_bboxes = len(ground_truths)

		# TP와 FP를 구합니다.
        for detection_idx, detection in enumerate(detections):
            ground_truth_img = [bbox for bbox in ground_truths if bbox[0] == detection[0]]
            num_gts = len(ground_truth_img)
            best_iou = 0

            for idx, gt in enumerate(ground_truth_img):
                iou = intersection_over_union(torch.tensor(detection[3:]),
                                              torch.tensor(gt[3:]),
                                              box_format=box_format)

                if iou > best_iou:
                    best_iou = iou
                    best_gt_idx = idx

            if best_iou > iou_threshold:
                if amount_bboxes[detection[0]][best_gt_idx] == 0:
                    TP[detection_idx] = 1
                    amount_bboxes[detection[0]][best_gt_idx] = 1
                else:
                    FP[detection_idx] = 1

            else:
                FP[detection_idx] = 1
        
        # cumsum은 누적합을 의미합니다.
        # [1, 1, 0, 1, 0] -> [1, 2, 2, 3, 3]
        TP_cumsum = torch.cumsum(TP, dim=0)
        FP_cumsum = torch.cumsum(FP, dim=0)
        recalls = TP_cumsum / (total_true_bboxes + epsilon)
        precisions = torch.divide(TP_cumsum, (TP_cumsum + FP_cumsum + epsilon))
        precisions = torch.cat((torch.tensor([1]), precisions))
        recalls = torch.cat((torch.tensor([0]),recalls))
        
        # torch.trapz(y,x) : x-y 그래프를 적분합니다.
        average_precisions.append(torch.trapz(precisions, recalls))

    return sum(average_precisions) / len(average_precisions)

참고자료

[1] www.youtube.com/watch?v=FppOzcDvaDI

[2] jonathan-hui.medium.com/map-mean-average-precision-for-object-detection-45c121a31173

[3] i-am-eden.tistory.com/7

[4] bskyvision.com/465

 

 

 

 

 

 

반응형