TOP
class="layout-aside-left paging-number">
본문 바로가기
[파이썬 Projects]/<파이썬 머신러닝>

[머신러닝] 분류 - 캐글 산탄데르 고객 만족 예측

by 기록자_Recordian 2024. 7. 7.
728x90
반응형
시작에 앞서
해당 내용은 '<파이썬 머신러닝 완벽 가이드> 권철민 지음. 위키북스' 를 토대로 작성되었습니다. 보다 자세한 내용은 해당 서적에 상세히 나와있으니 서적을 참고해 주시기 바랍니다.

캐글 산탄데르 고객 만족 예측

 

캐글의 산탄데르 고객 만족 데이터 세트에 대해서 고객 만족 여부를 XGBoost와 LightGBM을 활용해 예측.

산탄데르 고객 만족 예측 분석은 370개의 피처로 주어진 데이터 세트 기반에서 고객 만족 여부를 처리.

(클래스 레이블 명은 TARGET 이며, 이 값이 1이면 불만을 가진 고객, 0이면 만족한 고객이다.)

 

모델의 성능 평가는 ROC-AUC 로 평가(대부분이 만족이고 불만족인 데이터는 일부일 것이기 때문)


데이터 다운로드

 

https://www.kaggle.com/c/santander-customer-satisfaction/data

 

Santander Customer Satisfaction | Kaggle

 

www.kaggle.com

※ 다운로드 전 캐글 로그인 선행 필수

 

train.csv 파일 다운로드 클릭 후, Rules에서 하단의 'I Understand and Accept' 버튼을 클릭하면 다운로드.

※ 파일 다운로드 후 압축을 해제하고 실습 코드가 있는 디렉터리에 옮겨놓는다.


데이터 전처리

 

필요한 모듈을 로딩하고 학습 데이터를 DataFrame으로 로딩

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("C:/Users/niceq/Documents/DataScience/Python ML Guide/Data/04. train_santander.csv",
                      encoding='latin-1')
print('dataset shape:', cust_df.shape)
cust_df.head()


◆ 피처의 타입과 Null 값 파악하기

cust_df.info()

▶ 111개의 피처가 float형, 260개의 피처가 int 형으로 모든 피처가 숫자형, Null 값은 없다.


◆ 만족과 불만족의 비율 보기

레이블인 Target 속성의 값의 분포를 알아본다.

print(cust_df['TARGET'].value_counts())
unsatisfied_cnt = cust_df[cust_df['TARGET'] == 1].TARGET.count()
total_cnt = cust_df.TARGET.count()
print('unsatisfied 비율은 {0:.2f}'.format((unsatisfied_cnt / total_cnt)))

▶ 대부분이 만족이며 불만족의 비율은 4%


◆ 각 피처의 값 분포를 간단히 확인

cust_df.describe()

▶ var3 칼럼의 경우 min 값이 -999999 인데, 이러한 경우 NaN 이나 특정 예외 값을 해당 값으로 처리했을 가능성이 높다.

print(cust_df.var3.value_counts()[:10])

 

해당 값으로 처리한 값이 116개가 있음을 알 수 있는데, var3은 숫자형이고 -999999는 너무 편차가 심하므로 가장 값이 많은 2로 변환

ID 피처는 단순 식별자에 불과하므로 피처를 드롭.

# var3의 -999999 값을 2로 변환
cust_df['var3'].replace(-999999, 2, inplace=True)
# ID 피처 드롭
cust_df.drop('ID', axis=1, inplace=True)

 

그리고 클래스 데이터 세트와 피처 데이터 세트를 분리해 별도의 데이터 세트로 저장.

# 피처 세트와 레이블 세트 분리. 레이블 칼럼은 DF의 맨 마지막에 위치해 칼럼 위치 -1로 분리
X_features = cust_df.iloc[:, :-1]
y_labels = cust_df.iloc[:, -1]
print('피처 데이터 shape:{0}'.format(X_features.shape))


◆ 원본 데이터 세트에서 데이터 세트 분리(학습 데이터, 테스트 데이터)

비대칭한 데이터 세트이므로 클래스인 Target 값 분포도가 학습 데이터와 테스트 데이터 세트에 모두 비슷하게 추출됐는지 확인

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

▶ 학습과 테스트 데이터 세트 모두 TARGET의 값의 분포가 원본 데이터와 유사하게 전체 데이터의 4% 정도의 불만족 값(1)으로 만들어졌다.

 

XGBoost의 조기 중단(early stopping)의 검증 데이터 세트로 활용하기 위해 X_train, y_train을 다시 쪼개서 학습과 검증 데이터 세트로 만든다.

# 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)

XGBoost 모델 학습과 하이퍼 파라미터 튜닝

 

XGBoost의 학습 모델을 생성하고 예측 결과를 ROC AUC로 평가 (사이킷런 래퍼 XGBClassifier 기반)

n_estimators = 500, ealry_stopping_rounds=100, eval_metric='auc'

from xgboost import XGBClassifier
from sklearn.metrics import roc_auc_score

# n_estimators=500, 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))

▶ 테스트 데이터 세트로 예측 시 ROC AUC는 약 0.8429


◆ HyperOpt를 이용해 베이지안 최적화 기반 XGBoost의 하이퍼 파라미터 튜닝 수행

 

