본문 바로가기
Python (Linux)

43. Python - 결정 트리(Decision Tree)

by #Glacier 2019. 2. 12.
반응형

자. 오늘은 새로운 챕터, 결정 트리입니다.

SVM처럼 결정 트리(Decision tree)는 분류와 회귀 작업 그리고 다중출력 작업도 가능한 다재다능한 머신러닝 알고리즘입니다.

또한, 매우 복잡한 데이터셋도 학습할 수 있는 강력한 알고리즘입니다.

결정 트리는 최근에 자주 사용되는 가장 강력한 머신러닝 알고리즘인 랜덤 포레스트의 기본 구성 요소이기도 합니다.


이 장에서는 결정 트리의 훈련, 시각화, 예측 방법에 대해 먼저 알아보겠습니다.

그리고, 사이킷런의 CART 훈련 알고리즘을 둘러보고 트리에 규제를 가하는 방법과 회귀 문제에 적용하는 방법에 대해 알아봅니다.

마지막으로, 결정 트리의 제약 사항에 대해 알아보겠습니다.


결정 트리를 이해하기 위해 일단 하나를 만들어서, 어떻게 예측을 하는지 살펴보겠습니다.

다음은 붓꽃 데이터셋에 DecisionTreeClassifier를 훈련시키는 코드입니다.


# 기본 설정


import numpy as np

import os


np.random.seed(42)


%matplotlib inline

import matplotlib

import matplotlib.pyplot as plt


plt.rcParams['axes.labelsize'] = 14

plt.rcParams['xtick.labelsize'] = 12

plt.rcParams['ytick.labelsize'] = 12


#그림을 저장할 폴더

PROJECT_ROOT_DIR = '/root/ml'


def image_path(fig_id):

    return os.path.join(PROJECT_ROOT_DIR,  fig_id)


def save_fig(fig_id, tight_layout = True):

    if tight_layout :

        plt.tight_layout()

    plt.savefig(image_path(fig_id) + '.png', format='png', dpi=300)


#그리고, 터미널을 열어서 아래의 명령어로 설치해주세요.

yum install graphviz*


#아나콘다 배포판을 사용하고 있다면, conda install python-graphviz


from sklearn.datasets import load_iris

from sklearn.tree import DecisionTreeClassifier


iris = load_iris()

X = iris.data[:, 2:] #Petal length and width

y = iris.target


tree_clf = DecisionTreeClassifier(max_depth=2, random_state=42)

tree_clf.fit(X, y)

DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=2,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=42,
            splitter='best')

from sklearn.tree import export_graphviz


export_graphviz(

                                    tree_clf,

                                    out_file=image_path('iris_tree.dot'),

                                    feature_names=['꽃잎 길이(cm)', '꽃잎 너비(cm)'],

                                    class_names = iris.target_names,

                                    rounded = True,

                                    filled = True

                                    )


import graphviz


with open('/root/ml/iris_tree.dot') as f:

    dot_graph = f.read()


dot = graphviz.Source(dot_graph)

dot.format = 'png'

dot.render(filename = 'iris_tree', directory = '/root/ml', cleanup=True)

dot



자 이제 위의 트리가 어떻게 예측을 하는지 살펴보겠습니다.

새로 발견한 붓꽃의 품종을 분류하려 한다고 가정합니다.

먼저 루트 노드(최상위, 깊이가 0)에서 시작합니다. 이 노드는, 꽃잎의 길이가 2.45cm보다 짧은지 검사합니다.

만약 그렇다면, 아래 왼쪽노드(자식 노드, 깊이 1)로 이동합니다. 

이 경우, 이 노드가 리프 노드(leaf node, 자식 노드를 가지지 않는 노드)이므로 추가적인 검사를 하지 않습니다.

그냥 노드에 있는 예측 클래스를 보고, 결정 트리가 새로 발견한 꽃의 품종을 Iris-Setosa(class=Setosa)라고 예측합니다.


또 다른 꽃을 발견했는데, 이번에는 2.45cm보다 깁니다. 이번에는 루트 노트의 우측 아래로 이동합니다.

이 노드는 리프 노드가 아니라서, 추가로 꽃잎 너비가 1.75cm 보다 작은지 검사합니다. 

