앞선 결정 트리의 개념에 더하여 이번 블로그에서는 앙상블 모델에 대하여 알아보도록 하겠습니다.
앙상블(ensemble)이란?
앙상블은 여러 머신러닝 모델을 연결하여 더 강력한 모델은 만드는 데에 그 의의가 있습니다. 이번 블로그를 통해 저희가 알아볼 앙상블 모델은
- 랜덤 포레스트(Random Forest Classifier)
- 그래디언트 부스팅(Gradient Boosting Classifier)
이며, 결정 트리를 이용하여 알아보도록 하겠습니다!
랜덤 포레스트(RandomForest)
결정 트리의 가장 큰 단점 중 하나는 과대적합 될 경향이 있다는 것입니다. 랜덤 포레스트는 결정 트리의 과대적합 가능성을 막아주기 위해 생각해 볼 수 있는 방법입니다.
먼저 포레스트는 말 그대로 숲을 의미하듯이, Decision Tree를 여러개 만드는 기법을 의미합니다.
각 트리는 모델별로 예측을 잘할 테지만, 과대적합 경향을 가진다는데 기초하며, 서로 다른 방향으로 과대적합된 트리를 많이 만들면 그 결과를 평균냄으로써 과대적합 양을 줄이자 라는 겁니다.
이렇게 되면 트리 모델의 예측 성능은 그대로 유지되면서 과대적합이 줄어든 다는 것이 수학적으로 증명되었습니다.
말 그대로 숲(forest)을 이루기 위해서는 나무(tree)를 많이 만들어야 합니다. 각각의 트리는 타깃 예측을 잘해야 하고 다른 트리와 비슷한 예측을 하지 않도록 구별되어야 합니다.
랜덤은 말 그대로 무작위성을 의미합니다. 숲을 이루는 나무들이 각각 달라지도록 무작위성을 주입한다는 뜻입니다.
방식은 두 가지가 있습니다.
- 트리를 생성할 때 사용하는 데이터 포인트(특성)를 무작위로 선택
- 분할 테스트에서 특성을 무작위로 선택하는 방법
랜덤 포레스트 구축하기
랜덤 포레스트 모델을 구축할 때 먼저 트리의 개수를 정해야 합니다. RandomForestClassifier나 RandomForestRegressor 클래스의 n_estimators 매개변수를 이용할 수 있습니다.
예제에서는 트리가 10개라고 가정하고 시작합니다.
이 각각의 트리들은 완전히 독립적으로 만들어져야 하므로 알고리즘은 각 트리가 고유하게 만들어지도록 무작위 한 선택을 해야 합니다. 이때 트리를 만들기 위해 데이터의 부트스트랩 샘플(bootstrap sample)을 생성합니다.
매개변수로 n_samples 개의 데이터 포인트 중 무작위로 n_samples 횟수만큼 반복 추출 하게 되는데, 이때 한 샘플이 여러 번 추출될 수 있습니다.
결론적으로 트리를 만들어야 할 샘플 자체는 원본 데이터셋 크기와 같지만, 어떤 데이터 포인트는 누락될 수 있고 어떤 데이터 포인트는 중복되어 들어갈 수 있습니다.
예를 들어 ['a', 'b', 'c', 'd']에서 부트스트랩 샘플을 만든다면
- ['b', 'd', 'd', 'c']
- ['a', 'd', 'd', 'b']
같은 샘플이 만들어질 수 있다는 이야기입니다.
이렇게 만들어진 데이터셋을 이용하여 n_estimators 개수만큼 트리를 생성하게 됩니다. 하지만 우리가 본 결정 트리의 알고리즘과 조금 다른데요, 각 노드에서 전체 특성을 대상으로 최선의 테스트를 찾는 것이 아닌 알고리즘이 각 노드에서 후보 특성을 무작위로 선택한 후 이 후보들 중에서 최선의 테스트를 찾습니다.
몇 개의 특성을 고를지는 max_features 매개변수로 조절할 수 있습니다.
후보 특성을 무작위로 고르는 것은 매 노드마다 반복되므로 트리의 각 노드는 다른 후보 특성들을 사용하여 테스트를 만듭니다. 결론적으로 부트스트랩 샘플링은 랜덤 포레스트의 트리가 조금씩 다른 데이터셋을 이용해 만들어지도록 합니다. 이는 각 트리가 서로 무작위적으로 다른 특성, 다른 데이터셋을 가지게 되어 랜덤 포레스트의 모든 트리가 서로 달라지게 합니다.
이 방식에서 핵심이 되는 매개변수는 max_features 입니다. 값에 따른 특징들은 다음과 같습니다.
- n_features ( 총특성의 개수 )와 같다면 모든 트리에서 모든 특성을 고려하기 때문에 무작위성이 들어가지 않는다.(단, 부트스트랩 샘플링은 적용 )
- 1로 설정하면 트리는 테스트할 특성을 고를 필요가 없게 되어 그냥 무작위로 선택한 특성의 임계값만을 찾게 됩니다.
정리하자면 max_features의 개수가 많아질수록 랜덤 포레스트가 각 트리들은 구성 자체가 굉장히 비슷해지고 max_feature의 개수가 적어지면 트리는 데이터를 결정하기 위해 트리들의 깊이가 깊어지게 됩니다.
max_features와 n_samples 매개변수를 적절히 조절하여 트리들을 만들면 각 트리 들은 나름대로의 예측을 하게 되고 결과를 냅니다.
랜덤 포레스트는 회귀에서는 예측들의 평균을 내고, 분류에서는 각 트리들에서 제공한 예측 값에 대한 가능성 있는 출력 레이블의 확률을 평균 내어 가장 높은 확률을 가진 클래스가 예측값이 됩니다.
본격적으로 two_moon 데이터셋을 가지고 트리 5개로 구성된 랜덤 포레스트 모델을 만들어 보겠습니다.
#필요 라이브러리 임포트
from IPython.display import display
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import mglearn
from sklearn.model_selection import train_test_split
import platform
import graphviz
plt.rcParams['axes.unicode_minus'] = False
%matplotlib inline
path = 'c:/Windows/Fonts/malgun.ttf'
from matplotlib import font_manager, rc
if platform.system() == 'Darwin':
rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
font_name = font_manager.FontProperties(fname=path).get_name()
rc('font', family=font_name)
else:
print('Unknown system... sorry!')
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=100, noise=0.25, random_state=3)
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42)
forest = RandomForestClassifier(n_estimators=5, random_state=2)
forest.fit(X_train, y_train)
랜덤 포레스트에 의해 만들어진 트리들은 estimator_ 속성에 저장됩니다. 각 트리에서 학습된 결정 경계와 이를 취합해 만든 결정 경계를 함께 시각화해 보겠습니다.
fig, axes = plt.subplots(2, 3, figsize=(20, 10))
for i, (ax, tree) in enumerate(zip(axes.ravel(), forest.estimators_)):
ax.set_title("트리 {}".format(i))
mglearn.plots.plot_tree_partition(X, y, tree, ax=ax)
mglearn.plots.plot_2d_separator(forest, X, fill=True, ax=axes[-1, -1], alpha=.4)
axes[-1, -1].set_title("랜덤 포레스트")
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
다섯 개의 결정 트리가 만들어낸 경계는 서로 다른 경계를 만들어 낸다는 것이 확인됩니다. 부트스트랩 샘플링 때문에 한쪽 트리에 나타나는 훈련 포인트가 다른 트리에는 포함되지 않을 수 있어 각 트리는 불완전합니다만, 랜덤 포레스트는 각 트리들의 평균값을 취합하기 때문에 각 트리들보다 훨씬 과대적합이 덜 되고 좋은 결정 경계를 만들어 낸다는 것이 확인됩니다.
유방암 데이터셋을 RandomForest로
이번엔 유방암 데이터셋에 100개의 트리로 이뤄진 랜덤 포레스트를 적용해 보겠습니다.
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=0)
forest = RandomForestClassifier(n_estimators=100, random_state=0)
forest.fit(X_train, y_train)
print("훈련 세트 정확도 : {:.3f}".format(forest.score(X_train, y_train)))
print("테스트 세트 정확도 : {:.3f}".format(forest.score(X_test, y_test)))
확인 결과 테스트 세트 정확도가 꽤나 높게 나오는 것이 확인됩니다. 단일 결정 트리에서 max_features 매개변수를 따로 조정하거나 사전 가지치기를 할 수도 있습니다만, 여러 튜닝을 하지 않아도 랜덤 포레스트는 꽤나 높은 결과를 얻을 수 있습니다.
결정 트리처럼 랜덤 포레스트도 특성 중요도를 제공합니다. 일반적으로 랜덤 포레스트에서 제공하는 특성 정확도가 단일 트리에서 제공하는 특성 보다 더 신뢰가 갈 것 같네요.
def plot_feature_importances_cancer(model):
n_features = cancer.data.shape[1]
plt.barh(range(n_features), model.feature_importances_, align='center')
plt.yticks(np.arange(n_features), cancer.feature_names)
plt.xlabel("특성 중요도")
plt.ylabel("특성")
plt.ylim(-1, n_features)
plot_feature_importances_cancer(forest)
단순히 그래프만 봐도 랜덤 포레스트에서는 단일 트리의 경우보다 훨씬 많은 특성이 중요도를 갖습니다. 단일 결정 트리의 worst radius도 랜덤 포레스트에서는 중요하게 생각하고 있고, 랜덤 포레스트는 더 추가해서 worst perimeter도 매우 중요한 특성으로 생각하는 것 같네요
결론적으로 랜덤 포레스트가 단일 결정 트리보다 훨씬 더 넓은 시각으로 데이터를 바라볼 수 있다는 것을 알 수 있습니다.
장단점과 매개변수
랜덤 포레스트는 회귀, 분류 가리지 않고 오늘날 매우 많이 사용하는 머신러닝 알고리즘입니다. 랜덤 포레스트는 성능이 매우 뛰어나고 딱히 매개변수 튜닝을 하지 않아도 잘 작동하며 데이터의 스케일을 맞춰줄 필요가 없습니다.
하지만 랜덤 포레스트의 모델을 만들 때는 트리의 개수가 많다면 시간이 약간 느려지기도 합니다만, n_job 매개변수를 통해 작업할 cpu개수를 지정하여 병렬 처리도 가능합니다. 참고로 n_job = -1 매개변수를 사용하면 cpu의 모든 코어를 사용하여 병렬처리 합니다.
랜덤 포레스트를 사용할 때 주의해야 할 점은 말 그래도 랜덤이기 때문에 random_state 매개변수에 값을 지정하지 않는다면 랜덤 포레스트로 모델을 생성할 때마다 다른 트리가 만들어져 전혀 다른 모델이 만들어지기도 합니다. 트리가 많을수록 random_state 값의 변화에 따른 변동이 적어집니다.
즉 항상 같은 모델을 만들어야 한다면 random_state를 지정해 주어야만 합니다.
랜덤 포레스트의 단점은 텍스트 데이터 같은 차원(feature)이 많고 희소한 데이터에는 잘 작동하지 않습니다. 이런 모델에는 오히려 선형 모델이 더 어울립니다.
랜덤 포레스트는 큰 데이터셋에도 잘 동작하고, CPU 개수까지 조절할 수 있으나, 선형 모델보다 더 많은 메모리를 사용하며 훈련과 예측이 느려집니다. 즉 속도와 메모리 사용에 제약이 있는 애플리케이션을 제작한다면 랜덤 포레스트 보다 선형 모델이 더 어울릴 수 있습니다.
중요한 매개변수로써 n_estimators, max_feature 및 max_depth 같은 사전 가지치기 옵션이 있습니다. 트리의 개수를 결정짓는 n_estimators의 값은 높을수록 트리를 많이 만들기 때문에 과대적합 가능성이 적어지지만 메모리와 긴 훈련시간이 필요하게 됩니다.
보통은 "가용한 시간과 메모리만큼 많이" 만드는 것이 좋습니다.
max_features는 각 트리가 얼마나 무작위가 될지를 결정하며 작은 max_features는 마찬가지로 과대적합을 줄여줍니다. 일반적으로는 그냥 기본값을 사용하는 것이 좋습니다.
그래디언트 부스팅(GradientBoosting)
마찬가지로 앙상블 모델로써 랜덤 포레스트와 비슷하게 여러 개의 결정 트리를 만듭니다. 이름은 회귀 트리지만 분류와 회귀 모두 사용 가능합니다.
랜덤 포레스트와의 차이점은 그래디언트 부스팅 회귀 트리에는 무작위성이 없다는 점입니다. 그래디언트 부스팅은 이전 트리의 오차를 보완하는 방식으로 순차적으로 트리를 만들어 냅니다. 무작위성을 포기하는 대신에 강력한 사전 가지치기를 사용합니다.
그래디언트 부스팅 회귀 트리에는 보통 1~5개 정도의 깊이를 가진 결정 트리를 사용하기 때문에 메모리를 적게 사용하고 예측도 빠릅니다. 이러한 얕은 트리 같은 간단한 모델을 약한 학습기(weak learner)라고도 하는데, 그래디언트 부스팅 회귀 트리는 이러한 약한 학습기를 여러개 연결합니다.
각각의 트리는 데이터의 일부에 대해서만 예측을 잘 수행할 수 있어서 트리가 많이 추가될수록 성능이 좋아지게 됩니다.
랜덤 포레스트 보다는 매개변수 설정에 대해 조금 민감하긴 하지만 잘 조정하면 더 높은 정확도를 제공합니다.
앙상블에서 사용하는 대표적인 매개변수인 사전 가지치기, 트리 개수 조절 외에도 그래디언트 부스팅에서 중요한 매개변수는 이전 트리의 오차를 얼마나 강하게 보정할 것인지를 제어하는 learning_rate를 잘 조절해야 합니다. 학습률이 커지면 보정을 강하게 하기 때문에 복잡한 모델을 만들어 냅니다. n_estimators 값을 커지게 하면 앙상블에 트리가 더 많이 추가되어 모델의 복잡도가 커지고 훈련 세트에서의 실수를 바로잡을 기회가 더 많아집니다.
마찬가지로 유방암 데이터셋을 이용해 GradientBoostingClassifier를 사용해 보겠습니다. 기본값인 깊이가 3인 트리 100개와 학습률 0.1을 사용합니다.
from sklearn.ensemble import GradientBoostingClassifier
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state = 0)
gbrt = GradientBoostingClassifier(random_state=0)
gbrt.fit(X_train, y_train)
print("훈련 세트 정확도 : {:.3f}".format(gbrt.score(X_train, y_train)))
print("테스트 세트 정확도 : {:.3f}".format(gbrt.score(X_test, y_test)))
훈련 세트의 정확도가 100% 가 나왔기 때문에 과대적합된 거 같습니다. 과대적합을 만들기 위해 그래디언트 부스팅의 복잡도를 줄이기 위하여 트리의 최대 깊이를 줄여서 사전 가지치기를 강하게 하거나 학습률을 낮출 수 있습니다.
gbrt = GradientBoostingClassifier(random_state=0, max_depth=1)
gbrt.fit(X_train, y_train)
print("훈련 세트 정확도 : {:.3f}".format(gbrt.score(X_train, y_train)))
print("테스트 세트 정확도 : {:.3f}".format(gbrt.score(X_test, y_test)))
gbrt = GradientBoostingClassifier(random_state=0, learning_rate=0.01)
gbrt.fit(X_train, y_train)
print("훈련 세트 정확도 : {:.3f}".format(gbrt.score(X_train, y_train)))
print("테스트 세트 정확도 : {:.3f}".format(gbrt.score(X_test, y_test)))
위의 두 방식 모두 모델의 정확도를 감소시키기 대문에 훈련 세트의 정확도가 낮아지기 시작합니다. 학습률을 낮추는 것은 테스트 세트의 성능을 조금밖에 개선시키지 못했으나, 트리의 최대 깊이를 낮추는 것은 모델 성능 향상에 매우 큰 기여를 하였습니다.
랜덤 포레스트처럼 특성의 중요도를 시각화해보겠습니다. 트리를 100개나 사용했기 때문에 깊이가 1인 트리더라도 모든 트리를 전부 분석하기에는 쉽지는 않습니다.
gbrt = GradientBoostingClassifier(random_state=0, max_depth=1)
gbrt.fit(X_train, y_train)
plot_feature_importances_cancer(gbrt)
랜덤 포레스트의 특성 중요도와 비슷하게 등장하는데요, 랜덤 포레스트와 다르게 그래디언트 부스팅은 일부 특성은 완전히 무시하는 것이 보입니다.
비슷한 종류의 데이터에서 그래디언트 부스팅과 랜덤 포레스트 둘 다 잘 작동합니다만, 보통 더 안정적인 랜덤 포레스트를 먼저 사용합니다. 랜덤 포레스트가 잘 작동하더라도 예측 시간이 중요하거나 머신러닝 모델에서 마지막 성능까지 쥐어짜야 할 때 그래디언트 부스팅을 사용하면 도움이 됩니다.
장단점과 매개변수
마찬가지로 그래디언트 부스팅 결정 트리 역시 지도 학습에서 강력하고 널리 사용하는 모델 중 하나입니다. 단점은 매개변수에 너무 민감하다는 데에 있고, 훈련 시간이 꽤나 길다는 것입니다.
하지만 다른 트리 기반 모델처럼 특성의 스케일을 조정하지 않아도 되고 이진 특성이 연속적인 특성에서도 잘 동작합니다. 하지만 트리 기반 모델의 특성상 희소한 고차원 데이터에는 잘 작동하지 않습니다.
그래디언트 부스팅에서 중요한 매개변수는 n_estimators와 learning_rate가 중요합니다. 이 두 매개변수는 매우 깊게 연관되어 있으며 learing_rate를 낮추면 비슷한 복잡도의 모델을 만들기 위해서 더 많은 트리를 추가해야 합니다. n_estimators가 클수록 좋은 랜덤 포레스트와 다르게 그래디언트 부스팅에서 n_estimators 매개변수를 크게 하면 모델이 복잡해지고 과대적합될 가능성이 높아지게 됩니다.
일반적인 관례상 가용한 시간과 메모리 한도 내에서 n_estimators를 맞추고 나서 적절한 learning_rate를 찾는 것입니다.
또한 트리의 복잡도를 낮추기 위해 max_depth( 또는 max_leaf_nodes )를 조절해야 하는데, 통상적으로 그래디언트 부스팅에서는 max_depth를 작게 설정하여 트리의 깊이가 5보다 깊어지지 않게 하는 것이 좋습니다.
다음 포스팅에서는 서포트 백터 머신(SVM)에 대하여 알아보도록 하겠습니다.
'Programming > 특성 공학' 카테고리의 다른 글
[Machine Learning] 서포트 벡터 Support Vector Machine(SVM) (0) | 2023.06.13 |
---|---|
[Machine Learning] 의사결정 트리(Decision Tree) 시각화 (0) | 2023.05.30 |
[Machine Learning] 의사결정 트리(Decision Tree) (0) | 2023.05.23 |
[Machine Learning] 다중 선형 분류 (0) | 2023.05.16 |
[Machine Learning] 선형 이진 분류 (0) | 2023.05.09 |