본문 바로가기

Data Science/Machine Learning 구현

[ML] Scikit-learn을 이용해 Ensemble 모델들 구현하기

반응형

이번 포스팅에서는 Scikit-learn library를 이용해서 여러가지 Ensemble 모델들을 구현해보는 방법에 대해서 알아보려고 한다. 해당 포스팅에서는 Ensemble에 대한 이론적인 부분은 주요내용으로 다루지 않으므로 이론적인 내용에 대해 궁금하다면 여기를 참고하자.

 

https://jsideas.net/snapshot_ensemble/

 

구현해볼 여러가지 앙상블 모델들에 대한 목차는 다음과 같다.

 

1. Simple Voting

2. Bagging - Random Forest

3. Boosting - GBM

4. Boosting - XGBoost

5. Boosting - LightGBM

1. Simple Voting

Voting이라는 방법은 앙상블 모델의 기본적인 방법론 중 하나라고 볼 수 있다. 이 Voting도 구체적으로는 2가지 종류로 나누어 질 수 있다.

 

  • Hard Voting : 다수의 분류기들 간에 다수결로 최종 클래스를 선정하는 방법
  • Soft Voting : 다수의 분류기들이 각각 분류한 확률값들을 기반으로 평균 확률값을 내어서 더 높은 확률값을 갖는 클래스로 최종 선정하는 방법

Hard Voting 방법

 

일반적으로 성능은 Soft voting 방법이 더 우수하다고 알려져 있다. 이를 Scikit-learn을 이용해서 구현해보면 다음과 같다. 하드보팅을 이용할지 소프트 보팅을 이용할지는 옵션으로 선택해주면 된다.

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

# 개별 모델을 정의해주어야 한다
lr_clf = LogisticRegression()
knn_clf = KNeighborsClassifier()

# 개별 모델을 소프트 보팅 기반의 앙상블 모델로 구현한 분류기(기본값은 하드보팅방식)
# 개별 모델을 estimators라는 리스트 인자에다가 (key, value) 튜플 형태로 넣어주기
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=42)
# Voting분류기 학습
vo_clf.fit(x_train, y_train)
pred_y = vo_clf.predict(x_test)
print('Voting 분류기 정확도', accuracy_score(y_test, pred_y))

# LR, KNN 개별모델로도 학숩해보기
models = [lr_clf, knn_clf]
for model in models:
    model.fit(x_train, y_train)
    pred_y = model.predict(x_test)
    model_name = model.__class__.__name__
    print(f"{model_name} 정확도 :{accuracy_score(y_test, pred_y)}")

2. Bagging - Random Forest

배깅방식의 대표적인 모델인 랜덤 포레스트이다. 다른 앙상블 모델도 마찬가지겠지만 랜덤 포레스트는 다양한 파라미터 값을 가진다. 해당 코드에서 모든 파라미터값들을 이용해 GridSearch 해보진 않았지만 모든 파라미터를 보기 위해서는 Scikit-learn 공식 문서를 참조하자.

하단 코드의 출력값들은 성능지표값들이므로 생략하겠다. 모델을 구축하고 학습시키는 코드에 집중하는 것을 추천한다.

