[혼자공부하는 머신러닝 + 딥러닝] 12_트리의 앙상블
11. 트리의 앙상블
- 지금까지 우리는 k-최근접이웃, 선형회귀, 릿지, 라쏘, 다항회귀, 로지스틱 회귀를 배웠다. 그리고 확률적 경사 하강법 알고리즘을 사용한 분류기와 결정 트리 모델, 그리고 테스트 세트 사용 없이 모델의 성능을 평가하는 교차 검증과 하이퍼파라미터 튜닝까지 익혔다.
- 이번시간에는 먼저 랜덤 포레스트를 배워본다. 그전에 정형 데이터와 비정형 데이터에 대해 알아보자
- 어떠한 구조로 되어있는 정형 데이터는 csv파일이나 데이터베이스 혹은 엑셀에 저장하기 쉽도록 되어있다. 온라인 쇼핑몰에 진열된 상품과 구매한 쇼핑 정보 등이 그 예시다. 대다수의 프로그래머가 다루는 데이터는 정형데이터에 속한다.
- 반대로 비정형 데이터는 우리주위의 텍스트 데이터와 디지털 카메라로 찍은 이미지, 음악 등이 비정형 데이터에 해당한다.
- 지금까지 배운 머신러닝 알고리즘들은 정형 데이터에 잘 맞는다. 그중 정형 데이터를 다루는데 가장 뛰어난 성과를 내는 알고리즘이 앙상블 학습이다. 앙상블 학습은 대부분 결정 트리를 기반으로 만들어져 있다.
1. 랜덤 포레스트
- 랜덤 포레스트는 앙상블 학습의 대표 주자 중 하나로 안정적인 성능 덕분에 널리 사용되고 있다. 랜덤포레스트는 결정 트리를 랜덤하게 만들어 결정 트리의 숲을 만든다. 그리고 각 결정 트리의 예측을 사용해 최종 예측을 만든다.
- 랜덤 포레스트는 각 트리를 훈련하기 위한 데이터를 랜덤하게 만든다. 데이터를 만들 때 우리가 입력한 훈련 데이터에서 랜덤하게 샘플을 추출하여 훈련 데이터를 만든다. 이 때 한 샘플이 중복되어 추출되는 경우도 있다. 이렇게 만들어진 샘플을 부트스트랩 샘플이라 부른다. 기본적으로 부트스트랩 샘플은 훈련 세트의 크기와 같게 만든다.
- 또한 각 노드를 분할할 때 전체 특성 중에서 일부 특성을 무작위로 고른 다음 이 중에서 최선의 분할을 찾는다. 분류 모델인 RandomForestClassifier는 기본적으로 전체 특성 개수의 제곱근만큼의 특성을 선택한다. 4개의 특성이 있다면 노드마다 2개를 랜덤하게 선택해 사용한다. 다만 회귀 모델인 RandomForestRegressor는 전체 특성을 사용한다.
- RandomForestClassifier 클래스를 이전에 사용했던 화이트 와인을 분류하는 문제에 적용해 보자.
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_validate
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.experimental import enable_hist_gradient_boosting
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.inspection import permutation_importance
from xgboost import XGBClassifier
from lightgbm import LGBMClassifier
import warnings
warnings.filterwarnings(action='ignore')
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)
- cross_validate()를 사용해 교차검증을 수행해보겠다.
- RandomForestClassifier는 기본적으로 100개의 결정 트리를 사용하므로 n_jobs 매개변수를 -1로 지정해 모든 CPU코어를 사용하는것이 좋다. cross_vaildate도 -1로 지정하도록 한다. return_train_score 매개변수를 True로 지정하면 검증 점수 뿐 아니라 훈련 세트에 대한 점수도 같이 반환한다. 훈련 세트와 검증 세트의 점수를 비교하면 과대적합을 파악하는데 용이하다.
rf = RandomForestClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(rf, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.9973541965122431 0.8905151032797809
- 결과를 살펴보면 훈련 세트에 다소 과대적합된 것으로 보인다. 여기서는 알고리즘을 조사하는것이 목적이기 때문에 매개변수를 더 조정하지 않는다.
- 랜덤포레스트는 결정 트리의 앙상블이기 때문에 DecisionTreeClassifier가 제공하는 중요한 매개변수를 모두 제공한다. 또한 중요도 계산도 가능하다. 랜덤 포레스트의 특성 중요도는 각 결정 트리의 특성 중요도를 취합한 것이다.
rf.fit(train_input, train_target)
print(rf.feature_importances_)
[0.23167441 0.50039841 0.26792718]
- 전반적으로 이전에 트리를 사용해 중요도를 뽑은 결과보다 당도의 중요성은 낮아지고 나머지 특성의 중요도가 높아졌다. 이는 랜덤 포레스트가 특성의 일부를 랜덤하게 선택해 결정 트리를 훈련하기 때문이다. 그 결과 하나의 특성에 과도하게 집중하지 않고 좀 더 많은 특성이 훈련에 기여할 기회를 얻는다. 이는 과대적합을 줄이고 일반화 성능을 높이는데 도움이 된다.
- 랜덤 포레스트는 자체적으로 모델을 평가하는 점수를 얻을 수도 있다. 랜덤 포레스트는 훈련 세트에서 중복을 허용하여 부트스트랩 샘플을 만들어 결정 트리를 훈련한다. 이 때 부트스트랩 샘플에 포함되지 않고 남는 샘플이 발생한다. 이런 샘플을 OBB샘플이라고 한다. 남는 샘플을 사용해 부트스트랩 샘플로 훈련한 결정 트리를 평가할 수 있다.
- 위의 점수를 얻기 위해서는 RandomForestClassifier 클래스의 oob_score 매개변수를 True로 지정해야한다. 이렇게 조정해주면 각 결정 트리의 OOB점수를 평균해 출력해준다.
rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)
rf.fit(train_input, train_target)
print(rf.oob_score_)
0.8934000384837406
- 교차 검증에서 얻은 점수와 매우 비슷한 결과가 나온다. OOB점수를 활용하면 교차 검증을 대신할 수 있어 결과적으로 훈련 세트에 더 많은 샘플을 사용할 수 있다.
- 다음에는 랜덤 포레스와 아주 비슷한 엑스트라 트리에대해 알아보자.
2. 엑스트라 트리
- 엑스트라 트리는 랜덤 포레스트와 매우 비슷하게 작동한다. 기본적으로 100개의 결정 트리를 훈련하고, 랜덤 포레스트와 동일하게 결정 트리가 제공하는 대부분의 매개변수를 지원한다. 또한 전체 특성 중 일부 특성을 랜덤하게 선택해 노드를 분할하는 데 사용한다.
- 랜덤 포레스트와 엑스트라 트리의 차이점은 부트스트랩 샘플을 이용하지 않는다는 점이다. 엑스트라 트리는 각 결정 트리를 만들 때 전체 훈련 세트를 사용한다. 대신 노드를 분할할 때 가장 좋은 분할을 찾는 것이 아니라 무작위로 분할한다.
- 하나의 결정 트리에서 특성을 무작위로 분할한다면 성능이 낮아지겠지만 많은 트리를 앙상블 하기 때문에 과대적합을 막고 검증 세트의 점수를 높이는 효과가 있다.
et = ExtraTreesClassifier(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']))
0.9974503966084433 0.8887848893166506
- 랜덤 포레스트와 비슷한 결과가 나온 것을 확인할 수 있다. 보통은 엑스트라 트리가 무작위성이 더 크기 때문에 랜덤 포레스트보다 더 많은 결정 트리를 훈련해야 한다. 하지만 랜덤하게 노드를 분할하기 때문에 빠른 계산 속도가 엑스트라 트리의 장점이다.
- 엑스트라 트리도 랜덤 포레스트와 마찬가지로 특성 중요도를 제공한다.
et.fit(train_input, train_target)
print(et.feature_importances_)
[0.20183568 0.52242907 0.27573525]
- 엑스트라 트리의 회귀 버전은 ExtraTreeRegressor 클래스이다.
- 여기까지 랜덤 포레스트와 엑스트라 트리 앙상블 학습에 대해 알아보았다. 다른 앙상블 학습을 알아보자,
3. 그레이디언트 부스팅
- 그레이디언트 부스팅은 깊이가 얕은 결정 트리를 사용해 이전 트리의 오차를 보완하는 방식으로 앙상블을 수행하는 방법이다. 사이킷런의 GradientBoostingClassifier는 기본적으로 깊이가 3인 결정 트리를 100개 사용한다. 깊이가 얕은 결정 트리를 사용하기 때문에 과대적합에 강하고 일반적으로 높은 일반화 성능을 기대할 수 있다.
- 그레이디언트 부스팅은 경사 하강법을 사용해 트리를 앙상블에 추가한다. 분류에서는 로지스틱 손실 함수를 사용하고, 회귀에서는 평균 제곱 오차 함수를 사용한다.
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']))
0.8881086892152563 0.8720430147331015
- 그레이디언트 부스팅은 결정 트리의 개수를 늘려도 과대적합에 매우 강하다. 학습률을 증가시키고 트리의 개수를 늘리면 성능이 향상된다.
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']))
0.9464595437171814 0.8780082549788999
- 결정 트리 개수를 500개로 5배나 늘렸지만 과대적합을 잘 억제하고 있다. 학습률 learning_rate의 기본값은 0.1이다. 그레이디언트 부스팅도 특성 중요도를 제공한다. 아래의 결과에서 확인할 수 있다.
gb.fit(train_input, train_target)
print(gb.feature_importances_)
[0.15872278 0.68010884 0.16116839]
- 일반적으로 그레이디언트 부스팅이 랜덤 포레스트보다 조금 더 높은 성능을 기대할 수 있다. 하지만 순서대로 트리를 추가하기 때문에 훈련 속도가 느리다.
- 그레이디언트 부스팅의 속도와 성능을 개선한 것이 다음에 살펴볼 히스토그램 기반 그레이디언트 부스팅이다.
4. 히스토그램 기반 그레이디언트 부스팅
- 히스트그램 기반 그레이디언트 부스팅은 정형 데이터를 다루는 머신러닝 알고리즘 중에 가장 인기가 높은 알고리즘이다.
- 히스토그램 기반 그레이디언트 부스팅은 먼저 입력 특성을 256개 구간으로 나눈다. 노드를 분할할 때 최적의 분할을 매우 빠르게 찾을 수 있다.
- 입력에 누락된 특성이 있어도 이를 따로 전처리를 수행할 필요가 없다.
- 히스토그램 기반 그레이디언트 부스팅은 기본 매개변수에서도 안정적인 성능을 얻을 수 있다. 성능을 높이고 싶을땐 max_iter 매개변수를 테스트해보면 된다.
hgb = HistGradientBoostingClassifier(random_state=42)
scores = cross_validate(hgb, train_input, train_target, return_train_score=True)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.9321723946453317 0.8801241948619236
- 위의 결과를 확인해보면 과대적합을 잘 억제하면서 그레이디언트 부스팅보다 더 나은 성능을 제공한다.
- 특성 중요도도 확인해보다. 특성 중요도를 계산하기 위해 permutation_importance() 함수를 사용해보겠다. 이 함수는 특성을 하나씩 랜덤하게 섞어서 모델의 성능이 변화하는지를 관찰해 어떤 특성이 중요한지 계산한다.
- 먼저 히스토그램 기반 그레이디언트 부스팅 모델을 훈련하고 훈련 세트에서 특성 중요도를 계산해보자. n_repeats 매개변수를 이용해 몇 번 랜덤하게 섞을 것인지 지정한다.
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)
[0.08876275 0.23438522 0.08027708]
hgb.fit(train_input, train_target)
result = permutation_importance(hgb, test_input, test_target, n_repeats=10, random_state=42, n_jobs=-1)
print(result.importances_mean)
[0.05969231 0.20238462 0.049 ]
- permutation_importance() 함수가 반환하는 객체는 반복하여 얻은 특성 중요도, 평균, 표준편차를 담고 있다. 평균을 출력해보면 랜덤 포레스트와 비슷한 비율임을 알 수 있다. 테스트세트의 결과에서는 그레이디언트 부스팅과 비슷하게 조금 더 당도에 집중하고 있다는 것을 알 수 있다.
- 최종적으로 히스토그램 기반 그레이디언트 부스팅 분류기를 통해 테스트 세트에서의 성능을 확인해보자.
hgb.score(test_input, test_target)
0.8723076923076923
- 테스트 세트에서는 약 87%의 정확도를 얻었다. 앙상블 모델을 이용하면 단일 결정 트리 보다는 좋은 결과를 얻을 수 있다.
- 사이킷런 말고도 히스토그램 기반 그레이디언트 부스팅 알고리즘을 구현한 라이브러리가 여럿 존재한다.
- 대표적인 라이브러리는 XGBoost, LightGBM이 있다.
xgb = XGBClassifier(tree_method = 'hist', random_state=42)
scores = cross_validate(xgb, train_input, train_target, return_train_score=True)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
[14:22:27] WARNING: /Users/travis/build/dmlc/xgboost/src/learner.cc:1095: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.
[14:22:28] WARNING: /Users/travis/build/dmlc/xgboost/src/learner.cc:1095: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.
[14:22:28] WARNING: /Users/travis/build/dmlc/xgboost/src/learner.cc:1095: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.
[14:22:28] WARNING: /Users/travis/build/dmlc/xgboost/src/learner.cc:1095: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.
[14:22:28] WARNING: /Users/travis/build/dmlc/xgboost/src/learner.cc:1095: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.
0.9555033709953124 0.8799326275264677
- XGBoost의 XGBClassifier를 사용하면 위와 같이 그레이디언트 부스팅 모델을 사용할 수 있다. tree_method를 ‘hist’로 해주면 히스토그램 기반 그레이디언트 부스팅을 사용할 수 있다.
- 다음으로 마이크로소프트에서 만든 LightGBM이다. LightGBM은 빠르고 최신 기술을 많이 적용하고 있다.
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']))
0.935828414851749 0.8801251203079884
- 사이킷런의 히스토그램 기반 그레이디언트 부스팅이 LightGBM의 영향을 많이 받았다.
5. 마치며
- 이번 시간에는 앙상블 기법들에 대해 배워보았다. 랜덤 포레스트, 엑스트라 트리, 그레이디언트 부스팅 모델과 히스토그램 기반 그레이디언트 부스팅에 대해 배워보는 시간이였다.
- 앞의 내용들을 일부 잊어버려서 공부해 놓았던 것들을 다시 살펴보아야 할 것 같다.
Leave a comment