[Python] 머신러닝 완벽가이드 - 04. 분류[스태킹 앙상블]

Updated:

파이썬 머신러닝 완벽가이드 교재를 토대로 공부한 내용입니다.

실습과정에서 필요에 따라 내용의 누락 및 추가, 수정사항이 있습니다.


기본 세팅

import numpy as np
import pandas as pd

import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

import warnings
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

mpl.rc('font', family='NanumGothic') # 폰트 설정
mpl.rc('axes', unicode_minus=False) # 유니코드에서 음수 부호 설정

# 차트 스타일 설정
sns.set(font="NanumGothic", rc={"axes.unicode_minus":False}, style='darkgrid')
plt.rc("figure", figsize=(10,8))

warnings.filterwarnings("ignore")

1. 스태킹 앙상블

스태킹은 개별적인 여러 알고리즘을 서로 결합해 예측 결과를 도출한다는 점에서 배깅 및 부스팅과 공통점이 있다.

하지만 가장 큰 차이점은 개별 알고리즘으로 예측한 데이터를 기반으로 다시 예측을 수행한다는 점이다.

즉, 개별 알고리즘의 예측 결과 데이터 셋을 최종적인 메타 데이터 셋으로 만든 후 별도의 ML 알고리즘으로 최종 학습 및 예측을 진행하는 방식이다.

이런식으로 개별 모델의 예측 데이터를 기반으로 학습하고 예측하는 방식을 메타 모델이라고 한다.

1.1 기본 스태킹 모델

1.1.1 개별 모델 학습/예측/평가

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

# 위스콘신 유방암 데이터
cancer_data = load_breast_cancer()

X_data = cancer_data.data
y_label = cancer_data.target

X_train , X_test , y_train , y_test = train_test_split(X_data , y_label , test_size=0.2 , random_state=0)
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression

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

# 최종 모델
lr_final = LogisticRegression(C=10)

# 개별 모델 학습
knn_clf.fit(X_train, y_train)
rf_clf.fit(X_train , y_train)
dt_clf.fit(X_train , y_train)
ada_clf.fit(X_train, y_train)
AdaBoostClassifier(n_estimators=100)
  • KNN, 랜덤 포레스트, 결정 트리, 에이다부스트를 개별 모델로 설정하고 최종 모델은 로지스틱으로 설정하였다.
from sklearn.metrics import accuracy_score

# 개별 모델 예측 데이터 셋 및 정확도
knn_pred = knn_clf.predict(X_test)
rf_pred = rf_clf.predict(X_test)
dt_pred = dt_clf.predict(X_test)
ada_pred = ada_clf.predict(X_test)

knn_acc = accuracy_score(y_test, knn_pred)
rf_acc = accuracy_score(y_test, rf_pred)
dt_acc = accuracy_score(y_test, dt_pred)
ada_acc = accuracy_score(y_test, ada_pred)

print(f'KNN 정확도: {knn_acc:.4f}')
print(f'랜덤 포레스트 정확도: {rf_acc:.4f}')
print(f'결정 트리 정확도: {dt_acc:.4f}')
print(f'에이다부스트 정확도: {ada_acc:.4f}')
KNN 정확도: 0.9211
랜덤 포레스트 정확도: 0.9649
결정 트리 정확도: 0.9123
에이다부스트 정확도: 0.9561
  • 개별 모델로 test set 예측 결과를 저장하고 성능 평가를 진행하였다.

1.1.2 메타 데이터

# 개별 예측 결과를 stacking
pred = np.array([knn_pred, rf_pred, dt_pred, ada_pred])
print(pred.shape)

# 개별 모델의 예측 결과를 피처로 생성
pred = pred.T
print(pred.shape)
(4, 114)
(114, 4)
  • 개별 모델의 예측 결과를 결합하고 전치하여 메타 데이터를 생성한다.

1.1.3 최종 학습/예측/평가

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

print(f'최종 메타 모델의 예측 정확도: {lr_final_acc:.4f}')
최종 메타 모델의 예측 정확도: 0.9737
  • 개별 모델의 정확도에 비해 성능이 향상된 것이 확인 된다(무조건 향상되는 것은 아니다).

1.2 CV 스태킹 모델

앞서 작업은 개별 모델로 test set의 예측 결과를 결합해 메타 데이터를 생성

메타 데이터와 test set으로 최종 모델 학습, 최종 모델을 이용해 메타 데이터의 예측/평가를 진행하였다.

이러한 방법은 다음과 같은 문제가 있다.

  • 최종 학습에 test set을 사용했기에 과적합 문제가 발생할 수 있다.

  • 또한 최종 학습에 메타 데이터를 사용하고 메타 데이터에 대한 예측을 진행하였다.

CV 스태킹은 이를 개선한 방법으로 다음 예시로 이해해보자.

  • 개별 모델이 4개이고, 데이터는 100개로 train 60개, test 40개, fold는 3이라 가정

  • 첫 번째 모델에서 train 60개에 대해 학습시 train_fold 40개, test_fold 20개로 예측을 진행할 것이다.

  • 각 fold별로 예측값은 20개씩 3번 구해지고 train 갯수와 같은 총 60개의 예측값을 얻을 수 있을 것이다. 이를 train 메타 데이터로 사용한다.

  • 각 fold별로 test_fold가 아닌 test에 대해 예측을 진행한다. 즉, train_fold 40개로 test 40개를 예측한다.

  • 3번 반복하면 40개씩 3묶음의 예측 데이터가 생성될 것이다. 이를 평균내어 40개의 test 메타 데이터로 사용한다.

  • 개별 모델이 4개이므로 train 메타 데이터는 60 x 4, test 메타 데이터는 40 x 4로 생성 된다.

  • train 메타 데이터를 피처로, y_train을 레이블로 학습 후, test 메타 데이터에 대해 예측하고 y_test와 비교로 평가한다.

