Go together

If you want to go fast, go alone. If you want to go far, go together.

파이썬 머신 러닝 완벽 가이드

분류 - 스태킹 앙상블

NowChan 2021. 11. 16. 18:27

배운 내용

  1. Stacking Esemble
  2. np.array()
  3. np.transpose()
  4. CV 세트 기반의 스태킹
  5. np.concatenate()

스태킹 앙상블

스태킹(Stacking)은 개별 알고리즘의 예측 결과 데이터 세트를 메타 데이터 세트로 만들어 별도의 ML 알고리즘으로 최종 학습을 수행하고 테스트 데이터를 기반으로 다시 최종 예측을 수행하는 방식입니다. (이렇게 개별 모델의 예측된 데이터 세트를 다시 기반으로 하여 학습하고 예측하는 방식을 메타 모델이라고 합니다.)

 

스태킹 모델은 두 종류의 모델이 필요합니다. 첫 번째는 개별적인 기반 모델이고, 두 번째는 이 개별 기반 모델의 예측 데이터를 학습 데이터로 만들어서 학습하는 최종 메타 모델입니다. 스태킹 모델의 핵심은 여러 개별 모델의 예측 데이터를 각각 스태킹 형태로 결합해 최종 메타 모델의 학습용 피처 데이터 세트와 테스트용 피처 데이터 세트를 만드는 것입니다.

 

스태킹을 적용할 때는 많은 개별 모델이 필요하고, 2~3개의 개별 모델만 결합해서는 쉽게 예측 성능을 향상시킬 수 없습니다. 일반적으로 성능이 비슷한 모델을 결합해 좀 더 나은 성능 향상을 도출하기 위해 적용됩니다.

 

스태킹 모델

스태킹 모델은 위 그림처럼 여러 개의 모델에 대한 예측값을 합한 후, 즉 스태킹 형태로 쌓은 뒤 이에 대한 예측을 다시 수행하는 것입니다.

 

스태킹 모델 계산 과정

M개의 로우, N개의 피처(칼럼)을 가진 데이터 세트에 스태킹 앙상블을 적용한다고 가정하겠습니다. 학습에 사용할 ML 알고리즘 모델은 모두 3개입니다. 먼저 모델별로 각각 학습한 뒤 예측을 수행하면 M개의 로우를 가진 1개의 레이블 값을 도출할 것입니다. 이 Mx1이 3개가 도출 되므로 Mx3의 스태킹한 데이터 세트에 대해 최종 모델을 적용해 예측하는 것이 스태킹 앙상블 모델입니다.

 

기본 스태킹 모델

기본 스태킹 모델을 위스콘신 암 데이터 세트에 적용해보겠습니다.

import numpy as np

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

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

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)

스태킹에 사용될 머신러닝 알고리즘 클래스를 생성합니다. 개별 모델은 KNN, 랜덤 포레스트, 결정 트리, 에이다 부스트이며 이들 모델의 예측 결과를 합한 데이터 세트로 학습/예측하는 최종 모델은 로지스틱 회귀입니다.

# 개별 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(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)

개별 모델의 예측 데이터 세트를 반환하고 각 모델의 예측 정확도를 살펴보겠습니다.

# 학습된 개별 모델들이 각자 반환하는 예측 데이터 세트를 생성하고 개별 모델의 정확도 측정.
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)

print('KNN 정확도: {0:.4f}'.format(accuracy_score(y_test, knn_pred)))
print('랜덤 포레스트 정확도: {0:.4f}'.format(accuracy_score(y_test, rf_pred)))
print('결정 트리 정확도: {0:.4f}'.format(accuracy_score(y_test, dt_pred)))
print('에이다부스트 정확도: {0:.4f}'.format(accuracy_score(y_test, ada_pred)))
'''
KNN 정확도: 0.9211
랜덤 포레스트 정확도: 0.9649
결정 트리 정확도: 0.9123
에이다부스트 정확도: 0.9561
'''

개별 알고리즘의 예측된 값을 열방향으로 붙여서 피처 값으로 만들어, 최종 메타 모델에 학습 데이터로 사용하겠습니다. 반환된 예측 데이터 세트는 1차원 형태 ndarray이므로 예측 결과들을 행 형태로 붙인 뒤, ndarray.transpose()를 이용해 행과 열 위치를 바꿔줍니다.

 

np.array() & np.transpose()

pred = np.array([knn_pred, rf_pred, dt_pred, ada_pred])
print(pred.shape)

# transpose를 이용해 행과 열 위치를 교환. 칼럼 레벨로 각 알고리즘의 예측 결과를 피처로 만듦.
pred = np.transpose(pred)
print(pred.shape)
'''
(4, 114)
(114, 4)
'''

이렇게 예측 데이터로 생성된 데이터 세트를 기반으로 최종 메타 모델인 로지스틱 회귀를 학습하고 예측 정확도를 측정하겠습니다.

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

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

개별 모델의 예측 데이터를 스태킹으로 재구성해 최종 메타 모델에서 학습하고 예측한 결과입니다. 예측 정확도가 향상되진 않았지만, 많은 모델을 추가하면 향상될 수 있습니다. lr_final을 테스트할 데이터가 없어 Estimator.predict()에 학습에 사용한 데이터를 넣어 예측했습니다.

 

CV 세트 기반의 스태킹

