
1~3편에서는 분류 성능 평가의 이론적 기반을 단계적으로 정리했습니다.
- 1편: 정확도(Accuracy)의 한계
- 2편: 오차행렬, 정밀도·재현율, 트레이드오프
- 3편: F1 Score와 ROC-AUC
이번 4편은 이 모든 개념을 실제 데이터에 적용하는 종합 실습 편입니다.
대표적인 의료 데이터셋인 **피마 인디언 당뇨병 데이터셋(Pima Indians Diabetes Dataset)**을 이용해,
- 데이터 분포 확인
- 로지스틱 회귀 기반 기본 모델 학습
- 정확도·정밀도·재현율·F1·ROC-AUC 평가
- 데이터 품질 문제(이상치, 0값 처리) 개선
- 스케일링 적용
- 분류 결정 임계값(Threshold) 조정
을 순차적으로 수행하며,
**“왜 분류 평가는 지표를 하나만 보면 안 되는지”**를 명확하게 이해하는 것이 목표입니다.
1. 데이터 로딩 및 레이블 분포 확인
# -----------------------------------------------------------
# 1. 라이브러리 및 데이터 로딩
# -----------------------------------------------------------
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
accuracy_score, precision_score, recall_score,
f1_score, roc_auc_score, confusion_matrix
)
from sklearn.preprocessing import StandardScaler, Binarizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_recall_curve, roc_curve
# 피마 인디언 당뇨병 데이터 로딩
diabetes_data = pd.read_csv('./indians/diabetes.csv')
# 상위 데이터 확인
display(diabetes_data.head(5))
# 타깃 레이블(Outcome) 분포 확인
print(diabetes_data["Outcome"].value_counts())
0 500
1 268

👉 당뇨병이 없는 경우(0)가 더 많은 불균형 데이터입니다.
이 시점에서 이미 정확도 하나만으로 평가는 위험하다는 것을 예상할 수 있습니다.
2. 분류 성능 평가 함수 정의
여러 지표를 매번 직접 계산하는 것은 비효율적이므로,
평가 결과를 한 번에 확인할 수 있는 함수를 정의합니다.
def get_clf_eval(y_test, pred, pred_proba):
"""
분류 성능 평가 함수
- 오차행렬
- 정확도, 정밀도, 재현율, F1 Score, ROC-AUC 출력
"""
confusion = confusion_matrix(y_test, pred)
accuracy = accuracy_score(y_test, pred)
precision = precision_score(y_test, pred)
recall = recall_score(y_test, pred)
f1 = f1_score(y_test, pred)
# ROC-AUC는 반드시 Positive 클래스 확률(1차원)이 필요
roc_auc = roc_auc_score(y_test, pred_proba)
print("---------------------------------")
print("Confusion Matrix")
print(confusion)
print(f"정확도 : {accuracy}")
print(f"정밀도 : {precision}")
print(f"재현율 : {recall}")
print(f"F1 Score : {f1}")
print(f"ROC AUC : {roc_auc}")
3. 기본 로지스틱 회귀 모델 학습 및 평가
3-1. 학습/테스트 데이터 분리
# 입력 피처(X)와 타깃(y) 분리
X = diabetes_data.iloc[:, :-1]
y = diabetes_data.iloc[:, -1]
# 레이블 비율을 유지하기 위해 stratify 옵션 사용
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42,
stratify=y
)
3-2. 로지스틱 회귀 학습 및 예측
# 로지스틱 회귀 모델 생성
lr_clf = LogisticRegression(solver="liblinear")
# 학습
lr_clf.fit(X_train, y_train)
# 예측 결과
pred = lr_clf.predict(X_test)
# Positive 클래스(1)에 대한 예측 확률
pred_proba = lr_clf.predict_proba(X_test)[:, 1]
# 성능 평가
get_clf_eval(y_test, pred, pred_proba)
Confusion Matrix
[[85 15]
[27 27]]
정확도 : 0.7272
정밀도 : 0.6428
재현율 : 0.5000
F1 Score : 0.5625
ROC AUC : 0.8285
👉 정확도는 그럴듯해 보이지만,
재현율이 낮아 실제 당뇨 환자를 상당수 놓치고 있음을 확인할 수 있습니다.
4. 데이터 품질 문제: 0값은 정상적인가?
의료 데이터에서 다음 값들이 0이 될 수 있을까요?
- 혈당(Glucose)
- 혈압(BloodPressure)
- 인슐린(Insulin)
# 기초 통계 확인
diabetes_data.describe()

