본문 바로가기

Lecture ML

머신러닝 강좌 #12] 결정트리(DecisionTree)와 과적합에 대해

반응형

결정 트리는 ML알고리즘 중 직관적으로 이해하기 쉬운 알고리즘입니다. 규칙 노드(Decision Node)로 표시된 노드는 규칙 조건이 되는 것이고, 리프 노드(Leaf Node)로 표시된 노드는 결정된 클래스 값입니다. 그리고 새로운 규칙 조건마다 서브 트리(Sub Tree)가 생성됩니다. 데이터 세트에 피처가 있고 이러한 피처가 결합해 규칙 조건마다 서브 트리(Sub Tree)가 생성됩니다. 데이터 세트에 피처가 있고 이러한 피처가 결합해 규칙 조건을 만들 때마다 규칙 노드가 만들어집니다. 하지만 많은 규칙이 있다는 것은 곧 분류를 결정하는 방식이 더욱 복잡해진다는 뜻이며 과적합으로 이어지기 쉽습니다. 즉, 트리의 깊이(depth)가 깊어질수록 결정 트리의 예측 성능이 저하될 가능성이 높습니다.

 

 

가능한 한 적은 결정 노드로 높은 예측 정확도를 가지려면 데이터를 분류할 때 최대한 많은 데이터 세트가 해당 분류에 속할 수 있도록 결정 노드의 규칙이 정해져야 합니다. 이를 위해서는 트리를 분할(Split)할 것인가가 중요한데 최대한 균일한 데이터 세트를 구성할 수 있도록 분할하는 것이 필요합니다.

 

결정 노드는 정보 균일도가 높은 데이터 세트를 먼저 선택할 수 있도록 규칙 조건을 만듭니다. 이러한 정보의 균일도를 측정하는 대표적인 방법은 엔트로피를 이용한 정보 이득(Information Gain) 지수와 지니 계수가 있습니다.

 

  • 정보 이득은 엔트로피라는 개념을 기반으로 합니다. 엔트로피는 주어진 데이터 집합의 혼잡도를 의미하는데, 서로 다른 값이 섞여 있으면 엔트로피가 높고, 같은 값이 섞여 있으면 엔트로피가 낮습니다. 정보 이득 지수는 1에서 엔트로피 지수를 뺀 값입니다. 즉 1-엔트로피 지수입니다. 결정 트리는 이 정보 이득 지수로 분할 기준을 정합니다. 즉, 정보 이득이 높은 속성을 기준으로 분할합니다.
  • 머신러닝에서 지니 계수는 낮을수록 데이터 균일도가 높은 것으로 해석해지니 계수가 낮은 속성을 기준으로 분할합니다.

 

사이킷런에서의 DecisionTreeClassfier는 기본으로 지니 계수를 이용해 데이터 세트를 분할합니다. DecisisionTree의 장단점은 아래와 같습니다.

  • 결정 트리 장점: 쉽다. 직관적이다. 피처의 스케일링이나 정규화 등의 사전 가공 영향도가 크지 않음
  • 결정 트리 단점: 과적합으로 알고리즘 성능이 떨어진다. 이를 극복하기 위해 트리의 크기를 사전에 제한하는 튜닝 필요

 

결정 트리 파라미터에서 가장 많이 사용하는 파라미터는 다음과 같습니다.

  • min_samples_split: 노드를 분할하기 위한 최소한의 샘플 데이터 수로 과적합을 제어하는 데 사용됨
  • min_samples_leaf: 말단 노드(Leaf)가 되기 위한 최소한의 샘플 데이터 수
  • max_features: 최적의 분할을 위해 고려할 최대 피처 개수, 디폴트는 None으로 데이터 세트의 모든 피처를 사용해 분할 수행
  • max_depth: 트리의 최대 깊이를 규정

 

from sklearn.datasets import load_iris, make_classification
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import numpy as np

#Iris Data
iris_data = load_iris()

# iris_df = pd.DataFrame(data=iris_data.data, columns=iris_data.feature_names)
dt_clf = DecisionTreeClassifier(random_state=11)
X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, test_size=0.2, random_state=11, shuffle=True)
model_all_params = dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
print(accuracy_score(y_test,pred))

# Prepare a plot figure with set size.
plt.figure(figsize=(8,8))
# Plot the decision tree, showing the decisive values and the improvements in Gini impurity along the way.
plot_tree(model_all_params, filled=True, impurity=True)
# Display the tree plot figure.
plt.show()

 

먼저 더 이상 자식 노드가 없는 노드는 리프 노드입니다. 리프 노드는 최종 클래스(레이블) 값이 결정되는 노드입니다. 리프 노드가 되려면 오직 하나의 클래스 값으로 최종 데이터가 구성되거나 리프 노드가 될 수 있는 하이퍼 파라미터 조건을 충족하면 됩니다. 자식 노드가 있는 노드는 브랜치 노드이며 자식 노드를 만들기 위한 분할 규칙 조건을 가지고 있습니다. 

 

박스의 색이 짙어질수록 지니 계수가 낮고 해당 레이블에 속하는 샘플 데이터가 많다는 의미입니다. 

 

사이킷런에서는 feature_importance_ 속성을 제공합니다. ndarray형태로 값을 반환하며 피처 순서대로 값이 할당됩니다. 아래의 예시를 보면 petal width의 feature가 가중 중요함을 알 수 있습니다.

 

from sklearn.datasets import load_iris, make_classification
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import numpy as np

