본문 바로가기

Euron/정리

[Week3] 04. 분류(2)

중간에 모듈 설치하면서 오류가 났다.

anaconda가 원래 오류가 많이 난다고 한다....그럴 때는 pip로 주피터 노트북에 직접 설치하기.

오류찾기 힘들어서 중간에 colab으로 해볼까...했는데 운영진의 도움으로 해결했다.


05. GBM(Gradient Boosting Machine)

 

부스팅 알고리즘은 여러 약한 학습기를 순차적으로 학습-예측하면서 잘못 예측한 데이터에 가중치 부여를 통해 오류를 개선해 나가면서 학습하는 방식이다.

대표적으로 에이다 부스트(AdaBoost)와 그래디언트 부스트(Gradient Boost)가 있다.

두 방법 모두 오류 데이터에 가중치를 부여하며 업데이트 하지만, GBM은 경사 하강법(Gradient Descent)을 이용한다.

실제 결괏값=y, 피처 = x, 피처에 기반한 예측 함수 = F(x)라 할때,

오류식 H(x) = y -F(x)가 된다. 이 오류식을 최소화하는 방향성을 가지고 반복적으로 가중치 값을 업데이트하는 것이 결사 하강법이다.

 

사이킷런은 GBM 기반의 분류를 위해 GradientBoostingClassifier 클래스를 제공한다.

from sklearn.ensemble import GradientBoostingClassifier
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))

일반적으로 GBM이 랜덤 포레스트보다는 예측 성능이 뛰어난 경우가 많지만...수행시간이 너무 오래걸린다.

내 노트북에서는 10분 이상 걸려서 결국 결과는 볼 수 없었다.

여기에 하이퍼 파라미터 튜닝 노력도 더 필요하다.

 

GBM 하이퍼 파라미터 소개

트리 기반 자체의 파라미터 n_estimators, max_depth, max_features는 결정 트리, 랜덤 포레스트와 마찬가지로 기본으로 가진다.

  • loss : 경사 하강법에서 사용할 비용 함수 지정. default = 'deviance'
  • learning_rate : GBM이 학습을 진행할 때마다 적용하는 학습률. deault = 0.1(0~1)
    • 너무 작은 값을 적용하면 업데이트되는 값이 작아져 예측 성능이 옾아질 가능성이 높지만, 수행시간이 오래걸리고 최소 오류 값을 찾지 못할 수 있다. 
    • 반대로 큰 값을 적용하면 최소 오류 값을 찾지 못하고 지나쳐 예측 성능이 떨어질 가능성이 높다. 수행은 빠름..
    • 따라서 learning_rate는 n_estimators와 상호 보완적으로 조합해 사용한다.
  • n_estimatos : weak learner의 개수. default = 100. 개수가 많을 수록 예측 성능이 좋아질 수 있으나, 수행시간이 오래걸린다.
  • subsample : weak learner가 학습에 사용하는 데이터의 샘플링 비율. default = 1(전체 학습 데이터 기반 학습). 과적합이 염려되는 경우 subsample을 1보다 작은 값으로 설정한다.

06. XGBoost(eXtra Grdient Boost)

 

XGBoost는 츠리 기반의 앙상블 학습에서 가장 각광받고 있는 알고리즘 중 하나이다.

GBM의 단점인 느린 수행 시간 및 과적합 규제(Regularization) 부재 등의 문제를 해결한다.

본래 XGBoost 전용의 파이썬 패키지와 사이킷런과 호환되는 래퍼용 XGBoost가 함께 존재한다.

사이킷런 래퍼 클래스(Wrapper class)를 이용하면 사이킷런 estimator가 학습을 위해 사용하는 fit()과 predict()와 같은 표준 사이킷런 개발 프로세스 및 다양한 유틸리티를 활용할 수 있다.

 

파이썬 래퍼 XGBoost 하이퍼 파라미터

 

XGBoost는 GBM과 유사한 하이퍼 파라미터를 동일하게 가지며, 여기에 조기 중단 (early stopping), 과적합을 규제하기 위한 하이퍼 파라미터 등이 추가 됐다.

