[Python] Ensemble(앙상블) - Bagging

2020. 7. 31. 15:15ML in Python/Python

 

 이 게시글은 오로지 파이썬을 통한 실습만을 진행한다. 앙상블 기법중 Bagging의 개념 및 원리를 알고자하면 아래 링크를 통해학습을 진행하면 된다.

https://todayisbetterthanyesterday.tistory.com/48?category=822147

 

[Data Analysis 개념] Ensemble(앙상블)-2 : Bagging, RandomForest

 앙상블에 대한 종류와 전반적인 설명은 아래 링크에 존재한다. 이 게시글에서는 앙상블 모형중 Bagging과 RandomForest에 대해서 알아보겠다. https://todayisbetterthanyesterday.tistory.com/47 [Data Analysi..

todayisbetterthanyesterday.tistory.com

kc_house_data.csv
1.63MB

 실습에 사용할 데이터는 아래의 데이터이다. 집 price가 target이고 그 외 집의 특성을 뜻하는 변수들이 feature로 존재한다. 상세한 내용은 아래와 같다. 

id: 집 고유아이디
date: 집이 팔린 날짜 
price: 집 가격 (타겟변수)
bedrooms: 주택 당 침실 개수
bathrooms: 주택 당 화장실 개수
floors: 전체 층 개수
waterfront: 해변이 보이는지 (0, 1)
condition: 집 청소상태 (1~5)
grade: King County grading system 으로 인한 평점 (1~13)
yr_built: 집이 지어진 년도
yr_renovated: 집이 리모델링 된 년도
zipcode: 우편번호
lat: 위도
long: 경도

 

 이 데이터를 가지고 for문을 통해 Bagging 알고리즘을 직접 구현하는 방법과 sklearn.ensemble에 존재하는 BaggingRegressor를 가지고 Bagging알고리즘을 구현하는 방법 두 가지를 실습으로 진행하겠다. 순서는 아래와 같다.

1. 데이터 처리

2. for문을 이용한 Bagging 직접 구현

3. BaggingRegressor를 활용한 Bagging구현 

 그리고 Bagging의 경우는 Ensemble의 기법으로 Tree를 기반으로 자주 쓰지만, 다른 모형과 결합해서 쓸 수도 있기에 선형회귀모형과 DecisionTree 모형 두가지를 이용하여 Bagging 실습을 진행하겠다. 


1. 데이터 처리

 

[데이터 처리]

# data 처리를 위한 library

import os 
import pandas as pd 
import numpy as np
from sklearn.model_selection import train_test_split
# 현재경로 확인

os.getcwd()

 위의 코드는 파이썬 해당파일(.py / .ipynb)의 위치경로를 표기해주는 것이다. 만약 데이터가 다른 경로에 존재한다고 하였을 때, 전 경로를 확인하기 위해서 자주 사용한다. 

# 데이터 불러오기

data = pd.read_csv("./kc_house_data.csv") 
data.head() # 데이터 확인

 코드블럭 앞에서 데이터에 대한 설명을 하였다. target은 price이고 나머지는 feature변수이다. 그리고 몇몇 변수의 경우는 우리가 분석하기에 feature의 역할을 해줄 설명력이 없다고 판단되어 변수를 제거할 예정이다. 

# shape확인

nCar = data.shape[0] # 데이터 개수
nVar = data.shape[1] # 변수 개수
print('nCar: %d' % nCar, 'nVar: %d' % nVar )

 kc_house_data에는 21613개(row)와 14개 변수를 가진 데이터라고 볼 수 있다. 14개에는 target과 feature가 공존하기에 분리시킬 필요가 있다.

# shape확인

nCar = data.shape[0] # 데이터 개수
nVar = data.shape[1] # 변수 개수
print('nCar: %d' % nCar, 'nVar: %d' % nVar )
# 무의미한 변수 제거

