기본 콘텐츠로 건너뛰기

벡터와 행렬에 관련된 그림들

[ML] 결정트리(Decision Tree) 모델

결정트리(Decision Tree) 모델

결정트리(decision tree) 모델은 분류 및 회귀에 사용되는 비모수적 지도학습방법으로 데이터의 특징들에서 추론된 규칙들에 의해 분류하여 예측값을 반환합니다. klearn.tree.DecisionTreeClassifier() 클래스를 사용하여 모델을 생성합니다.

클래스이 인수는 다음과 같습니다.

  • max_depth: 분류 단계를 지정, 기본값은 min_sample_split에 지정된 수 또는 분류된 객체의 entropy가 최소가 될때까지 분류가 진행
  • min_sample_split: 분류된 그룹의 데이터수로 기본값은 2, 이에 적합된 노드에서 학습은 종료됩니다.
  • max_feature: 최적의 분류를 찾을 때 고려되는 특징의 수, 기본값은 none
  • random_state: random_seed의 역할

다음은 make_classification() 함수를 적용하여 생성한 가공 데이터 셋에 대해 생성한 결정트리모델입니다.

import numpy as np
import pandas as pd
from sklearn.datasets import make_classification
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier 
from sklearn.model_selection import train_test_split 
from sklearn import metrics 
import matplotlib.pyplot as plt
import seaborn as sns
X1, y =make_classification(n_samples=100, random_state=41)
X=X1[:, :2]
X.shape, y.shape
((100, 2), (100,))
clf=DecisionTreeClassifier().fit(X, y)
pred=clf.predict(X)
acc=metrics.accuracy_score(y, pred)
acc
1.0

결정트리의 각 단계를 노드(node)라고 하며 최상위의 노드를 뿌리 노드(root node), 다음의 각 단계를 노드(node), 마지막의 최종 결과를 터미널 노드 또는 잎 노드(terminal or leaf node)라고 합니다.

다음은 이 모델에 의한 분류 과정을 tree.plot_tree() 함수를 사용하여 시각화 한 것입니다. 함수의 인수인 max_depth에 정수를 전달하여 분류 단계를 분리하여 나타낼 수 있습니다. 다음 그림은 분류 2단계까지 나타낸 것이로 분류 기준과 대응하는 결과를 나타내고 있습니다.

from sklearn import tree
fig=plt.figure(figsize=(10, 8))
_=tree.plot_tree(clf, class_names=["0","1"], max_depth=2, filled=True, fontsize=9)
plt.show()

위 그림의 각 노드는 분류기준과 그에 따른 결과를 나타냅니다. 결정트리와 다중 클래스의 분류 모델인 RandomForest는 분류기준으로 데이터의 엔트로피(entropy or impurity)로 부터 계산되는 정보획득 또는 gini index를 사용합니다. 위 그림에서는 gini index를 사용하는 것으로 식 1과 같이 계산됩니다.

\begin{align} \text{gini index}&=1-\sum^n_{i=0}p(x=i)^2\\\tag{식 1}n:&\,\text{클래스의 수}\\p(x=i):&\,\text{class i의 확률 }\end{align}

다음은 1단계에서 True로 분류된 노드의 라벨의 내용입니다. 모두가 1이므로 p(x=1)=1, p(x=0)=0이므로 gini index는 0이 됩니다.

t1=np.where(X[:,0]<=-1.471)[0]
y[t1]
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
gini_t1=1-1**2; gini_t1
0

다음은 1단계에서 False로 분류된 노드의 라벨 내용입니다.

f1=np.where(X[:,0]>-1.471)[0]
y[f1]
array([1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1,
       0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0,
       0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0,
       0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1,
       0])
np.unique(y[f1], return_counts=True)
(array([0, 1]), array([50, 39], dtype=int64))
T=len(f1)
gini=1-(50/T)**2-(39/T)**2
gini
0.4923620754955182

결정트리는 모델에 대한 이해가 용이하며 시각화 할 수 있습니다. 또한 정규화, 누락값 제거등의 데이터의 전처리가 필요하지 않으며 다중 출력을 위한 모델을 생성할 수 있습니다. 그러나 지나치게 복잡한 트리를 형성하면 과적합의 문제를 유발시킬 수 있으며 데이터의 작은변화로 다른 트리가 생성되는 불안정성을 내재하고 있습니다. 즉, 이 모델을 일반화하는 것은 쉽지 않습니다.

다음은 데이터 pima-indians-diabetes에 대해 결정트리 모델을 생성합니다. 다운받은 데이터는 .csv 형식으로 pd.read_csv(경로, …) 함수로 호출합니다.