#Iris Data
iris_data = load_iris()
# iris_df = pd.DataFrame(data=iris_data.data, columns=iris_data.feature_names)
dt_clf = DecisionTreeClassifier(random_state=11)
X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, test_size=0.2, random_state=11, shuffle=True)
model_all_params = dt_clf.fit(iris_data.data, iris_data.target)
model_all_params = dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
print(accuracy_score(y_test,pred))

#feature importance 추출
print(dt_clf.feature_importances_)

#feature별 importance 매핑
for name, value in zip(iris_data.feature_names, dt_clf.feature_importances_):
    print(name,value)

sns.barplot(x=dt_clf.feature_importances_, y=iris_data.feature_names)
plt.show()

 


결정 트리 과적합(Overfitting)

 

결정 트리가 어떻게 학습 데이터를 분할해 예측을 수행하는지와 이로 인한 과적합 문제를 시각화해 알아보겠습니다. 먼저 분류를 위한 데이터 세트를 임의로 만들어 보겠습니다.  사이킷런은 분류를 위한 테스트용 데이터를 쉽게 만들 수 있도록 make_classification() 함수를 제공합니다. 

 

? 과적합은 모델이 학습 데이터에만 과도하게 최적화되어, 실제 예측을 다른 데이터로 수행할 경우에는 예측 성능이 떨어지는 경우를 말합니다. 이런 과적합의 오류를 수정하기 위해 교차 검증은 이러한 데이터 편증을 막기 위해서 별도의 여러 세트로 구성된 학습 데이터 세트와 검증 데이터 세트에서 학습과 평가를 수행하는 것입니다. 

 

from sklearn.datasets import make_classification
import matplotlib.pyplot as plt

plt.title("3 Class Value with 2 Features Sample data creation")

#2차원 시각화를 위해서 피처는 2개, 클래스는 3가지 유형의 분류 샘플 데이터 생성,
X_feature, y_labels = make_classification(n_features=2, n_redundant=0, n_informative=2, n_classes=3, n_clusters_per_class=1, random_state=0)

#그래프 형태로 2개의 피처로 2차원 좌표 시각화, 각 클래스 값은 다른 색깔로 표시됨.
plt.scatter(X_feature[:,0], X_feature[:,1], marker='o', c=y_labels, s=25, edgecolors='k')
plt.show()

 

각 피처와 X, Y축으로 나열된 2차원 그래프이며, 3개의 클래스 값 구분은 색깔로 돼 있습니다. 이제 X_feaures와 y_labels데이터 세트를 기반으로 결정 트리를 학습하겠습니다.  먼저 결정 트리 생성에 별다른 제약이 없도록 하이퍼 파라미터가 디폴트인 Classifier를 학습하고 결정기준 경계를 시각화해 보겠습니다.

 

def visualize_boundary(model, X, y):
    fig, ax = plt.subplots()

    # 학습 데이타 scatter plot으로 나타내기
    ax.scatter(X[:, 0], X[:, 1], c=y, s=25, cmap='rainbow', edgecolor='k',
               clim=(y.min(), y.max()), zorder=3)
    ax.axis('tight')
    ax.axis('off')
    xlim_start, xlim_end = ax.get_xlim()
    ylim_start, ylim_end = ax.get_ylim()

    # 호출 파라미터로 들어온 training 데이타로 model 학습 . test
    model.fit(X, y)
    # meshgrid 형태인 모든 좌표값으로 예측 수행.
    xx, yy = np.meshgrid(np.linspace(xlim_start, xlim_end, num=200), np.linspace(ylim_start, ylim_end, num=200))
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)

    # contourf() 를 이용하여 class boundary 를 visualization 수행.
    n_classes = len(np.unique(y))
    contours = ax.contourf(xx, yy, Z, alpha=0.3,levels=np.arange(n_classes + 1) - 0.5,cmap='rainbow' ,zorder=1)

    
from sklearn.tree import DecisionTreeClassifier

#특정한 트리 생성 제약 없는 결정 트리의 학습과 결정 경계 시각화
dt_clf =DecisionTreeClassifier().fit(X_feature, y_labels)
visualize_boundary(dt_clf, X_feature, y_labels)
plt.show()

일부 이상치(Outlier) 데이터까지 분류하기 위해 분할이 자주 일어나서 결정 기준 경계가 매우 많아졌습니다. 결정 트리의 기본 하이퍼 파라미터 설정은 리프 노드 안에 데이터가 모두 균일하거나 하나만 존재해야 하는 엄격한 분할 기준으로  인해 결정 기준 경계가 많아지고 복잡해졌습니다. 이렇게 복잡한 모델은 학습 데이터 세트의 특성과 약간만 다른 형태의 데이터 세트를 예측하면 예측 정확도가 떨어지게 됩니다.

 

dt_clf = DecisionTreeClassifier(min_samples_leaf=6).fit(X_feature, y_labels)
visualize_boundary(dt_clf, X_feature, y_labels)
plt.show()

min_samples_leaf=6을 설정해 6개 이하의 데이터는 리프 노드를 생성할 있도록 리프 노드 생성 규칙을 완화한 뒤 실행하면 좀 더 일반화된 분류 규칙에 따라 분류됐음을 알 수 있습니다. 본 option을 설정하는 게 모델의 성능을 좀 더 향상할 수 있습니다. 

 

모든 내용은 하기의 ipny화일을 다운로드 하시면 됩니다.

LT_12_DecisionTree.ipynb
0.14MB

 

감사합니다.

반응형