스팸 메일 탐지기나 결제 이상 거래 탐지기는 결과가 둘 중 하나로 나뉘는 분류 모델이다. 그런데 "100개 중 95개를 맞혔다"는 사실만으로 좋은 모델이라고 말할 수 있을까?
위험한 거래가 100건 중 2건뿐이라면 모든 거래를 정상으로 예측해도 정확도는 98%가 나온다. 결국 중요한 것은 어떤 종류의 실수를 했는지다.
분류에서는 관심 대상을 Positive(양성)으로 정한다. 결제 이상 거래를 탐지한다면 다음처럼 정의할 수 있다.
1: 이상 거래(Positive)0: 정상 거래(Negative)실제값과 예측값의 조합은 네 가지다.
| 실제 | 예측 | 이름 | 의미 |
|---|---|---|---|
| 0 | 0 | TN | 정상 거래를 정상이라고 예측 |
| 0 | 1 | FP | 정상 거래를 이상 거래라고 잘못 예측 |
| 1 | 0 | FN | 이상 거래를 정상이라고 놓침 |
| 1 | 1 | TP | 이상 거래를 이상 거래라고 탐지 |
이 네 가지를 정리한 표가 혼동행렬(Confusion Matrix)이다.
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
즉 행은 실제 클래스, 열은 예측 클래스다. 라이브러리나 시각화 설정에 따라 축 표현이 달라질 수 있으므로 항상 레이블을 확인한다.
[
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는 중요한 오류를 숨길 수 있다.
[
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이 중요하다.
[
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보다 중요하다는 일반 법칙은 없다.
[
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 하나로 판단하는 것도 적절하지 않다.
classification_report()from sklearn.metrics import classification_report
print(classification_report(y_true, y_pred, zero_division=0))
이 함수는 클래스별 Precision, Recall, F1, Support 등을 출력한다.
불균형 데이터에서는 다수 클래스의 Weighted average가 좋아 보여도 소수 클래스의 Recall이 낮을 수 있다. 클래스별 행을 먼저 읽어야 한다.
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))
정확도가 좋아 보이는 기준 모델이 실제 양성 클래스를 하나도 찾아내지 못하는 경우가 있다는 사실을 확인할 수 있다.
분류 모델은 클래스를 바로 반환하는 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이 낮아질 수 있다. 반드시 실제 평가 결과로 확인해야 한다.
임계값은 테스트 데이터를 반복해서 보며 고르기보다 별도의 검증 데이터나 교차검증 예측으로 결정하는 것이 바람직하다.
| 질문 | 우선 확인할 지표 |
|---|---|
| 전체적으로 얼마나 맞혔나? | Accuracy |
| Positive라고 예측한 결과가 얼마나 믿을 만한가? | Precision |
| 실제 Positive를 얼마나 놓치지 않았나? | Recall |
| Precision과 Recall을 함께 비교하고 싶은가? | F1 |
| 어떤 유형의 오류가 발생했는가? | Confusion Matrix |
평균 점수가 아니라 어떤 실수가 더 위험한지부터 정의하면 올바른 지표를 고르기 쉬워진다.