Go together

If you want to go fast, go alone. If you want to go far, go together.

파이썬 머신 러닝 완벽 가이드

분류

NowChan 2021. 11. 6. 10:38

배운 내용

  1. 결정 트리
  2. Graphviz
  3. export_graphviz()
  4. DecisionTreeClassifier.feature_importances_
  5. sns.barplot()
  6. make_classification() 
  7. DataFrame.GroupBy.cumcount()
  8. DataFrame.reset_index()
  9. GridSearchCV
  10. grid_cv.best_score_ & grid_cv.best_params_
  11. grid_cv.best_estimator_
  12. Estimator.feature_importance_
  13. Series.sort_values()
  14. Ensemble learning
  15. VoringClassifier
  16. load_breast_cancer()
  17. RandomForestClassifier
  18. GradientBoostingClassifier
  19. XGBoost
  20. DataFrame.values
  21. early_stopping_rounds
  22. xgboost.cv()
  23. plot_importance()
  24. LightGBM

 

지도학습은 레이블(Label), 즉 명시적인 정답이 있는 데이터가 주어진 상태에서 학습하는 머신러닝 방식입니다. 지도학습의 대표적인 유형은 분류(Classification)입니다. 분류는 미지의 레이블값을 입력된 피처를 통해 예측합니다.

 

아래는 분류를 구현할 수 있는 머신러닝 알고리즘들입니다.

  • 베이즈(Bayes) 통계와 생성 모델에 기반한 나이브 베이즈(Naive Bayes)
  • 독립변수와 종속변수의 선형 관계성에 기반한 로지스틱 회귀(Logistic Regression)
  • 데이터 균일도에 따른 규칙 기반의 결정 트리(Decision Tree)
  • 개별 클래스 간의 최대 분류 마진을 효과적으로 찾아주는 서포트 벡터 머신(Support Vector Machine)
  • 근접 거리를 기준으로 하는 최소 근접(Nearest Neighbor) 알고리즘
  • 심층 연결 기반의 신경망(Neural Network)
  • 서로 다른(또는 같은) 머신러닝 알고리즘을 결합한 앙상블(Ensemble)

이번 장에서는 앙상블 방법(Ensemble Method)을 집중적으로 다룹니다. 앙상블은 정형 데이터 예측 분석 영역에서 높은 예측 성능을 자랑합니다. 앙상블은 서로 다른/또는 같은 알고리즘을 단순히 결합한 형태도 있으나, 일반적으로 배깅(Bagging)부스팅(Boosting) 방식으로 나뉩니다.

 

Bagging 방식의 대표는 랜덤 포래스트(Random Forest) 방식입니다. Boosting 방식의 대표는 Gradient Boosting 방식입니다. Gradient Boosting 방식은 뛰어난 예측 성능에 비해 느린 수행 시간이 단점이 있어서 최적화 모델 튜닝이 어려웠습니다. 하지만, XgBoost(eXtra Gradient Boost)와 LightGBM 등 예측 성능, 수행시간을 단축시킨 알고리즘의 등장으로 Boostig 방식이 최근 활발하게 사용되고 있습니다.

 

이 장에서는 앙상블 방법의 개요, 랜덤 포레스트, 그래디언트 부스팅, XGBoost, LightGBM, Stacking 기법 등을 배우겠습니다. 앙상블은 서로 다른/또는 같은 알고리즘을 결합한다고 했는데, 대부분 동일한 알고리즘을 결합합니다. 앙상블의 기본 알고리즘은 결정 트리입니다.

 

결정 트리는 데이터 스케일링, 정규화 등의 사전 가공 영향이 매우 적지만, 예측 성능을 향상시키기 위해 복잡한 구조를 가지고 있고, 이로 인해 과적합(overfitting)이 발생해 반대로 예측 성능이 저하될 수도 있습니다. 하지만, 이러한 단점이 앙상블 기법에서는 장점으로 작용합니다. 앙상블은 매우 많은 약한 학습기(성능이 떨어지는 학습 알고리즘)를 결합해 확률을 보완하고 오류가 발생한 부분의 가중치를 업데이트함으로써 예측성능을 향상시킬 수 있기 때문입니다. 

 

결정 트리

결정트리는 데이터에 있는 규칙을 학습을 통해 자동으로 찾아내 Tree 기반의 분류 규칙을 만드는 것입니다. 일반적으로 규칙을 가장 쉽게 표현하는 방법은 if/else 기반으로 나타내는 것입니다. 결정 트리는 규칙 노드(Decision Node)와 리프 노드(Leaf Node)로 구성돼있습니다. 회색의 경우 Leaf Node이며, 결정된 클래스 값입니다. 

결정 트리 묘사

새로운 규칙 노드 마다 서브 트리(Sub Tree)가 생성됩니다. 규칙 노드가 많다는 것은 분류를 결정하는 방식이 복잡해진다는 얘기고, 이는 과적합으로 이어지기 쉽습니다. 

서브 트리

즉, 트리의 깊이(depth)가 깊어질 수록 예측 성능이 저하될 가능성이 높습니다.

depth의 정의

 

분할한 Leaf Node에 최대한 많은 데이터 세트가 속할 수 있게 규칙 노드의 조건이 결정돼야합니다. 어떻게 분할(split)할 것인지도 중요한데, 최대한 균일한 데이터 세트를 구성할 수 있도록 분할하는 것이 좋습니다.

 

예를 들어, 레고 블록이 10개 있다고 합시다. 형태는 동그라미, 세모, 네모가 있고, 색깔은 노랑, 빨강, 파랑이 있습니다. 노란색은 모두 동그라미 모양이라고 할 때 첫 번째 규칙 노드의 조건을 if 색깔 == 노란색으로 만드는 것입니다. 그리고 나머지 블록에 대해 다시 균일도 조건을 찾아 분류하는 것이 가장 효율적인 분류 방식입니다. 

 

정보의 균일도를 측정하는 대표적인 방법은 엔트로피를 이용한 정보 이득(Information Gain)지수와 지니 계수가 있습니다.

 

정보 이득 = 1 - 엔트로피 지수입니다. 엔트로피는 데이터 집합의 혼잡도를 의미하는데, 서로 다른 값이 섞여 있으면 엔트로피가 높고, 같은 값이 섞여 있으면 엔트로피가 낮습니다. 정보이득이 높을 수록 균일한 데이터 집합입니다.

 

지니계수는 0이 가장 평등하고 1로 갈 수록 불평등합니다. 지니계수가 낮을 수록 균일한 데이터 집합입니다.

 

사이킷런에서 구현한 DecisionTreeClassifier는 지니 계수를 이용해 데이터 세트를 분할합니다.  지니 계수가 낮은 조건(가장 잘 구분될 수 있는 질문)을 찾아 자식 트리 노드에 걸쳐 반복적으로 분할한 뒤, 데이터가 모두 특정 분류에 속하게 되면 분할을 멈추고 분류를 결정합니다.

해당 상황에서 가장 잘 구분될 수 있는 질문(지니 계수가 낮은 조건)을 한다.
분류2
과적합

과적합된 사진을 보면 트리의 depth가 깊은 것을 확인할 수 있다.

 

결정 트리 모델의 특징

결정 트리의 장점균일도라는 룰에 기반하고 있어 알고리즘이 직관적이라는 것입니다. 데이터의 균일도만 신경쓰면 되므로 피처의 스케일링, 정규화 같은 전처리 작업이 필요 없습니다. 단점으로는 과적합으로 테스트 데이터에 대한 유연성이 떨어진다는 점입니다. 피처 데이터의 균일도에 따른 룰 규칙으로 서브 트리를 계속 만들다 보면 트리의 depth가 깊어질 수 밖에 없습니다. 따라서 트리의 크기를 사전에 제한하는 튜닝이 필요합니다.

 

결정 트리 파라미터

사이킷런에서 결정 트리를 구현한 클래스는 DecisionTreeClassifier, DecisionTreeRegressor가 있습니다. 두 클래스 모두 CART(Classification And Regression Trees)를 기반으로 만들어졌습니다.

 

 DecisionTreeClassifier, DecisionTreeRegressor 모두 파라미터는 다음과 같이 동일한 파라미터를 사용합니다.

  • min_samples_split
    • 노드를 분할할 수 있는 최소 샘플 데이터 수, default는 2
  • min_samples_leaf
    • 말단 노드(leaf node)가 되기 위한 최소 샘플 데이터 수, min_samples_split 기준에 맞아서 분할을 하더라도 min_samples_leaf 기준에 맞지 않아서 말단 leaf를 만들 수 없으면 최종 leaf 노드로 생성되지 못합니다. 
  • max_features
    • 무작위로 선택한 feature column의 갯수입니다. default는 None으로 전체 피처를 선정합니다.
    • int형은 피처의 갯수, float은 퍼센트, sqrt, auto, log 가 있음
  • max_depth
    • 트리의 최대 깊이를 설정, default는 None이다.
  • max_leaf_nodes
    • 말단 노드(Leaf Node)의 최대 개수

 

결정 트리 모델의 시각화

Graphviz 패키지를 이용하면 결정 트리 알고리즘이 어떤 규칙을 가지고 트리를 생성하는지 시각적으로 보여줄 수 있습니다. 

 

export_graphviz()

사이킷런은 Graphviz 패키지와 쉽게 인터페이스할 수 있도록 export_graphviz() API를 제공합니다. 파라미터로는 학습이 왼료된 Estimator, 피처의 이름 리스트, 레이블 이름 리스트가 있습니다.

 

Graphviz 설치방법

Graphviz는 C/C++로 운영 체제에 포팅된 패키지 이므로 Graphviz를 설치한 뒤 파이썬과 인터페이스할 수 있는 파이선 래퍼(Wrapper) 모듈을 별도로 설치해야 합니다.

  1. https://graphviz.gitlab.io/_pages/Download/Download_windows.html에서 window 버전 Graphviz를 다운받는다.
  2. Graphviz의 파이썬 래퍼 모듈을 pip를 통해 설치합니다. 아나콘다 콘솔이나 os command 콘솔에서 pip install graphviz 명령어로 설치할 수 있습니다. (관리자 권한으로 실행해야합니다.)
  3. Graphviz와 그 파이썬 래퍼 모듈을 연결하려면 사용자 변수와 시스템 변수에 path값을 설정해야합니다. 
    • Graphviz가 C:\Program Files(x86)\Graphviz2.38 디렉터리에 설치됐다고 가정하겠습니다.
    • 사용자 변수 경로에 Graphviz가 C:\Program Files(x86)\Graphviz2.38\bin을 추가합니다.
    • 시스템 변수 경로에 Graphviz가 C:\Program Files(x86)\Graphviz2.38\bin\dot.exe를 추가합니다.

설치가 완료되면, 주피터 노트북 서버 프로그램을 재기동해야합니다. 환경 변수 path를 주피터 노트북에서 사용하려면 서버 프로그램에서 이 변수를 재로딩해야 하기 때문입니다.

 

붓꽃 데이터 세트와 DecisionTreeClassifier를 이용한 규칙 트리 그래프

붓꽃 데이터 세트를 DTC에 이용해 학습한 뒤 어떤 형태로 규칙 트리가 만들어지는지 확인해보겠습니다. 

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings('ignore')

# DecisionTree Classifier 생성
dt_clf = DecisionTreeClassifier(random_state=156)

# 붓꽃 데이터를 로딩하고, 학습과 테스트 데이터 세트로 분리
iris_data = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target,\
                                                    test_size=0.2, random_state=11)

# DecisionTreeClassifier 학습
dt_clf.fit(X_train, y_train)

