본문 바로가기

Study doc./Machine Learning

[ML] 스태킹(Stacking) 완벽 정리

이 포스팅만 읽으면 스태킹을 쉽게 이해할 수 있도록 정리해봤습니다 :)

천천히 읽어볼까요?

 

순서

  1. 스태킹의 핵심 개념
  2. 스태킹의 원리 
    1. 목표 
    2. 기본모델
    3. 최종모델
  3. 코드

 


 

1. 스태킹의 핵심 개념

스태킹은 여러 가지 모델들의 예측값을 최종 모델의 학습 데이터로 사용하는 예측하는 방법 입니다.

아직 잘 와닿지가 않나요?

간단한 예시를 아래 그림과 함께 들어보겠습니다.

 

저는 knn, logistic regression, randomforest, xgboost 모델을 이용해서 4종류의 예측값을 구했습니다.

그리고 이 예측값을 하나의 데이터 프레임으로 만들어 최종모델인 lightgbm의 학습데이터로 사용했습니다.

지금은 기본 모델로부터 예측된 값들이 최종모델의 학습데이터로 사용된다는 것만 이해하면 됩니다.

자세한 내용은 다음 절에서 다루겠습니다.

 

 

스태킹 예시

이렇게 하면 성능이 무조건 좋아지냐?

그건 아닙니다. 현실 모델에서도 많이 사용되지 않습니다.

다만 성능이 올라가는 경우가 더러 있기 때문에 캐글이나 데이콘과 같은 미세한 성능 차이로 승부를 결정하는 대회에서 주로 사용됩니다.

특히 기본 모델로 4개 이상을 선택해야 좋은 결과를 기대할 수 있다고 합니다!! 

그럼 이제 스태킹의 원리에 대해 알아봅시다.

 

 

2. 스태킹의 원리 

2-1. 목표 

기본 모델들을 통해 예측된 값들을 학습용 데이터로 다시 이용한다?

예측값(y값)을 다시 학습데이터로 쓰면, 그 데이터의 target 변수(레이블)는 뭐야?

 

이런 궁금증을 정리시켜주기 위해 최종 모델의 모습을 보기 쉽게 시각화해봤습니다.

 

최종 모델(메타 모델)의 데이터 모습 

여기서는 이것만 이해하시면 됩니다.

 

" 아~ 최종 모델에서는 y 예측값과 실제값이 독립변수와 종속변수로 작용하는구나. "

 

이제 저희가 알아야 할 것은 기본 모델들을 통해 'y_train_예측값'과 'y_test_예측값' 데이터프레임을 만드는 것입니다.

결론부터 말씀드리면 해당 데이터 프레임은 기본 모델의 예측값들을 옆으로 쭉 붙인 것입니다.

마치 이렇게요!

 

 

왜 하필 옆으로 붙인걸까요?

행 단위로 생각해보면 이해가 될 것입니다.

첫 행을 예로 들어보면, 각 모델들의 첫 번째 y_train 원소 값에 대한 예측값들이 있습니다.

즉 최종 모델이 하고자 하는 것은 y 실제값이 이정도 일 때, knn에서 이정도 값, 로지스틱 회귀에서 이정도 값, 랜덤 포레스트에서 이정도 값, XGBoost에서 이정도 값을 예측한다 는 것을 학습시키는 것입니다. 

 

그렇다면 학습된 모델은 기본 모델들의 예측값을 통해 실제값을 예측할 수 있겠죠?

스태킹이 이런 원리로 작동되는 것입니다.

 

2-2. 기본모델

4종류의 기본 모델 중 한 가지 모델의 동작 원리를 살펴보겠습니다(나머지 모델도 동일한 원리를 가집니다).

먼저 과적합(overfitting) 방지를 위해 교차검증을 실시합니다(ex. 3회).

그럼 아래와 같은 그림처럼 X-train을 3등분으로 나누어서 표현할 수 있는데요,

 

기본 모델을 교차검증한 모습

X_train 중 두 부분을 모델의 학습데이터로 사용하고, 나머지는 테스트 데이터로 사용합니다.

아래와 같이 말이죠!

여기서 핵심은 두 부분으로 학습시킨 모델로 두 번 테스트를 한다는 것입니다.

 

 

첫 번째 예측으로 y_train_3 에 대한 예측값(y_train_3_pred)이 생기고,

두 번째 예측으로 y_test 에 대한 예측값(y_test_pred_1)이 생깁니다.

 

그 다음, 학습 데이터로 X_train_1과 X_train_3를 사용하고, 테스트 데이터로 X_train_2와 X_test를 사용합니다.

그 결과로 y_train_2 에 대한 예측값(y_train_2_pred)이 생기고,

y_test 에 대한 예측값(y_test_pred_1)이 생깁니다.

 