만약 그렇다면, 이 꽃은 Iris-Versicolor(깊이 2, 초록색 바탕)이고, 그렇지 않다면, Iris-Virginica(깊이 2, 보랏빛 바탕) 으로 예측합니다. 


[ NOTE ]

결정 트리의 여러 장점 중 하나는 데이터 전처리가 거의 필요하지 않다는 것입니다.

특히 특성의 스케일을 맞추거나, 평균을 원점에 맞추는 작업이 필요하지 않습니다.


노드의 sample 속성은 얼마나 많은 훈련 샘플이 적용되었는지 센 것입니다. 예를 들어, 100개의 훈련 샘플이 2.45cm보다 길고, 그 중 54개의 샘플이 너비가 1.75cm보다 짧습니다. 


Value속성은, 노드에서 각 클래스에 얼마나 많은 훈렴 샘플이 있는지 알려줍니다. 에를 들어, 보라색 바탕인 virginica를 보면, Iris-Setosa가 0개, Iris-Versicolor가 1개, Iris-Virginica가 45개 있습니다.


마지막으로 gini 속성은 불순도(impurity)를 측정합니다. 한 노드의 모든 샘플이 같은 클래스에 속해 있다면 이 노드를 순수하다고 합니다(gini=0). 


아래의 식은, 훈련 알고리즘이 i번째 노드의 gini 점수를 계산하는 방법을 보여줍니다.

예를 들어, 깊이 2의 왼쪽 노드의 gini점수는 입니다.


[ 지니 불순도 계산식 ]

는 i번째 노드에 있는 훈련 샘플 중 클래스 k에 속한 샘플의 비율입니다.


오분류율을 계산하는 식이라기보다, 아주 불순한 상태(1) 에서 클래스에 어떻게 분배가 되었나를 기점으로 보면 될 것 같습니다. 불순도를 측정하는 또 다른 방법도 있는데, 이는 뒤에서 설명하겠습니다.


[NOTE]

사이킷런은 이진 트리만 만드는 CART 알고리즘을 사용합니다. 그러므로 리프 노드 외의 모든 노드는 자식 노드를 두 개씩 가집니다. (즉 예/아니오) 하지만 ID3같은 알고리즘은 둘 이상의 자식 노드를 가진 결정 트리를 만들 수 있습니다.


아래 그림은 결정 트리의 결정 경계를 보여줍니다.


굵은 수직선이 루트 노드(깊이 0)의 결정 경계(꽃잎 길이 = 2.45cm) 를 나타냅니다. 

왼쪽 영역은 순수 노드(Iris-Setosa만 있음)이기 때문에 더 이상 나눌 수 없고, 오른쪽 영역은 순수 노드가 아니므로 깊이 1의 오른쪽 노드는 꽃잎 너비 1.75cm에서 나누어집니다. max_depth를 2로 설정하였기 때문에 결정 트리는 더 분할되지 않았습니다. 하지만 max_depth를 3으로 하면, 깊이 2 두 노드가 각각 결정 경계를 추가로 만듭니다. (점선)


[ 모델 해석 : 화이트박스와 블랙 박스 ]


여기에서 볼 수 있듯이, 결정 트리는 매우 직관적이고 결정 방식을 이해하기 쉽습니다.

이런 모델을 화이트 박스(white box) 모델이라고 합니다. 

반대로, 앞으로 보게 될 랜덤 포레스트나 신경망은 블랙 박스(black box) 모델입니다. 

이 알고리즘들은 성능이 뛰어나고 예측을 만드는 연산 과정을 쉽게 확인할 수 있지만, 

왜 그런 예측을 만드는 지는 쉽게 설명하기 어렵습니다. 

예를 들어, 신경망이 어떤 사람이 사진에 있다고 판단했을 때, 

무엇이 이런 예측을 낳게 했는지 파악하기 매우 어렵습니다.



[ 클래스 확률 추정 ]


결정 트리는 한 샘플이 특정 클래스 k에 속할 확률을 추정할 수도 있습니다.

먼저, 이 샘플에 대해 리프 노드를 찾기 위해 트리를 탐색하고, 그 노드에 있는 클래스 k의 훈련 샘플의 비율을 반환합니다.

