[ML] Classification with Supervised Learning

jul ee·2025년 5월 9일

데이터 성장기

목록 보기
89/139

🖇  데이터 확인
🖇  의사결정나무
🖇  랜덤포레스트
🖇  XGBoost
🖇  교차검증
🖇  평가(분류)


머신러닝은 크게 지도학습(Supervised Learning), 비지도학습(Unsupervised Learning), 강화학습(Reinforcement Learning)으로 나뉜다.

이 중 지도학습은 정답(label)이 주어진 데이터를 기반으로 예측 모델을 학습하는 방식이다. 지도학습의 대표적인 문제 유형은 분류(classification)회귀(regression)로 나뉘는데, 분류는 입력 데이터에 대해 미리 정의된 카테고리(클래스) 중 어떤 범주에 속하는지를 예측하는 문제를 의미한다. 예를 들어 이메일이 스팸인지 아닌지 구분하거나, 종양이 양성인지 악성인지 판별하는 문제 등이 분류 문제에 해당한다.

이 글에서는 사이킷런과 XGBoost를 활용하여

대표적인 분류 알고리즘인 의사결정나무, 랜덤포레스트, XGBoost를 실습해보고, 각 모델의 동작 방식과 하이퍼파라미터 설정이 성능에 어떤 영향을 주는지 확인해 보았다.

모델 평가에 있어 단순한 정확, 정밀도, 재현율, F1 점수, ROC-AUC와 같은 다양한 지표들을 함께 살펴보았고, 데이터셋을 보다 안정적으로 나누는 KFold, StratifiedKFold 기반 교차검증 방법까지 정리해 보았다.

자세한 데이터 및 출력 결과는 GitHub repository에서 확인할 수 있다.



🖇  데이터 확인

# 라이브러리 불러오기
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 데이터 생성
from sklearn.datasets import load_breast_cancer

def make_dataset():
    iris = load_breast_cancer()
    df = pd.DataFrame(iris.data, columns=iris.feature_names)
    df['target'] = iris.target
    X_train, X_test, y_train, y_test = train_test_split(
        df.drop('target', axis=1), df['target'], test_size=0.5, random_state=42
    )
    return X_train, X_test, y_train, y_test

X_train, X_test, y_train, y_test = make_dataset()
X_train.shape, X_test.shape, y_train.shape, y_test.shape

# 타겟 확인
y_train.value_counts()



🖇  의사결정나무

의사결정나무(Decision Tree)는 지도학습 알고리즘 중 하나로, 분류와 회귀 문제 모두에 사용할 수 있지만 특히 분류 문제에서 널리 활용되는 기법이다. 이 알고리즘은 매우 직관적인 구조를 가지고 있어 초보자도 이해하기 쉬운 것이 장점이다.

모델은 트리의 root에서 시작해, 정보이득(Information Gain)이 최대가 되는 특성(feature)을 기준으로 데이터를 반복적으로 분할하며 하위 노드를 구성한다. 이때 사용되는 불순도(impurity)를 측정하는 지표로는 주로 지니 지수(Gini Index)엔트로피(Entropy)가 사용되며, 두 지표 모두 데이터가 한 클래스에 몰려 있을수록 값이 0에 가까워지고, 클래스가 고르게 섞일수록 1에 가까워진다. 즉, 불순도가 낮은 쪽으로 데이터를 잘 나눌수록 정보이득이 크다고 판단하고 해당 특성을 선택해 분기한다.

하지만 의사결정나무는 데이터를 지나치게 세밀하게 나누는 경향이 있어 과대적합(overfitting)이 발생하기 쉬우며, 이를 방지하기 위해서는 트리의 깊이 제한(max_depth), 리프 노드의 최소 샘플 수(min_samples_leaf) 등과 같은 하이퍼파라미터를 적절히 조정하는 것이 필요하다.


의사결정나무

# 의사결정나무
from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier(random_state=0)
model.fit(X_train, y_train)
pred = model.predict(X_test)
accuracy_score(y_test, pred)

