by museonghwang

결정 트리(Decision Tree)

|

결정 트리(Decision Tree)

결정 트리(Decision Tree) 는 ML 알고리즘 중 직관적으로 이해하기 쉬운 알고리즘으로, 데이터에 있는 규칙을 학습을 통해 자동으로 찾아내 트리(Tree) 기반의 분류 규칙을 만드는 것 입니다. 따라서 데이터의 어떤 기준을 바탕으로 규칙을 만들어야 가장 효율적인 분류가 될 것인가가 알고리즘의 성능을 크게 좌우 합니다.


다음 그림은 결정 트리의 구조를 간략하게 나타낸 것으로, 규칙 노드(Decision Node) 는 규칙 조건이 되는 것이고, 리프 노드(Leaf Node) 는 결정된 클래스 값입니다. 그리고 새로운 규칙 조건마다 서브 트리(Sub Tree) 가 생성됩니다.

데이터 세트에 피처가 있고 이러한 피처가 결합해 규칙 조건을 만들 때마다 규칙 노드가 만들어집니다. 하지만 많은 규칙이 있다는 것은 곧 분류를 결정하는 방식이 더욱 복잡해진다는 얘기이고, 이는 곧 과적합으로 이어지기 쉽습니다. 즉, 트리의 깊이(depth)가 깊어질수록 결정 트리의 예측 성능이 저하될 가능성이 높습니다.

image


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

다음 그림에서 가장 균일한 데이터 세트부터 순서대로 나열한다면 어떻게 될까요?

image


  • C -> B -> A
    • C의 경우 모두 검은 공으로 구성되므로 데이터가 모두 균일.
    • B의 경우 일부 하얀 공을 가지고 있지만, 대부분 검은 공으로 구성되어 다음으로 균일도가 높음.
    • A의 경우는 검은 공 못지않게 많은 하얀 공을 가지고 있어 균일도가 제일 낮음.
  • 데이터 세트의 균일도는 데이터를 구분하는 데 필요한 정보의 양에 영향을 미칩니다.
    • C : 하나의 데이터를 뽑았을 때 데이터에 대한 별다른 정보 없이도 ‘검은 공’이라고 쉽게 예측할 수 있음.
    • A : 상대적으로 혼잡도가 높고 균일도가 낮기 때문에 같은 조건에서 데이터를 판단하는 데 있어 더 많은 정보가 필요.


결정 노드정보 균일도가 높은 데이터 세트를 먼저 선택할 수 있도록 규칙 조건을 만듭니다. 즉, 정보 균일도가 데이터 세트로 쪼개질 수 있도록 조건을 찾아 서브 데이터 세트를 만들고, 다시 이 서브 데이터 세트에서 균일도가 높은 자식 데이터 세트 쪼개는 방식을 자식 트리로 내려가면서 반복하는 방식으로 데이터 값을 예측하게 됩니다.


정보의 균일도를 측정하는 대표적인 방법

  • 엔트로피를 이용한 정보 이득(Information Gain)지수
    • 엔트로피주어진 데이터 집합의 혼잡도 를 의미하는데, 서로 다른 값이 섞여 있으면 엔트로피가 높고, 같은 값이 섞여 있으면 엔트로피가 낮습니다. 정보 이득 지수는 1에서 엔트로피 지수를 뺀 값으로, 결정 트리는 이 정보 이득 지수로 분할 기준을 정합니다. 즉, 정보 이득이 높은 속성을 기준으로 분할합니다.
  • 지니 계수
    • 지니계수 는 0이 가장 평등하고 1로 갈수록 불평등합니다. 머신러닝에 적용될 때는 지니 계수가 낮을수록 데이터 균일도가 높은 것으로 해석 해 지니 계수가 낮은 속성을 기준으로 분할합니다.


결정 트리 알고리즘을 사이킷런에서 구현한 DecisionTreeClassifier 는 기본적으로 지니 계수를 이용해 데이터 세트를 분할합니다. 결정 트리의 일반적인 알고리즘은 데이터 세트를 분할하는 데 가장 좋은 조건, 즉 정보 이득이 높거나 지니 계수가 낮은 조건을 찾아서 자식 트리 노드에 걸쳐 반복적으로 분할 한 뒤, 데이터가 모두 특정 분류에 속하게 되면 분할을 멈추고 분류를 결정합니다.

image


결정 트리 모델의 특징

  • 장점
    • 정보의 ‘균일도’라는 룰을 기반으로 하고 있어서 알고리즘이 쉽고 직관적.
    • 정보의 균일도만 신경 쓰면 되므로 특별한 경우를 제외하고는 각 피처의 스케일링과 정규화 같은 전처리 작업이 필요 없음
  • 단점
    • 과적합으로 정확도가 떨어질 수 있음. 이를 극복하기 위해 트리의 크기를 사전에 제한하는 튜닝 필요.


