Skip to main content

의사결정나무 구축과 최적화 방법

의사결정나무에 대한 종합 가이드로, 이론, 모델 구축, 평가, 그리고 Random Forest와 XGBoost 같은 고급 알고리즘을 포함합니다. 이 글은 AI 번역본입니다. 오역이 의심되면 댓글로 알려주세요.
Created on September 15|Last edited on September 15


목차



의사결정나무 이해하기

의사결정나무 단순하지만 강력하다 머신 러닝 이메일이 스팸인지 아닌지 판단하거나 주택 가격을 예측하는 등 의사결정이나 예측을 도와주는 도구입니다. 일상에서 결정을 내리듯 일련의 예/아니오 질문을 통해 작동하므로 이해하고 사용하기 쉽습니다. 숫자형과 범주형 등 다양한 유형의 데이터를 다룰 수 있어 금융, 의료 등 여러 분야에서 널리 활용됩니다.
이 글에서는 Decision Tree의 기본부터 Weights & Biases를 활용한 실제 프로젝트 적용까지 꼭 알아야 할 모든 내용을 다룹니다. 먼저 Decision Tree가 무엇이며 어떻게 동작하는지 살펴보고, 데이터 분할 기준을 비롯해 모델이 작동하는 수학적 원리를 이해합니다. 그다음 타이타닉 생존자 예측 프로젝트로 배운 내용을 실습해 보겠습니다. 진행하면서 Decision Tree의 성능을 높이는 방법을 익히고, Decision Tree와 연관된 고급 모델인 Random Forest와 XGBoost도 함께 살펴봅니다.

의사결정나무의 이론적 기반

의사결정나무는 간단하고 해석 가능하다는 점에서 널리 알려진 많은 머신 러닝 알고리즘의 근본적인 구성 요소입니다. 이는 다음과 같은 용도로 사용됩니다 분류 작업, 여기서 목표는 입력을 두 개 이상의 클래스로 분류하는 것이며, 그리고 회귀 작업, 여기서 목표는 연속값을 예측하는 것입니다. 이제 의사결정나무의 이론적 기반을 살펴보겠습니다. 트리의 구조, 분할 기준, 그리고 의사결정나무의 유형을 포함합니다.

의사결정나무의 구조

의사결정나무는 노드, 간선, 리프로 구성되며 트리와 유사한 구조로 배치됩니다.

루트 노드: 의사결정이 시작되는 최상위 노드입니다.
내부 노드: 이 노드들은 특정 속성을 검사하고 그 결과에 따라 분기합니다. 각 내부 노드는 데이터셋의 특징(또는 속성)에 대응합니다.
간선/분기: 이들은 테스트 결과를 나타내며 다음 노드나 리프와 연결됩니다.
리프 노드: 이 노드들은 의사결정 또는 최종 결과를 나타냅니다. 분류 트리에서는 클래스 레이블을, 회귀 트리에서는 연속값을 나타냅니다.

의사결정나무의 주된 목적은 주어진 기준에 따라 최종 결론에 도달하도록 돕는 것입니다.
예를 들어, 아래 의사결정나무는 특정 날에 달리기를 할지 말지를 결정하는 데 도움을 줍니다.

각 노드의 의사결정 분기를 따라가며 탐색하면 우리의 질문에 대한 답에 도달할 수 있고, 그 과정에서 정확하고 근거 있는 결론을 내릴 수 있습니다.

분할 기준

머신러닝에서처럼 더 복잡한 의사결정나무의 경우, 정확한 트리를 구축하는 일은 까다롭습니다. 단순한 예/아니오 결정이 아니라, 여러 요인과 다양한 수치가 함께 작용하기 때문입니다. 가장 큰 과제는 트리의 각 분기를 어떤 방식으로 분할하는 것이 최선인지 찾아내는 것입니다.
그렇다고 해도, 의사결정나무를 구축하기 위해 각 노드를 분할하는 데에는 여러 가지 전략이 있습니다. 주로 사용되는 두 가지 방법은 다음과 같습니다:

  • 정보 이득: 이는 ID3와 C4.5 같은 의사결정나무 알고리즘에서 사용됩니다. 정보 이득은 데이터셋을 특정 속성으로 분할한 뒤 엔트로피 또는 불순도가 얼마나 감소했는지를 측정합니다. 목표는 정보 이득을 최대화하는 것이며, 정보 이득이 높을수록 그룹의 동질성이 더 커집니다.

