Coding

[scikit-learn] Ensemble 앙상블

linguana 2021. 2. 4. 16:36

Introduction

사람들은 차를 구매하려고 할 때, 웬만하면 딜러 매장에 가서 바로 차를 구입하려고 하진 않을 것이다. 그 딜러가 말한 가격이나 사실이 맞나 확인을 하기 위해서 주변 사람들의 조언도 듣고, 인터넷의 검색도 할 것이다.

 

머신러닝에서는 이러한 상황과 마찬가지로, 일단은 여러가지의 모델로부터 결과를 도출한 후, 이 사실들을 종합하여 최종 결론을 낸다. 그것이 바로 앙상블(Ensemble)이다. (앙상블의 사전적인 의미는 '전체적인 어울림이나 통일'를 뜻한고 음악에서는 2명 이상의 합창 혹은 합주를 의미한다)

 

예시를 통해 이 개념을 이해해보도록 하자.

영화 감독인 당신은 흥미롭고 중요한 주제를 다룬 단편영화 한 편 제작하였다. 당신은 이 영화를 대중에게 공개하기 전에 사전 평가를 받아보려고 한다. 어떤 방식이 있을까?

 

  1. 첫째로, 주위 친구들에게 평가해달라고 부탁할 수 있다. 이 경우에는 아무리 당신의 작품이 형편 없어도 친구들은 당신을 정말 좋아하기 때문에 낮은 별점을 줘서 마음의 상처를 내고 싶지 않을 수 있다.
  2. 둘째로, 직장 동료 5명에게 평가를 받을 수도 있다. 아마 당신의 친구들보다는 더 솔직한 평을 들을 수 있겠다. 비록 그들은 해당 주제에 관한 전문가는 아니기 때문에 당신의 영화가 담고 있는 특유의 "감성"은 못 느낄 수 있겠지만, 전반적으로 영화의 구도나 음향이나 하는 일반적인 사항은 비전문가들보단 더 잘 이해할 것이다.
  3. 셋째로, 50명에게 평가를 받을 수도 있다. 이 사람들 중에는 당신의 친구나 동료도 있을 수 있겠지만 완전히 관계 없는 낯선 사람일 수도 있다. 앞선 두 경우에서 편향성이 있는 것과 달리 이 경우에는 다양한 의견을 얻음으로써 좀 더 일반화된 반응을 기대할 수 있다.

이처럼 머신러닝에서도 단 한 개의 모델보단 다양한 모델을 실행시키는 것이 더 좋은 결과를 도출할 수 있다. 이것이 앙상블의 핵심 개념이다.


Prerequisites

개념은 이제 알겠다. 그럼 구체적으로 어떻게 앙상블을 구현하는지 알아보도록 하자.

데이터 사이언스 분야에서 경진대회를 나가본 적이 한 번이라도 있다면 알고 있겠지만, 앙상블은 좋은 결과치를 내기 위해 반드시 알아야 할 핵심 내용이다. 혹자는 앙상블 모델이 높은 정확도를 내기 위한 가장 매력적인 모델이라고도 한다. 

 

본격적인 내용을 들어가기 전에, 이 글을 이해하기 위해 필요한 기본 지식은 다음과 같다:


Basic Models

이 파트에서는 기초 앙상블 학습 테크닉 세가지를 알아보도록 하자.

 

(1) Max Voting

Max Voting(다수결 방법)은 일반적으로 <분류> 문제에서 활용된다. 각 모델이 예측하는 값은 하나의 '투표'로 해석되며, 다수결을 받은 예측치가 최종 예측으로 사용된다. 

 

앞선 예시를 계속해서 보자면, 당신의 영화에 대해서 동료 5명이 별점을 주었다고 하자. 5명 중 2명은 5점, 3명은 4점을 줬다면, 당신의 영화는 최종적으로 4점인 영화가 되는 것이다.

  동료 1 동료 2 동료 3 동료 4 동료 5
평점 5 4 5 4 4

 

아래의 코드로 이해해보도록 하자.

x_train은 독립변수로 이루어진 훈련 데이터이고, y_train은 종속변수로 이루어진 훈련 데이터이다. 검정(validation) 세트는 x_test(독립변수)와 y_test(종속변수)이다. 

 

