My ExtraTree(Mnist version)

3 minute read

Machine Learning

문제 1 : 엑스트라 트리 직접 구현

먼저 엑스트라 트리에 대해 설명하자면 엑스트라 트리는 랜덤 포레스트와 같이 결정트리 모델을 이용한 배깅 학습을 하는 앙상블 학습 모델이다.

엑스트라 트리의 핵심은 결정트리 모델이 노드를 분리 할 때 사용할 특성을 임의로 선택할 뿐만 아니라 그 임계값도 모두 해보는 것이 아니라 일부를 무작위로 선택하여 학습 하는 모델을 말한다.

  • 훈련에 사용할 특성 무작위 선택 후 그 중 최적 선택
  • 임계값 무작위 선택 후 그 중 최적 선택

기본 설정

# 파이썬 ≥3.5 필수 (파이썬 3.7 추천)
import sys
assert sys.version_info >= (3, 5) 

# 사이킷런 ≥0.20 필수
import sklearn
assert sklearn.__version__ >= "0.20"

# 공통 모듈 임포트
import numpy as np
import os

# 노트북 실행 결과를 동일하게 유지하기 위해
np.random.seed(42)

1단계 : 결정 트리 모델 훈련

일단 사이킷런에서 지원하는 mnist 데이터를 가져오겠습니다

from sklearn.datasets import fetch_openml

mnist = fetch_openml('mnist_784', version=1)
mnist.target = mnist.target.astype(np.uint8)

특성 데이터와 레이블을 훈련 세트와 테스트 세트로 나누었습니다. (테스트 셋트 10000개 나머지 훈련세트 60000개)

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    mnist.data, mnist.target, test_size=10000, random_state=42)

DecisionTreeClassifier 클래스는 결정트리 알고리즘을 활용한 분류 모델을 지원한다.

여기서 DecisionTreeClassifer 클래스의 splitter=”random”으로 해줌으로써 결정트리를 훈련 할 때 특성과 임곗값을 모두 사용하지 않고, 무작위로 선택하여 그 중 최적을 선택하도록 합니다.

max_features=”auto” 는 고려할 최대 특성 수를 말하는데 auto는 보통 최대 특성 수의 제곱근만큼 특성이 선택 될 수 있습니다.

  • splitter=”random”과 max_features=”auto”을 통해서 특성과 임곗값을 임의로 선택하는 엑스트라 트리의 핵심을 구현하였습니다.
from sklearn.tree import DecisionTreeClassifier    # 사이킷런에서 지원하는 결정트리 분류기 입니다.

from sklearn.model_selection import GridSearchCV


params = {'max_leaf_nodes': list(range(2, 100)), 'min_samples_split': [2, 3, 4]}
grid_search_cv = GridSearchCV(DecisionTreeClassifier(splitter="random", max_features=3, random_state=42 ), params, verbose=1, cv=3)

grid_search_cv.fit(X_train, y_train)
Fitting 3 folds for each of 294 candidates, totalling 882 fits


[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done 882 out of 882 | elapsed:  3.6min finished





GridSearchCV(cv=3, error_score=nan,
             estimator=DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None,
                                              criterion='gini', max_depth=None,
                                              max_features=3,
                                              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='deprecated',
                                              random_state=42,
                                              splitter='random'),
             iid='deprecated', n_jobs=None,
             param_grid={'max_leaf_nodes': [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
                                            13, 14, 15, 16, 17, 18, 19, 20, 21,
                                            22, 23, 24, 25, 26, 27, 28, 29, 30,
                                            31, ...],
                         'min_samples_split': [2, 3, 4]},
             pre_dispatch='2*n_jobs', refit=True, return_train_score=False,
             scoring=None, verbose=1)

교차검증을 이용한 그리드 탐색을 이용하여 DecisionTreeClassifier 모델의 최적의 하이퍼파라미터 값을 찾습니다.

  • max_leaf_nodes : 최종 분류되는 잎(리프) 노드의 갯수를 의미합니다.
  • min_samples_split : 노드를 분할하기 위해 필요한 최소 샘플 수

여기서는 max_leaf_nodes 에 큰 중점을(많은 경우를 탐색) 두고 그리드 탐색을 하여 최적의 파라미터 값을 구하도록 합니다.

grid_search_cv.best_estimator_
DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='gini',
                       max_depth=None, max_features=3, max_leaf_nodes=99,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort='deprecated',
                       random_state=42, splitter='random')