정보 이득 = 엔트로피(부모) − ∑(자식의 가중치 × 엔트로피(자식))

  • 지니 불순도: CART(Classification and Regression Trees) 알고리즘에서 사용되는 지니 불순도는, 부분집합의 레이블 분포에 따라 무작위로 레이블을 할당했을 때 데이터셋의 임의의 요소가 잘못 레이블될 빈도를 측정합니다. 트리는 지니 불순도가 가장 낮아지는 분할을 선택합니다.

지니 불순도 = 1 − ∑ p_i^2

여기서 pi는 항목이 특정 클래스에 분류될 확률을 의미합니다.

의사결정나무 노드 분할 예시



1단계: 전체 불순도 계산하기(분류용 지니)

먼저 전체 데이터셋의 지니 불순도를 계산합니다. Buys의 클래스가 “Yes”와 “No” 두 가지뿐이라고 가정합니다.

  • 총 인스턴스: 14
  • Yes 인스턴스: 9
  • No 인스턴스: 5

전체 지니 = 1 − ((9/14)^2 + (5/14)^2) ≈ 0.459


2단계: 각 분할의 불순도 계산하기

다음으로 Age와 Income을 기준으로 데이터를 어떻게 분할할지 살펴봅니다.

Age로 분할
  • Young: 5개 인스턴스(Yes 2개, No 3개)
  • Middle: 4개 인스턴스(Yes 4개, No 0개)
  • Old: 5개 인스턴스(Yes 3개, No 2개)

Age 분할의 가중 평균 지니 ≈ 0.343

각 소득 그룹의 지니 불순도를 계산한 뒤, Income 기준 분할의 가중 평균 지니 불순도를 계산하세요.

Income로 분할
  • High: 4개 인스턴스(Yes 2개, No 2개)
  • Medium: 6개 인스턴스(Yes 3개, No 3개)
  • Low: 4개 인스턴스(Yes 2개, No 2개)

Income 분할의 가중 평균 지니 ≈ 0.500

각 소득 그룹의 지니 불순도를 계산한 뒤, Income 기준 분할의 가중 평균 지니 불순도를 계산하세요.


3단계: 최적 분할 결정

최적 분할은 가중 평균 지니 불순도가 가장 낮아 Buys 결과에 대한 불확실성이 더 크게 감소하는 분할입니다. 따라서 최적 분할은 가중 평균 지니 불순도가 0.343으로 가장 낮은 나이입니다.

나이 기준 분할의 예시 계산
예시로 Young 그룹의 지니 불순도를 계산해 보겠습니다:
GiniYoung=1((2/5)2+(3/5)2)GiniYoung=1−((2/5)2+(3/5)2)


그다음 Middle과 Old에 대해서도 동일하게 지니 불순도를 계산하고, Age 전체 분할에 대한 가중 평균 지니 불순도를 구합니다.



사용한 데이터셋



이번 실습 부분에서는 다음을 다루겠습니다 Titanic 데이터셋머신러닝 커뮤니티에서 폭넓게 연구되고 풍부한 정보를 담은 데이터셋입니다.
Titanic 데이터셋은 불운한 타이타닉 항해에 탑승한 승객들에 대한 통찰을 제공하는 12개의 서로 다른 열로 구성되어 있습니다. 이들 열에는 승객 등급(Pclass), 이름, 성별, 나이, 함께 탑승한 형제/배우자 수(SibSp), 함께 탑승한 부모/자녀 수(Parch), 티켓 번호, 요금, 객실 번호, 승선 항구와 같은 중요한 속성이 포함됩니다. 가장 중요�� 것은 'Survived' 열로, 각 승객이 재난에서 생존했는지를 나타내며, 1은 생존, 0은 비생존을 의미합니다.
이 데이터셋은 머신러닝에서 분류 문제의 기초적인 벤치마크로 활용됩니다. 우리는 이러한 속성들을 분석하여 승객의 생존 여부를 예측하는 결정 트리 모델을 구축하고자 합니다. 이러한 모델은 이 비극적 사건에서 생존 가능성에 영향을 미친 핵심 요인을 이해하는 데 도움을 줍니다.