마지막으로, 학습 데이터로 X_train_2과 X_train_3를 사용하고, 테스트 데이터로 X_train_1와 X_test를 사용합니다.

그 결과로 y_train_1 에 대한 예측값(y_train_1_pred)이 생기고,

y_test 에 대한 예측값(y_test_pred_1)이 생깁니다.

 

결국 y_train_1_pred 부터 y_train_3_pred 까지 총 3개를 쌓아 'y_train_예측값'을 구했습니다.

그리고 폴드마다 y_test를 예측한 값들을 평균내서 'y_test_예측값'을 구했습니다.

 

이해를 돕기위해 코드를 첨부합니다.

from sklearn.neighbors import KNeighborsClassifier

# 객체 생성
knn_clf = KNeighborsClassifier()

# 모델 학습
knn_clf.fit(X_train_1_2, y_train_1_2)

# 테스트
y_train_3_pred = knn_clf.predict(X_train_3) # y_train_3에 대한 예측
y_test_pred_1 = knn_clf.predict(X_test) # y_test에 대한 예측

#... X_train_2_3에 대한 예측, X_train_1_3에 대한 예측도 같은 방법으로 진행합니다.

# 결과
y_train_pred = pd.concat([y_train_1_pred, y_train_2_pred, y_train_3_pred], axis=0) 
y_test_pred = np.mean(y_test_pred_1, y_test_pred_2, y_test_pred_3)

 

지금 구한 값이 전체에서 어떤 역할을 하느냐?

아래 그림의 빨간 상자인 한 열을 생성한 것입니다.

 

 

이제 다른 모델들도 같은 방식으로 y_train_예측값과 y_test_예측값을 구하고,

옆으로 쭉 이어 붙이면 최종 모델을 위한 학습데이터를 완성하는 것입니다.

 

 

2-3. 최종모델

다음은 최종 모델의 학습데이터 모습입니다.

이 학습데이터는 개별 모델의 교차검증으로부터 예측된 값들로 구성되어있고,

학습데이터의 레이블은 기존 데이터의 y_train 값입니다.

 

메타 모델의 학습 데이터

 

다음은 최종 모델의 테스트 데이터 입니다.

테스트 데이터 역시 개별 모델로부터 만들어졌고,

위 그림의 학습 데이터로 학습된 모델에 아래 그림의 테스트 데이터를 입력하면 우리가 원하는 타겟값인 y_test 값이 나옵니다.

메타 모델의 테스트 데이터

 

 

전체적인 구조를 위해 한번 정리해봤습니다!!

메타 모델 구조도

 

 

3. 코드

* 코드는 파이썬 머신러닝 완벽 가이드(권철민, 2019) 교재에서 발췌한 것임을 밝힙니다.

3-1. 메타 모델에 필요한 학습데이터, 테스트 데이터 개별 모델로부터 가져오기

from sklearn.model_selection import KFold

# 메타 모델을 위한 학습 및 테스트 데이터 만들기
def get_stacking_base_datasets(model, X_train_n, y_train_n, X_test_n, n_folds):
    kf = KFold(n_splits=n_folds, shuffle=False, random_state=11)
    # 빈 배열 생성
    train_fold_pred = np.zeros((X_train_n.shape[0],1))
    test_pred = np.zeros((X_test_n.shape[0],n_folds))
    
    
    for folder_counter, (train_index, valid_index) in enumerate(kf.split(X_train_n)):
        print('폴드 세트 : ', 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) # y_train 예측, 폴드 끝나면 concat해야함
        test_pred[:, folder_counter] = model.predict(X_test_n) # y_test 예측, 폴드 끝나면 평균 낼거임
        
    test_pred_mean = np.mean(test_pred, axis=1).reshape(-1,1)
    
    return train_fold_pred, test_pred_mean # 하나의 모델에 대한 학습데이터, 테스트 데이터 생성

 

3-2. 최종 모델 (메타 모델) 실행

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 # 메타 모델

# 객체 생성
knn_clf = KNeighborsClassifier()
rf_clf = RandomForestClassifier()
dt_clf = DecisionTreeClassifier()
ada_clf = AdaBoostClassifier()
lr_final = LogisticRegression()

# 개별 모델로부터 메타 모델에 필요한 데이터 셋 만들기
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)

# 개별 모델로부터 나온 y_train 예측값들 옆으로 붙이기
Stack_final_X_train = np.concatenate((knn_train,rf_train,dt_train,ada_train), axis=1)
# 개별 모델로부터 나온 y_test 예측값들 옆으로 붙이기
Stack_final_X_test = np.concatenate((knn_test,rf_test,dt_test,ada_test), axis=1)

lr_final.fit(Stack_final_X_train, y_train)
stack_final = lr_final.predict(Stack_final_X_test)