LightGBM은 XGBoost와 함께 부스팅 계열 알고리즘에서 가장 각광을 받고 있다. LightGBM과 XGBoost의 예측 성능은 별다른 차이가 없지만 LightGBM이 XGBoost보다 2년 후에 만들어지다 보니 XGBoost의 장점은 계승하고 단점은 보완하는 방식으로 개발됐다. 또한 기능상의 다양성은 LightGBM이 약간 더 많다.

XGBoost와 마찬가지로, 대용량 데이터에 대한 뛰어난 예측 성능 및 병렬 컴퓨팅 기능을 제공하며, 최근에는 GPU까지 지원하고 있다.

 

<LightGBM의 XGBoost 대비 장점>

●더 빠른 학습과 예측 수행 시간

●더 작은 메모리 사용량

●카테고리형 피처의 자동 변환과 최적 분할(원-핫 인코딩등을 사용하지 않고도 카테고리형 피처를 자동으로 변환하고 이에 따른 최적의 노드 분할 수행)

 

<LightGBM의 트리 분할 방식 - 리프 중심 트리 분할>

기존의 대부분 트리 기반 알고리즘은 tree를 수평으로 확장(level-wise)하는 반명 LightGBM은 tree를 수직으로 확장(leaf-wise)

 

●Level-wise tree growth(수평 확장, 균형 트리 분할)

최대한 균형 잡힌 트리를 유지하면서 분할하기 때문에 트리의 깊이가 최소화되어 overfitting에 강한 구조이다. 

하지만 균형을 맞추기 위한 시간이 필요

●Leaf-wise tree growth(수직 확장, 리프 중심 트리 분할)

트리의 균형을 맞추지 않고, 최대 손실 값(max delta loss)을 가지는 리프 노드를 지속적으로 분할하면서 트리의 깊이가 깊어지고 비대칭적인 트리가 생성된다. 이렇게 생성된 트리는 학습을 반복할수록 결국은 균형 트리 분할 방식보다 예측 오류 손실을 최소화할 수 있다.

 

즉, 트리의 어느 레벨에서 모든 노드를 확장시키는 것이 아닌 최종 노드 하나만 분할하는 방식을 사용

loss가 가장 큰 부분을 쪼개고 쪼개서 결국 최대한으로 줄여지는 것이 가능

다른 노드들을 분할시키지 않고 오로지 residual이 큰 노드만 쪼개다보니 메모리의 절약과 속도를 향상시킬 수 있음

 

<LightGBM 파이썬 구현>

XGBoost와 마찬가지로 파이썬 래퍼, 사이킷런 래퍼로 구분됨

 

<LightGBM 하이퍼 파라미터>

LightGBM 하이퍼 파라미터는 XGBoost와 많은 부분 유사하지만, LightGBM은 XGBoost와 다르게 리프 노드가 계속 분할되면서 트리의 깊이가 깊어지므로 이러한 트리 특성에 맞는 하이퍼 파라미터 설정이 필요

하이퍼 파라미터 튜닝 방안

num_leaves의 개수를 중심으로 min_child_samples, max_depth를 함께 조정하면서 모델의 복잡도를 줄이는 것이 기본 튜닝 방안임

●num_leaves는 LightGBM 모델의 복잡도를 제어하는 주요 파라미터로 일반적으로 num_leaves의 개수를 높이면 정확도가 높아지지만, 반대로 트리의 깊이가 깊어지고 모델의 복잡도가 커져 과적합 영향도가 커짐

●min_child_samples는 과적합을 개선하기 위한 중요한 파라미터로 num_leaves와 학습 데이터의 크기에 따라 달라지지만, 보통 큰 값으로 설정하면 트리가 깊어지는 것을 방지

●max_depth는 num_leaves, min_child_samples와 결합해 과적합을 개선하는 데 사용

 

또한 learning_rate를 작게 하면서 n_estimators를 크게 하는 것은 부스팅 계열 튜닝에서 가장 기본적인 튜닝 방안이므로 이를 적용하는 것도 좋음. 물론 n_estimators를 너무 크게 하는 것은 과적합으로 오히려 성능이 저하될 수 있음에 유념

 

이 밖에 과적합을 제어하기 위해서 reg_lambda, reg_alpha와 같은 regularization을 적용하거나 학습 데이터에 사용할 피처의 개수나 데이터 샘플링 레코드 개수를 줄이기 위해 colsample_bytree, subsample 파라미터를 적용할 수 있다.



LightGBM 실습 - 위스콘신 유방암 예측

from lightgbm import LGBMClassifier

import pandas as pd
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

dataset = load_breast_cancer()

cancer_df = pd.DataFrame(data=dataset.data, columns=dataset.feature_names)
cancer_df['target'] = dataset.target
X_features = cancer_df.iloc[:, :-1]
y_label = cancer_df.iloc[:, -1]

# 전체 데이터 중 80%는 학습용 데이터, 20%는 테스트용 데이터 추출
X_train, X_test, y_train, y_test = train_test_split(X_features, y_label, test_size=0.2, random_state=156)

# 위에서 만든 X_train, y_train을 다시 쪼개서 90%는 학습, 10%는 검증용 데이터로 분리
X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train, test_size=0.1, random_state=156)

lgbm_wrapper = LGBMClassifier(n_estimators=400, learning_rate=0.05)
# 조기 중단 수행
evals = [(X_tr, y_tr), (X_val, y_val)]
lgbm_wrapper.fit(X_tr, y_tr, early_stopping_rounds=50, eval_metric='logloss',
                eval_set=evals)
preds = lgbm_wrapper.predict(X_test)
pred_proba = lgbm_wrapper.predict_proba(X_test)[:, 1]
[1]	training's binary_logloss: 0.625671	valid_1's binary_logloss: 0.628248
[2]	training's binary_logloss: 0.588173	valid_1's binary_logloss: 0.601106
[3]	training's binary_logloss: 0.554518	valid_1's binary_logloss: 0.577587
... ... ...
[60]	training's binary_logloss: 0.0550801	valid_1's binary_logloss: 0.260746
[61]	training's binary_logloss: 0.0532381	valid_1's binary_logloss: 0.260236
[62]	training's binary_logloss: 0.0514074	valid_1's binary_logloss: 0.261586
... ... ...
[109]	training's binary_logloss: 0.00915965	valid_1's binary_logloss: 0.280752
[110]	training's binary_logloss: 0.00882581	valid_1's binary_logloss: 0.282152
[111]	training's binary_logloss: 0.00850714	valid_1's binary_logloss: 0.280894

조기 중단으로 111번째 반복까지만 수행된 후 학습이 종료했다. 111번째 반복의 logloss가 0.280894, 61번째 반복의 logloss가 0.260236으로서 50번 반복하는 동안 성능 평가 지수가 향상되지 못해서 더 이상 수행하지 않고 학습이 종료됐다. 이렇게 학습된 모델로 예측 성능을 평가해 보자.

from sklearn.metrics import *

def get_clf_eval(y_test, pred=None, pred_proba=None):
    confusion = confusion_matrix(y_test, pred)
    accuracy = accuracy_score(y_test, pred)
    precision = precision_score(y_test, pred)
    recall = recall_score(y_test, pred)
    f1 = f1_score(y_test, pred)
    roc_auc = roc_auc_score(y_test, pred_proba)
    print('오차 행렬')
    print(confusion)
    print('정확도:{0:.4f}, 정밀도:{1:.4f}, 재현율:{2:.4f}, \
          F1:{3:.4f}, AUC:{4:.4f}'.format(accuracy, precision, recall, f1, roc_auc))
get_clf_eval(y_test, preds, pred_proba)

이렇게 학습된 모델로 예측한 결과 정확도는 95.61%이다.

 

마찬가지로, LightGBM은 XGBoost와 동일하게 plot_importance()를 통해 피처 중요도를 시각화할 수 있다.

from lightgbm import plot_importance
import matplotlib.pyplot as plt
%matplotlib inline

fig, ax = plt.subplots(figsize=(10,12))
plot_importance(lgbm_wrapper, ax=ax)