데이터셋 준비하기

1단계: 필요한 라이브러리 가져오기

먼저 데이터 조작, 시각화, 머신러닝 모델링 및 평가를 위해 필수적인 Python 라이브러리를 가져오겠습니다. 이 단계는 이후의 모든 데이터 분석과 모델 구축 작업을 위한 기초를 마련합니다.
import seaborn as sns
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.utils import shuffle
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import cross_val_score


2단계: W&B 가져오기 및 초기화

import wandb

# Initialize a new run
wandb.init(project='titanic_decision_tree', entity='Enter your W&B user here')

3단계: 데이터셋 불러오기

다음으로 CSV 파일에서 Titanic 데이터셋을 불러와 Pandas DataFrame으로 로드하겠습니다. 이 단계는 데이터셋을 분석과 모델링에 적합한 구조화된 형태로 준비하는 데 매우 중요합니다.
titanic = pd.read_csv('/kaggle/input/test-file/tested.csv')


4단계: 데이터 전처리 - 결측치 처리

데이터의 불완전성을 해결하기 위해 'Fare'와 'Age' 열의 결측값을 각각 해당 열의 중앙값으로 채우겠습니다. 대부분의 머신러닝 알고리즘이 올바르게 작동하려면 결측값을 제거하거나 적절히 대치하는 과정이 필수적입니다.
titanic['Fare'].fillna(titanic['Fare'].median(), inplace=True)

titanic['Age'].fillna(titanic['Age'].median(), inplace=True)

titanic.drop('Cabin', axis=1, inplace=True)


5단계: 불필요한 열 제거

모델에 필요하지 않은 'Cabin', 'Name', 'Ticket', 'PassengerId' 열을 제거하겠습니다. 이 단계는 목표 변수를 예측하는 데 유용하지 않을 가능성이 높은 특성을 제거해 모델을 단순화하는 데 초점을 둡니다.
titanic.drop(['Name', 'Ticket', 'PassengerId'], axis=1, inplace=True)


6단계: 범주형 변수 인코딩

이어서 범주형 변수인 'Embarked'와 'Sex'를 각각 원-핫 인코딩과 라벨 인코딩으로 수치 형식으로 변환하겠습니다. 머신러닝 알고리즘은 수치형 입력을 요구하므로, 이 단계에서 범주형 데이터를 적합한 형식으로 변환합니다.
embarked_dummies = pd.get_dummies(titanic['Embarked'], prefix='Embarked')
titanic = pd.concat([titanic, embarked_dummies], axis=1)
titanic.drop('Embarked', axis=1, inplace=True)

le = LabelEncoder()
titanic['Sex'] = le.fit_transform(titanic['Sex'])


7단계: 특성 공학

데이터 처리 단계를 마무리하기 위해 'SibSp'와 'Parch'를 결합하여 새로운 특성 'family_size'를 생성하겠습니다. 이 단계는 기존 특성으로부터 새로운 특성을 만들어 모델 성능 향상에 도움이 될 수 있는 추가적 통찰을 발견하려는 목적을 갖습니다.
titanic['family_size'] = titanic['SibSp'] + titanic['Parch'] + 1


8단계: 특성과 타깃 준비

이제 데이터셋을 특성(X)과 타깃 변수(y)로 분리하겠습니다. 이 기본 단계는 이후의 학습과 테스트 단계에 맞게 데이터를 체계화합니다.
X = titanic.drop('Survived', axis=1)
y = titanic['Survived']


9단계: 레이블 셔플링

