본문 바로가기

Data Science/Machine Learning 구현

[ML] Scikit-learn을 이용한 Stacking 구현하기

반응형

이번 포스팅에서는 저번 시간에 다루었던 Ensemble(앙상블)모델의 연장선인 Stacking에 대해 알아보려고 한다. Stacking에 대한 개념과 동작 방법에 대해 시각적으로 알아보고 이를 Python과 Scikit-learn을 이용해 직접 구현하는 방법에 대해 살펴본다. 목차는 다음과 같다.

 

1. Stacking이란?

2. Stacking이 동작하는 방식

3. Scikit-learn을 이용해 Stacking을 구현해보기

1. Stacking이란?

Stacking의 'Stack'의 사전적 의미는 '쌓다' 정도로 볼 수 있다. 컴퓨터의 자료구조 중 하나로서 '스택'이라는 개념도 존재하는데, 의미가 완전하게 동일하진 않지만 '쌓다' 라는 의미의 관점에서는 통한다고 볼 수 있을 것 같다.(지극히 개인적인 의견..)

 

앙상블의 모델 종류 중 하나로서 Stacking이란, 개별적인 모델들이 학습하고 예측한 데이터를 쌓아서 또 다른 학습데이터를 만들고 이 데이터를 기반으로 메타 모델을 하나 더 만들어 예측하는 모델이다. 이렇게 정의내리면 무슨 말인지 알아듣지 못할 수도 있기에 다음 순서인 Stacking이 동작하는 방식을 통해서 이해해보자.

 

Stack이란, '쌓는다'의 의미이다.

2. Stacking이 동작하는 방식

본론에 들어가기 앞서 설명에 사용된 자료는 인프런의 '파이썬 머신러닝 완벽 가이드' 강사님인 권철민님의 자료임을 밝힌다. 

Stacking은 위에서 언급했다시피 개별적인 여러모델이 학습을하고 예측한 데이터를 다시 학습데이터로 학습하는 메타모델을 만들어서 예측하는 방법이다. 이를 간단히 도식화하게 되면 다음과 같다.

 

Stacking이 동작하는 방식

 

그렇다면 개별적인 여러 모델을 예측한 결과값을 어떻게 쌓는(stacking)다는 걸까? 다음 자료를 보면서 구체적으로 알아보자,

 

Stacking의 구체적인 동작 방식

 

그림의 왼쪽처럼 M by N의 원본 데이터가 존재할 때 개별적인 모델인 Model 1~3 이 원본 데이터를 학습하고 각각의 결과값을 1차원 array형태로 반환해준다. 그리고 결과값들을 학습데이터의 포맷형태로 변환시켜주기 위해 반환된 예측 결과값들을 Stacking 해준다. 그리고 이 Stacking한 데이터를 기반으로 사전에 설정해주었던 메타 모델을 학습시켜 최종 예측결과값을 반환하게 된다.

 

그런데 Stacking은 보통 CV(Cross Validation) 형태의 Stacking을 사용하는 것으로 알려져있다. 그렇다면 CV를 활용한 Stacking 방법은 어떻게 동작할까? 우선 개별 모델 안에서 CV하는 과정부터 살펴보자.

 

개별 모델 내부에서 CV Stacking이 동작하는 과정

 

위 그림은 K-fold라는 CV 방법을 사용했고 이 때 K값은 3으로 3개의 fold를 설정하고 가정한 후 진행되는 과정이다. 먼저 학습 데이터에서 1번 째 fold를 검증 fold로 사용하고 2,3번 fold를 기반으로 개별 모델이 학습을 한다. 그리고 검증폴드인 1번 fold로 예측값을 반환한다. 추가적으로 2,3번 fold를 기반으로 학습한 모델이 원본의 테스트 데이터셋을 대상으로 또 한 번 예측결과값을 반환한다. 

 

이렇게 되면 결국 개별 모델이 예측을 2번한 셈이고 예측결과값이 2개의 세트(정확하게 이야기하면 2개의 array)가 된다. 이 때 2개의 결과값들 중 학습 데이터 내부의 검증 fold에 기반해 예측한 결과값은 최종 메타 모델에서 학습데이터(그림 속 2번과 2-1번)로, 다른 결과값은 최종 메타 모델에서 테스트 데이터로 사용(그림 속 3번과 3-1번)이 된다. 이렇게 하면 fold 1번에 대한 완전한 iteration이 동작한 셈이다. 

 

fold 횟수를 3으로 설정했으니 이렇게 똑같은 과정을 3번 반복을 한다. 3번을 모두 실시한 후 마지막으로 해주어야 하는 것은 각각의 검증 폴드에 의해 나온 예측결과값들끼리 Stacking하고 원본 테스트 세트를 예측했을 때 나온 3번의 결과값들을 average(평균값)을 취해줌으로써 Stacking을 해준다.

 

위와 같은 과정이 개별모델 1개에서 이루어지는 CV가 적용된 Stacking 과정이다. 이제 남은 절차는 개별적인 모델들이 내놓은 예측결과값들을 기반으로 학습 데이터와 검증 데이터를 만드는 과정을 할 차례다. 다음 그림을 보자.

 

개별모델이 도출한 결과값들을 기반으로 Stacking하는 과정

 

