Level 9: 종합 프로젝트
🏆

Level 9

모델 평가

mAP, 혼동 행렬, F1-score로 성능 측정

60분
모델 평가 강의 영상
강의 영상 보기 (새 탭에서 재생)YouTube

📓Google Colab에서 실습하기

이 레슨은 PyTorch/GPU가 필요합니다. 노트북을 다운로드 후 Google Colab에서 열어주세요.

학습 내용

모델 평가

학습 목표

  • 검출 모델의 핵심 평가 지표(IoU, mAP)를 이해한다
  • 분류 모델의 혼동 행렬과 F1-score를 계산한다
  • Precision-Recall 곡선과 ROC 곡선을 그려본다
  • 번호판 인식 시스템 전체의 성능을 종합적으로 평가한다

왜 평가가 중요한가?

비유: 요리사가 맛을 보지 않고 음식을 내놓을 수 있을까요?

모델을 학습시키는 것은 요리를 만드는 과정이고, 평가는 맛을 보는 과정입니다. 평가 없이 배포하는 것은 눈을 감고 운전하는 것과 같습니다. 학습 데이터에서는 잘 작동하지만 실제 환경에서 엉망인 경우가 매우 흔하기 때문입니다.


검출 모델 평가: IoU와 mAP

IoU (Intersection over Union)

비유: 두 사람이 각각 "여기가 번호판이야"라고 영역을 표시했을 때, 두 영역이 얼마나 겹치는지 측정하는 것이 IoU입니다.

IoU는 예측한 바운딩 박스와 실제 정답 박스가 얼마나 일치하는지를 0에서 1 사이 값으로 나타냅니다.

IoU 값의미판정
0.0전혀 겹치지 않음완전 실패
0.5절반 정도 겹침최소 기준
0.75대부분 겹침좋은 검출
1.0완벽히 일치이상적
python
import numpy as np def calculate_iou(box1, box2): """IoU 계산 (box 형식: [x1, y1, x2, y2])""" x1 = max(box1[0], box2[0]) y1 = max(box1[1], box2[1]) x2 = min(box1[2], box2[2]) y2 = min(box1[3], box2[3]) intersection = max(0, x2 - x1) * max(0, y2 - y1) area1 = (box1[2] - box1[0]) * (box1[3] - box1[1]) area2 = (box2[2] - box2[0]) * (box2[3] - box2[1]) union = area1 + area2 - intersection return intersection / union if union > 0 else 0 # 예시: 두 박스의 IoU 계산 gt_box = [100, 100, 300, 200] # 정답 박스 pred_box = [120, 90, 310, 210] # 예측 박스 iou = calculate_iou(gt_box, pred_box) print(f"IoU: {iou:.3f}") for t in [0.5, 0.7, 0.9]: print(f" IoU >= {t}: {'SUCCESS' if iou >= t else 'FAIL'}")

Precision과 Recall

검출 결과를 분류하면 네 가지 경우가 생깁니다.

구분설명번호판 인식 예시
TP (True Positive)있는 걸 있다고 함번호판을 정확히 검출
FP (False Positive)없는 걸 있다고 함광고판을 번호판으로 오인
FN (False Negative)있는 걸 없다고 함번호판을 놓침
TN (True Negative)없는 걸 없다고 함배경을 배경으로 판단
python
import numpy as np # 번호판 검출 결과 예시 TP = 90 # 번호판을 정확히 검출 FP = 5 # 광고판을 번호판으로 오인 FN = 10 # 번호판을 놓침 precision = TP / (TP + FP) recall = TP / (TP + FN) f1 = 2 * precision * recall / (precision + recall) print(f"TP: {TP}, FP: {FP}, FN: {FN}") print(f"Precision: {precision:.3f} (high = few false alarms)") print(f"Recall: {recall:.3f} (high = few missed)") print(f"F1-score: {f1:.3f} (balance of both)")

mAP (mean Average Precision)

비유: 시험에서 "평균 점수"를 내는 것처럼, mAP는 다양한 기준에서의 검출 성능을 하나의 숫자로 요약합니다.

mAP는 Precision-Recall 곡선 아래의 면적(AP)을 모든 클래스에 대해 평균낸 값입니다. 번호판 검출은 클래스가 하나(번호판)이므로 AP와 mAP가 같습니다.

지표의미YOLO 기본 출력
mAP@0.5IoU 0.5 기준 APmap50
mAP@0.5:0.95IoU 0.5부터 0.95까지 평균 APmap
mAP@0.75IoU 0.75 기준 AP (엄격)map75
python
# YOLO 모델 평가 (자동으로 mAP 계산) from ultralytics import YOLO model = YOLO("best.pt") results = model.val(data="plate_dataset.yaml") print(f"mAP@0.5: {results.box.map50:.3f}") print(f"mAP@0.5:0.95: {results.box.map:.3f}") print(f"Precision: {results.box.mp:.3f}") print(f"Recall: {results.box.mr:.3f}")