본 스크립트는 부스팅에 대한 이해를 더 돕기 위해 '김성범[소장/인공지능공학연구소]'의 Boosting 영상을 보고 공부한 내용임을 밝힌다.


bagging vs boosting


◎Boosting 아이디어

●여러 개의 weak learner 모델을 순차적으로 구축하여 최종적으로 합침(앙상블)

●각 단계에서 새로운 base learner를 학습하여 이전 단계의 base learner의 단점을 보완

●각 단계를 거치면서 모델이 점차 강해짐 → boosting

 

◎Boosting 알고리즘 종류

●Adaptive boosting(Adaboost)

●Gradient boosting machines(GBM)

●XGBoost

●Light gradient boost machines(LightGBM)

●Catboost


Adaboost

◎Adaptive boosting(Adaboost)

●각 단계에서 새로운 base learner를 학습하여 이전 단계의 base learner의 단점을 보완

●traing error가 큰 관측치의 선택 확률(가중치)을 높이고, traing error가 작은 관측치의 선택 확률(가중치)을 낮춤

→모든 관측치 별로 erorr를 보고, 오분류한 관측치에 보다 집중

●앞 단계에서 오분류한 관측치에 조정된 확률(가중치)을 기반으로 다음 단계에서 사용될 traing dataset를 구성

●위 과정들을 반복해서 각 weak learner 모델을 순차적으로 구축하여 최종적으로 합침

 

◎Adaboost algorithm

위 과정이 무슨 말인지 아래에서 쉽게 알아보자.

모든 데이터에 초기 가중치는 0.1로 주어진다.


Gradient Boosting Machines(GBM)

◎GBM

●Gradient Boosting = Boosting with gradient decent

●첫번째 단계의 모델 tree1을 통해 y를 예측하고, Residual(실제값 - 예측값, y - y_hat)을 다시 두번째 단계 모델 tree2를 통해 예측하고, 여기서 발생한 Residual을 모델 tree3로 예측 → 점차 Residual은 작아짐(실제값과 예측값의 차이가 작아진다) → 점차 정확해짐

●Gradient boosted model = tree1 + tree2 + tree3

설명:

첫 번째 그림 - 정답지라고 생각하면 됨

두 번째 그림 - tree1은 트리 모델을 이용해 이 데이터를 fit한 것이다. 

세 번째 그림 - tree2에 있는 점들은 첫 번째 트리 모델(파란색 선)과 실제 y값의 차이이다. 즉, 첫 번째 트리로부터 나오는 Residual 값이다. 이 Residual 값을 두 번째 트리 모델로 fit한 것이다. 

네 번째 그림 - 두 번째 트리 모델로 부터 나오는 Residual 값을 다시 세 번째 트리 모델로 fit한 것이다.

 

쉽게 생각해, 일단은 rough하게 첫 번째 트리를 이용해 fit을 한다. 그러면 나머지 우리가 놓친 부분이 있을 것이다. 이 놓친 부분을 Residual이라 하고, 놓친 부분을 두 번째 트리로 fit하고, 두 번째 트리에서도 놓친 부분을 세 번째 트리로 fit하고, 세 번째 트리에서도 놓친 부분을 네 번째 트리로 fit함 → 이런식으로 Residual을 계속 모델링해서 거기서 부터 나오는 모델을 최종적으로 다 더하는 아이디어라고 보면 됨

 

◎Why gradient?

왜 gradient라고 불렀을까? 라는 궁금증이 생긴다.

gradient는 미분이라고 생각하면 된다.  

위의 식은 loss function(손실 함수)이다. (식을 보면, 실제 y값과 모델로 부터 나오는 y값의 차이의 제곱, 즉 실제값과 예측값의 차이의 제곱을 다 더하고 1/2로 나눠줌)

위 식을 f(x)로 미분을 할 것이다.

결국, loss function의 gradient는 loss function을 f(x)로 미분한 결과로 보면된다. 

gradient를 구했는데 -Residual인 것을 알 수 있다.

 

 

그러면 Residual은 -gradient이다. 이것을 'Residual = Negative Gradient' 라고 한다.

 

 

 

 

이번에는 그림으로 이해해보기로 하자.

 

 

 

 

사이킷런 래퍼 XGBoost의 개요 및 적용

사이킷런 래퍼 XGBoost 모듈은 사이킷런의 다른 estimator와 사용법이 같고, 분류의 경우 XGBClassifier, 회귀의 경우 XGBRegressor가 존재한다.


사이킷런 래퍼 XGBoost 적용 - 위스콘신 유방암 예측

분류 문제이므로 XGBClassifier를 이용해 예측해보자.

모델의 하이퍼 파라미터는 앞의 파이썬 래퍼 XGBoost와 동일하게 n_estimators(num_boost_rounds)는 400, learning_rate(eta)는 0.05, max_depth=3으로 설정하고, 앞 실습과 다르게 학습 데이터는 검증데이터로 분할되기 이전인 X_trian, y_train을 이용하고, 테스트 데이터는 그대로 X_test, y_test를 이용하겠다. 

from xgboost import XGBClassifier

xgb_wrapper = XGBClassifier(n_estimators=400, learning_rate=0.05, max_depth=3)
xgb_wrapper.fit(X_train, y_train)
w_preds = xgb_wrapper.predict(X_test)
w_pred_proba = xgb_wrapper.predict_proba(X_test)[:, 1]
get_clf_eval(y_test, w_preds, w_pred_proba)

앞의 파이썬 래퍼 XGBoost보다 더 좋은 결과가 나왔다. 그 이유는 위스콘신 데이터 세트의 개수가 워낙 작은데, 앞에서는 조기 중단을 위해서 최초 학습 데이터인 X_train을 다시 학습용 X_tr, X_val로 분리하면서 최종 학습 데이터 건수가 작아지기 때문에 발생한 것으로 예상된다. 즉, 위스콘신 데이터 세트는 작기 때문에 전반적으로 검증 데이터를 분리하거나 교차검증등을 적용할 때 성능 수치가 불안정한 모습을 보인다.

하지만 데이터 건수가 많은 데이터의 경우라면, 원본 학습 데이터를 다시 학습과 검증 데이터로 분리하고 여기에 조기 중단 횟수를 적절하게 부여할 경우 일반적으로는 과적합을 개선할 수 있어서 모델 성능이 조금 더 향상될 수 있다.

 

이번에는 사이킷런 래퍼 XGBoost에서 조기 중단을 수행해보자. 조기 중단 관련 파라미터인 early_stopping_rounds, eval_metric, eval_set을 fit()에 입력하면 된다.

파이썬 래퍼 XGBoost 실습과 마찬가지로 최초 학습 데이터에서 다시 분리된 최종 학습 데이터와 검증 데이터를 이용하여 학습과 조기 중단을 적용해본다.

 

주의할 점은 eval_set은 파이썬 래퍼 XGBoost와 다르게 학습과 검증을 의미하는 문자열을 넣지 않는다. [(X_tr, y_tr), (X_val, y_val)]과 같이 지정하면 맨 앞의 튜플이 학습용 데이터, 뒤의 튜플이 검증용 데이터로 자동 인식 된다.

from xgboost import XGBClassifier

xgb_wrapper = XGBClassifier(n_estimators=400, learning_rate=0.05, max_depth=3)
evals = [(X_tr, y_tr), (X_val, y_val)]
xgb_wrapper.fit(X_tr, y_tr, early_stopping_rounds=50, eval_metric='logloss', eval_set=evals)

ws50_preds = xgb_wrapper.predict(X_test)
ws50_pred_proba = xgb_wrapper.predict_proba(X_test)[:, 1]
[0]	validation_0-logloss:0.65016	validation_1-logloss:0.66183
[1]	validation_0-logloss:0.61131	validation_1-logloss:0.63609
[2]	validation_0-logloss:0.57563	validation_1-logloss:0.61144
... ... ...
[125]	validation_0-logloss:0.01998	validation_1-logloss:0.25714
[126]	validation_0-logloss:0.01973	validation_1-logloss:0.25587
[127]	validation_0-logloss:0.01946	validation_1-logloss:0.25640
... ... ...
[175]	validation_0-logloss:0.01267	validation_1-logloss:0.26086
[176]	validation_0-logloss:0.01258	validation_1-logloss:0.26103

