4 minute read

트리의 앙상블

정형 데이터 : 어떤 구조(CSV,database,excel)에 저장되는 데이터

비정형 데이터 : 데이터베이스나 엑셀로 표현하기 어려운 것들 (텍스트 사진 음악 등)

비정형 데이터에는 신경망 알고리즘을 사용한다. 전통적인 머신러닝 방법으로는 모델을 만들기 까다롭다.

앙상블 : 여려 개의 모델을 사용해서 하나의 결과를 만들어내는 것

앙상블 학습은 정형 데이터를 다루는 데 가장 뛰어난 성과를 내는 알고리즘이다.

앙상블이 트리에 사용하는 이유 : 트리는 성능이 좋지만 과대적합이 쉬우므로 이 트리를 여러개를 만들어서 과대적합을 줄일 수 있으면 좋은 모델이 탄생할 것

랜덤 포레스트

랜덤 포레스트

결정 트리를 랜덤하게 만들어서 결정 트리의 숲을 만든다.

그리고 각 결정 트리의 예측을 사용해 최종 예측을 만든다.

앙상블에서는 랜덤성을 이용해서 트리의 성능을 낮춘다.

부트스트랩 샘플

부트스트랩

랜덤 포레스트는 각 트리를 훈련하기 위한 데이터를 랜덤하게 만든다.

훈련 데이터에서 랜덤하게 샘플을 추출하여 훈련 데이터를 만든다(복원 추출/중복을 허용한 샘플링)

이 샘플을 부트스트랩 샘플이라고 한다. 훈련 세트만큼 뽑기 때문에 훈련 세트와 크기가 같다.

분류의 경우에는 각 부트스트랩 샘플에서 결정 트리 훈련을 하면 나오는 확률들을 모두 더해서 트리 개수로 나누어준다.

회귀의 경우에도 결정 트리 훈련에서 각 예측값이 나오게 되는데 그 값들을 트리개수만큼 나우어서 평균내준다.

랜덤 포레스트 구조

결정트리 훈련을 하여 결정트리 한 개를 만드는 경우에

노드를 분할할 때 이전에는 불순도차이가 최대가 되도록 만드는데 원래는 모든 특성을 사용하지만

전체 특성 중에서 일부 특성을 무작위로 고른 다음에 이 중에서 최선의 분할을 찾는다.

RandomForestClassifier는 전체 특성의 제곱근만큼의 특성을 랜덤하게 골라서 선택한다.

Scikit-Learn의 랜덤 포레스트는 기본적으로 100개의 결정 트리를 이런 방식으로 훈련한다.

랜덤 포레스트는 랜덤하게 선택한 샘플과 특성을 사용했기 때문에 훈련 세트에 과대적합되는것을 막아주고 검증 세트와 테스트 세트에서 안정적인 성능을 얻을 수 있다.

코드를 살펴보면

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)
# cross_validate (RandomForestClassifier, 훈련 , 타깃값, 훈련 세트 점수 반환 : True) 

print(np.mean(scores['train_score']), np.mean(scores['test_score']))
# 훈련 세트 점수, 검증 세트 점수
# 0.9973541965122431 0.8905151032797809 

rf.fit(train_input, train_target)
print(rf.feature_importances_)
# 특성의 중요도 확인 
# [0.23167441 0.50039841 0.26792718]
# DecisionTreeClassifier 특성 중요도 [0.12345626 0.86862934 0.0079144 ]

rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)
# oob 샘플 사용
rf.fit(train_input, train_target)
print(rf.oob_score_)
# oob 샘플을 사용한 스코어 (위의 검증 세트와 비슷함)
# 0.8934000384837406

image-20220510172921182

DecisionTreeClassifier의 특성 중요도가 당도가 높게 나온 것에 비해서 RandomForestClassifier랜덤하게 사용하기 때문에 다른 특성들도 강제로 사용하게 되는 부분이 있어서 당도의 특성이 내려가고 다른 특성의 중요도가 올라간 것을 볼 수있다.

부트스트랩은 중복을 허용한 샘플링을 하기 때문에 트리에서 사용하지 않는 부분들 이 생기는데 이 부분을 OOB라고 한다.