data = data.drop(['id', 'date', 'zipcode', 'lat', 'long'], axis = 1) # id, date, zipcode, lat, long  제거

 여기서 제거한 변수는 id / date / zipcoe / lat / long이 포함되어있다. id와 zipcode의 경우는 보편적으로 생각하기에 의미가 없다고 판단될 수 있지만, date (팔린 날짜), lat/long( 위도와 경도 - 위치변수)는 의미가 있다고 볼 수 있다. 하지만, 이 실습에서는 집의 구조적 특성을 기준으로 학습을 진행시키기 위해 제외하도록 하겠다. 

 그리고 만약 범주형 변수가 존재한다면, 이러한 전처리 과정에서 수치(0,1,2,...)로 변환해줄 필요가 있다. 하지만 해당 데이터에는 그러한 부분이 없기에 과정을 생략한다.

# features/target, train/test dataset 분리

feature_columns = list(data.columns.difference(['price'])) # Price를 제외한 모든 행
X = data[feature_columns] # 설명변수 
y = data['price']         
train_x, test_x, train_y, test_y = train_test_split(X, y, test_size = 0.3, random_state = 42) 
# 학습데이터와 평가데이터의 비율을 7:3

print(train_x.shape, test_x.shape, train_y.shape, test_y.shape) # 데이터 개수 확인

 

[선형회귀를 통한 기본적인 오차 검증]

# 선형회귀 OLS 적합 과정

import statsmodels.api as sm
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
from math import sqrt


sm_train_x = sm.add_constant(train_x, has_constant = "add")# b0 상수항 추가 
sm_model = sm.OLS(train_y,sm_train_x)                      # OLS 모델
fitted_sm_model = sm_model.fit()                           # 적합 
fitted_sm_model.summary()


sm_test_x = sm.add_constant(test_x, has_constant="add")    # 예측을 위한 test 상수항 추가
sm_model_predict = fitted_sm_model.predict(sm_test_x)      # 예측

sqrt(mean_squared_error(sm_model_predict, test_y))   # RMSE 계산

 이렇게 기본적인 분석방법인 선형회귀를 진행했을 때, RMSE오차는 239804가 나왔다. 이제 bagging을 통해서 진행했을 때 성능을 확인해보자.

2. for문을 이용한 Bagging 직접 구현

 

# for문을 활용한 bagging 알고리즘

bagging_predict_result = []
for _ in range(10):   # 데이터셋을 10번 반복복원추출, Sampling 개수와 동일range(n)
    # 반복 복원추출과정
    data_index = [data_index for data_index in range(train_x.shape[0])]
    random_data_index = np.random.choice(data_index, train_x.shape[0]) # 복원추출을 의미
    print(len(set(random_data_index)))   # unique한 것을 뽑음, data의 63%가량 unique data 추출  
    
    # 선형회귀모델
    sm_train_x = train_x.iloc[random_data_index,]
    sm_train_y = train_y.iloc[random_data_index,]
    sm_train_x = sm.add_constant(sm_train_x, has_constant="add")
    sm_model = sm.OLS(sm_train_y,sm_train_x)
    fitted_sm_model = sm_model.fit()
    
    # 각 데이터셋의 예측결과
    pred = fitted_sm_model.predict(sm_test_x)
    bagging_predict_result.append(pred)
    print(sqrt(mean_squared_error(pred,test_y)))

 위의 출력결과는 bagging을 통해 반복복원추출된 데이터셋의 개수와 그 데이터를 선형회귀모델로 적합시켰을 때, 예측오차(RMSE)를 출력한 것이다. 맨 처음 shape를 통해 확인한 train 데이터의 개수는 15000개 가량이었다. 거기에 9500개 가량이 반복복원추출을 통해 매번 뽑힌 것으로 보아 63%정도의 데이터가 뽑히는 것을 알 수 있다. 

 그리고 각각의 데이터셋에서 학습한 결과의 오차는 단순선형회귀분석을 통해 추출한 오차와 비슷하다. 이는 모델이 동일하고, 선형회귀분석과 같은 경우는 적합의 정확성에 한계가 있기 때문이다. 