사이킷런 트리 모듈은 export_graphviz() 함수를 통해 Graphviz가 읽어 들여서 그래프 형태로 시각화할 수 있는 출력 파일을 생성합니다. export_graphviz()에 인자로 학습된 estimator, output 파일명, 결정 클래스의 명칭, 피처의 명칭을 입력하면 됩니다.

from sklearn.tree import export_graphviz

# export_graphviz()의 호출 결과로 out_file로 지정된 tree.dot파일을 생성함.
export_graphviz(dt_clf, out_file="tree.dot", class_names=iris_data.target_names\
                , feature_names=iris_data.feature_names, impurity=True, filled=True)

생성된 출력 파일 tree.dot을 Graphviz의 파이썬 래퍼 모듈을 호출해 결정 트리의 규칙을 시각적으로 표현할 수 있다.

import graphviz
# 위에서 생성된 tree.dot 파일을 Graphviz가 읽어서 주피터 노트북상에서 시각화
with open("tree.dot") as f:
  dot_graph = f.read()
graphviz.Source(dot_graph)
'''결과1'''

결과1

결과1을 통해 각 규칙에 따라 브랜치(branch) 노드와 말단 리프(leaf) 노드가 어떻게 구성되는지 한 눈에 볼 수 있습니다.

 

더 이상 자식 노드가 없는 노드리프 노드로 최종 클래스(레이블) 값이 결정 되는 노드입니다. 리프 노드가 되려면 오직 하나의 클래스 값으로 최종 데이터가 구성되거나 리프 노드가 될 수 있는 하이퍼 파라미터 조건을 충족하면 됩니다.

 

자식 노드가 있는 노드브랜치 노드이며 자식 노드를 만들기 위한 분할 규칙 조건을 가지고 있습니다.

 

노드 속 지표의 의미

위 그림에서 첫 번째 브랜치 노드를 통해 살펴본 지표의 의미는 다음과 같습니다.

  • petal length (cm) <= 2.45와 같이 피처의 조건이 있는 것은 자식 노드를 만들기 위한 규칙 조건입니다. 이 조건이 없으면 리프노드 입니다.
  • gini는 value로 주어진 데이터 분포에서의 지니 계수입니다. iris의 경우 K=3이다.
    • 첫 번째 브랜치 노드에서 p1 = 41/120, p2=40/120, p3=39/120 이므로 1-(p1)**2 -(p2)**2 -(p3)**2을 하면, 0.667이 나온다.

K개의 class 라벨이 있는 샘플들의 Classification 작업에서 Gini impurity, J=K

  • samples는 현 규칙에 해당하는 데이터 건수로 120개의 데이터가 있다는 뜻입니다.
  • value는 각 클래스에 해당하는 데이터 건수입니다. iris는 0, 1, 2의 클래스를 가지며 각각 Setosa, Versicolor, Virginica입니다.
  • class = setosa는 하위 노드를 가질 경우에 setosa의 개수가 41개로 제일 많다는 의미입니다.
  • 색깔은 주황색: 0, 초록색: 1, 보라색: 2 레이블을 의미합니다. 색깔이 짙을 수록 지니 계수가 낮고 해당 레이블에 속하는 샘플 데이터가 많다는 의미입니다.

이렇게 petal length (cm) <= 2.45 규칙이 True/False로 분기하게 되면 2번, 3번 노드가 만들어집니다. 2번 노드는 모든 데이터가 Setosa로 결정되므로 리프 노드가 됩니다.

 

결정 트리의 규칙 생성 로직이 필요한 이유

위 그림의 4번 노드를 보면, value=[0, 37, 1]를 완벽하게 구분하기 위해 노드를 계속 만드는 것을 확인할 수 있습니다. 이러한 문제로 인해 트리가 복잡해지고 과적합 문제가 발생합니다. 때문에 결정 트리 알고리즘을 제어하는 하이퍼 파라미터는 대부분 복잡한 트리가 생성되는 것을 막기 위한 용도입니다.

 

min_samples_split=4인 경우, 아래 그림처럼 말단에 samples=3인 leaf node가 더 이상 분할되지 않음을 알 수 있습니다.

min_samples_split=4인 결정 트리

 

DecisionTreeClassifier.feature_importances_

결정 트리는 균일도에 기반해 어떠한 속성을 규칙 조건으로 선택하느냐가 중요한 요건입니다.

특히 몇몇의 피처가 명확한 규칙 트리를 만드는데 기여하기 때문에, DecisionTreeClassifier의 객체에서 결정 트리를 만드는데 기여한 중요도를 feature_importances_로 제공하고 있습니다. ndarray 형태로 반환됩니다. 값이 높을 수록 중요한 피처입니다.

 

피처의 중요도를 3가지 형태로 출력해보겠습니다.

import seaborn as sns
import numpy as np
%matplotlib inline

# feature importance 추출
print("Feature importances:\n{0}".format(np.round(dt_clf.feature_importances_, 3)))

# feature별 importance 매핑, zip(A, B)는 A와 B의 원소 쌍을 튜플로 반환 
for name, value in zip(iris_data.feature_names, dt_clf.feature_importances_):
  print('{0} : {1:.3f}'.format(name, value))

# feature importance를 column별로 시각화하기
sns.barplot(x=dt_clf.feature_importances_, y=iris_data.feature_names)

'''
Feature importances:
[0.025 0.    0.555 0.42 ]
sepal length (cm) : 0.025
sepal width (cm) : 0.000
petal length (cm) : 0.555
petal width (cm) : 0.420
결과2
'''

결과2

petal length가 0.555로 피처 중요도가 가장 높음을 알 수 있습니다.

 

결정 트리 과적합(Overfitting)

결정 트리가 학습 데이터를 분할해서 트리를 만들고 예측을 수행하는 원리를 알아보고 과적합으로 인한 문제를 시각화해보겠습니다. 사이킷런은 분류를 위한 데이터를 쉽게 만들 수 있는 make_classification() 함수를 제공합니다. 

 

X축, Y축은 각각의 피처이고, 색깔은 클래스 레이블 값을 나타냅니다.

from sklearn.datasets import make_classification
import matplotlib.pyplot as plt
%matplotlib inline

plt.title("3 Class values with 2 Features Sample data creation")

# 2차원 시각화를 위해서 피처는 2개, 클래스는 3가지 유형의 분류 샘플 데이터 생성.
X_features, y_labels = make_classification(n_features=2, n_redundant=0, n_informative=2\
                                           , n_classes=3, n_clusters_per_class=1, random_state=0)

# 그래프 형태로 2개의 피처를 2차원 좌표 시각화, 각 클래스 값은 다른 색깔로 표시됨.
plt.scatter(X_features[:, 0], X_features[:, 1], marker='o', c=y_labels, s=25, edgecolor='k')
'''결과3'''

결과3

예제 코드에서 제공하는 visualize_voundary() 함수를 통해 머신러닝 model이 클래스 값을 예측하는 결정 기준을 색상과 경계로 나타냅니다.

# Classifier의 Decision Boundary를 시각화 하는 함수
def visualize_boundary(model, X, y):
    fig,ax = plt.subplots()
    
    # 학습 데이타 scatter plot으로 나타내기
    ax.scatter(X[:, 0], X[:, 1], c=y, s=25, cmap='rainbow', edgecolor='k',
               clim=(y.min(), y.max()), zorder=3)
    ax.axis('tight')
    ax.axis('off')
    xlim_start , xlim_end = ax.get_xlim()
    ylim_start , ylim_end = ax.get_ylim()
    
    # 호출 파라미터로 들어온 training 데이타로 model 학습 . 
    model.fit(X, y)
    # meshgrid 형태인 모든 좌표값으로 예측 수행. 
    xx, yy = np.meshgrid(np.linspace(xlim_start,xlim_end, num=200)\
                         ,np.linspace(ylim_start,ylim_end, num=200))
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
    
    # contourf() 를 이용하여 class boundary 를 visualization 수행. 
    n_classes = len(np.unique(y))
    contours = ax.contourf(xx, yy, Z, alpha=0.3,
                           levels=np.arange(n_classes + 1) - 0.5,
                           cmap='rainbow', clim=(y.min(), y.max()),
                           zorder=1)
    
from sklearn.tree import DecisionTreeClassifier

# 특정한 트리 생성 제약 없는 결정 트리의 학습과 경계 시각화
dt_clf = DecisionTreeClassifier().fit(X_features, y_labels)
visualize_boundary(dt_clf, X_features, y_labels)
'''결과4'''

결과4

DecisionTreeClassifier의 파라미터는 모두 default 값이기 때문에, 트리의 depth가 깊어진 상태입니다. 이는 이상치(Outlier) 데이터까지 분류하기 위해 분할이 많이 일어났기 때문입니다. 예를 들면, 이상치는 value= [0, 37, 1]에서 1을 의미합니다. 이렇게 분류할 경우 테스트 데이터를 예측할 때 정확도가 떨어지게 됩니다.

 

이번엔 min_samples_leaf = 6으로 설정해 리프 노드가 될 수 있는 samples의 수를 조절해보겠습니다.

# min_samples_leaf=6으로 트리 생성 조건을 제약한 결정 경계 시각화
dt_clf = DecisionTreeClassifier(min_samples_leaf=6).fit(X_features, y_labels)
visualize_boundary(dt_clf, X_features, y_labels)
'''결과5'''

결과5

결과를 보면 이상치에 크게 반응하지 않으면서 좀 더 일반화된 분류 규칙에 따라 분류됐음을 알 수 있습니다.

 

결정 트리 실습 - 사용자 행동 인식 데이터 세트

이번에는 결정 트리를 이용해 UCI Machine Learning Repository에서 제공하는 사용자 행동 인식(Human Activity Recognition) 데이터 세트에 대한 예측 분류를 수행해보겠습니다. 이는 사람의 동작과 관련된 피처를 수집한 데이터입니다. https://archive.ics.uci.edu/ml/datasets/Human+Activity+Recognition+Using+Smartphones로 들어가 Data Folder > UCI HAR Dataset.zip파일을 다운로드 받는다. 그리고 UCI HAR Dataset 폴더의 공란을 없앱니다. 예를 들면, UCI_HAR_Dataset으로 바꿔줍니다.

 

README.txtfeatures_info.txt에는 데이터 세트와 피처에 대한 간략한 설명이 있습니다. features.txt에는 피처의 이름이 기술돼 있습니다. activity_labels.txt는 동작 레이블 값에 대한 설명이 있습니다. train과 test에는 각각의 용도에 맞게 피처 데이터 세트와 레이블 데이터 세트가 들어 있습니다. 피처는 모두 561개가 있으며, 공백으로 분리돼 있습니다.

 

features.txt도 공백으로 피처명을 구분하고 있습니다. 이를 로딩해 명칭만 간단히 확인해보겠습니다.

import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

# features.txt 파일에는 피처 이름 index와 피처명이 공백으로 분리되어 있음. 
# 이를 DataFrame으로 로드.
# sep='\s+'는 길이가 정해지지 않은 공백이다. names은 column명 지정, header는 column 명으로 사용할 행 지정
feature_name_df = pd.read_csv('/content/drive/MyDrive/military/human_activity/features.txt'\
                              , sep='\s+', header=None, names=['column_index', 'column_name'])

# 피처명 index를 제거하고, 피처명만 리스트 객체로 생성한 뒤 샘플로 10개만 추출
feature_name = feature_name_df.iloc[:, 1].values.tolist()
print('전체 피처명에서 10개만 추출:', feature_name[:10])
'''
전체 피처명에서 10개만 추출: ['tBodyAcc-mean()-X', 'tBodyAcc-mean()-Y', 'tBodyAcc-mean()-Z'
, 'tBodyAcc-std()-X', 'tBodyAcc-std()-Y', 'tBodyAcc-std()-Z', 'tBodyAcc-mad()-X'
, 'tBodyAcc-mad()-Y', 'tBodyAcc-mad()-Z', 'tBodyAcc-max()-X']
'''