결정 트리 모델의 시각화

Graphviz 패키지를 이용하여 결정 트리 알고리즘이 어떠한 규칙을 가지고 트리를 생성하는지 시각적으로 볼 수있습니다. 사이킷런은 Graphviz 패키지와 쉽게 인터페이스할 수 있도록 export_graphviz() API를 제공하며, 함수 인자로 학습이 완료된 Estimator, 피처의 이름 리스트, 레이블 이름 리스트를 입력하면 학습된 결정 트리 규칙을 실제 트리 형태로 시각화해 보여줍니다.

이렇게 결정 트리가 만드는 규칙을 시각화해보면 결정 트리 알고리즘을 더욱 쉽게 이해할 수 있습니다.


설치가 완료된 Graphviz 를 이용해 붓꽃 데이터 세트에 결정 트리, 즉 DecisionTreeClassifer 를 적용할 때 어떻게 서브 트리가 구성되고 만들어지는지 시각화해 보겠습니다.

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings('ignore')

# DecisionTree Classifier 생성
dt_clf = DecisionTreeClassifier(random_state=156)

# 붓꽃 데이터를 로딩하고, 학습과 테스트 데이터 셋으로 분리
iris_data = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris_data.data,
    iris_data.target,
    test_size=0.2,
    random_state=11
)

# DecisionTreeClassifer 학습. 
dt_clf.fit(X_train, y_train)


사이킷런의 트리 모듈은 Graphviz 를 이용하기 위해 export_graphviz() 함수를 제공합니다. export_graphviz()Graphviz 가 읽어 들여서 그래프 형태로 시각화할 수 있는 출력 파일을 생성합니다. export_graphviz() 에 인자로 학습이 완료된 estimator, output 파일 명, 결정 클래스의 명칭, 피처의 명칭을 입력해주면 됩니다.

from sklearn.tree import export_graphviz

# export_graphviz()의 호출 결과로 out_file로 지정된 tree.dot 파일을 생성함. 
export_graphviz(
    dt_clf,
    out_file="tree.dot",
    class_names=iris_data.target_names,
    feature_names=iris_data.feature_names,
    impurity=True,
    filled=True
)


이렇게 생성된 출력 파일 ‘tree.dot’ 을 다음과 같이 Graphviz 의 파이썬 래퍼 모듈을 호출해 결정 트리의 규칙을 시각적으로 표현할 수 있습니다.

import graphviz

# 위에서 생성된 tree.dot 파일을 Graphviz 읽어서 Jupyter Notebook상에서 시각화 
with open("tree.dot") as f:
    dot_graph = f.read()
graphviz.Source(dot_graph)

image


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

위 그림에서 노드 내에 기술된 지표의 의미는 다음과 같습니다.

  • petal length(cm) <= 2.45와 같이 피처의 조건이 있는 것은 자식 노드를 만들기 위한 규칙 조건입니다. 이 조건이 없으면 리프 노드입니다.
  • gini는 다음의 value=[]로 주어진 데이터 분포에서의 지니 계수입니다.
  • samples는 현 규칙에 해당하는 데이터 건수입니다.
  • value = []는 클래스 값 기반의 데이터 건수입니다. 붓꽃 데이터 세트는 클래스 값으로 0, 1, 2를 가지고 있습니다. 만일 Value = [41, 40, 39]라면 클래스 값의 순서로 Setosa 41개, Vesicolor 40개, Virginica 39개로 데이터가 구성돼 있다는 의미입니다.

각 노드의 색깔은 붓꽃 데이터의 레이블 값을 의미 합니다. 주황색은 0: Setosa, 초록색은 1:Versicolor, 보라색은 2: Virginica 레이블을 나타냅니다. 색깔이 짙어질수록 지니 계수가 낮고 해당 레이블에 속하는 샘플 데이터가 많다는 의미 입니다.


결정 트리는 균일도에 기반해 어떠한 속성을 규칙 조건으로 선택하느냐가 중요한 요건 입니다. 중요한 몇 개의 피처가 명확한 규칙 트리를 만드는 데 크게 기여하며, 모델을 좀 더 간결하고 이상치(Outlier)에 강한 모델을 만들 수 있기 때문 입니다. 사이킷런은 결정 트리 알고리즘이 학습을 통해 규칙을 정하는 데 있어 피처의 중요한 역할 지표를 DecisionTreeClassifier 객체의 feature_importances_ 속성으로 제공합니다.

feature_importances_피처가 트리 분할 시 정보 이득이나 지니 계수를 얼마나 효율적으로 잘 개선시켰는지를 정규화된 값으로 표현한 것 으로 ndarray 형태로 값을 반환하며 피처 순서대로 값이 할당됩니다. 일반적으로 값이 높을수록 해당 피처의 중요도가 높다는 의미 입니다.

