Ensemble Learning

본 포스팅의 주요 내용은 고려대학교 강필성 교수님의 강의를 정리하여 작성하였습니다.


Bagging

이번 포스팅에서는 앙상블 기법중 하나인 bagging에 대해서 알아보겠습니다.

Bagging의 원리

Bagging은 bootatrap aggregating의 줄임말로

복원추출로 샘플을 뽑아 training set으로 지정하여 모델을 학습한 다음 그 결과를 aggregation하는 기법입니다.

자세한 설명을 위해 아래 그림을 첨부합니다.

Raw data에서 데이터를 sampling 한 다음 sampling 된 데이터를 training data로 설정합니다. 설정된 데이터를 학습시키고 싶은 model에 적합시켜 결과를 도출해냅니다. 각각 random으로 복원추출한 data이기 때문에 생성되는 model의 형태가 다르므로 각 model들을 결합(aggregation)해주어야 합니다. 그 결과로 final model이 생성하게 됩니다.

Aggregation 종류

마지막에 결합하는 방법은 target변수의 성질에 따라 다릅니다. Target 변수가 연속형 변수라면 prediction값의 평균을 사용하고, 범주형 변수라면 각 model의 사후확률의 평균 또는 분류결과의 투표방법을 통해 결합하게 됩니다.

Bagging의 장점

앙상블 기법을 사용하는 이유는 model의 정확성과 안정성을 늘리기 위함입니다.

위의 그림은 의사결정나무 model의 앙상블 기법인 random forest model의 결과 입니다. Tree수가 늘어 날수록 정확도의 지표인 오분류 비율이 낮아지는 것을 알 수 있고 평균제곱오차 또한 낮아지는 것을 알 수 있습니다. Out of bag은 out of bagging을 뜻하는 것인데 이는 bagging에 sampling된 데이터를 제외한 나머지 데이터들을 말합니다. 나머지 데이터들도 tree 수가 늘어남에 따라 결과가 좋아 지는 것을 알 수 있지만 bagging 으로 만들어진 model에 비해서는 성능이 좋지 않은 것을 알 수 있습니다.

Bagging Regressor 예시

아래는 regresson을 bagging model으로 fitting시킨 것입니다. 싸이킷런의 Bagging model은 기본값이 의사결정 나무 model이므로 의사결정 트리의 bagging model의 결과를 보여줍니다.

import pandas as pd
import numpy as np
from sklearn import model_selection
from sklearn import metrics
import matplotlib.pyplot as plt
from sklearn.ensemble import BaggingRegressor

x_data = pd.read_csv('C:/Users/korewa/Desktop/bagging_x.csv')
y_data = pd.read_csv('C:/Users/korewa/Desktop/bagging_y.csv')
#csv 파일 읽기

print(x_data.shape) 
print(y_data.shape) 
#데이터 형태확인

print(x_data[:2])
print(y_data[:2])
#2열까지 데이터 확인



x_train, x_test, y_train, y_test = model_selection.train_test_split(x_data, y_data, test_size=0.33)
#데이터 train과 testset으로 split

estimator = BaggingRegressor(base_estimator=None, n_estimators=10, max_samples=1.0, bootstrap=True, oob_score=False, n_jobs=1, random_state=None)
#bagging regressor 사용

#base_estimator = None -> 기본 model은 의사결정트리로 되어있음
#n_estimators -> 앙상블에 사용할 tree의 수
#max_samples -> 추출할 sample의 수
#oob_score -> out of bagging data로 오차를 추정할 것인지
#random_state -> 랜덤으로 sampling 할 때 난수생성 seed

estimator.fit(x_train, y_train)
#모델 적합

y_predict = estimator.predict(x_test)
score = metrics.metrics.mean_squared_error(y_test, y_predict)
print(score)

#mean-squared-error 측정

아래는 classfication을 bagging model으로 fitting시킨 것입니다. 분류 model 역시 기본값이 의사결정 나무 model이므로 의사결정 트리의 bagging model의 결과를 보여줍니다.

Bagging Classifier 예시


import pandas as pd
import numpy as np
from sklearn import model_selection
from sklearn import metrics
import matplotlib.pyplot as plt
from sklearn.ensemble import BaggingClassifier 