피처명을 통해 tBodyAcc(인체의 움직임)와 관련된 속성의 평균/표준편차가 X, Y, Z축 값으로 저장돼있음을 알 수 있습니다. 피처명을 통해 데이터를 DataFrame으로 로딩할때 중복된 피처명들을 가지고 있으면 에러가 발생하므로, 중복을 없애줘야합니다.

 

먼저, 중복된 피처명이 얼마나 있는지 알아보겠습니다. 총 42개의 column 피처명이 중복됐음을 알 수 있습니다.

# column_name을 기준으로 값들이 같은 행의 갯수 새기
feature_dup_df = feature_name_df.groupby('column_name').count()
# 값들이 같은(중복된) 행이 1개 이상인 것들 새기
print(feature_dup_df[feature_dup_df['column_index']>1].count())
# 값들이 같은(중복된) 행을 일부만 출력
feature_dup_df[feature_dup_df['column_index']>1].head()

'''
column_index    42
결과1
'''

결과1

피처명 중복을 없애는 함수인 get_new_feature_name_df()를 선언하겠습니다.

def get_new_feature_name_df(old_feature_name_df):
  # cumcount는 column의 중복되는 값들을 새주는 함수이다.
  feature_dup_df = pd.DataFrame(data=old_feature_name_df.groupby('column_name').cumcount(),\
                                columns=['dup_cnt'])
  # 인덱스를 새로 매긴다.
  feature_dup_df = feature_dup_df.reset_index()
  # how는 같은 column을 기준으로 merge하는데, 중복되지 않는 값들도 나머지를 NaN으로 채워 합병한다.
  new_feature_name_df = pd.merge(old_feature_name_df.reset_index(), feature_dup_df, how='outer')
  new_feature_name_df['column_name'] = new_feature_name_df[['column_name', 'dup_cnt']].apply(\
                                       lambda x: x[0]+'_'+str(x[1]) if x[1]>0 else x[0], axis=1)
  new_feature_name_df = new_feature_name_df.drop(['index'], axis=1)
  return new_feature_name_df

train용 피처 데이터 세트와 레이블 데이터 세트, test용 피처 데이터 세트와 레이블 데이터 세트를 DataFrame에 로드하는 함수 get_human_dataset()를 선언하겠습니다.

def get_human_dataset():
  # 각 데이터 파일은 공백으로 분리되어 있으므로 read_csv에서 공백 문자를 sep으로 할당.
  feature_name_df = pd.read_csv('/content/drive/MyDrive/military/human_activity/features.txt',\
                                sep='\s+', header=None, names=['column_index', 'column_name'])
  
  # 중복된 피처명을 수정하는 get_new_feature_name_df()를 이용, 신규 피처명 DataFrame 생성.
  new_feature_name_df = get_new_feature_name_df(feature_name_df)
  
  # DataFrame에 피처명 칼럼으로 부여하기 위해 리스트 객체로 다시 변환
  feature_name = new_feature_name_df.iloc[:, 1].values.tolist()

  # 학습 피처 데이터 세트와 테스트 피처 데이터세트를 DataFrame으로 로딩하고 칼럼명은 action으로 부여
  X_train = pd.read_csv('/content/drive/MyDrive/military/human_activity/train/X_train.txt',\
                        sep='\s+', names=feature_name)
  X_test = pd.read_csv('/content/drive/MyDrive/military/human_activity/test/X_test.txt',\
                       sep='\s+', names=feature_name)
  
  # 학습 레이블과 테스트 레이블 데이터를 DataFrame으로 로딩하고 칼럼명은 action으로 부여
  y_train = pd.read_csv('/content/drive/MyDrive/military/human_activity/train/y_train.txt',\
                        sep='\s+', header=None, names=['action'])
  y_test = pd.read_csv('/content/drive/MyDrive/military/human_activity/test/y_test.txt',\
                       sep='\s+', header=None, names=['action'])
  
  # 로드된 학습/테스트용 DataFrame을 모두 반환
  return X_train, X_test, y_train, y_test

X_train, X_test, y_train, y_test = get_human_dataset()

로드한 학습용 피처 데이터 세트를 간략히 살펴보겠습니다.

print('## 학습 피처 데이터셋 info()')
print(X_train.info())
'''
## 학습 피처 데이터셋 info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7352 entries, 0 to 7351
Columns: 561 entries, tBodyAcc-mean()-X to angle(Z,gravityMean)
dtypes: float64(561)
memory usage: 31.5 MB
None
'''

학습 데이터 세트는 7352개의 레코드로 561개의 피처를 가지고 있습니다. 피처는 모두 float 형이라서 별도의 카테고리 인코딩은 수행할 필요가 없습니다. 피처 대부분은 움직임 위치와 관련된 속성임을 X_train.head()로 알 수 있습니다.

 

print(y_train['action'].value_counts())
'''
6    1407
5    1374
4    1286
1    1226
2    1073
3     986
Name: action, dtype: int64
'''

레이블 값은 1~6이고, 분포도는 고르게 분포됨을 알 수 있습니다.

 

DecisionTreeClassifier를 이용해 동작 예측 분류를 수행해보겠습니다. DecisionTreeClassifier의 하이퍼 파라미터는 모두 디폴트값으로 설정하고 하이퍼 파라미터 값을 모두 추출해보겠습니다.

from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

# 예제 반복 시마다 동일한 예측 결과 도출을 위해 random_state 설정
dt_clf = DecisionTreeClassifier(random_state=156)
dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
accuracy = accuracy_score(y_test, pred)
print('결정 트리 예측 정확도: {0:.4f}'.format(accuracy))

# DecisionTreeClassifier의 하이퍼 하라미터 추출
print('DecisionTreeClassifier 기본 하이퍼 파라미터:\n', dt_clf.get_params())
'''
결정 트리 예측 정확도: 0.8548
DecisionTreeClassifier 기본 하이퍼 파라미터:
 {'ccp_alpha': 0.0, 'class_weight': None, 'criterion': 'gini', 
 'max_depth': None, 'max_features': None, 'max_leaf_nodes': None,
 'min_impurity_decrease': 0.0, 'min_impurity_split': None, 'min_samples_leaf': 1,
 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'presort': 'deprecated',
 'random_state': 156, 'splitter': 'best'}
'''

약 85.48%의 정확도를 가지고 있습니다.

 

grid_cv.best_params_ & grid_cv.best_score_

 

이번엔 트리 depth가 예측 정확도에 주는 영향을 살펴보겠습니다. GridSearchCV를 이용해 max_depth값을 변화시키며 예측 성능을 확인해 보겠습니다. 교차 검증은 5개 세트 입니다.(다음 예제는 5개의 CV 세트로 7개의 max_depth를 테스트합니다.)

from sklearn.model_selection import GridSearchCV

params={
    'max_depth': [6, 8, 10, 12, 16, 20, 24]
}
grid_cv = GridSearchCV(dt_clf, param_grid=params, scoring='accuracy', cv=5, verbose=1)
grid_cv.fit(X_train, y_train)
print('GridSearchCV 최고 평균 정확도 수치: {0:.4f}'.format(grid_cv.best_score_))
print('GridSearchCV 최적 하이퍼 파라미터:', grid_cv.best_params_)
'''
Fitting 5 folds for each of 7 candidates, totalling 35 fits
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done  35 out of  35 | elapsed:  1.9min finished
GridSearchCV 최고 평균 정확도 수치: 0.8513
GridSearchCV 최적 하이퍼 파라미터: {'max_depth': 16}
'''

학습 데이터의 일부분을 검증으로 사용한 검증용 데이터에서는 depth가 16일 때 0.8513의 정확도를 가집니다. max_depth의 변화에 따른 평가 데이터 세트의 평균 정확도 수치('mean_test_score'값)를 cv_results_에서 추출해보겠습니다.

# GridSearchCV 객체의 cv_results_ 속성을 DataFrame으로 생성.
cv_results_df = pd.DataFrame(grid_cv.cv_results_)

# max_depth 파라미터 값과 그때의 테스트 세트, 학습 데이터 세트의 정확도 수치 추출
cv_results_df[['param_max_depth', 'mean_test_score']]
'''
결과2
'''

결과2

mean_test_score는 5개 CV세트에서 검증용 데이터 세트의 정확도 평균 수치입니다. depth가 16 이후 부터는 계속 정확도가 떨어짐을 알 수 있습니다. 이는 결정 트리가 overfitting됐음을 의미합니다. 이번엔 별도의 테스트 데이터 세트에서 max_depth의 변화에 따른 값을 측정해보겠습니다.

max_depths = [6, 8, 10, 12, 16, 20, 24]
# max_depth 값을 변화시키면서 그때마다 학습과 테스트 세트에서의 예측 성능 측정
for depth in max_depths:
  dt_clf = DecisionTreeClassifier(max_depth=depth, random_state=156)
  dt_clf.fit(X_train, y_train)
  pred = dt_clf.predict(X_test)
  accuracy = accuracy_score(y_test, pred)
  print('max_depth={0} 정확도: {1:.4f}'.format(depth, accuracy))
'''
max_depth=6 정확도: 0.8558
max_depth=8 정확도: 0.8707
max_depth=10 정확도: 0.8673
max_depth=12 정확도: 0.8646
max_depth=16 정확도: 0.8575
max_depth=20 정확도: 0.8548
max_depth=24 정확도: 0.8548
'''

테스트 데이터에서는 max_depth=8에서 정확도가 약 87%가 나왔습니다. max_depth= 8 이후로 계속 정확도가 떨어지는 것으로 보아 최적의 depth=8로 보입니다. 

 

 

grid_cv.best_estimator_

 

이번엔 max_depth와 min_samples_split을 같이 변경하며 정확도 성능을 튜닝해보겠습니다.

params={
    'max_depth': [6, 8, 10, 12, 16, 20, 24],
    'min_samples_split': [16, 24]
}
grid_cv = GridSearchCV(dt_clf, param_grid=params, scoring='accuracy', cv=5, verbose=1)
grid_cv.fit(X_train, y_train)
print('GridSearchCV 최고 평균 정확도 수치: {0:.4f}'.format(grid_cv.best_score_))
print('GridSearchCV 최적 하이퍼 파라미터:', grid_cv.best_params_)
'''
Fitting 5 folds for each of 14 candidates, totalling 70 fits
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done  70 out of  70 | elapsed:  3.7min finished
GridSearchCV 최고 평균 정확도 수치: 0.8549
GridSearchCV 최적 하이퍼 파라미터: {'max_depth': 8, 'min_samples_split': 16}
'''

max_depth=8, min_samples_split=16일 때 가장 최고의 정확도 85.5%가 검증용 데이터에서 나왔습니다. grid_cv.best_estimator_는 가장 최적의 하이퍼 파라미터 max_depth=8, min_samples_split=16로 학습이 완료된 Estimator 객체입니다. 이를 테스트 데이터로 예측을 수행해보겠습니다.

best_df_clf = grid_cv.best_estimator_
pred1 = best_df_clf.predict(X_test)
accuracy = accuracy_score(y_test, pred1)
print('결정 트리 예측 정확도:{0:.4f}'.format(accuracy))
'''
결정 트리 예측 정확도:0.8717
'''

정확도는 약 87%가 나옵니다.

 

Estimator.feature_importance_ & Series.sort_values()

마지막으로 max_depth=8, min_samples_split=16일 때 각 피처의 중요도를 Estimator.feature_importance_ 속성을 이용해 알아보겠습니다. 중요도가 높은 순으로 Top 20 피처를 막대그래프로 표현했습니다.