CV 세트 기반의 스태킹 모델은 앞의 예제에서 마지막에 메타모델이 학습한 데이터 세트와 테스트한 데이터 세트가 같아서 과적합이 일어날 수 있는 문제를 해결합니다. 개별 모델들이 각각 교차 검증으로 메타 모델을 위한 학습용 스태킹 데이터 생성과 예측을 위한 테스트용 스태킹 데이터를 생성한 뒤 이를 기반으로 메타 모델이 학습과 예측을 수행합니다.

 

이 과정을 단계별로 나타내면 아래와 같습니다.

  1. 각 모델별로 원본 학습/테스트 데이터를 예측한 결과를 저장합니다.
  2. 각 모델들의 학습 데이터의 예측 결과들을 스태킹해 최종 메타 모델의 학습 데이터를 만들고, 테스트 데이터의 예측 결과들을 스태킹해 최종 메타 모델의 테스트 데이터를 만듭니다.

 

1단계 도식화

CV=3이라고 할 때, 학습 데이터에서는 검증 폴드를 변경하며 총 3번 예측한 결과값을 모두 저장해 최종 메타 모델에서 학습 데이터로 사용합니다. 그리고 이 때, 각 검증 폴드를 변경하기 전, 테스트 데이터 세트를 학습된 개별 모델에서 예측하고 결과들을 저장하고 이들을 평균해서 최종 메타 모델에서 테스트 데이터로 활용합니다. 

2단계 도식화

1단계에서 만들어진 메타 모델 학습용 데이터들을 모두 스태킹해 원본 학습용 레이블과 비교하며 최종 메타 모델을 학습합니다.

메타 테스트용 데이터들을 모두 스태킹해 학습된 최종 메타 모델에 넣어 예측하고 이를 원본 테스트용 레이블과 비교하며 예측 정확도를 측정합니다.

 

1단계를 코드로 구현한 get_stacking_base_datasets( ) 함수를 만들어보겠습니다.

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

이제 개별 분류 모델 별로 get_stack_base_model( ) 함수를 수행합니다.

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)

 

 np.concatenate()

스텝 2를 구현할 것인데, 단지 각 모델 별 학습 데이터와 테스트 데이터를 합치기만 하면 됩니다. np.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)
print('원본 학습 피처 데이터 Shape:', X_train.shape, '원본 테스트 피처 데이터 Shape:', X_test.shape)
print('스태킹 학습 피처 데이터 Shape:', Stack_final_X_train.shape,\
      '스태킹 테스트 피처 데이터 Shape:', Stack_final_X_test.shape)
'''
원본 학습 피처 데이터 Shape: (455, 30) 원본 테스트 피처 데이터 Shape: (114, 30)
스태킹 학습 피처 데이터 Shape: (455, 4) 스태킹 테스트 피처 데이터 Shape: (114, 4)
'''

피처들을 이용해 예측한 확률을 각 모델별로 반환한 것을 스태킹하므로 ROW 레벨은 같고, COLUMN 레벨은 ML 모델 갯수와 같습니다.

 

최종 메타 모델인 LogisticRegression에 학습시키고 정확도를 측정해보겠습니다.

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)))
'''
최종 메타 모델의 예측 정확도: 0.9737
'''

예측 정확도가 전보다 높게 나왔습니다. 지금까지의 예제에서는 개별 모델의 알고리즘에서 파라미터 튜닝을 하지 않았지만, 일반적으로 스태킹을 이루는 모델은 파라미터를 최적으로 튜닝한 후 스태킹합니다. 일반적으로 스태킹 모델의 파라미터 튜닝은 개별 알고리즘 모델의 파라미터를 최적으로 튜닝하는 것을 말합니다.

 

정리

분류에서 가장 많이 사용되는 앙상블에 대해 집중적으로 배웠습니다. 대부분의 앙상블 기법은 결정 트리 기반의 다수의 약한 학습기(Weak learner)를 결합해 예측 오류를 줄입니다. 

 

앙상블 기법의 대표적인 것은 배깅과 부스팅입니다. 배깅 방식은 학습 데이터 중복을 허용해 다수의 세트로 샘플링 하여 다수의 약한 학습기가 학습한 뒤 최종 결과를 결합해 예측하는 방식입니다. 배깅 방식의 대표적인 것은 랜덤 포레스트입니다.

 

부스팅은 학습기들이 순차적으로 학습을 진행하면서 예측이 틀린 데이터에 가중치를 부여해 다음번 학습기가 학습할 때 이전 보다 높은 정확도로 예측할 수 있도록 도와줍니다. GBM이 대표적인 방법이지만 수행 시간이 너무 오래 걸리는 단점이 있스빈다. XGBoost와 LightGBM이 이 단점을 해결한 대표적인 머신러닝 패키지입니다.

 

마지막으로 배운 스태킹 모델은 여러 개별 모델들이 생성한 예측 데이터를 기반으로 최종 메타 모델이 학습해 별도의 학습 데이터와 예측 테스트 데이터 세트를 재생성하는 기법입니다. 스태킹 모델의 핵심은 메타 모델이 학습할 학습용 데이터 세트와 테스트 데이터 세트를 개별 모델의 예측값들을 스태킹 형태로 결합해 생성하는데 있습니다.

 

 

 

출처: 파이썬 머신러닝 완벽 가이드(권철민)

사진 출처:https://towardsdatascience.com/simple-model-stacking-explained-and-automated-1b54e4357916https://velog.io/@dbj2000/MLhttps://techblog-history-younghunjo1.tistory.com/103

https://velog.io/@sset2323/04-10.-%EC%8A%A4%ED%83%9C%ED%82%B9-%EC%95%99%EC%83%81%EB%B8%94