Ensemble Learning - Ensemble의 Ensemble

Ensemble의 Ensemble

  • Ensemble의 개념 자체가 여러 개의 기본 모델을 활용하여 하나의 새로운 모델을 만들어 내는 개념이다.

Ensemble의 개념

  • 그러므로 Ensemble 모델을 하나의 weak learner로 설정하면 Ensemble의 Ensemble 모델을 만들 수 있다.

Ensemble의 Ensemble 모델 개념 - 01

  • Ensemble의 Ensemble 모델은 다양한 모델을 사용하므로 Boosting 계열 알고리즘이 갖는 hyper parameter에 민감한 경향을 완화시켜 줄 수 있다. 또한, 패키지로 되어있지 않기 때문에 셔플을 통한 데이터의 추룰로 Bagging과 같은 효과를 줄 수 있고, 추가적으로 feature들에도 마치 deep learning의 dropout 같은 효과를 통해 Randomforest 알고리즘과 같은 효과를 기대 할 수도 있다.

Ensemble의 Ensemble 모델 개념 - 02


1
2
3
# 데이터 불러오기
data = pd.read_csv("../data/kc_house_data.csv")
data.head() # 데이터 확인

불필요한 데이터 제거 및 train, test set 분리


1
2
3
4
5
6
data = data.drop(['id', 'date', 'zipcode', 'lat', 'long'], axis = 1) # id, date, zipcode, lat, long  제거
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) # 데이터 개수 확인

LightGBM 학습

  • Scikit-learn과 호환되는 LightGBMClassifier API를 이용하는 것이 더 편하지만, 필자는 python wrapper package를 사용해 예시를 들어 볼 것이다.

1
2
3
4
5
6
7
8
9
# !pip install lightgbm
import lightgbm as lgb
start = time.time() # 시작 시간 지정
lgb_dtrain = lgb.Dataset(data = train_x, label = train_y) # 학습 데이터를 LightGBM 모델에 맞게 변환
lgb_param = {'max_depth': 10, # 트리 깊이
'learning_rate': 0.01, # Step Size
'n_estimators': 500, # Number of trees, 트리 생성 개수
'objective': 'regression'} # 파라미터 추가, Label must be in [0, num_class) -> num_class보다 1 커야한다.
lgb_model = lgb.train(params = lgb_param, train_set = lgb_dtrain) # 학습 진행
  • 위에서 적합 시킨 Boosting계열의 Ensemble 모델인 LightGBM 모델의 성능을 먼저 측정해 보면 다음과 같다.
1
2
3
4
from sklearn.metrics import mean_squared_error, r2_score
from math import sqrt

sqrt(mean_squared_error(lgb_model.predict(test_x),test_y))

결과

1
210904.17249451784

Ensemble의 Ensemble

  • Ensemble의 Ensemble을 함으로써, hyper paramter tuning에 덜 민감해 질 수 있다.
  • 아래 코드를 실해하고나면 random하게 추출된 데이터로 학습된 30번의 LightGBM 모델의 예측 결과의 배열이 존재하는 리스트의 값을 얻을 수 있다.
    • random하게 추출하였기에 Bagging의 효과를 얻을 수 있으며, 필자는 데이터만 임의복원추출하였지만, feature도 임의복원추출 한다면 RandomForest와 같은 효과를 기대할 수 있을 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import random
bagging_predict_result = [] # 빈 리스트 생성
for _ in range(30):
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)))
lgb_dtrain = lgb.Dataset(data = train_x.iloc[random_data_index,], label = train_y.iloc[random_data_index]) # 학습 데이터를 LightGBM 모델에 맞게 변환
lgb_param = {'max_depth': 10, # 트리 깊이
'learning_rate': 0.01, # Step Size
'n_estimators': 500, # Number of trees, 트리 생성 개수
'objective': 'regression'} # regression이므로 목적(비용)함수를 regression으로 바꾸어준다.
lgb_model = lgb.train(params = lgb_param, train_set = lgb_dtrain) # 학습 진행

predict1 = lgb_model.predict(test_x) # 테스트 데이터 예측
bagging_predict_result.append(predict1) # 반복문이 실행되기 전 빈 리스트에 결과 값 저장
print(sqrt(mean_squared_error(lgb_model.predict(test_x),test_y)))

  • 위의 결과를 토대로 ensemble 기법을 사용하기 위해선 다음과 같이 각 모델별 동일한 인덱스의 예측값에 대한 평균을 구해 최종적인 예측값으로 사용해야 할 것이다.