마지막에 앞서, 타깃 변수 'Survived'를 셔플하여 무작위화된 데이터에서의 모델 성능을 이해하기 위한 기준선을 준비하겠습니다. 이 단계는 모델이 실제 패턴을 포착하는 능력과 데이터를 단순히 암기하는 현상을 구분하는 데 도움을 줍니다.
y_shuffled = shuffle(y, random_state=42)


10단계: 학습용과 테스트용 데이터셋 분할

마지막으로, 데이터 처리 단계의 마무리로 데이터셋을 학습용과 테스트용으로 나누어 보이지 않은 데이터에서의 모델 성능을 평가하겠습니다. 이 중요한 단계는 모델의 일반화 능력을 판단하는 데 도움이 됩니다.
X_train_shuffled, X_test_shuffled, y_train_shuffled, y_test_shuffled = train_test_split(X, y_shuffled, test_size=0.2, random_state=42)


의사결정나무 모델 구축

단계 11: 셔플된 데이터에서의 모델 학습과 평가

이 단계에서는 셔플된 레이블을 사용해 'max_depth'를 달리한 Decision Tree Classifier를 학습하고, 학습용과 테스트용 데이터셋 모두에서 정확도를 평가합니다. 이 과정은 모델 복잡도를 반복적으로 조정하여 성능에 미치는 영향을 분석하고, 특히 과적합 또는 과소적합 경향을 식별하는 데 목적이 있습니다.
max_depth_range = range(1, 3)
shuffled_accuracies = []
cv_shuffled_accuracies = []

for depth in max_depth_range:
clf = DecisionTreeClassifier(max_depth=depth, random_state=42)
# Train and evaluate on shuffled labels
clf.fit(X_train_shuffled, y_train_shuffled)
shuffled_accuracy = clf.score(X_test_shuffled, y_test_shuffled)
shuffled_accuracies.append(shuffled_accuracy)
# Cross-validation on shuffled labels
cv_shuffled_accuracy = cross_val_score(clf, X, y_shuffled, cv=5).mean()
cv_shuffled_accuracies.append(cv_shuffled_accuracy)

의사결정나무 시각화

12단계: W&B에 데이터 로깅

# Log metrics to Weights & Biases
wandb.log({'max_depth': depth,
'training_accuracy': shuffled_accuracy,
'cv_accuracy': cv_shuffled_accuracy})


Weights & Biases에 학습 및 정확도 그래프를 로깅했습니다.


단계 13: 모델 성능 시각화

모델을 평가하기 위해 트리 깊이에 따른 학습 정확도와 교차 검증 정확도를 함께 그려, 모델 복잡도가 정확도에 어떤 영향을 미치는지 시각적으로 확인하겠습니다. 이러한 시각화는 편향과 분산의 균형을 이루기 위한 최적의 모델 복잡도를 식별하는 데 도움이 됩니다.
plt.figure(figsize=(10, 6))
plt.plot(max_depth_range, shuffled_accuracies, label='Shuffled Training Accuracy')
plt.plot(max_depth_range, cv_shuffled_accuracies, label='Shuffled CV Accuracy')
plt.xlabel('Tree Depth')
plt.ylabel('Accuracy')
plt.title('Model Complexity vs. Accuracy on Shuffled Data')
plt.legend()
plt.show()


모델 성능 평가

단계 14: 최종 모델 정확도 보고

마지막 단계로, 셔플된 데이터로 학습한 모델의 최종 학습 정확도와 교차 검증 정확도를 출력하겠습니다. 이 단계는 모델 성능을 요약하여, 셔플된 레이블을 기반으로 한 예측 능력의 효과성을 파악하는 데 도움을 줍니다.
final_shuffled_accuracy = shuffled_accuracies[-1]
print(f'Final Training Accuracy on Shuffled Data (Depth {max_depth_range.stop - 1}): {final_shuffled_accuracy:.2f}')

final_cv_shuffled_accuracy = cv_shuffled_accuracies[-1]
print(f'Final CV Accuracy on Shuffled Data (Depth {max_depth_range.stop - 1}): {final_cv_shuffled_accuracy:.2f}')

