Programming

[분류 성능 평가지표 4편] 피마 인디언 당뇨병 예측 실습으로 이해하는 분류 평가의 모든 것

Lucas.Kim 2025. 12. 13. 05:53
반응형

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값)과 스케일링은 성능에 직접적인 영향을 줍니다.
  • 임계값 조정을 통해 업무 목적에 맞는 모델을 설계할 수 있습니다.

이제 여러분은
👉 “분류 모델의 성능을 어떻게 해석해야 하는지”
👉 “업무 상황에 맞게 어떤 지표를 선택해야 하는지”

를 실전 데이터로 설명할 수 있는 수준에 도달했습니다.

 

반응형