또한 기존 파이썬 모듈과 이름 규칙에 따라 파라미터 명이 달라졌다.

 

  • 일반 파라미터 : 일반적으로 실행 시 스레드의 개수나 silent 모드 등의 선택을 위한 파라미터(default 기본)
  • 부스터 파라미터 : 트리 최적화, 부스팅, regularization 등과 관련 파라미터 등을 지칭
  • 학습 태스크 파라미터 : 학습 수행 시의 객체 함수, 평가를 위한 지표 등을 설정하는 파라미터

대부분의 하이퍼 파라미터는 Booster 파라미터 이다.

 

주요 일반 파라미터

  • booster : gbtree(tree base model) 또는 gblinear(linear model) 선택. default = gbtree
  • silent : default = 0. 출력메시지를 나타내고 싶지 않은 경우 1.
  • nthread : CPU의 실행 스레드 개수 조정. default는 전체 스레드 다 사용.

주요 부스터 파라미터

  • eta [default=0.3, alias: learning_rate] : GBM의 학습률(learning rate)와 같은 파라미터. 부스팅 스텝을 반복적으로 수행할 때 업데이트되는 학습률 값. 보통 0.01~0.2
    • 파이썬 기반 : default = 0.3
    • 사이킷런 기반 : eta는 learning_rate 파라미터로 대체되며, default=0.1
  • num_boost_rounds : GBM의 n_estimators와 같은 파라미터
  • min_child_weight[default=1] : 트리에서 추가적으로 가지를 나눌지 결정하기 위한 데이터들의 weight 총합. 값이 클수록 분할을 자제하며, 과적합을 조절한다.
  • gamma [default=0, alias : min_split_loss] : 트리의 리프 노드를 추가적으로 나눌지를 결정할 최소 손실 감소 값. 해당 값보다 손실(loss)이 클 경우에 리프 노드 분리. 값이 클수록 과적합 감소 효과.
  • max_depth[default=6] : 트리 기반 알고리즘의 max_depth와 같다. 높을 수록 과적합 가능성이 높아진다. 보통 3~10의 값을 가진다.
  • sub_sample[default=1] : GBM의 subsample과 동일하다. 트리가 커져 과적합되는 것을 제어하기 위해 데이터를 샘플링하는 비율 지정. 보통 0.5~1 사용
  • colsample_bytree[default=1] : GBM의 max_features와 유사하다. 매우 많은 피처가 있는 경우 과적합을 조정하는 데 적용한다.
  • lambda [default=1, alias: reg_lambda] : L2 Regularization 적용 값. 피처 개수가 많을 경우 적용을 검토하며 값이 클수록 과적합 감소 효과가 있다.
  • alpha [default=0, alias: reg_alpha] : L1 Regularization 적용 값. 피처 개수가 많을 경우 적용을 검토하며 값이 클수록 과적합 감소 효과가 있다.
  • scale_pos_weight [default=1] : 특정 값으로 치우친 비대칭한 클래스로 구성된 데이터 세트의 균형을 유지하기 위한 파라미터

학습 태스크 파라미터

  • objective : 최솟값을 가져야 할 손실 함수 정의
  • binary:logistic : 이진 분류일 떄 적용
  • multi:softmax : 다중 분류일 때 적용. 레이블 클래스의 개수인 num_class 파라미터 지정 필요
  • multi:softprob : 위와 유사하나 개별 레이블 클래스의 해당되는 예측 확률 반환
  • eval_metric : 검증에 사용되는 함수 정의. 회귀인 경우 default=rmse, 분류일 경우 error

뛰어난 알고리즘일수록 파라미터를 튜닝할 필요가 적다. 또한 성능 향상 효과가 높지 않은 경우가 많다.

 

과적합 문제 해결

  • eta 값 낮추기(0.01~0.1). eta 값을 낮출경우 num_round(또는 n_estimators)는 반대로 높여줘야 한다.
  • max_depth 값 낮추기
  • min_child_weight 값 높이기
  • gamma 값 높이기
  • subsample과 colsample_bytree 조정

XGBoost는 자체적으로 교차 검증, 성능 평가, 피처 중요도 시각화 등의 기능을 가진다.

또한 수행 속도 향상을 위한 대표적인 조기 중단(Early Stopping) 기능이 있다.