n_estimators가 400이지만 400번을 반복하지 않고 파이썬 래퍼 XGBoost의 조기 중단과 동일하게 176번째 반복에서 학습을 마무리했다. 마찬가지로 126번째 반복에서 검증 데이터 세트의 성능 평가인 validation_1-logloss가 0.25587로 가장 낮았고, 이후 50번 반복까지 더 이상 성능이 향상되지 않았기 때문에 학습이 조기 종료 되었다.

 

조기 중단으로 학습된 XGBClassifier의 예측 성능을 살펴보자. 결과는 파이썬 래퍼의 조기 중단 성능과 동일할 것이다. 앞에서 언급한 대로 위스콘신 데이터 세트는 워낙 작기 때문에 조기 중단을 위한 검증 데이터를 분리하면 검증 데이터가 없는 학습 데이터를 사용했을 때보다 성능이 저조하다.

get_clf_eval(y_test, ws50_preds, ws50_pred_proba)

early_stopping_rounds를 10으로 줄여보자. 10으로 하면 아직 성능이 향상될 여지가 있음에도 불구하고 10번 반복하는 동안 성능 평가지표가 향상되지 않으면 반복이 멈춰 버려서 충분한 학습이 되지 않아 예측 성능이 나빠질 수 있다. 즉, 조기 중단값을 너무 급격하게 줄이면 예측 성능이 저하될 우려가 크다. 

# early_stopping_rounds를 10으로 설정하고 재학습
xgb_wrapper.fit(X_tr, y_tr, early_stopping_rounds=10, eval_metric='logloss', eval_set=evals)

ws10_preds = xgb_wrapper.predict(X_test)
ws10_pred_proba = xgb_wrapper.predict_proba(X_test)[:, 1]

get_clf_eval(y_test, ws10_preds, ws10_pred_proba)
[0]	validation_0-logloss:0.65016	validation_1-logloss:0.66183
[1]	validation_0-logloss:0.61131	validation_1-logloss:0.63609
[2]	validation_0-logloss:0.57563	validation_1-logloss:0.61144
... ... ...
[92]	validation_0-logloss:0.03152	validation_1-logloss:0.25918
[93]	validation_0-logloss:0.03107	validation_1-logloss:0.25864
[94]	validation_0-logloss:0.03049	validation_1-logloss:0.25951
... ... ...
[101]	validation_0-logloss:0.02751	validation_1-logloss:0.25955
[102]	validation_0-logloss:0.02714	validation_1-logloss:0.25901
[103]	validation_0-logloss:0.02668	validation_1-logloss:0.25991

오차 행렬
[[34  3]
 [ 3 74]]
정확도:0.9474, 정밀도:0.9610, 재현율:0.9610,           F1:0.9610, AUC:0.9933

103번째 반복까지 수행된 후 학습이 종료했는데, 103번째 반복에서 검증 데이터 세트의 성능 평가인 validation_1-logloss가 0.25991, 93번째 반복의 validation_1-logloss가 0.25864로서 10번 반복하는 동안 성능 평가 지수가 향상되지 못해서 더 이상 수행하지 않고 학습이 종료됐다. 이렇게 학습된 모델로 예측한 결과 정확도는 0.9474로 early_stopping_rounds=50일 때의 0.9561보다 낮다.

 

파이썬 래퍼 XGBoost와 마찬가지로 피처의 중요도를 시각화하는 plot_importance()를 통해 피처의 중요도를 시각화할 수 있다. 

from xgboost import plot_importance
import matplotlib.pyplot as plt
%matplotlib inline

fig, ax = plt.subplots(figsize=(10,12))
plot_importance(xgb_wrapper, ax=ax)


Reference

https://dining-developer.tistory.com/3?category=929228 

 

XGBoost (1) - 입문용 예제로 개념 쉽게 이해하기

요즘 현업에서 자주 사용하는 모델 중 하나가 XGBoost이다. 개인적으로 내 업무는 Data Scientist보다 Data Engineer에 가까워서 모델에 관해 심도 깊은 이해는 필요 없지만, 어느 정도의 이해는 필요하다

dining-developer.tistory.com

(1), (2), (3), (4) 참고

'파이썬 머신러닝 완벽가이드 > [4장] 분류' 카테고리의 다른 글

LightGBM  (0) 2023.02.19
부스팅 정리  (0) 2023.02.15
XGBoost(eXtra Gradient Boost) (1)  (0) 2023.02.12
부스팅  (0) 2023.02.10
배깅  (0) 2023.02.09

●XGBoost는 트리 기반의 앙상블 학습에서 가장 각광받고 있는 알고리즘 중 하나로 일반적으로 다른 머신러닝보다 뛰어난 예측 성능을 나타낸다.

●XGBoost는 GBM에 기반하고 있지만, GBM의 단점인 느린 수행 시간 및 과적합 규제 기능이 없는 등의 문제를 해결해서 매우 각광을 받고 있다.(GBM에 과적합 규제 기능을 추가한 것)

 

<XGBoost의 장점>

1) 뛰어난 예측 성능

분류와 회귀에서 뛰어난 예측 성능을 발휘

 

2) GBM 대비 빠른 수행 시간

GBM은 순차적으로 weak learner가 가중치를 증감하는 방법으로 학습하기 때문에 전반적으로 속도가 느리다. 하지만 XGBoost는 병렬 수행으로 GBM에 비해 빠른 수행 성능을 보장한다.

XGBoost가 GBM에 비해 수행 시간이 빠르다는 것이지, 다른 머신러닝 알고리즘(예를 들어 랜덤포레스트)에 비해서 빠르다는 의미는 아니다.

 

3) 과적합 규제(Regularization)

GBM의 경우 과적합 규제 기능이 없으나 XGBoost는 자체에 과적합 규제 기능으로 과적합에 좀 더 강한 내구성을 가진다.

 

4) Tree pruning(나무 가지치기)

GBM과 마찬가지로 XGBoost도 max_depth 파라미터로 분할 깊이를 조정하기도 하지만, Tree pruning으로 더 이상 긍정 이득이 없는 분할을 가지치기 해서 분할 수를 더 줄이는 추가적인 장점을 가지고 있다.

 

5) 자체 내장된 교차검증

XGBoost는 반복 수행 시마다 내부적으로 교차검증을 수행해 최적화된 반복 수행 횟수를 가질 수 있다.

→지정된 반복 횟수가 아니라 교차검증을 통해 비용 평가 값이 최적화되면 반복을 중간에 멈출 수 있는 조기 중단 기능이 있다.

 

6) 결측값 자체 처리

XGBoost는 결측값을 자체 처리할 수 있는 기능을 가지고 있다.


<XGBoost 파이썬 구현>

초기의 독자적인 XGBoost 프레임워크 기반의 XGBoost를 파이썬 래퍼 XGBoost 모듈, 사이킷런과 연동되는 모듈을 사이킷런 래퍼 XGBoost 모듈이 존재한다.

 

사이킷런 래퍼 XGBoost 모듈은 사이킷런의 다른 estimator와 사용법이 같은 데 반해 파이썬 래퍼 XGBoost 모듈은 고유의 API와 하이퍼 파라미터를 이용한다.

 

# 파이썬 래퍼 XGBoost 모듈과 사이킷런 래퍼 XGBoost 모듈 API 비교

 

# XGBoost의 하이퍼 파라미터

일반 파라미터

: 일반적으로 실행 시 스레드의 개수나 silent 모드 등의 선택을 위한 파라미터, default값을 바꾸는 일은 거의 없음

부스트 파라미터

: 트리 최적화, 부스팅, regularization 등과 관련된 파라미터를 지칭

학습 태스크 파라미터

: 학습 수행 시의 객체함수, 평가를 위한 지표 등을 설정하는 파라미터


<과적합 문제가 심각할 경우 : 과적합 제어>

●eta 값을 낮춤(0.01 ~ 0.1) → eta 값을 낮출 경우 num_boost_rounds(n_estimators)는 반대로 높여줘야 한다.