1
2
3
4
5
6
7
# Bagging을 바탕으로 예측한 결과값에 대한 평균을 계산
bagging_predict = [] # 빈 리스트 생성
for lst2_index in range(test_x.shape[0]): # 테스트 데이터 개수만큼의 반복
temp_predict = [] # 임시 빈 리스트 생성 (반복문 내 결과값 저장)
for lst_index in range(len(bagging_predict_result)): # Bagging 결과 리스트 반복
temp_predict.append(bagging_predict_result[lst_index][lst2_index]) # 각 Bagging 결과 예측한 값 중 같은 인덱스를 리스트에 저장
bagging_predict.append(np.mean(temp_predict)) # 해당 인덱스의 30개의 결과값에 대한 평균을 최종 리스트에 추가

  • 최종적인 성능값은 다음과 같다. 단일 Ensemble을 사용한 경우보다 성능이 좋아졌다는 것을 확인 할 수 있다.

1
sqrt(mean_squared_error(bagging_predict,test_y))

결과

1
209736.90720982864

중요변수 추출 방법

  • 모델의 성능이 좋아야하는 것고 물론 중요하지만, Y(target)에 대한 중요한 변수가 무엇인지 어떻게 영향을 끼치는지를 해석하고 활용하는 부분도 굉장히 중요하다.
  • 아래와 같이 단일 모형들은 상대적으로 변수에 대한 해석이 용이하다. 예를 들어, 선형회귀는 각 변수들의 계수를 통해 해당 변수가 반응변수에 미치는 영향력의 크기와 방향을 알 수 있으며, 그에 따른 신뢰도도 확인할 수 있다. 또한 Decision Tree의 경우에는 가장 information gain이 큰 변수가 제일 상위 노드에 위치할 것이며, 이런 방식을 통해 어떤 변수들이 반응 변수에 영향력을 주는지 확인 할 수 있다.

변수 추출 방법

  • 이에 반해, Ensemble Model들과 Deep Learning의 경우 변수에 대한 해석이 어렵다. 각각의 장단점은 존재한다. 아래 그림에서 볼 수 있듯이 Accuracy(성능)와 변수 설명의 용이함에 따른 트레이드 오프가 존재한다고 볼 수 있다.

Ensemble Model과 deep learning의 변수 해석의 어려움

  • 물론 Ensemble Model들도 feature importance를 구할 수는 있다. 허나 해당 수치를 바탕으로 합리적으로 변수가 반응변수에 미치는 영향을 합리적으로 판단하여 생각할 뿐이지, 영향력의 방향은 알 수 없다. 예를 들어 아래 그림에서와 같이 은행 이용 고객 데이터에 대해 수입이 50만 달러가 넘는지를 예측하는 Ensemble Model의 상위 12개의 feature에 대한 importance를 그래프로 표현하면 오른쪽 막대 그래프와 같다고 하자. 이 경우 회귀 분석과는 다르게 수치만 보았을 경우 명확히 나이가 반응 변수에 미치는 영향력의 크기는 상대적으로 가늠할 순 있겠지만 정확한 크기나 방향은 전혀 알 수 없다. 단지 우리가 나이가 많을수록 수입이 높아진다는 경험적사고를 바탕으로 합리적 생각을 하는 것 뿐이다.

Ensemble Model의 Feature Importance 해석

  • 위와 같이 Ensemble Model의 Feature Importance를 측정하는 기준이 존재한다.
    • Weight는 단순히 변수 별 데이터를 분리하는 데 사용된 횟수인데 반해, Cover는 분리된 데이터의 수를 가중치로 사용하기 때문에 트리의 구조상 가장 상위에 높은 분별력을 지닌 노드가 가중치가 가장 높기에 Weight에 비해 합리적이다. 마지막으로 Gain은 커버와 비슷한 개념이다.

Ensemble Model Feature Importance를 측정하는 기준 - 01

  • 아래 그림과 같이 각 Feature Importance를 측정하는 기준에 따라 변수의 중요도가 달라진다. 또한, 위에서 언급한 것과 같이 해석이 용이하지 않다. 단지, 해당 변수가 중요하다는 정도만을 알 수 있을 뿐 양의방향으로 영향을 미치는지 음의 방향으로 영향을 미치는지 또는 얼마나 영향을 미치는지에 대해서는 알 수 없다.