data=pd.read_csv("pima-indians-diabetes.csv")
data.head(3)
preg plas pres skin test mass pedi age class
0 6 148 72 35 0 33.6 0.627 50 1
1 1 85 66 29 0 26.6 0.351 31 0
2 8 183 64 0 0 23.3 0.672 32 1

위 데이터의 변수 class를 라밸로 설정하며 훈련세트(train)와 검정세트(test)로 분리하여 훈련세트로 학습합니다.

ind=data.drop(columns="class")
de=data.iloc[:,-1]
de[:3]
0    1
1    0
2    1
Name: class, dtype: int64
Xtr, Xte, ytr, yte=train_test_split(ind, de, test_size=0.1, random_state=1)
Xtr.shape, Xte.shape
((691, 8), (77, 8))

훈련세트에 대해 모델을 생성하고 훈련과 검증 세트에 대한 정확도를 측정합니다. 정확도는 model의 메소드.score(X, y) 또는 metrics.accuracy_score(y, y_pre) 함수로 즉정할 수 있습니다.

sklearn.tree.DecisionTreeClassifier() 클래스로 모델을 생성합니다. 이 클래스 인수 max_depth에 의해 분류의 단계를 설정할 수 있으며 기본값은 분류된 객체들의 엔트로피(불순도)가 순수해 질 때까지 또는 min_sample_split에 지정된 수에 도달 할 때까지 이루어 집니다. 다음 결과는 max_depth를 지정하지 않은 경우로 훈련세트의 정확도는 1이지만 검정 세트에 대해서 67% 정도로 큰 차이를 보입니다. 즉, 이 모델의 일반화는 어렵다고 할 수 있습니다.

clfS=DecisionTreeClassifier(random_state=1).fit(Xtr, ytr)
clfS.score(Xtr, ytr)
1.0
clfS.score(Xte, yte)
0.6753246753246753

모델의 max_depth의 변화에 따른 훈련과 검정 세트에 대한 정확도의 변화를 시각해 봅니다.

rng=np.arange(4, 19, 2)
trScore={}
teScore={}
for i in rng:
    m=DecisionTreeClassifier(max_depth=i, random_state=1).fit(Xtr, ytr)
    trScore[i]=m.score(Xtr, ytr)
    teScore[i]=m.score(Xte, yte)
plt.figure(figsize=(4,3))
plt.plot(trScore.keys(), trScore.values(), color="b", label="Train set")
plt.plot(teScore.keys(), teScore.values(), color="r", label="Test set")
plt.xlabel("max_depth")
plt.ylabel("accuacy")
plt.legend(loc="best")
plt.show()

위 결과는 이 모델에 의한 일반화가 쉽지 않음을 나타냅니다. 다음은 max_depth=14에서 이진분류 모델의 평가에 적용되는 여러 결과를 나타내봅니다(이진분류 추정기의 평가 참조).

model=DecisionTreeClassifier(max_depth=14, random_state=1).fit(Xtr, ytr)
def clf_eval(y, y_pre):
    confMat=confusion_matrix(y, y_pre)
    acc=accuracy_score(y, y_pre)
    preci=precision_score(y, y_pre)
    recall=recall_score(y, y_pre)
    f1=f1_score(y, y_pre)
    re=pd.Series([acc, preci, recall, f1], index=["accuracy","precision","recall", "F1 score"])
    return(confMat, re)
predTr=model.predict(Xtr)
tr_eval=clf_eval(ytr, predTr)
tr_eval
(array([[452,   0],
        [  1, 238]], dtype=int64),
 accuracy     0.998553
 precision    1.000000
 recall       0.995816
 F1 score     0.997904
 dtype: float64)

댓글

이 블로그의 인기 게시물

[Linear Algebra] 유사변환(Similarity transformation)