●max_depth 값을 낮춤

●min_child_weight 값을 높임

●gamma(min_split_loss) 값을 높임

●sub_sample(subsample), colsample_bytree 값을 낮춤 → 트리가 너무 복잡하게 생성되는 것을 막아 과적합 문제에 도움이 될 수 있음


XGBoost 자체적으로 교차검증, 성능 평가, 피처 중요도 등의 시각화 기능을 가지고 있다. 또한 XGBoost는 GBM에서 부족한 다른 여러 가지 성능 향상 기능이 있다. 그중에 수행 속도를 향상시키기 위한 대표적인 기능으로 조기 중단(Early Stopping) 기능이 있다.

GBM의 경우 num_boost_rounds(n_estimators)에 지정된 횟수만큼 반복적으로 학습 오류를 감소시키며 학습을 진행하면서 중간에 반복을 멈출 수 없고 num_boost_rounds(n_estimators)에 지정된 횟수를 다 완료해야 한다. 하지만 XGBoost, LightGBM은 모두 조기 중단 기능이 있어서 n_estimators에 지정한 부스팅 반복 횟수에 도달하지 않더라도 예측 오류가 더 이상 개선되지 않으면 반복을 끝까지 수행하지 않고 중지해 수행 시간을 개선할 수 있다.

 

<XGBoost 조기 중단 기능(Early Stopping)>

●XGBoost는 특정 반복 횟수 만큼 더 이상 비용함수가 감소하지 않으면 지정된 반복횟수를 다 완료하지 않고 수행을 종료할 수 있음

●학습을 위한 시간을 단축 시킬 수 있음. 특히 최적화 튜닝 단계에서 적절하게 사용 가능

●너무 반복 횟수를 단축할 경우 예측 성능 최적화가 안된 상태에서 학습이 종료 될 수 있으므로 유의 필요

●조기 중단 설정을 위한 주요 파라미터

▷early_stopping_rounds: 더 이상 비용 평가 지표가 감소하지 않는 최대 반복횟수

▷eval_metric: 반복 수행 시 사용하는 비용 평가 지표

▷evals(eval_set): 평가를 수행하는 별도의 검증 데이터 세트. 일반적으로 검증 데이터 세트에서 반복적으로 비용 감소 성능 평가(파이썬 래퍼: evals / 사이킷런 래퍼: eval_set)

 

예를 들어 n_estimators를 200으로 설정하고 조기 중단 파라미터 값을 50으로 설정하면, 1부터 200회까지 부스팅을 반복하다가 50회를 반복하는 동안 학습 오류가 감소하지 않으면 더 이상 부스팅을 진행하지 않고 종료한다.(가령 100회에서 학습 오류 값이 0.8인데, 101~150회 반복하는 동안 예측 오류가 0.8보다 작은 값이 하나도 없으면 부스팅을 종료한다)


파이썬 래퍼 XGBoost 적용 - 위스콘신 유방암 예측

위스콘신 유방암 데이터 세트는 종양의 크기, 모양 등의 다양한 속성값을 기반으로 악성 종양(malignant)인지 양성 종양(benign)인지를 분류한 데이터 세트이다. 

위스콘신 유방암 데이터 세트에 기반해 종양의 다양한 피처에 따라 악성 종양(malignant)인지 양성 종양(benign)인지를 XGBoost를 이용해 예측해보자!

import xgboost as xgb
from xgboost import plot_importance
import pandas as pd
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings('ignore')

dataset = load_breast_cancer()
features = dataset.data
labels = dataset.target

cancer_df = pd.DataFrame(data=features, columns=dataset.feature_names)
cancer_df['target'] = labels
cancer_df.head(3)

# 레이블 분포 확인
print(dataset.target_names)
print(cancer_df['target'].value_counts())

위스콘신 유방암 데이터 세트의 80%를 학습용, 20%를 테스트용으로 추출한 뒤 이 80%의 학습용 데이터에서 90%를 최종 학습용, 10%를 검증용으로 분할한다. 검증용 데이터 세트를 별도로 분할하는 이유는 XGBoost가 제공하는 기능인 검증 성능 평가와 조기 중단(early stopping)을 수행하기 위해서이다.

# 피처와 레이블 구분
X_features = cancer_df.iloc[:, :-1]
y_label = cancer_df.iloc[:, -1]

# 전체 데이터 중 80%는 학습용 데이터, 20%는 테스트용 데이터 추출
X_train, X_test, y_train, y_test = train_test_split(X_features, y_label, test_size=0.2, random_state=156)

# 위에서 만든 X_train, y_train을 다시 쪼개서 90%는 학습과 10%는 검증용 데이터로 분리
X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train, test_size=0.1, random_state=156)

print(X_train.shape, X_test.shape)
print(X_tr.shape, X_val.shape)

전체 569개의 데이터 세트에서 최종 학습용 409개, 검증용 46개, 테스트용 114개가 추출됨

 

파이썬 래퍼 XGBoost만의 전용 데이터 객체인 DMatrix를 사용해야 한다. 때문에 numpy, pandas로 되어 있는 학습, 검증, 테스트용 데이터 세트를 모두 전용의 데이터 객체인 DMatrix로 생성하여 모델에 입력해 줘야 한다. 

(파이썬 래퍼 XGBoost 초기 버전은 주로 넘파이를 입력 파라미터를 받아서 DMatrix를 생성하였지만, 현 버전은 넘파이 외에도 DataFrame, Series 기반으로도 DMatrix를 생성할 수 있음→초기 버전은 판다스의 DataFrame과 호환되지 않아서 DMatrix 생성 시 오류가 발생할 경우 DataFrame.values를 이용해 넘파이로 일차 변환한 뒤에 이를 이용해 Dmatrix 변환을 적용해야 함)

DMatrix의 주요 입력 파라미터는 data와 label로 data는 피처 데이터 세트, label은 분류의 경우 레이블 데이터 세트, 회귀의 경우 숫자형인 종속값 데이터 세트이다.

# 학습, 검증, 테스트용 DMatrix 생성
dtr = xgb.DMatrix(data=X_tr, label=y_tr)
dval = xgb.DMatrix(data=X_val, label=y_val)
dtest = xgb.DMatrix(data=X_test, label=y_test)

파이썬 래퍼 XGBoost로 학습을 수행하기 전에 먼저 XGBoost의 하이퍼 파라미터를 딕셔너리 형태로 설정하자.

params = {'max_depth':3,
         'eta':0.05,
         'objective':'binary:logistic',
         'eval_metric':'logloss'
         }
num_rounds = 400

파이썬 래퍼 XGBoost는 하이퍼 파라미터를 xgboost 모듈의 train() 함수에 파라미터로 전달한다.(사이킷런 래퍼 XGBoost의 경우 Estimator의 생성자를 하이퍼 파라미터로 전달)

 

조기 중단(Early Stopping)의 성능 평가는 별도의 검증 데이터 세트를 이용한다. XGBoost는 학습 반복 시마다 검증 데이터 세트를 이용해 성능을 평가할 수 있는 기능을 제공한다. 조기 중단은 xgboost의 train() 함수에 early_stopping_rounds 파라미터를 입력하여 설정한다. 여기서는 조기 중단 할 수 있는 최대 반복 횟수를 50으로 설정하겠다.

 

early_stopping_rounds 파라미터를 설정해 조기 중단을 수행하기 위해서는 반드시 평가용 데이터 세트 지정과 eval_metric을 함께 설정해야 한다. XGBoost는 반복마다 지정된 평가용 데이터 세트에서 eval_metric의 지정된 평가 지표로 예측 오류를 측정한다.

