결정 트리(Decision Tree)와 앙상블(Ensemble)에 대한 이론이 필요하신 분들은 아래 링크를 참조해주시기 바랍니다.
[인공지능][개념] 분류(Classification) - 결정 트리(Decisioin Tree)와 가지치기(Pruning) : https://itstory1592.tistory.com/12
[인공지능][실습] 결정 트리(Decision Tree) 모델로 와인(Wine) 데이터셋을 분류하고 교차 검증(Cross Validation)과 그리드 서치(Grid Search)로 최적의 하이퍼 파라미터를 찾아보자! : https://itstory1592.tistory.com/13
[인공지능][개념&실습] 트리의 앙상블(Ensemble)[1] - 랜덤 포레스트(Random Forest) : https://itstory1592.tistory.com/15
엑스트라 트리(Extra Tree)
이전 글의 랜덤 포레스트(Random Forest)에 대해 이해했다면, 엑스트라 트리(Extra Trees)를 쉽게 이해할 수 있을 것이다.
엑스트라 트리(Extra Trees)는 랜덤 포레스트와 매우 비슷하게 동작하는데, 기본적으로 100개의 결정 트리를 훈련시키며,
전체 특성 중에 일부 특성을 랜덤하게 선택하여 노드를 분할하기 위해 사용한다.
랜덤 포레스트와 엑스트라 트리의 차이점은 부트스트랩 샘플(중복된 훈련 샘플)을 사용하지 않는다는 점이다.
즉, 각 결정 트리를 만들어낼 때 전체 훈련 세트를 사용한다는 것인데, 이는 분할할 때 가장 좋은 분할을 찾는 것이 아닌,
무작위(Random)로 분할한다는 뜻이다.
또한, 심지어 데이터 샘플 개수와 특성을 설정하는 것까지 무작위성을 주입한다.
그래프를 통해 확인해보면...
랜덤 포레스트에서 보다 무작위성이 더 주입된 엑스트라 트리에서의 특성 중요도가
전반적으로 조금씩 더 상승했음을 확인해볼 수 있다.
이는 특성까지도 무작위로 선택하여 트리를 형성하기 때문에 발생한 결과이다.
결정 트리에서 특성을 무작위로 분할하게 되면 성능은 낮아진다는 단점이 있지만,
많은 트리를 앙상블하기 때문에 과대적합을 막고 검증 세트의 점수를 높이는 효과가 있다.
일반적으로 엑스트라 트리는 랜덤 포레스트보다 무작위성이 조금 더 크기 때문에, 더 많은 결정 트리를 훈련해야 한다.
하지만 무작위로 분할하기 때문에 속도가 빠르다는 장점이 있다.
그럼 이제, 코드로 엑스트라 트리를 직접 구현해보자! ✌
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_validate
wine = pd.read_csv('https://bit.ly/wine_csv_data')
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)
모델을 훈련시키기 위해, 와인 데이터셋을 불러와 전체 데이터를 특징(feature)과 타겟(target)으로 따로 저장해주고,
훈련 데이터셋과 테스트 데이터셋으로 나누어준다.
from sklearn.ensemble import ExtraTreesClassifier
et = ExtraTreesClassifier(n_estimators=1000, n_jobs=-1, random_state=42)
scores = cross_validate(et, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
사이킷런(sklearn)에서 제공하는 ExtraTreesClassifier 클래스를 임포트해주고,
결정 트리 1000개를 모든 CPU 코어를 사용하여 훈련시키기 위해, n_jobs를 -1로 설정해준다.
그런 다음, 해당 모델을 교차 검증(Cross Validation)으로 훈련시켰을 때의 점수를 가져오고
훈련 점수와 테스트 점수를 출력해보자.
결과로 랜덤 포레스트와 비슷한 점수를 확인해볼 수 있다.
해당 예제는 특성이 많지 않기 때문에 두 모델의 차이가 크지 않다.
일반적으로 엑스트라 트리가 무작위성이 더 크기 때문에 랜덤 포레스트보다 더 많은 결정 트리를 훈련시킬 필요가 있지만, 위에서 설명한 것처럼 노드를 랜덤하게 분할하기 때문에 계산 속도가 빠르다는 장점이 있다.
그레이디언트 부스팅(Gradient Boosting)
그레이디언트 부스팅(Gradient Boosting)은 얕은 깊이의 트리를 사용하여 이전 트리의 오차를 보완하는 방식의 앙상블이다.
깊이가 얕은 결정 트리를 사용하기 때문에, 과대적합에 강하며 높은 일반화 성능을 기대할 수 있다.
그레이디언트란 이름처럼 경사 하강법을 사용하여 앙상블에 트리를 추가하는 방식이며,
모델의 가중치와 절편을 조금씩 바꿔가며, 결정 트리를 계속 추가는 방식으로 오차를 줄여간다.
분류에서는 로지스틱 손실 함수를, 회귀에서는 평균 제곱 오차(MSE) 함수를 사용한다.
일반적으로, 그레이디언트 부스팅이 랜덤 포레스트보다 조금 더 높은 성능을 기대할 수 있지만,
오차를 줄여가는 방식으로 트리를 순서대로 추가하기 때문에, 랜덤으로 노드를 분할하는 랜덤 포레스트와 달리 훈련 속도가 느리다.
코드를 통해 알아보자!
from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(random_state=42)
scores = cross_validate(gb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
GradientBoostingClassifier 클래스를 임포트해주고 교차검증을 통해 점수를 얻어 출력해보면 위와 같은 결과를 얻을 수 있다.
결과를 확인해보면 거의 과대 적합되지 않았음을 확인해볼 수 있다.
gb = GradientBoostingClassifier(n_estimators=500, learning_rate=0.2, random_state=42)
scores = cross_validate(gb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
그레이디언트 부스팅은 결정 트리의 개수를 늘려도 과대 적합에 매우 강하므로 학습률과 트리의 개수를 늘리면
더 높은 성능의 모델을 얻을 수도 있다.
(기본값은 깊이가 3인 결정 트리 100개를 사용한다.)
학습률을 0.2로 설정하고 결정 트리의 개수를 500개로 늘렸음에도 과대 적합을 잘 억제하는 모습을 확인할 수 있다.
gb.fit(train_input, train_target)
print(gb.feature_importances_)
그레이디언트 부스팅도 특성 중요도를 제공하는데, 결과에서처럼 그레이디언트 부스팅에서는 랜덤 포레스트보다 sugar(당도)에 더 집중하고 있다.
추가적으로, 그레이디언트 부스팅에서는 훈련에 사용할 훈련 세트 비율을 정하는 subsample이라는 매개변수가 있는데,
기본값은 1.0으로 훈련 세트를 모두 사용하지만 설정을 통해 일부 샘플을 랜덤하게 선택하는
확률적 경사 하강법이나 미니배치 경사 하강법과 비슷하게 작동시킬 수도 있다.
다음으로는 그레이디언트 부스팅의 속도와 성능을 더욱 개선시킨 히스토그램 기반 그레이디언트 부스팅을 살펴보자.
히스토그램 기반 그레이디언트 부스팅(Histogram-based Gradient Boosting)
히스토그램 기반 그레이디언트 부스팅은 정형 데이터를 다루는 머신러닝 알고리즘 중에 가장 높은 인기를 가지고 있는 알고리즘이다.
히스토그램 기반 그레이디언트 부스팅은 입력 특성을 256개의 구간을 나누기 때문에,
노드를 분할할 때 최적의 분할을 매우 빠르게 찾을 수 있다는 장점이 있다.
히스토그램 기반 그레이디언트 부스팅은 256개의 구간 중에서 하나를 떼어 놓고 누락된 값을 위해서 사용한다.
따라서 입력에 누락된 특성이 있더라도 따로 전처리할 필요가 없다.
이 모델도 사이킷런에서 클래스로 제공해주고 있는데, 아직 테스트 과정에 있는 상태이다.
from sklearn.experimental import enable_hist_gradient_boosting
from sklearn.ensemble import HistGradientBoostingClassifier
hgb = HistGradientBoostingClassifier(random_state=42)
scores = cross_validate(hgb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
이 클래스를 사용하기 위해서는 사이킷런(sklearn)에 있는 enable_hist_gradient_boosting이라는 모듈을 임포트 해주어야 한다.
결과적으로 수치를 보면 과대 적합을 잘 억제하면서 그레이디언트 부스팅보다 높은 효과를 보이고 있다.
from sklearn.inspection import permutation_importance
hgb.fit(train_input, train_target)
result = permutation_importance(hgb, train_input, train_target, n_repeats=10,
random_state=42, n_jobs=-1)
print(result.importances_mean)
히스토그램 기반 그레이디언트 부스팅에서 특성 중요도를 계산하기 위해서 permutation_importance() 메소드를 사용하겠다.
이 함수는 특성을 하나씩 랜덤하게 섞어서 모델의 성능이 변화하는지를 관찰하고, 어떤 특성이 중요한지 계산한다.
매개변수 n_repeats는 랜덤하게 섞을 횟수를 의미하는데, 기본값은 5이지만 여기서는 10으로 설정해보자.
그러고 나서 importances_mean에 접근하면 특성 중요도의 평균을 얻을 수 있다.
다음은 테스트 데이터셋에서의 점수를 확인해보자.
result = permutation_importance(hgb, test_input, test_target, n_repeats=10,
random_state=42, n_jobs=-1)
print(result.importances_mean)
결과를 보면 그레이디언트 부스팅과 비슷하게 당도에 조금 더 집중하고 있는데,
이런 분석을 통해 모델이 실전에서 어떤 특성에 관심을 둘지 예상해볼 수 있다.
사이킷런(sklearn)에서 제공하는 히스토그램 기반 그레이디언트 부스팅 클래스 외에도 XGBoost, LightGBM 등 다양한 라이브러리가 존재한다.
간단히 코드와 결과를 확인해보며 글을 마무리하겠다.
XGBoost
from xgboost import XGBClassifier
xgb = XGBClassifier(tree_method='hist', random_state=42)
scores = cross_validate(xgb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
XGBClassifier는 사이킷런에서 제공하는 클래스 아님에도 불구하고 cross_validate() 메소드와 함께 사용할 수 있다.
XGBoost는 다양한 부스팅 알고리즘을 지원하는데,
그중에서도 tree_method를 'hist'로 지정하면 히스토그램 기반 그레이디언트 부스팅을 사용할 수 있다.
훈련 점수와 테스트 점수를 모두 출력해보면, 훌륭한 점수와 동시에 과대 적합을 낮추고 있음을 알 수 있다.
LightGBM
from lightgbm import LGBMClassifier
lgb = LGBMClassifier(random_state=42)
scores = cross_validate(lgb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
lightbgm은 마이크로소프트에서 만든 라이브러리로, 사이킷런의 히스토그램 기반 그레이디언트 부스팅에 큰 영향을 주었다.
LightGBM 또한 사이킷런에서 제공하는 cross_validate() 메소드와 함께 사용할 수 있다.
결과를 확인해보면, XGBoost를 사용했을 때보다는 약간의 과대 적합한 경향이 있지만 다른 라이브러리에 비해 잘 억제하고 있음을 알 수 있다.
전체 소스 코드 :
(이해가 다소 힘들거나, 틀린 부분이 있다면 댓글 부탁드리겠습니다! 😊)
💖댓글과 공감은 큰 힘이 됩니다!💖