중복을 허용해서 램덤하게 샘플을 추출하기때문에 남는 부분도 랜덤하게 결정된다. 그러므로 이 부분을 사용한다면 검증세트의 역할을 할 수 있게 된다.

엑스트라 트리

랜덤 포레스트와 매우 비슷하게 동작

랜덤 포레스트와의 차이점은 부트스트랩을 사용하지 않는다는 것

각 결정 트리 모델을 만들 때 전체 훈련 세트를 사용

노드를 분할할 때 가장 좋은 분할을 찾는 것이 아니라 무작위로 분할한다.

랜덤하게 분할하기 때문에 속도가 빠른 대신 랜덤 포래스트 보다 더 많은 트리 개수를 요구한다.

splitter=random으로 지정한 결정 트리 과대적합을 막고 검증 세트의 점수를 높이는 효과가 있다.

코드를 보면

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]
# 랜덤 포래스트와 비슷한 모습

랜덤 포레스트와 엑스트라 트리는 둘 다 과대적합을 줄이는 역할을 하는데 어떤 상황일 때 선택하는가?? 찾아보기

그레이디언트 부스팅

경사하강법처럼 트리를 앙상블에 추가하는데

분류의 경우 로지스틱 손실함수 사용

회귀의 경우 보통 평균제곱오차를 사용한다.

위 손실함수의 값을 낮추도록 트리를 추가한다. 어떤 실수값을 낮추기 때문에 그레이디언트 부스팅 분류/회귀 모두 추가되는 트리는 회귀 트리(DecisionTreeRegressor)이다.

앞의 랜덤포래스트나 엑스트라 트리처럼 과대적합을 막기 위해 learning rate(학습율)을 지정해준다. 경사하강법에서 최적점을 지나치는 문제가 나타나는 문제를 막기 위해

GradientBoostingClassifier은 기본적으로 깊이가 3인 결정 트리를 100개 사용

깊이가 얕은 결정 트리를 사용하기 때문에 과대적합에 강하고 일반적으로 높은 일반화 성능을 기대할 수 있다.

하지만 경사하강법에서 next step이 있는 것 처럼 순서대로 추가해야 하기 때문에 랜덤 포레스트나 엑스트라 트리처럼 병렬로 훈련하는 것이 불가능하므로 시간이 더 걸린다.

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']))
# 훈련 세트 검증 세트 출력
# 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

gb.fit(train_input, train_target)
print(gb.feature_importances_)
#[0.15853457 0.68010884 0.1613566 ]
# 당도가 조금 더 높게 나옴

히스토그램 기반 그레이디언트 부스팅

정형 데이터를 다루는 머신러닝 알고리즘 주엥 가장 인기가 높은 알고리즘

입력 특성을 256개 구간으로 나누어서 노드를 분할할 때 최적의 분할을 매우 빠르게 찾을 수 있다.

255구간으로 나누고 1개는 누락된 값을 위해 예비로 놓는다.

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']))
#0.9321723946453317 0.8801241948619236

Permutation Importance

Permutation Importance

특성을 섞어서 특성에 대한 중요도를 판별하는 방법

위 사진에서 특성 a를 섞었을 때 더 낮은 성능을 보였기 때문에 특성a가 제일 중요한 특성이라는것을 알 수 있다.

코드를 보면

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)
# n_repeats 섞는 횟수

print(result.importances_mean)
# 값의 의미 : 정확도가 x만큼 떨어진다.
# [0.08876275 0.23438522 0.08027708]

result = permutation_importance(hgb, test_input, test_target, n_repeats=10,
                                random_state=42, n_jobs=-1)
#테스트 샘플에서의 permutation_importance도 가능하다

print(result.importances_mean)
#[0.05969231 0.20238462 0.049]
# 실전에서는 ph특성이 테스트보다 더 의미가 없을 수 있다.

hgb.score(test_input, test_target)
# 0.8723076923076923
# 실전에서 87퍼센트로 구분 가능

result.importances_mean 의 결과를 보면 합이 1이 안되는데 의미는

첫번째 특성인 도수를 섞으면 0.08876275만큼의 정확도가 떨어진다는 것이다.

XGBoost & LightGBM

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']))
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']))

참고

박해선,혼자 공부하는 머신러닝, 한빛미디어, 2021,263~280p