●평가용 데이터 세트는 학습과 평가용 데이터 세트를 명기하는 개별 튜플을 가지는 리스트 형태로 설정. 가령 dtr이 학습용, dval이 평가용이라면 [(dtr, 'train'), (dval, 'evel')]와 같이, 학습용 DMatrix는 'train'으로, 평가용 DMatrix는 'eval'로 개별 튜플에서 명기하여 설정(과거 버전 XGBoost는 위와 같이 학습 데이터 세트와 평가용 데이터 세트를 명기해주어야 했으나 현재 버전은 평가용 데이터 세트만 명기해 줘도 성능 평가를 수행하므로 [(dval, 'eval')]로 설정해도 무방

●eval_metric은 평가 세트에 적용할 평가 지표. 분류일 경우 주로 'error'(분류 오류), 'logloss'를 적용

# 학습 데이터 셋은 'train', 평가 데이터 셋은 'eval'로 명기
eval_list = [(dtr, 'train'), (dval, 'eval')] # eval_list = [(dval, 'eval')]

xgb_model = xgb.train(params=params, dtrain=dtr, num_boost_round=num_rounds,
                     early_stopping_rounds=50, evals=eval_list)
[0]	train-logloss:0.65016	eval-logloss:0.66183
[1]	train-logloss:0.61131	eval-logloss:0.63609
[2]	train-logloss:0.57563	eval-logloss:0.61144
... ... ...
[125]	train-logloss:0.01998	eval-logloss:0.25714
[126]	train-logloss:0.01973	eval-logloss:0.25587
[127]	train-logloss:0.01946	eval-logloss:0.25640
[128]	train-logloss:0.01927	eval-logloss:0.25685
... ... ...
[175]	train-logloss:0.01267	eval-logloss:0.26086
[176]	train-logloss:0.01258	eval-logloss:0.26103

train()으로 학습을 수행하면서 반복 시마다 train-logloss와 eval-logloss가 지속적으로 감소하고 있다. 하지만 num_boost_round를 400회로 설정했음에도 불구하고 학습은 400번을 반복하지 않고 0부터 시작하여 176번째 반복에서 완료했음을 알 수 있다. 출력 결과에서 126번째 반복에서 eval-logloss로 표시되는 검증 데이터에 대한 logloss 값이 0.25587로 가장 낮다. 이후 126번에서 176번까지 early_stopping_rounds로 지정된 50회 동안 logloss 값은 이보다 향상되지 않았기 때문에(logloss가 작을수록 성능이 좋음. 즉, 예측 오류가 작을수록 성능이 좋음) 더 이상 반복하지 않고 멈춘 것이다.

 

xgboost를 이용해 모델의 학습이 완료했으니 이를 이용해 테스트 데이터 세트에 예측을 수행해보자.

한 가지 유의할 점은 사이킷런 래퍼 XGBoost의 predict() 메서드는 예측 결과 클래스 값을 반환하는 데 파이썬 래퍼 XGBoost의 predict()는 예측 결과값이 아닌 예측 결과를 추정할 수 있는 확률값을 반환한다. 

본 실습은 암이 악성인지, 양성인지를 판단하는 이진 분류이므로 예측 확률이 0.5보다 크면 1, 그렇지 않으면 0으로 예측값을 결정하는 로직을 추가하면 된다.

pred_probs = xgb_model.predict(dtest)
print('predict() 수행 결과값을 10개만 표시, 예측 확률값으로 표시됨')
print(np.round(pred_probs[:10], 3))

# 예측 혹률이 0.5보다 크면 1, 그렇지 않으면 0으로 예측값 결정하여 list 객체인 preds에 저장
preds = [1 if x > 0.5 else 0 for x in pred_probs]
print('예측값 10개만 표시:', preds[:10])

XGBoost 모델의 예측 성능을 평가해 보자.

from sklearn.metrics import *

def get_clf_eval(y_test, pred=None, pred_proba=None):
    confusion = confusion_matrix(y_test, pred)
    accuracy = accuracy_score(y_test, pred)
    precision = precision_score(y_test, pred)
    recall = recall_score(y_test, pred)
    f1 = f1_score(y_test, pred)
    roc_auc = roc_auc_score(y_test, pred_proba)
    print('오차 행렬')
    print(confusion)
    print('정확도:{0:.4f}, 정밀도:{1:.4f}, 재현율:{2:.4f}, \
          F1:{3:.4f}, AUC:{4:.4f}'.format(accuracy, precision, recall, f1, roc_auc))
get_clf_eval(y_test, preds, pred_probs)

xgboost 패키지에 내장된 시각화 기능인 plot_importance() API는 피처의 중요도를 시각화해준다.

f스코어는 해당 피처가 트리 분할 시 얼마나 자주 사용되었는지를 지표로 나타낸 값으로, f스코어를 기반으로 해당 피처의 중요도를 나타낸다. (사이킷런은 Estimator 객체의 feature_importances_ 속성을 이용해 직접 시각화 코드를 작성해야 하지만, xgboost 패키지는 plot_importance()를 이용해 바로 피처 중요도를 시각화할 수 있다.) 

 

plot_importance() 호출 시 파라미터로 앞에서 학습이 완료된 모델 객체, 맷플롯립의 ax 객체를 입력한다.

plot_importance() 이용 시 유의할 점은 xgboost를 DataFrame이 아닌 넘파이 기반의 피처 데이터로 학습하면 넘파이에서 피처명을 제대로 알 수가 없으므로 Y축의 피처명을 나열 시 f0, f1과 같이 피처 순서별로 f자 뒤에 순서를 붙여서 피처명을 나타낸다.(f0는 첫 번째 피처, f1은 두 번째 피처)

import matplotlib.pyplot as plt
%matplotlib inline

fig, ax = plt.subplots(figsize=(10,12))
plot_importance(xgb_model, ax=ax)


파이썬 래퍼 XGBoost의 교차검증 수행 및 최적 파라미터 구하기

: 사이킷런의 GridSearchCV와 유사하게 교차검증 수행 및 최적 파라미터를 구할 수 있는 cv() API를 제공한다.

xgboost.cv(params, dtrain, num_boost_round=10, nfold=3, stratified=False, folds=None, metrics=(), obj=None, feval=None, maximize=False, early_stopping_rounds=None, fpreproc=None, as_pandas=True, verbose_eval=None, show_stdv=True, seed=0, callbacks=None, shuffle=True)

●params: 부스터 파라미터(딕셔너리 형태)

●dtrain: 학습 데이터(DMatrix 형태)

●num_boost_round: 부스팅 반복 횟수(생성할 약한 학습기 수)

●nfold: cv 폴드 수

●stratified: cv 수행 시 층화 표본 추출 수행 여부(bool 형태, 원본 데이터의 레이블 비율을 활용할것인지)

●metrics: cv 수행 시 모니터링할 성능 평가 지표(string 또는 list of strings 형태)

●early_stopping_rounds: 조기 중단을 활성화시킴. 반복 횟수 지정

 

→xgb.cv의 반환값은 DataFrame 형태

'파이썬 머신러닝 완벽가이드 > [4장] 분류' 카테고리의 다른 글

부스팅 정리  (0) 2023.02.15
XGBoost(eXtra Gradient Boost) (2)  (0) 2023.02.14
부스팅  (0) 2023.02.10
배깅  (0) 2023.02.09
앙상블 학습  (0) 2023.02.09

●부스팅은 여러 개의 약한 학습기(weak learner)를 순차적으로 학습-예측하면서 잘못 예측한 데이터에 가중치 부여를 통해 오류를 개선해 나가면서 학습하는 방식

●부스팅의 대표적인 구현은 AdaBoost(Adaptive boosing)와 그래디언트 부스트가 있음

 

<AdaBoost>

오류 데이터에 가중치를 부여하면서 부스팅을 수행하는 대표적인 알고리즘

맨 왼쪽 그림과 같이 +와 -로 된 피처 데이터 세트가 있다면

●step 1: 첫 번째 약한 학습기가 분류 기준 1로 +와 -를 분류한 것이다. 오른쪽에 + 데이터는 잘못 분류된 오류 데이터이다.

●step 2: 이 오류 데이터에는 다음 약한 학습기가 잘 분류할 수 있게 가중치 값을 부여한다.

●step 3: 두 번째 약한 학습기가 분류 기준 2로 +와 -를 분류하고, 왼쪽의 -는 잘못 분류된 오류 데이터이다.

●step 4: 이 오류 데이터에는 다음 약한 학습기가 잘 분류할 수 있게 가중치 값을 부여한다.

●step 5: 세 번째 약한 학습기가 분류 기준 3으로 +와 -를 분류하고 오류 데이터를 찾는다. 에이다 부스트는 이렇게 약한 학습기가 순차적으로 오류 값에 대해 가중치를 부여한 예측 결정 기준을 모두 결합해 예측을 수행한다.

●마지막은 모든 약한 학습기를 결합한 결과 예측이다. 하나의 약한 학습기들보다 훨씬 정확도가 올라갔다.

 

<GBM(Gradient Boost Machine)>

GBM도 에이다부스트와 유사하나, 가중치 업데이트를 경사 하강법(Gradient Descent)을 이용하는 것이 큰 차이이다.

 

오류 값은 실제값-예측값이다. 분류의 실제 결과값을 y, 피처를 x1, x2, ..., xn 그리고 이 피처에 기반한 예측 함수를 F(x) 함수라고 하면 오류식 h(x) = y - F(x)를 최소화하는 방향성을 가지고 반복적으로 가중치 값을 업데이트하는 것이 경사 하강법(Gradient Descent)이다.

 

경사 하강법은 반복 수행을 통해 오류를 최소화할 수 있도록 가중치의 업데이트 값을 도출하는 기법으로서 머신러닝에서 중요한 기법 중 하나이다.

 

 

<GBM 하이퍼 파라미터>

사이킷런은 GBM 분류를 위해 GradientBoostingClassifier 클래스를 제공한다.(회귀의 경우 GradientBoostingRegressor)

 

n_estimators, max_features, max_depth, min_samples_leaf, min_samples_split과 같은 트리 기반의 자체의 파라미터는 결정트리, 랜덤 포레스트에서 이미 소개했으므로 생략하고 나머지 다르 파라미터에 대해 설명하겠다.

 

●loss: 경사 하강법에서 사용할 비용 함수를 지정한다. 특별한 이유가 없으면 default인 'deviance'를 그대로 적용한다.

●learning_rate: GBM이 학습을 진행할 때마다 적용하는 학습률이다. weak learner가 순차적으로 오류값을 보정해 나가는 데 적용하는 계수이다.('하강하는 보폭' 이라고 생각)

→0~1 사이의 값을 지정할 수 있으며 기본값은 0.1이다. 너무 작은 값을 적용하면 업데이트 되는 값이 작아져서 최소 오류 값을 찾아 예측 성능이 높아질 가능성이 높지만 많은 weak learner는 순차적인 반복이 필요해서 수행 시간이 오래 걸리고, 모든 weak learner의 반복이 완료되어도 최소 오류 값을 찾지 못할 수 있다. 반대로 큰 값을 적용하면 최소 오류 값을 찾지 못하고 그냥 지나쳐 버려 예측 성능이 떨어질 가능성이 있지만 빠른 수행이 가능하다. 이러한 특성때문에 learning_rate는 n_estimators와 상호 보완적으로 조합해 사용하는데 작은 learning_rate를 사용하고 n_estimator를 크게 하면 최고의 성능을 보일 수 있는 지점까지 학습이 가능하겠지만 수행 시간이 너무 오래 걸리고 예측 성능도 들이는 시간 만큼 현격하게 좋아지지는 않는다.

●n_estimators: weak learner의 개수이다. weak learner가 순차적으로 오류를 보정하므로 개수가 많을수록 예측 성능이 일정 수준까지는 좋아질 수 있다. 하지만 개수가 많을수록 수행 시간이 오래 걸린다. default는 100이다.

●subsample: weak learner가 학습에 사용하는 데이터의 샘플링 비율이다. default는 1이며, 이는 전체 학습 데이터를 기반으로 학습한다는 의미이다(0.5이면 학습 데이터의 50%). 과적합이 염려되는 경우 subsample을 1보다 작은 값으로 설정한다.


마찬가지로 사용자 행동 인식 데이터 세트를 GradientBoostingClassifier를 이용해 예측해 보자. 그리고 GBM으로 학습하는 시간이 얼마나 걸리는지 수행 시간도 같이 측정해본다.

재 수행 할 때마다 동일한 예측 결과를 출력하기 위해 RandomForestClassifier의 random_state는 0으로 설정한다.

from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import accuracy_score
import time
import warnings
warnings.filterwarnings('ignore')

X_train, X_test, y_train, y_test = get_human_dataset()

# GBM 수행 시간 측정을 위함. 시작 시간 설정
start_time = time.time()

gb_clf = GradientBoostingClassifier(random_state=0)
gb_clf.fit(X_train, y_train)
gb_pred = gb_clf.predict(X_test)
gb_accuracy = accuracy_score(y_test, gb_pred)

print('GBM 정확도:{0:.4f}'.format(gb_accuracy))
print('GBM 수행 시간:{0:.1f}초'.format(time.time() - start_time))

default 하이퍼 파라미터만으로 93.89%의 정확도가 나왔다. GBM은 수행 시간이 오래 걸리기 때문에 이 문제는 GBM이 극복해야 할 중요한 과제이다.

*수행 시간이 오래 걸리는 이유: 사이킷런의 GBM은 약한 학습기의 순차적인 예측 오류 보정을 통해 학습을 수행하므로 멀티 CPU 코어 시스템을 사용하더라도 병렬 처리가 지원되지 않아서 대용량 데이터의 경우 학습에 매우 많은 시간이 필요하다.

 

이번에는 GridSearchCV를 이용하여 하이퍼 파라미터 튜닝을 해보겠다.(시간이 너무 오래 걸리는 관계로 결과는 스크래핑)

from sklearn.model_selection import GridSearchCV

params = {
    'n_estimators':[100, 500],
    'learning_rate':[0.05, 0.1]
}
grid_cv = GridSearchCV(gb_clf, param_grid=params, cv=2)
grid_cv.fit(X_train, y_train)
print('최적 하이퍼 파라미터:\n', grid_cv.best_params_)
print('최고 예측 정확도:{0:.4f}'.format(grid_cv.best_score_))

최적 하이퍼 파라미터:

{'n_estimaotrs':500, 'learning_rate':0.05}

최고 예측 정확도:0.9013

 

검증용 데이터 세트에서 예측 성능이 90.13%의 정확도가 나왔고 해당 하이퍼 파라미터로 테스트 데이터 세트에도 적용하여 성능을 확인해보자.

# GridSearchCV를 이용하여 최적으로 학습된 estimator로 predict 수행
gb_pred = grid_cv.best_estimator_.predict(X_test)
gb_accuracy = accuracy_score(y_test, gb_pred)
print('GBM 정확도:{0:.4f}'.format(gb_accuracy))

GBM 정확도: 0.9396


GBM은 과적합에도 강한 뛰어난 예측 성능을 가진 알고리즘이며 수행 시간이 오래걸린다는 단점이 존재한다.

따라서 GBM의 단점을 보완하면서 GBM을 기반한 알고리즘들이 새롭게 만들어지고 있다. 그 중 XGBoost와 LightGBM이 가장 많이 사용되고 있는데 이 두개에 대한 정리는 다음 글에서 소개한다!

<배깅(Bagging) - 랜덤 포레스트(Random Forest)>

 

●배깅의 대표적인 알고리즘은 랜덤 포레스트이다.

●랜덤 포레스트는 다재 다능한 알고리즘으로 앙상블 알고리즘 중 비교적 빠른 수행 속도를 가지고 있으며, 다양한 영역에서 높은 예측 성능을 보이고 있다.

●랜덤 포레스트는 여러 개의 결정 트리 분류기가 전체 데이터에서 배깅 방식으로 각자의 데이터를 샘플링해 개별적으로 학습을 수행한 뒤 최종적으로 모든 분류기가 보팅을 통해 예측 결정을 한다.

●랜덤 포레스트뿐만 아니라 부스팅 기반의 다양한 앙상블 알고리즘은 대부분 결정 트리 알고리즘을 기반 알고리즘으로 채택하고 있다.

 

 

 

 

-랜덤 포레스트의 부트스트래핑 분할

●랜덤 포레스트는 개별적인 분류기의 기반 알고리즘은 결정 트리이지만 개별 트리가 학습하는 데이터 세트는 전체 데이터에서 일부가 중첩되게 샘플링된 데이터 세트이다. 이렇게 여러 개의 데이터 세트를 중첩되게 분리하는 것을 부트스트래핑(bootstrapping) 분할 방식이라고 한다.(그래서 배깅(Bagging)이 bootstrap aggregating의 줄임말이다.)

●랜덤 포레스트의 서브세트 데이터는 이러한 부트스트래핑으로 데이터가 임의로 만들어지고, 서브세트의 데이터 건수는 전체 데이터 건수와 동일하지만, 개별 데이터가 중첩되어 만들어진다.

원본 데이터의 건수가 10개인 학습 데이터 세트에 랜덤 포레스트를 3개의 결정 트리 기반으로 학습하려고 n_estimators=3으로 하이퍼 파라미터를 부여하면 위와 같이 데이터 서브세트가 만들어진다.

 

→이렇게 데이터가 중첩된 개별 데이터 세트에 결정 트리 분류기를 각각 적용하여 학습을 수행한 뒤 최종적으로 모든 분류기가 보팅을 통해 예측 결정을 하는 것이 랜덤 포레스트이다.

 

<랜덤 포레스트 하이퍼 파라미터>

사이킷런은 랜덤 포레스트 분류를 위해 RandomForestClassifier 클래스를 제공한다.(회귀의 경우 RandomForestRegressor)

●n_estimators: 랜덤 포레스트에서 결정 트리의 개수를 지정한다. 디폴트는 100개이다. 많이 설정할수록 좋은 성능을 기대할 수 있지만 계속 증가시킨다고 성능이 무조건 향상되는 것은 아니다. 또한 늘릴수록 학습 수행 시간이 오래 걸리는 것도 감안해야 한다.

●max_features는 결정 트리에 사용된 max_features 파라미터와 같다. 하지만 RandomForestClassifier의 기본 max_features는 'None'이 아니라 'auto', 즉 'sqrt'와 같다. 따라서 랜덤 포레스트의 트리를 분할하는 피처를 참조할 때 전체 피처가 아니라 sqrt만큼 참조한다.(전체 피처가 16개라면 분할을 위해 4개 참조-각각의 결정 트리는 이 4개의 피처가 다를 수 있음) 

●max_depth나 min_samples_leaf, min_samples_split과 같이 결정 트리에서 과적합을 개선하기 위해 사용되는 파라미터가 랜덤 포레스트에도 똑같이 적용될 수 있다.


이전에 사용자 행동 인식 데이터 세트를 RandomForestClassifier를 이용해 예측해 보자. 재 수행 할 때마다 동일한 예측 결과를 출력하기 위해 RandomForestClassifier의 random_state는 0으로 설정한다.

(해당 데이터 세트는 자주 사용되서 DataFrame을 생성하는 로직을 함수로 만들어났음)

import pandas as pd

feature_name_df = pd.read_csv(r'human_acttivity\features.txt', sep='\s+',
                             header=None, names=['column_index', 'column_name'])
def get_new_feature_name_df(old_feature_name_df):
    feature_dup_df = pd.DataFrame(data=feature_name_df.groupby(['column_name']).cumcount(),
                                                              columns=['dup_cnt'])
    feature_dup_df = feature_dup_df.reset_index()
    new_feature_name_df = pd.merge(old_feature_name_df.reset_index(), feature_dup_df, on='index', how='outer')
    new_feature_name_df['column_name'] = new_feature_name_df[['column_name', 'dup_cnt']].apply(lambda x : x[0]+'_'+str(x[1])
                                                                                               if x[1] > 0 else x[0], axis=1)
    new_feature_name_df = new_feature_name_df.drop(['index'], axis=1)
    return new_feature_name_df

def get_human_dataset():
    # 각 데이터 파일은 공백으로 분리되어 있으므로 read_csv에서 공백 문자를 sep로 할당
    feature_name_df = pd.read_csv(r'human_acttivity\features.txt', sep='\s+',
                             header=None, names=['column_index', 'column_name'])
    
    # 중복된 피처명을 수정하는 get_new_feature_name_df()를 이용, 신규 피처명 DataFrame 생성
    new_feature_name_df = get_new_feature_name_df(feature_name_df)
    # DataFrame에 피처명을 컬럼으로 부여하기 위해 리스트 객체로 다시 변환(values로 ndarray 변환 후 tolist()로 리스트 만듬)
    feature_name = new_feature_name_df.iloc[:, 1].values.tolist()
    
    # 학습 피처 데이터와 테스트 피처 데이터를 DataFrame으로 로딩. 컬럼명은 feature_name 적용
    X_train = pd.read_csv(r'human_acttivity\train\X_train.txt', sep='\s+', names=feature_name)
    X_test = pd.read_csv(r'human_acttivity\test\X_test.txt', sep='\s+', names=feature_name)
    
    # 학습 레이블 데이터와 테스트 레이블 데이터를 DataFrame으로 로딩. 컬럼명은 action으로 부여
    # (pd.read_csv() 할때 names를 할당했기 때문에 굳이 header=None 입력안해도 무방한듯)
    y_train = pd.read_csv(r'human_acttivity\train\y_train.txt', sep='\s+', header=None, names=['action']) 
    y_test = pd.read_csv(r'human_acttivity\test\y_test.txt', sep='\s+', header=None, names=['action']) 
    
    # 로드된 학습/테스트용 DataFrame을 모두 반환
    return X_train, X_test, y_train, y_test
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import warnings
warnings.filterwarnings('ignore')

X_train, X_test, y_train, y_test = get_human_dataset()

# 랜덤 포레스트 학습 및 별도의 테스트 데이터 세트로 예측 성능 평가
rf_clf = RandomForestClassifier(n_estimators=100, random_state=0, max_depth=8) # n_esitmator 디폴트 값은 100
rf_clf.fit(X_train, y_train)
pred = rf_clf.predict(X_test)
accuracy = accuracy_score(y_test, pred)
print('랜덤 포레스트 정확도:{0:.4f}'.format(accuracy))

이번에는 GridSearchCV를 이용해 랜덤 포레스트의 하이퍼 파라미터를 튜닝해보자. 튜닝 시간을 절약하기 위해 n_estimators는 100, cv는 2로만 설정해 최적의 하이퍼 파라미터를 구함.

그리고 n_jobs=-1 파라미터를 추가하면 모든 CPU 코어를 이용해 학습할 수 있다.

from sklearn.model_selection import GridSearchCV

params = {'max_depth':[8, 16, 24],
         'min_samples_leaf':[1, 6, 12],
         'min_samples_split':[2, 8, 16]
}
# RandomForestClassifier 객체 생성 후 GridSearchCV 수행
rf_clf = RandomForestClassifier(n_estimators=100, random_state=0, n_jobs=-1)
grid_cv = GridSearchCV(rf_clf, param_grid=params, cv=2, n_jobs=-1)
grid_cv.fit(X_train, y_train)

print('최적 하이퍼 파라미터:\n', grid_cv.best_params_)
print('최고 예측 정확도:{0:.4f}'.format(grid_cv.best_score_))

max_depth:16, min_samples_leaf:6, min_samples_split:2일 때 2개의 cv 세트에서 91.65%의 평균 정확도가 측정됐다.(검증용 데이터 세트에서 예측 성능 측정)

이렇게 추출된 최적의 하이퍼 파라미터로 다시 RandomForestClassifier를 학습시킨 뒤에 이번에는 별도의 테스트 데이터 세트에서 예측 성능을 측정해보자.

rf_clf1 = RandomForestClassifier(n_estimators=100, max_depth=16, min_samples_leaf=6,
                                min_samples_split=2, random_state=0)
rf_clf1.fit(X_train, y_train)
pred = rf_clf1.predict(X_test)
print('예측 정확도:{0:.4f}'.format(accuracy_score(y_test, pred)))

별도의 테스트 데이터 세트에서 수행한 예측 정확도는 92.60%이다. 

RandomForestClassifier도 DecisionTreeClassifier와 똑같이 feature_importances_ 속성을 이용해 알고리즘이 선택한 피처의 중요도를 알 수 있다.(Tree 기반 알고리즘은 feature_importances_ 속성을 가짐)

import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

# 피처 중요도가 높은 상위 20개
ftr_importances_values = rf_clf1.feature_importances_
ftr_importances = pd.Series(ftr_importances_values, index=X_train.columns)
ftr_top20 = ftr_importances.sort_values(ascending=False)[:20]

plt.figure(figsize=(8,6))
plt.title('Feature importances Top 20')
sns.barplot(x=ftr_top20, y=ftr_top20.index)
plt.show()

●분류와 회귀를 둘 다 지원한다.

 

앙상블(ensemble)은 프랑스어로 '같이', '함께' 뭐 이런 뜻이다. 즉, 앙상블 학습을 통한 분류는 여러 개의 분류기를 생성하고 그 예측을 결합함으로써 보다 정확한 최종 예측을 도출하는 기법이다.

 

쉽게 생각해 어려운 문제의 결론을 내기 위해 여러 명의 전문가로 위원회를 구성해 다양한 의견을 수렴하고 결정하듯이 앙상블 학습의 목표는 다양한 분류기의 예측 결과를 결합함으로써 단일 분류기보다 신뢰성이 높은 예측값을 얻는 것이다.

→비슷한 놈들의 집합. 정도로 생각하면 편하겠다.

 

<앙상블의 특징>

●단일 모델의 약점을 다수의 모델들을 결합하여 보완

●뛰어난 성능을 가진 모델들로만 구성하는 것보다 성능이 떨어지더라도 서로 다른 유형의 모델을 섞는 것이 오히려 전체 성능에 도움이 될 수 있음

●배깅의 랜덤 포레스트 및 뛰어난 부스팅 알고리즘들은 모두 결정 트리 알고리즘을 기반 알고리즘으로 적용함

●결정 트리의 단점인 과적합(overfitting)을 수십~수천개의 많은 분류기를 결합해 보완하고 장점인 직관적인 분류 기준은 강화됨

 

<앙상블 유형>

●앙상블의 유형은 일반적으로 보팅(Voting), 배깅(Bagging), 부스팅(Boosting)으로 구분할 수 있으며, 이외에 스태킹(Stacking)등의 기법이 있다.

●대표적인 배깅은 랜덤 포레스트(Random Forest)알고리즘이 있으며, 부스팅은 에이다 부스팅, 그래디언트 부스팅, XGBoost, LightGBM 등이 있다. 정형 데이터의 분류나 회귀에서는 GBM 부스팅 계열의 앙상블이 전반적으로 높은 예측 성능을 나타낸다.(이미지, 영상, 음성 등의 비정형 데이터의 분류나 회귀는 딥러닝이 뛰어난 성능을 보임)

 

1) 보팅(Voting)과 배깅(Bagging)