조기 중단 기능은 n_estimators에 지정한 부스팅 반복 횟수에 도달하지 않더라도 예측 오류가 더 이상 개선되지 않으면 반복을 끝까지 수행하지 않고 중지해 수행 시간을 개선할 수 있다.

 

파이썬 래퍼 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()
X_features= dataset.data
y_label = dataset.target

# cancer_df에서 feature용 DataFrame과 Label용 Series 객체 추출
# 맨 마지막 칼럼이 Label임. Feature용 DataFrame은 cancer_df의 첫번째 칼럼에서 맨 마지막 두번째 칼럼까지를 :-1 슬라이싱으로 추출.
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)
(455, 30) (114, 30)
(409, 30) (46, 30)
# 569개의 데이터 세트에서 최종 학습용 409개, 검증용 46개, 테스트용 114개 추출

검증 성능 평가와 조기 중단을 수행해 보기 위해 검증용 데이터 세트를 별도로 분할한다.

파이썬 래퍼 XGBoost(사이킷런 x)는 XGBoost만의 전용 데이터 객체인 DMatrix를 사용한다.

따라서 DMatix로 변환이 필요하다. 주요 입력 파라미터는 data(피처 데이터 세트), label(레이블 데이터 세트/숫자형인 종속값 데이터 세트)

 

XGBoost의 하이퍼 파라미터는 주로 딕셔너리 형태로 입력한다.

# 트리 최대 깊이 3, 학습률 eta는 0.1, 이진 분류이므로 목적함수는 이진 로지스틱, 
# 오류 함수의 평가 성능 지표는 logloss, 부스팅 반복 횟수는 400회
params = { 'max_depth':3,
          'eta': 0.05,
          'objective':'binary:logistic',
          'eval_metric':'logloss'
         }
num_rounds = 400

조기 중단은 xgboost의 train() 함수에 early_stopping_rounds 파라미터를 입력하여 설정한다. 

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

# 학습 데이터 셋은 'train' 또는 평가 데이터 셋은 'eval' 로 명기합니다. 
eval_list = [(dtr,'train'),(dval,'eval')] # 또는 eval_list = [(dval,'eval')] 만 명기해도 무방. 

# 하이퍼 파라미터와 early stopping 파라미터를 train( ) 함수의 파라미터로 전달
xgb_model = xgb.train(params = params , dtrain=dtr , num_boost_round=num_rounds , \
                      early_stopping_rounds=50, evals=eval_list )

 

출력 결과에서는 126번째 반복에서 eval-logloss로 표시되는 값이 가장 낮다. 

176번까지 logloss값은 이보다 향상되지 않으므로 더이상 반복하지 않고 176번째 반복에서 멈춘다.

 

XGBoost는 train() 함수를 호출해 학습이 완료된 모델 객체를 반환하게 되는데, 이 모델 객체는 예측을 위해 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])

# output

predict( ) 수행 결과값을 10개만 표시, 예측 확률 값으로 표시됨
[0.845 0.008 0.68  0.081 0.975 0.999 0.998 0.998 0.996 0.001]
예측값 10개만 표시: [1, 0, 1, 0, 1, 1, 1, 1, 1, 0]

 

다음은 xgboost의 plot_importance() api를 이용해 피처의 중요도를 막대그래프 형식으로 나타낸다.

기본 평가 지표는 f 스코어(해당 피처가 트리 분할 시 얼마나 자주 사용되었는 지를 지표로 나타낸 값)

유의할 점은 xgboost를 넘파이 기반의 피처 데이터로 학습 시에는 피처명을 제대로 알 수 없으므로 Y축의 피처명을 나열 시 f자 뒤에 순서를 붙여서 피처명을 나타낸다.

import matplotlib.pyplot as plt
%matplotlib inline

fig, ax = plt.subplots(figsize=(10, 12))
# plot_importance() API -> 피처의 중요도를 막대그래프 형식으로 나타낸다.
plot_importance(xgb_model, ax=ax)
plt.savefig('p239_xgb_feature_importance.tif', format='tif', dpi=300, bbox_inches='tight')

plot_importance()

또한 트리 기반 규칙 구조도 xgboost에서 시각화가 가능하다.

