[혼공머신] CHAPTER 05-3 트리의 앙상블
정형 데이터 | 비정형 데이터 |
- 수치만으로 의미 파악이 쉬운 데이터 - 값이 의미를 파악하기 쉽고, 규칙적인 값으로 데이터가 들어가는 경우 ex) 엑셀, CSV, 데이터베이스 |
- 정해진 규칙이 없어서 값의 의미를 쉽게 파악하기 힘든 경우 ex) 텍스트 데이터, 사진, 영상, 음성 |
앙상블 학습(ensemble learning) : 정형 데이터를 다루는 데 가장 뛰어난 성과를 내는 알고리즘, 더 좋은 예측 결과를 만들기 위해 여러 개의 모델을 훈련하는 머신러닝 알고리즘
랜덤 포레스트(Random Forest) : 여러 개의 무작위 의사결정트리 기반의 앙상블 학습 방법
- 입력한 훈련 데이터에서 랜덤하게 샘플을 추출하여 훈련 데이터를 만든다 -> 샘플 중복 가능
- 부트스트랩 샘플(bootstrap sample) : 무작위로 중복을 허용해서 선택한 n개의 데이터를 선택하는 과정으로 샘플링하여 분류한 데이터
· 훈련세트의 크기와 같게 만든다
· 각 노드를 분할할 때 전체 특성 중에서 일부 특성을 무작위로 고른 다음 이 중에서 최선의 분할을 찾는다
RandomForestClassifier는 기본적으로 전체 특성 개수의 제곱근만큼의 특성을 선택한다.
ex) 4개의 특성이 있다면 노드마다 2개를 랜덤하게 선택하여 사용
* 회귀모델인 RandomForestRegressor는 전체특성 사용
- 랜덤하게 선택한 샘플과 특성을 사용하기 때문에 훈련세트에 과대적합되는 것을 막아준다
- 검증세트와 테스트세트에서 안정적인 성능을 얻을 수 있다
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
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)
from sklearn.model_selection import cross_validate
from sklearn.ensemble import RandomForestClassifier
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']))
RandomForestClassifier
- n_jobs = -1 : CPU 코어 전체 사용
cross_validate() 함수 : 다중 평가 지표, 교차 검증 결과 정확도 점수의 배열을 반환
- n_jobs = -1 : 병렬로 교차 검증 수행
- return_train_score = True : 검증 점수와 훈련세트 점수 반환, 훈련세트 점수 반환 여부(default = False)
0.9973541965122431 0.8905151032797809
훈련세트에 과대적합 된 것 같다
랜덤 포레스트의 특성 중요도
rf.fit(train_input, train_target)
print(rf.feature_importances_)
[알코올 도수, 당도, pH]의 중요도가 1장의 결정 트리와 다르게 알코올 도수의 중요도가 감소하고 나머지 두 개의 특성의 중요도가 높아졌다
[0.23167441 0.50039841 0.26792718]
-> 랜덤 포레스트가 특성의 일부를 랜덤하게 선택하여 결정 트리를 훈련하기 때문이다. 하나의 특성에 과도하게 집중하지 않고 좀 더 많은 특성이 훈련에 기여하게 된다. -> 과대적합을 줄이고 일반화 성능을 높인다.
RandomForestClassifier는 자체적으로 모델을 평가하는 점수를 얻을 수 있다. 훈련 세트에서 중복을 허용하여 부트스트랩 샘플을 만들어 결정 트리를 훈련한다.
OOB(out of bag) 샘플 : 부트스트랩 샘플에 포함되지 않고 남는 샘플
- 부트스트랩 샘플로 훈련한 결정 트리를 평가할 수 있다
- 교차 검증을 대신할 수 있어서 훈련세트에 더 많은 샘플을 사용할 수 있다
rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)
rf.fit(train_input, train_target)
print(rf.oob_score_)
RandomForestClassifier 클래스의 oob_score 매개변수를 True로 지정하면 점수를 얻을 수 있다.
랜덤 포레스트는 각 결정 트리의 OOB 점수를 평균하여 출력한다.
0.8934000384837406
교차 검증에서 얻은 점수와 매우 비슷한 결과!
엑스트라 트리(Extra Trees) : 랜덤 포레스트와 비슷한 앙상블 학습으로 랜덤 포레스트보다 조금 더 극단적이고 랜덤하게 만든 모델
- 랜덤 포레스트 보다 약 3배정도 연산이 빠르다
- 부트스트랩 샘플을 사용하지 않고 전체 훈련 데이터를 사용한다
- 노드를 분할할 때 무작위로 분활한다.
- 엑스트라 트리가 사용하는 결정트리는 splitter='random'인 결정트리
-> 특성을 무작위로 분할하면 성능이 낮아지지만 많은 트리를 앙상블하기 때문에 과대적합을 줄이고 검증 세트의 점수를 높이는 효과가 있다
- bias(편향)와 variance(분산)을 낮출 수 있다.
편향 | 분산 |
- 지나치게 단순한 모델로 인한 에러 - 편향이 크면 과소적합 - 예측값이 정답과 얼마나 멀리 떨어져 있는지로 측정 가능 - 학습데이터를 충분히 표현할 수 없기 때문에 발생 |
- 지나치게 복잡한 모델로 인한 에러 - 분산이 크면 과대적합 - 훈련데이터에 지나치게 적합을 시켜 일반화가 되지 않음 - 훈련 데이터에 너무 민감하게 반응함 |
모델이 복잡해질수록 편향이 작아지고 분산은 커진다(과대적합)
모델이 단순해질수록 편향이 커지고 분산이 작아진다(과소적합) -> 편향과 분산의 합이 최소가 되는 적당한 지점을 찾아야한다.
사이킷런의 ExtraTreesClassifier
from sklearn.ensemble import ExtraTreesClassifier
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]
결정 트리보다 당도에 대한 의존성이 낮다
그레이디언트 부스팅(gradient boosting) : 깊이가 얕은 결정 트리를 사용하여 이전 트리의 오차를 보완하는 방식으로 순차적으로 트리를 만든다.
- 결정 트리를 연속적으로 추가하여 손실 함수를 최소화
- 깊이가 얕은 트리와 같은 간단한 모델을 많이 연결하는 것 -> 데이터의 일부에 대해서만 예측을 잘 수행할 수 있어서 트리가 많이 추가될수록 성능이 높아짐
사이킷런의 GradientBoostingClassifier
GradientBoostingClassifier(ccp_alpha=0.0, criterion='friedman_mse', init=None,
learning_rate=0.2, loss='deviance', max_depth=3,
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, n_estimators=500,
n_iter_no_change=None, presort='deprecated',
random_state=42, subsample=1.0, tol=0.0001,
validation_fraction=0.1, verbose=0,
warm_start=False)
subsample : 트리 훈련에 사용할 훈련세트의 비율을 정한다.
- 기본값 = 1.0, 전체 훈련세트 사용
- 1보다 작으면 훈련세트의 일부를 사용
- 경사하강법 단계마다 일부 샘플을 랜덤하게 선택하여 진행하는 확률적 경사 하강법이나 미니배치 경사하강법과 비슷
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']))
깊이가 3인 결정 트리를 100개 사용
깊이가 얕은 트리 사용 -> 과대적합에 강하고 높은 일반화 성능 기대
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']))
결정 트리 개수를 500개로 늘렸지만 과대적합을 잘 억제한다.
0.9464595437171814 0.8780082549788999
n_estimators : 트리의 개수 지정
learning_rate : 이전 트리의 오차를 보정하는 정도
- 기본값 = 0.1
- n_estimators와 연관되어 있으며 해당 변수를 낮추면 비슷한 복잡도의 모델을 만들기 위해서는 n_estimators를 늘려서 더 많은 트리를 추가해야한다
- 일반적으로는 가용한 시간과 메모리 한도에서 n_estimators를 맞추고 나서 적절한 learning_rate를 찾는다
gb.fit(train_input, train_target)
print(gb.feature_importances_)
그레이디언트 부스팅이 랜덤 포레스트보다 당도에 더 집중한다.
[0.15872278 0.68010884 0.16116839]
히스토그램 기반 그레이디언트 부스팅(Histrogram-based Gradient Boosting) : 훈련데이터를 256개의 구간으로 나누고, 그 구간 중에서 하나를 떼어놓고 누락된 값을 위해서 사용한다.
-> 노드를 분할할 때 최적의 분할을 매우 빠르게 찾을 수 있다
HistGradientBoostingClassifier
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)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
max_iter : 최대 반복 횟수, 이진 분류를 위한 최대 트리 수
0.9321723946453317 0.8801241948619236
과대적합을 억제하면서 그레이디언트 부스팅보다 조금 더 높은 성능을 제공한다.
from sklearn.inspection import permutation_importance
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)
permutation_importance() : 특성을 하나씩 랜덤하게 섞어서 모델의 성능이 변화하는지를 관찰하여 어떤 특성이 중요한지를 계산
-> 랜덤하게 섞었을 때 모델의 성능이 떨어지면 중요한 특성인 것이고 성능이 그대로이거나 좋아지면 중요하지 않은 특성임을 알 수 있다.
ex) 중요한 특성을 랜덤하게 섞으면 결과 데이터가 정확하게 되지 않으므로 예측 정확도가 떨어진다. 중요하지 않은 특성을 랜덤하게 섞으면 결과 예측에 거의 영향을 미치지 않아 중요도가 떨어지지 않는다
- n_repeats : 랜덤하게 섞을 횟수 지정, 기본값 = 5
[0.08876275 0.23438522 0.08027708]
두 번째(당도) 특성을 섞으면 정확도가 0.234만큼 떨어진다 -> 그만큼 두 번째 특성이 중요하다
첫 번째(도수), 세 번째(pH) 특성을 섞어도 정확도의 손실이 0.08밖에 되지 않는다 -> 그만큼 중요도가 높지 않다
fit 메소드에 넣은 특성을 기반으로 트리를 만들 때 특성 중요도를 계산 -> 훈련세트에 대해서만 중요도를 알 수 있었다
훈련세트, 테스트세트, 사이킷런에서 제공하는 추정기 모델에 모두 사용 가능
result = permutation_importance(hgb, train_input, train_target,
n_repeats=10, random_state=42, n_jobs=-1)
print(result.importances_mean)
테스트세트의 중요도 -> 이 모델을 실전에 투입했을 때 어떤 특성에 더 중점을 둘 것인지, 어떤 특성에 더 민감한지를 미리 예상해 볼 수 있다.
[0.05969231 0.20238462 0.049 ]
세 번째 특성인 pH는 실전에 투입했을 때 더 큰 의미가 없는 특성이 될 것이라고 예상할 수 있다.
hgb.score(test_input, test_target)
테스트세트에서는 약 87%의 정확도를 얻었다.
0.8723076923076923
실전에 투입했을 때 이보다 낮은 성능을 보일 것이다
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)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
tree_method를 'hist'로 정해 히스토그램 기반 그레이디언트 부스팅 사용
0.8824322471423747 0.8726214185237284
기본 매개변수에서 과대적합을 잘 억제함을 볼 수 있다
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']))
사이킷런의 히스토그램 기반 그레이디언트 부스팅 점수와 비슷함을 확인할 수 있다
0.9338079582727165 0.8789710890649293
-> 사이킷런의 히스토그램 기반 그레이디언트 부스팅이 LightBGM에서 영향을 많이 받았다고 한다.