Ensemble Model Feature Importance를 측정하는 기준 - 02

  • Ensemble Model의 Feature에 대한 해석에 어려움은 대부분의 Feature importance 지표가 Inconsistency하기 때문이기도 하다. 예를 들면, Bagging을 할 경우 부트스트랩 방식으로 train data를 임의복원추출하여 매번 진행하기 때문에 각 모델을 적합 시킨 데이터가 다르므로 Feature Importance를 계산해보아도 모든 모델에 대해 변수가 동일한 Feature Importance를 갖기 어렵다 그러므로 Feature Importance를 통해 변수를 비교하는 것은 어렵다.

Ensemble Model의 Feature 해석의 어려움 - 02

  • 아래 그림에서 Decision Tree Model들은 각각 열이나 기침에 의해서 어떤 질병이 걸릴 score에 대한 것이다. 허나 학습에 사용되어진 데이터에 의해 각 변수의 중요도는 모델에 따라 상이함을 확인할 수 있다.

Ensemble Model의 Feature 해석의 어려움 예시

Shap value

  • 위와 같은 문제점에 대해 조금 다르게 바라보고, 정말 중요한 변수가 무엇이고 어떻게 영향을 미치는지를 보기위한 지표이다.

Shap value의 개념

  • 다음과 같이 평균 아파트 값이 310,000 유로 라고 할 때, 4가지 변수(park-nearby, cat-forbidden, area-50$ m^2 $, floor-2nd)를 사용하여 모델을 학습시킨 결과 예측값이 300,000 유로라고 가정해보자.

Shap value 예시 - 01

  • shap value의 목적은 평균 예측값과 실제 예측값의 차이에 대해 변수들에 대한 기여도를 계산하는 것이다.

Shap value 예시 - 02

  • 임의의 변수에 대해서 기여도를 측정하고자 한다면 측정하고자하는 변수들을 제외한 나머지 다른 변수들은 무작위로 임의복원추출을 하여 학습시킨 후 예측치와 측정하고자 하는 변수들 중 특정 하나의 변수와 측정하지 않는 변수들을 임의복원추출하고 나머지 측정 대상 변수들은 고정을 시켜 학습한 모델의 예측치의 차이를 여러번 계산한 값들의 평균으로 기여도를 계산한다.

Shap value 예시 - 03

  • 아래 그림처럼 Shap value를 통해 변수를 살펴보면 수치는 어느 정도 차이가 있지만 어떤 변수가 더 중요한지에 대한 일치성은 갖게 된다.

Shap value 예시 - 04

Shap value 예시 - 05

  • 평균적으로 어떤 방향을 갖는다는 것을 의미하는 것이지, 회귀모형의 계수 해석시에 인과관계로 해석하면 안되는 것과 같이 Shap value 또한 인과관계로 해석하면 안된다.

Shap value 해석시 유의점

  • 아래 그림을 살펴보면 하나의 point는 하나의 관측치를 의미한다. $ x $축은 shap value를 의미하고, $ y $축은 각각의 변수들을 나타낸다. 점들이 많이 몰려있으면 색이 진해지므로 색이 진할수록 각 방향으로 영향력이 높은 것이다.

Shap value 해석 예시 - 01

  • 아래 그래프에서 20대 근방과 60대 이후에는 수직적으로 넓게 분포하고 30대 부터는 shap value가 양의 값을 갖는 경향을 보인다. 이를 바탕으로 30대 부터 60대 이전까지는 대체로 양의 영향을 주며, 20 근방에서는 다른 변수가 Age의 Importance에 영향을 준다고 해석할 수 있다.

Shap value 해석 예시 - 02

Shap value 해석 예시 - 03

Shap value 해석 예시 - 04

Shap value 실습


1
2
3
4
import os
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

데이터 불러오기


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

데이터 상위 5개

데이터 info

  • 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: 경도

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

의미가 없다고 판단되는 변수 제거


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

범주형 변수를 이진형 변수로 변환

  • 범주형 변수는 waterfront 컬럼 뿐이며, 이진 분류이기 때문에 0, 1로 표현한다.
  • 데이터에서 0, 1로 표현되어 있으므로 과정 생략