파라미터 검색 공간

max_depth = 5~15, 1간격 / min_child_weight=1~6, 1간격 / colsample_bytree = 0.5~0.95 / learning_rate=0.01~0.2

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) }

 

목적 함수 생성

3 Fold 교차 검증을 이용해 평균 ROC-AUC 값 반환하되 -1을 곱하여 최대 ROC-AUC 값이 최소 반환값이 되게 한다.

교차 검증 시 조기 중단과 검증 데이터 성능 평가를 위해서 KFold 클래스를 이용하여 직접 학습과 검증 데이터 세트를 추출하고 이를 교차 검증 횟수만큼 학습과 성능 평가 수행.

수행 시간을 줄이기 위해 estimators는 100으로 줄이고, 조기중단 횟수도 30으로 줄여서 테스트.

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']),
                            learning_rate=search_space['learning_rate'],
                            colsample_bytree=search_space['colsample_bytree'])
    # 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_rounds =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() 함수를 호출해 최적의 하이퍼 파라미터 도출

max_eval=50회만큼 반복하면서 최적의 하이퍼 파라미터를 도출 (꽤 긴 시간이 소요된다)

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)

 

도출된 최적 하이퍼 파라미터를 기반으로 XGBClassifier를 재학습 시키고 테스트 데이터 세트에서 ROC AUC 측정

n_estimators는 500으로 증가

# n_estimators를 500으로 증가 후 최적으로 찾은 하이퍼 파라미터를 기반으로 학습과 예측 수행
xgb_clf = XGBClassifier(n_estimators=500, learning_rate=round(best['learning_rate'],5),
                        max_depth=int(best['max_depth']),
                        min_child_weight=(best['min_child_weight']),
                        colsample_bytree=round(best['colsample_bytree'], 5)
                       )

# evaluation metric을 auc로, early stopping은 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))


피처의 중요도 그래프

 

튜닝된 모델에서 각 피처의 중요도를 피처 중요도 그래프로 표현 (xgboost 모듈의 plot_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)

▶ XGBoost의 예측 성능을 좌우하는 가장 중요한 피처는 var38, var15 순임을 알 수 있다.


LightGBM 모델 학습과 하이퍼 파라미터 튜닝

 

위의 XGBoost 에제 코드에서 만들어진 데이터 세트를 기반으로 LightGBM 학습을 수행하고 ROC-AUC 측정

n_estimators=500, 조기 중단 횟수는 100, 앞서 분리한 학습과 검증 데이터 세트를 이용하여 학습 진행 후 테스트 데이터 세트로 평가된 ROC-AUC 값 확인

※ LightGBM 4.X 버전은 조기 중단 횟수를 callbacks를 이용하여 진행하여야 한다.

from lightgbm import LGBMClassifier
# lightgbm 업데이트로 인한 조기 중단 import
from lightgbm import early_stopping, log_evaluation

lgbm_clf = LGBMClassifier(n_estimators=500)

eval_set = ([(X_tr, y_tr), (X_val, y_val)])
lgbm_clf.fit(X_tr, y_tr, eval_set=eval_set, eval_metric='auc',
             callbacks=[early_stopping(stopping_rounds=100), log_evaluation(period=1)]
            )

lgbm_roc_score = roc_auc_score(y_test, lgbm_clf.predict_proba(X_test)[:, 1])
print('ROC AUC:{0:.4f}'.format(lgbm_roc_score))

▶ LightGBM 수행 결과 ROC-ACU는 약 0.8384를 나타낸다.

또한, XGBoost에 비해 소요 시간이 적다.


◆ HyperOpt를 이용하여 다양한 하이퍼 파라미터 튜닝 수행

튜닝 대상은 num_leaves, max_depth, min_child_samples, subsample, learning_rate

 

해당 튜닝 대상을 위한 하이퍼 파라미터 검색 공간 설정

# HyperOpt를 이용한 하이퍼 파라미터 튜닝 위한 검색 공간 설정
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_rounds =30으로 설정하고 추출된 학습과 검증 데이터로 LGBMClassifier 학습 수행
        lgbm_clf.fit(X_tr, y_tr, eval_metric='auc',
                     eval_set=[(X_tr, y_tr), (X_val, y_val)],
                     callbacks=[early_stopping(stopping_rounds=30), log_evaluation(period=1)])
        
        # 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)

 

fmin( ) 함수 호출하여 최적 하이퍼 파라미터 도출 (이 역시 꽤 긴 시간이 소요된다)

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)

 

 

하이퍼 파라미터를 이용해 LightGBM 학습 후에 테스트 데이터 세트에서 ROC-AUC 평가

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, eval_metric='auc',
                     eval_set=[(X_tr, y_tr), (X_val, y_val)],
                     callbacks=[early_stopping(stopping_rounds=100), log_evaluation(period=1)])

lgbm_roc_score = roc_auc_score(y_test, lgbm_clf.predict_proba(X_test)[:,1])
print('ROC AUC: {0:.4f}'.format(lgbm_roc_score))

▶ LightGBM의 경우 테스트 데이터 세트에서 ROC-AUC가 약 0.8412로 측정되었다.


전체 코드

04.santander.ipynb
1.11MB

 

728x90
반응형