# bagging 앙상블 모형의 최종 error를 구하는 과정 

bagging_predict = []

for lst2_index in range(test_x.shape[0]):
    temp_predict = []
    for lst_index in range(len(bagging_predict_result)):
        temp_predict.append(bagging_predict_result[lst_index].values[lst2_index])
    bagging_predict.append(np.mean(temp_predict))

# 최종 에러
sqrt(mean_squared_error(bagging_predict,test_y))

 위의 최종결과는 선형회귀분석을 활용한 bagging 기법의 최종에러이다. 앙상블에서 각각의 데이터셋에 대해서 학습한 결과의 에러를 평균내는 것으로 error를 계산할 수도 있지만,  좀 더 정확한 결과를 위해서 맨 처음 나눈 test데이터를 활용하여 최종에러를 계산하였다. 

 선형회귀모형의 경우 위에서 말했듯이, 적합의 정확도에 대한 한계가 존재한다. 게다가 변수가 많아질 수록 이 한계점은 명확하게 드러난다. 그러다 보니, 앙상블을 사용하였다고 하여도 에러가 좋아지는 큰 기대를 하기 힘들다. 하지만 이 방법이 Tree 또는 다른 모델들을 활용하면 보다 개선된 결과를 확인할 수 있다. 

 이번에는 편의를 위해 sklearn.ensemble에 존재하는 BaggingRegressor을 통해서 선형회귀모형을 bagging기법을 통해서 학습시켜보자. 

 

3. BaggingRegressor를 활용한 Bagging구현 

 

[선형회귀모형 Bagging]

# regression model 생성

regression_model = LinearRegression() # 선형 회귀 모형
linear_model1 = regression_model.fit(train_x, train_y) # 학습 데이터를 선형 회귀 모형에 적합
predict1 = linear_model1.predict(test_x) # 학습된 선형 회귀 모형으로 평가 데이터 예측
print("RMSE: {}".format(sqrt(mean_squared_error(predict1, test_y)))) # RMSE 결과

 BaggingRegressor에는 model parameter가 존재한다. 즉, 학습모델을 parameter로 넣어야 한다는 뜻이다. 이는 DecisionTree가 될 수도 있고, SVM 등등 다른 모델이 될 수도 있다. 이제 이 regression_model을 활용하여 bagging함수를 적용시켜보자. 

# bagging 진행

from sklearn.ensemble import BaggingRegressor
bagging_model = BaggingRegressor(base_estimator = regression_model, # 선형회귀모형
                                 n_estimators = 5, # 5개의 샘플링
                                 verbose = 1) # 학습 과정 표시
linear_model2 = bagging_model.fit(train_x, train_y) # 학습 진행
predict2 = linear_model2.predict(test_x) # 학습된 Bagging 선형 회귀 모형으로 평가 데이터 예측
print("RMSE: {}".format(sqrt(mean_squared_error(predict2, test_y)))) # RMSE 결과

 위의 결과는 BaggingRegressor을 활용하여 bagging기법을 적용시킨 RMSE이다. 이 또한 For문으로 구현하는 것에 비해 크게 달라진 것이 없다. 물론 반복복원추출이기에 ensemble기법을 적용시킬 때마다 다른결과가 나오지만 그렇게 큰 차이가 생기진 않는다. 만약 같은 ensemble기법과 학습모델을 선택한다면 parameter를 경험적으로 조정해보는 것이 훨씬 더 많은 성능차이를 야기할 수도 있다.

 그리고 아래 빨간 블럭으로 출력이 된 결과는 verbose=1로 표시했기에 학습과정을 출력한다는 의미이다. 이제 parameter를 조정하는 과정을 진행해보자. 

 

[Sampling 개수 증가]

# bagging 진행

bagging_model2 = BaggingRegressor(base_estimator = regression_model, # 선형 회귀모형
                                  n_estimators = 30, # 30개의 샘플링
                                  verbose = 1) # 학습 과정 표시