의사결정나무 하이퍼파라미터

  • criterion(기본값 gini): 불순도 지표 (또는 엔트로피 불순도 entropy)
  • max_depth(기본값 None): 최대 한도 깊이
  • max_leaf_modes(기본값 None): 리프 노드의 최대 개수
  • min_samples_split(기본값 2): 자식 노드를 갖기 위한 최소한의 데이터 수
  • min_samples_leaf(기본값 1): 리프 노드가 되기 위한 최소 샘플 수

💡 어떤 하이퍼파라미터 값이 좋은가?

데이터마다, 모델마다 다를 수 있다.
그리드서치나 랜덤서치 등의 방법으로 하이퍼파라미터 값을 자동을 찾아줄 수 있다.

# 의사결정나무 하이퍼파라미터: 예시
from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier(
    criterion='entropy',
    max_depth=7,
    min_samples_split=3,
    min_samples_leaf=2,
    random_state=0)
model.fit(X_train, y_train)
pred = model.predict(X_test)
accuracy_score(y_test, pred)



🖇  랜덤포레스트

랜덤포레스트(Random Forest)도 마찬가지로 지도학습 알고리즘으로, 분류와 회귀 문제 모두에 활용될 수 있으며, 특히 높은 성능과 안정성으로 많이 사용된다. 이 알고리즘은 이름 그대로 여러 개의 의사결정나무(Decision Tree)를 앙상블로 결합한 모델로, 개별 트리 하나에 의존하지 않고 다수의 트리를 조합함으로써 성능을 향상시키는 것이 특징이다.

랜덤포레스트는 학습 과정에서 부트스트랩 샘플링(bootstrap sampling)이라는 방식을 사용해, 전체 학습 데이터에서 중복을 허용하며 무작위로 샘플을 추출하여 각 트리를 학습시킨다. 이렇게 학습된 여러 개의 트리는 예측 단계에서 다수결(voting) 방식으로 최종 예측값을 결정하며, 이를 통해 단일 트리에서 발생하기 쉬운 과대적합 문제를 효과적으로 완화할 수 있다.

랜덤포레스트는 앙상블 학습 기법 중 배깅(bagging) 방식에 해당하는데, 이는 동일한 알고리즘(여기서는 결정트리)을 기반으로 여러 개의 모델을 독립적으로 학습시켜 결과를 통합하는 방식이다.

반면, XGBoost와 같은 알고리즘은 부스팅(boosting) 방식으로, 이전 모델의 오차를 보완하면서 순차적으로 학습을 이어가는 점에서 차이가 있다.

랜덤포레스트

# 랜덤포레스트
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(random_state=0)
model.fit(X_train, y_train)
pred = model.predict(X_test)
accuracy_score(y_test, pred)

랜덤포레스트 하이퍼파라미터

  • n_estimators(기본값 100): 트리의 수
  • criterion(기본값 gini): 불순도 지표
  • max_depth(기본값 None): 최대 한도 깊이
  • min_samples_split(기본값 2): 자식 노드를 갖기 위한 최소한의 데이터 수
  • min_samples_leaf(기본값 1): 리프 노드가 되기 위한 최소 샘플 수
# 랜덤포레스트 하이퍼파라미터: 예시
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(
    n_estimators=300,
    random_state=0)
model.fit(X_train, y_train)
pred = model.predict(X_test)
accuracy_score(y_test, pred)

# 파라미터 변경에 따른 모델 결과 (현재 기준 변경사항만)
# 0.968421052631579# 0.9508771929824561 - max_depth=3, default*



🖇  XGBoost

  • eXtreme Gradient Boosting
  • 부스팅(앙상블) 기반 알고리즘
    • 트리 앙상블 중 성능이 좋은 알고리즘
  • 약한 학습기가 계속해서 업데이트를 하며 좋은 모델을 만들어 감
  • 캐글(글로벌 AI 경진대회)에서 뛰어난 성능을 보이면서 인기가 높아짐