분류 모델 평가: 혼동 행렬과 F1-score

혼동 행렬 (Confusion Matrix)

비유: 학생들의 시험 답안을 채점할 때, "어떤 문제를 어떤 답으로 틀렸는지" 표로 정리하는 것이 혼동 행렬입니다. 예를 들어 "7"을 "1"로 자주 틀린다면, 그 패턴을 알아야 개선할 수 있습니다.

혼동 행렬의 행은 실제 클래스, 열은 예측 클래스를 나타냅니다. 대각선은 정확한 예측이고, 비대각선은 혼동(오류)입니다. 이것을 시각화해보겠습니다.

python
import numpy as np import matplotlib.pyplot as plt # 혼동 행렬 데이터 (번호판 문자 인식) class_names = ["0", "1", "2", "3", "ga", "na"] cm = np.array([ [28, 0, 1, 5, 0, 0], [ 0, 30, 4, 0, 1, 0], [ 1, 2, 31, 0, 0, 1], [ 2, 0, 0, 33, 0, 0], [ 0, 1, 0, 0, 29, 3], [ 0, 0, 1, 0, 4, 28] ]) fig, ax = plt.subplots(1, 1, figsize=(8, 6)) im = ax.imshow(cm, interpolation="nearest", cmap="Blues") ax.set_title("Confusion Matrix - Character Recognition", fontsize=14) plt.colorbar(im, ax=ax) # 축 설정 tick_marks = np.arange(len(class_names)) ax.set_xticks(tick_marks) ax.set_xticklabels(class_names, fontsize=12) ax.set_yticks(tick_marks) ax.set_yticklabels(class_names, fontsize=12) ax.set_xlabel("Predicted", fontsize=12) ax.set_ylabel("Actual", fontsize=12) # 셀에 숫자 표시 thresh = cm.max() / 2 for i in range(len(class_names)): for j in range(len(class_names)): color = "white" if cm[i, j] > thresh else "black" ax.text(j, i, str(cm[i, j]), ha="center", va="center", color=color, fontsize=14) plt.tight_layout() plt.show() print("Diagonal = correct predictions") print("Off-diagonal = confused character pairs")

F1-Score

Precision과 Recall 중 하나만 높으면 안 됩니다. F1-score는 둘의 조화평균으로, 둘 다 높아야 높은 값이 나옵니다.

비유: 축구에서 공격(Recall)만 잘하고 수비(Precision)가 약하면 이길 수 없습니다. F1-score는 공격과 수비의 균형을 측정하는 지표입니다.

python
import numpy as np # 앞의 혼동 행렬에서 F1 계산 cm = np.array([ [28, 0, 1, 5, 0, 0], [ 0, 30, 4, 0, 1, 0], [ 1, 2, 31, 0, 0, 1], [ 2, 0, 0, 33, 0, 0], [ 0, 1, 0, 0, 29, 3], [ 0, 0, 1, 0, 4, 28] ]) names = ["0", "1", "2", "3", "ga", "na"] print(f"{'Class':>6} {'Precision':>10} {'Recall':>10} {'F1':>10}") print("-" * 38) f1_scores = [] for i in range(len(names)): tp = cm[i, i] fp = cm[:, i].sum() - tp fn = cm[i, :].sum() - tp p = tp/(tp+fp) if tp+fp > 0 else 0 r = tp/(tp+fn) if tp+fn > 0 else 0 f1 = 2*p*r/(p+r) if p+r > 0 else 0 f1_scores.append(f1) print(f"{names[i]:>6} {p:>10.3f} {r:>10.3f} {f1:>10.3f}") print("-" * 38) print(f"{'Avg':>6} {'':>10} {'':>10} {np.mean(f1_scores):>10.3f}")

Precision-Recall 곡선과 ROC 곡선

신뢰도 임계값을 바꾸면 Precision과 Recall이 달라집니다. 이 관계를 곡선으로 그려보겠습니다.

