결정트리는 특성(feature)과 타겟(Target)의 관계를 특성에 대한 질문에 따라 데이터를 나눕니다. 이것은, 선형 회귀나 로지스틱 회귀처럼 특성(feature)을 가중치(weight)와 곱하는 방식과 다릅니다. 예를들어, 결정트리가 온도 특성을 가지고 데이터를 두 그룹으로 나눈다면, 온도가 20도 이상인 그룹과 이하인 그룹을 나누고, 이어서 각 그룹을 여름인 그룹과 아닌 그룹으로 계절을 기반으로 나눌 수 있습니다. 이렇게 일정 수준의 정확도에 도달할때까지 데이터를 새로운 그룹으로 나누는 과정을 반복합니다.
결정트리에서 응용된 기법으로 랜덤 포레스트(Random Forest)와 경사 부스팅 결정트리 (Gradient Boosted Decision Tree)가 있습니다.
결정트리는 부스팅(Boosting)했을때에 우수한 성능을 제공하기 때문에 XGBoost의 기본 학습기(base learner)로 선호됩니다. XGBoost는 앙상블 모델을 만드는데 사용하는 머신러닝 모델로서, 오류로부터 학습하기 위하여 기본학습기를 사용합니다.
아래와 Max Depth가 1인 결정트리를 그릴 수 있습니다.
plt.figure(figsize=(10,7))
plot_tree(dt, max_depth=1, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()
아래는 Max Depth가 1인 결정트리입니다.
결정트리(Decision Tree)의 특징은 아래와 같습니다.
- 결과를 해석하고 이해하기 쉽습니다.
- 분류/회귀에 모두 사용 가능합니다.
- 입력 데이터를 정규화할 필요가 없어서, Data preprocessing(Scailing)을 거의 하지 않습니다.
- Outliner에 민감하지 않습니다.
- 연속형 변수(Numinical feature), 범주형 변수(Category)에 모두 적용 가능합니다.
- 대규모의 데이터 셋에서도 잘 동작합니다.
- 결정트리의 결정경계는 영역을 반복하여 나눠가는 과정에서 정해지기 때문에 직선형태를 취하지 않습니다.
- 데이터의 특성이 특정 변수에 수직/수평적으로 구분되지 못할 경우 분류률이 떨어지고 트리가 복잡해집니다. 즉, Tree의 depth가 깊어질수록 느려집니다.
- 트리가 깊어질수록 학습에 사용되는 데이터 수가 적어져, 과적합(Overfitting)이 발생하기 쉬워지므로, 규제(Regularization)이 필요합니다. 대표적인 가지치기 방법은 "Pruning(가지치기)"으로 max_depth를 이용합니다.
- 비선형 문제에도 적용할 수 있지만, 선형 분리 문제는 잘 풀지 못합니다.
- 데이터 분포가 특정 클래스에 쏠려 있으면 잘 풀지 못합니다.
- 데이터의 작은 변화에도 결과가 크게 바뀌기 쉽습니다.
- 예측성능은 보통입니다.
- 배치 학습으로만 학습할 수 있습니다.
결정트리에서 노드를 분할하는 기준(Criterion)으로 불순도(Impurity)를 사용합니다. 이것은 노드 내 데이터의 불순도를 최소화하는 방향으로 분할함을 의미합니다. 불순도는 한 범주 안에 서로 다른 데이터가 얼마나 섞여 있는지를 나타내는 정도입니다. 한 범주에 한 종류만 있다면 불순도가 최소(혹은 순도가 최대)고, 서로 다른 데이터가 같은 비율로 있다면 불순도가 최대(혹은 순도가 최소)입니다.
결정트리는 학습 데이터로부터 조건식을 만들고, 예측할 때는 트리의 루트부터 순서대로 조건 분기를 타면서 Leaf에 도달하면 예측결과를 내는 알고리즘입니다. 이때, 불순도(Imputiry)를 기준으로 가능한 같은 클래스끼리 모이도록 조건 분기를 학습합니다.
분류에서는 Gini Impurity와 Entropy Imputiry를 이용합니다.
지니 불순도(Gini Impurity)은 정답이 아닌 값이 나올 확율을 의미 합니다. 지니 불순도 값이 클수록 불순도는 높고 작을수록 불순도는 낮습니다. 엔트로피와 마찬가지로 지니 불순도가 낮아지는 방향으로 노드를 분할 합니다. Scikit-learn에서는 기본값으로 Gini impurity을 사용합니다.
Entropy Imputiry는 정보의 불확실성 또는 무질서도를 의미합니다. 엔트로피 값이 클수록 불순도가 높고, 작을수록 불순도가 낮습니다. 1에서 엔트로피를 뺀 수치(1-엔트로피)를 정보이득(Information Gain)이라 하는데, 결정트리는 정보 이득을 최대화하는 방향(엔트로피를 최소화)으로 분할합니다.
노드를 분할하기 위한 특성을 선택하는 방법에 Information Gain이 있습니다.
정보이득(Information Gain)은 부모노드가 가진 정보량에서 자식노드들의 정보량을 뺀 차이입니다.부모노드와 자식노드의 정보량의 차이가 없을때, 트리는 분기 split을 멈추게 됩니다.
결정트리는 모든 Train dataset이 정확히 매핑될때까지 그룹을 만들수도 있으몰 100% 정확도를 달성(과대적합) 할 수 있습니다. 하지만 이런 모델은 새로운 데이터에 일반화되기 어렵습니다. 과대적합을 막기 위해 아래 방법을 사용할 수 있습니다.
-
Hyperparameter tunning 입니다.
-
많은 트리의 예측을 모으는 방법입니다. Random Forest와 XGBoost가 사용하는 방법입니다.
‘min’값을 높이거나 ’max’값을 줄이면 모델에 규제가 커져 이용하여 과대적합(overfit)을 막을 수 있습니다.
- max_depth: 트리의 최대 깊이를 의미합니다. (루트 노드 깊이=0)
- min_samples_leaf: 리프노드가 가지고 있어야 할 샘플 개수를 제한합니다. 기본값인 1을 선택하면 리프 노드는 하나의 샘플로 구성될 수 있습니다. (괴대적합이 되기 쉬움)
- max_leaf_nodes: 리프노드의 전체 개수를 지정합니다. 10을 지정하면 리프 노드가 최대 10을 넘을 수 없습니다.
- max_features: 각 노드에서 분할에 사용할 특성의 최대 수를 의미합니다. 분산을 줄이는 데 효과적인 매개변수로서 매번 지정된 개수의 특성중에서 선택합니다. None/auto는 전체를 사용하고, sqrt는 전체 특성 개숫의 제곱근을 사용하며, log2는 전체 특성 개숫의 로그를 사용하는데 32개라면 5개의 특성만을 사용하게 됩니다.
- min_samples_split: 분할되기 전에 노드가 가져야 하는 최소 샘플 수를 제한합니다. 기본값은 2이입니다.
- splitter: 노드를 분할하기 위한 특성 선택방법으로 'random'과 'best'를 선택합니다. 기본값은 'best'로 정보이득(information gain)이 가장 큰 특성을 선택합니다. splitter를 'random'으로 하면 괴대적합을 막고 다양한 트리를 만들 수 있습니다.
- criterion: 결정트리의 회귀와 분류 모델은 다른 criterion을 가지는데 분할 품질을 측정하는 방법을 제공합니다. 회귀 모델의 경우에 'squared_error(평균제곱오차)' (기본값), 'friedman_mse', 'absolute_error(평균 절대값 오차)', 'poisson(포아송 편차가 있습니다. 분류 모델은 'gini'(기본값)와 'entropy'가 있습니다.
- min_impurity_decrease: 분할하기 위한 최소 불순도를 지정합니다. scikit-learn 0.23 버전에서 삭제되었습니다.
- min_weight_fraction_leaf: 리프 노드가 되기 위한 전체 가중치의 최소 비율입니다. sample_weight를 지정하지 않으면 모두 동일한 가중치를 가집니다. min_samples_leaf과 같지만 전체 샘플에서 클래스 별 샘플 수 비율을 고려할 수 있습니다. 기본값은 0.0입니다. 0.01로 지정하고 500개의 샘플이 있다면, 리프 노드가 되기 위한 최소 샘플 갯수는 5개로 지정됩니다.
- ccp_alpha는 트리를 만든후 가지치기(pruning)하는 기능입니다.
DecisionTreeRegressor의 기본 Hyperparameter는 아래와 같습니다.
from sklearn.tree import DecisionTreeRegressor
dt = DecisionTreeRegressor(random_state=2)
params = dt.get_params(deep=True)
print(params)
이때의 결과입니다.
{'ccp_alpha': 0.0, 'criterion': 'mse', 'max_depth': None, '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,
'random_state': 2, 'splitter': 'best'}
DecisionTreeClassifier의 기본 Hyperparameter는 아래와 같습니다.
from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier(random_state=2)
params = clf.get_params(deep=True)
print(params)
이때의 결과는 아래와 같습니다.
{'ccp_alpha': 0.0, 'class_weight': None, 'criterion': 'gini', 'max_depth': None, '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,
'random_state': 2, 'splitter': 'best'}
decision_tree.ipynb에서는 결정트리에 대해 예를 보여주고 있는데 아래에서 상세하게 설명합니다.
- 데이터를 준비합니다.
데이터를 읽어옵니다. 여기에는 "alcohol", "sugar", "pH"라는 3개의 column이 있습니다.
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
wine.head()
Train / Test Set을 만들고 정규화를 합니다.
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(
data, target, test_size=0.2, random_state=42)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
- Decision Tree를 아래와 같이 구합니다.
scikit-learn의 DecisionTreeClassifier로 score()를 구하면 정확도 (Accuracy)를 아래처럼 구할 수 있습니다.
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))
0.996921300750433
0.8592307692307692
이 값은 Logistric Regression 결과보다는 좋지만 과대적합(Overfit)인 결과를 얻습니다.
이때의 트리구조를 sciket-learn의 plot_tree로 그리면, 아래와 같습니다.
- 가지치기 (Pruning)
가지치기를 위해 max_depth를 3으로 설정했을때 아래와 같습니다.
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))
0.8454877814123533
0.8415384615384616
이것을 plot_tree로 그리면 아래와 같습니다.
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()
이때, 아래 그림과 같이 5197개의 sample중에 1141개 sample이 레드 와인으로 분류됩니다.
아래는 정규화를 하지 않은 경우인데, 거의 동일한 결과를 얻고 있습니다.
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_input, train_target)
print(dt.score(train_input, train_target))
print(dt.score(test_input, test_target))
0.8454877814123533
0.8415384615384616
결정 트리 모델의 특성중요도(feature importances) 속성으로 특성 중요도를 알수 있습니다. 아래와 같이 "alcohol", "sugar", "pH"의 중요도는 0.15210271, 0.70481604, 0.14308125이고, 이것의 합은 1입니다. 즉, "sugar"가 가장 중요한 특성으로 불순도에 줄이는데 가장 큰 역할하고 있으므로, 직관적으로 문제를 이해하는데 도움이 됩니다.
print(dt.feature_importances_)
[0.15210271 0.70481604 0.14308125]