또, GridSearchCV와 유사하게 교차 검증 수행 후 최적 파라미터를 구할 수 있는 방법을 cv() API로 제공한다.

 

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

 

파이썬 래퍼 XGBoost외에도 사이킷런의 프레임워크와 연동하기 위해 개발하였다.

분류를 위한 XGBClassifier, 회귀를 위한 XGBRegressor 래퍼 클래스로 나뉜다.

네이티브 하이퍼 파라미터가 약간 수정된 것 이외에는 거의 비슷하다.

  • eta -> learning_Rate
  • sub_sample -> subsample 
  • lambda -> reg_lambda
  • alpha -> reg_alpha

또한 plot_importance() API도 동일하게 적용가능하다.


07. LightGBM

 

LightGBM은 XGBoost와 함께 부스팅 계열 알고리즘에서 가장 각광받고 있다.

예측 성능은 XGBoost와 비슷하지만 학습에 걸리는 시간이 훨씬 적고, 메모리 사용량도 상대적으로 적다.

단점은 적은 데이터 세트에 적용할 경우 과적합이 발생하기 쉽다.

 

LightGBM은 이전 GBM의 균형 트리 분할과 다르게 리프 중심 트리 분할(Leaf Wise) 방식을 사용하여 균형 잡힌 트리를 생성하지 않는다.

균형 잡힌 트리는 오버 피팅에 보다 강하지만 시간이 필요하고, 리프 중심 트리는 예측 오류 손실을 최소화할 수 있다.

 

LightGBM 설치

 

LightGBM또한 파이썬 래퍼용만 개발됐으나, 사이킷런과의 호환성을 위해 사이킷런 래퍼 LightGBM이 추가로 개발됐다.

 

아나콘다를 통해 설치하려고 했지만...오류 때문에 설치가 안돼서 주피터 노트북에 pip install LightGBM으로 직접 설치했다.

단, 그냥 이렇게 설치하면 실습할 때 TypeError: LGBMClassifier.fit() got an unexpected keyword argument 'early_stopping_rounds'  이런 오류가 날 수 있다.

높은 버전에서는 파라미터로 조기 중단을 지원하지 않고 다른 방식으로 바뀌기 때문에...인자를 알 수 없다는 오류다.

따라서 책 그대로 하고 싶다면 pip install LightGBM==3.3.2로 버전을 명시해주자!

 

LightGBM 하이퍼 파라미터

 

XGBoost와 유사하지만 리프 노드가 계속 분할하므로 알맞는 하이퍼 파라미터 설정이 필요하다.

  • num_itreations [default = 100] : 반복 수행하려는 트리의 개수 지정. 크게 지정할수록 예측 성능이 높아지나, 과적합으로 성능이 저하될 수 있다.(사이킷런 호환 클래스는 n_estimators로 이름 변경)
  • learning_rate [default = 0.1] : 부스팅 스텝을 반복적으로 수행할 때 업데이트 되는 학습률
  • max_depth [ default = 1] : 트리 기반 알고리즘과 같으나, LightGBM은 상대적으로 깊이가 깊다.
  • min_data_in_leaf [ default = 20] : 결정트리의 min_samples_leaf와 같다. 리프 노드가 되기 위해 최소한으로 필요한 레코드의 수.(사이킷런에서는 min_child_samples)
  • num_leaves [default = 31] : 하나의 트리가 가질 수 있는 최대 리프 개수
  • boosting [default = gbdt] : 부스팅의 트리를 생성하는 알고리즘
  • bagging_fraction [default = 1.0] : 트리가 커져서 과적합 되는 것을 제어하기 위해 데이터 샘플링 비율 지정
  • feature_fraction [default = 1.0] : 개별 트리를 학습할 때마다 무작위로 선책하는 피처의 비율. 과적합을 막는다.
  • lambda_l2 [default = 0.0] : L2 regulation 제어. 피처 개수가 많을 경우 적용 검토, 과적합 효과.
  • lambda_l1 [default = 0.0] : L1 regulation 제어.

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

 

하이퍼 파라미터 비교

 

명명 규칙에 따라 다르다.

LightGBM 적용 - 위스콘신 유방암 예측

 

XGBoost와 거의 비슷하다.