x_data = pd.read_csv('C:/Users/korewa/Desktop/bagging_x.csv', engine='python')
y_data = pd.read_csv('C:/Users/korewa/Desktop/bagging_y.csv', engine='python')
#csv 파일 읽기

print(x_data.shape) 
print(y_data.shape) 
#데이터 형태확인

print(x_data[:2])
print(y_data[:2])
#2열까지 데이터 확인

x_train, x_test, y_train, y_test = model_selection.train_test_split(x_data, y_data, test_size=0.33)

#데이터 train과 testset으로 split

#estimator = BaggingClassifier()
estimator = BaggingClassifier(base_estimator=None, n_estimators=10, max_samples=1.0, 
                              bootstrap=True, oob_score=False, 
                              n_jobs=1, random_state=None)

#base_estimator = None -> 기본 model은 의사결정트리로 되어있음
#n_estimators -> 앙상블에 사용할 tree의 수
#max_samples -> 추출할 sample의 수
#oob_score -> out of bagging data로 오차를 추정할 것인지
#random_state -> 랜덤으로 sampling 할 때 난수생성 seed

estimator.fit(x_train, y_train)
#모델 적합

y_predict = estimator.predict(x_test)
score = metrics.classification_report(y_test, y_predict)
print(score)
#precision, recall, f1-score 측정


1보다 0이 많은 unbalanced data이므로 accuracy보다는 precision, recall, f1-score을 채택하였습니다.

K fold cross validation

Bagging설명에 이어 방법과 목적이 비슷한 k fold cross validation에 대해서도 다루어 보겠습니다. K fold cross validation을 하는 목적은 bagging과 동일 합니다. Model의 안정성과 정확성을 높이는 것입니다. 목적은 같지만 방식에서 bagging과 조금 다릅니다. 자세한 설명을 위해 그림을 첨부합니다.

Bagging의 경우 dataset을 random으로 복원추출 했음에 비해 k fold cross validation은 전체 데이터를 미리 정해진 하이퍼 파라미터 k만큼 분리해논 후 (위 그림은 k=5입니다) 비복원 추출로 한 part씩 test set으로 지정하고 나머지를 training set으로 지정해 model에 각각 적합시킵니다. 그 결과로 나온 model 평가 값들을 일반적으로 평균을 내어 model의 정확도를 측정합니다. 방법은 다르지만 목적이 같기에 헷갈릴 수 있어 다루어 보았습니다.

Bagging의 결과는 항상 단일모델의 결과보다 좋을까?

그렇다면 항상 앙상블 model의 결과 좋을까요?

Bagging이 정확도를 어떻게 향상시킬 수 있는지에 대한 많은 연구와 증명들이 있었습니다. 저는 그 중에 Breiman의 증명을 소개 하고자 합니다. 다음 증명은 평균예측모델의 기대손실이 단일 예측모델의 기대손실보다 작다는 것을 보여줍니다. 증명은 이곳을 참고하였습니다.

<정리 1>

$(X,Y)$ 를 $L$과 독립인 미래의 관측값이라 하자.

제곱손실함수 $\mathcal{L}(y,a)=(y-a)^2$에 대하여 $f(x,\mathcal{L})$와 $f_A(x)$의 기대손실 $R$과 $R_A$를 다음과 같이 정의한다.

$R=E_{(X,Y)}E_\mathcal{L}L(Y,f(X,\mathcal{L}))
R_A=E_{(X,Y)}L(Y,f_A(X))$이라면

항상 $R\ge\ R_A$가 성립한다.

Proof.제곱함수는 볼록함수이므로 Jensen 부등식에 의해서

이 성립한다.

따라서

위의 증명에서 중요한 사실 하나를 확인할 수 있는데 $R-R_A$는

이다.

$f(x,L)$ 의 분산이 크다면 평균예측모형이 예측력이 단일 예측모형에 비해 크게 향상되며, 반대로 분산이 작으면 평균예측모형의 예측력의 향상 정도 가 줄어듭니다. training data를 얻은 모집단의 분포를 모르기 때문에 실제문제에서는 평균예측모형을 구할 수 없습니다. 그 대신 training data를 모집단으로 생각하고 이로부터 평균예측모형을 구한 것이 바로 bagging의 예측모형이라 할 수 있습니다. 배깅은 주어진 예측모형의 평균예측모형을 구하는 것이고 이를 통하여 분산을 줄여줌으로써 예측력을 향상시킵니다.