그렇게 찾은 최적의 하이퍼파라미터 값을 찾아보니

  • max_leaf_nodes = 99
  • min_samples_split = 2 를 확인할 수 있었습니다.

GridSearchCV은 자연스럽게 훈련세트에 대해 찾은 최적의 하이퍼파라미터로 훈련을 시킵니다. (기본값으로 refit=True 로 설정되기 때문입니다.) 따라서 바로 훈련된 결정트리 모델에 대하여 테스트 세트에 대한 성능 평가를 해보겠습니다.

from sklearn.metrics import accuracy_score

y_pred = grid_search_cv.predict(X_test)
accuracy_score(y_test, y_pred)
0.4495

성능이 약 44.95%로 확인되었습니다. 성능이 매우 안 좋은 것을 알 수 있습니다.

dec = DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='gini',
                       max_depth=None, max_features='auto', max_leaf_nodes=5,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort='deprecated',
                       random_state=42, splitter='random')

2단계 : 엑스트라 트리 구현

이번에는 위에서 구한 결정트리를 이용하여 엑스트라 트리 모델을 구현하여 보겠습니다.

먼저 데이타 세트의 부분집합을 1000개 만듭니다. 각각의 부분집합은 무작위로 100개의 훈련 샘플들을 선택하여 가지고 있습니다. (중복 허용)

  • 여기서 사이킷런의 ShuffleSplit을 이용하여 진행하였습니다.
from sklearn.model_selection import ShuffleSplit

n_trees = 1000
n_instances = 100

mini_sets = []

rs = ShuffleSplit(n_splits=n_trees, test_size=len(X_train) - n_instances, random_state=42)
for mini_train_index, mini_test_index in rs.split(X_train):
    X_mini_train = X_train[mini_train_index]
    y_mini_train = y_train[mini_train_index]
    mini_sets.append((X_mini_train, y_mini_train))

각각의 만들어진 훈련 부분 집합 샘플세트들을 위에서 찾은 최적의 하이퍼파라미터의 결정트리 모델에 훈련시킵니다. 그리고 그 1000개의 훈련된 결정트리 모델을 테스트 세트로 성능을 평가합니다.

from sklearn.base import clone

forest = [clone(grid_search_cv.best_estimator_) for _ in range(n_trees)]

accuracy_scores = []

for tree, (X_mini_train, y_mini_train) in zip(forest, mini_sets):
    tree.fit(X_mini_train, y_mini_train)
    
    y_pred = tree.predict(X_test)
    accuracy_scores.append(accuracy_score(y_test, y_pred))

np.mean(accuracy_scores)
0.3250305

1단계에서는 전체 훈련세트를 이용하였지만, 여기서는 각각의 모델이 더 작은 훈련세트를 이용하였기 때문에 성능이 1단계에서 찾은 모델에 비해 안 좋아 보입니다. ( 약 32%를 보입니다. - 이 수치는 1000개의 결정트리 모델의 성능 수치에 대한 평균입니다.)

자 이제 엑스트라 트리의 최종 예측을 구현하겠습니다.

  • 1000개의 훈련된 분류기가 테스트 세트의 각각의 샘플에 대해 예측값을 예측합니다.
  • 그리고 최종적으로 각각의 테스트 샘플에 대한 1000개의 예측값 중 최빈값(여기서는 클래스를 2개로 분류하므로 과반수)을 최종적으로 샘플에 대한 랜덤 포레스트 모델의 예측값으로 합니다.
Y_pred = np.empty([n_trees, len(X_test)], dtype=np.uint8)

for tree_index, tree in enumerate(forest):
    Y_pred[tree_index] = tree.predict(X_test)

SciPy의 mode()를 이용해서 과반수 값을 최종 예측값으로 합니다.

from scipy.stats import mode

y_pred_majority_votes, n_votes = mode(Y_pred, axis=0)

자 이제 랜덤 포레스트 모델의 테스트 세트에 대한 성능을 평가하여 보겠습니다.

accuracy_score(y_test, y_pred_majority_votes.reshape([-1]))
0.7341

약 73.4%로 1단계 결정트리에 비해서 거의 30%가량 더 높은 성능, 2단계 개별 분류기에 비해서는 거의 2배 이상의 성능을 보입니다. 즉, 앙상블 학습인 엑스트라 트리 모델 훈련을 통해 단일의 결정 트리 모델의 성능보다 더 좋아졌다는 것이 확인이 되었습니다. 앙상블 학습의 효과 즉, 성능이 낮은 모델들로 성능이 높은 모델을 훈련시키는 효과를 확인 할 수 있었습니다.

Updated: