머신러닝 + 딥러닝

[혼공머신] CHAPTER 05-3 트리의 앙상블

2021. 10. 7. 01:44
728x90
정형 데이터 비정형 데이터
- 수치만으로 의미 파악이 쉬운 데이터
- 값이 의미를 파악하기 쉽고, 규칙적인 값으로 데이터가 들어가는 경우
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(분산)을 낮출 수 있다.


편향 분산
- 지나치게 단순한 모델로 인한 에러
- 편향이 크면 과소적합
- 예측값이 정답과 얼마나 멀리 떨어져 있는지로 측정 가능
- 학습데이터를 충분히 표현할 수 없기 때문에 발생
- 지나치게 복잡한 모델로 인한 에러
- 분산이 크면 과대적합
- 훈련데이터에 지나치게 적합을 시켜 일반화가 되지 않음 
- 훈련 데이터에 너무 민감하게 반응함

http://scott.fortmann-roe.com

모델이 복잡해질수록 편향이 작아지고 분산은 커진다(과대적합)

모델이 단순해질수록 편향이 커지고 분산이 작아진다(과소적합) -> 편향과 분산의 합이 최소가 되는 적당한 지점을 찾아야한다.


사이킷런의 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에서 영향을 많이 받았다고 한다.

 

 

728x90