단일모델과 Bagging모델의 비교(code)

위의 증명을 실제로 확인 해보기 위해 분류 데이터와 예측 데이터를 여러가지 model과 그 model에 bagging 기법을 적용하여 그 결과를 비교해 보는 code를 짜보았습니다.

Classification model bagging

우선 target이 binary인 data로 분류 model을 구축해 보았습니다. 이 데이터는 은행 저축 상품 판매 마케팅 data로 target변수가 deposit(상품 구매 유무)입니다.

아래는 변수 설명입니다.



from sklearn.model_selection import cross_val_score
from sklearn.ensemble import BaggingClassifier, ExtraTreesClassifier, RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import RidgeClassifier
from sklearn.svm import SVC
seed = 1075

np.random.seed(seed)

x_data = pd.read_csv('C:/Users/korewa/Desktop/시벤티지 데이터/bagging_x.csv', engine='python')
y_data = pd.read_csv('C:/Users/korewa/Desktop/시벤티지 데이터/bagging_y.csv', engine='python')

#분류 데이터 read

print(x_data.shape) #input 변수의 형태 확인 (42개의 변수와 11162개의 obs)
print(y_data.shape) #target 변수의 형태 확인 (개의 변수와 11162개의 obs)
print(x_data[:2]) #2열까지 input데이터 확인
print(y_data[:2]) #2열까지 target데이터 확인

x_train, x_test, y_train, y_test = model_selection.train_test_split(x_data, y_data, test_size=0.33)

#데이터를 train, test set으로 split testsize=33%

rf = RandomForestClassifier()
et = ExtraTreesClassifier()
knn = KNeighborsClassifier()
svc = SVC()
rg = RidgeClassifier()

분류를 5가지 model로 학습시킬 것입니다.

vanilla_scores = cross_val_score(rf, x_train, y_train, cv=10, n_jobs=-1,scoring='accuracy')
print(vanilla_scores.mean(), vanilla_scores.std())
#random forest normal score

bagging_clf = BaggingClassifier(rf, max_samples=0.4,  random_state=seed)
bagging_scores = cross_val_score(bagging_clf, x_train, y_train,scoring='accuracy', cv=10, n_jobs=-1)
print(bagging_scores.mean(), bagging_scores.std())
#random forest bagging score


vanilla_scores = cross_val_score(et, x_train, y_train, cv=10, n_jobs=-1,scoring='accuracy')
print(vanilla_scores.mean(), vanilla_scores.std())
bagging_clf = BaggingClassifier(et, max_samples=0.4,  random_state=seed)
bagging_scores = cross_val_score(bagging_clf, x_test, y_test, cv=10, n_jobs=-1,scoring='accuracy')
print(bagging_scores.mean(), bagging_scores.std())

#extra classfier normal and bagging score


vanilla_scores = cross_val_score(knn, x_train, y_train, cv=10, n_jobs=-1,scoring='accuracy')
print(vanilla_scores.mean(), vanilla_scores.std())
bagging_clf = BaggingClassifier(knn, max_samples=0.4,  random_state=seed)
bagging_scores = cross_val_score(bagging_clf, x_test, y_test, cv=10, n_jobs=-1,scoring='accuracy')
print(bagging_scores.mean(), bagging_scores.std())

#knn normal and bagging score


vanilla_scores = cross_val_score(svc, x_train, y_train, cv=10, n_jobs=-1,scoring='accuracy')
print(vanilla_scores.mean(), vanilla_scores.std())
bagging_clf = BaggingClassifier(svc, max_samples=0.4,  random_state=seed)
bagging_scores = cross_val_score(bagging_clf, x_test, y_test, cv=10, n_jobs=-1,scoring='accuracy')
print(bagging_scores.mean(), bagging_scores.std())

#svm normal and bagging score