import seaborn as sns

ftr_importances_values = best_df_clf.feature_importances_
# 시본(Seaborn)의 막대그래프로 쉽게 표현하기 위해 Series 변환
ftr_importances = pd.Series(ftr_importances_values, index=X_train.columns)
# 중요도값 순으로 Series를 정렬
ftr_top20 = ftr_importances.sort_values(ascending=False)[:20]

plt.figure(figsize=(8, 6))
plt.title('Feature importances Top 20')
sns.barplot(x = ftr_top20, y = ftr_top20.index)
plt.show()
'''
결과3
'''

결과3

막대 그래프상에서 확인해보면 중요도 Top 5의 피처들이 매우 중요하게 규칙 생성에 영향을 미치는 것을 알 수 있습니다.

 

앙상블 학습(Ensemble Learning)

앙상블 학습을 통한 분류는 여러 개의 분류기(Classifier)를 생성하고 그 예측을 결합함으로써 보다 정확한 최종 예측을 도출하는 기법을 말합니다. 비정형 데이터의 분류는 딥러닝이 뛰어난 성능을 보이고 있지만, 정형 데이터 분류에서는 앙상블이 뛰어난 성능을 나타내고 있습니다. 앙상블 알고리즘에는 대표적으로 랜덤 포레스트와 그래디언트 부스팅 알고리즘이 있습니다. 이후 성능이 뛰어난 XGBoost, LightGBM, Stacking 등 다양한 앙상블 알고리즘이 등장했습니다.

 

앙상블의 학습 유형은 전통적으로 Voting, Bagging, Boosting 세 가지로 나눌 수 있습니다. 이외에도 stacking 등 다양한 앙상블 방법이 있습니다. Voting과 Bagging의 다른 점은 Voting은 보통 서로 다른 알고리즘을 가진 분류기가 결합하는 것이고, Bagging은 각각 분류기가 모두 같은 유형의 알고리즘이지만, 데이터 샘플링을 서로 다르게 하여 학습을 수행해 보팅(투표)을 수행하는 것입니다.

voting과 bagging의 차이

Bagging의 대표적인 방식이 랜덤 포레스트 알고리즘입니다. 위 그림의 Bagging에서처럼 개별 Classifier에게 원본 데이터를 샘플링해서 전달하는 방식을 부트스트래핑(Bootstrapping) 분할 방식이라고 부릅니다. Bootstrapping 방식으로 샘플링된 데이터 세트를 학습해서 개별적인 예측을 수행한 결과를 보팅해서 최종 예측 결과를 선정하는 방식이 Bagging 앙상블 방식입니다. Bagging 방식은 샘플링된 데이터 세트에서 선택한 데이터에 대한 중복 추출을 허용합니다.

 

Boosting 방식은 여러 개의 분류기가 순차적으로 학습을 하되, 앞에서 학습한 분류기가 예측이 틀린 데이터에 대해서는 올바르게 예측할 수 있도록 다음 분류기에 가중치(weight)를 부여하면서 학습과 예측을 진행하는 것입니다. 대표적인 Boosting 모듈로 그래디언트 부스트, XGBoost, LightGBM이 있습니다.

 

Stacking은 여러 가지 다른 모델의 예측 결과값을 다시 학습 데이터로 만들어서 다른 모델(메타 모델)로 재학습시켜 결과를 예측하는 방법입니다.

 

보팅 유형 - 하드 보팅(Hard Voting)과 소프트 보팅(Soft Voting)

보팅 방법에는 하드 보팅과 소프트 보팅이 있습니다. 하드 보팅을 이용한 분류는 예측한 결과값들 중 다수의 분류기가 결정한 예측 값을 최종 보팅 결과값으로 선정하는 것입니다.

 

소프트 보팅은 분류기들의 각 레이블 값들의 결정 확률을 모두 더하고 이를 평균해서 이들 중 확률이 가장 높은 레이블 값을 최종 보팅 결과값으로 선정합니다. 일반적으로 소프트 보팅이 보팅 방법으로 적용됩니다. 

하드 보팅과 소프트 보팅의 차이, 소프트 보팅은 각 분류기의 레이블 별 결정확률을 평균해서 높은 레이블 값을 선택한다.

 

VoringClassifier

사이킷런은 보팅 방식의 앙상블을 구현한 VotingClassifier 클래스를 제공하고 있습니다. 위스콘신 유방암 데이터 세트는 악성, 양성종양 여부를 결정하는 이진 분류 데이터 세트이며, 종양의 크기 모양 등의 형태 피처를 가지고 있습니다. 이를 load_breast_cancer() 함수를 통해 생성할 수 있습니다.

 

로지스틱 회귀와 KNN을 기반으로 보팅 분류기(Voting Classifier)를 만들어보겠습니다. 그 전에 필요한 모듈과 데이터를 로딩한 후 유방암 데이터 세트를 간략히 살펴보겠습니다.

import pandas as pd
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

cancer = load_breast_cancer()

data_df = pd.DataFrame(cancer.data, columns=cancer.feature_names)
data_df.head(3)
'''
결과1
'''

결과1

로지스틱 회귀와 KNN을 기반으로 하여 소프트 보팅 방식으로 새롭게 보팅 분류기를 만들어 보겠습니다. VotingClassifier 클래스의 생성 인자로 estimators와 voting 값을 입력 받습니다. estimators에는 리스트 값으로 보팅에 사용될 여러 개의 Classifier 객체들을 튜플 형식으로 입력 받으며, voting에는 'hard' 또는 'soft' 등 방식을 입력받습니다.

 

Classifier.__class__.__name__

# 개별 모델은 로지스틱 회귀와 KNN임.
lr_clf = LogisticRegression()
knn_clf = KNeighborsClassifier(n_neighbors=8)

# 개별 모델을 소프트 보팅 기반의 앙상블 모델로 구현한 분류기
vo_clf = VotingClassifier( estimators=[('LR', lr_clf), ('KNN', knn_clf)], voting='soft')

X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target,\
                                                    test_size=0.2, random_state=156)

# VotingClassifier 학습/예측/평가
vo_clf.fit(X_train, y_train)
pred = vo_clf.predict(X_test)
print('Voting 분류기 정확도: {0:.4f}'.format(accuracy_score(y_test, pred)))

# 개별 모델의 학습/예측/평가
classifiers = [lr_clf, knn_clf]
for classifier in classifiers:
  classifier.fit(X_train, y_train)
  pred = classifier.predict(X_test)
  class_name = classifier.__class__.__name__
  print('{0} 정확도: {1:.4f}'.format(class_name, accuracy_score(y_test, pred)))
'''
Voting 분류기 정확도: 0.9474
LogisticRegression 정확도: 0.9386
KNeighborsClassifier 정확도: 0.9386
'''

보팅 분류기가 조금 더 높은 정확도를 가졌는데, 데이터의 특성과 분포에 따라 기반 분류기 중 하나가 더 나은 정확도를 가질 수 있습니다. 그럼에도 불구하고 voting을 보함한 bagging, boosting 등 앙상블 방법은 단일 ML 알고리즘보다 뛰어난 예측 성능을 가지는 경우가 많습니다. 앙상블 방법은 여러 ML 알고리즘을 결합해 높은 유연성을 가질 수 있는데, 이는 다양한 테스트 데이터에 의해 검증 될 때 중요한 평가 요소가 됩니다. 이런 관점에서 편향 - 분산 트레이드 오프는 ML 모델이 극복해야할 중요 과제입니다. 

 

voting, stacking은 서로 다른 알고리즘을 기반으로 하고 있지만, bagging, boosting은 대부분 결정 트리 알고리즘을 기반으로 합니다. 결정 트리 알고리즘은 정확한 예측을 위해 학습 데이터의 예외 상황에 집착한 나머지 오히려 과적합이 발생해 유연성의 부족으로 이어집니다. 이 같은 결정 트리 알고리즘의 단점을 앙상블 학습에서는 결정 트리 분류기를 수십~ 수천개다양한 상황에 학습시켜서 극복하고 있습니다. 이는 결정 트리 알고리즘의 장점은 그대로 취하고, 단점을 보완해 편향 - 분산 트레이드 오프 효과를 극대화할 수 있습니다.

 

랜덤 포레스트

bagging의 대표적인 알고리즘은 랜덤 포레스트입니다. 랜덤 포레스트는 앙상블 알고리즘 중 비교적 빠른 수행 속도와 높은 예측 성능을 보입니다. 랜덤 포레스트 기반 알고리즘은 결정 트리로서, 결정 트리의 쉽고 직관적인 장점을 그대로 가지고 있습니다.

 

렌덤 포레스트

원본 데이터에서 샘플링한 결과를 학습해 보팅을 통해 예측 결정을 하게 됩니다.

 

Bootstraping 분할 방식

여러 개의 데이터 세트를 중첩되게 분리하는 것을 Bootstraping 분할 방식이라 한다. 데이터의 중복을 허용해서 샘플링하며 서브세트(Subset) 데이터를 만든다. 서브 세트의 데이터 건수는 원본 전체 데이터 건수와 일치하지만, 개별 데이터가 중복되어 만들어 집니다. 원본 데이터가 10건이라면, subset 데이터도 10건입니다. 랜덤 포레스트의 n_estimator = 3으로 하이퍼 파라미터를 부여하면 아래 그림과 같이 데이터 서브 세트가 만들어집니다.

 

부트 스트래핑 분할 방식

 

RandomForestClassifier

사이킷런은 RandomForestClassifier 클래스를 통해 랜덤 포레스트 기반의 분류를 지원합니다. 앞에서 사용자 행동 인식 예측 데이터에서 사용한 get_human_dataset을 이용해서 연습해보겠습니다.

from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import pandas as pd
import warnings
warnings.filterwarnings('ignore')

# 결정 트리에서 사용한 get_human_dataset()을 이용해 학습/테스트용 DataFrame 반환
X_train, X_test, y_train, y_test = get_human_dataset()

# 랜덤 포레스트 학습 및 별도의 테스트 세트로 예측 성능 평가
rf_clf = RandomForestClassifier(random_state=0)
rf_clf.fit(X_train, y_train)
pred = rf_clf.predict(X_test)
accuracy = accuracy_score(y_test, pred)
print('랜덤 포레스트 정확도:{0:.4f}'.format(accuracy))
'''
랜덤 포레스트 정확도:0.9253
'''

 

랜덤 포레스트 하이퍼 파라미터 및 튜닝

트리 기반의 앙상블 알고리즘의 단점을 굳이 뽑자면 하이퍼 파라미터가 많고 그로 인해서 튜닝 시간이 많이 소모되는 것입니다.

랜덤 포레스트의 하이퍼 파라미터는 아래와 같습니다.

  • n_estimator: 랜덤 포레스트에서 결정 트리 분류기 개수를 지정합니다. default는 100개 이고, 많이 설정할 수록 예측이 무조건 향상되는 건 아닙니다. 학습 수행 시간이 오래걸릴 수 있습니다.
  • max_features: 결정 트리의 max_features와 같습니다. 단, default가 'auto', 즉 sqrt와 같습니다. 피처를 참조할 때 전체 피처가 아니라 sqrt(전체 피처 개수)만큼 참조합니다(전체 피처가 16개라면 분할을 위해 4개 참조)
  • max_depth, min_samples_leaf 등 결정 트리에서 과적합 방지를 위해 사용한 파라미터를 똑같이 사용할 수 있습니다.

GridSearchCV를 이용해 랜덤포래스트 하이퍼 파라미터를 튜닝하겠습니다. n_estimators = 100, cv = 2로 설정하고, 다른 하이퍼 파라미터를 최적화한 뒤 n_estimator = 300으로 증가시켜 예측 성능을 평가하겠습니다.

 