출력:
셔플된 데이터의 최종 학습 정확도(깊이 9): 0.60
셔플된 데이터의 최종 교차 검증 정확도(깊이 9): 0.57


고급 결정 트리 알고리즘

랜덤 포레스트

가장 먼저 살펴볼 것은 랜덤 포레스트입니다. 랜덤 포레스트는 학습 과정에서 여러 결정 트리로 이루어진 ‘숲’을 만드는 고급 결정 트리 알고리즘입니다. 단일 결정 트리에 의존하는 대신, 랜덤 포레스트는 데이터와 특징의 무작위 부분집합으로 학습된 많은 트리를 생성합니다.
예측을 수행할 때는 숲에 있는 각 트리가 투표를 하며, 분류 문제에서는 가장 많이 나온 결과가, 회귀 문제에서는 평균값이 최종 예측이 됩니다. 여러 트리의 결과를 평균함으로써 정확도를 높이고 과적합 위험을 줄일 수 있어, Random Forest는 강력하고 범용적인 머신러닝 모델로 널리 활용됩니다.
아래는 11, 13, 14단계의 코드 대체본으로, 더 단순한 결정 트리 알고리즘 대신 더 복잡한 Random Forest를 활용합니다.

단계 11: 셔플된 데이터에서의 모델 학습과 평가

from sklearn.ensemble import RandomForestClassifier

n_estimators_range = range(1, 3, 1)
rf_shuffled_accuracies = []
rf_cv_shuffled_accuracies = []

for n_estimators in n_estimators_range:
rf_clf = RandomForestClassifier(n_estimators=n_estimators, random_state=42)
rf_clf.fit(X_train_shuffled, y_train_shuffled)
shuffled_accuracy = rf_clf.score(X_test_shuffled, y_test_shuffled)
rf_shuffled_accuracies.append(shuffled_accuracy)
cv_shuffled_accuracy = cross_val_score(rf_clf, X, y_shuffled, cv=5).mean()
rf_cv_shuffled_accuracies.append(cv_shuffled_accuracy)


단계 13: 모델 성능 시각화

plt.figure(figsize=(10, 6))
plt.plot(list(n_estimators_range), rf_shuffled_accuracies, label='Shuffled Training Accuracy - RF')
plt.plot(list(n_estimators_range), rf_cv_shuffled_accuracies, label='Shuffled CV Accuracy - RF')
plt.xlabel('Number of Estimators')
plt.ylabel('Accuracy')
plt.title('Random Forest Complexity vs. Accuracy on Shuffled Data')
plt.legend()
plt.show()


단계 14: 최종 모델 정확도 보고

final_rf_shuffled_accuracy = rf_shuffled_accuracies[-1]
final_rf_cv_shuffled_accuracy = rf_cv_shuffled_accuracies[-1]
print(f'Final Training Accuracy on Shuffled Data (RF, Estimators {list(n_estimators_range)[-1]}): {final_rf_shuffled_accuracy:.2f}')
print(f'Final CV Accuracy on Shuffled Data (RF, Estimators {list(n_estimators_range)[-1]}): {final_rf_cv_shuffled_accuracy:.2f}')

셔플된 데이터에서의 최종 학습 정확도(XGB, 최대 깊이 9): 0.69
셔플된 데이터에서의 최종 교차 검증 정확도(XGB, 최대 깊이 9): 0.64


XGBoost

다음으로 XGBoost 알고리즘을 살펴보겠습니다. XGBoost는 eXtreme Gradient Boosting의 약자로, 그라디언트 부스팅을 효율적이고 확장 가능하게 구현한 방법입니다. 이 알고리즘은 예측기(결정 트리)를 순차적으로 추가하면서, 새로 추가되는 각 트리가 이전에 학습된 트리들이 만든 오류를 보정하는 방식으로 동작합니다. 여기서 “그라디언트”는 새 모델을 추가할 때 손실을 최소화하기 위해 경사하강법을 사용하는 것을 의미합니다.
XGBoost는 특히 구조화된 탭형 데이터 대회에서 속도와 성능으로 잘 알려져 있습니다. 대규모 데이터셋을 효율적으로 처리하고, 과적합을 방지하기 위한 내장 정규화를 제공하며, 수많은 머신러닝 대회에서 우승한 알고리즘이기도 합니다. 또한 XGBoost는 분류, 회귀, 랭킹, 사용자 정의 예측 문제 등 다양한 과제에 폭넓게 활용될 수 있는 범용성을 갖추고 있습니다.
아래 코드는 단계 11, 13, 14를 보다 단순한 결정 트리 알고리즘 대신 더 복잡한 XGBoost 알고리즘으로 대체한 것입니다.