1.2.1 CV 스태킹 함수

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

# 개별 모델별 메타 데이터
def get_stacking_base_datasets(model, X_train, y_train, X_test, n_folds ):
    # KFold 생성
    kf = KFold(n_splits=n_folds, shuffle=False, random_state=0)
    
    # 메타 데이터 반환을 위한 기본 배열
    train_cnt = X_train.shape[0]
    test_cnt = X_test.shape[0]
    train_meta = np.zeros((train_cnt, 1))
    test_meta = np.zeros((test_cnt, n_folds))
    
    print(model.__class__.__name__ , ' model 시작 ')
    
    # train 데이터를 기반으로 fold를 나눠 학습/예측
    for i , (train_fold_idx, test_fold_index) in enumerate(kf.split(X_train)):
        # train, test fold 생성
        print(f'\t 폴드 세트: {i+1} 시작 ')
        x_train_fold = X_train[train_fold_idx] 
        y_train_fold = y_train[train_fold_idx] 
        x_test_fold = X_train[test_fold_index]  
        
        # train_fold로 학습
        model.fit(x_train_fold , y_train_fold)       
        
        # train 메타 데이터 생성 (x_test_fold 예측)
        train_meta[test_fold_index, :] = model.predict(x_test_fold).reshape(-1,1)
        
        # test 메타 데이터 생성 (x_test 예측) - 평균 전
        test_meta[:, i] = model.predict(X_test)
            
    # test 메타 데이터 생성 - 평균 진행
    test_meta_mean = np.mean(test_meta, axis=1).reshape(-1,1)    
    
    # train test 메타 데이터 반환
    return train_meta , test_meta_mean
knn_train, knn_test = get_stacking_base_datasets(knn_clf, X_train, y_train, X_test, 7)
rf_train, rf_test = get_stacking_base_datasets(rf_clf, X_train, y_train, X_test, 7)
dt_train, dt_test = get_stacking_base_datasets(dt_clf, X_train, y_train, X_test,  7)    
ada_train, ada_test = get_stacking_base_datasets(ada_clf, X_train, y_train, X_test, 7)
KNeighborsClassifier  model 시작 
	 폴드 세트: 1 시작 
	 폴드 세트: 2 시작 
	 폴드 세트: 3 시작 
	 폴드 세트: 4 시작 
	 폴드 세트: 5 시작 
	 폴드 세트: 6 시작 
	 폴드 세트: 7 시작 
RandomForestClassifier  model 시작 
	 폴드 세트: 1 시작 
	 폴드 세트: 2 시작 
	 폴드 세트: 3 시작 
	 폴드 세트: 4 시작 
	 폴드 세트: 5 시작 
	 폴드 세트: 6 시작 
	 폴드 세트: 7 시작 
DecisionTreeClassifier  model 시작 
	 폴드 세트: 1 시작 
	 폴드 세트: 2 시작 
	 폴드 세트: 3 시작 
	 폴드 세트: 4 시작 
	 폴드 세트: 5 시작 
	 폴드 세트: 6 시작 
	 폴드 세트: 7 시작 
AdaBoostClassifier  model 시작 
	 폴드 세트: 1 시작 
	 폴드 세트: 2 시작 
	 폴드 세트: 3 시작 
	 폴드 세트: 4 시작 
	 폴드 세트: 5 시작 
	 폴드 세트: 6 시작 
	 폴드 세트: 7 시작 
  • 각 모델별로 train, test 메타 데이터를 생성하였고 이를 합치기만 하면 최종 메타 데이터가 완성된다.

1.2.2 메타 데이터

final_X_train_meta = np.concatenate((knn_train, rf_train, dt_train, ada_train), axis=1)
final_X_test_meta = np.concatenate((knn_test, rf_test, dt_test, ada_test), axis=1)

print('원본 train 피처 데이터 Shape:',X_train.shape)
print('원본 test 피처 데이터 Shape:',X_test.shape)
print('최종 train 피처 메타 데이터 Shape:', final_X_train_meta.shape)
print('최종 test 피처 메타 데이터 Shape:',final_X_test_meta.shape)
원본 train 피처 데이터 Shape: (455, 30)
원본 test 피처 데이터 Shape: (114, 30)
최종 train 피처 메타 데이터 Shape: (455, 4)
최종 test 피처 메타 데이터 Shape: (114, 4)
  • 최종 메타 데이터가 잘 생성되었고 한 가지, 메타 데이터의 열 갯수는 개별 모델 갯수임을 기억하자.

1.2.3 최종 학습/예측/평가

lr_final.fit(final_X_train_meta, y_train)
final_pred = lr_final.predict(final_X_test_meta)
final_acc = accuracy_score(y_test, final_pred)
print(f'최종 메타 모델의 예측 정확도: {final_acc:.4f}')
최종 메타 모델의 예측 정확도: 0.9737
  • 정확도 수치 자체는 기본 스태킹 모델과 동일하게 나타났다.

1.3 참고사항

지금까지 예제에서 개별 모델의 파라미터를 튜닝하지 않았다.

실제로 스태킹을 진행할 때는 각 개별 모델에서 최적 파라미터로 튜닝 후 스태킹 모델을 만들어야한다.

스태킹 모델을 현실에서 적용하는 경우는 드물지만 캐글 대회 등에서 성능을 조금이라도 올리고 싶을때 사용된다.

스태킹 모델을 사용할 땐 많은 개별 모델이 있어야 예측 성능이 향상된다(물론 반드시 성능 향상이 되는 것은 아니다).

Leave a comment