반응형

원본 링크

Using XGBoost in Python

XGBoost는 요즘 가장 유명한 ML 알고리즘중 하나이다.

(이거 모회사 - 예전에 검색엔진으로 유명했던 -의 챗봇 솔루션의 분류엔진으로 사용되고 있는 것을 알고 있다. 이전에 Random Forest사용하다가 바꾼걸로..)

예측작업의 형태에 관계없는 회귀(Regression) 또는 분류(Classification)

XGBoost는 다른 ML 알고리즘 보다 더 나은 솔루션을 제공하는 것으로 잘 알려져 있다. 사실, XGBoost의 인셉션(inception) 때문에 구조적 데이터를 다루기 위한 "최신" ML 알고리즘이 되었다.

이 글에서는 파이썬으로 XGBoost를 사용하여 ML 모델을 구축해 본다. 더 구체적으로는 다음의 것을 알아본다.

  • 부스팅(Boosting)이 무엇이고 XGBoost는 어떻게 동작하는가
  • 데이터셋에 XGBoost를 적용하는 방법과 결과를 검증하는 방법
  • 모델의 성능을 개선하기 위해 XGBoost에서 튜닝할 수 있는 다양한 하이퍼파라미터
  • Boosted Tree와 속성 중요도(Feature Importance) 시각화 방법

하지만, 무엇이 XGBoost를 유명하게 만들었을까?

  • 속도와 성능 : 원래 C++로 작성되어 다른 앙상블(ensemble) 분류기에 비해 비교적 더 빠르다.
  • 병렬 코어 알고리즘 : 코어 XGBoost 알고리즘이 병렬이기 때문에 멀티코어 프로세스를 활용할 수 있다. 또한 매우 대규모 데이터셋에서 훈련이 가능하도록 만드는 GPU와 컴퓨터 네트워크에서도 병렬이다.
  • 지속적으로 다른 알고리즘보다 좋은 성능 : 다양한 ML 밴치마크 데이터셋에서 더 나은 성능을 보인다.
  • 다양한 튜닝 파라미터 : XGBoost는 내부적으로 교차검증(cross-validation), 정규화(regularization), 사용자 정의 목적 함수(user-defined objective function), 손실값(missing value), 트리 매개변수(tree parameters), scikit-learn 호환가능한 API 등에 대한 파라미터를 갖는다.

XGBoost(Extremem Gradient Boosting)은 부스팅 알고리즘(boosting algoritms) 제품군에 속하고 코어에 GBM(Gradient Boosting) 프레임워크를 사용한다. 이는 최적화된 분산 gradient boosting 라이브러리이다.

부스팅(Boosting)

부스팅은 앙상블 이론에 따라 동작하는 순차적(sequential) 기술이다. 부스팅은 약학습자(weak learner)를 결합하고 개선된 예측 정확도를 전달한다. 임의의 순간 t에서 모델 결과는 이전 순간 t-1의 결과를 기초로 가중된다. 올바르게 예측된 결과는 더 낮은 가중치가 주어지고 잘못 분류된 것은 더 높은 가중치가 된다. 약학습기(weak learner)는 임의로 추측하는 것보다 약간 더 좋은 것이다. 예측이 50%보다 약간 더 좋은 결정 트리(decision tree)를 예로 들 수 있다.
간단한 그림으로 일반적인 부스팅을 알아보자.

위 그림처럼, (4개의 박스 안에) 4개의 분류기가 가능한 같은종류로 $+$와 $-$를 분류하려고 한다.

  1. Box 1 : 첫번째 분류기(보통 decision stump)는 D1에서 수직 라인(split)을 만든다. 이는 D1의 왼쪽에 있는 것은 $+$이고 D1의 오른쪽에 있는 것은 $-$인 것을 나타낸다. 그러나, 이 분류기는 3개의 $+$를 잘못 분류한다.
    Note Decision Stump는 1-depth(one level)에서 단지 나누는 결정 트리(Decision Tree) 모델이다. 따라서 최종 예측은 단지 하나의 특성에 기초한다.
  2. Box 2 : 두번째 분류기는 잘못 분류된 3개의 $+$에 좀 더 가중치를 부여한다. (위 그림에서 좀 더 커진 $+$를 보자) 그리고 D2에 수직 라인을 만든다. 이것은 다시 D2의 오른쪽에 있는 것이 $-$이고 왼쪽은 $+$인 것을 나타낸다. 여전히 3개의 $-$를 제대로 분류하지 못한다.
  3. Box 3 : 세번째 분류기는 잘못 분류된 3개의 $-$에 좀더 가중치를 주고 D3에 수평선을 만든다. 하지만 여전히 올바른 분류에 실패한다. (원으로 표시된 것을 보자)
  4. Box 4 : 약분류기(weak classifier, Box 1/2/3)의 가중치가 적용된 조합이다. 보이는 것처럼 모든 점을 올바르게 분류한다.