단계 11: 셔플된 데이터에서의 모델 학습과 평가

import xgboost as xgb

max_depth_range = range(1, 31)
xgb_shuffled_accuracies = []
xgb_cv_shuffled_accuracies = []

for max_depth in max_depth_range:
xgb_clf = xgb.XGBClassifier(max_depth=max_depth, n_estimators=300, use_label_encoder=False, eval_metric='logloss', random_state=42)
xgb_clf.fit(X_train_shuffled, y_train_shuffled)
shuffled_accuracy = xgb_clf.score(X_test_shuffled, y_test_shuffled)
xgb_shuffled_accuracies.append(shuffled_accuracy)
cv_shuffled_accuracy = cross_val_score(xgb_clf, X, y_shuffled, cv=5).mean()
xgb_cv_shuffled_accuracies.append(cv_shuffled_accuracy)


단계 13: 모델 성능 시각화

plt.figure(figsize=(10, 6))
plt.plot(list(max_depth_range), xgb_shuffled_accuracies, label='Shuffled Training Accuracy - XGB')
plt.plot(list(max_depth_range), xgb_cv_shuffled_accuracies, label='Shuffled CV Accuracy - XGB')
plt.xlabel('Max Depth')
plt.ylabel('Accuracy')
plt.title('XGBoost Complexity vs. Accuracy on Shuffled Data')
plt.legend()
plt.show()


단계 14: 최종 모델 정확도 보고

final_xgb_shuffled_accuracy = xgb_shuffled_accuracies[-1]
final_xgb_cv_shuffled_accuracy = xgb_cv_shuffled_accuracies[-1]
print(f'Final Training Accuracy on Shuffled Data (XGB, Max Depth {list(max_depth_range)[-1]}): {final_xgb_shuffled_accuracy:.2f}')
print(f'Final CV Accuracy on Shuffled Data (XGB, Max Depth {list(max_depth_range)[-1]}): {final_xgb_cv_shuffled_accuracy:.2f}')
셔플된 데이터에서의 최종 학습 정확도(RF, 추정기 2개): 0.67
셔플된 데이터에서의 최종 교차 검증 정확도(RF, 추정기 2개): 0.63

보시다시피, 두 가지 고급 알고리즘은 보다 단순한 결정 트리 알고리즘과 비교했을 때 더 나은 성능을 보여줍니다. 여기서는 차이가 크게 눈에 띄지 않을 수 있지만, 더 복잡한 데이터셋에서는 이러한 모델과 일반 결정 트리 간의 성능 격차가 훨씬 더 뚜렷해질 것입니다.

요약

요약하면, 결정 트리는 단순성과 강력한 예측 성능을 겸비해 머신러닝을 배우고자 하는 모든 이들에게 탄탄한 출발점을 제공합니다.
이 글 전반에서 우리는 이러한 모델이 작동하는 기본 원리, 타이타닉과 같은 실제 데이터셋에서의 활용, 그리고 고급 기법과 알고리즘을 통해 성능을 향상시키는 방법을 다뤘습니다. 결정 트리를 받아들이면 데이터 분석을 위한 다재다능한 도구를 갖출 뿐만 아니라, 더 복잡한 모델에 도전할 기반을 마련하여 머신러닝의 가능성과 활용에 대한 폭넓은 이해를 보장할 수 있습니다.

이 글은 AI로 번역되었습니다. 오역이 의심되는 부분이 있다면 댓글로 알려주세요. 원문은 다음 링크에서 확인할 수 있습니다: 원문 보고서 보기