# LightGBM의 파이썬 패키지인 lightgbm에서 LGBMClassifier 임포트
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 )

# 앞서 XGBoost와 동일하게 n_estimators는 400 설정.
lgbm_wrapper = LGBMClassifier(n_estimators=400, learning_rate=0.05)

# LightGBM도 XGBoost와 동일하게 조기 중단 수행 가능.
# 왜 오류 나지...
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, verbose=True)
preds = lgbm_wrapper.predict(X_test)
pred_proba = lgbm_wrapper.predict_proba(X_test)[:, 1]

08. 베이지안 최적화 기반의 HyperOpt를 이용한 하이퍼 파라미터 튜닝

 

하이퍼 파라미터 튜닝을 위해 사이킷런에서 제공하는 Grid Search 방식 대신, 베이지안 최적화를 사용한다.

베이지안 최적화는 목적 함수 식을 제대로 알 수 없는 함수에서 최대 또는 최소 함수 반환 값을 만드는 최적 입력값을 적은 시도를 통해 빠르고 효과적으로 찾아주는 방식이다.

새로운 데이터를 입력받았을 때 최적 함수를 예측하는 사후 모델을 개선해 나가면서 최적 함수 모델을 만들어 낸다.

 

중요 요소 

  • 대체 모델(Surrogate Model) : 획득 함수로부터 최적 함수를 예측할 수 있는 입력값을 추천 받은 뒤 이를 기반으로 최적 함수 모델을 개선해 나간다.
  • 획득 함수(Acquistion Function) : 개선된 대체 모델을 기반으로 최적 입력값을 계산한다.

입력값은 하리퍼 파라미터이다.

즉, 대체 모델은 획득 함수가 계산한 하이퍼 파라미터를 입력 받으며 개선되고, 개선된 대체 모델을 기반으로 획득 함수는 더 정확한 하이퍼 파라미터를 계산할 수 있다.

 

베이지안 최적화 단계

  1. 최초에는 랜덤하게 하이퍼 파라미터들을 샘플링하고 성능 결과를 관측한다.
  2. 관측된 값을 기반으로 대체 모델은 최적 함수를 추정한다.
  3. 추정된 함수를 기반으로 획득 함수(Acquistion Function)는 다음으로 관측할 하이퍼 파라미터 값을 계산한다. 획득 함수는 이전의 최적 관측값보다 더 큰 최댓값을 가질 가능성이 높은 지점을 찾아 다음에 관측할 하이퍼 파라미터를 대체모델에 전달한다.
  4. 획득 함수로부터 전달된 하이퍼 파라미터를 수행하여 관측된 값을 기반으로 대체 모델은 갱신되어 다시 최적 함수를 예측 추정한다.

일반적으로 최적 함수를 추정할 때는 가우시안 프로세스(Gaussian Process)를 저용하지만, 다음 실습의 HyperOpt는 트리 파르젠 Estimator(TPE)를 사용한다.

 

HyperOpt 사용하기

 

HyperOpt는 목적 함수 반환 값의 최댓값이 아닌 최솟값을 가지는 최적 입력값을 유추한다.

 

입력 변수명과 입력값 검색 공간은 파이썬 딕셔너리 형태로 설정되어야 한다.

quniform()을 이용해 입력값 검색 공간을 지정했다.

from hyperopt import hp

# -10 ~ 10까지 1간격을 가지는 입력 변수 x와 -15 ~ 15까지 1간격으로 입력 변수 y 설정.
search_space = {'x': hp.quniform('x', -10, 10, 1), 'y': hp.quniform('y', -15, 15, 1) }

입력값 검색 공간 제공 대표 함수

label은 입력 변수명 다시 입력, low는 최솟값, high는 최댓값, q는 간격

목적 함수는 변숫값과 검색 공간을 가지는 딕셔너리를 인자로 받고, 특정 값을 반환하는 구조로 만들어져야 한다.

from hyperopt import STATUS_OK

# 목적 함수를 생성. 변숫값과 변수 검색 공간을 가지는 딕셔너리를 인자로 받고, 특정 값을 반환
def objective_func(search_space):
    x = search_space['x']
    y = search_space['y']
    retval = x**2 - 20*y
    
    return retval