python
import numpy as np import matplotlib.pyplot as plt np.random.seed(42) pos_scores = np.random.beta(5, 2, size=150) # 양성: 높은 신뢰도 neg_scores = np.random.beta(2, 5, size=150) # 음성: 낮은 신뢰도 scores = np.concatenate([pos_scores, neg_scores]) labels = np.concatenate([np.ones(150), np.zeros(150)]) thresholds = np.linspace(0.01, 0.99, 50) precisions, recalls, fprs, tprs = [], [], [], [] for t in thresholds: pred = (scores >= t).astype(int) tp = np.sum((pred == 1) & (labels == 1)) fp = np.sum((pred == 1) & (labels == 0)) fn = np.sum((pred == 0) & (labels == 1)) tn = np.sum((pred == 0) & (labels == 0)) precisions.append(tp/(tp+fp) if tp+fp > 0 else 1.0) recalls.append(tp/(tp+fn) if tp+fn > 0 else 0.0) tprs.append(tp/(tp+fn) if tp+fn > 0 else 0) fprs.append(fp/(fp+tn) if fp+tn > 0 else 0) ap = abs(np.trapz(precisions, recalls)) auc_val = abs(np.trapz(tprs, fprs)) fig, axes = plt.subplots(1, 2, figsize=(14, 5)) axes[0].plot(recalls, precisions, "b-", linewidth=2) axes[0].set_xlabel("Recall"); axes[0].set_ylabel("Precision") axes[0].set_title(f"PR Curve (AP={ap:.3f})") axes[0].set_xlim([0, 1.05]); axes[0].set_ylim([0, 1.05]) axes[0].grid(True, alpha=0.3) axes[1].plot(fprs, tprs, "r-", linewidth=2, label=f"AUC={auc_val:.3f}") axes[1].plot([0, 1], [0, 1], "k--", alpha=0.5, label="Random") axes[1].set_xlabel("FPR"); axes[1].set_ylabel("TPR") axes[1].set_title("ROC Curve") axes[1].set_xlim([0, 1.05]); axes[1].set_ylim([0, 1.05]) axes[1].legend(); axes[1].grid(True, alpha=0.3) plt.tight_layout() plt.show() print("PR: top-right = better | ROC: top-left = better")

번호판 인식 시스템 전체 평가

번호판 인식은 검출과 인식 두 단계로 구성됩니다. 전체 시스템 평가도 단계별로 해야 합니다.

단계별 평가 지표

단계핵심 지표목표
번호판 검출mAP@0.50.95 이상
문자 분류F1-score (macro)0.98 이상
전체 파이프라인번호판 완전 일치율0.90 이상

전체 시스템 정확도 계산

python
import numpy as np # 전체 파이프라인 정확도 = 검출 x (문자 정확도 ^ 글자수) det_acc = 0.96 plate_len = 7 # 12가3456 print("Impact of per-character accuracy on full plate accuracy:") print(f"(Detection accuracy: {det_acc:.0%}, Plate length: {plate_len})") print() for ca in [0.99, 0.98, 0.97, 0.95, 0.90]: total = det_acc * (ca ** plate_len) print(f" Char {ca:.0%} -> Full plate {total:.1%}")

조건별 성능 분석

다양한 환경에서 모델이 어떻게 작동하는지 분석해야 합니다.

환경정확도판정
주간96%목표 달성
야간82%주의 필요
비/안개78%개선 필요
역광71%개선 필요
먼 거리85%목표 달성
기울어짐88%목표 달성

약한 조건(야간, 비, 역광)을 파악하고 해당 데이터를 보강하면 전체 성능이 올라갑니다.


평가 결과를 개선에 활용하기

평가는 그 자체가 목적이 아니라, 개선 방향을 찾기 위한 도구입니다.

평가 결과원인 분석개선 방법
검출 mAP 낮음학습 데이터 부족데이터 증강, 추가 라벨링
특정 문자 F1 낮음비슷한 문자 혼동해당 클래스 데이터 보강
야간 정확도 낮음조명 조건 다양성 부족야간 데이터 추가, 전처리 개선
속도 느림모델이 너무 큼경량화 (pruning, distillation)

개선 사이클

평가와 개선은 한 번으로 끝나지 않습니다. "평가 -> 분석 -> 개선 -> 재평가"를 반복하며 성능을 올려갑니다.

이것이 실제 AI 프로젝트의 핵심 루프입니다. 논문에서 보는 높은 정확도는 이 사이클을 수십 번 반복한 결과입니다.


핵심 정리

지표대상의미
IoU검출예측 박스와 정답 박스의 겹침 정도
mAP검출다양한 임계값에서의 종합 검출 성능
Confusion Matrix분류어떤 클래스를 어떻게 혼동하는지
F1-score분류Precision과 Recall의 조화평균
PR Curve종합임계값에 따른 정밀도-재현율 변화
ROC / AUC종합전체적인 분류 성능 요약
  1. IoU: 바운딩 박스의 겹침 정도, 0.5 이상이면 검출 성공으로 판정
  2. mAP: 검출 모델의 종합 점수, YOLO가 자동 계산해줌
  3. 혼동 행렬: 어떤 문자를 어떻게 헷갈리는지 한눈에 파악 가능
  4. F1-score: Precision과 Recall의 균형을 하나의 숫자로 요약
  5. 조건별 분석: 약한 환경을 찾아 데이터를 보강하는 것이 핵심 전략

레슨 정보

레벨
Level 9: 종합 프로젝트
예상 소요 시간
60분
참고 영상
YouTube 링크

💡실습 환경 안내

이 레벨은 PyTorch/GPU가 필요하여 Google Colab 사용을 권장합니다.

Colab은 무료 GPU를 제공하여 PyTorch, CNN, Transformer 등을 실행할 수 있습니다.