예를 들어 깊이가 2인 왼쪽 노드는 클래스에 해당할 확률을 출력합니다.

Iris-Setosa는 0%(0/54), Iris-Versicolor는 90.7%(49/54), Iris-Verginica는 9.3%(5/54)입니다.

당연히 클래스 1개를 예측한다면 가장 높은 확률을 가진 Iris-Versicolor(클래스 1)를 출력할 것입니다.


tree_clf.predict_proba([[5, 1.5]])

array([[0.        , 0.90740741, 0.09259259]])

tree_clf.predict([[5, 1.5]])

array([1])


[ CART 훈련 알고리즘 ]


사이킷런은 결정 트리를 훈련시키기 위해 (즉, 트리를 성장시키기 위해) CART(Classification And Regression Tree) 알고리즘을 사용합니다. 이 알고리즘의 아이디어는, 훈련 세트를 하나의 특성 의 임곗값 를 사용해 두 개의 서브셋으로 나눕니다.

아까처럼 (꽃잎의 길이가 몇 cm 이하인지?) 그렇다면 어떻게 와 를 고를까요? 

(크기에 따른 가중치가 적용된) 가장 순수한 서브셋으로 나눌 수 있는 (,  ) 짝을 찾습니다.

이 알고리즘이 최소화해야 하는 비용 함수는 아래와 같습니다.


# 분류에 대한 CART 비용 함수


는 왼쪽 / 오른쪽 서브셋의 불순도

는 왼쪽 / 오른쪽 서브셋의 샘플 수


이 과정을 max_depth 매개변수로 정의된 최대 깊이가 되면 중지하거나, 불순도를 줄이는 분할을 찾을 수 없을 때 멈추게 됩니다.

잠시 후에 살펴볼, 다른 몇 개의 매개변수도 중지 조건에 관여합니다.

(min_samples_split, min_samples_leaf, min_weight_fraction_leaf, max_leaf_nodes) 


[NOTE]

여기에서 볼 수 있듯이, CART 알고리즘은 탐욕적 알고리즘(greedy algorithm)입니다.

맨 위 루트 노드에서 최적의 분할을 찾으며 각 단계에서 이 과정을 반복합니다. 

현재 단계의 분할이 몇 단계를 거쳐 가장 낮은 불순도로 이어질 수 있을지 없을지는 고려하지 않습니다.

탐욕적 알고리즘은 종종 납득할만한 훌륭한 솔루션을 만들어 내지만, 최적의 솔루션을 보장하지는 않습니다.


불행하게도 최적의 트리를 찾는 것은 NP-완전(NP-Complete) 문제로 알려져 있습니다. (수학적 미해결 문제)

이 문제는  시간이 필요하고 매우 작은 훈련 세트에서 적용하기 어렵습니다.

그러므로 '납득할 만한 좋은 솔루션' 으로만 만족해야 합니다.


[ 계산 복잡도 ]


예측을 하려면 결정 트리를 루트 노드에서부터 리프 노드까지 탐색해야 합니다.

일반적으로 결정 트리는 거의 균형을 이루고 있으므로 결정 트리를 탐색하기 위해서는 약 개의 노드를 거쳐야 합니다. 각 노드는 하나의 특성값만 확인하기 때문에 예측에 필요한 전체 복잡도는 특성 수와 무관하게 입니다.

그래서 큰 훈련 세트를 다룰 때도 예측 속도가 매우 빠릅니다.


그러나, 훈련 알고리즘은 각 노드에서 모든 훈련 샘플의 모든(또는 max_features에 지정된 수보다 적은) 특성을 비교합니다.

그래서 훈련 복잡도는 이 됩니다. 

훈련 세트가 수천 개 이하의 샘플 정도로 작을 경우, 사이킷런은 (presort=True로 지정하면) 미리 데이터를 정렬하여 훈련 속도를 높일 수 있습니다. 하지만 훈련 세트가 크면 느려집니다.


[ 지니 불순도 또는 엔트로피 ]


기본적으로 지니 불순도가 사용되지만, criterion 매개변수를 'entropy'로 지정하여 엔트로피 불순도를 사용할 수 있습니다.