# Glucose 분포 확인
plt.hist(diabetes_data["Glucose"], bins=100)
plt.show()
👉 Glucose 값이 0인 데이터는 현실적으로 존재할 수 없는 이상치입니다.

4-1. 0값 비율 확인
zero_features = ["Glucose", "BloodPressure", "SkinThickness", "Insulin", "BMI"]
total_count = diabetes_data["Glucose"].count()
for col in zero_features:
zero_count = diabetes_data[diabetes_data[col] == 0][col].count()
print(f"{col} → 0값 개수: {zero_count}, 비율: {100 * zero_count / total_count:.2f}%")
👉 특히 Insulin, SkinThickness는 절반 가까이가 0입니다.
이는 모델 성능에 큰 악영향을 줍니다.

5. 0값을 평균값으로 대체 (데이터 클린징)
# 0값을 각 컬럼의 평균값으로 대체
diabetes_data[zero_features] = diabetes_data[zero_features].replace(
0, diabetes_data[zero_features].mean()
)

6. 스케일링 적용 후 재학습
로지스틱 회귀는 스케일에 민감한 모델이므로,
StandardScaler를 적용합니다.
# 입력/타깃 재분리
X = diabetes_data.iloc[:, :-1]
y = diabetes_data.iloc[:, -1]
# 표준화
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y,
test_size=0.2,
random_state=156,
stratify=y
)
# 재학습
lr_clf = LogisticRegression(solver="liblinear")
lr_clf.fit(X_train, y_train)
pred = lr_clf.predict(X_test)
pred_proba = lr_clf.predict_proba(X_test)[:, 1]
get_clf_eval(y_test, pred, pred_proba)
👉 데이터 전처리만으로도 재현율과 F1 Score가 개선되는 것을 확인할 수 있습니다.

7. 분류 결정 임계값(Threshold) 조정
이제 재현율을 더 높이기 위해 임계값을 조정해봅니다.
def get_eval_by_threshold(y_test, pred_proba_c1, thresholds):
"""
여러 임계값에 대해 분류 성능을 비교하는 함수
"""
for custom_threshold in thresholds:
binarizer = Binarizer(threshold=custom_threshold)
custom_pred = binarizer.fit_transform(pred_proba_c1)
print(f"\n임계값 : {custom_threshold}")
get_clf_eval(y_test, custom_pred, pred_proba_c1)
thresholds = [0.3, 0.33, 0.36, 0.39, 0.42, 0.45, 0.48, 0.5]
get_eval_by_threshold(y_test, pred_proba.reshape(-1, 1), thresholds)
👉 임계값을 낮출수록
- 재현율은 증가
- 정밀도는 감소
임계값 0.3
---------------------------------
Confusion Matrix
정확도 : [[65 35]
[11 43]]
정밀도 : 0.7012987012987013
재현율 : 0.5512820512820513
f1 : 0.6515151515151515
ROC AUC : 0.8433333333333334
임계값 0.33
---------------------------------
Confusion Matrix
정확도 : [[71 29]
[11 43]]
정밀도 : 0.7402597402597403
재현율 : 0.5972222222222222
f1 : 0.6825396825396824
ROC AUC : 0.8433333333333334
임계값 0.36
---------------------------------
Confusion Matrix
정확도 : [[76 24]
[15 39]]
정밀도 : 0.7467532467532467
재현율 : 0.6190476190476191
f1 : 0.6666666666666666
ROC AUC : 0.8433333333333334
임계값 0.39
---------------------------------
Confusion Matrix
정확도 : [[78 22]
[16 38]]
정밀도 : 0.7532467532467533
재현율 : 0.6333333333333333
f1 : 0.6666666666666667
ROC AUC : 0.8433333333333334
임계값 0.42
---------------------------------
Confusion Matrix
정확도 : [[84 16]
[18 36]]
정밀도 : 0.7792207792207793
재현율 : 0.6923076923076923
f1 : 0.6792452830188679
ROC AUC : 0.8433333333333334
임계값 0.45
---------------------------------
Confusion Matrix
정확도 : [[85 15]
[18 36]]
정밀도 : 0.7857142857142857
재현율 : 0.7058823529411765
f1 : 0.6857142857142857
ROC AUC : 0.8433333333333334
임계값 0.48
---------------------------------
Confusion Matrix
정확도 : [[88 12]
[19 35]]
정밀도 : 0.7987012987012987
재현율 : 0.7446808510638298
f1 : 0.6930693069306931
ROC AUC : 0.8433333333333334
임계값 0.5
---------------------------------
Confusion Matrix
정확도 : [[90 10]
[21 33]]
정밀도 : 0.7987012987012987
재현율 : 0.7674418604651163
f1 : 0.6804123711340206
ROC AUC : 0.8433333333333334
하는 정밀도–재현율 트레이드오프가 명확히 드러납니다.
8. 최종 선택: 임계값 0.48
# 임계값 0.48 적용
binarizer = Binarizer(threshold=0.48)
pred_th_048 = binarizer.fit_transform(pred_proba.reshape(-1, 1))
get_clf_eval(y_test, pred_th_048, pred_proba)
👉 당뇨병 예측이라는 의료 도메인 특성상 재현율을 더 중시하는 전략입니다.