유사변환(Similarity transformation) n×n 차원의 정방 행렬 A, B 그리고 가역 행렬 P 사이에 식 1의 관계가 성립하면 행렬 A와 B는 유사행렬(similarity matrix)이 되며 행렬 A를 가역행렬 P와 B로 분해하는 것을 유사 변환(similarity transformation) 이라고 합니다. $$\tag{1} A = PBP^{-1} \Leftrightarrow P^{-1}AP = B $$ 식 2는 식 1의 양변에 B의 고유값을 고려한 것입니다. \begin{align}\tag{식 2} B - \lambda I &= P^{-1}AP – \lambda P^{-1}P\\ &= P^{-1}(AP – \lambda P)\\ &= P^{-1}(A - \lambda I)P \end{align} 식 2의 행렬식은 식 3과 같이 정리됩니다. \begin{align} &\begin{aligned}\textsf{det}(B - \lambda I ) & = \textsf{det}(P^{-1}(AP – \lambda P))\\ &= \textsf{det}(P^{-1}) \textsf{det}((A – \lambda I)) \textsf{det}(P)\\ &= \textsf{det}(P^{-1}) \textsf{det}(P) \textsf{det}((A – \lambda I))\\ &= \textsf{det}(A – \lambda I)\end{aligned}\\ &\begin{aligned}\because \; \textsf{det}(P^{-1}) \textsf{det}(P) &= \textsf{det}(P^{-1}P)\\ &= \textsf{det}(I)\end{aligned}\end{align} 유사행렬의 특성 유사행렬인 두 정방행렬 A와 B는 'A ~ B' 와 같...

[sympy] Sympy객체의 표현을 위한 함수들

Sympy객체의 표현을 위한 함수들 General simplify(x): 식 x(sympy 객체)를 간단히 정리 합니다. import numpy as np from sympy import * x=symbols("x") a=sin(x)**2+cos(x)**2 a $\sin^{2}{\left(x \right)} + \cos^{2}{\left(x \right)}$ simplify(a) 1 simplify(b) $\frac{x^{3} + x^{2} - x - 1}{x^{2} + 2 x + 1}$ simplify(b) x - 1 c=gamma(x)/gamma(x-2) c $\frac{\Gamma\left(x\right)}{\Gamma\left(x - 2\right)}$ simplify(c) $\displaystyle \left(x - 2\right) \left(x - 1\right)$ 위의 예들 중 객체 c의 감마함수(gamma(x))는 확률분포 등 여러 부분에서 사용되는 표현식으로 다음과 같이 정의 됩니다. 감마함수는 음이 아닌 정수를 제외한 모든 수에서 정의됩니다. 식 1과 같이 자연수에서 감마함수는 factorial(!), 부동소수(양의 실수)인 경우 적분을 적용하여 계산합니다. $$\tag{식 1}\Gamma(n) =\begin{cases}(n-1)!& n:\text{자연수}\\\int^\infty_0x^{n-1}e^{-x}\,dx& n:\text{부동소수}\end{cases}$$ x=symbols('x') gamma(x).subs(x,4) $\displaystyle 6$ factorial 계산은 math.factorial() 함수를 사용할 수 있습니다. import math math.factorial(3) 6 a=gamma(x).subs(x,4.5) a.evalf(3) 11.6 simpilfy() 함수의 알고리즘은 식에서 공통사항을 찾아 정리하...

sympy.solvers로 방정식해 구하기

sympy.solvers로 방정식해 구하기 대수 방정식을 해를 계산하기 위해 다음 함수를 사용합니다. sympy.solvers.solve(f, *symbols, **flags) f=0, 즉 동차방정식에 대해 지정한 변수의 해를 계산 f : 식 또는 함수 symbols: 식의 해를 계산하기 위한 변수, 변수가 하나인 경우는 생략가능(자동으로 인식) flags: 계산 또는 결과의 방식을 지정하기 위한 인수들 dict=True: {x:3, y:1}같이 사전형식, 기본값 = False set=True :{(x,3),(y,1)}같이 집합형식, 기본값 = False ratioal=True : 실수를 유리수로 반환, 기본값 = False positive=True: 해들 중에 양수만을 반환, 기본값 = False 예 $x^2=1$의 해를 결정합니다. solve() 함수에 적용하기 위해서는 다음과 같이 식의 한쪽이 0이 되는 형태인 동차식으로 구성되어야 합니다. $$x^2-1=0$$ import numpy as np from sympy import * x = symbols('x') solve(x**2-1, x) [-1, 1] 위 식은 계산 과정은 다음과 같습니다. $$\begin{aligned}x^2-1=0 \rightarrow (x+1)(x-1)=0 \\ x=1 \; \text{or}\; -1\end{aligned}$$ 예 $x^4=1$의 해를 결정합니다. solve() 함수의 인수 set=True를 지정하였으므로 결과는 집합(set)형으로 반환됩니다. eq=x**4-1 solve(eq, set=True) ([x], {(-1,), (-I,), (1,), (I,)}) 위의 경우 I는 복소수입니다.즉 위 결과의 과정은 다음과 같습니다. $$x^4-1=(x^2+1)(x+1)(x-1)=0 \rightarrow x=\pm \sqrt{-1}, \; \pm 1=\pm i,\; \pm1$$ 실수...