이것이 약한 모델(weak model)을 만들고 있는 부스팅 알고리즘의 기본 아이디어이고 이것은 다양한 특성 중요도와 파라미터에 대한 결론을 내린다. 그리고 새로운 강력한 모델을 구축하고 이전 모델의 오분류(misclassification) 오류를 활용하여 이를 감소시키기 위해 앞의 결론을 사용한다.

이제 XGBoost를 보자. 시작하기 위해서는 XGBoost의 기본 기초 학습기(default base learner): 트리 앙상블(tree ensembles)에 대해 알아야 한다. 트리 앙상블 모델은 분류 및 회귀 트리(CART - Classification And Regression Trees)의 모음이다. 트리는 차례로 성장하고 그 다음의 반복에서 만들어지는 오분류 비율을 감소시키려고 한다.

만약 트리 앙상블 세션에서 이미지를 체크하려면, 각 트리가 보는 데이터에 기초하여 다른 예측 점수를 제공하고 각각의 트리의 점수는 최종 점수를 얻기 위해 합산된다는 것을 알 수 있다.

이 글에서는 회귀 문제를 해결하기 위해 XGBoost를 사용한다. 데이터셋은 UCI Machine Learning Repository로부터 가지고 왔다. 이 데이터셋은 sklern의 dataset 모듈에도 존재한다. 여기에는 보스턴의 거주용 주택에 대해 여러 방면으로 설명하는 14개의 설명 변수가 있다. 문제는 $1000당 소유자가 거주하는 주택의 중간값을 예측하는 것이다.

Using XGBoost in Python

우선, 다른 어떤 데이터셋으로 작업하는 것과 같이 Boston Housing 데이터셋을 임포트하고 boston 변수에 저장한다. Scikit-learn에서 데이터셋을 임포트 하기 위해 아래 코드를 입력한다.


from sklearn.datasets import load_boston
boston = load_boston()

boston 변수는 딕셔너리(dictionary)이기 때문에 .keys() 메소드를 사용하여 key 목록을 체크할 수 있다.


print(boston.keys())
dict_keys(['data', 'target', 'feature_names', 'DESCR'])

데이터셋의 크기를 반환하는 boston.data.shape 속성을 사용하여 쉽게 모양을 확인할 수 있다.


print(boston.data.shape)
(506, 13)

(506, 13)은 506개의 열과 13개의 컬럼(행))이 있다는 의미디다. 만약 13개의 컬럼이 무엇인지 알고 싶다면, 간단하게 .feature_names 속성을 사용하여 특성 이름을 확인할 수 있다.


print(boston.feature_names)

['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO' 'B' 'LSTAT']

데이터셋에 대한 설명(description)은 .DESCR을 사용하여 볼 수 있다.


print(boston.DESCR)

Boston House Prices dataset


Notes

Data Set Characteristics:  

    :Number of Instances: 506

    :Number of Attributes: 13 numeric/categorical predictive

    :Median Value (attribute 14) is usually the target

    :Attribute Information (in order):
        - CRIM     per capita crime rate by town
        - ZN       proportion of residential land zoned for lots over 25,000 sq.ft.
        - INDUS    proportion of non-retail business acres per town
        - CHAS     Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
        - NOX      nitric oxides concentration (parts per 10 million)
        - RM       average number of rooms per dwelling
        - AGE      proportion of owner-occupied units built prior to 1940
        - DIS      weighted distances to five Boston employment centres
        - RAD      index of accessibility to radial highways
        - TAX      full-value property-tax rate per $10,000
        - PTRATIO  pupil-teacher ratio by town
        - B        1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
        - LSTAT    % lower status of the population
        - MEDV     Median value of owner-occupied homes in $1000's

    :Missing Attribute Values: None

    :Creator: Harrison, D. and Rubinfeld, D.L.

This is a copy of UCI ML housing dataset.
http://archive.ics.uci.edu/ml/datasets/Housing


This dataset was taken from the StatLib library which is maintained at Carnegie Mellon University.

