지금까지 꽤 많은 모델들을 설계해봤고 저번 글에서는 테스트 세트를 사용하지 않고 모델의 성능을 평가하고 하이퍼 파라미터 튜닝까지 해봤다. 이들 중에서 어떤 알고리즘이 가장 좋은 알고리즘인지 평가할 수 있을까? 문제마다 성능이 다르다 하더라도 대체로 성능이 좋은 알고리즘이 무엇일지 생각해보자.
정형 데이터와 비정형 데이터
앞에서 생선의 길이, 높이, 무게 등을 데이터로 사용하였다. 또한 이 데이터들은 CSV파일에 정리되어있었다. 쉽게 말해 어떤 구조로 되어있다는 뜻이다. 이를 정형 데이터(Structured Data)라고 한다. 이런 데이터들은 CSV나 데이터베이스, 엑셀에 저장하기 쉽다.
정형 데이터의 반대가 비정형 데이터(Unstructured Data)이다. 비정형 데이터는 데이터베이스나 엑셀로 표현하기 어려운 것들이다. 예를 들어, 책의 글과 같은 텍스트 데이터, 디지털카메라로 찍은 사진, 핸드폰으로 듣는 음악 등이 있다.
지금까지 배운 머신러닝 알고리즘들은 정형 데이터에 잘 맞는다. 그 중에서 정형 데이터를 다루는 데 가장 뛰어난 성과를 내는 알고리즘이 앙상블 학습(Ensemble Learning)이다. 이 알고리즘은 대부분 결정 트리를 기반으로 만들어져 있다.
그렇다면 비정형 데이터로는 어떤 알고리즘을 사용해야 할까? 나중에 공부할 신경망 알고리즘이다. 비정형 데이터는 규칙성을 찾기가 어려워서 전통적인 머신러닝 방법으로는 모델을 만들기 까다롭지만 신경망 알고리즘이 많이 발전해서 사진을 인식하고 텍스트를 이해하는 모델을 만들 수 있다. 이번 글에서는 앙상블 학습에 대해 알아보자.
랜덤 포레스트(Random Forest)
앙상블 학습중에 가장 대표적이라고 할 수 있는 랜덤 포레스트는 안정적인 성능 덕에 널리 사용되고 있다. 이름에서 느낄 수 있지만 결정 트리를 랜덤 하게 만들어서 숲을 만든다는 뜻이다. 각 결정 트리의 예측을 사용해서 최종 예측을 한다.
랜덤 포레스트는 입력한 훈련 데이터에서 랜덤하게 샘플을 추출하여 훈련 데이터를 만든다. 이때 한 샘플이 중복되어 추출될 수도 있다. 1000개 중에서 100개의 샘플을 뽑는다면 먼저 1개를 뽑고 다시 넣은 뒤 다시 뽑는 식으로 1000번 진행한다고 생각하면 된다. 이렇게 만들어진 샘플을 부트스트랩 샘플(Bootstrap Sample)이라고 한다. 기본적으로 부트스트랩 샘플은 훈련 세트의 크기와 같게 만든다.
또한 각각의 노드를 분할할 때는 전체 특성 중에서 일부 특성을 무작위로 고른 다음 이 중에서 최선의 분할을 찾는다.
분류 모델인 RandomForestClassifier는 특성 개수의 제곱근(루트)만큼의 특성을 선택한다. RandomForestRegressior에서는 제곱근만큼이 아닌 전체 특성을 사용한다.
랜덤 포레스트는 기본적으로 100개의 결정 트리를 이런 방식으로 훈련한다. 분류 모델인 경우에는 각 트리의 확률을 평균하여 가장 높은 확률을 가진 클래스를 예측으로 삼고, 회귀 모델인 경우에는 각 트리의 예측을 평균한다.
랜덤 하게 선택한 샘플과 특성을 사용하기 때문에 랜덤 포레스트는 훈련 세트에 과대 적합되는 것을 막아주고 검증 세트와 테스트 세트에서 안정적인 성능을 얻을 수 있다.
RandomForestClassifier 클래스로 화이트 와인과 레드 와인을 분류하는 문제에 적용해보자. 데이터를 가져와서 훈련 세트와 테스트 세트로 나누자.
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
wine = pd.read_csv('https://bit.ly/wine_csv_data')
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()
train_input, test_input, train_target, test_target = train_test_split(
data, target, test_size=0.2, random_state=42
)
cross_validate() 함수로 교차 검증을 수행해보자. return_train_score 매개변수를 True로 지정하면 검증 점수뿐만 아니라 훈련 세트에 대한 점수도 같이 반환된다.
from sklearn.model_selection import cross_validate
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(rf, train_input, train_target,
return_train_score=True, n_jobs=-1)
훈련 세트에 과대 적합되어 있는 것을 확인할 수 있지만 알고리즘에 대해 알아보는 것이 목적이므로 조정하지 않겠다.
랜덤 포레스트는 결정 트리의 앙상블이기 때문에 DecisionTreeClassifier가 제공하는 중요한 매개변수를 모두 제공한다.
또한 결정 트리의 큰 장점 중 하나인 중요도를 계산한다. 랜덤 포레스트의 특성 중요도는 각 결정 트리의 특성 중요도를 취합한 것이다. 앞의 랜덤 포레스트 모델을 훈련 세트에 훈련한 후 특성 중요도를 출력해보자.
이전에 썼던 결정 트리글에서 마지막에 특성 중요도의 출력 값은 다음과 같았다.
당도의 중요성이 이전에 썼던 결정 트리에서보다 조금 낮아졌다. 이런 현상이 나타나는 이유는 랜덤 포레스트가 특성의 일부를 랜덤 하게 선택해서 결정 트리를 훈련하기 때문이다. 그 결과 하나의 특성에 과도하게 집중하지 않고 좀 더 많은 특성이 훈련에 기여할 기회를 얻는 것이다.
RandomForestClassifier에는 자체적으로 모델을 평가하는 점수를 얻을 수 있다. 랜덤 포레스트는 부트스트랩 샘플을 만들어서 결정 트리를 훈련한다고 했는데, 이때 부트스트랩 샘플에 포함되지 않고 남는 샘플을 OOB(Out Of Bag)라고 한다.
이 OOB샘플을 사용해서 부트스트랩 샘플로 훈련한 결정 트리를 평가할 수 있다. 검증 세트의 역할을 한다고 생각하면 된다. RandomForestClassifier의 oob_score 변수를 True로 지정해주면 된다. 이렇게 하면 각 결정 트리의 OOB 점수를 평균하여 출력한다.
교차 검증에서 얻은 점수와 매우 비슷하다. OOB 점수를 사용하면 교차 검증을 대신할 수 있어서 결과적으로 훈련 세트에 더 많은 샘플을 사용할 수 있다.
엑스트라 트리(Extra Tree)
엑스트라 트리도 랜덤 포레스트와 매우 비슷하다. 기본적으로 100개의 결정 트리를 훈련하고, 결정 트리가 제공하는 대부분의 매개변수를 지원하고, 전체 특성 중에서 일부 특성을 랜덤 하게 선택하여 노드를 분할하는 것 또한 같다.
하지만 엑스트라 트리는 부트스트랩 샘플을 사용하지 않는다. 즉 각 결정 트리를 만들 때 전체 훈련 세트를 사용한다. 대신 노드를 분할할 때 최적의 분할을 찾는 것이 아닌 무작위로 분할한다. 하나의 결정 트리에서 특성을 무작위로 분할한다면 성능이 낮아지겠지만 많은 트리를 앙상블하기 때문에 과대 적합을 막고 검증 세트의 점수를 높이는 효과가 있다. 사이킷 런에서 제공하는 엑스트라 트리는 ExtraTreesClassifier이다. 이 모델의 교차 검증 점수를 확인해보자.
from sklearn.ensemble import ExtraTreesClassifier
et = ExtraTreesClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(et, train_input, train_target,
return_train_score=True, n_jobs=-1)
보통 엑스트라 트리가 무작위성이 좀 더 커서 랜덤 포레스트보다 더 많은 결정 트리를 훈련하지만 이 예제는 특성이 많지 않아서 두 모델의 차이가 크지 않다. 엑스트라 트리의 장점은 랜덤 하게 노드를 분할하여 빠른 계산을 하는 것이다.
그레이디언트 부스팅(Gradient Boosting)
그레이디언트 부스팅은 깊이가 얕은 결정 트리를 사용하여 이전 트리의 오차를 보완하는 방식으로 앙상블 하는 방법이다. GradientBoostingClassifier는 깊이가 3인 결정 트리를 100개 사용한다. 깊이가 얕은 결정 트리를 사용하기 때문에 과대 적합에 강하고 일반적으로 높은 일반화 성능을 기대할 수 있다.
이름에서 알 수 있듯이 이전에 공부했던 경사 하강법을 사용하여 트리를 앙상블에 추가한다. 분류 모델에서는 로지스틱 손실 함수를, 회귀에서는 평균 제곱 오차 함수를 사용한다.
경사 하강법에서는 손실 함수를 산으로 정의하고 가장 낮은 곳을 찾아 내려오는 과정으로 비유했다면, 그레이디언트 부스팅은 결정 트리를 계속해서 추가하면서 가장 낮은 곳을 찾아 이동한다. 천천히 조금씩 이동한다는 느낌이기 때문에 깊이가 얕은 트리를 사용해야 하는 것이다.
다시 교차 검증 점수를 확인해보자.
from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(random_state=42)
scores = cross_validate(gb, train_input, train_target,
return_train_score=True, n_jobs=-1)
그레이디언트 부스팅은 결정 트리의 개수를 늘려도 과대 적합에 매우 강하다. 학습률을 증가시키고 트리의 개수를 늘린다면 성능이 조금 더 향상될 수 있다.
또한 그레이디언트 부스팅은 랜덤 포레스트보다 일부 특성에 더 집중한다. (특성 중요도 생략)
그레이디언트 부스팅의 subsample 변수가 1.0이면 전체 훈련 세트를 사용하고, 1보다 작으면 훈련 세트의 일부를 사용한다. 경사 하강법 단계마다 일부 샘플을 랜덤하게 선택하여 진행하는 확률적 경사 하강법(1개씩)이나 미니 배치 경사 하강법(여러 개)과 비슷하다.
그레이디언트 부스팅이 랜덤 포레스트보다 조금 더 높은 성능을 얻을 수는 있지만, 순서대로 트리를 추가하기 때문에 훈련 속도가 느리다. 즉 GradientBoostingClassifier에는 n_jobs 매개변수가 없다.
히스토그램 기반 그레이디언트 부스팅(Histogram-based Gradient Boosting)
히스토그램 기반 그레이디언트 부스팅은 그레이디언트 부스팅의 속도와 성능을 개선한 알고리즘이다. 정형 테이터를 다루는 머신러닝 알고리즘 중에 가장 인기가 높은 알고리즘이라고 한다.
이 알고리즘은 입력 특성을 256개의 구간으로 나누기 때문에 노드를 분할할 때 최적의 분할을 매우 빠르게 찾을 수 있다. 히스토그램 기반 그레이디언트 부스팅은 256개의 구간 중에서 하나를 떼어 놓고 누락된 값을 위해서 사용한다. 따라서 입력에 누락된 특성이 있더라도 따로 전처리할 필요가 없다.
히스토그램 기반 그레이디언트 부스팅 클래스는 HistGradientBoostingCalssifier이다.
일반적으로 HistGradientBoostingCalssifier는 기본 매개변수에서 안정적인 성능을 얻을 수 있다. 이 클래스에는 트리의 개수를 지정하는데 n_estimators 대신 부스팅 반복 횟수를 지정하는 max_iter를 사용한다.
HistGradientBoostingCalssifier는 아직 테스트 과정에 있어서 sklearn.experimental 패키지 아래에 있다.
from sklearn.experimental import enable_hist_gradient_boosting
from sklearn.ensemble import HistGradientBoostingClassifier
hgb = HistGradientBoostingClassifier(random_state=42)
scores = cross_validate(hgb, train_input, train_target,
return_train_score=True, n_jobs=-1)
과대 적합을 잘 억제하면서 그레이디언트 부스팅보다 조금 더 높은 성능을 제공한다. 특성 중요도를 확인해보자.
특성 중요도를 계산하기 위해 permutation_importance() 함수를 사용하자. 이 함수는 특성을 하나씩 랜덤하게 섞어서 모델의 성능이 변화하는지를 관찰하여 어떤 특성이 중요한지 계산한다. 훈련 세트뿐만 아니라 테스트 세트에도 적용할 수 있고 사이킷런에서 제공하는 추정기 모델에 모두 사용할 수 있다.
히스토그램 기반 그레이디언트 부스팅 모델을 훈련하고 훈련 세트에서 특성 중요도를 계산해보자. n_repeats 변수는 랜덤하게 섞을 횟수를 지정한다.
from sklearn.inspection import permutation_importance
hgb.fit(train_input, train_target)
result = permutation_importance(hgb, train_input, train_target,
n_repeats=10, random_state=42, n_jobs=-1)
permutation_importance() 함수가 반환하는 객체는 반복하여 얻은 특성 중요도, 평균, 표준 편차를 담고 있다. 평균을 출력해 보면 랜덤 포레스트와 비슷한 비율임을 알 수 있다.
result = permutation_importance(hgb, test_input, test_target,
n_repeats=10, random_state=42, n_jobs=-1)
테스트 세트의 결과를 보면 그레이디언트 부스팅과 비슷하게 조금 더 당도의 중요도가 높다.
HistGradientBoostingClassifier를 사용해서 테스트 세트에서의 성능을 확인해보자.
앙상블 모델은 확실히 단일 결정 트리보다 좋은 결과를 얻을 수 있다는 것을 확인할 수 있다.
사이킷런 말고도 히스토그램 기반 그레이디언트 부스팅 알고리즘을 구현한 라이브러리가 더 있다.
그중에서 가장 대표적인 라이브러리는 XGBoost이다. XGBoost는 사이킷런의 cross_validate() 함수와 함께 사용할 수도 있다. tree_method 매개변수를 'hist'로 지정하면 히스토그램 기반 그레이디언트 부스팅을 사용할 수 있다. 그럼 XGBoost를 사용해서 와인 데이터의 교차 검증 점수를 확인해 보자.
from xgboost import XGBClassifier
xgb = XGBClassifier(tree_method='hist', random_state=42)
scores = cross_validate(xgb, train_input, train_target,
return_train_score=True)
마이크로소프트에서 만든 또 다른 히스토그램 기반 그레이디언트 부스팅 라이브러리인 LightGBM은 빠르고 최신 기술을 많이 적용하고 있다.
from lightgbm import LGBMClassifier
lgb = LGBMClassifier(random_state=42)
scores = cross_validate(lgb, train_input, train_target,
return_train_score=True, n_jobs=-1)
XGBoost와 LightGBM에 대한 추가적인 정보는 아래의 링크를 참고하자.
XGBoost: https://xgboost.readthedocs.io/en/latest
LightGBM: https://lightgbm.readthedocs.io/en/latest
'AI > 머신 러닝(ML)' 카테고리의 다른 글
K-평균 알고리즘 (0) | 2021.08.28 |
---|---|
군집 알고리즘 (Clustering) (0) | 2021.08.24 |
교차 검증과 그리드 서치(Cross Validation & Grid Search) (0) | 2021.08.18 |
결정 트리 (Decision Tree) (0) | 2021.08.16 |
확률적 경사 하강법 (Stochastic Gradient Descent) (0) | 2021.08.14 |