Machine Learning

[머신러닝#4] 지도학습 - 의사결정 나무(Decision Tree)

j.d 2023. 7. 6. 16:59

계속해서 지도학습의 알고리즘에 공부해보겠습니다.

 

지난시간에는 로지스틱 회귀를 이용해 binary값을 예측하는 모델을 만들어보았습니다.

 

[머신러닝#3] 지도 학습 - 로지스틱 회귀(Logistic Regression)

이번 시간에 이야기해볼 지도 학습 머신러닝 모델은 로지스틱 회귀입니다. 로지스틱 회귀 로지스틱 회귀 모델은 분류에 사용될 수 있는 회귀 알고리즘입니다. 어떤 상황을 가정해 봅시다. 만약

just-data.tistory.com

 

오늘은 또 다른 지도학습의 대표적인 알고리즘인 결정트리에 대해 알아보겠습니다.


의사결정 나무(Decision Tree)

의사결정나무란 학습 데이터를 분석하여 데이터에 내재되어 있는 패턴을 통해, 한번에 하나씩의 설명변수를 사용하여 분류와 회귀가 가능한 규칙들의 집합을 생성하는 모델입니다.

 

적절한 분리 기준과 깊이를 지정하여 모델을 생성합니다.

※ 스무고개와 비슷한 개념이라고 할 수 있습니다.

 

의사결정 나무는 매우 복잡한 데이터셋도 학습할 수 있는 강력한 알고리즘입니다.

 

또한 최근에 자주 사용되는 랜덤 포레스트의 기본 구성 요소이기도 합니다.

※ 랜덤 포레스트는 추후 앙상블 모델에서 다루도록 하겠습니다.

 

먼저 학습 과정을 살펴보겠습니다.

  1. 한번에 설명 변수 하나씩 데이터를 선택
  2. 2개 혹은 그 이상의 부분 집합으로 분할
  3. 데이터 순도가 균일해지는 방향으로 재귀적으로 분할

그런데 어떻게 데이터 순도가 균일해지는지 알 수 있을까요?

 

여기서 엔트로피지니계수가 등장합니다.

엔트로피(entropy)

엔트로피는 랜덤변수의 정보량을 수치화한 수치로 사건을 표현할 수 있는 평균 정보의 양입니다.

 

정보량은 어떤 사건이 가지고 있는 정보의 양을 의미하며, 그래프와 식은 다음과 같이 나타낼 수 있습니다.

$$I(x)=-log_{2}P(x)$$

그래프에서 0으로 갈수록 확률이 높아지고, 1로 갈수록 확률이 낮아지는 것을 확인할 수 있습니다.

 

다시 말해, 발생 확률이 증가할수록 정보량은 낮아진다 라고 해석할 수 있습니다.

 

여기서 엔트로피의 식과 그래프 확인해보겠습니다.

$$H(X)=\sum_{k}^{}P(k)log_{2}P(k)$$

평균 정보의 양이기 때문에 정보의 양에 확률을 곱한 것의 합으로 나타내게 됩니다.

 

직관적으로 생각해보면 확률이 0또는 1로 갈수록 해당 사건이 발생할 확률이 높기 때문에 순도가 높아진다는 관점이란걸 알 수 있습니다.

 

지니계수(Gini index)

또 다른 불순도 지표로는 지니계수가 있습니다.

 

지니계수는 범주형 변수에서 사용되며, 데이터의 통계적 분산 정도를 정량화해서 표현한 값입니다.

경제 분야에서는 경제적 분평등 정도를 수치화한 값입니다.

 

정의에서 알 수 있듯이 값이 높을수록 분산이 많이 됐다고 생각해보면 다양하게 섞여 있다고 추론해볼 수 있습니다.

 

반대로, 값이 작을수록 분산이 적다는 얘기는 값이 한쪽으로 몰려있을 가능성이 높습니다.

 

지니계수는 다음과 같이 나타낼 수 있으며, 0부터 1사이의 값을 가집니다.

$$GI(A)=1-\sum_{k=1}^{m}p_{k}^2$$

 

앞에서의 불순도 지표를 통해 학습한 의사결정 나무는 회귀와 분류 모두 가능하기 때문에 추론하는 방법이 약간 다릅니다.

  • 분류
    • 끝 노드에서 가장 빈도가 높은 범주에 새로운 데이터를 분류
  • 회귀 
    • 끝 노드에서 종속 변수의 평균을 예측값으로 반환

 

장점

의사결정 나무의 가장 큰 장점은 직관적이기 때문에 이해하기 쉽고 적용하기 용이하다는 점입니다.

 

밑에서 코드로 구현해서 시각화를 해보면 직관적인 분리 기준에 의해 표현되기 때문에 쉽게 이해할 수 있습니다.

 

이러한 특징으로 인해 의사결정과정에 대한 설명이 가능하게 됩니다. 

※ 이러한 모델을 '화이트 박스' 모델이라고 합니다.

 

예를 들어, 스포츠 경기에 대한 취소 여부를 결정하는 상황에서 어떤 요인(습도, 바람, 날씨 등)으로 인해 모델이 예측을 하였는지를 알 수 있습니다.

 

또한 중요한 변수를 선택할 때 유용합니다.

 

0부터 100까지 임의의 숫자를 up, down으로 맞춘다고 가정했을 때, 우리는 맨처음 50을 외칠것입니다.

 

가장  많이 걸러낼 수 있기 때문이죠.

 

이와 마찬가지로 의사결정 나무에서 상단에서 사용된 변수일수록 변별력이 높기 때문에 중요도가 높다고 할 수 있습니다.

 

마지막으로 정규분포 가정과 같은 통계적 가정이 필요 없습니다.

※ 추가적으로 데이터 전처리(특성의 스케일링 맞추기 등)가 거의 필요하지 않습니다. 

 

단점

강력한 알고리즘인 만큼 좋은 모형을 만들기 위해 많은 데이터와 시간이 소요됩니다.

 

또한 계단 모양의 결정 경계를 만들기 때문에 훈련 세트의 회전에 민감합니다.

※ 아래에서 그래프를 통해 알아보도록 하겠습니다.

 

따라서 훈련 데이터를 더 좋은 방향으로 회전시키는 것이 필요합니다.

 

마지막으로 훈련데이터에 있는 작은 변화에도 매우 민감하다는 것입니다.

 

사실 이러한 점을 극복하기 위해 랜덤 포레스트가 등장하게 됩니다.

 

알고리즘

재귀적 분할 알고리즘은 크게 3가지가 있습니다.

CART(Classification And Regression Tree)

CART는 지니계수를 사용한 가장 널리 사용되는 의사결정 알고리즘입니다.

 

먼저 특성 $k$의 입곗값$t_{k}$를 사용해 두 개의 서브셋으로 나눕니다.

 

이후 아래의 비용 함수를 최소화하는 방향으로 서브셋을 나누는 과정을 반복합니다.

$$J(k, t_{k})=\frac{m_{left}}{m}G_{left}+\frac{m_{right}}{m}G_{right}$$

 

여기에서 알 수 있듯이 CART는 탐욕적 알고리즘으로 현재의 단계에서 가장 좋은 선택을 하게 됩니다. 

 

따라서 최적의 솔루션을 보장하지는 않습니다.

 

하지만 납득할 만한 좋은 솔루션으로 많이 사용되고 있습니다.

 

C4.5

C4.5 또는 C5.0은 엔트로피를 사용한 방식으로 정보 이론에 기초한 알고리즘입니다.

 

CHAID

CHAID는 분류에만 사용할 수 있는 알고리즘으로, chi-square를 사용하여 불순도를 측정합니다.

 

지금까지의 알고리즘을 비교해보면 아래의 표와 같습니다.


이제 코드를 이용해 실습해보도록 하겠습니다.

 

분류

iris 데이터 셋을 이용했습니다.

 

간단한 전처리는 한번에 넘어가겠습니다.

import seaborn as sns
iris = sns.load_dataset('iris') 

X = iris.drop('species', axis=1) 
y_ = iris['species']

from sklearn.preprocessing import LabelEncoder

classle = LabelEncoder()
y = classle.fit_transform(iris['species'].values) 

from sklearn.model_selection import train_test_split

X_train,X_test,y_train,y_test=train_test_split(X,y, test_size=0.3, random_state=1, stratify=y)

의사결정나무 클래스를 통해 모델을 불러오겠습니다.

 

이때, 불순도 알고리즘을 옵션을 통해 선택할 수 있습니다.("gini", "entropy")

※  default는 "gini" 입니다.

from sklearn.tree import DecisionTreeClassifier

dtc = DecisionTreeClassifier(criterion='gini', max_depth=5, random_state=1)

dtc.fit(X_train, y_train)
y_train_pred = dtc.predict(X_train)  
y_test_pred = dtc.predict(X_test)

이제 정확도와 confusion matrix를 확인해보겠습니다.

from sklearn.metrics import accuracy_score

print(accuracy_score(y_train, y_train_pred))
print(accuracy_score(y_test, y_test_pred))

from sklearn.metrics import confusion_matrix

print(confusion_matrix(y_test,y_test_pred))

또한 위에서 말씀드렸다시피 시각화를 통해 모델이 어떻게 구성되었는지 알 수 있습니다.

from sklearn.tree import plot_tree
import matplotlib.pyplot as plt

plt.figure(figsize=(25,15))
features = iris.columns[:-1]
classes = ['setosa','versicolor','virginica']
plot_tree(dtc,feature_names=features,class_names=classes,filled=True,fontsize=15)
plt.show()

 

회귀

데이터를 임의로 만들어서 사용하겠습니다.

import numpy as np

rng = np.random.RandomState(1)
X = np.sort(5 * rng.rand(80, 1), axis=0) 
y = np.sin(X).ravel()
y[::5] += 3 * (0.5 - rng.rand(16)) #noise 생성

회귀 알고리즘에서도 불순도를 선택할 수 있습니다.("mse", "friedman_mse","mae")

※ default는 "mse"입니다.

 

from sklearn.tree import DecisionTreeRegressor

regr_1 = DecisionTreeRegressor(max_depth=2)
regr_2 = DecisionTreeRegressor(max_depth=5)
regr_1.fit(X, y)
regr_2.fit(X, y)

y_pred_1 = regr_1.predict(X)
y_pred_2 = regr_2.predict(X)

MSE를 이용해 성능을 평가하겠습니다.

from sklearn.metrics import mean_squared_error

print(mean_squared_error(y, y_pred_1))
print(mean_squared_error(y, y_pred_2))

추가적으로 시험데이터로 예측해보겠습니다.

X_test = np.arange(0.0, 5.0, 0.01)[:, np.newaxis] 

y_pred_1 = regr_1.predict(X_test)
y_pred_2 = regr_2.predict(X_test)
import matplotlib.pyplot as plt

# Plot the results
plt.figure()
plt.scatter(X, y, s=20, edgecolor="black", c="darkorange", label="data")
plt.plot(X_test, y_pred_1, color="cornflowerblue", label="max_depth=2", linewidth=2)
plt.xlabel("data")
plt.ylabel("target")
plt.title("Decision Tree Regression")
plt.legend()
plt.show()

plt.figure()
plt.scatter(X, y, s=20, edgecolor="black", c="darkorange", label="data")
plt.plot(X_test, y_pred_2, color="yellowgreen", label="max_depth=5", linewidth=2)
plt.xlabel("data")
plt.ylabel("target")
plt.title("Decision Tree Regression")
plt.legend()
plt.show()

그래프에서 알 수 있듯이 모델2는 noise에 과적합되는 현상을 보이고 있습니다.


여기까지 의사결정 나무의 내용이었습니다.

 

나중에 랜덤 포레스트의 구성요소로 다시 등장하니 알아두시면 좋을 것 같습니다.

 

포스팅 내용 중  다른 생각이 있는 분 혹은 수정해야 할 부분이 있으시면 댓글을 통해 그 의견을 나눠보면 너무 좋을 것 같습니다.