The Boston house-price data of Harrison, D. and Rubinfeld, D.L. 'Hedonic
prices and the demand for clean air', J. Environ. Economics & Management,
vol.5, 81-102, 1978.   Used in Belsley, Kuh & Welsch, 'Regression diagnostics
...', Wiley, 1980.   N.B. Various transformations are used in the table on
pages 244-261 of the latter.

The Boston house-price data has been used in many machine learning papers that address regression
problems.   

**References**

   - Belsley, Kuh & Welsch, 'Regression diagnostics: Identifying Influential Data and Sources of Collinearity', Wiley, 1980. 244-261.
   - Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning. In Proceedings on the Tenth International Conference of Machine Learning, 236-243, University of Massachusetts, Amherst. Morgan Kaufmann.
   - many more! (see http://archive.ics.uci.edu/ml/datasets/Housing)

이제 판다스 데이터프레임(DataFrame)으로 변환하자. 이를 위해 pandad 라이브러리를 임포트하고 boston.data를 인자로 하여 DataFrame() 함수를 호출한다. 컬럼의 이름을 지정하기 위해 boston.feature_names에 판다스 데이터프레임의 .columns 속성을 할당한다.


import pandas as pd

data = pd.DataFrame(boston.data)
data.columns = boston.feature_names

판다스 데이터프레임에서 head() 메소드를 사용하여 상위 5개 열을 살펴본다.


data.head()

데이터 프레임내에 PRICE 컬럼이 없는 것을 알 수 있다. 이는 대상 컬럼이 boston.target인 다른 속성에서 사용가능하기 때문이다. 판다스 데이터프레임에 boston.target을 추가하자.


data['PRICE'] = boston.target

데이터에 대한 유용한 정보를 얻기 위해 데이터프레임에서 .info() 메소드를 실행해 보자.


data.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 506 entries, 0 to 505
Data columns (total 14 columns):
CRIM       506 non-null float64
ZN         506 non-null float64
INDUS      506 non-null float64
CHAS       506 non-null float64
NOX        506 non-null float64
RM         506 non-null float64
AGE        506 non-null float64
DIS        506 non-null float64
RAD        506 non-null float64
TAX        506 non-null float64
PTRATIO    506 non-null float64
B          506 non-null float64
LSTAT      506 non-null float64
PRICE      506 non-null float64
dtypes: float64(14)
memory usage: 55.4 KB

14개의 컬럼(목표 변수 PRICE를 포함하는)과 506열을 가진 데이터셋이 보인다. 어떤 결측값도 없는 연속적인 특성만 존재하는 것을 나타내는 float 타입의 컬럼인 것을 기억하자. 데이터셋의 다른 특성에 대한 요약 통계를 보려면 decribe() 메소드를 사용한다.

Note decribe()는 본직적으로 범주가 아닌 연속적 컬럼의 요약통계를 제공한다.


data.describe()

만약 범주적 특성을 가진 데이터셋에서 XGBoost를 사용할 계획이라면, 모델 훈련전에 그같은 특성들에 몇가지 인코딩(원핫인코딩 같은)을 적용하는 것을 생각해 볼 수 있다. 또한 데이터셋에 NA 같은 결측치가 있다면 그것의 처리를 분리 또는 분리하지 않을 수 있다. 이는 XGBoost가 내부적으로 결측치를 다룰 수 있기 때문이다.

추가적인 탐험적 분석과 특성공학 없이 데이터셋에서 모델을 훈련하기 위한 알고리즘을 적용할 수 있다.

XGBoost의 scikit-learn 호환 API를 사용하여 기초 학습기(base learner)로 Tree를 사용하는 모델을 구축한다. 그 과정에서 모델 성능을 개선하기 위해 XGBoost가 제공하는 몇가지 공통 튜닝 파라미터와 테스트 셋에서 훈련된 모델의 성능을 점검하기 위한 RMSE(Root Mean Squared Error)를 사용하는 것에 대해 배울 수 있다.

$$ RMSE = \sqrt{\frac{\sum_{i=1}^{n}(y_i - x_i) ^ 2}{n}} $$

예측값 실제값 error(예측값 - 실제값)
5 3 2
4 1 3
5 4 1
1 1 0

$$ RMSE = \sqrt{\frac{2 ^ 2 + 3 ^ 2 + 1 ^ 2 + 0 ^ 2}{4}} = 1.87 $$

xgboost 라이브러리와 모델을 구축하기 위해 사용할 다른 중요한 라이브러리를 임포트한다.

Note pip install xgboost 를 이용하여 시스템에 xgboost 라이브러리를 설치할 수 있다.


import xgboost as xgb
from sklearn.metrics import mean_squared_error
import pandas as pd
import numpy as np

목표 변수(target variable)과 나머지 변수를 .iloc을 사용하여 나눈다.


X, y = data.iloc[:,:-1],data.iloc[:,-1]

이제 XGBoost가 제공하고 성능과 효율 이익을 주는 Dmatrix라는 최적화된 데이터 구조로 데이터셋을 변환한다.


data_dmatrix = xgb.DMatrix(data=X,label=y)

XGBoost's hyperparameters

모델 구축전에 XGBoost가 제공하는 튜닝 파라미터(tuning parameters)에 대해 알아야 한다. XGBoost에는 트리기반 학습기에 대한 많은 튜닝 파라미터가 있고 전체 내용은 여기에서 볼 수 있다. 하지만 가장 공통적으로 알아야 할 것은 다음과 같다.

  • learning_rate : 오버피팅을 막기위해 사용되는 축소 단계 크기로 0과 1사이의 값이다.
  • max_depth : 부스팅 라운드 동안 각각의 트리가 얼마나 깊게 성장할 수 있는지 결정
  • subsample : 트리당 사용되는 샘플의 비율. 낮은 값은 언더피팅으로 이어질 수 있다.
  • colsample_bytree : 트리당 사용되는 특성의 비율. 높은 값은 오버피팅으로 이저질 수 있다.
  • n_estimators : 구축하고자하는 트리의 개수
  • objective : 손실함수 결정
    • reg:linear : 회귀문제
    • reg:logistic : 오직 결정(선택)만 있는 분류 문제
    • binary:logistic : 확률을 포함하는 분류 문제

XGBoost는 또한 더 복잡해진 모델을 제한하기 위해 정규화(regularization) 파라미터를 지원하여 더 간단한 모델로 감소시킨다.

  • gamma : 분할 후 손실에서 예상 감소에 기초하여 주어진 노드를 분할할지 여부를 제어한다. 높은 값은 더 적은 분할로 이어진다. 오직 트리기반 학습기만 지원된다.
  • alpha : 리프(leaf) 가중치에 L1 정규화. 큰 값은 더 많은 정규화로 이어진다.
  • lambda : 리프 가중치에 L2 정규화를 하고 L1 정규화보다 더 부드럽다.(smooth)

비록 기초 학습기로 트리를 사용한다하더라도 XGBoost의 비교적 덜 유명한 선형(linear) 학습기 및 dart로 알려진 다른 트리 학습기 또한 사용할 수 있다. 이를 하기 위해서는 booster 파라미터에 gbtree(default), gblinear 또는 dart를 설정하면 된다.

이제 데이터의 20% 크기로 test_size를 설정한 sklearn의 model_selection 모듈의 train_test_split 함수를 사용하여 결과를 교차 검증(cross-validation)하기 위한 훈련과 테스트 셋을 만든다. 또한, 결과의 재현성을 유지하기 위해 random_state 또한 할당한다.


from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)

