컴퓨터공학 300 주제 시리즈의 268번째 글이다. 전체 지도는 여기.

한 줄 요약

분류 모델은 혼동 행렬(TP·FP·FN·TN)에서 출발해 정밀도·재현율·F1·ROC AUC 등으로, 회귀 모델은 MAE·RMSE·R² 등으로 평가하며, 어떤 지표를 고를지는 틀렸을 때의 비용이 정한다.

왜 필요한가

환자 1,000명 중 10명이 질병을 가진 데이터에서 “모두 건강하다”고만 답하는 모델의 정확도는 99% 다. 숫자는 훌륭하지만 이 모델은 단 한 명의 환자도 찾지 못한다.

지표는 모델이 무엇을 잘하고 무엇을 못하는지 드러내는 렌즈다. 잘못된 렌즈를 고르면 나쁜 모델을 배포하거나 좋은 모델을 버린다. 그리고 학습 과정 전체(하이퍼파라미터 선택, 임계값 결정, 모델 비교)가 이 지표를 기준으로 돌아가므로, 지표 선택은 모델 선택보다 먼저 와야 한다.

핵심 개념

혼동 행렬

양성(1)을 찾는 이진 분류에서

  예측 양성 예측 음성
실제 양성 TP (맞게 찾음) FN (놓침)
실제 음성 FP (헛경보) TN (맞게 거름)

모든 분류 지표는 이 네 칸의 조합이다.

주요 지표

지표 식 질문
정확도 (TP+TN) / 전체 전체 중 몇 개 맞혔나
정밀도 TP / (TP+FP) 양성이라 한 것 중 진짜는 몇 개인가
재현율(민감도) TP / (TP+FN) 진짜 양성 중 몇 개를 찾았나
특이도 TN / (TN+FP) 진짜 음성 중 몇 개를 걸렀나
F1 2·P·R / (P+R) 정밀도와 재현율의 조화 평균

정밀도와 재현율은 서로 당긴다. 임계값을 낮추면 더 많이 양성이라 부르므로 재현율은 오르고 정밀도는 내린다.

어느 쪽이 중요한지는 비용으로 정한다.

  • 스팸 필터: 정상 메일을 스팸함에 넣는 것(FP)이 더 나쁘다. 정밀도 중시.
  • 암 선별 검사: 환자를 놓치는 것(FN)이 더 나쁘다. 재현율 중시.
  • 장애 알림: 둘 다 나쁘다. 헛경보가 많으면 사람이 알림을 무시하게 되고, 놓치면 장애가 커진다.

임계값과 무관한 지표 — ROC 와 PR 곡선

모델이 확률이나 점수를 내면, 임계값을 바꿔 가며 지표를 그릴 수 있다.

  • ROC 곡선: x 축 FPR = FP/(FP+TN), y 축 TPR(재현율). 대각선이 무작위 추측이다.
  • AUC: ROC 곡선 아래 넓이. 무작위로 고른 양성의 점수가 무작위로 고른 음성의 점수보다 높을 확률과 같다. 1 이면 완벽한 순서, 0.5 면 동전 던지기.
  • PR 곡선: x 축 재현율, y 축 정밀도. 양성이 아주 드물면 ROC 는 TN 이 워낙 많아 낙관적으로 보이므로 PR 곡선과 평균 정밀도(AP)가 더 정직하다.

회귀 지표

지표 식 성질
MAE 평균 |ŷ − y| 단위가 원래와 같음. 이상치에 덜 민감
RMSE √평균(ŷ − y)² 큰 오차를 크게 벌줌
R² 1 − SSE/SST 평균 대비 설명력
MAPE 평균 |ŷ − y|/|y| 비율. y 가 0 근처면 폭발

다중 클래스

클래스마다 정밀도·재현율을 구한 뒤 매크로 평균(클래스별 단순 평균, 작은 클래스도 동등하게)이나 마이크로 평균(전체 TP·FP·FN 을 합쳐 계산, 큰 클래스가 지배)으로 묶는다. 불균형 데이터에서 둘은 크게 다르다.

직접 해 보기

양성 4개, 음성 6개에 대한 모델 점수로 혼동 행렬, 지표, AUC 를 직접 계산한다.

y     = [1,1,1,1,0,0,0,0,0,0]
score = [.9,.8,.55,.3,.7,.45,.4,.2,.15,.1]
def cm(th):
    tp = sum(1 for t,s in zip(y,score) if t==1 and s>=th)
    fp = sum(1 for t,s in zip(y,score) if t==0 and s>=th)
    fn = sum(1 for t,s in zip(y,score) if t==1 and s<th)
    tn = sum(1 for t,s in zip(y,score) if t==0 and s<th)
    return tp, fp, fn, tn
