분류 모델 평가지표: 혼동행렬부터 Precision·Recall·F1까지

Alchemist·2일 전

KT AIVLE

목록 보기
116/130

스팸 메일 탐지기나 결제 이상 거래 탐지기는 결과가 둘 중 하나로 나뉘는 분류 모델이다. 그런데 "100개 중 95개를 맞혔다"는 사실만으로 좋은 모델이라고 말할 수 있을까?

위험한 거래가 100건 중 2건뿐이라면 모든 거래를 정상으로 예측해도 정확도는 98%가 나온다. 결국 중요한 것은 어떤 종류의 실수를 했는지다.

1. Positive와 Negative를 먼저 정의하자

분류에서는 관심 대상을 Positive(양성)으로 정한다. 결제 이상 거래를 탐지한다면 다음처럼 정의할 수 있다.

  • 1: 이상 거래(Positive)
  • 0: 정상 거래(Negative)

실제값과 예측값의 조합은 네 가지다.

실제예측이름의미
00TN정상 거래를 정상이라고 예측
01FP정상 거래를 이상 거래라고 잘못 예측
10FN이상 거래를 정상이라고 놓침
11TP이상 거래를 이상 거래라고 탐지

이 네 가지를 정리한 표가 혼동행렬(Confusion Matrix)이다.

2. 혼동행렬을 코드로 확인하기

from sklearn.metrics import confusion_matrix

y_true = [0, 0, 0, 0, 1, 1, 1, 1]
y_pred = [0, 0, 1, 0, 1, 0, 1, 1]

cm = confusion_matrix(y_true, y_pred, labels=[0, 1])
print(cm)
# [[3 1]
#  [1 3]]

Scikit-learn의 일반적인 이진 혼동행렬은 다음 구조다.

             예측 0   예측 1
실제 0          TN       FP
실제 1          FN       TP

즉 행은 실제 클래스, 열은 예측 클래스다. 라이브러리나 시각화 설정에 따라 축 표현이 달라질 수 있으므로 항상 레이블을 확인한다.

3. Accuracy: 전체 중에서 맞힌 비율

[
Accuracy = \frac{TP+TN}{TP+TN+FP+FN}
]

from sklearn.metrics import accuracy_score

print(accuracy_score(y_true, y_pred))
# 0.75

전체 8건 중 6건을 맞혔으므로 정확도는 75%다.

하지만 데이터가 심하게 불균형하다면 Accuracy는 중요한 오류를 숨길 수 있다.

4. Precision: 이상 거래라고 했을 때 얼마나 맞는가?

[
Precision = \frac{TP}{TP+FP}
]

정밀도(Precision)는 모델이 Positive라고 예측한 사례 중 실제 Positive인 비율이다.

from sklearn.metrics import precision_score

print(precision_score(y_true, y_pred))
# 0.75

정밀도가 낮다면 정상 거래를 이상 거래로 잘못 표시하는 오탐(False Positive)이 많다는 뜻일 수 있다.

실제 서비스에서 정상 고객의 결제를 너무 자주 막는다면 고객 불만과 운영 비용이 발생할 수 있다. 이런 문제에서는 Precision이 중요하다.

5. Recall: 진짜 이상 거래 중 몇 개를 잡았는가?

[
Recall = \frac{TP}{TP+FN}
]

재현율(Recall)은 실제 Positive 사례 가운데 모델이 찾아낸 비율이다.

from sklearn.metrics import recall_score

print(recall_score(y_true, y_pred))
# 0.75

Recall이 낮다면 실제 이상 거래를 정상 거래로 놓치는 미탐(False Negative)이 많다는 뜻이다.

이상 거래를 놓쳐서 큰 손실이 발생하는 상황이라면 Recall을 우선시할 수 있다.

어떤 오류가 더 중요한지는 의료, 보안, 금융, 고객 지원 등 실제 업무의 비용 구조에 따라 달라진다. Recall이 항상 Precision보다 중요하다는 일반 법칙은 없다.

6. F1-Score: Precision과 Recall을 함께 고려

[
F1 = 2\times\frac{Precision\times Recall}{Precision+Recall}
]

F1-Score는 정밀도와 재현율의 조화평균이다. 한 지표만 높고 다른 지표가 낮으면 F1은 높아지기 어렵다.