다음은 인자로 하이퍼파라미터를 전달받은 XGBoost 라이브러리로부터 XGBRegressor() 클래스를 호출하여 XGBoost regressor 객체를 초기화한다. 분류문제에서는 XGBClassifier() 클래스를 사용한다.


xg_reg = xgb.XGBRegressor(objective ='reg:linear', colsample_bytree = 0.3, learning_rate = 0.1, max_depth = 5, alpha = 10, n_estimators = 10)

.fit() 메소드로 훈련셋으로 regressor를 훈련하고 .predict() 메소드로 테스트 셋에서 예측값을 만든다.


xg_reg.fit(X_train,y_train)

preds = xg_reg.predict(X_test)

sklearn의 metric 모듈의 mean_squared_error 함수를 사용하여 RMSE를 계산한다.


rmse = np.sqrt(mean_squared_error(y_test, preds))
print("RMSE: %f" % (rmse))

RMSE: 10.569356

가격 예측에 대한 RMSE가 $1000당 약 10.5 정도인 것을 알 수 있다.

k-fold Cross Validation using XGBoost

좀더 가력한 모델을 구축하기 위해서는 보통 원본 훈련 데이터셋의 모든 항목이 훈련과 검즘 모두에 사용되는 k-fold 교차 검증을 수행한다. 또한 각 항목(entry)은 단 한번만 검증에 사용된다. XGBoost는 cv() 메서드를 통해 k-fold 교차검증을 지원한다. 단지 만들고자하는 교차 검증 묶음의 수인 nfolds 파라미터를 지정하는 것으로 이를 수행 할 수 있다. 전체 파라미터는 여기를 보자.

  • num_boost_round : 구축한 트리의 수를 나타낸다.(n_estimators와 유사)
  • metrics : 교차검증동안 관찰할 평가 지표
  • as_pandas : 판다스 데이터프레임으로 결과 반환
  • early_stopping_rounds : 만약 주어진 횟수 동안 정해진 지표가 개선되지 않으면, 일찍 모델의 훈련을 종료한다.
  • seed : 결과의 재현성을 위함