이제 개별 모델들이 CV를 기반으로 Stacking을 함으로써 메타모델이 학습할 학습/검증 데이터를 생성을 완료했다. 이제 개별모델이 생성한 학습/검증 데이터들을 각각 Stacking을 해주자. 우선 개별 모델들이 도출해낸 학습데이터들을 Stacking한 후 최종 메타 모델을 학습시킨다. 이 때 최종 메타 모델을 학습시키는 데이터들 즉, 개별 모델들이 도출해낸 학습데이터들을 Stacking한 데이터label(정답=y값)은 맨 처음에 주어졌던 원본(raw) 학습 데이터의 label을 갖고 학습시켜야 한다. 

 

그리고 개별모델들이 도출시켰던 검증 데이터셋들도 똑같이 Stacking 시키고 최종 메타 모델이 이 Stacking한 데이터들을 기반으로 평가(검증)할 때는 원본 테스트 데이터의 label을 갖고 평가해야 한다.

3. Scikit-learn을 이용해 Stacking을 구현해보기

이제 위에서 배웠던 이론 내용들을 Scikit-learn을 이용해서 구현해보자. 본 포스팅에서는 CV가 적용된 Stacking 방식에 대한 구현 코드만 소개하려고 한다. 그리고 Scikit-learn에서 편히 제공하는 라이브러리가 있지만 이에 대한 것은 다루지 않고 Python을 이용해서 직접 CV가 적용한 Stacking 방식을 어떻게 구현하는지에 대해서 알아본다. 

 

우선 개별모델의 구축하고 개별 모델 내부에서 CV를 적용하여 Stacking하는 코드를 살펴보자.

# 개별 모델 정의
knn_clf = KNeighborsClassifier()
rf_clf = RandomForestClassifier()
ab_clf = AdaBoostClassifier()
dt_clf = DecisionTreeClassifier()
# 최종 메타 모델 정의
lr_final = LogisticRegression(C=10)

from sklearn.model_selection import KFold
from sklearn.metrics import accuracy_score
import warnings
warnings.filterwarnings(action='ignore')

# 개별모델 내부에서 CV 적용해 Stacking하는 함수 구현
def get_stacking_datasets(model, x_train_n, y_train_n, x_test_n, n_folds):
    # CV하기 위해 K-fold 설정
    kf = KFold(n_splits=n_folds, shuffle=False, random_state=42)
    
    # 최종 메타 모델이 사용할 학습 데이터 반환을 위해서 넘파이 배열을 0으로 만들어서 초기화
    train_fold_pred = np.zeros((x_train_n.shape[0], 1)) # 2차원으로
    test_pred = np.zeros((x_test_n.shape[0], n_folds))
    print(model.__class__.__name__, '모델 시작')
    
    for folder_counter, (train_idx, valid_idx) in enumerate(kf.split(x_train_n)):
        # 개별 모델 내부에서 학습하고 1개의 fold로 예측할 데이터 셋 추출
        print(f" Fold 횟수 : {folder_counter+1}")
        x_tr = x_train_n[train_idx]
        y_tr = y_train_n[train_idx]
        x_te = x_train_n[valid_idx]
        
        # 개별 모델이 학습한 후 1개의 fold데이터셋으로 예측값 반환 후 최종 메타모델이 학습할 데이터셋에 첨가
        model.fit(x_tr, y_tr)
        train_fold_pred[valid_idx, :] = model.predict(x_te).reshape(-1,1)
        # 개별 모델이 원본 데이터셋의 검증 데이터셋을 기반으로 예측 결과값 반환 후 최종 메타모델이 검증할 데이터셋에 첨가
        test_pred[:, folder_counter] = model.predict(x_test_n)
    
    # 개별모델안에서 테스트 데이터셋을 기반으로 예측한 결과값들 mean취해주고 2차원으로 바꾸어주기
    test_pred_mean = np.mean(test_pred, axis=1).reshape(-1,1)
    
    return train_fold_pred, test_pred_mean

knn_train, knn_test = get_stacking_datasets(knn_clf, x_train, y_train, x_test, 5)
rf_train, rf_test = get_stacking_datasets(rf_clf, x_train, y_train, x_test, 5)
ab_train, ab_test = get_stacking_datasets(ab_clf, x_train, y_train, x_test, 5)
dt_train, dt_test = get_stacking_datasets(dt_clf, x_train, y_train, x_test,  

결과값은 다음과 같다.

K-Fold 수행 출력

그리고 이제 개별모델이 생성한 학습/검증 데이터셋을 각각 Stacking한 후 최종모델에 학습시키고 평가를 해보자.

# 개별모델이 생성한 학습/검증 데이터 최종 메타 모델이 학습/검증하도록 결합
stack_final_x_train = np.concatenate((knn_train, rf_train, ab_train, dt_train), axis=1)
stack_final_x_test = np.concatenate((knn_test, rf_test, ab_test, dt_test), axis=1)

# 최종 메타모델로 학습
# 최종 메타모델 학습시 label은 원본 데이터의 label(y값)
lr_final.fit(stack_final_x_train, y_train)
stack_final_pred = lr_final.predict(stack_final_x_test)

# 최종 메타모델 성능 평가(비교할 때 원본 데이터의 검증 데이터 label과 비교)
print(f"최종 메타모델 정확도 : {accuracy_score(y_test, stack_final_pred):.4f}")

 

반응형