핵심 개념 요약: 분류 성능 평가, 문제로 다시 정리해보기
앞선 실습과 설명을 통해 분류 성능 평가의 전반적인 흐름을 살펴보았습니다.
이제 마지막으로, 실제 자주 출제되는 문제 유형을 통해 핵심 개념을 한 번 더 정리해보겠습니다.
아래 문제들은 모두 이번 1~4편에서 다룬 내용만 정확히 이해하고 있다면 충분히 풀 수 있는 수준입니다.
분류 결정 임계값(Threshold)과 정밀도·재현율의 관계
문제
분류 모델의 분류 결정 임계값(Classification Threshold)을 낮추면 일반적으로 재현율(Recall)과 정밀도(Precision)에 어떤 영향을 미칠까요?
정답
👉 재현율은 증가하고, 정밀도는 감소합니다.
이유 설명
임계값을 낮춘다는 것은 Positive(양성)로 판단하는 기준을 완화한다는 의미입니다.
그 결과,
- 실제 양성을 더 많이 잡아낼 수 있어 재현율(Recall)은 증가합니다.
- 하지만 음성을 양성으로 잘못 예측하는 경우(FP)도 함께 증가하여 정밀도(Precision)는 감소합니다.
이는 앞에서 실습으로 확인한 **정밀도–재현율 트레이드오프(Trade-off)**의 전형적인 예입니다.
2️⃣ 의료·암 진단과 같은 문제에서 가장 중요한 평가지표
문제
암 진단처럼 실제 양성을 놓쳤을 때 심각한 결과를 초래하는 예측 문제에서는 어떤 평가지표를 더 중요하게 고려해야 할까요?
정답
👉 재현율(Recall)
이유 설명
재현율은
실제 양성 중에서 모델이 얼마나 많은 양성을 제대로 찾아냈는지
를 나타내는 지표입니다.
의료 진단, 금융 사기 탐지처럼 FN(False Negative)를 최소화해야 하는 문제에서는
정확도나 정밀도보다 재현율이 더 중요한 판단 기준이 됩니다.
3️⃣ 오차 행렬에서 FN(False Negative)의 의미
문제
오차 행렬(Confusion Matrix)에서 재현율 계산에 영향을 미치는 FN은 무엇을 의미할까요?
정답
👉 실제는 양성인데, 모델이 음성으로 잘못 예측한 경우
이유 설명
재현율 공식은 다음과 같습니다.
Recall=TPTP+FNRecall = \frac{TP}{TP + FN}
즉, FN이 커질수록 재현율은 급격히 감소합니다.
따라서 재현율이 중요한 문제에서는 FN을 얼마나 줄이느냐가 핵심이 됩니다.
4️⃣ 불균형 데이터에서 정확도(Accuracy)가 위험한 이유
문제
불균형 데이터셋에서 정확도가 성능 평가에 오해를 줄 수 있는 가장 큰 이유는 무엇일까요?
정답
👉 다수 클래스의 예측 정확도만 높게 나타날 수 있기 때문입니다.
이유 설명
예를 들어 전체 데이터의 90%가 음성(0)이라면,
모든 데이터를 0으로 예측해도 정확도는 90%가 됩니다.
하지만 이 경우
- 소수 클래스(양성)에 대한 예측 성능은 완전히 무시됩니다.
그래서 불균형 데이터에서는 반드시
정밀도, 재현율, F1 Score, ROC-AUC를 함께 확인해야 합니다.
5️⃣ 정밀도와 재현율의 균형을 고려한 지표
문제
정밀도(Precision)와 재현율(Recall)의 균형을 고려해 모델 성능을 평가하고 싶을 때 주로 사용하는 지표는 무엇일까요?
정답
👉 F1 Score
이유 설명
F1 Score는 정밀도와 재현율의 조화 평균입니다.
F1=2×Precision×RecallPrecision+RecallF1 = 2 \times \frac{Precision \times Recall}{Precision + Recall}
두 지표 중 하나만 극단적으로 높고 다른 하나가 낮으면 F1 Score는 낮아집니다.
따라서 두 지표의 균형이 중요한 문제에서 매우 유용한 평가 지표입니다.
4편 전체 마무리 정리
이번 분류 성능 평가 시리즈(1~4편)를 통해 다음을 정리했습니다.
- 정확도는 가장 직관적이지만 가장 위험한 지표일 수 있습니다.
- 오차 행렬을 통해 예측 오류의 유형을 반드시 확인해야 합니다.
- 정밀도와 재현율은 업무 목적에 따라 중요도가 달라집니다.
- F1 Score는 두 지표의 균형을 평가하는 핵심 지표입니다.
- ROC-AUC는 임계값에 독립적인 전반적인 분류 성능 지표입니다.
- 분류 결정 임계값 조정은 모델을 실무에 맞게 튜닝하는 핵심 도구입니다.
이제 분류 모델을 평가할 때
👉 “정확도가 몇 %인가?”가 아니라
👉 “이 문제에서 가장 중요한 지표는 무엇인가?”를 먼저 고민할 수 있는 단계에 도달했습니다.
이번 4편에서는 피마 인디언 당뇨병 데이터를 통해
분류 성능 평가의 전 과정을 실전 흐름으로 정리했습니다.
핵심 요약입니다.
- 분류 평가는 정확도 하나로 절대 판단하지 않습니다.
- 오차행렬, 정밀도, 재현율, F1, ROC-AUC를 함께 봐야 합니다.
- 데이터 품질(이상치, 0값)과 스케일링은 성능에 직접적인 영향을 줍니다.
- 임계값 조정을 통해 업무 목적에 맞는 모델을 설계할 수 있습니다.
이제 여러분은
👉 “분류 모델의 성능을 어떻게 해석해야 하는지”
👉 “업무 상황에 맞게 어떤 지표를 선택해야 하는지”
를 실전 데이터로 설명할 수 있는 수준에 도달했습니다.
'Programming' 카테고리의 다른 글
| 결정트리 속성 중요도(Feature Importance)와 과적합(Overfitting) 쉽게 이해하기 (0) | 2025.12.16 |
|---|---|
| 결정트리(Decision Tree) 완전 정리: 개념부터 앙상블, 하이퍼파라미터, 시각화까지 (0) | 2025.12.16 |
| [분류 성능 평가지표 3편] F1 Score·ROC-AUC로 이진 분류 성능 완성하기 (0) | 2025.12.13 |
| [분류 성능 평가지표 2편] 오차행렬·정밀도·재현율 그리고 트레이드오프 (0) | 2025.12.13 |
| [분류 성능 평가지표 1편] 정확도(Accuracy)는 왜 조심해서 봐야 할까? (0) | 2025.12.13 |