입력값의 검색 공간과 목적 함수를 설정했으면 목적 함수의 반환값이 최소가 될 수 있는 최적의 입력값을 베이지안 최적화 기법에 기반하여 찾아줘야 한다.

HyperOpt는 위 기능을 수행하는 fmin() 함수를 제공한다.

 

주요 인자

  • fn : 위에서 생성한 objective_func과 같은 목적 함수
  • space: 위에서 생성한 search_space와 같은 검색 공간 딕셔너리
  • algo : 베이지안 최적화 적용 알고리즘. default = tpe.suggest(TPE)
  • max_evals : 최적 입력값을 찾기 위한 입력값 시도 횟수
  • trials : 최적 입력값을 찾기 위해 시도한 입력값 및 해당 입력값의 목적 함수 반환값 결과를 저장하는 데 사용
  • rstate : fmin()을 수행할 때마다 동일한 결괏값을 가질 수 있도록 설정하는 랜덤 seed 값
from hyperopt import fmin, tpe, Trials
import numpy as np

# 입력 결괏값을 저장한 Trials 객체값 생성.
trial_val = Trials()

# 목적 함수의 최솟값을 반환하는 최적 입력 변숫값을 5번의 입력값 시도(max_evals=5)로 찾아냄.
best_01 = fmin(fn=objective_func, space=search_space, algo=tpe.suggest, max_evals=5
               , trials=trial_val, rstate=np.random.default_rng(seed=0))
print('best:', best_01)

# output

100%|█████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 313.49trial/s, best loss: -224.0]
best: {'x': -4.0, 'y': 12.0}

Trials 객체는 함수의 반복 수행 시마다 입력되는 변숫값들과 함수 반환값을 속성으로 가지고 있다.

 

중요 속성

  • results : 파이썬 리스트형태. 리스트 내 개별 원소는 딕셔너리 형태. {'loss' : 함수 반환값, 'status' : 반환 상태값}
  • vals : 딕셔너리 형태. {'입력변수명' : 개별 수행 시마다 입력된 값의 리스트}

 

HyperOpt를 이용한 XGBoost 하이퍼 파라미터 최적화

 

정수형 파라미터는 hp.quniform()을 사용했고, bytree는 hp.uniform()을 사용했다.

from hyperopt import hp

# max_depth는 5에서 20까지 1간격으로, min_child_weight는 1에서 2까지 1간격으로
# colsample_bytree는 0.5에서 1사이, learning_rate는 0.01에서 0.2 사이 정규 분포된 값으로 검색.
xgb_search_space = {'max_depth': hp.quniform('max_depth', 5, 20, 1), 
                    'min_child_weight': hp.quniform('min_child_weight', 1, 2, 1),
                    'learning_rate': hp.uniform('learning_rate', 0.01, 0.2),
                    'colsample_bytree': hp.uniform('colsample_bytree', 0.5, 1),
                   }

 

 

다음으로 목적 함수를 설정한다.

이때, 검색 공간에서 목적 함수로 입력되는 모든 인자들은 실수형 값이므로 XGBoostClassifier의 정수형 하이퍼 파라미터값으로 설정할 때는 정수형으로 형변환을 해야한다.

또한 HyperOpt의 목적 함수는 최솟값을 반환할 수 있도록 최적화해야 하기 때문에 정확도와 같이 값이 클수록 좋은 성능 지표일 경우 -1을 곱한 뒤 반환하여, 더 큰 성능 지표가 더 작은 반환값이 되도록 만들어 줘야 한다.

from sklearn.model_selection import cross_val_score
from xgboost import XGBClassifier
from hyperopt import STATUS_OK