●보팅과 배깅은 여러 개의 분류기가 투표를 통해 최종 예측 결과를 결정하는 방식이다.

●보팅과 배깅의 다른점은 보팅의 경우 일반적으로 서로 다른 알고리즘을 가진 분류기를 결합하는 것이고, 배깅의 경우 각각의 분류기가 모두 같은 유형의 알고리즘 기반이지만, 부트스트래핑(Bootstrapping) 방식으로 데이터 샘플링을 서로 다르게 가져가면서 학습을 수행해 보팅을 수행 하는 것이다.

 

*부트스트래핑(Bootstrapping)은 데이터에서 단순랜덤 복원추출 방법을 활용하여 동일한 크기의 표본을 여러개 생성하는 샘플링 방법이다. (복원추출이기 때문에 중첩을 허용하므로 표본마다 중첩된 데이터가 있다.)

2) 부스팅(Boosting)

부스팅은 여러 개의 분류기가 순차적으로 학습을 수행하되, 앞에서 학습한 분류기가 예측이 틀린 데이터에 대해서는 올바르게 예측할 수 있도록 다음 분류기에게는 가중치(weight)를 부여하면서 학습과 예측을 진행하는 것이다.

 

3) 스태킹(stacking)

스태킹은 여러 가지 다른 모델의 예측 결과값을 다시 학습 데이터로 만들어서 다른 모델(메타 모델)로 재학습시켜 결과를 예측하는 방법