랜덤 포레스트는 CPU 병렬 처리도 효과적으로 수행되어 빠른 학습이 가능해 이후 소개할 그래디언트 부스팅보다 예층 성능이 떨어지더라도 일단 랜덤 포레스트로 기반 모델을 구축하는 경우가 많습니다. 

 

멀티 코어 환경에서는 RandomForestClassifier 생성자와 GridSearchCV 생성 시 n_jobs = -1 파라미터를 추가하면 모든 CPU 코어를 이용해 학습할 수 있습니다.

from sklearn.model_selection import GridSearchCV

params = {
    'n_estimators':[100],
    'max_depth':[6, 8, 10, 12],
    'min_samples_leaf':[8, 12, 18],
    'min_samples_split':[8, 16, 20]
}

# RandomForestClassifier 객체 생성 후 GridSearchCV 수행
rf_clf = RandomForestClassifier(random_state=0, n_jobs=-1)
grid_cv = GridSearchCV(rf_clf, param_grid=params, cv=2, n_jobs=-1)
grid_cv.fit(X_train, y_train)

print('최적 하이퍼 파라미터:\n', grid_cv.best_params_)
print('최고 예측 정확도: {0:.4f}'.format(grid_cv.best_score_))
'''
최적 하이퍼 파라미터:
 {'max_depth': 10, 'min_samples_leaf': 8, 'min_samples_split': 8, 'n_estimators': 100}
최고 예측 정확도: 0.9180
'''

최적 하이퍼 파라미터들을 사용하되 n_estimators = 300으로 증가시켜서 RandomForestClassifier를 학습시킨 뒤에, 이번에는 별도의 테스트 데이터 세트에서 예측 성능을 측정해보겠습니다.

rf_clf1 = RandomForestClassifier(n_estimators=300, max_depth=10, min_samples_leaf=8\
                                 , min_samples_split=8, random_state=0)
rf_clf1.fit(X_train, y_train)
pred = rf_clf1.predict(X_test)
print('예측 정확도: {0:.4f}'.format(accuracy_score(y_test, pred)))
'''
예측 정확도: 0.9165
'''

 

RandomForestClassifier 역시 DecisionTreeClassifier와 똑같이 feature_importances_ 속성을 이용해 알고리즘이 선택한 피처의 중요도를 알 수 있습니다. 이를 막대그래프로 시각화해보겠습니다.

import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

ftr_importances_values = rf_clf1.feature_importances_
ftr_importances = pd.Series(ftr_importances_values, index=X_train.columns)
ftr_top20 = ftr_importances.sort_values(ascending=False)[:20]

plt.figure(figsize=(8, 6))
plt.title('Feature importances Top 20')
sns.barplot(x=ftr_top20, y=ftr_top20.index)
plt.show()
'''
결과2
'''

결과2

위와 같은 피처들이 중요도가 높다는 걸 알 수 있습니다.

 

GBM(Gradient Boosting Machine)

부스팅 알고리즘의 대표적인 구현은 AdaBoost(Apativbe boosting)과 그래디언트 부스트가 있습니다. 에이다 부스트(AdaBoost)는 오류 데이터에 가중치를 부여하면서 부스팅을 수행하는 대표적인 알고리즘입니다. 

 

AdaBoost

AdaBoost는 위 그림을 통해 설명하겠습니다. 첫 번째 약한 학습기가 빨간 점과 파란 점을 분류했습니다. 잘못 분류한 데이터에 대해 두 번째 약한 학습기가 더 잘 분류할 수 있게 가중치(크기)를 키워서 분류합니다. 두 번째 약한 학습기도 오류 데이터에 대해 가중치를 키워서 세 번째 약한 학습기에서 다시 분류를 시작합니다. 그리고, 각각의 개별 약한 학습기에 대해서도 각각 가중치를 부여해 결합합니다. 예를 들어 첫 번째 학습기에 가중치 0.3, 두 번째 학습기에 가중치 0.5, 세 번째 학습기에 가중치 0.8을 부여한 후 모두 결합해 예측을 수행합니다.

 

GBM도 에이다 부스트와 유사하나, 가중치 업데이트를 경사 하강법(Gradient Descent)을 이용하는 것이 큰 차이입니다. 오류 값 = 실제값 - 예측값이고, 분류의 실제 결과값을 y, 피처를 x1, x2 ... 라고 할 때 이 피처에 기반한 예측 함수를 F(x)라고 하면, 오류식은 h(x) = y - F(x)가 됩니다. h(x)를 최소화하는 방향으로 반복적으로 가중치 값을 업데이트하는 것이 경사 하강법입니다. 

 

 

GradientBoostingClassifier

GBM은 CART 기반의 다른 알고리즘과 마찬가지로 분류와 회귀 모두 가능합니다. 사이킷런은 GBM 기반 분류를 위해 GradientBoostingClassifier 클래스를 제공합니다. 사용자 행동 데이터 세트를 예측 분류해보겠습니다. 이번엔 GBM으로 학습하는 시간이 얼마나 걸리는지 수행 시간도 측정해보겠습니다.

from sklearn.ensemble import GradientBoostingClassifier
import time
import warnings
warnings.filterwarnings('ignore')

X_train, X_test, y_train, y_test = get_human_dataset()

# GBM 수행 시간 측정을 위함. 시작 시간 설정.
start_time = time.time()

gb_clf = GradientBoostingClassifier(random_state=0)
gb_clf.fit(X_train, y_train)
gb_pred = gb_clf.predict(X_test)
gb_accuracy = accuracy_score(y_test, gb_pred)

print('GBM 정확도: {0:.4f}'.format(gb_accuracy))
print('GBM 수행 시간: {0:.1f} 초'.format(time.time()-start_time))
'''
GBM 정확도: 0.9386
GBM 수행 시간: 809.5 초
'''

기본 하이퍼 파라미터로도 랜덤 포레스트보다 정확도가 뛰어납니다. 하지만, 약한 학습기의 순차적인 예측 오류 보정을 통해 학습을 수행하므로 멀티 CPU 코어 시스템을 사용하더라도 병렬 처리가 지원되지 않아서 학습에 많은 시간이 필요합니다. 반면 랜덤 포레스트는 상대적으로 빠른 수행 시간을 보장해주기 때문에 더 쉽게 예측 결과를 도출할 수 있습니다.

 

GBM 하이퍼 파라미터 및 튜닝

트리 기반 자체의 파라미터인 n_estimators, max_depth, max_features를 사용할 수 있고, 추가적인 파라미터는 아래와 같습니다.

  • loss: 경사 하강법에서 사용할 비용 함수를 지정합니다. default는 'deviance'입니다.
  • learning_rate: 학습률로 Weak learner가 순차적으로 오류 값을 보정해 나가는데 적용하는 계수입니다. default 값은 0.1입니다. n_estimators와 상호 보완적으로 조합해 사용합니다. n_estimators의 개수가 많을 시 학습률이 낮으면 예측 성능에는 변화가 없지만, 수행 시간이 오래 걸리기 때문입니다.
  • n_estimators: Weak learner의 개수입니다. weak leaner가 순차적으로 오류를 보정하므로 많을 수록 예측 수준이 일정 순까지는 좋아질 수 있지만, 시간이 오래 걸리게 됩니다. default 값은 100입니다.
  • subsample: Weak learner가 학습에 사용하는 데이터 샘플링 비율입니다. default 값은 1이며, 전체 학습 데이터로 학습한다는 의미입니다. 과적합이 염려될 경우 1보다 작은 값으로 설정합니다.

 

GridSearchCV를 이용해 하이퍼 파라미터를 최적화해보겠습니다. 시간이 많이 걸릴 것이라서 파라미터의 갯수를 조금만 입력하겠습니다. 교차 검증 세트도 cv=2로 하겠습니다.

from sklearn.model_selection import GridSearchCV

params={
    'n_estimators':[100, 500],
    'learning_rate':[0.05, 0.1]
}
grid_cv = GridSearchCV(gb_clf, param_grid=params, cv=2, verbose=1)
grid_cv.fit(X_train, y_train)
print('최적 하이퍼 파라미터:\n', grid_cv.best_params_)
print('최고 예측 정확도: {0:.4f}'.format(grid_cv.best_score_))
'''
Fitting 2 folds for each of 4 candidates, totalling 8 fits
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   8 out of   8 | elapsed: 81.0min finished
최적 하이퍼 파라미터:
 {'learning_rate': 0.05, 'n_estimators': 500}
최고 예측 정확도: 0.9013
'''

이 설정을 그대로 테스트 데이터 세트에 적용해 예측 정확도를 확인해 보겠습니다.

# GridSearchCV를 이용해 최적으로 학습된 estimator로 예측 수행
gb_pred = grid_cv.best_estimator_.predict(X_test)
gb_accuracy = accuracy_score(y_test, gb_pred)
print('GBM 정확도:{0:.4f}'.format(gb_accuracy))
'''
GBM 정확도:0.9396
'''

GBM은 과적합에도 강한 알고리즘이지만. 수행시간이 길다는 단점이 있습니다. GBM을 기반으로 한 많은 알고리즘이 개발중에 있는데, 가장 각광받고 있는 두개의 그래디언트 부스팅 기반 ML 패키지는 XGBoost와 LightGBM이 있습니다. 먼저 XGBoost를 살펴보겠습니다.

 

 

XGBoost(eXtra Gradient Boost)

XGBoost는 트리 기반의 앙상블 학습에서 가장 각광받고 있는 알고리즘 중 하나입니다. 분류에 있어서 다른 머신러닝보다 뛰어난 예측 성능을 나타냅니다. XGBoost는 GBM에 기반하고 있지만, GBM의 단점인 느린 수행 시간과적합 규제(Regularization) 부재 등의 문제를 해결해서 매우 각광을 받고 있습니다. 특히, XGBoost는 병렬 CPU 환경에서 병렬 학습이 가능해 기존 GBM보다 빠르게 학습을 완료할 수 있습니다. 다음은 XGBoost의 주요 장점입니다.

  • tree pruning (나무 가지치기): 일반적으로 GBM은 분할 시 부정 손실이 발생하면 분할을 더 이상 수행하지 않지만, 이러한 방식도 자칫 지나치게 많은 분할을 발생시킬 수 있습니다. 다른 GBM과 마찬가지로 XGBoost도 max_depth 파라미터로 분할 깊이를 조정하기도 하지만, tree pruning으로 더 이상 긍정 이득이 없는 분할을 가지치기 해서 분할 수를 더 줄이는 추가적인 장점을 가지고 있습니다.
  • 자체 내장된 교차 검증: XGBoost는 반복 수행 시마다 내부적으로 학습 데이터 세트와 평가 데이터 세트에 대한 교차 검증을 수행해 최적화된 반복 수행 횟수(cv)를 가질 수 있습니다. 지정된 반복 횟수가 아니라 교차 검증을 통해 평가 데이터 세트의 평가 값이 최적화 되면 반복을 중간에 멈출 수 있는 조기 중단 기능이 있습니다.
  • 결손값 자체 처리: XGBoost는 결손값을 자체 처리할 수 있는 기능을 가지고 있습니다.

 

XGBoost의 핵심 라이브러리는 C/C++로 작성돼있습니다. XGBoost를 구동할 수 있는 파이썬 패키지가 있습니다. 이 파이썬 패키지의 역할은 대부분 C/C++ 핵심 라이브러리를 호출하는 것입니다. 패키지명은 'xgboost'입니다. xgboost 패키지 내에는 XGBoost 전용의 파이썬 패키지사이킷런과 호환되는 래퍼용 XGBoost가 함께 존재합니다. xgboost 파이썬 패키지는 사이킷런과 호환되지 않는 XgBoost 전용의 패키지였습니다. 하지만, 사이킷런과 연동될 수 있는 래퍼 클래스(Wrapper class)를 제공하기로 했습니다. XGBoost 패키지의 사이킷런 래퍼 클래스는 XGBClassifier XGBRegressor가 있습니다. 이를 이용하면 사이킷런 estimator가 학습을 위해 사용하는 fit()과 predict()와 같은 표준 사이킷런 개발 프로세스 및 다양한 유틸리티를 활용할 수 있습니다.

 