for th in (0.5, 0.35):
    tp, fp, fn, tn = cm(th)
    acc = (tp+tn)/len(y); prec = tp/(tp+fp); rec = tp/(tp+fn)
    f1 = 2*prec*rec/(prec+rec)
    print(f"임계값 {th}: TP={tp} FP={fp} FN={fn} TN={tn} | 정확도={acc:.2f} 정밀도={prec:.2f} 재현율={rec:.2f} F1={f1:.2f}")
# AUC = 양성 점수가 음성 점수보다 클 확률
pos = [s for t,s in zip(y,score) if t==1]; neg = [s for t,s in zip(y,score) if t==0]
auc = sum((p>n) + 0.5*(p==n) for p in pos for n in neg) / (len(pos)*len(neg))
print(f"ROC AUC = {auc:.3f}")
# 모두 0 이라고 답하는 모델
print("항상 0 모델 정확도:", sum(1 for t in y if t==0)/len(y), "/ 재현율: 0.0")

실행 결과:

임계값 0.5: TP=3 FP=1 FN=1 TN=5 | 정확도=0.80 정밀도=0.75 재현율=0.75 F1=0.75
임계값 0.35: TP=3 FP=3 FN=1 TN=3 | 정확도=0.60 정밀도=0.50 재현율=0.75 F1=0.60
ROC AUC = 0.833
항상 0 모델 정확도: 0.6 / 재현율: 0.0

몇 가지를 읽어 낼 수 있다.

  • 임계값을 0.5 에서 0.35 로 낮췄는데 재현율은 그대로이고 정밀도만 떨어졌다. 놓친 양성(점수 0.3)이 0.35 보다도 낮기 때문이다. 임계값을 낮추면 재현율이 “오를 수 있다”는 것이지 반드시 오르는 것은 아니다.
  • AUC 0.833 은 양성-음성 쌍 24개 중 20개에서 양성 점수가 더 높다는 뜻이다. 임계값 선택과 무관하게 모델의 순서 매기기 능력만 본다.
  • “항상 0” 모델의 정확도 0.6 은 아무것도 배우지 않은 기준선이다. 우리 모델의 정확도 0.8 은 이 기준선과 비교해야 의미가 있다.

현업에서는

  • 지표는 업무 지표와 연결한다. 추천 모델의 오프라인 AUC 가 올라도 클릭률이 안 오를 수 있다. 오프라인 지표는 온라인 A/B 테스트의 예선일 뿐이다.
  • 임계값은 운영 단계에서 고른다. 모델은 점수를 내고, “하루 검토 가능한 건수 200건” 같은 운영 제약으로 임계값을 정하는 경우가 많다.
  • 지표에도 신뢰구간이 있다. 평가 데이터가 수백 건이면 정확도 1~2%p 차이는 우연일 수 있다. 부트스트랩으로 구간을 함께 보고한다.
  • 알림 설계와 같은 문제다. 홈랩 클러스터의 장애 알림 규칙도 결국 FP(헛경보)와 FN(놓친 장애)의 저울질이다. 알림 규칙을 바꿀 때 과거 사건에 대해 “몇 건을 잡고 몇 건을 헛울렸을지” 세어 보면 그것이 정밀도·재현율 평가다.

확인 문제

  1. 사기 거래가 0.1% 인 데이터에서 정확도가 지표로 부적절한 이유는?
  2. TP=30, FP=10, FN=20 일 때 정밀도, 재현율, F1 은?
  3. AUC 를 확률적으로 해석하라.
  4. 양성이 아주 드문 문제에서 ROC 보다 PR 곡선이 권장되는 이유는?
  5. 매크로 F1 과 마이크로 F1 이 크게 다르다면 무엇을 짐작할 수 있는가?

풀이

  1. 모두 정상이라고만 해도 정확도 99.9% 가 나와 사기를 하나도 못 잡는 모델과 구별되지 않는다.
  2. 정밀도 30/40 = 0.75, 재현율 30/50 = 0.6, F1 = 2·0.75·0.6/1.35 ≈ 0.667.
  3. 무작위로 고른 양성 하나와 음성 하나를 비교했을 때 양성의 점수가 더 높을 확률.
  4. TN 이 압도적으로 많아 FPR 이 작게 유지되므로 ROC 가 좋아 보인다. 정밀도는 TN 을 쓰지 않아 헛경보 비율을 그대로 드러낸다.
  5. 클래스 불균형이 있고, 작은 클래스에서의 성능이 큰 클래스보다 나쁘다(또는 그 반대).

더 읽을거리 (References)