01. 분류(Classification)의 개요
지도학습의 대표적인 유형인 분류(Classification)는 학습데이터로 주어진 데이터의 피처와 레이블값(결정값, 클래스 값)을 머신러닝 알고리즘으로 학습해 모델을 생성하고, 생성된 모델에 새로운 데이터 값이 주어졌을 때 미지의 레이블 값을 예측하는 것이다.
분류는 다양한 머신러닝 알고리즘으로 구현할 수 있다.
이번 장에서는 다양한 머신러닝 알고리즘 중 앙상블 방법(Ensemble Method)을 집중적으로 다룬다.
앙상블은 일반적으로 배깅(Bagging), 부스팅(Boosting)방식으로 나눈다.
이 장에서는 앙상블 방법의 개요와 배깅 방식의 대표인 랜덤 포레스트, 부스팅 방법의 효시라고 할 수 있는 그래디언트 부스팅의 전통적인 앙상블 기법 뿐만 아니라 최신 기법인 XGBoost, LightBGM, 스태킹(Stacking)기법에 대해서도 알아본다.
02. 결정 트리
결정트리(Decision Tree)는 데이터에 있는 규칙을 학습을 통해 자동으로 찾아내 트리 기반의 분류 규칙을 만드는 것이다.
쉽게 생각하면, 스무고개 게임과 유사하며 룰 기반의 프로그램에 적용되는 if, else를 자동으로 찾아내 예측을 위한 규칙을 만드는 알고리즘이다.
규칙 노드(Decision Node): 규칙 조건
리프 노드(Leaf Node): 결정된 클래스 값
새로운 규칙 조건마다 서브 트리(Sub Tree)가 생성된다.
많은 규칙이 생기게되면 과적합으로 이어져 트리의 깊이가 깊어질수록 결정 트리의 예측 성능이 저하될 가능성이 높다.
그러므로 가능한 적은 결정 노드로 높은 예측 정확도를 가지려면 데이터를 분류할 때 최대한 많은 데이터 세트가 해당 분류에 속할 수 있도록 규칙 노드가 정해져야한다.
이를 위해서는 균일한 데이터 세트를 구성할 수 있도록 분할(split)하는 것이 중요하다.
균일한 데이터 세트란 무엇일까?
이 그림을 보면 C-B-A순으로 균일도가 높다고 할 수 있다.
가령 눈을 가린 채 데이터 세트 C에서 하나의 데이터를 뽑았을 때 별 다른 정보없이 검은 공이라고 예측할 수 있다.
반면, A의 경우에는 상대적으로 혼잡도가 높고 균일도가 낮기 때문에 데이터를 판단하는 데 있어 더 많은 정보를 필요로 한다.
이렇게 데이터 세트의 균일도는 데이터를 구분하는 데 필요한 정보의 양에 영향을 미친다.
결정 노드는 정보 균일도가 높은 데이터 세트를 먼저 선택할 수 있도록 규칙 조건을 만든다.
이러한 정보 균일도를 측정하는 대표적인 방법은 엔트로피를 이용한 정보 이득(Information Gain), 지니계수가 있다.
즉 정보 이득이 높거나 지니계수가 낮은 조건을 찾아 자식 트리 노드에 걸쳐 반복적으로 분할한 뒤 데이터가 모두 특정 분류에 속하게 되면 분할을 멈추고 분류를 결정한다.
<결정 트리 모델의 특징>
장점 :
- 쉽고 직관적이다.
- 피처의 스케일링이나 정규화 등의 사전 가공 영향도가 크지않다.
단점:
- 과적합으로 알고리즘 성능이 떨어진다. 이를 극복하기 위해 트리의 크기를 사전에 제한하는 튜닝이 필요하다.
<결정 트리 파라미터>
사이킷런의 결정 트리 구현은 CART(Classification And Regression Tree) 알고리즘 기반으로 분류, 회귀 모두 사용될 수 있는 트리 알고리즘이다.
여기서는 분류를 위한 DecisionTreeClassifier 클래스만 다룬다.
- min_samples_split
- 노드를 분할하기 위한 최소한의 샘플 데이터 수로 과적합을 제어하는 데 사용됨.
- 디폴트 2, 작게 설정할수록 분할되는 노드가 많아져 과적합 가능성 증가
- min_samples_leaf
- 분할이 될 경우 왼쪽과 오른쪽의 브랜치 노드에서 가져야 할 최소한의 샘플 데이터의 수
- 큰 값으로 설정될수록, 분할될 경우 왼쪽과 오른쪽의 브랜치 노드에서 가져야 할 최소한의 샘플 데이터 수 조건을 만족시키기가 어려우므로 노드 분할을 상대적으로 덜 수행함
- min_samples_split과 유사하게 과적합 제어 용도, 그러나 비대칭적(imbalanced) 데이터의 경우 특정 클래스의 데이터가 극도로 작을 수 있으므로 이 경우 작게 설정 필요
- max_features
- 최적의 분할을 위해 고려할 최대 피처 개수, 디폴트는 None으로 데이터 세트의 모든 피처를 사용해 분할 수행
- int 형으로 지정하면 대상 피처의 개수, float형으로 지정하면 전체 피처 중 대상 피처의 퍼센트
- 'sqrt'는 전체 피처 중 sqrt(전체 피처 개수), 즉 루트 전체 피처 개수만큼 선정
- 'auto'로 지정하면 sqrt와 동일
- 'log'는 전체 피처 중 log2(전체 피처 개수)선정
- 'None'은 전체 피처 선정
- max_depth
- 트리의 최대 깊이를 규정
- 디폴트는 None, None으로 설정하면 완벽하게 클래스 결정 값이 될 때까지 깊이를 계속 키우며 분할하거나 노드가 가지는 데이터 개수가 min_samples_split보다 작아질 때까지 계속 깊이를 증가시킴
- 깊이가 깊어지면 min_samples_split의 설정대로 최대 분할하여 과적합 할 수 있으므로 적절한 값으로 제어 필요
- min_leaf_nodes
- 말단 노드(Leaf)의 최대 개수
<결정 트리 모델의 시각화>
Graphviz 패키지를 통해 결정 트리 알고리즘이 어떠한 규칙을 가지고 트리를 생성하는지 시각적으로 볼 수 있다.
사이킷런은 Graphviz 패키지와 쉽게 인터페이스 할 수 있도록 export_graphviz() API를 제공한다.
붓꽃 데이터 세트를 DecisionTreeClassifier를 이용해 학습한 뒤 어떠한 형태로 규칙 트리가 만들어지는지 확인해보자
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)
#DecisionClassifier 학습
dt_clf.fit(X_train, y_train)
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)
리프 노드 : 더 이상 자식이 없는 노드로 최종 클래스(레이블)값이 결정되는 노드
리프노드가 되려면 오직 하나의 클래스 값으로 최종 데이터가 구성되거나 하이퍼 파라미터 조건을 충족하면 된다.
브랜치 노드: 자식노드가 있고 이를 만들기 위한 분할 규칙 조건을 가지고 있음
위 그림에서 노드 내에 기술된 지표의 의미를 살펴보자
- petal length(cm) <= 2.45와 같이 피처의 조건이 있는 것은 자식 노드를 만들기 위한 규칙 조건이다. 이 조건이 없으면 리프 노드
- gini는 value=[]로 주어진 데이터 분포에서의 지니계수이다.
- samples는 현 규칙에 해당하는 데이터 건수
- value=[]는 클래스 값 기반의 데이터 건수로 붓꽃 데이터 세트는 클래스 값으로 0,1,2를 가짐 (0: Setosa, 1: Versicolor, 2: Virginica)
노드 별로 자세히 설명하자면,
루트노드인 1번 노드
- samples=120개는 전체 데이터가 120개
- value=[41,40,39]는 Setosa 41개, Versicolor 40개, Virginica 39개로 데이터 구성
- sample 120개가 value=[41,40,39] 분포도로 되어있으므로 지니계수는 0.667
- petal length(cm) <=2.45 규칙으로 자식 노드 생성
- class = setosa는 하위 노드를 가질 경우에 setosa의 개수가 41개로 제일 많다는 의미
petal length <= 2.45 규칙이 True 또는 False로 분기하게 되면 2번, 3번 노드가 만들어진다.
각 노드의 색깔은 붓꽃 데이터의 레이블 값을 의미하고 색깔이 짙어질수록 지니 계수가 낮고 해당 레이블에 속하는 샘플 데이터가 많다는 의미이다. (=데이터 정보가 균일하다.)
4번 노드를 보면 38개의 데이터 중 단 1개가 Virginica임에도 불구하고 Versicolor와 구분하기 위해 다시 자식 노드를 생성한다.
이처럼 결정 트리는 로직을 미리 제어하지 않으면 복잡한 트리가 만들어져 모델이 과적합되는 문제점이 생긴다.
이를 막기위해 하이퍼 파라미터가 사용되는 것이다.
max_depth는 결정 트리의 최대 트리 깊이를 제어한다.
min_samples_splits 는 자식 규칙 노드를 분할해 만들기 위한 최소한 샘플 데이터 개수이다.
만약 min_samples_splits를 4로 설정한 경우에는 자식 노드로 분할하려면 최소한 샘플개수가 4개가 필요한데, 샘플이 3개 밖에 없으므로 더 이상 자식 규칙 노드를 위한 분할을 하지 않고 리프 노드가 된다.
min_samples_leaf 하이퍼 파라미터는 분할될 경우 왼쪽과 오른쪽 자식 노드 각각이 가지게 될 최소 데이터 건수를 지정한다.
즉, 왼쪽과 오른쪽 노드 중에 하나라도 min_samples_leaf로 지정된 최소 데이터 건수보다 더 작은 데이터 건수를 갖게 된다면, 해당 노드는 더 이상 분할하지 않고 리프 노드가 된다.
min_samples_leaf를 큰 값으로 지정하면 분할될 때 자식 노드들 모두가 해당 조건을 만족하기에 어려운 조건이 되므로 분할 되지 않고 리프 노드가 될 수 있는 가능성이 높아진다.
결정 트리는 균일도에 기반해 어떠한 속성을 규칙 조건으로 선택하느냐가 중요한 요건이다.
사이킷런은 피처의 중요한 역할 지표를 DecisionTreeClassifier 객체의 feature_importance_ 속성으로 제공한다.
feature_importance_는 ndarray 형태로 값을 반환하며 피처 순서대로 값이 할당된다.
예외 사항이 있지만, 일반적으로 값이 높을수록 해당 피처의 중요도가 높다는 의미이다.
import numpy as np
import seaborn as sns
%matplotlib inline
#feature importance 추출
print('Feature importance:\n{0}'.format(np.round(dt_clf.feature_importances_,3)))
#feature별 importance 매핑
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)
[output]
Feature importance:
[0.025 0. 0.555 0.42 ]
sepal length (cm):0.025005
sepal width (cm):0.000000
petal length (cm):0.554903
petal width (cm):0.420092
여러 피처들 중 petal_length가 가장 피처 중요도가 높음을 알 수 있다.
이렇게 규칙 트리 알고리즘은 규칙 트리의 시각화와 feature_importance_속성을 통해 결정 트리 알고리즘이 어떻게 동작하는지 직관적으로 이해할 수 있다.
<결정 트리 과적합(Overfitting)>
결정 트리가 어떻게 학습 데이터를 분할해 예측을 수행하는지와 이로 인한 과적합 문제를 시각화해 알아보자.
사이킷런은 분류를 위한 테스트용 데이터를 쉽게 만들 수 있도록 make_classification() 함수를 제공한다.
이 함수가 반환하는 객체는 피처 데이터 세트와 클래스 레이블 데이터 세트이다.
이 함수를 이용하여 3가지 유형의 클래스 값을 가지는 데이터 세트를 만들고 이를 그래프 형태로 시각화해보자
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')
첫 번째 학습 시에는 결정 트리 생성에 별다른 제약이 없도록 결정 하이퍼 파라미터를 디폴트로 한 뒤, 결정 트리 모델이 어떠한 결정 기준을 가지고 분할하면서 데이터를 분류하는지 확인한다.
이를 위해 별도의 유틸리티 함수 visualize_boundary를 생성한다.
# 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(random_state=156).fit(X_features, y_labels)
visualize_boundary(dt_clf,X_features, y_labels)
일부 이상치 데이터가 분류하기 위해 분할이 자주 일어나서 결정 기준 경계가 매우 많아졌다.
결정 트리의 기본 하이퍼 파라미터 설정은 리프 노드 안에 데이터가 모두 균일하거나 하나만 존재해야하는 엄격한 분할 기준으로 인해 결정 기준 경계가 많아지고 복잡해졌다.
이렇게 복잡한 모델은 학습 데이터 세트의 특성과 약간만 다른 데이터 세트를 예측하면 예측정확도가 떨어지게 된다.
이번에는 min_samples_leaf = 6을 설정해 6개 이하의 데이터는 리프노드를 생성할 수 있도록 해보고 결정 기준 경계가 어떻게 변하는지 살펴보자
dt_clf = DecisionTreeClassifier(min_samples_leaf=6, random_state=156).fit(X_features, y_labels)
visualize_boundary(dt_clf, X_features, y_labels)
첫 번째 모델보다 이상치에 크게 반응하지 않으면서 좀 더 일반화된 분류 규칙에 따라 분류됐음을 알 수 있다.
Ref) 파이썬 머신러닝 완벽가이드
'데이터 > 머신러닝' 카테고리의 다른 글
[Clustering] Chapter 7 | 군집화 (02. 군집 평가) (0) | 2023.01.23 |
---|---|
[Clustering] Chapter 7 | 군집화 (01. K-평균 알고리즘의 이해) (0) | 2023.01.22 |
[Evaluation] Chapter 3 | 평가 (06. 피마 인디언 당뇨병 예측) (0) | 2022.08.29 |
[Evaluation] Chapter 3 | 평가 (04. F1 스코어 ~ 05. ROC 곡선과 AUC) (0) | 2022.08.29 |
[Evaluation] Chapter 3 | 평가 (01. 정확도 ~ 03. 정밀도와 재현율) (0) | 2022.08.28 |