(DecisionTreeClassifier의 criterion 매개변수의 기본값은 'gini'이고, DecisionTreeRegressor의 기본값은 'mse'입니다.)

엔트로피는 분자의 무질서함을 측정하는 것으로 원래 열역학의 개념입니다. 분자가 안정되고 질서 정연하면 0에 가깝게 됩니다.

이 개념은 후에 여러 분야에 퍼졌는데, 메시지의 평균 정보 양을 측정하는 섀넌(Shannon)의 정보 이론도 여기에 포함됩니다.

여기서는 모든 메시지가 동일할 때 엔트로피가 0이 됩니다. 머신러닝에서는 불순도의 측정 방법으로 자주 사용됩니다.

어떤 세트가 한 클래스의 샘플만 담고 있다면 엔트로피가 0이 됩니다. 아래의 식은 엔트로피 계산식입니다.


[ 엔트로피 ]


예를 들어, 깊이 2의 왼쪽 노드의 엔트로피는


와 같습니다.


지니 불순도와 엔트로피 중 어떤 것을 사용해야 할까요? 실제로는 큰 차이가 없습니다.

즉, 둘 다 비슷한 트리를 만들어냅니다. 지니 불순도가 조금 더 계산이 빠르기 때문에 기본값으로 좋습니다.

그러나, 다른 트리가 만들어지는 경우 지니 불순도가 가장 빈도 높은 클래스를 한쪽 가지(branch)로 고립시키는 경향이 있는 반면 엔트로피는 조금 더 균형 잡힌 트리를 만듭니다.


[TIP]

감소되는 엔트로피의 양을 종종 정보 이득(information gain)이라고 부릅니다. 

열역학 제 2법칙을 생각하면 이상하게 들릴 수 있지만, 여기서는 노드가 점점 더 순수하게 분할되기 때문에 엔트로피가 감소되며 이진 로그를 사용하여 계산합니다. 일반적으로, 머신 러닝에서 정보 이득을 말할 때는 쿨백-라이블러 발산을 뜻합니다.


물체의 상태만으로 결정되는 엔트로피라는 양을 정의하고, 이것으로 제2법칙에 대해, '열의 출입이 차단된 고립계에서는 엔트로피가 감소하는 변화가 일어나지 않고, 항상 엔트로피가 증가하는 방향으로 변하며, 결국에는 엔트로피가 극대값을 가지는 평형상태에 도달한다'고 할 수 있다.

[네이버 지식백과]열역학 제2법칙 [the second law of thermodynamics, 熱力學第二法則] (두산백과)


쿨백-라이블러 발산(Kullback–Leibler divergence, KLD)은 두 확률분포의 차이를 계산하는 데에 사용하는 함수로, 어떤 이상적인 분포에 대해, 그 분포를 근사하는 다른 분포를 사용해 샘플링을 한다면 발생할 수 있는 정보 엔트로피 차이를 계산한다.