설명변수와 타겟변수를 분리, 학습데이터와 평가데이터 분리


1
2
3
4
5
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) # 데이터 개수 확인
결과
1
(15129, 8) (6484, 8) (15129,) (6484,)

LightGBM 을 이용하여 아파트 가격 예측 (회귀)


1
2
3
4
5
6
7
8
9
10
11
import lightgbm as lgb
from math import sqrt
from sklearn.metrics import mean_squared_error
lgb_dtrain = lgb.Dataset(data = train_x, label = train_y) # 학습 데이터를 LightGBM 모델에 맞게 변환
lgb_param = {'max_depth': 10, # 트리 깊이
'learning_rate': 0.01, # Step Size
'n_estimators': 1000, # Number of trees, 트리 생성 개수
'objective': 'regression'} # 목적 함수 (L2 Loss)
lgb_model = lgb.train(params = lgb_param, train_set = lgb_dtrain) # 학습 진행
lgb_model_predict = lgb_model.predict(test_x) # 평가 데이터 예측
print("RMSE: {}".format(sqrt(mean_squared_error(lgb_model_predict, test_y)))) # RMSE
결과
1
RMSE: 212217.42594653403

Shap Value를 이용하여 변수 별 영향도 파악


1
2
3
4
5
6
7
8
# !pip install shap (에러 발생시, skimage version 확인 (0.14.2 버젼보다 최신 버젼을 권장))
# import skimage -> skimage.__version__ (skimage version 확인 방법)
# skimage version upgrade -> !pip install --upgrade scikit-image
import shap
import skimage
# skimage.__version__
explainer = shap.TreeExplainer(lgb_model) # 트리 모델 Shap Value 계산 객체 지정
shap_values = explainer.shap_values(test_x) # Shap Values 계산

  • 해당 데이터의 변수 중 ‘yr_built=1986’와 ‘waterfront=0’이라는 것이 음의 영향을 미친다는 것을 확인 할 수 있다.

1
2
3
shap.initjs() # 자바스크립트 초기화 (그래프 초기화)
shap.force_plot(explainer.expected_value, shap_values[0,:], test_x.iloc[0,:]) # 첫 번째 검증 데이터 인스턴스에 대해 Shap Value를 적용하여 시각화
# 빨간색이 영향도가 높으며, 파란색이 영향도가 낮음

첫번째 관측치에 대한 shap value 그래프

  • 전체 검증 데이터에 대한 그래프는 각 변수마다 볼 수 있다.

1
shap.force_plot(explainer.expected_value, shap_values, test_x) # 전체 검증 데이터 셋에 대해서 적용

전체 데이터에 대한 shap value 그래프

  • floor 변수처럼 값이 섞여있는 경향이 크면 그만큼 반응변수에 미치는 영향이 거의 없다는 것으로 해석할 수 있다.
    • grade : 변수의 값이 높을 수록, 예상 가격이 높은 경향성이 있다.
    • yr_built : 변수의 값이 낮을 수록(예전에 지어진 집일수록), 예상 가격이 높은 경향성이 있다.
    • bathrooms : 변수의 값이 높을 수록, 예상 가격이 높은 경향성이 있다.
    • bedrooms : 변수의 값이 높을 수록, 예상 가격이 높은 경향성이 있다.
    • condition : 변수의 값이 높을 수록, 예상 가격이 높은 경향성이 있다
    • waterfront : 변수의 값이 높을 수록, 예상 가격이 높은 경향성이 있다.
    • floors : 해석 모호성 (Feature Value에 따른 Shap Values의 상관성 파악 모호)
    • yr_renovated : 해석 모호성 (Feature Value에 따른 Shap Values의 상관성 파악 모호)

1
shap.summary_plot(shap_values, test_x)

전체 변수에 대한 shap plot

  • 각 변수에 대한 Shap Values의 절대값으로 중요도 파악

1
shap.summary_plot(shap_values, test_x, plot_type = "bar")

Shap value의 절대값 그래프


1
shap.dependence_plot("grade", shap_values, test_x)

grade에 대한 Shap value와 의존적인 변수의 관계 그래프

  • 확실히 완공시점이 낮을수록 집값이 높다는 사실을 확인할 수 있다.