구분을 위해 초기의 독자적인 XGBoost 프레임워크 기반의 XGBoost를 파이썬 네이티브 XGBoost 모듈, 사이킷런과 연동되는 모듈사이킷런 래퍼 XGBoost 모듈이라 지칭하겠습니다.

 

파이썬 네이티브 XGBoost는 고유의 API와 하이퍼 파라미터를 이용합니다. 사이킷런 래퍼 XGBoost(사이킷런의 다른 Estimator와 사용법이 같음)와 크게 다르지 않지만, 몇 가지 주의할 점이 있습니다.

 

 

XGBoost 설치하기

XGBoost는 아나콘다를 이용하면 쉽게 설치할 수 있습니다.

 

Windows 기반에서 설치하려면 아나콘다 Command 창을 관리자 권한으로 연 뒤 아래 커멘드를 입력합니다.

conda install -c anaconda py-xgboost

리눅스에 설치하는 것도 아나콘다를 이용하면 간단하게 설치할 수 있습니다. OS 터미널에서 conda 명령어를 입력합니다.

conda install -c conda-forge xgboost

설치가 정상적으로 완료됐는지 확인하려면, 아래 코드를 입력해보면 됩니다.

import xgboost as xgb
from xgboost import XGBClassifier

위 코드를 import시 'no module named xgboost'와 같이 xgboost 모듈을 찾을 수 없다는 에러 메시지가 나오면 다시 설치하셔야합니다.

 

 

파이썬 래퍼 XGBoost 하이퍼 파라미터

XGBoost는 GBM의 하이퍼 파라미터를 가지고 있으며, 조기 중단(early stopping), 과적합 규제를 위한 하이퍼 파라미터 등이 추가됐습니다. 파이썬 래퍼 XGBoost 모듈과 사이킷런 래퍼 XGBoost 모듈은 동일한 기능의 하이퍼 파라미터이지만, 사이킷런 파라미터의 범용화된 이름 규칙(Naming Rule)에 따라 파라미터 명이 달라집니다.

 

파이썬 래퍼 XGBoost 하이퍼 파라미터를 유형별로 나누면 다음과 같습니다.

  • 일반 파라미터: 일반적으로 실행 시 스레드의 개수나 silent 모드 등의 선택을 위한 파라미터로서 디폴트 파라미터 값을 바꾸는 경우는 거의 없습니다.
  • 부스터 파라미터: 트리 최적화, 부스팅, regularization 등과 관련 파라미터 등을 지칭합니다.
  • 학습 태스크 파라미터: 학습 수행 시의 객체 함수, 평가를 위한 지표 등을 설정하는 파라미터입니다.

 

주요 일반 파라미터

  • booster: gbtree(tree based model) 또는 gblinear(linear model) 선택. 디폴트는 gbtree
  • silent: default는 0이며, 출력 메시지를 나타내고 싶지 않을 경우 1로 설정
  • nthread: CPU 실행 스레드 개수를 조정합니다. default는 CPU의 전체 스레드를 사용하는 것입니다. 멀티 코어/스레드 CPU에서 일부 CPU만 사용하려할 때 변경합니다.

 

주요 부스터 파라미터

  • eta [default=0.3, alias: learning_rate]:  GBM의 학습률과 같은 파라미터입니다. 사이킷런 래퍼 클래스에서는 learning_rate 파라미터로 대체됩니다. 사이킷런에서는 default=0.1, 보통은 0.01~0.2의 값을 선호합니다.
  • num_boost_rounds: GBM의 n_estimators와 같은 파라미터입니다.
  • min_child_weight [default=1]: 트리에서 추가적으로 가지를 나눌지 결정하기 위해 필요한 데이터들의 weight 총합. 값이 클수록 분할을 자제하며, 과적합을 조절하기 위해 사용됩니다.
  • gamma [default=0, alias: min_split_loss]: 추가적으로 트리의 리프 노드를 나눌지를 결정할 최소 손실 감소값입니다. 해당 값보다 큰 손실(loss)이 감소된 경우 리프 노드를 분리합니다. 값이 클수록 과적합 감소 효과가 있습니다.
  • max_depth [default=6]: 트리 기반 알고리즘의 max_depth와 같습니다. 0을 지정하면 깊이에 제한이 없어집니다. 과적합 가능성 때문에 보통 3~10 사이의 값을 적용합니다.
  • sub_sample [default=1]: GBM의 subsample과 동일합니다. 트리가 과적합되는 걸 방지하기 위해 데이터를 샘플링하는 비율을 지정합니다. 일반적으로 0.5~1 사이의 값을 사용합니다.
  • colsample_bytree [default=1]: GBM의 max_features와 유사합니다. 피처가 많을 때 과적합을 조정하는 데 적용합니다.
  • lambda [default=1, alias: reg_lambda]: L2 Regularization 적용값입니다. 피처 개수가 많은 경우 쓰이며, 값이 클수록 과적합을 감소시킵니다.
  • alpha [default=0, alias: reg_alpha]: L1 Regularization 적용값입니다. 피처 개수가 많은 경우에 쓰이며, 값이 클수록 과적합을 감소시킵니다.
  • scale_pos_weight [default=1]: 특정 값으로 치우친 비대칭 클래스로 구성된 데이터 세트의 균형을 유지하기 위한 파라미터입니다.

 

학습 태스크 파라미터

  • objective: 최솟값을 가져야할 손실 함수를 정의합니다. 주로 사용되는 손실함수는 이진 분류인지 다중 분류인지에 따라 달라집니다.
  • binary:logistic: 이진 분류일 때 적용합니다.
  • multi:softmax: 다중 분류일 때 적용합니다. 사용할 경우, 레이블 클래스의 개수인 num_class 파라미터를 지정해줘야합니다.
  • multi:softprob: multi:softmax와 유사하나 개별 레이블 클래스의 해당되는 예측 확률을 반환합니다.
  • eval_metric: 검증에 사용되는 함수를 정의합니다. default는 회귀는 rmse, 분류는 error입니다. 자식 리스트들은 eval_metirc의 값 유형입니다.
    • rmse: Root Mean Square Error
    • mae: Mean Absolute Error
    • logloss: Negative log-likelihood
    • error: Binary classification error late (0.5 threshold)
    • merror: Multiclass classification error rate
    • mlogloss: Multiclass logloss
    • auc: Area under the curve

 

파라미터터를 튜닝하는 경우는 피처의 수가 많거나, 피처 간 상관되는 정도가 많거나 데이터 세트에 따라 여러 특성이 있을 수 있습니다.

 

과적합 문제가 심각하다면, 다음과 같이 적용할 것을 고려할 수 있습니다.

  • eta 값을 낮춥니다.(0.01~0.1) eta 값을 낮출 경우 num_round(또는 n_estimators)는 반대로 높여줘야 합니다.
  • max_depth 값을 낮춥니다.
  • min_child_weight 값을 높입니다.
  • gamma 값을 높입니다.
  • subsample, colsample_bytree를 조정하는 것도 트리가 너무 복잡하게 생성되는 것을 막는 데 도움이 됩니다.

 

XGBoost는 자체적으로 교차 검증, 성능 평가, 피처 중요도 등의 시각화 기능을 가지고 있습니다. 또한 조기 중단(Early Stopping) 기능이 있습니다. n_estimators(또는 num_round)에 지정된 횟수만큼 반복해야하지만, 예를 들어 조기 중단 파라미터 값을 50으로 설정하면 50회 동안 예측 오류에 변화가 없을 때 부스팅을 종료합니다.

 

파이썬 래퍼 XGBoost 적용 - 위스콘신 유방암 예측

import xgboost as xgb
from xgboost import plot_importance
import pandas as pd
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings('ignore')

dataset = load_breast_cancer()
X_features = dataset.data
y_label = dataset.target
cancer_df = pd.DataFrame(data=X_features, columns=dataset.feature_names)
cancer_df['target'] = y_label
cancer_df.head(3)
'''
결과1
'''

결과1

target 레이블 값의 종류는 악성인 'malignant'가 0 값으로, 양성인 'benign'이 1 값으로 돼있습니다.

print(dataset.target_names)
print(cancer_df['target'].value_counts())
'''
['malignant' 'benign']
1    357
0    212
Name: target, dtype: int64
'''
# 전체 데이터 중 80%는 학습용 데이터, 20%는 테스트용 데이터 추출
X_train, X_test, y_train, y_test = train_test_split(X_features, y_label,\
                                                    test_size=0.2, random_state=156)
print(X_train.shape, X_test.shape)
'''
(455, 30) (114, 30)
'''

파이썬 래퍼 XGBoost는 사이킷런과 차이가 여러 가지 있지만, 가장 눈에 띄는 차이는 학습용과 테스트용 데이터 세트를 위해 별도의 객체인 DMatrix를 생성한다는 점입니다.

 

DataFrame.values

 

DMatrix는 numpy 배열을 입력 파라미터로 받아서 만들어지는 XGBoost만의 전용 데이터 세트입니다. 주요 입력 파라미터는 data와 label입니다. data는 피처 데이터 세트이며, label은 분류의 경우에는 레이블 데이터 세트, 회귀의 경우는 숫자형인 종속값 데이터 세트입니다. 넘파이 외에도 libsvm txt 포맷 파일, xgboost 이진 버퍼 파일을 파라미터로 입력받아 변환할 수 있습니다. 판다스의 DataFrame는 DataFrame.values를 이용해 넘파이로 변환해야합니다.

 

dtrain = xgb.DMatrix(data=X_train, label=y_train)
dtest = xgb.DMatrix(data=X_test, label=y_test)

하이퍼 파라미터를 아래와 같이 설정합니다.

params ={
    'max_depth':3,
    'eta': 0.1,
    'objective': 'binary:logistic',
    'eval_metric': 'logloss',
    'early_stoppings': 100
}
num_rounds = 400

파이썬 래퍼 XGBoost는 하이퍼 파라미터를 xgboost 모듈의 train()함수에 파라미터로 전달합니다. (사이킷런의 경우는 Estimator의 생성자를 하이퍼 파라미터로 전달합니다.) 

 

조기 중단은 xgboost의 train() 함수에 early_stopping_rounds 파라미터를 입력하여 설정합니다. 예제에서는 100으로 설정했습니다. early_stopping_rounds 파라미터를 설정해 조기 중단을 수행하기 위해서는 반드시 eval_set, eval_metric이 함께 설정돼야 합니다.eval_set은 성능 평가를 수행할 평가용 데이터 세트를 설정하는 역할입니다. eval_metrix는 평가 세트에 적용할 성능 평가 방법. 분류일 경우 주로 'error'(분류 오류), 'logloss'를 적용합니다.

 

evals 파라미터학습 데이터 세트eval 데이터 세트를 명기해주면 평가를 eval 데이터 세트에 수행하면서 조기 중단을 적용할 수 있습니다. xgboost 모듈의 train() 함수를 호출하면 학습을 수행하고, evals에 표시된 데이터 세트에 대해 평가지표 결과가 출력됩니다. 그리고 train()은 학습이 완료된 모델 객체를 반환합니다.