linear_model3 = bagging_model2.fit(train_x, train_y) # 학습 진행
predict3 = linear_model3.predict(test_x) # 학습된 Bagging 선형 회귀 모형으로 평가 데이터 예측
print("RMSE: {}".format(sqrt(mean_squared_error(predict3, test_y)))) # RMSE 결과

 이전 학습과 다른점은 샘플링 개수를 늘린 것이다. 5개에서 30개로 늘리면 시간은 그만큼 더 많이 소요된다. 물론 학습결과 또한 좀 더 나아졌다. 하지만 이 차이를 선형회귀모델에서 크게 느끼기는 힘들다. 

 그렇다면, 이제 모델을 DecisionTree로 바꾸었을때, Bagging을 진행해보자.

 

[의사결정나무(DecisionTree) Bagging]

# DecisionTree 모델 생성

from sklearn.tree import DecisionTreeRegressor

decision_tree_model = DecisionTreeRegressor() # 의사결정나무 모형
tree_model1 = decision_tree_model.fit(train_x, train_y) # 학습 데이터를 의사결정나무 모형에 적합
predict1 = tree_model1.predict(test_x) # 학습된 의사결정나무 모형으로 평가 데이터 예측
print("RMSE: {}".format(sqrt(mean_squared_error(predict1, test_y)))) # RMSE 결과

 Decision Tree의 기본적인 error는 위와 같다. DecisionTreeRegressor의 특성상 구간예측을 하는 경향이 어느정도 강하기에 정확한 MSE가 높을 수 있다. 이를 Bagging작업을 통해 오차가 줄어드는지 확인해보자. 

# DecisionTree sampling 5 - bagging

bagging_decision_tree_model1 = BaggingRegressor(base_estimator = decision_tree_model, # 의사결정나무 모형
                                                n_estimators = 5, # 5번 샘플링
                                                verbose = 1) # 학습 과정 표시
tree_model2 = bagging_decision_tree_model1.fit(train_x, train_y) # 학습 진행
predict2 = tree_model2.predict(test_x) # 학습된 Bagging 의사결정나무 모형으로 평가 데이터 예측
print("RMSE: {}".format(sqrt(mean_squared_error(predict2, test_y)))) # RMSE 결과

# DecisionTree sampling 30 - bagging

bagging_decision_tree_model1 = BaggingRegressor(base_estimator = decision_tree_model, # 의사결정나무 모형
                                                n_estimators = 30, # 5번 샘플링
                                                verbose = 1) # 학습 과정 표시
tree_model2 = bagging_decision_tree_model1.fit(train_x, train_y) # 학습 진행
predict2 = tree_model2.predict(test_x) # 학습된 Bagging 의사결정나무 모형으로 평가 데이터 예측
print("RMSE: {}".format(sqrt(mean_squared_error(predict2, test_y)))) # RMSE 결과

 먼저 DecisionTreeRegressor의 경우 bagging작업을 한 오차가 단순 DecisionTree보다 눈에 띄게 오차를 줄인 것을 확인할 수 있다. 실제로 앙상블 모형들이 Tree기반인 모형이 많은 이유 또한 위의 결과와 같다. 선형회귀의 경우는 Overfitting이 잘 일어나지 않으며, 좋아지는데 한계가 명확하지만, 그렇지 않은 모델일 경우 Ensemble기법은 큰 성능차이를 발생시킬 수 있다. 

 게다가 두 번째처럼 sampling의 개수를 늘리게 되면, 즉 학습을 더 많은 경우로 나누어 시키고 종합시킨다면 error또한 더 낮아지는 것을 확인할 수 있다. 물론 이 error의 감소는 무한하지 않다. 저 sampling 개수도 최적의 n개가 존재할 수 있다. 하지만, 정해진 것은 없기에 경험적으로 찾아야 할 필요가 있다. 

 이상으로 오늘 LinearRegression과 DecisionTreeRegression을 활용한 Bagging을 for문과 sklearn.ensemble을 활용해 실습을 진행해봤다. 다음 게시글에는 RandomForest를 진행해보도록 하자.