(1) 첫번째는 밑바닥에서부터 하는 방식이다.

# 모듈 임포팅 없는 쌩 코딩
model1 = tree.DecisionTreeClassifier()
model2 = KNeighborsClassifier()
model3 = LogisticRegression()

model1.fit(x_train, y_train)
model2.fit(x_train, y_train)
model3.fit(x_train, y_train)

pred1 = model1.predict(x_test)
pred2 = model2.predict(x_test)
pred3 = model3.predict(x_test)

final_pred = np.array([])
for i in range(0, len(x_test)):
	final_pred = np.append(final_mode,  mode([pred1[i], pred2[i], pred3[i]))

 

(2) 두번째는 sklearn에서 VotingClassifier를 임포트하는 방식이다.

from sklearn.ensemble import VotingClassifier

model1 = LogisticRegression(random_state=1)
model2 = tree.DecisionTreeClassifier(random_state=1)

model = VotingClassifier(estimators=[('lr', model1), ('dt', model2)], voting = 'hard')

model.fit(x_train, y_train)
model.score(x_test, y_test)

 

(2) Averaging

Averaging(평균 방법)은 다수결 방법과 비슷하게 여러 예측이 이루어지고, 그 값들을 평균내는 방식이다. 평균 방법은 회귀 문제에서 예측값을 내기 위해서 쓰이거나 분류 문제에서 확률값을 측정하기 위해 쓰인다.

위에서 언급한 영화 평점 예시에서 5동료들의 평점을 모두 평균을 내면 4.4점이라는 결론을 얻을 수 있다.

 

(5 + 5 + 4 + 4 + 4) / 5 = 22 / 5 = 4.4

 

코드로 보면 다음과 같다:

model1 = tree.DecisionTreeClassifier()
model2 = KNeighborsClassifier()
model3 = LogisticRegression()

model1.fit(x_train, y_train)
model2.fit(x_train, y_train)
model3.fit(x_train, y_train)

pred1 = model1.predict(x_test)
pred2 = model2.predict(x_test)
pred3 = model3.predict(x_test)

final_pred = (pred1 + pred2 + pred3)/3

 

(3) Weighted Average

Weighted Average(가중치 평균 방법)은 평균 방법의 연장선이다. 모든 모델에는 각 중요도에 따라 다른 가중치가 배정된다. 예를 들어, 당신의 동료 중 두 명이 비평가이고 다른 사람들은 이 분야에 대해 잘 모른다면, 비평가인 두 동료의 평가가 다른 사람들보다 더 중요하다.

 

  동료 1 동료 2 동료 3 동료 4 동료 5 최종 평점
가중치 0.23 0.23 0.18 0.18 0.18  
평점 5 4 5 4 4 4.41

결과적으로 다음과 같이 계산할 수 있다: [(5 * 0.23) + (4 * 0.23) + (5 * 0.18) + (4 * 0.18) + (4 * 0.18)] = 4.41

 

코드로 구현해보자:

model1 = tree.DecisionTreeClassifier()
model2 = KNeighborsClassifier()
model3 = LogisticRegression()

model1.fit(x_train, y_train)
model2.fit(x_train, y_train)
model3.fit(x_train, y_train)

pred1 = model1.predict_proba(x_test)
pred2 = model2.predict_proba(x_test)
pred3 = model3.predict_proba(x_test)

final_pred = (pred1 * 0.3 + pred2 * 0.3 + pred3 * 0.4)

 

아래 그림과 같이 가중치가 없는 경우에는 모든 모델이 같은 비중으로 최종 결과물에 반영되지만, 가중치가 있는 경우 각 모델의 기여도가 다르다. 일반적으로 앙상블은 가중치를 부여하여 사용한다. 왜냐하면 각 모델의 특징에 따라 강약점이 다르므로, 적절한 가중치를 부여하는 방식이 더 효과적이기 때문이다.

 

출처: https://blogs.sas.com/content/subconsciousmusings/2017/05/18/stacked-ensemble-models-win-data-science-competitions/


Advanced Models

기본 앙상블 기법에 대해서 알아보았으니, 이제 고급 기법에 대해서 알아보도록 하자. 

 

(1) Stacking

Stacking(쌓아올리기 방법; 스태킹)은 다수의 모델로부터(예를 들어, decision tree, knn, svm) 예측값을 받아 새로운 모델을 만드는 방법이다. 이 새로운 모델은 테스트 세트에 대한 예측을 하기 위해 활용된다. 간단하게 쌓아올린 앙상블 모델을 코드로 이해해보도록 하자.

 

일반적인 순서 설명

  1. 각 기반 모델(base model)로 훈련하여 예측값 도출.
  2. 결과물로 나온 예측값을 "쌓아서" 훈련 데이터로 만들어줌 (i.e. pd.DataFrame).
  3. 이 훈련 데이터를 새로운 모델(i.e. LogisticRegression)에 넣어줘서 최종적으로 예측. 

Stacking; 출처: https://courses.analyticsvidhya.com/courses/take/ensemble-learning-and-ensemble-learning-techniques/texts/11145996-implementing-stacking

 

아래의 데이터를 다운로드하여 직접 실행시켜볼 수 있다. 

data_cleaned.csv
0.05MB

# Stacking
### Importing Libraries and Dataset
import numpy as np
import pandas as pd
data = pd.read_csv('data_cleaned.csv')

#checking missing values
print(data.isnull().sum())

### Separating Dependent and Independent Variables
x = data.drop(["Survived"], axis = 1)
y = data['Survived']

### Making test and training set
from sklearn.model_selection import train_test_split as tts
train_x, test_x, train_y, test_y = tts(x, y, random_state = 9, stratify = y)

#Feature Scaling
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
train_x = scaler.fit_transform(train_x)
test_x = scaler.transform(test_x)

### Model Training and Predictions 
def model_predictions(model, train_x, train_y, test_x):
	#train the model
	model.fit(train_x,train_y)
    
	#storing predictions for train and test
	pred_train=model.predict(train_x)
	pred_test=model.predict(test_x)
	return pred_train, pred_test

## Base models
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier

#Model 1 - Decision Tree
DT = DecisionTreeClassifier(random_state= 101)
M1_train, M1_test = model_predictions(DT, train_x, train_y, test_x)

#Model 2 - Logistic Regression
LR = LogisticRegression(random_state= 101)
M2_train, M2_test = model_predictions(LR, train_x, train_y, test_x)

#Model 3 - k Nearest Neighbour
knn = KNeighborsClassifier()
M3_train, M3_test = model_predictions(knn, train_x, train_y, test_x)

## Stacking Model
#Creating a New train dataframe
train_prediction = {
              'DT': M1_train,
              'LR': M2_train,
              'knn': M3_train
              }
train_predictions = pd.DataFrame(train_prediction)

#Creating a New test dataframe
test_prediction = {
              'DT': M1_test,
              'LR': M2_test,
              'knn': M3_test
              }
test_predictions = pd.DataFrame(test_prediction)

# Stacker Model
model = LogisticRegression()
model.fit(train_predictions, train_y)
model.score(test_predictions, test_y)
print(train_predictions.head())

위 코드는 스태킹을 아주 간단한 구조로만 하였다. 하지만 스태킹은 창의력을 발휘하여 여러 방식으로 구조를 짤 수 있다. 제시된 코드에서는 모델들의 예측치로부터 만드는 새로운 모델(빨간색 박스)이 하나였지만, 여러 개를 만들 수 있다. 나아가 여러 개로 만들어진 새로운 모델로부터 또 다시 스태킹 방법을 적용할 수도 있다 (다층 구조). 혹은, 위 코드에서는 기반 모델의 예측치로만 새로운 모델에게 인풋으로 제공했지만, 원래 있던 훈련 세트를 포함하여 인풋으로 제공할 수도 있다. 이외에도 여러 가지 방식으로 스태킹을 할 수 있다.

하지만 몇 가지 주의사항이 있다:

  • 스태킹을 하는 목적은 최종 결과의 성능을 향상시키는 데 있다. 최종 예측의 정확도가 올라가지 않는 (혹은 떨어지는) 방식은 피하자.
  • 스태킹은 쌓으면 쌓을 수록 복잡해지기 때문에 '적절한' 정도까지만 쌓아야 한다.
  • 이러한 기본적인 방식의 스태킹은 과적합(Overfitting)의 문제가 존재한다.