# train 데이터 세트는 'train', evaluation(test) 데이터 세트는 'eval'로 명기합니다.
wlist = [(dtrain, 'train'), (dtest, 'eval')]
# 하이퍼 파라미터와 early stopping 파라미터를 train() 함수의 파라미터로 전달
xgb_model = xgb.train(params = params, dtrain = dtrain, num_boost_round=num_rounds,\
                      early_stopping_rounds=100, evals=wlist)
'''
[0]	train-logloss:0.609688	eval-logloss:0.61352
Multiple eval metrics have been passed: 'eval-logloss' will be used for early stopping.

Will train until eval-logloss hasn't improved in 100 rounds.
[1]	train-logloss:0.540803	eval-logloss:0.547843
[2]	train-logloss:0.483753	eval-logloss:0.494248
...
[311]	train-logloss:0.00545	eval-logloss:0.085948
Stopping. Best iteration:
[211]	train-logloss:0.006413	eval-logloss:0.085593
'''

train()으로 학습을 수행하면 반복 시 train-error와 eval-logloss가 지속적으로 감소합니다. 학습이 완료된 모델 객체는 예측을 위해 predict() 메서드를 이용하는데, 예측 결과 클래스 값(0, 1)이 아닌 예측 확률값을 반환합니다.

pred_probs = xgb_model.predict(dtest)
print('predict() 수행 결과값을 10개만 표시, 예측 확률값으로 표시됨')
print(np.round(pred_probs[:10], 3))

# 예측 확률이 0.5보다 크면 1, 그렇지 않으면 0으로 예측값을 결정해 리스트 객체인 preds에 저장
preds = [1 if x > 0.5 else 0 for x in pred_probs]
print('예측값 10개만 표시:', preds[:10])
'''
predict() 수행 결과값을 10개만 표시, 예측 확률값으로 표시됨
[0.934 0.003 0.91  0.094 0.993 1.    1.    0.999 0.997 0.   ]
예측값 10개만 표시: [1, 0, 1, 0, 1, 1, 1, 1, 1, 0]
'''

get_clf_eval() 함수를 통해 XGBoost 모델의 예측 성능을 평가해보겠습니다.

from sklearn.metrics import accuracy_score, precision_score, recall_score, confusion_matrix, roc_auc_score, f1_score

def get_clf_eval(y_test, pred, pred_proba=None):
  confusion = confusion_matrix(y_test, pred)
  accuracy = accuracy_score(y_test, pred)
  precision = precision_score(y_test, pred)
  recall = recall_score(y_test, pred)
  f1 = f1_score(y_test, pred)
  # ROC-AUC 추가
  roc_auc = roc_auc_score(y_test, pred_proba)
  print('오차 행렬')
  print(confusion)
  # ROC-AUC print 추가
  print('정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f}, F1:{3:.4f}, \
  AUC:{4:.4f}'.format(accuracy, precision, recall, f1, roc_auc))

get_clf_eval(y_test, preds, pred_probs)
'''
오차 행렬
[[35  2]
 [ 1 76]]
정확도: 0.9737, 정밀도: 0.9744, 재현율: 0.9870, F1:0.9806,   AUC:0.9951
'''

 

plot_importance()

 

이번에는 xgboost 패키지에 내장된 시각화 기능을 수행해보겠습니다. xgboostplot_importance() API는 피처의 중요도를 막대그래프 형식으로 나타냅니다. plot_importance() 호출 시 파라미터로 앞에서 학습이 완료된 모델 객체 및 맷플롯립의 ax 객체를 입력하기만 하면 됩니다.

 

내장된 plot_importance() 이용 시 유의할 점은 xgboost 넘파이 기반의 피처 데이터 학습시 피처명을 제대로 알 수 없습니다. 따라서 f0, f1처럼 피처 순서별로  f자 뒤에 순서를 붙여 X축에 피처들로 나열합니다. (f0는 첫 번째 피처, f1은 두 번째 피처를 의미함)

 

from xgboost import plot_importance
import matplotlib.pyplot as plt
%matplotlib inline

fig, ax = plt.subplots(figsize=(10, 12))
plot_importance(xgb_model, ax=ax)
'''
결과2
'''

결과2

 

xgboost.to_graphviz()

 

결정 트리에서 보여준 트리 기반 규칙 구조도 xgboost에서 시각화할 수 있습니다. xgboost 모듈의 to_graphviz() API를 이용하면 주피터 노트북에 바로 규칙 트리 구조를 그릴 수 있습니다. xgboost.to_graphviz()에 파라미터로 학습 완료된 모델 객체와 Graphviz가 참조할 파일명을 입력해주면 됩니다.

 

 

xgboost.cv()

 

파이썬 래퍼 XGBoost는 데이터 세트에 대한 교차 검증 후 최적 파라미터를 구할 수 있는 방법인 cv()를 API로 제공합니다. 

xgboost.cv(params, dtrain, num_boost_round=10, nfold=3, stratified=False, folds=None, \
           metrics=(), obj=None, feval=None, maximize=None, early_stopping_rounds=None, \
           fpreproc=None, as_pandas=True, verbose_eval=None, show_stdv=True, seed=0, \
           callbacks=None, shuffle=True, custom_metric=None)

https://xgboost.readthedocs.io/en/latest/python/python_api.html

 

Python API Reference — xgboost 1.6.0-dev documentation

eval_set (Optional[Sequence[Tuple[Union[da.Array, dd.DataFrame, dd.Series], Union[da.Array, dd.DataFrame, dd.Series]]]]) – A list of (X, y) tuple pairs to use as validation sets, for which metrics will be computed. Validation metrics will help us track t

xgboost.readthedocs.io

xgboost.cv의 반환값은 DataFrame 형태입니다. 앞으로는 사이킷런과 호환돼 편리하게 사용할 수 있는 사이킷런 래퍼 XGboost를 사용하겠습니다.

 

 

사이킷런 래퍼 XGBoost의 개요 및 적용

사이킷런을 위한 래퍼 XGBoost는 크게 분류를 위한 래퍼 클래스인 XGBClassifier, 회귀를 위한 래퍼 클래스인 XGBRegressor입니다. 

 

XGBClassifier의 기존 사이킷런에서 사용하는 하이퍼 파라미터의 호환성을 위해 xgboost 모듈에서 사용하던 네이티브 하이퍼 파라미터 몇 개를 다음과 같이 변경했습니다.

  • eta → learning_rate
  • sub_sample → subsample
  • lambda → reg_lambda
  • alpha → reg_alpha
  • num_boost_round → n_estimators

 

앞에서 사용하던 위스콘신 유방암 데이터 세트의 X_train, X_test, y_train, y_test를 그대로 적용하겠습니다.

# 사이킷런 래퍼 XGBoost 클래스인 XGBClassifier 임포트
from xgboost import XGBClassifier

xgb_wrapper = XGBClassifier(n_estimators=400, learning_rate=0.1, max_depth=3)
xgb_wrapper.fit(X_train, y_train)
w_preds = xgb_wrapper.predict(X_test)
w_pred_proba = xgb_wrapper.predict_proba(X_test)[:, 1]

# 모델 예측 성능 평가
get_clf_eval(y_test, w_preds, w_pred_proba)
'''
오차 행렬
[[35  2]
 [ 1 76]]
정확도: 0.9737, 정밀도: 0.9744, 재현율: 0.9870, F1:0.9806,   AUC:0.9951
'''

앞 예제의 파이썬 래퍼 XGBoost와 동일한 평가 결과가 나옴을 알 수 있습니다. 사이킷런 래퍼 XGBoost의 조기 중단 관련 파라미터는 fit()에 입력하는데, 평가 지표가 향상될 수 있는 반복 횟수를 정의하는 early_stopping_rounds, 조기 중단을 위한 평가 지표인 eval_metric, 그리고 성능 평가를 수행할 데이터 세트인 eval_set입니다. eval_set은 학습 데이터가 아니라 별도의 데이터 세트여야 합니다.

 

early_stopping_rounds를 100, eval_metric은 logloss, eval_set은 테스트 데이터 세트로 설정하겠습니다. 원래는 테스트 데이터가 아닌 별도의 평가 데이터로 학습을 수행해야합니다. 테스트 데이터는 학습 시에 완전히 알려지지 않은 데이터 세트를 사용해야 합니다.

from xgboost import XGBClassifier

xgb_wrapper = XGBClassifier(n_estimators=400, learning_rate=0.1, max_depth=3)
evals = [(X_test, y_test)]
xgb_wrapper.fit(X_train, y_train, early_stopping_rounds=100, eval_metric="logloss"\
                , eval_set=evals, verbose=True)
ws100_preds = xgb_wrapper.predict(X_test)
ws100_pred_proba = xgb_wrapper.predict_proba(X_test)[:, 1]
# 아래는 xgb_wrapper.fit()의 결과
'''
[0]	validation_0-logloss:0.61352
Will train until validation_0-logloss hasn't improved in 100 rounds.
[1]	validation_0-logloss:0.547842
[2]	validation_0-logloss:0.494247
...
[310]	validation_0-logloss:0.085923
[311]	validation_0-logloss:0.085948
Stopping. Best iteration:
[211]	validation_0-logloss:0.085593
'''

311번 반복한 후 멈춘 이유는 211번 반복 시 logloss가 0.085593이고, 311번 반복시 logloss가 0.085948인 걸로 봐서, 211번에서 311번까지 성능 평가 지수가 향상되지 않았기 때문입니다.

get_clf_eval(y_test, ws100_preds, ws100_pred_proba)
'''
오차 행렬
[[34  3]
 [ 1 76]]
정확도: 0.9649, 정밀도: 0.9620, 재현율: 0.9870, F1:0.9744,   AUC:0.9954
'''

조기 중단이 적용되지 않은 결과보다 약간 저조한 성능이지만 큰 차이는 없습니다. 이번엔 early_stopping_rounds를 10으로 하면 즉, 조기 중단의 기준을 너무 낮게 잡으면 어떤 문제가 발생하는지 알아보겠습니다.

# early_stopping_rounds를 10으로 설정하고 재학습.
xgb_wrapper.fit(X_train, y_train, early_stopping_rounds=10,\
                eval_metric="logloss", eval_set=evals, verbose=True)
ws10_preds = xgb_wrapper.predict(X_test)
ws10_pred_proba = xgb_wrapper.predict_proba(X_test)[:, 1]
get_clf_eval(y_test, ws10_preds, ws10_pred_proba)
'''
[0]	validation_0-logloss:0.61352
Will train until validation_0-logloss hasn't improved in 10 rounds.
[1]	validation_0-logloss:0.547842
[2]	validation_0-logloss:0.494247
...
[61]	validation_0-logloss:0.091461
[62]	validation_0-logloss:0.090311
Stopping. Best iteration:
[52]	validation_0-logloss:0.089577

오차 행렬
[[34  3]
 [ 2 75]]
정확도: 0.9561, 정밀도: 0.9615, 재현율: 0.9740, F1:0.9677,   AUC:0.9947
'''

정확도가 early_stopping_rounds=100일 때보다 낮아진 걸 확인할 수 있습니다. 즉, 이 파라미터를 너무 낮게 측정하면, 정확도 및 성능이 향상될 여지가 있을 수 있습니다.

 

plot_importance()

 

xgboost의 피처의 중요도를 시각화하는 모듈인 plot_importance() API는 사이킷런 래퍼 클래스를 입력해도 파이썬 래퍼 클래스를 입력한 결과와 똑같이 시각화 결과를 도출해줍니다.

from xgboost import plot_importance
import matplotlib.pyplot as plt
%matplotlib inline

fig, ax = plt.subplots(figsize=(10, 12))
# 사이킷런 Wrapper 클래스를 입력해도 무방.
plot_importance(xgb_wrapper, ax=ax)
'''
결과1		
'''

