027 결정 트리 분류 상세
키워드: 결정 트리, dt
개요
결정 트리(Decision Tree)는 직관적이고 해석하기 쉬운 분류 알고리즘입니다. 데이터를 조건에 따라 분할하여 트리 구조로 결정을 내립니다.
실습 환경
- Python 버전: 3.11 권장
- 필요 패키지:
pycaret[full]>=3.0
결정 트리 원리
[나이 <= 30?]
/ \
Yes No
/ \
[소득 > 50K?] [클래스: 승인]
/ \
Yes No
/ \
[클래스: 승인] [클래스: 거부]
PyCaret에서 결정 트리
from pycaret.classification import *
from pycaret.datasets import get_data
# 027 데이터 로드
data = get_data('diabetes')
clf = setup(data, target='Class variable', session_id=42, verbose=False)
# 027 결정 트리 모델 생성
dt = create_model('dt')
주요 하이퍼파라미터
# 027 max_depth: 최대 깊이 (과적합 방지)
dt_d3 = create_model('dt', max_depth=3)
dt_d5 = create_model('dt', max_depth=5)
dt_d10 = create_model('dt', max_depth=10)
# 027 min_samples_split: 분할에 필요한 최소 샘플 수
dt_split = create_model('dt', min_samples_split=20)
# 027 min_samples_leaf: 리프 노드의 최소 샘플 수
dt_leaf = create_model('dt', min_samples_leaf=10)
# 027 criterion: 분할 기준
dt_gini = create_model('dt', criterion='gini') # 기본값
dt_entropy = create_model('dt', criterion='entropy')
# 027 max_features: 분할 시 고려할 특성 수
dt_sqrt = create_model('dt', max_features='sqrt')
dt_log2 = create_model('dt', max_features='log2')
깊이에 따른 성능 비교
from pycaret.classification import *
from pycaret.datasets import get_data
import pandas as pd
data = get_data('credit')
clf = setup(data, target='default', session_id=42, verbose=False)
results = []
for depth in [3, 5, 7, 10, None]: # None = 제한 없음
dt = create_model('dt', max_depth=depth, verbose=False)
metrics = pull()
results.append({
'depth': depth if depth else 'unlimited',
'accuracy': metrics['Accuracy'].mean(),
'auc': metrics['AUC'].mean(),
'std': metrics['Accuracy'].std() # 분산으로 과적합 확인
})
df = pd.DataFrame(results)
print(df)
# 027 depth가 너무 깊으면 과적합 (std 증가)
트리 시각화
from pycaret.classification import *
from pycaret.datasets import get_data
data = get_data('iris')
clf = setup(data, target='species', session_id=42, verbose=False)
dt = create_model('dt', max_depth=3, verbose=False)
# 027 트리 시각화 (텍스트)
from sklearn.tree import export_text
tree_rules = export_text(dt, feature_names=list(get_config('X_train').columns))
print(tree_rules)
그래픽 시각화
from sklearn.tree import plot_tree
import matplotlib.pyplot as plt
plt.figure(figsize=(20, 10))
plot_tree(
dt,
feature_names=list(get_config('X_train').columns),
class_names=list(map(str, dt.classes_)),
filled=True,
rounded=True,
fontsize=10
)
plt.tight_layout()
plt.savefig('decision_tree.png', dpi=150)
plt.show()
특성 중요도
from pycaret.classification import *
from pycaret.datasets import get_data
import pandas as pd
data = get_data('diabetes')
clf = setup(data, target='Class variable', session_id=42, verbose=False)
dt = create_model('dt', max_depth=5, verbose=False)
# 027 특성 중요도 추출
feature_names = get_config('X_train').columns
importances = dt.feature_importances_
importance_df = pd.DataFrame({
'feature': feature_names,
'importance': importances
}).sort_values('importance', ascending=False)
print("특성 중요도:")
print(importance_df)
# 027 시각화
plot_model(dt, plot='feature')
튜닝
from pycaret.classification import *
from pycaret.datasets import get_data
data = get_data('credit')
clf = setup(data, target='default', session_id=42, verbose=False)
# 027 기본 모델
dt = create_model('dt', verbose=False)
# 027 자동 튜닝
tuned_dt = tune_model(dt, optimize='AUC')
# 027 커스텀 그리드
custom_grid = {
'max_depth': [3, 5, 7, 10, 15],
'min_samples_split': [2, 5, 10, 20],
'min_samples_leaf': [1, 2, 5, 10],
'criterion': ['gini', 'entropy']
}
tuned_dt = tune_model(dt, custom_grid=custom_grid, optimize='AUC')
가지치기 (Pruning)
과적합 방지를 위한 사후 가지치기:
# 027 Cost Complexity Pruning
from pycaret.classification import *
from pycaret.datasets import get_data
data = get_data('credit')
clf = setup(data, target='default', session_id=42, verbose=False)
# 027 ccp_alpha: 가지치기 강도 (높을수록 단순한 트리)
dt_pruned = create_model('dt', ccp_alpha=0.01, verbose=False)
# 027 최적 ccp_alpha 찾기
from sklearn.tree import DecisionTreeClassifier
import numpy as np
X_train = get_config('X_train')
y_train = get_config('y_train')
# 027 기본 트리로 가능한 alpha 값들 확인
clf_temp = DecisionTreeClassifier(random_state=42)
clf_temp.fit(X_train, y_train)
path = clf_temp.cost_complexity_pruning_path(X_train, y_train)
alphas = path.ccp_alphas
print(f"가능한 alpha 범위: {alphas.min():.6f} ~ {alphas.max():.6f}")
장단점
장점:
- 해석하기 매우 쉬움
- 전처리 거의 불필요 (스케일링, 정규화 불필요)
- 범주형/수치형 모두 처리
- 비선형 관계 포착
- 특성 중요도 제공
단점:
- 과적합 경향 (가지치기 필요)
- 불안정 (데이터 변화에 민감)
- 축 평행 결정 경계만 가능
- 편향된 데이터에서 편향된 트리
언제 사용하나?
# 1. 해석이 필수인 경우 (의료, 법률, 금융)
# 027 의사결정 과정을 설명해야 할 때
# 2. 랜덤 포레스트/XGBoost의 기반 이해
# 027 앙상블 방법의 기초
# 3. 빠른 프로토타이핑
# 027 데이터 이해와 중요 특성 파악
# 4. 비즈니스 규칙 추출
# 027 트리를 규칙으로 변환 가능
결정 트리 규칙 추출
def tree_to_rules(tree, feature_names):
"""결정 트리를 규칙으로 변환"""
tree_ = tree.tree_
feature_name = [
feature_names[i] if i != -2 else "undefined!"
for i in tree_.feature
]
rules = []
def recurse(node, rule):
if tree_.feature[node] != -2: # 리프가 아니면
name = feature_name[node]
threshold = tree_.threshold[node]
# 왼쪽 자식 (<=)
left_rule = rule + [f"{name} <= {threshold:.2f}"]
recurse(tree_.children_left[node], left_rule)
# 오른쪽 자식 (>)
right_rule = rule + [f"{name} > {threshold:.2f}"]
recurse(tree_.children_right[node], right_rule)
else:
# 리프 노드
class_idx = tree_.value[node].argmax()
rules.append((rule, class_idx))
recurse(0, [])
return rules
# 027 규칙 추출
rules = tree_to_rules(dt, list(get_config('X_train').columns))
for rule, class_idx in rules[:5]: # 처음 5개 규칙
print(f"IF {' AND '.join(rule)} THEN class={class_idx}")
정리
- 결정 트리는 직관적이고 해석 가능한 알고리즘
- max_depth, min_samples_leaf로 과적합 방지
- 특성 중요도로 중요한 변수 파악
- 트리 시각화로 의사결정 과정 설명
- 앙상블 방법(랜덤 포레스트, XGBoost)의 기초
다음 글 예고
다음 글에서는 랜덤 포레스트 분류 상세를 다룹니다.
PyCaret 머신러닝 마스터 시리즈 #027