# xgboost
from xgboost import XGBClassifier
model = XGBClassifier(random_state=0)
model.fit(X_train, y_train)
pred = model.predict(X_test)
accuracy_score(y_test, pred)

XGBoost 하이퍼파라미터

  • booster(기본값 gbtree): 부스팅 알고리즘 (또는 dart, gblinear)
  • objective(기본값 binary:logistic): 이진분류 (다중분류: multi:softmax)
  • max_depth(기본값 6): 최대 한도 깊이
  • learning_rate(기본값 0.1): 학습률
  • n_estimators(기본값 100): 트리의 수
  • subsample(기본값 1): 훈련 샘플 개수의 비율
  • colsample_bytree(기본값 1): 특성 개수의 비율
  • n_jobs(기본값 1): 사용 코어 수 (-1: 모든 코어를 다 사용)

주의) leanrinng_rate 값을 낮췄다면, n_estimators 값은 높여야 한다.

learning_rate ↓ → n_estimators ↑

learning_rate는 각 트리의 기여도를 조절하는 보폭 역할을 한다. 작게 설정하면 모델이 신중하게 조금씩 가중치를 조정하며 학습한다.
(e.g., 학습률 0.1 → 트리 하나가 예측에 10%)

💡 learning_rate를 낮추면 각 트리의 영향력이 줄어들기 때문에 모델이 충분히 학습되기 위해 n_estimators를 더 높게 설정해야 한다.

  • 낮은 learning_rate에 트리 개수가 적으면 학습 부족
  • 높은 learning_rate에 트리 개수를 너무 많이 주면 과적합
# xgboost 하이퍼파라미터: 예시*from xgboost import XGBClassifier
model = XGBClassifier(
    booster='gbtree',
    objective='binary:logistic',
    max_depth=5,
    learning_rate=0.07,
    n_estimators=200,
    subsample=1,
    colsample_bytree=1,
    n_jobs=-1,
    random_state=0)
model.fit(X_train, y_train)
pred = model.predict(X_test)
accuracy_score(y_test, pred)

# 0.9649122807017544
# 0.968421052631579  - max_depth=3, default
# 0.9719298245614035 - learning_rate=0.07, n_estimators=200
#                    - learning_rate=0.05, n_estimators=500
# 조기종료
model = XGBClassifier(
    # eval_metric='logloss',
    early_stopping_rounds=10,  # 10번을 돌렸는데 더이상 성능 향상이 없으면 멈춤
    learning_rate=0.07,
    n_estimators=200,
    random_state=0)
eval_set = [(X_test, y_test)]
model.fit(X_train, y_train, eval_set=eval_set,)
pred = model.predict(X_test)
accuracy_score(y_test, pred)



🖇  교차검증

일반적으로 모델을 학습시킬 때 데이터를 train set과 test set으로 나누어 train set을 가지고 학습을 수행한다.

교차검증이란 여기서 train set을 다시 train set과 validation set으로 나누어 학습 중 검증과 수정을 수행하는 것을 의미한다.

# 데이터셋 로드
def make_dataset2():
    iris = load_breast_cancer()
    df = pd.DataFrame(iris.data, columns=iris.feature_names)
    df['target'] = iris.target
    return df.drop('target', axis=1), df['target']
X, y = make_dataset2()

Kfold

  • 일반적으로 사용되는 교차 검증 기법
# KFold*from sklearn.model_selection import KFold
model = DecisionTreeClassifier(random_state=0)

kfold = KFold(n_splits=5)
for train_idx, test_idx in kfold.split(X):
    X_train, X_test = X.iloc[train_idx], X.iloc[test_idx]
    y_train, y_test = y.iloc[train_idx], y.iloc[test_idx]

    model.fit(X_train, y_train)
    pred = model.predict(X_test)
    print(accuracy_score(y_test, pred))

StratifiedKfold

  • 불균형한 타겟 비율을 가진 데이터(불균형 데이터)가 한쪽으로 치우치는 것을 방지