from sklearn.metrics import f1_score

print(f1_score(y_true, y_pred))
# 0.75

두 지표의 균형을 확인할 때 편리하다. 다만 TN은 F1 계산에 직접 포함되지 않으므로, 모든 상황을 F1 하나로 판단하는 것도 적절하지 않다.

7. 모든 지표를 한 번에 보는 classification_report()

from sklearn.metrics import classification_report

print(classification_report(y_true, y_pred, zero_division=0))

이 함수는 클래스별 Precision, Recall, F1, Support 등을 출력한다.

  • Support: 평가 데이터에 실제로 존재하는 각 클래스의 건수
  • Macro average: 클래스별 지표를 단순 평균
  • Weighted average: 각 클래스의 실제 건수를 가중치로 평균

불균형 데이터에서는 다수 클래스의 Weighted average가 좋아 보여도 소수 클래스의 Recall이 낮을 수 있다. 클래스별 행을 먼저 읽어야 한다.

8. 실제 예측 모델로 확장하기

Scikit-learn에서 제공하는 인공 데이터 생성 함수를 활용해 불균형 분류 문제를 만든다.

from sklearn.datasets import make_classification
from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

X, y = make_classification(
    n_samples=1000,
    n_features=8,
    n_informative=5,
    n_redundant=1,
    weights=[0.9, 0.1],
    random_state=42,
)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42, stratify=y
)

baseline = DummyClassifier(strategy="most_frequent")
model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))

for name, estimator in [("최빈값 기준", baseline), ("로지스틱 회귀", model)]:
    estimator.fit(X_train, y_train)
    predicted = estimator.predict(X_test)
    print(f"\n[{name}]")
    print(confusion_matrix(y_test, predicted, labels=[0, 1]))
    print(classification_report(y_test, predicted, zero_division=0))

정확도가 좋아 보이는 기준 모델이 실제 양성 클래스를 하나도 찾아내지 못하는 경우가 있다는 사실을 확인할 수 있다.

9. 예측 확률과 임계값

분류 모델은 클래스를 바로 반환하는 predict() 외에 클래스별 확률 점수를 제공하는 predict_proba()를 지원하기도 한다.

model.fit(X_train, y_train)
positive_prob = model.predict_proba(X_test)[:, 1]

# 양성 판단 기준을 기본 0.5보다 낮춰 보기
predicted_035 = (positive_prob >= 0.35).astype(int)

print(classification_report(y_test, predicted_035, zero_division=0))

판정 임계값을 낮추면 일반적으로 더 많은 사례를 Positive라고 판정한다. 그러면 Recall이 높아질 수 있지만 FP도 늘어 Precision이 낮아질 수 있다. 반드시 실제 평가 결과로 확인해야 한다.

임계값은 테스트 데이터를 반복해서 보며 고르기보다 별도의 검증 데이터나 교차검증 예측으로 결정하는 것이 바람직하다.

10. 상황별로 무엇을 볼까?

질문우선 확인할 지표
전체적으로 얼마나 맞혔나?Accuracy
Positive라고 예측한 결과가 얼마나 믿을 만한가?Precision
실제 Positive를 얼마나 놓치지 않았나?Recall
Precision과 Recall을 함께 비교하고 싶은가?F1
어떤 유형의 오류가 발생했는가?Confusion Matrix

평균 점수가 아니라 어떤 실수가 더 위험한지부터 정의하면 올바른 지표를 고르기 쉬워진다.

관련 공식 문서

핵심 정리

  • 분류 모델은 단순한 정답 개수보다 오류 유형이 중요할 수 있다.
  • 혼동행렬은 TN, FP, FN, TP로 예측 결과를 정리한다.
  • Accuracy는 전체 예측 중 맞힌 비율이다.
  • Precision은 Positive로 예측한 건 중 실제 Positive의 비율이다.
  • Recall은 실제 Positive 중 찾아낸 비율이다.
  • F1-Score는 Precision과 Recall의 조화평균이다.
  • 불균형 데이터라면 클래스별 지표와 기준 모델을 반드시 확인한다.
  • 확률 임계값을 바꾸면 Precision과 Recall의 균형이 달라질 수 있다.
profile
html_programming_language

0개의 댓글