[위키백과]정보 이득 [https://ko.wikipedia.org/wiki/%EC%A0%95%EB%B3%B4_%EC%9D%B4%EB%93%9D]


[ 규제 매개변수 ]


결정 트리는 훈련 데이터에 대한 제약사항이 거의 없습니다. (반대로, 선형 모델은 데이터가 꼭 선형일 거라 가정합니다.)

제한을 두지 않으면 트리가 훈련 데이터에 아주 가깝게 맞추려고 해서 대부분 과대적합되기 쉽습니다.

결정 트리는 모델 파라미터가 전혀 없는 것이 아니라 (보통 많음), 훈련되기 전에 파라미터 수가 결정되지 않기 때문에 이런 모델을 비파라미터 모델(nonparametric model)이라 부르곤 합니다. 그래서 모데레 구조가 데이터에 맞춰져서 고정되지 않고 자유롭습니다. 반대로, 선형 모델과 같은 파라미터 모델(parametric model)은 미리 정의된 모델 파라미터 수를 가지므로 자유도가 제한되고 과대적합될 위험이 줄어듭니다. (하지만, 과소적합될 위험은 커집니다.)


훈련 데이터에 대한 과대적합을 피하기 위해 학습할 때, 결정 트리의 자유도를 제한할 필요가 있습니다.

이미 알고 있듯, 이를 규제라고 합니다. 규제 매개변수는 사용하는 알고리즘에 따라 다르지만, 보통 적어도 최대 깊이는 제어할 수 있습니다. 사이킷런에서는 max_depth 매개변수로 이를 조절합니다. (기본값은 None, 무제한) max_depth를 줄이면 모델을 규제하게 되고, 과대적합의 위험이 감소합니다.


DecisionTreeClassifier에는 비슷하게 결정 트리의 형태를 제한하는 다른 매개변수가 몇 개 있습니다.


min_samples_split (분할되기 위해 노드가 가져야 하는 최소 샘플 수)

min_samples_leaf (리프 노드가 가지고 있어야 할 최소 샘플 수)

min_weight_fraction_leaf (min_samples_leaf 와 같지만 가중치가 부여된 전체 샘플 수에서의 비율)

max_leaf_nodes (리프 노드의 최대 수)

max_features (각 노드에서 분할에 사용할 특성의 최대 수)


** 사이킷런 0.19에서는 이 외에도 분할로 얻어질 불순도 감소량을 지정하는 min_impurity_decrease와 분할 대상이 되기 위해 필요한 최소한의 불순도를 지정하는 min_impurity_split이 추가되었습니다. DecisionTreeRegressor에도 동일한 매개변수가 있습니다.


위의 min_으로 시작하는 매개변수를 증가시키거나 max_로 시작하는 매개변수를 감소시키면 모델에 규제가 커집니다.


아래의 코드는 moons 데이터셋에 훈련시킨 두 개의 결정 트리입니다.

왼쪽 결정 트리는 기본 매개변수를 사용하여 훈련시켰고 (즉 규제가 없음), 오른쪽 결정 트리는 min_samples_leaf=4로 지정하여 훈련시켰습니다. 왼쪽 모델은 확실히 과대적합되었고, 오른쪽 모델은 일반화 성능이 더 좋을 것 같아 보입니다.


from sklearn.datasets import make_moons


Xm, ym = make_moons(n_samples=100, noise=0.25, random_state=53)


deep_tree_clf1 = DecisionTreeClassifier(random_state=42)

deep_tree_clf2 = DecisionTreeClassifier(random_state=42, min_samples_leaf=4)

deep_tree_clf1.fit(Xm,ym)

deep_tree_clf2.fit(Xm,ym)


plt.figure(figsize=(11,4))

plt.subplot(121)

plot_decision_boundary(deep_tree_clf1, Xm, ym, axes=[-1.5, 2.5, -1, 1.5], iris=False)

plt.title('규제 없음', fontsize=16)


plt.subplot(122)

plot_decision_boundary(deep_tree_clf2, Xm, ym, axes=[-1.5, 2.5, -1, 1.5], iris=False)

plt.title('min_samples_leaf={}'.format(deep_tree_clf2.min_samples_leaf), fontsize=14)


plt.show()



[ 회귀 ]


결정 트리는 회귀 문제에서도 사용할 수 있습니다.

사이킷런의 DecisionTreeRegressor를 사용해 노이즈가 섞인 2차 함수 형태의 데이터셋에서 max_depth=2 설정으로 회귀 트리를 만들어보겠습니다.


np.random.seed(42)

m = 200

X = np.random.rand(m, 1)

y = 4* (X-0.5)**2

y = y + np.random.randn(m, 1) / 10


from sklearn.tree import DecisionTreeRegressor


tree_reg1 = DecisionTreeRegressor(random_state=42, max_depth=2)

tree_reg2 = DecisionTreeRegressor(random_state=42, max_depth =3)

tree_reg1.fit(X, y)

tree_reg2.fit(X, y)


def plot_regression_predictions(tree_reg, X, y, axes=[0, 1, -0.2, 1], ylabel='$y$'):

    x1 = np.linspace(axes[0], axes[1], 500).reshape(-1, 1)

    y_pred = tree_reg.predict(x1)

    plt.axis(axes)

    plt.xlabel('$x_1$', fontsize=18)

    if ylabel :

        plt.ylabel(ylabel, fontsize=18, rotation=0)

    plt.plot(X, y, 'b.')

    plt.plot(x1, y_pred, 'r.-', linewidth=2, label=r'$\hat{y}$')

    

plt.figure(figsize=(11,4))

plt.subplot(121)

plot_regression_predictions(tree_reg1, X, y)

for split, style in ((0.1973, 'k-'), (0.0917, 'k--'), (0.7718, 'k--')):

    plt.plot([split, split], [-0.2, 1], style, linewidth=2)

plt.text(0.21, 0.65, '깊이=0', fontsize=15)

plt.text(0.01, 0.2, '깊이=1', fontsize=13)

plt.text(0.65, 0.8, '깊이=1', fontsize=13)

plt.legend(loc='upper center', fontsize=18)

plt.title('max_depth=2', fontsize=14)


plt.subplot(122)

plot_regression_predictions(tree_reg2, X, y, ylabel=None)

for split, style in ((0.1973, 'k-'), (0.0917, 'k--'), (0.7718, 'k--')):

    plt.plot([split, split], [-0.2, 1], style, linewidth=2)

for split in (0.0458, 0.1298, 0.2873, 0.9040):

    plt.plot([split, split], [-0.2, 1], "k:", linewidth=1)

plt.text(0.3, 0.5, "깊이=2", fontsize=13)

plt.title("max_depth=3", fontsize=14)


plt.show()


export_graphviz(

        tree_reg1,

        out_file=image_path("regression_tree.dot"),

        feature_names=["x1"],

        rounded=True,

        filled=True

    )


import graphviz

with open("/root/ml/regression_tree.dot") as f:

    dot_graph = f.read()

dot = graphviz.Source(dot_graph)

dot.format = 'png'

dot.render(filename='regression_tree', directory='/root/ml', cleanup=True)

dot




앞서 만든 분류 트리와 매우 비슷해 보입니다. 

주요한 차이는, 각 노드에서 클래스를 예측하는 대신, 어떤 값을 예측한다는 점입니다.

예를 들어, x1=0.6인 샘플의 클래스를 예측한다고 가정해보겠습니다.

루트 노드부터 시작해서 트리를 순회하면 결국 value=0.111인 리프 노드에 도달하게 됩니다.

이 리프 노드에 있는 110개 훈련 샘플의 평균 타깃값이 예측값이 됩니다.

이 예측값을 사용하여 110개 샘플에 대한 평균 제곱오차(MSE)를 계산하면 0.015가 됩니다.


이 모델의 예측은 아래의 그림 왼쪽 그래프에 나타나있습니다.


각 영역의 예측값은 항상 그 영역에 있는 타깃값의 평균이 됩니다.

알고리즘은 예측값과 가능한 많은 샘플이 가까이 있도록 영역을 분할합니다.


CART 알고리즘은 훈련 세트 불순도를 최소화하는 방향으로 분할하는 대신 평균제곱오차(MSE)를 최소화하도록 분할하는 것을 제외하고는 앞서 설명한 것과 거의 비슷하게 작동합니다. 

아래의 식은, 알고리즘이 최소화 하기 위한 비용 함수를 보여줍니다.


[회귀를 위한 CART 비용 함수]



분류에서와 같이 회귀 작업에서도 결정 트리가 과대적합되기 쉽습니다.

규제가 없다면(기본 매개변수를 사용한다면) 아래 그림의 왼쪽과 같은 예측을 하게 됩니다.

아주 크게 과대적합된 모습인데, 우측의 min_sample_leaf=10으로 지정하면 훨씬 나은 모델을 만듭니다.


# 회귀 작업에서의 과대적합 모델과 규제모델


tree_reg1 = DecisionTreeRegressor(random_state=42)

tree_reg2 = DecisionTreeRegressor(random_state=42, min_samples_leaf  = 10)

tree_reg1.fit(X, y)

tree_reg2.fit(X, y)


x1 = np.linspace(0, 1, 500).reshape(-1, 1)

y_pred1 = tree_reg1.predict(x1)

y_pred2 = tree_reg2.predict(x1)


plt.figure(figsize=(11,4))


plt.subplot(121)

plt.plot(X, y, 'b.')

plt.plot(x1, y_pred1, 'r.-', linewidth=2, label=r'$\hat{y}$')

plt.axis([0, 1, -0.2, 1.1])

plt.xlabel("$x_1$", fontsize=18)

plt.ylabel("$y$", fontsize=18, rotation=0)

plt.legend(loc="upper center", fontsize=18)

plt.title("규제 없음", fontsize=14)


plt.subplot(122)

plt.plot(X, y, "b.")

plt.plot(x1, y_pred2, "r.-", linewidth=2, label=r"$\hat{y}$")

plt.axis([0, 1, -0.2, 1.1])

plt.xlabel("$x_1$", fontsize=18)

plt.title("min_samples_leaf={}".format(tree_reg2.min_samples_leaf), fontsize=14)


plt.show()




[ 불안정성 ]


아마 결정 트리가 장점이 많다는 것을 알게 되었을 것입니다.

이해와 해석이 쉽고, 사용하기 편하고, 여러 용도로 사용할 수 있으며, 성능도 뛰어납니다.

하지만 몇 가지 제한사항이 있습니다.


결정 트리는 계단 모양의 결정 경계를 만듭니다. (모든 분할은 축에 수직입니다.)

그래서 훈련 세트의 회전에 민감합니다. 


아래의 데이터셋은 간단한 선형으로 구분될 수 있는 데이터셋을 예로 보여줍니다.

왼쪽의 결정 트리는 쉽게 구분하지만, 데이터셋을 45도 회전한 오른쪽의 결정 트리는 불필요하게 구불구불합니다.

두 결정 트리 모두 훈련 세트를 완벽하게 학습하지만, 오른쪽 모델은 잘 일반화가 될 것 같지 않습니다.

이 문제를 해결하는 방법 중 하나는, 훈련 데이터를 더 좋은 방향으로 회전키는 PCA 기법을 사용하는 것입니다.

(이후에 나옴)


np.random.seed(6)

Xs = np.random.rand(100, 2) - 0.5

ys = (Xs[:, 0] > 0).astype(np.float32) * 2


angle = np.pi / 4

rotation_matrix = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])