#### 해당 함수들은 사전적으로 정의된 함수이므로 skip해도 무방하다.
# 원본 데이터 중복 제거해주는 함수
def get_new_feature_name_df(old_feature_name_df):
    feature_dup_df = pd.DataFrame(data=old_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, 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():
    feature_name_df = pd.read_csv('features.txt', sep='\s+',
                                 header=None, names=['column_index', 'column_name'])
    # 중복된 칼럼 새롭게 수정
    new_feature_df = get_new_feature_name_df(feature_name_df)
    # feature이름을 변수로 부여하기 위해 리스트 객체로 반환
    feature_name = new_feature_df.iloc[:,1].values.tolist()
    
    x_train = pd.read_csv('./train/X_train.txt',
                         sep='\s+', names=feature_name)
    x_test = pd.read_csv('./test/X_test.txt',
                        sep='\s+', names=feature_name)
    
    y_train = pd.read_csv('./train/y_train.txt',
                         sep='\s+', header=None)
    y_test = pd.read_csv('./test/y_test.txt',
                        sep='\s+', header=None)
    
    return x_train, x_test, y_train, y_test
    
# Random Forest로 학습시키기
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings(action='ignore')

x_train, x_test, y_train, y_test = get_human_dataset()

rf_clf = RandomForestClassifier()
rf_clf.fit(x_train, y_train)
y_pred = rf_clf.predict(x_test)
acc = accuracy_score(y_test, y_pred)
print(f"랜덤 포레스트 정확도 : {acc: .4f}")

저번시간에 배웠던 GridSearchCV를 이용해 교차검증도 하면서 최적의 파라미터를 찾아보자.

# GridSearchCV로 최적의 하이퍼파라미터 찾아주면서 교차검증 해보기
from sklearn.model_selection import GridSearchCV

# search 해볼 파라미터 정의
# n_estimators는 결합해줄 약한 분류기들 개수(디폴트값은 10)
# 나머지 하이퍼파라미터는 Decision Tree와 동일하지만 
# max_features값은 sqrt를 적용한다는 점!
params = {
    'n_estimators' : [50],
    'max_depth': [6,8,10],
    'min_samples_leaf':[8,12,16],
    'min_samples_split':[8,16,20]
}
# n_jobs = -1 로 모든 CPU 집중시키기
rf_clf = RandomForestClassifier(random_state=42, n_jobs=-1)
grid_cv = GridSearchCV(rf_clf, param_grid=params,
                      cv=2, n_jobs=-1)
grid_cv.fit(x_train, y_train)

# 최적의 파라미터와 최고의 성능 도출
print(f"최적의 파라미터 : {grid_cv.best_params_}")
print(f"최고 성능 : {grid_cv.best_score_}")

# 위에서 도출된 최적의 파라미터로 test데이터에 검증해보기
rf_clf = RandomForestClassifier(max_depth=8, min_samples_leaf=8,
                               min_samples_split=8,
                               n_estimators=50, random_state=43)
rf_clf.fit(x_train, y_train)
y_pred = rf_clf.predict(x_test)
acc = accuracy_score(y_test, y_pred)

print(f"최고의 파라미터를 적용한 모델의 검증 데이터에 대한 성능:{acc:.4f}")

3. Boosting - GBM(Gradient Boosting Machine)

GBM은 경사하강법에 기반하여 부스팅하는 기법이다. 순차적으로 분류기들을 학습하기 때문에 학습시간이 상대적으로 오래걸린다는 단점이 있다.

from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import accuracy_score
import time

x_train, x_test, y_train, y_test = get_human_dataset()

# GBM 수행 시간 측정해보기 - 시작하는 시간
start_time = time.time()

gb_clf = GradientBoostingClassifier()
gb_clf.fit(x_train, y_train)
gb_pred = gb_clf.predict(x_test)
gb_acc = accuracy_score(y_test, gb_pred)

print(f"모델 정확도 : {gb_acc :.4f}")
print(f"학습 시간 : {time.time()-start_time :.4f}")

## GridSearchCV 이용해서 교차검증&최적의 파라미터 찾기
from sklearn.model_selection import GridSearchCV

params = {
    'n_estimators':[20,40],
    'learning_rate':[0.05, 0.1]
}

grid_cv = GridSearchCV(gb_clf, param_grid=params,
                      n_jobs=-1, cv=2, verbose=1)
grid_cv.fit(x_train, y_train)
print("최적의 파라미터:", grid_cv.best_params_)
print("최고의 정확도 :", grid_cv.best_score_)

4. Boosting - XGBoost(eXtra Gradient Boosting)

XGBoost는 병렬처리가 불가능한 GBM의 단점을 보완한다. GPU지원이 가능하며 추가적으로 정규화 기능, Tree pruning 기능, Early Stopping, 내장된 교차검증과 결측치 처리 등 여러가지 부가기능이 담겨져 있다. 

 

XGBoost는 본래 C++언어에 기반해 만들어졌다. 하지만 Python을 사용해서도 XGBoost를 사용할 수 있도록 만들었고 또 Scikit-learn 라이브러리를 위한 것이 개별적으로 존재한다. 해당 글에서는 Scikit-learn에서 제공하는 라이브러리에 맞는 문법을 적용한 코드 예시를 소개하려 한다.

from xgboost import XGBClassifier

# 원래 여기 데이터에는 검증 데이터를 넣어야함 Test 데이터 넣으면 안됨!
# 검증 데이터 넣어주어서 교차검증 해보도록하기
evals = [(x_test, y_test)]
xgb_wrapper = XGBClassifier(n_estimators=400, learning_rate=0.1,
                           max_depth=3)
# eval_metric넣어주면서 검증 데이터로 loss 측정할 때 사용할 metric 지정
xgb_wrapper.fit(x_train, y_train, early_stopping_rounds=200,
               eval_set=evals, eval_metric='logloss')

preds = xgb_wrapper.predict(x_test)
preds_proba = xgb_wrapper.predict_proba(x_test)[:, 1]
print(preds_proba[:10])

XGBoost는 feature별 중요도를 plot 해주는 라이브러리를 개별적으로 제공한다. feature별 중요도를 보기 위해서 간단하게 시각화하는 코드는 다음과 같다.

# feature별 중요도 시각화하기
from xgboost import plot_importance

fig, ax = plt.subplots(figsize=(9,11))
plot_importance(xgb_wrapper, ax)

출력화면은 다음과 같다.

Feature Importance를 나타낸 그래프

5. Boosting - LightGBM

LightGBM은 기본적으로 Leaf-wise-tree 알고리즘을 사용한다. 지금까지 살펴보았던 XGBoost는 Level-wise-tree 알고리즘을 사용해 왔다. 두 개의 차이점은 다음과 같다. 일반적인 Level-wise-tree 알고리즘은 Tree가 깊어지는 것을 막기 위해 Tree의 균형을 유지하는 방식을 고수한다. 반면에 Leaf-wise-tree 알고리즘은 Tree가 깊어진다고 해도 높은 loss값을 갖고 있는 Leaf 노드를 계속적으로 분할하면서 loss를 낮추는 방식으로 진행된다. 두 알고리즘의 차이에 대한 직관적인 그림은 다음과 같다.

 

https://www.mdpi.com/2227-7390/8/5/765/htm

 

LightGBM은 대용량 데이터 처리에 적합하며 XGBoost와 마찬가지로 병렬처리인 GPU를 지원해준다. 뿐만 아니라 메모리를 적게 사용하므로 빠른속도가 장점이다. 하지만 데이터 수가 너무 적을 때는 과적합 문제를 일으키기도 한다. 

 

다음은 LightGBM을 Scikit-learn을 이용해 구현하는 예시 코드이다.

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

dataset = load_breast_cancer()
x_features = dataset.data
y_labels = dataset.target

x_train, x_test, y_train, y_test = train_test_split(x_features,
                                                   y_labels,
                                                   test_size=0.2,
                                                   random_state=42)

lgbm_wrapper = LGBMClassifier(n_estimators=200)

#검증 데이터 할당(Test데이터 사용했지만 실제로는 Test데이터 사용하면 치팅임)
evals = [(x_test, y_test)]
lgbm_wrapper.fit(x_train, y_train, early_stopping_rounds=100,
                eval_set=evals, eval_metric='logloss',
                verbose=True)
#예측값 할당
preds = lgbm_wrapper.predict(x_test)
#예측값 확률중 Positive(1)로 분류될 확률만 할당
preds_proba = lgbm_wrapper.predict_proba(x_test)[:,1]

print(f"예측값 : {preds}")
print(f"예측값 확률 {preds_proba}")

LightGBM도 XGBoost와 마찬가지로 feature별로 중요도를 보여주는 라이브러리를 제공한다. 이를 시각화하는 방법은 XGBoost 방법과 동일하다.

# LightGBM도 feature Importance출력할 수 있는 라이브러리 제공해주고 있음
from lightgbm import plot_importance
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(11,8))
plot_importance(lgbm_wrapper, ax)

 

반응형