<보팅 유형 - 하드 보팅(Hard Voting)과 소프트 보팅(Soft Voting)>

●일반적으로 하드 보팅보다는 소프트 보팅이 예측 성능이 상대적으로 우수하여 주로 사용됨

●사이킷런은 VotingClassifier 클래스를 통해 보팅(Voting)을 지원(회귀의 경우 VotingRegressor)


지금부터 보팅 방식의 앙상블을 이용해 위스콘신 유방암 데이터 세트를 예측 분석해보자. 위스콘신 유방암 데이터 세트는 유방암의 악성종양, 양성종양 여부를 결정하는 이진 분류 데이터 세트이며 종양의 크기, 모양 등의 형태와 관련한 많은 피처를 가지고 있다. 사이킷런은 load_breast_cancer() 함수를 통해 자체에서 위스콘신 유방암 데이터 세트를 생성할 수 있다.

import pandas as pd

from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

cancer = load_breast_cancer()

data_df = pd.DataFrame(cancer.data, columns=cancer.feature_names)
data_df.head(3)

로지스틱 회귀와 KNN을 기반으로 하여 소프트 보팅 방식으로 보팅 분류기를 만들자.

VotingClassifier 클래스는 주요 생성 인자로 estimators와 voting 값을 입력받는다. estimators는 리스트 값으로 보팅에 사용될 여러 개의 Classifier 객체들을 튜플 형식으로 입력받으며 voting은 'hard'시 하드 보팅, 'soft'시 소프트 보팅 방식을 적용한다.(디폴트는 'hard'이다.)

