결정트리모델은 특성들을 기준으로 샘플을 분류해 나가는데, 마치 Tree model처럼 뻗어나간다고해서 Decision Tree라는 이름을 갖는다.
결정트리는 학습 결과로 IF-THEN 형태의 규칙이 만들어진다.
결정트리는 퍼셉트론이나 로지스틱 회귀와 달리 선형 분리 불가능한 데이터도 분류할 수 있으나, 선형분리 가능한 문제는 잘 풀지 못한다. 또한 데이터를 조건 분기로 나눠가는 특성상, 트리가 깊어질수록 학습에 사용되는 데이터 수가 적어져 과적합을 일으키기 쉽다.
결정 트리는 학습 데이터로부터 조건식을 만들고, 예측할 때는 트리의 루트(최상위조건)부터 순서대로 조건분기를 타면서 리프에 도착하면 예측결과를 내는 알고리즘이다.
불순도(Impurity)를 기준으로 가능한 한 같은 클래스끼리 모이도록 조건 분기를 학습한다.
구체적으로는 정보획득(Information Gain)이나 지니게수(Gini Coefficient)등의 값을 불순도로 사용해, 그 값이 낮아지도록 데이터를 분할한다.
이후 결정트리가 응용되어 랜덤포레스트와 경사부스팅결정트리가 생겨났다.
결정트리의 각 노드는 뿌리, 중간, 말단 노드로 나뉠 수 있다.
지니불순도
※ 지니계수 : 경제학에서 불평등지수를 나타낼 때 사용하는 것으로 0일 때 완전 평등, 1일 때 완전 불평등을 의미합니다.
머신러닝에서는 데이터가 다양한 값을 가질수록 평등하며 특정 값으로 쏠릴 때 불평등한 값이 됩니다. 즉, 다양성이 낮을수록 균일도가 높다는 의미로 1로 갈수록 균일도가 높아 지니계수가 높은 속성을 기준으로 분할
임의의 새로운 변수가 데이터셋으로부터 클래스 라벨의 분포를 따라 무작위로 분류된 경우, 임의 변수의 새로운 인스턴스의 잘못된 분류 가능성에 대한 측정치.
분류가 잘 되었는지 판단할 수 있는 척도이다.
$G_i = {1 - \Sigma_{k=1}^n{p_ik^2} }$
$I_G(p) = \Sigma_{i=1}^J{p_i ( 1-p_i) } = 1 - \Sigma_{i=1}^J{p_i^2} $
정보획득은 특정한 특성을 사용해 분할했을 떄 엔트로피의 감소량을 뜻한다.
여기서 불순도(impurity) 라는 개념은 여러 범주가 섞여 있는 정도를 이야기 한다.
예를들어 A, B 두 클래스가 혼합된 데이터가 있을 때 (A, B) 비율이 (45%, 55%)인 샘플(두 범주 수가 비슷)은 불순도가 높은 것이며
(80%, 20%)인 샘플이 있다면 상대적으로 위의 상태보다 불순도가 낮은 것 입니다. (순수도(purity)는 높음)
엔트로피
${\displaystyle \mathrm {H} (T)=\operatorname {I} _{E}\left(p_{1},p_{2},...,p_{J}\right)=-\sum _{i=1}^{J}{p_{i}\log _{2}p_{i}}}$
불순도를 측정하는 지표로서, 정보량의 기댓값.
$Entropy(S) = \Sigma_{i=1}^c{p_i * I(x_i)}$
S : 이미 발생한 사건의 모음 , c: 사건의 개수
정보량이란? :: 어떤 사건이 갖고있는 정보의 양을 의미하며, $I(x) = \log_2{1\over{p(x)}}$ 이다.
여기서 $p(x) : 사건이 발생할 확률$
사건 x가 발생할 확률을 x축, 정보량을 y축으로 그래프를 그리면
사건 x가 발생할 확률이 증가할 수록 정보량은 0에 수렴 → 자주 발생하는 사건일수록 정보량이 떨어진다!
순도 100% (한 종류의 class만 있는경우) 의 경우에는 Entropy가 0이고, 두 클래스가 정확히 반반 섞여있을 때 Entropy가 가장 높다.
___
결정트리의 특성
- 학습한 모델을 사람이 해석하기 쉽다.
- 입력 데이터를 정규화 할 필요가 없다.
- 범주형 변수나 데이터의 누락값 (NaN)이 있어도 용인된다.
- 특정 조건에서 과적합을 일으키기 쉽다.
- 비선형 문제에는 적용할 수 있지만, 선형 분리 문제는 잘 풀지 못한다.
- 데이터의 분포가 특정 클래스에 쏠려있으면 잘 풀지 못한다.
- 데이터의 작은 변화에도 결과가 크게 바뀌기 쉽다.
- 예측 성능은 보통이다.
- 배치 학습으로만 학습할 수 있다.
Parameter | Describe |
---|---|
min_samples_split | 노드를 분할하기 위한 최소한의 샘플 데이터수 → 과적합을 제어하는데 사용- |
Default = 2 → 작게 설정할 수록 분할 노드가 많아져 과적합 가능성 증가 | |
min_samples_leaf | 리프노드가 되기 위해 필요한 최소한의 샘플 데이터수- min_samples_split과 함께 과적합 제어 용도 |
- 불균형 데이터의 경우 특정 클래스의 데이터가 극도로 작을 수 있으므로 작게 설정 필요 | |
max_features | 최적의 분할을 위해 고려할 최대 feature 개수- Default = None → 데이터 세트의 모든 피처를 사용 |
- int형으로 지정 →피처 갯수 / float형으로 지정 →비중- sqrt 또는 auto : 전체 피처 중 √(피처개수) 만큼 선정 | |
- log : 전체 피처 중 log2(전체 피처 개수) 만큼 선정 | |
max_depth | 트리의 최대 깊이- default = None→ 완벽하게 클래스 값이 결정될 때 까지 분할또는 데이터 개수가 min_samples_split보다 작아질 때까지 분할 |
-깊이가 깊어지면 과적합될 수 있으므로 적절히 제어 필요 | |
max_leaf_nodes | 리프노드의 최대 개수 |
Decision Tree의 과적합을 줄이기 위한 파라미터 튜닝
(1) max_depth 를 줄여서 트리의 깊이 제한
(2) min_samples_split 를 높여서 데이터가 분할하는데 필요한 샘플 데이터의 수를 높이기
(3) min_samples_leaf 를 높여서 말단 노드가 되는데 필요한 샘플 데이터의 수를 높이기
(4) max_features를 높여서 분할을 하는데 고려하는 feature의 수 제한
min_samples_leaf 를 높여서 말단 노드가 되는데 필요한 샘플 데이터의 수를 높이기
from category_encoders import OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.pipeline import make_pipeline
from sklearn.tree import DecisionTreeClassifier
def pipe_min_sample_leaf_elbow(howmany,X_train,y_train,X_val,y_val):
trainAccuracy=[]
valAccuracy=[]
for leafs in range(1,howmany):
pipe_msl=make_pipeline(
OneHotEncoder(),
SimpleImputer(),
DecisionTreeClassifier(min_samples_leaf=leafs, random_state=2)
)
pipe_msl.fit(X_train,y_train)
trainAccuracy.append(pipe_msl.score(X_train,y_train))
valAccuracy.append(pipe_msl.score(X_val,y_val))
return trainAccuracy, valAccuracy
ta_msl , tv_msl = pipe_min_sample_leaf_elbow(howmany=30 ,
X_train=X_train_oh,
y_train=y_train,
X_val=X_val_oh,
y_val=y_val)
pd.DataFrame({'msl_training': ta_msl, 'msl_val':tv_msl}, index=range(1,30)).plot(figsize=(10,8))
plt.grid()
최종 Leaf의 수가 많아질 수록 점점 train데이터의 정확도가 줄어드는 반면, Validation데이터의 정확도는 증가합니다.
하지만 어느정도 한계가 있는듯, 곡선의 기울기가 약 leafs=15부터는 상승하는 정도가 매우 작아집니다.
max_depth 를 줄여서 트리의 깊이 제한
- 트리의 최대 깊이
- default = None → 완벽하게 클래스 값이 결정될 때 까지 분할 또는 데이터 개수가 min_samples_split보다 작아질 때까지 분할
- 깊이가 깊어지면 과적합될 수 있으므로 적절히 제어 필요
def pipe_max_depth_elbow(howmany,X_train,y_train,X_val,y_val):
trainAccuracy=[]
valAccuracy=[]
for depth in range(1,howmany):
pipe_msl=make_pipeline(
OneHotEncoder(),
SimpleImputer(),
DecisionTreeClassifier(max_depth=depth, random_state=2)
)
pipe_msl.fit(X_train,y_train)
trainAccuracy.append(pipe_msl.score(X_train,y_train))
valAccuracy.append(pipe_msl.score(X_val,y_val))
return trainAccuracy, valAccuracy
ta_mxd , tv_mxd = pipe_max_depth_elbow(howmany=20 ,
X_train=X_train_oh,
y_train=y_train,
X_val=X_val_oh,
y_val=y_val)
pd.DataFrame({'mxd_training': ta_mxd, 'mxd_val':tv_mxd}, index=range(1,20)).plot(figsize=(10,8))
plt.grid()
max_depth 는 트리의 최대 깊이를 나타냅니다.
default 는 None으로 완벽하게 클래스 값이 결정될때까지 or min_samples_split보다 작아질 때 까지 분할합니다.
깊이가 깊어질 수록 과적합될 가능성이 높아집니다. training데이터의 경우 깊이가 깊어질수록 정확도가 한없이 증가하는 모습을 보입니다.
하지만 validation데이터의 경우 깊이가 깊어질 수록 정확도가 떨어지는 모습을 보입니다.
해당 depth에 맞게 training데이터가 학습(fit)된 이후, validation데이터가 해당 트리를 통해 걸러져서 분류된 결과가 depth가 깊어짐에 따라 점점 안맞게 된다는 뜻으로 해석됩니다.
min_samples_split 를 높여서 데이터가 분할하는데 필요한 샘플 데이터의 수를 높이기
def pipe_min_sample_split_elbow(howmany,X_train,y_train,X_val,y_val):
trainAccuracy=[]
valAccuracy=[]
for splits in range(1,howmany):
pipe_msl=make_pipeline(
OneHotEncoder(),
SimpleImputer(),
DecisionTreeClassifier(min_samples_split=splits, random_state=2)
)
pipe_msl.fit(X_train,y_train)
trainAccuracy.append(pipe_msl.score(X_train,y_train))
valAccuracy.append(pipe_msl.score(X_val,y_val))
return trainAccuracy, valAccuracy
ta_mss , tv_mss = pipe_min_sample_leaf_elbow(howmany=20 ,
X_train=X_train_oh,
y_train=y_train,
X_val=X_val_oh,
y_val=y_val)
pd.DataFrame({'mss_training': ta_mss, 'mss_val':tv_mss}, index=range(1,20)).plot(figsize=(10,8))
plt.grid()
min_samples_split : int or float, default=2
The minimum number of samples required to split an internal node: 규칙노드(내부노드)의 분기를 위한 최소 샘플 개수를 지정하는 방식입니다.
기존의 디폴트값은 2로, 어떤 하나와 다른 하나가 나타나는 순간 그 지점에서 분기를 하는반면, 값이 커질수록 다른 n개의 sample이 나타나야 분기를 하는것으로, 분기하는 정도가 조금 더 약해진다고 볼 수 있습니다.
GridSearchCV를 통한 튜닝
from sklearn.model_selection import GridSearchCV
params = {
'max_depth' : [4, 5, 6, 7, 8, 9, 10],
'min_samples_split' : range(10,20),
'min_samples_leaf' : range(5,15)
}
gridCV = GridSearchCV(DecisionTreeClassifier(random_state=2),
param_grid=params, scoring='accuracy', cv=5, verbose=1)
## OneHotEncoder() 미리 사용
ohe=OneHotEncoder()
X_train_oh = ohe.fit_transfort(X_train)
X_val_oh = ohe.transform(X_val)
X_test_oh = ohe.transform(X_test)
## SimpleImuter() 를 미리 사용
imp_mean=SimpleImputer()
X_train_oh_imputed = imp_mean.fit_transform(X_train_oh)
X_val_oh_imputed = imp_mean.transform(X_val_oh)
X_test_oh_imputed = imp_mean.transform(X_test_oh)
## GridSearchCV사용
gridCV.fit(X_train_oh_imputed , y_train)
print(gridCV.best_score_)
gridCV.best_params_
# 0.8244818005868766
# {'max_depth': 7, 'min_samples_leaf': 14, 'min_samples_split': 10}
GridSearchCV를 사용해서 params의 조합을 통하여 엄청난 연산을 수행 끝에 거의 5분~10분만에 끝났습니다.
최고 스코어는 : 0.8244818005868766
그 때의 조합 : {'max_depth': 7, 'min_samples_leaf': 14, 'min_samples_split': 10}
이 조합이 기존에 Pipeline으로 하던 것에서 최상의 조합이라고 볼 수 있습니다.
(지금까지는.. 아마 나중에 뭔가 다른방법을 알게된다면 시도해보겠습니다.)
from sklearn import tree
gridbest = gridCV.best_estimator_
plt.figure(figsize=(12,12), dpi=200)
tree.plot_tree(gridbest,max_depth=3);
각 columns의 중요도 시각화
doctor_recc_h1n1
의사의 권유가 있었다는 feature가 중요성이 높았고, 그 다음 건강보험 유무였는데, 이 두 변수 사이에 작은 상관관계가 있을것으로 추정됩니다. 그 이유는 보험있는사람들이 병원에 좀더 자유롭게 (가격부담없이) 진료받으러 갔을것이며, 또한 보험이 있기에 백신접종 또한 권유를 더 잘받지않았을까? 또한, 세번째 피쳐가 백신이 효과적이라고 생각하는것 네번째가 의료종사자, 다섯번째는 백신위험이 적다는의견입니다.
plt.figure(figsize=(6,10))
pd.Series(gridbest.feature_importances_, X_train_oh.columns).sort_values().tail(15).plot.barh()
참조:
머신러닝 실무 프로젝트 / 아리아 미치아키 / 한빛미디어
[Chapter 4. 분류] Decision Tree Classifier :: 데이터분석, 머신러닝 정리 노트 (tistory.com)
참조2
'Machine Learning > Tree Based Models' 카테고리의 다른 글
Random Forests 랜덤포레스트 (0) | 2021.02.10 |
---|