# Stratified Kfold
from sklearn.model_selection import StratifiedKFold
model = DecisionTreeClassifier(random_state=0)

kfold = StratifiedKFold(n_splits=5)
# 타켓 데이터(y)가 split이 되는 것이 아니라, 타켓 확인을 위해 추가
for train_idx, test_idx in kfold.split(X, y):
    X_train, X_test = X.iloc[train_idx], X.iloc[test_idx]
    y_train, y_test = y.iloc[train_idx], y.iloc[test_idx]

    model.fit(X_train, y_train)
    pred = model.predict(X_test)
    print(accuracy_score(y_test, pred))

사이킷런 교차검증

  • 사이킷런 내부 API를 통해 fit(학습) - predict(예측) - evaluation(평가)
# 교차검증
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv=3)
scores

# 평균 점수
scores.mean()

# 교차검증 Stratified Kfold
kfold = StratifiedKFold(n_splits=5)
scores = cross_val_score(model, X, y, cv=kfold)
scores

# 평균점수
scores.mean()



🖇  평가(분류)

  • 정확도 accuracy: 실제 값과 예측값이 일치하는 비율

  • 정밀도 precision: 양성이라고 예측한 값 중 - 실제 양성인 값의 비율 (암이라고 예측 한 값 중 실제 암)

  • 재현율 recall: 실제 양성 값 중 양성으로 예측한 값의 비율 (암을 암이라고 판단)

  • F1: 정밀도와 재현율의 조화평균 (정밀도와 재현율은 trade-off 관계)

  • ROC-AUC

    • ROC: 참 양성 비율(True Positive Rate)에 대한 거짓 양성 비율(False Positive Rate) 곡선

    • AUC: ROC곡선 아래 면적 (완벽하게 분류되면 AUC가 1임)

# 정확도
from sklearn.metrics import accuracy_score
accuracy_score(y_test, pred)

# 정밀도
from sklearn.metrics import precision_score
precision_score(y_test, pred)

# 재현율
from sklearn.metrics import recall_score
recall_score(y_test, pred)

# f1
from sklearn.metrics import f1_score
f1_score(y_test, pred)

# roc_auc
from sklearn.metrics import roc_auc_score
model = XGBClassifier(random_state=0)
model.fit(X_train, y_train)
pred = model.predict_proba(X_test)  # predict_proba: 클래스별 '확률' 결과 값(0, 1)

roc_auc_score(y_test, pred[:,1])  # 1일 확률 기준으로 AUC 계산 (,: 다차원 배열에서 행과 열을 구분하여 인덱싱)



인사이트 및 회고

모델 적용 시 하이퍼파라미터 튜닝과 평가 지표의 선택이 모델 성능에 큰 영향을 줄 수 있다. XGBoost에서는 learning_rate와 n_estimators 간의 trade-off를 조정해 성능을 안정적으로 끌어올릴 수 있고, max_depth와 같은 트리의 구조적 제약도 모델의 복잡도 조절에 효과적이다.

정확도(accuracy)만으로는 모델 성능을 판단하기 어렵다. 특히 불균형 데이터셋을 다룰 경우 정밀도와 재현율 간의 균형이 중요하며, 이를 통합적으로 볼 수 있는 F1 점수와 ROC-AUC 점수가 신뢰할 수 있는 기준이 될 수 잇다.

교차검증에서는 StratifiedKFold를 활용하면 타겟 클래스의 분포를 유지하면서 데이터셋을 나누는 방식이 안정적인 성능 평가로 이어질 수 있다.

이번에 정리한 분류 모델들을 기반으로 다양한 데이터셋에서 성능 비교를 해보고, 하이퍼파라미터 최적화를 위한 그리드서치, 랜덤서치 등으로 확장해 볼 필요를 느꼈다.

profile
AI에 관심을 가지고, 데이터로 가치를 만들어 나가는 과정을 기록합니다.

0개의 댓글