vanilla_scores = cross_val_score(rg, x_train, y_train, cv=10, n_jobs=-1,scoring='accuracy')
print(vanilla_scores.mean(), vanilla_scores.std())
bagging_clf = BaggingClassifier(rg, max_samples=0.4,  random_state=seed)
bagging_scores = cross_val_score(bagging_clf, x_test, y_test, cv=10, n_jobs=-1,scoring='accuracy')
print(bagging_scores.mean(), bagging_scores.std())

#ridge normal and bagging score

분류 모델에서는 대체적으로 bagging 예측모델이 결과가 잘 나온것을 확인 할 수 있지만 일부, svc와 ridge model에서는 단일 예측모델이 결과가 좋게 나온것을 확인 할 수 있습니다.

Regression model bagging

위의 그림은 수치형 예측 data로 여러가지 날씨 변수들로 파고주기를 예측하는 data입니다.


from sklearn.model_selection import cross_val_score
from sklearn.ensemble import BaggingRegressor, ExtraTreesRegressor, RandomForestRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.svm import SVC
seed = 1075
np.random.seed(seed)

x_data = pd.read_csv('C:/Users/korewa/Desktop/bagging_x.csv')
y_data = pd.read_csv('C:/Users/korewa/Desktop/bagging_y.csv')

print(x_data.shape) #input 변수의 형태 확인 (7개의 변수와 44527개의 obs)
print(y_data.shape) #target 변수의 형태 확인 (1개의 변수와 44527개의 obs)
print(x_data[:2]) #2열까지 input데이터 확인
print(y_data[:2]) #2열까지 target데이터 확인

x_train, x_test, y_train, y_test = model_selection.train_test_split(x_data, y_data, test_size=0.33)

#데이터를 train, test set으로 split testsize=33%

rf = RandomForestRegressor()
et = ExtraTreesRegressor()
knn = KNeighborsRegressor()
svc = SVC()


vanilla_scores = cross_val_score(rf, x_train, y_train, cv=10, n_jobs=-1,scoring='mean_squared_error')
print(vanilla_scores.mean(), vanilla_scores.std())
#random forest normal score

bagging_clf = BaggingRegressor(rf, max_samples=0.4,  random_state=seed)
bagging_scores = cross_val_score(bagging_clf, x_train, y_train,scoring='mean_squared_error', cv=10, n_jobs=-1)
print(bagging_scores.mean(), bagging_scores.std())

#random forest bagging score


vanilla_scores = cross_val_score(et, x_train, y_train, cv=10, n_jobs=-1,scoring='mean_squared_error')
print(vanilla_scores.mean(), vanilla_scores.std())
#extra classfier normal score

bagging_clf = BaggingRegressor(et, max_samples=0.4,  random_state=seed)
bagging_scores = cross_val_score(bagging_clf, x_test, y_test, cv=10, n_jobs=-1,scoring='mean_squared_error')
print(bagging_scores.mean(), bagging_scores.std())

#extra bagging score


vanilla_scores = cross_val_score(knn, x_train, y_train, cv=10, n_jobs=-1,scoring='mean_squared_error')
print(vanilla_scores.mean(), vanilla_scores.std())
#knn normal score

bagging_clf = BaggingRegressor(knn, max_samples=0.4,  random_state=seed)
bagging_scores = cross_val_score(bagging_clf, x_test, y_test, cv=10, n_jobs=-1,scoring='mean_squared_error')
print(bagging_scores.mean(), bagging_scores.std())

#knn bagging score


vanilla_scores = cross_val_score(svc, x_train, y_train, cv=10, n_jobs=-1,scoring='mean_squared_error')
print(vanilla_scores.mean(), vanilla_scores.std())
#svm normal score

bagging_clf = BaggingRegressor(svc, max_samples=0.4,  random_state=seed)
bagging_scores = cross_val_score(bagging_clf, x_test, y_test, cv=10, n_jobs=-1,scoring='mean_squared_error')
print(bagging_scores.mean(), bagging_scores.std())
#svm bagging score

수치형 예측모델에서는 단일예측모델의 평가지표보다 bagging 예측모델의 지표가 항상 높게 나오는 것을 확인 할 수 있었습니다.

결론적으로 분류모델과 예측모델을 같은 조건으로 Bagging 기법에 적용 시켰을 때 무조건 Bagging의 결과가 좋은 것은 아님을 확인 할 수 있었습니다.