# fmin()에서 입력된 search_space 값으로 입력된 모든 값은 실수형임.
# XGBClassifier의 정수형 하이퍼 파라미터는 정수형 변환을 해줘야 함.
# 정확도는 높을수록 더 좋은 수치임. -1 * 정확도를 곱해서 큰 정확도 값일수록 최소가 되도록 변환
def objective_func(search_space):
    # 수행 시간 절약을 위해 nestimators는 100으로 축소
    xgb_clf = XGBClassifier(n_estimators=100, max_depth=int(search_space['max_depth']),
                            min_child_weight=int(search_space['min_child_weight']),
                            learning_rate=search_space['learning_rate'],
                            colsample_bytree=search_space['colsample_bytree'],
                            eval_metric='logloss')
    accuracy = cross_val_score(xgb_clf, X_train, y_train, scoring='accuracy', cv=3)
    
    # accuracy는 cv=3 개수만큼 roc-auc 결과를 리스트로 가짐. 이를 평균해서 반환하되 -1을 곱함.
    return {'loss':-1 * np.mean(accuracy), 'status': STATUS_OK}
    
from hyperopt import fmin, tpe, Trials

trial_val = Trials()
best = fmin(fn=objective_func,
            space=xgb_search_space,
            algo=tpe.suggest,
            max_evals=50, # 최대 반복 횟수를 지정합니다.
            trials=trial_val, rstate=np.random.default_rng(seed=9))
print('best:', best)

# output

100%|███████████████████████████████████████████████| 50/50 [00:14<00:00,  3.57trial/s, best loss: -0.9670616939700244]
best: {'colsample_bytree': 0.5424149213362504, 'learning_rate': 0.12601372924444681, 'max_depth': 17.0, 'min_child_weight': 2.0}

다만 cross_val_socre()를 적용할 때는 조기 중단이 지원되지 않는다.

조기중단을 위해서는 직접 교차 검증을 수행해야 한다.

 


11. 스태킹 앙상블

 

스태킹(Stacking)은 배깅과 부스팅과 비슷하지만, 개별 알고리즘으로 예측한 데이터를 기반으로 다시 예측을 수행한다는 차이점이 있다.

스태킹은 개별적인 기반 모델과 이 개별 기반 모델의 예측 데이터를 학습 데이터로 만들어서 학습하는 최종 메타 모델 2 종류의 모델이 필요하다.

스태킹

개별 알고리즘으로부터 예측된 예측값을 칼럼 레벨로 옆으로 붙여서 피처 값으로 만들어, 최종 메타 모델인 로지스틱 회귀에서 학습데이터로 다시 사용한다.

반환된 예측 데이터 세트는 1차원 형태의 ndarray이므로 transpose()를 이용해 행과 열 위치를 바꾼 ndarray로 변환한다.

# 개별 ML 모델 생성
knn_clf = KNeighborsClassifier(n_neighbors=4)
rf_clf = RandomForestClassifier(n_estimators=100, random_state=0)
dt_clf = DecisionTreeClassifier()
ada_clf = AdaBoostClassifier(n_estimators=100)

# 스태킹으로 만들어진 데이터 세트를 학습, 예측할 최종 모델
lr_final = LogisticRegression()

# 학습하기~

lr_final.fit(pred, y_test)
final = lr_final.predict(pred)

print('최종 메타 모델의 예측 정확도: {0:.4f}'.format(accuracy_score(y_test, final)))

# output

최종 메타 모델의 예측 정확도: 0.9737

 

CV 세트 기반의 스태킹

 

CV 세트 기반의 스태킹 모델은 과적합을 개선하기 위해 최종 메타 모델을 위한 데이터 세트를 만들 때 교차 검증 기반으로 예측된 결과 데이터 세트를 이용한다.

 

주요 프로세스

  1. 학습용 데이터 세트를 3개의 폴드로 나누되, 2개는 학습데이터 폴드, 1개는 검증 데이터 폴드로 나눈다.
  2. 학습된 개별 모델은 검증 폴드 1개 데이터로 예측하고 결과를 저장한다. 이 로직을 3번 반복하며 예측결과를 별도로 저장한다. 저장한 데이터는 메타 모델의 학습 데이터로 사용된다.
  3. 개별 모델은 원본 테스트 데이터를 예측하여 예측값을 생성한다. 이를 3번 반복하며 이 예측값의 평균으로 최종 결과값을 생성하고, 이를 메타 모델을 위한 테스트 데이터로 사용한다.

CV 기반의 스태킹 모델 전체

from sklearn.model_selection import KFold
from sklearn.metrics import mean_absolute_error

