XGBoost/LightGBM 사용해서 분류 예측 진행
Early Stopping과 베이지안 최적화를 이용한 하이퍼 파라미터 튜닝을 중점으로 진행
데이터 출처: https://www.kaggle.com/competitions/santander-customer-satisfaction/rules
산탄데르 은행의 고객 만족 예측
각 칼럼명은 어느정도 인코딩이 되어 있어서 뜻을 식별하기가 힘듦
TARGET:0 -> 만족
TARGET:1 -> 불만족
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
import warnings
warnings.filterwarnings('ignore')
cust_df = pd.read_csv("./train_santander.csv", encoding='latin-1')
print('dataset shape:', cust_df.shape)
cust_df.info()
dataset shape: (76020, 371)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 76020 entries, 0 to 76019
Columns: 371 entries, ID to TARGET
dtypes: float64(111), int64(260)
memory usage: 215.2 MB
칼럼이 너무 많아서 칼럼명이 나오지 않음
결측값은 없는 데이터임
Target(Label) 비율 확인
# 0: 만족, 1: 불만족
print(cust_df['TARGET'].value_counts())
unsatisfied_cnt = cust_df[cust_df['TARGET'] == 1].shape[0]
total_cnt = cust_df.shape[0]
print('unsatisfied 비율은 {0:.2f}'.format((unsatisfied_cnt / total_cnt)))
0 73012
1 3008
Name: TARGET, dtype: int64
unsatisfied 비율은 0.04
칼럼별 통계량 확인
cust_df.describe( )
(일부)
var3에 min 값이 이상함
아마 결측 등의 처리를 저렇게 해준 것 같음
좀 더 구체적으로 확인하기
cust_df['var3'].value_counts()
2 74165
8 138
-999999 116
9 110
3 108
...
177 1
87 1
151 1
215 1
191 1
Name: var3, Length: 208, dtype: int64
- -999999 값이 116개가 있음, 대부분을 차지하는 2로 replace 해주기
- 예측에 도움이 되지 않는 ID 칼럼은 drop
- Feature 데이터세트와 Label 데이터 세트를 분리
# var3 피처 값 대체 및 ID 피처 드롭
cust_df['var3'].replace(-999999, 2, inplace=True)
cust_df.drop('ID', axis=1, inplace=True)
# 피처 세트와 레이블 세트분리. 레이블 컬럼은 DataFrame의 맨 마지막에 위치해 컬럼 위치 -1로 분리
X_features = cust_df.iloc[:, :-1]
y_labels = cust_df.iloc[:, -1]
print('피처 데이터 shape:{0}'.format(X_features.shape))
피처 데이터 shape:(76020, 369)
train/test 데이터세트 분리하고 Label 분포 확인
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_features, y_labels,
test_size=0.2, random_state=0, stratify=y_labels)
train_cnt = y_train.count()
test_cnt = y_test.count()
print('학습 세트 Shape:{0}, 테스트 세트 Shape:{1}'.format(X_train.shape , X_test.shape))
print(' 학습 세트 레이블 값 분포 비율')
print(y_train.value_counts()/train_cnt)
print('\n 테스트 세트 레이블 값 분포 비율')
print(y_test.value_counts()/test_cnt)
학습 세트 Shape:(60816, 369), 테스트 세트 Shape:(15204, 369)
학습 세트 레이블 값 분포 비율
0 0.960438
1 0.039562
Name: TARGET, dtype: float64
테스트 세트 레이블 값 분포 비율
0 0.960405
1 0.039595
Name: TARGET, dtype: float64
stratify
를 사용해서 두 데이터세트의 분포가 비슷하게 나왔음
train 데이터 세트를 다시 train과 validation으로 분리
# X_train, y_train을 다시 학습과 검증 데이터 세트로 분리.
X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train,
test_size=0.3, random_state=0, stratify=y_train)
XGBoost 모델 사용하는데 Early Stopping 사용
평가지표로는 ROC_AUC_SCORE 이용
관련 파라미터 복습
early_stopping_rounds: 더이상 비용 평가 지표 감소하지 않는 최대 반복 횟수
eval_metric: 반복 수행 시 사용하는 비용 평가 지표
eval_set: 평가 수행하는 별도의 검증 데이터 세트
from xgboost import XGBClassifier
from sklearn.metrics import roc_auc_score
# n_estimators는 500으로, learning_rate 0.05, random state는 예제 수행 시마다 동일 예측 결과를 위해 설정.
xgb_clf = XGBClassifier(n_estimators=500, learning_rate=0.05, random_state=156)
# 성능 평가 지표를 auc로, 조기 중단 파라미터는 100으로 설정하고 학습 수행.
xgb_clf.fit(X_tr, y_tr, early_stopping_rounds=100, eval_metric='auc', eval_set = [(X_tr, y_tr), (X_val, y_val)])
xgb_roc_score = roc_auc_score(y_test, xgb_clf.predict_proba(X_test)[:, 1])
print('ROC AUC: {0:.4f}'.format(xgb_roc_score))
[0] validation_0-auc:0.82361 validation_1-auc:0.81441
[1] validation_0-auc:0.82781 validation_1-auc:0.81954
[2] validation_0-auc:0.83186 validation_1-auc:0.82203
[3] validation_0-auc:0.83492 validation_1-auc:0.82536
[4] validation_0-auc:0.83504 validation_1-auc:0.82500
[5] validation_0-auc:0.83602 validation_1-auc:0.82564
[6] validation_0-auc:0.83678 validation_1-auc:0.82595
...
[237] validation_0-auc:0.91499 validation_1-auc:0.84361
[238] validation_0-auc:0.91502 validation_1-auc:0.84358
[239] validation_0-auc:0.91507 validation_1-auc:0.84356
[240] validation_0-auc:0.91507 validation_1-auc:0.84358
[241] validation_0-auc:0.91512 validation_1-auc:0.84354
[242] validation_0-auc:0.91521 validation_1-auc:0.84348
[243] validation_0-auc:0.91534 validation_1-auc:0.84340
ROC AUC: 0.8233
ROC AUC Score: 0.8233 나온 것 확인하기
HyperOpt 패키지 사용해서 베이지안 최적화로 하이퍼 파라미터 튜닝
Search Space를 다음과 같이 만듦
from hyperopt import hp
# max_depth는 5에서 15까지 1간격으로, min_child_weight는 1에서 6까지 1간격으로
# colsample_bytree는 0.5에서 0.95사이, learning_rate는 0.01에서 0.2사이 정규 분포된 값으로 검색
xgb_search_space = {'max_depth': hp.quniform('max_depth', 5, 15, 1),
'min_child_weight': hp.quniform('min_child_weight', 1, 6, 1),
'colsample_bytree': hp.uniform('colsample_bytree', 0.5, 0.95),
'learning_rate': hp.uniform('learning_rate', 0.01, 0.2)
}
목적 함수를 만듦
주석 천천히 따라가보기
최솟값을 찾아야 하니까 마지막에 -1을 곱함
from sklearn.model_selection import KFold
from sklearn.metrics import roc_auc_score
# 목적 함수 설정
# 추후 fmin()에서 입력된 search_space값으로 XGBClassifier 교차 검증 학습 후 -1* roc_auc 평균 값을 반환.
def objective_func(search_space):
xgb_clf = XGBClassifier(n_estimators = 100, max_depth=int(search_space['max_depth'])
, min_child_weight = int(search_space['min_child_weight'])
, colsample_bytree = search_space['colsample_bytree']
, learning_rate = search_space['learning_rate'])
# 3개 k-fold 방식으로 평가된 roc_auc 지표를 담는 list
roc_auc_list= []
# 3개 k-fold방식 적용
kf = KFold(n_splits=3)
# X_train을 다시 학습과 검증용 데이터로 분리
for tr_index, val_index in kf.split(X_train):
# kf.split(X_train)으로 추출된 학습과 검증 index값으로 학습과 검증 데이터 세트 분리
X_tr, y_tr = X_train.iloc[tr_index], y_train.iloc[tr_index]
X_val, y_val = X_train.iloc[val_index], y_train.iloc[val_index]
# early stopping은 30회로 설정하고 추출된 학습과 검증 데이터로 XGBClassifier 학습 수행.
xgb_clf.fit(X_tr, y_tr, early_stopping_rounds=30, eval_metric='auc'
, eval_set = [(X_tr, y_tr), (X_val, y_val)])
# 1로 예측한 확률값 추출후 roc auc 계산하고 평균 roc auc 계산을 위해 list에 결과값 담음.
score = roc_auc_score(y_val, xgb_clf.predict_proba(X_val)[:, 1])
roc_auc_list.append(score)
# 3개 k-fold로 계산된 roc_auc값의 평균값을 반환하되,
# HyperOpt는 목적함수의 최소값을 위한 입력값을 찾으므로 -1을 곱한 뒤 반환.
return -1 * np.mean(roc_auc_list)
fmin 함수로 베이지안 최적화를 사용해서 최적의 하이퍼 파라미터 조합 찾기
코드 다 돌아가는데 약 30분 정도 걸림
from hyperopt import fmin, tpe, Trials
trials = Trials()
# fmin()함수를 호출. max_evals지정된 횟수만큼 반복 후 목적함수의 최소값을 가지는 최적 입력값 추출.
best = fmin(fn=objective_func,
space=xgb_search_space,
algo=tpe.suggest,
max_evals=50, # 최대 반복 횟수를 지정합니다.
trials=trials, rstate=np.random.default_rng(seed=30))
print('best:', best)
[0] validation_0-auc:0.82974 validation_1-auc:0.81182
[1] validation_0-auc:0.83251 validation_1-auc:0.81290
[2] validation_0-auc:0.83539 validation_1-auc:0.81428
[3] validation_0-auc:0.84001 validation_1-auc:0.81700
[4] validation_0-auc:0.84284 validation_1-auc:0.81478
[5] validation_0-auc:0.84487 validation_1-auc:0.81854
...
[58] validation_0-auc:0.90524 validation_1-auc:0.84219
[59] validation_0-auc:0.90588 validation_1-auc:0.84203
[60] validation_0-auc:0.90647 validation_1-auc:0.84243
[61] validation_0-auc:0.90703 validation_1-auc:0.84254
[62] validation_0-auc:0.90771 validation_1-auc:0.84241
[63] validation_0-auc:0.90805 validation_1-auc:0.84249
100%|███████████████████████████████████████████████| 50/50 [36:52<00:00, 44.25s/trial, best loss: -0.8450266698065375]
best: {'colsample_bytree': 0.5776770858963758, 'learning_rate': 0.10188910033482984, 'max_depth': 5.0, 'min_child_weight': 6.0}
베이지안 최적화로 찾은 최적의 하이퍼 파라미터
- 'colsample_bytree': 0.5776770858963758
- 'learning_rate': 0.10188910033482984
- 'max_depth': 5.0
- 'min_child_weight': 6.
최종적으로 찾은 하이퍼 파라미터를 사용하고,
Early Stopping까지 이용해서 fit() 하고 test 데이터에 대해 예측을 진행
예측 결과를 ROC AUC Score로 확인
[0] validation_0-auc:0.81909 validation_1-auc:0.81373
[1] validation_0-auc:0.82667 validation_1-auc:0.82210
[2] validation_0-auc:0.83212 validation_1-auc:0.82924
[3] validation_0-auc:0.83394 validation_1-auc:0.82849
[4] validation_0-auc:0.83605 validation_1-auc:0.82922
[5] validation_0-auc:0.84101 validation_1-auc:0.83263
[6] validation_0-auc:0.84398 validation_1-auc:0.83572
[7] validation_0-auc:0.84467 validation_1-auc:0.83622
[8] validation_0-auc:0.84570 validation_1-auc:0.83781
...
[175] validation_0-auc:0.90163 validation_1-auc:0.84437
[176] validation_0-auc:0.90185 validation_1-auc:0.84421
[177] validation_0-auc:0.90203 validation_1-auc:0.84405
[178] validation_0-auc:0.90209 validation_1-auc:0.84407
[179] validation_0-auc:0.90223 validation_1-auc:0.84405
[180] validation_0-auc:0.90227 validation_1-auc:0.84403
[181] validation_0-auc:0.90242 validation_1-auc:0.84396
[182] validation_0-auc:0.90274 validation_1-auc:0.84389
[183] validation_0-auc:0.90284 validation_1-auc:0.84395
ROC AUC: 0.8258
베이지안 최적화 전보다 ROC AUC Score가 소폭 상승했음
Feature Importance 시각화로 마무리
from xgboost import plot_importance
import matplotlib.pyplot as plt
%matplotlib inline
fig, ax = plt.subplots(1,1,figsize=(10,8))
plot_importance(xgb_clf, ax=ax , max_num_features=20,height=0.4)
LightGBM도 비슷하니까 필요한 부분만 설명
하이퍼 파라미터 변경 없이 ROC AUC 스코어 출력
Early Stopping만 사용
from lightgbm import LGBMClassifier
lgbm_clf = LGBMClassifier(n_estimators=500)
eval_set=[(X_tr, y_tr), (X_val, y_val)]
lgbm_clf.fit(X_tr, y_tr, early_stopping_rounds=100, eval_metric="auc", eval_set=eval_set)
lgbm_roc_score = roc_auc_score(y_test, lgbm_clf.predict_proba(X_test)[:,1])
print('ROC AUC: {0:.4f}'.format(lgbm_roc_score))
[1] training's auc: 0.824228 training's binary_logloss: 0.156831 valid_1's auc: 0.816744 valid_1's binary_logloss: 0.158547
[2] training's auc: 0.835661 training's binary_logloss: 0.151145 valid_1's auc: 0.827251 valid_1's binary_logloss: 0.153561
[3] training's auc: 0.843182 training's binary_logloss: 0.146979 valid_1's auc: 0.828392 valid_1's binary_logloss: 0.150106
[4] training's auc: 0.8459 training's binary_logloss: 0.143663 valid_1's auc: 0.828847 valid_1's binary_logloss: 0.147372
...
[138] training's auc: 0.94852 training's binary_logloss: 0.0939292 valid_1's auc: 0.835307 valid_1's binary_logloss: 0.136192
[139] training's auc: 0.948622 training's binary_logloss: 0.0938259 valid_1's auc: 0.835327 valid_1's binary_logloss: 0.136203
[140] training's auc: 0.948728 training's binary_logloss: 0.0937181 valid_1's auc: 0.835033 valid_1's binary_logloss: 0.136284
[141] training's auc: 0.948793 training's binary_logloss: 0.09363 valid_1's auc: 0.834781 valid_1's binary_logloss: 0.136354
ROC AUC: 0.8213
Search Space 생성
LightGBM은 Tree의 깊이에 크게 상관 없어서max_depth
크게 잡음
lgbm_search_space = {'num_leaves': hp.quniform('num_leaves', 32, 64, 1),
'max_depth': hp.quniform('max_depth', 100, 160, 1),
'min_child_samples': hp.quniform('min_child_samples', 60, 100, 1),
'subsample': hp.uniform('subsample', 0.7, 1),
'learning_rate': hp.uniform('learning_rate', 0.01, 0.2)
}
목적 함수 설정
def objective_func(search_space):
lgbm_clf = LGBMClassifier(n_estimators=100, num_leaves=int(search_space['num_leaves']),
max_depth=int(search_space['max_depth']),
min_child_samples=int(search_space['min_child_samples']),
subsample=search_space['subsample'],
learning_rate=search_space['learning_rate'])
# 3개 k-fold 방식으로 평가된 roc_auc 지표를 담는 list
roc_auc_list = []
# 3개 k-fold방식 적용
kf = KFold(n_splits=3)
# X_train을 다시 학습과 검증용 데이터로 분리
for tr_index, val_index in kf.split(X_train):
# kf.split(X_train)으로 추출된 학습과 검증 index값으로 학습과 검증 데이터 세트 분리
X_tr, y_tr = X_train.iloc[tr_index], y_train.iloc[tr_index]
X_val, y_val = X_train.iloc[val_index], y_train.iloc[val_index]
# early stopping은 30회로 설정하고 추출된 학습과 검증 데이터로 XGBClassifier 학습 수행.
lgbm_clf.fit(X_tr, y_tr, early_stopping_rounds=30, eval_metric="auc",
eval_set=[(X_tr, y_tr), (X_val, y_val)])
# 1로 예측한 확률값 추출후 roc auc 계산하고 평균 roc auc 계산을 위해 list에 결과값 담음.
score = roc_auc_score(y_val, lgbm_clf.predict_proba(X_val)[:, 1])
roc_auc_list.append(score)
# 3개 k-fold로 계산된 roc_auc값의 평균값을 반환하되,
# HyperOpt는 목적함수의 최소값을 위한 입력값을 찾으므로 -1을 곱한 뒤 반환.
return -1*np.mean(roc_auc_list)
베이지안 최적화 진행
from hyperopt import fmin, tpe, Trials
trials = Trials()
# fmin()함수를 호출. max_evals지정된 횟수만큼 반복 후 목적함수의 최소값을 가지는 최적 입력값 추출.
best = fmin(fn=objective_func, space=lgbm_search_space, algo=tpe.suggest,
max_evals=50, # 최대 반복 횟수를 지정합니다.
trials=trials, rstate=np.random.default_rng(seed=30))
print('best:', best)
[1] training's auc: 0.829117 training's binary_logloss: 0.166068 valid_1's auc: 0.813958 valid_1's binary_logloss: 0.16088
[2] training's auc: 0.831752 training's binary_logloss: 0.163964 valid_1's auc: 0.814755 valid_1's binary_logloss: 0.159158
[3] training's auc: 0.834075 training's binary_logloss: 0.162086 valid_1's auc: 0.817047 valid_1's binary_logloss: 0.157587
[4] training's auc: 0.836349 training's binary_logloss: 0.160373 valid_1's auc: 0.819498 valid_1's binary_logloss: 0.156133
...
[85] training's auc: 0.925372 training's binary_logloss: 0.107112 valid_1's auc: 0.835739 valid_1's binary_logloss: 0.136436
[86] training's auc: 0.925729 training's binary_logloss: 0.106903 valid_1's auc: 0.835716 valid_1's binary_logloss: 0.136424
[87] training's auc: 0.926147 training's binary_logloss: 0.106699 valid_1's auc: 0.835601 valid_1's binary_logloss: 0.136437
[88] training's auc: 0.926626 training's binary_logloss: 0.106467 valid_1's auc: 0.835517 valid_1's binary_logloss: 0.136471
100%|███████████████████████████████████████████████| 50/50 [05:05<00:00, 6.11s/trial, best loss: -0.8409080734703344]
best: {'learning_rate': 0.031064257415817302, 'max_depth': 115.0, 'min_child_samples': 97.0, 'num_leaves': 32.0, 'subsample': 0.9033288761262107}
찾은 하이퍼 파라미터 조합으로 test 데이터에 대해 예측 수행
lgbm_clf = LGBMClassifier(n_estimators=500, num_leaves=int(best['num_leaves']),
max_depth=int(best['max_depth']),
min_child_samples=int(best['min_child_samples']),
subsample=round(best['subsample'], 5),
learning_rate=round(best['learning_rate'], 5)
)
# evaluation metric을 auc로, early stopping은 100 으로 설정하고 학습 수행.
lgbm_clf.fit(X_tr, y_tr, early_stopping_rounds=100,
eval_metric="auc",eval_set=[(X_tr, y_tr), (X_val, y_val)])
lgbm_roc_score = roc_auc_score(y_test, lgbm_clf.predict_proba(X_test)[:,1])
print('ROC AUC: {0:.4f}'.format(lgbm_roc_score))
[1] training's auc: 0.82595 training's binary_logloss: 0.163518 valid_1's auc: 0.813214 valid_1's binary_logloss: 0.163902
[2] training's auc: 0.83005 training's binary_logloss: 0.160989 valid_1's auc: 0.818465 valid_1's binary_logloss: 0.161587
[3] training's auc: 0.835574 training's binary_logloss: 0.158793 valid_1's auc: 0.822633 valid_1's binary_logloss: 0.159556
[4] training's auc: 0.836537 training's binary_logloss: 0.156857 valid_1's auc: 0.822741 valid_1's binary_logloss: 0.157735
...
[198] training's auc: 0.914469 training's binary_logloss: 0.112229 valid_1's auc: 0.842075 valid_1's binary_logloss: 0.133661
[199] training's auc: 0.914576 training's binary_logloss: 0.112161 valid_1's auc: 0.842022 valid_1's binary_logloss: 0.133682
[200] training's auc: 0.914738 training's binary_logloss: 0.112083 valid_1's auc: 0.842048 valid_1's binary_logloss: 0.133684
[201] training's auc: 0.914877 training's binary_logloss: 0.112014 valid_1's auc: 0.842014 valid_1's binary_logloss: 0.133686
ROC AUC: 0.8239
ROC AUC 스코어가 소폭 상승한 것을 확인