결과1

 

 

 

LightGBM

LightGBM은 XGBoost가 대용량 데이터를 학습할 때 수행 시간이 길다는 단점을 극복한 알고리즘입니다. 높은 병렬도로 학습을 진행하고, 메모리도 적게 사용합니다. LightGBM의 유일한 단점은 학습할 데이터 세트가 적을 시 과적합이 쉽게 발생한다는 점입니다. 보통 10,000건 이하의 데이터는 적다고 표현합니다. 또한, 카테고리형 피처의 자동 변환과 최적 분할(원-핫 인코딩 등을 사용하지 않고도 카테고리형 피처를 최적으로 변환하고 이에 따른 노드 분할 수행) 기능을 제공합니다.

 

LightGBM은 일반 GBM 계열의 트리 분할 방법과 다르게 리프 중심 트리 분할(Leaf Wise) 방식을 사용합니다. 대부분 트리 기반 알고리즘은 트리의 depth를 효과적으로 줄이기 위해 균형 트리 분할 방식(Level Wise)을 사용합니다. 최대한 균형 잡힌 트리를 유지하며 분할하기 때문에 깊이가 최소화될 수 있습니다. 이는 오버피팅에 보다 강한 구조입니다.

 

반면, 리프 중심 트리 분할(Leaf Wise)은 트리의 균형은 맞추지 않고, 최대 손실값(max delta loss)을 가지는 리프 노드를 지속적으로 분할합니다. 이는 비대칭적인 규칙 트리를 만들지만, 학습을 반복할 수록 결국 균형 트리 분할 방식보다 예측 오류 손실을 최소화할 수 있다는 것이 LightGBM의 구현 사상입니다.

균형 트리 분할 VS 리프 중심 트리 분할

LightGBM의 파이썬 패키지명은 'lightgbm'입니다. XGBoost와 마찬가지로 파이썬 래퍼용 LightGBM과 사이킷런 래퍼용 LightGBM이 있습니다. 사이킷런 래퍼 LightGBM 클래스는 LGBMClassifier 클래스와 LGBMRegressor 클래스가 있습니다.

 

LightGBM 설치

LightGBM은 아나콘다를 통해 설치할 수 있습니다. 윈도우에서 설치할 경우 Visual Studio Build tool 2015 이상이 설치돼 있어야 합니다. Visual Studio가 설치됐으면 OS 터미널에서 아래 conda 명령어를 수행하면 됩니다.(Windows10에서는 아나콘다 프롬프트를 관리자 권한으로 실행)

conda install -C conda-forge lightgbm

 

LightGBM 하이퍼 파라미터

LightGBM은 XGBoost와 하이퍼 파라미터가 많은 부분이 유사하며, 주의해야 할 점은 리프 노드가 계속 분할되면서 트리의 깊이가 깊어지므로 이러한 트리 특성에 맞는 하이퍼 파라미터 설정이 필요하다는 점입니다.(ex: max_depth를 크게 키움)

 

주요 파라미터

  • num_iterations [default=100]: 반복 수행할 트리의 개수를 지정합니다. LightGBM의 사이킷런 호환 클래스에서는 n_estimators로 이름이 변경 됩니다.
  • learning_rate [default=0.1]: 학습률을 설정합니다.
  • max_depth [default=1]: 트리의 깊이를 지정합니다.
  • min_data_in_leaf [default=20]: 결정 트리의 min_samples_leaf와 같은 파라미터입니다. 사이킷런 래퍼 LightGBM의 LGBMClassifier에서는 min_child_samples로 이름이 변경됩니다.
  • num_leaves [default=3]: 하나의 트리가 가질 수 있는 최대 리프 개수입니다.
  • boosting [default=gbdt]: 부스팅의 트리를 생성하는 알고리즘을 기술합니다.
    • gdbt: 일반적인 그래디언트 부스팅 결정 트리
    • rf: 랜덤 포레스트
  • bagging_fraction [default=1.0]: 데이터를 샘플링하는 비율을 지정합니다. GBM & XGBClassifier의 subsample 파라미터와 동일하고, LGBMClassifier에서는 subsample로 파라미터 이름이 변경됩니다. 트리가 커지는 것을 막아 과적합을 방지합니다.
  • feature_fraction [deafult=1.0]: 개별 트리를 학습할 때 무작위로 선택하는 피처의 비율입니다. GBM의 max_features, XGBClassifier의 colsample_bytree와 같습니다. LGBMClassifier에서는 colsample_bytree로 파라미터 이름이 변경됩니다.
  • lambda_l2 [default=0.0]: L2 regulation 제어를 위한 값입니다. 피처 개수가 많을 수록 검토하며 값이 클 수록 과적합 감소 효과가 있습니다. XGBClassifier의 reg_lambda와 동일하고 LGBMClassifier에서는 reg_lambda로 변경됩니다.
  • lambda_l1 [default=0.0]: L1 regulation 제어를 위한 값입니다. L2와 마찬가지로 과적합을 방지하며, XGBClassifier의 reg_alpha와 동일하고 LGBMClassifier에서는 reg_alpha로 변경됩니다.

 

Learning Task 파라미터

  • objective: 최솟값을 가져야 할 손실함수를 정의합니다. XGBoost의 objective 파라미터와 동일합니다. 어플리케이션의 유형, 즉 회귀, 다중 클래스 분류, 이진 분류인지에 따라 objective인 손실함수가 결정됩니다.

 

하이퍼 파라미터 튜닝 방안

num_leaves의 개수를 중심으로 min_child_samples, max_depth를 함께 조정하면서 모델의 복잡도를 줄이는 것이 기본 튜닝 방안입니다.

  • num_leaves의 개수를 높이면 정확도가 높아지지만, 반대로 트리가 깊어지고 모델 복잡도가 커져 과적합 영향도가 커집니다.
  • min_child_samples는 큰 값으로 설정하면 트리가 깊어지는 것을 방지합니다. num_leaves와 학습 데이터 크기에 따라 달라집니다.
  • max_depth는 깊이의 크기를 제한합니다.

learning_rate를 작게하고, n_estimators를 크게 하는 것은 부스팅 계열 튜닝에서 가장 기본적인 튜닝 방법입니다. 물론 n_estimators를 너무 크게 하는 건 과적합을 일으킬 수 있습니다. regularization( reg_lambda, reg_alpha )를 적용하거나 학습에 사용할 피처의 개수, 데이터 샘플링 레코드 개수를 줄이기 위해 colsample_bytree, subsample 파라미터를 적용할 수 있습니다.

 

 

파이썬 래퍼 LightGBM과 사이킷런 래퍼 XGBoost, LightGBM의 하이퍼 파라미터 비교

XGBoost의 사이킷런 래퍼 클래스와 LightGBM의 사이킷런 래퍼 클래스는 비슷한 하이퍼 파라미터를 공유합니다. 초기 파이썬 래퍼 LightGBM도 추가해 비교해보겠습니다.

유형 파이썬 래퍼 LightGBM 사이킷런 래퍼 LightGBM 사이킷런 래퍼 XGBoost
 파라미터 명 num_iterations n_estimators n_estimators
learning_rate learning_rate learning_rate
max_depth max_depth max_depth
min_data_in_leaf min_child_samples N/A
bagging_fraction subsample subsample
feature_fraction colsample_bytree colsample_bytree
lambda_l2 reg_lambda reg_lambda
lambda_l1 reg_alpha reg_alpha
early_stopping_round early_stopping_rounds early_stopping_rounds
num_leaves num_leaves N/A
min_sum_hessian_in_leaf min_child_weight min_child_weight

 

 

LightGBM 적용 - 위스콘신 유방암 예측

LightGBM도 XGBoost와 동일하게 조기 중단(early stopping)이 가능합니다. XGBClassifier와 동일하게 LGBMClassifier의 fit()에 조기 중단 관련 파라미터를 설정해주면 됩니다.

# LightGBM의 파이썬 패키지인 lightgbm에서 LGBMClassifier 임포트
from lightgbm import LGBMClassifier

import pandas as pd
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

dataset = load_breast_cancer()
ftr = dataset.data
target = dataset.target

# 전체 데이터 중 80%는 학습용 데이터, 20%는 테스트용 데이터 추출
X_train, X_test, y_train, y_test = train_test_split(ftr, target, \
                                                    test_size=0.2, random_state=156)

# 앞서 XGBoost와 동일하게 n_estimators는 400 설정.
lgbm_wrapper = LGBMClassifier(n_estimators=400)

# LightGBM도 XGBoost와 동일하게 조기 중단 수행 가능
evals = [(X_test, y_test)]
lgbm_wrapper.fit(X_train, y_train, early_stopping_rounds=100,\
                 eval_metric="logloss", eval_set=evals, verbose=True)
preds = lgbm_wrapper.predict(X_test)
pred_proba = lgbm_wrapper.predict_proba(X_test)[:, 1]
'''
[1]	valid_0's binary_logloss: 0.565079	valid_0's binary_logloss: 0.565079
Training until validation scores don't improve for 100 rounds.
[2]	valid_0's binary_logloss: 0.507451	valid_0's binary_logloss: 0.507451
[3]	valid_0's binary_logloss: 0.458489	valid_0's binary_logloss: 0.458489
[4]	valid_0's binary_logloss: 0.417481	valid_0's binary_logloss: 0.417481
...
[146]	valid_0's binary_logloss: 0.190334	valid_0's binary_logloss: 0.190334
[147]	valid_0's binary_logloss: 0.192769	valid_0's binary_logloss: 0.192769
Early stopping, best iteration is:
[47]	valid_0's binary_logloss: 0.126108	valid_0's binary_logloss: 0.126108
'''

47번 반복한 후로부터 100번까지 손실 함수가 더 줄지 않았습니다. 예측 성능을 평가해보겠습니다.

get_clf_eval(y_test, preds, pred_proba)
'''
오차 행렬
[[33  4]
 [ 2 75]]
정확도: 0.9474, 정밀도: 0.9494, 재현율: 0.9740, F1:0.9615,   AUC:0.9926
'''

LightGBM도 XGBoost와 같이 plot_importance 내장 API를 통해 피처의 중요도를 시각화할 수 있습니다.

# plot_importance()를 이용해 피처 중요도 시각화
from lightgbm import plot_importance
import matplotlib.pyplot as plt
%matplotlib inline

fig, ax = plt.subplots(figsize=(10, 12))
plot_importance(lgbm_wrapper, ax=ax)
'''
결과1
'''

결과1

 

출처: 파이썬 머신러닝 완벽 가이드 (권철민)

사진 출처:

https://velog.io/@jiselectric/Ensemble-Learning-Voting-and-Bagging-at6219ae

https://iludaslab.tistory.com/68

https://asthtls.tistory.com/1274

https://ichi.pro/ko/adaboost-laendeom-poleseuteu-mich-xgbooste-daehan-gung-geugjeog-in-gaideu-140269991631951

https://velog.io/@jiselectric/Ensemble-Learning-Voting-and-Bagging-at6219ae

https://velog.io/@khsfun0312/Decision-Tree

https://ratsgo.github.io/data%20structure&algorithm/2017/10/21/tree/

https://cis.cju.ac.kr/wp-content/lecture-materials/computer-algorithms/Chapter%2008%20%ED%8A%B8%EB%A6%AC(Tree).pdf

https://blog.naver.com/NBlogTop.naver?isHttpsRedirect=true&blogId=samsjang&Redirect=Dlog&Qs=/samsjang/220976772778 

 

[16편] 의사결정트리 학습(Decision tree learning)

우리는 한번쯤 스무고개라는 놀이를 해본 경험이 있을 겁니다. 상대방이 가지고 있는 답을 20번 이내의 질...

blog.naver.com