이제 모든 하이퍼파라미터와 그 값을 키-값 쌍으로 갖는 params 딕셔너리를 만든다. 하지만, num_boost_rounds를 사용하기 때문에 params 딕셔너리에서 n_estimators는 제외한다.

XGBoost의 cv() 메소드를 호출하여 3-폴드 교차검증 모델을 구축하기 위해 이 파라미터를 사용하고 그 결과는 cv_results 데이터프레임에 저장한다. 여기서 미리 생성한 Dmatrix 객체를 사용한다는 것에 주의하자.


params = {"objective":"reg:linear",'colsample_bytree': 0.3,'learning_rate': 0.1,
                'max_depth': 5, 'alpha': 10}

cv_results = xgb.cv(dtrain=data_dmatrix, params=params, nfold=3,
                    num_boost_round=50,early_stopping_rounds=10,metrics="rmse", as_pandas=True, seed=123)

cv_results는 각 부스팅 라운드에 대한 훈련과 테스트 RMSE 지표(metrics)를 포함한다.


cv_results.head()
test-rmse-mean test-rmse-std train-rmse-mean train-rmse-std
0 21.746693 0.019311 21.749371 0.033853
1 19.891096 0.053295 19.859423 0.029633
2 18.168509 0.014465 18.072169 0.018803
3 16.687861 0.037342 16.570206 0.018556
4 15.365013 0.059400 15.206344 0.015451

최종 부스팅 라운드를 추출하여 출력한다.


print((cv_results["test-rmse-mean"]).tail(1))

49    4.031162
Name: test-rmse-mean, dtype: float64

가격 예측에 대한 RMSE가 이전보다 줄어든 $1000당 4.03 정도인 것을 알 수 있다. 다른 하이퍼파리미터 조합으로 더 낮은 RMSE에 도달할 수 있다. 아마도 최적화된 하이퍼파라미터 조합에 도달하기 위해 Grid Search, Random Search와 Bayesian Optimzation같은 기술을 적용하는 것을 고려햘 수도 있다.

Visualize Boosting Trees and Feature Importance

전체 주택 데이터셋을 사용하여 XGBoost가 생성한 fully boosted 모델로부터 각각을 트리를 시각화 할 수 있다. XGBoost는 쉽게 이 형태를 시각화하는 plot_tree() 함수를 가지고 있다. XGBoost 학습 API를 사용하여 모델을 훈련한 후, num_trees 인자를 사용하여 도식화하고자하는 트리의 수를 plot_tree() 함수에 전달할 수 있다.


xg_reg = xgb.train(params=params, dtrain=data_dmatrix, num_boost_round=10)

matplotlib 라이브러리로 첫번째 트리를 도식화한다.


import matplotlib.pyplot as plt

xgb.plot_tree(xg_reg,num_trees=0)
plt.rcParams['figure.figsize'] = [50, 10]
plt.show()

이 도표는 어떻게 모델이 최종 결론에 도달하고 어떤 분할이 이러한 결론데 도달하게 만들었는지에 대한 통찰을 제공한다.

Note 만약 위 도식이 'graphviz' 오류를 발생시킨다면, 'pip install graphviz' 명령어로 graphviz 패키지를 설치해야 한다.

XGBoost 모델을 시각화하기 위한 또다른 방법은 모델의 원본 데이터셋에서 각 특성 컬럼의 중요도를 평가하는 것이다.

이를 위한 간단한 방법중 하나는 모델에서 모든 부스팅 라운드(트리)에 걸쳐 분할된 각 특성의 수를 세는 것이다. 그리고 결과를 얼마나 많이 나타났는지(사용되었는지)에 따라 정렬된 특성을 막대 그래프로 시각화 한다. XBBoost는 정확하기 이 작업을 하는 plot_importance() 함수를 제공한다.


xgb.plot_importance(xg_reg)
plt.rcParams['figure.figsize'] = [5, 5]
plt.show()

위에서 보이는 것과 같이 RM 특성은 모든 특성가운데 가장 높은 중요도를 갖는다.

반응형

+ Recent posts