# 개별 모델은 로지스틱 회귀와 KNN
lr_clf = LogisticRegression(solver='liblinear')
knn_clf = KNeighborsClassifier(n_neighbors=8)

# 개별 모델을 소프트 보팅 기반의 앙상블 모델로 구현한 분류기
vo_clf = VotingClassifier(estimators=[('LR', lr_clf), ('KNN', knn_clf)], voting='soft')

X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target,
                                                   test_size=0.2, random_state=156)

# VotingClassifier 학습/예측/평가
vo_clf.fit(X_train, y_train)
pred = vo_clf.predict(X_test)
print('Voting 분류기 정확도:{0:.4f}'.format(accuracy_score(y_test, pred)))

# 개별 모델의 학습/예측/평가
classifiers = [lr_clf, knn_clf]
for classifier in classifiers:
    classifier.fit(X_train, y_train)
    pred = classifier.predict(X_test)
    class_name = classifier.__class__.__name__
    print('{0} 정확도:{1:.4f}'.format(class_name, accuracy_score(y_test, pred)))

보팅 분류기가 정확도가 조금 높게 나타났는데, 보팅으로 여러 개의 기반 분류기를 결합한다고 해서 무조건 기반 분류기보다 예측 성능이 향상되지는 않는다. 데이터의 특성과 분포 등 다양한 요건에 따라 오히려 기반 분류기 중 가장 좋은 분류기의 성능이 보팅했을 때보다 나을 수도 있다.

'파이썬 머신러닝 완벽가이드 > [4장] 분류' 카테고리의 다른 글

부스팅  (0) 2023.02.10
배깅  (0) 2023.02.09
결정 트리 실습 - 사용자 행동 인식 데이터 세트  (0) 2023.02.09
결정 트리(Decision Tree)  (0) 2023.02.04
분류(Classification)의 개요  (0) 2023.02.04

결정 트리 실습 - 사용자 행동 인식 데이터 세트.ipynb
0.10MB

'파이썬 머신러닝 완벽가이드 > [4장] 분류' 카테고리의 다른 글

부스팅  (0) 2023.02.10
배깅  (0) 2023.02.09
앙상블 학습  (0) 2023.02.09
결정 트리(Decision Tree)  (0) 2023.02.04
분류(Classification)의 개요  (0) 2023.02.04

+ Recent posts