Xsr = Xs.dot(rotation_matrix)


tree_clf_s = DecisionTreeClassifier(random_state=42)

tree_clf_s.fit(Xs, ys)

tree_clf_sr = DecisionTreeClassifier(random_state=42)

tree_clf_sr.fit(Xsr, ys)


plt.figure(figsize=(11, 4))

plt.subplot(121)

plot_decision_boundary(tree_clf_s, Xs, ys, axes=[-0.7, 0.7, -0.7, 0.7], iris=False)

plt.subplot(122)

plot_decision_boundary(tree_clf_sr, Xsr, ys, axes=[-0.7, 0.7, -0.7, 0.7], iris=False)


plt.show()



결정 트리의 주된 문제는 훈련 데이터에 있는 작은 변화도 매우 민감하다는 것입니다.

예를 들어, 훈련 세트에서 가장 넓은 Iris-Versicolor를 제거하고 결정 트리를 훈련시키면 이와 같은 모델을 얻게 됩니다.

이전에 만든 결정 트리와는 매우 다른 모습입니다.


(제거 후)


(제거 전)


다음 장에서 보게 될 랜덤 포레스트는 많은 트리에서 만든 예측을 평균하여 이런 불안정성을 극복할 수 있습니다.


 블로그 

출처


이 글의 상당 부분은  [핸즈온 머신러닝, 한빛미디어/오렐리앙 제롱/박해선] 서적을 참고하였습니다.


나머지는 부수적인 함수나 메서드에 대해 부족한 설명을 적어두었습니다.

학습용으로 포스팅 하는 것이기 때문에 복제보다는 머신러닝에 관심이 있다면 구매해보시길 추천합니다.


도움이 되셨다면 로그인 없이 가능한

아래 하트♥공감 버튼을 꾹 눌러주세요! 

반응형

'Python (Linux)' 카테고리의 다른 글

45. Python - 앙상블 학습과 랜덤포레스트  (0) 2019.02.21
44. Python - 6장 연습문제  (0) 2019.02.12
42. Python - 5장 연습문제  (0) 2019.02.10
41. Python - SVM 이론 2  (0) 2019.02.10
40. Python - SVM 이론 1  (0) 2019.02.08