위 예제에서 fit() 으로 학습된 DecisionTreeClassifier 객체 변수인 df_clf 에서 feature_importances_ 속성을 가져와 피처별로 중요도 값을 매핑하고 이를 막대그래프로 표현해 보겠습니다.

import seaborn as sns
import numpy as np
%matplotlib inline

# feature importance 추출 
print("Feature importances:\n{0}".format(np.round(dt_clf.feature_importances_, 3)))

# feature별 importance 매핑
for name, value in zip(iris_data.feature_names, dt_clf.feature_importances_):
    print('{0} : {1:.3f}'.format(name, value))

# feature importance를 column 별로 시각화 하기 
sns.barplot(x=dt_clf.feature_importances_ , y=iris_data.feature_names)
[output]
Feature importances:
[0.025 0.    0.555 0.42 ]
sepal length (cm) : 0.025
sepal width (cm) : 0.000
petal length (cm) : 0.555
petal width (cm) : 0.420

image


여러 피처들 중 petal_length 가 가장 피처 중요도가 높음을 알 수 있습니다.


결정 트리 과적합(Overfitting)

결정 트리가 어떻게 학습 데이터를 분할해 예측을 수행하는지와 이로 인한 과적합 문제를 시각화 해 알아보겠습니다.

먼저 분류를 위한 데이터 세트를 임의로 만들어 보겠습니다. 사이킷런은 분류를 위한 테스트용 데이터를 쉽게 만들 수 있도록 make_classification() 함수를 제공합니다. 이 함수를 이용해 2개의 피처가 3가지 유형의 클래스 값을 가지는 데이터 세트를 만들고 이를 그래프 형태로 시각화하겠습니다. make_classification() 호출 시 반환되는 객체는 피처 데이터 세트와 클래스 레이블 데이터세트입니다.

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

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

# 2차원 시각화를 위해서 feature는 2개, 결정값 클래스는 3가지 유형의 classification 샘플 데이터 생성. 
X_features, y_labels = make_classification(
    n_features=2,
    n_redundant=0,
    n_informative=2,
    n_classes=3,
    n_clusters_per_class=1,
    random_state=0
)

# plot 형태로 2개의 feature로 2차원 좌표 시각화, 각 클래스값은 다른 색깔로 표시됨. 
plt.scatter(
    X_features[:, 0],
    X_features[:, 1],
    marker='o',
    c=y_labels,
    s=25,
    cmap='rainbow',
    edgecolor='k'
)

image


각 피처가 X, Y축으로 나열된 2차원 그래프이며, 3개의 클래스 값 구분은 색깔로 돼 있습니다. 이제 X_featuresy_labels 데이터 세트를 기반으로 결정 트리를 학습하겠습니다. 첫 번째 학습 시에는 결정 트리 생성에 별다른 제약이 없도록 결정 트리의 하이퍼 파라미터를 디폴트로 한 뒤, 결정 트리 모델이 어떠한 결정 기준을 가지고 분할하면서 데이터를 분류하는지 확인할 것입니다.

먼저 결정 트리 생성에 별다른 제약이 없도록 하이퍼 파라미터가 디폴트인 Classifier를 학습하고 결정기준 경계를 시각화해 보겠습니다.

from sklearn.tree import DecisionTreeClassifier

# 특정한 트리 생성 제약없는 결정 트리의 Decsion Boundary 시각화.
dt_clf = DecisionTreeClassifier(random_state=156).fit(X_features, y_labels)
visualize_boundary(dt_clf, X_features, y_labels)

image


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

이번에는 min_samples_leaf = 6을 설정해 6개 이하의 데이터는 리프 노드를 생성할 수 있도록 리프노드 생성 규칙을 완화한 뒤 하이퍼 파라미터를 변경해 어떻게 결정 기준 경계가 변하는지 살펴보겠습니다.

# min_samples_leaf=6 으로 트리 생성 조건을 제약한 Decision Boundary 시각화
dt_clf = DecisionTreeClassifier(min_samples_leaf=6, random_state=156).fit(X_features, y_labels)
visualize_boundary(dt_clf, X_features, y_labels)

image


이상치에 크게 반응하지 않으면서 좀 더 일반화된 분류 규칙에 따라 분류됐음을 알 수 있습니다. 다양한 테스트 데이터 세트를 기반으로 한 결정 트리 모델의 예측 성능은 첫 번째 모델보다는 min_samples_leaf=6으로 트리 생성 조건을 제약한 모델이 더 뛰어날 가능성이 높습니다. 왜냐하면 테스트데이터 세트는 학습 데이터 세트와는 다른 데이터 세트인데, 학습 데이터에만 지나치게 최적화된 분류기준은 오히려 테스트 데이터 세트에서 정확도를 떨어뜨릴 수 있기 때문입니다.