# 개별 기반 모델에서 최종 메타 모델이 사용할 학습 및 테스트용 데이터를 생성하기 위한 함수
def get_stacking_base_datasets(model, x_train_n, y_train_n, x_test_n, n_folds ):
    # 지정된 n_folds값으로 KFold 생성.
    kf = KFold(n_splits=n_folds, shuffle=False)
    # 추후에 메타 모델이 사용할 학습 데이터 반환을 위한 넘파이 배열 초기화
    train_fold_pred = np.zeros((x_train_n.shape[0], 1))
    test_pred = np.zeros((x_test_n.shape[0], n_folds))
    print(model.__class__.__name__,' model 시작')
    
    for folder_counter, (train_index, valid_index) in enumerate(kf.split(x_train_n)):
        # 입력된 학습 데이터에서 기반 모델이 학습/예측할 폴드 데이터 세트 추출
        print('\t 폴드 세트: ', folder_counter, ' 시작 ')
        x_tr = x_train_n[train_index]
        y_tr = y_train_n[train_index]
        x_te = x_train_n[valid_index]
        
        # 폴드 세트 내부에서 다시 만들어진 학습 데이터로 기반 모델의 학습 수행.
        model.fit(x_tr, y_tr)
        # 폴드 세트 내부에서 다시 만들어진 검증 데이터로 기반 모델의 예측 후 데이터 저장.
        train_fold_pred[valid_index, :] = model.predict(x_te).reshape(-1,1)
        # 입력된 원본 테스트 데이터를 폴드 세트내 학습된 기반 모델에서 예측 후 데이터 저장.
        test_pred[:, folder_counter] = model.predict(x_test_n)
        
    # 폴드 세트 내에서 원본 테스트 데이터를 예측한 데이터를 평균하여 테스트 데이터로 생성
    test_pred_mean = np.mean(test_pred, axis=1).reshape(-1,1)
    
    # train_fold_pred는 최종 메타 모델이 사용하는 학습 데이터, test_pred_mean은 테스트 데이터
    return train_fold_pred, test_pred_mean

분류 모델별로 위 함수를 수행 후 각 데이터 세트를 반환한다.

concatenate() 를 이용해 각 모델별 학습, 테스트 데이터를 합친다.

그렇게 만들어진 학습용 피처 데이터 세트와 원본 학습 레이블 데이터로 최종 메타 모델(여기서는 로지스틱 회귀)을 학습 한 후 스태킹된 테스트 데이터 세트를 예측한다.

# concatenate() -> 여러 개의 넘파이 배열을 칼럼 또는 로우 레벨로 합쳐주는 기능 제공
Stack_final_x_train = np.concatenate((knn_train, rf_train, dt_train, ada_train), axis=1)
Stack_final_x_test = np.concatenate((knn_test, rf_test, dt_test, ada_test), axis=1)
    
lr_final.fit(Stack_final_x_train, y_train)
stack_final = lr_final.predict(Stack_final_x_test)

print('최종 메타 모델의 예측 정확도: {0:.4f}'.format(accuracy_score(y_test, stack_final)))

# output

최종 메타 모델의 예측 정확도: 0.9737

# 회고

 

어렵다! 

여러 알고리즘 별로 특징이랑 파라미터들이 아직 익숙하지가 않다. 복습과제랑 기계학습 과제로 스스로 알고리즘을 수행해봐야겠다.

그리고 상관없을 줄 알았는데 버전 문제로 알고리즘 학습할 때 오류가 있었다.

https://lightgbm.readthedocs.io/en/stable/

 

Welcome to LightGBM’s documentation! — LightGBM 4.0.0 documentation

© Copyright 2023, Microsoft Corporation. Revision d73c6b53.

lightgbm.readthedocs.io

공식문서를 보는 습관을 들이자!

 

그리고 중간에 4.9, 4.10이 빠졌는데 이부분은 따로 실습할 예정이라 한다!

'Euron > 정리' 카테고리의 다른 글

[Week5] 05. 회귀(1)  (1) 2023.10.07
[Week4] 04. 분류 - 캐글 실습  (0) 2023.09.29
[Week2] 04. 분류(1)  (0) 2023.09.17
[Week1] 03. 평가  (0) 2023.09.12
[Week1] 02. 사이킷런으로 시작하는 머신러닝  (0) 2023.09.12