1
shap.dependence_plot("yr_built", shap_values, test_x)

yr_built에 대한 Shap value와 의존적인 변수의 관계 그래프

DS분야에서 Tree 기반 모델이 쓰이는 이유

  • NN은 각 층을 연결하는 노드의 가중치를 업데이트하면서 학습하기에 overffiting이 심하게 일어나고 학습시간이 상대적으로 머신러닝 알고리즘보다 오래 걸렸기 때문에 이전에는 머신러닝 기반의 알고리즘들이 훨씬 많이 사용되어졌다.

DS 분야에서 Tree 기반 모델이 쓰이는 이유 - 01

  • 허나, 알고리즘 및 GPU의 발전이 이전의 NN보다 훨씬 깊은 층을 학습할 수 있도록 연산의 속도를 높였으며, overfitting을 완화시킬 수 있는 Dropout과 같은 기법들을 통해 보완하며 복잡한 데이터의 학습이 가능토록 하였다.

DS 분야에서 Tree 기반 모델이 쓰이는 이유 - 02

  • 이미지 분류에서의 기존 머신러닝 모델은 각각의 픽셀 값을 독립변수로 사용하였는데, 이때 이 피처들 사이의 관계를 독립이라고 가정한 채로 문제를 푸는 형식이었다. 허나, 이미지의 픽셀값은 주변의 픽셀값과의 관계가 없는 것이 아니라는 점은 직관적으로 생각할 수 있다. 이렇게 각 변수들이 독립적인 가정을 이미지의 공간적인 특성을 잡아내는데 적합하지 않다.

DS 분야에서 Tree 기반 모델이 쓰이는 이유 - 03

  • 그에 반해, Deep Learning에서 CNN은 Convolution Layer를 통과해 feature map을 구성하며 이미지의 지역적인 특성부터 최종적으로 전체적인 이미지의 공간적인 특성을 잡아낼 수 있다.

DS 분야에서 Tree 기반 모델이 쓰이는 이유 - 04

DS 분야에서 Tree 기반 모델이 쓰이는 이유 - 05

DS 분야에서 Tree 기반 모델이 쓰이는 이유 - 06

  • 이미지가 아닌 정형 데이터인 게임 User data를 통해 User의 이탈을 예측하는 문제를 푼다고 생각해보자. 각 변수들은 모두 연속적인 관계를 갖는가? 변수들간의 관계를 독립적이라고 가정해도 무방할 것이다. 물론 그 중 몇가지 Feature들은 연관성이 있겠지만 모든 Feature가 그렇진 않을 것이다.

DS 분야에서 Tree 기반 모델이 쓰이는 이유 - 07

최근 10년간 Deep Learning의 부흥기라고 해도 과언이 아닐 것이다. 그럼에도 불구 하고 Kaggle 대회를 비롯한 각종 머신러닝 대회에서 boosting 계열 알고리즘이 우승하는 가장 큰 이유는 대회의 데이터의 성질 때문이라고 생각한다. 물론 필자의 개인적인 생각이지만 머신러닝 알고리즘은 위에서 언급한 것과 같이 개별적인 피처들간의 관계를 독립이라고 가정하고 푸는 알고리즘들이 대부분이다. 예를 들어 회귀분석에서는 최대한 다중공선성을 제거하기 위해 전처리를 하는 것만 생각해봐도 알 수 있을 것이다. 이 처럼 피처간의 관계가 독립이라고 가정하고 문제를 풀어도 무관하거나 실제로 독립적인 피처를 갖춘 데이터로 문제를 풀 경우는 Deep Learning 보단 Machine Learning 알고리즘이 더 강세라고 생각한다.

또한 Machine Learning과 Deep Learning의 가장 큰 차이점은 Machine Learning의 경우 사람이 입력해주는 Feature를 잘 처리해주어야 기본적으로 잘 작돌하고, Deep Learning의 경우 입력을 넣어주면 Layer를 통과하면서 입력 데이터의 특징을 잘 잡아 마지막에 잡아낸 Feature를 Fully connected layer(NN)을 통해 학습하는 방식이므로 Feature에 대한 사람의 노력이 상대적으로 덜 소요될 순 있을 것이다.

DS 분야에서 Tree 기반 모델이 쓰이는 이유 - 08