티스토리 뷰
05-3 트리의 앙상블¶
- 정형 데이터와 비정형 데이터¶
랜덤 포레스트에 대해 배우기 전에 잠시 우리가 다루었던 데이터를 되돌아보자. 이전에 사용한 생선 데이터와 와인 데이터는 모두 CSV 파일이다. 이런 형태의 데이터를 정형 데이터라고 부른다. 쉽게 말해 어떤 구조로 되어있는 이런 데이터는 CSV나 데이터베이스, 혹은 엑셀에 저장하기 쉽다. 이와 반대되는 데이터를 비정형 데이터라고 부른다. 비정형 데이터는 데이터베이스나 엑셀로 표현하기 어려운 것들이다. 예를 들면 텍스트 데이터, 카메라로 찍은 사진, 핸드폰으로 듣는 디지털 음악 등이 있다.
지금까지 배운 머신러닝 알고리즘은 정형 데이터에 잘 맞는다. 그중에 정형 데이터를 다루는데 가장 뛰어난 성과를 내는 알고리즘이 앙상블 학습이다. 이 알고리즘은 대부분 결정 트리를 기반으로 만들어져 있다.
- 랜덤 포레스트¶
랜덤 포레스트는 앙상블 학습의 대표 주자 중 하나로 안정적인 성능 덕분에 널리 사용되고 있다. 이름 자체로 유추할 수 있듯 랜덤 포레스트는 결정 트리를 랜덤하게 만들어 결정 트리의 숲을 만든다. 그리고 각 결정 트리의 예측을 사용해 최종 예측을 만든다. 랜덤 포레스트는 각 트리를 훈련하기 위한 데이터를 랜덤하게 만드는데, 이 데이터를 만드는 방법이 독특하다. 우리가 입력한 훈련 데이터에서 랜덤하게 샘플을 추출하여 훈련 데이터를 만든다. 이때 한 샘플이 중복되어 추출될 수도 있다.
예를 들어 1000개의 가방에서 100개씩 샘플을 뽑는다면 먼저 1개를 뽑고, 뽑았던 1개를 다시 가방에 넣는다. 이런 식으로 계속해서 100개를 가방에서 뽑으면 중복된 샘플을 뽑을 수 있다. 이렇게 만들어진 샘플을 부트스트랩 샘플이라고 부른다. 기본적으로 부트스트랩 샘플은 훈련 세트의 크기와 같게 만든다. 1000개 가방에서 중복하여 1000개의 샘플을 뽑기 때문에 부트스트랩 샘플은 훈련 세트와 크기가 같다.
또한 각 노드를 분할할 때 전체 특성 중에서 일부 특성을 무작위로 고른 다음 이 중에서 최선의 분할을 찾는다. 분류 모델인 RandomForestClassifier는 기본적으로 전체 특성 개수의 제곱근만큼의 특성을 선택한다. 즉 4개의 특성이 있다면 2개를 랜덤하게 선택하여 사용한다. 다만 회귀 모델인 RandomForestRegressor는 전체 특성을 사용한다.
사이킷런의 랜덤 포레스트는 기본적으로 100개의 결정 트리를 이런 방식으로 훈련한다. 그다음 분류일 때는 각 트리의 클래스별 확률을 평균하여 가장 높은 확률을 가진 클래스를 예측으로 삼는다. 회귀일 때는 각 트리의 예측을 평균한다.
랜덤 포레스트는 랜덤하게 선택한 샘플과 특성을 사용하기 때문에 훈련 세트에 과대적합되는 것을 막아주고 검증 세트와 테스트 세트에서 안정적인 성능을 얻을 수 있다. 기본 매개변수만으로도 아주 좋은 결과를 낸다.
이제 사이킷런의 RandomForestClassifier 클래스를 화이트 와인을 분류하는 문제에 적용해 보자.
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
wine = pd.read_csv('/home/jaeyoon89/hg-mldl/wine.csv')
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() 함수를 사용해 교차 검증을 해보자. RandomForestCalssifier는 기본적으로 100개의 결정 트리를 사용한다. n_jobs 매개변수를 -1로 지정하여 모든 CPU 코어를 사용하는 것이 좋다. cross_validate() 함수의 n_jobs 매개변수도 -1로 지정하여 최대한 병렬로 교차 검증을 수행하자. 또 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)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.9973541965122431 0.8905151032797809
출력된 결과를 보면 훈련 세트에 과대적합된 것 같다. 여기선 알고리즘을 조사하는 것이 목적이므로 매개변수를 더 조정하지 않도록 하자.
랜덤 포레스트는 결정 트리의 앙상블이기 때문에 DecisionTreeClassifier가 제공하는 중요한 매개변수를 모두 제공한다. criterion, max_depth, max_features, min_samples_split, min_impurity_decrease, min_samples_leaf 등이다. 또한 결정트리의 큰 장점 중 하나인 특성 중요도를 계산한다. 랜덤 포레스트의 특성 중요도는 각 결정 트리의 특성 중요도를 취합한 것이다. 앞의 랜덤 포레스트 모델을 훈련 세트에 훈련한 후 특성 중요도를 출력해 보자.
rf.fit(train_input, train_target)
print(rf.feature_importances_)
[0.23167441 0.50039841 0.26792718]
앞서 결정 트리에서 만든 특성 중요도와 비교해보면 각각 알코올도수, 당도, pH 였는데 두 번째 특성인 당도의 중요도가 감소하고 알코올 도수와 pH특성의 중요도가 조금 상승했다. 이런 이유는 랜덤 포레스트가 특성의 일부를 랜덤하게 선택하여 결정 트리를 훈련하기 때문이다. 그 결과 하나의 특성에 과도하게 집중하지 않고 좀 더 많은 특성이 훈련에 기여할 기회를 얻는다. 이는 과대적합을 줄이고 일반화 성능을 높이는데 도움이 된다.
RandomForestClassifier에는 재미있는 기능이 하나 더 있는데, 자체적으로 모델을 평가하는 점수를 얻을 수 있다. 랜덤 포레스트는 훈련 세트에서 중복을 허용하여 부트스트랩 샘플을 만들어 결정 트리를 훈련한다고 했다. 이때 부트스트랩 샘플에 포함되지 않고 남는 샘플이 있다. 이런 샘플을 OOB 샘플이라고 한다. 이 점수를 얻으려면 RandomForestClassifier 클래스의 oob_score 매개변수를 True로 지정해야한다. 이렇게 하면 랜덤 포레스트는 각 결정 트리의 OOB 점수를 평균하여 출력한다.
rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)
rf.fit(train_input, train_target)
print(rf.oob_score_)
0.8934000384837406
교차 검증에서 얻은 점수와 매우 비슷한 결과를 얻었다. OOB 점수를 사용하면 교차 검증을 대신할 수 있어서 결과적으로 훈련 세트에 더 많은 샘플을 사용할 수 있다.
- 엑스트라 트리¶
엑스트라 트리는 랜덤 포레스트와 매우 비슷하게 동작한다. 기본적으로 100개의 결정 트리를 훈련한다. 랜덤 포레스트와 동일하게 결정 트리가 제공하는 대부분의 매개변수를 지원한다. 또한 전체 특성 중에 일부 특성을 랜덤하게 선택하여 노드를 분할하는 데 사용한다. 랜덤 포레스트와 엑스트라 트리의 차이점은 부트스트랩 샘플을 사용하지 않는다는 점이다. 즉 각 결정 트리를 만들 때 전체 훈련 세트를 사용한다. 대신 노드를 분할할 때 가장 좋은 분할을 찾는 것이 아니라 무작위로 분할한다. 엑스트라 트리가 사용하는 결정트리는 splitter='random'인 결정트리이다. 하나의 결정 트리에서 특성을 무작위로 분할한다면 성능은 낮아지겠지만 많은 트리를 앙상블 하기 때문에 과대적합을 막고 검증 세트의 점수를 높이는 효과가 있다. 사이킷런에서 제공하는 엑스트라 트리는 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)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.9974503966084433 0.8887848893166506
랜덤 포레스트와 비슷한 결과가 나왔다. 와인 예제는 특성이 많지 않아 두 모델의 차이가 크지 않다. 엑스트라 트리가 무작위성이 좀 더 크기 때문에 랜덤 포레스트보다 더 많은 결정 트리를 훈련해야 한다. 하지만 랜덤으로 노드를 분할하기 때문에 빠른 계산 속도가 엑스트라 트리의 장점이다.
엑스트라 트리도 랜덤 포레스트와 마찬가지로 특성 중요도를 제공한다. 알코올 도수, 당도, pH 순서 인데 결과를 보면 엑스트라 트리도 결정 트리보다 당도에 대한 의존성이 작다.
et.fit(train_input, train_target)
print(et.feature_importances_)
[0.20183568 0.52242907 0.27573525]
엑스트라 트리의 회귀 버전은 ExtraTreesRegressor 클래스이다.
- 그레이디언트 부스팅¶
그레이디언트 부스팅은 깊이가 얕은 결정 트리를 사용하여 이전 트리의 오차를 보완하는 방식으로 앙상블 하는 방법이다. 사이킷런의 GradienBoostingClassifier는 기본적으로 깊이가 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)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.8881086892152563 0.8720430147331015
과대적합이 거의 되지 않았다. 그레이디언트 부스팅은 결정 트리의 개수를 늘려도 과대적합에 매우 강하다. 학습률을 증가시키고 트리의 개수를 늘리면 조금 더 성능이 향상될 수 있다.
gb = GradientBoostingClassifier(n_estimators=500, learning_rate=0.2, random_state=42)
scores = cross_validate(gb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.9464595437171814 0.8780082549788999
결정 트리의 개수를 500개로 5배 늘렸지만 과대적합을 잘 억제하고 있다. 학습률 learning_rate의 기본값은 0.1이다. 그리이디언트 부스팅도 특성 중요도를 제공한다.
gb.fit(train_input, train_target)
print(gb.feature_importances_)
[0.15872278 0.68010884 0.16116839]
결과를 보면 랜덤 포레스트보다 당도에 더 집중한다.
재미있는 매개변수가 하나 더 있다. 트리 훈련에 사용할 훈련 세트의 비율을 정하는 subsample이다. 이 매개변수의 기본값은 1.0으로 전체 훈련 세트를 사용한다. 하지만 subsample이 1보다 작으면 훈련 세트의 일부를 사용한다. 이는 마치 경사 하강법 단계마다 일부 샘플을 랜덤하게 선택하여 진행하는 확률적 경사 하강법이나 미니배치 경사 하강법과 비슷하다.
- 히스토그램 기반 그레이디언트 부스팅¶
히스토그램 기반 그레이디언트 부스팅은 정형 데이터를 다루는 머신러닝 알고리즘 중에 가장 인기가 높은 알고리즘이다. 히스토그램 기반 그레이디언트 부스팅은 먼저 입력 특성을 256개의 구간으로 나눈다. 따라서 노드를 분할할 때 최적의 분할을 매우 빠르게 찾을 수 있다. 히스토그램 기반 그레이디언트 부스팅은 256개의 구간 중에서 하나를 떼어놓고 누락된 값을 위해서 사용한다. 따라서 입력에 누락된 특성이 있더라도 이를 따로 전처리할 필요가 없다.
사이킷런의 히스토그램 기반 그레이디언트 부스팅 클래스는 HistGradientBoostingClassifier이다. 일반적으로 HistGradientBoostingClassifier는 기본 매개변수에서 안정적인 성능을 얻을 수 있다. HistGradientBoostingClassifier에는 트리의 개수를 지정하는데 n_estimators 대신에 부스팅 반복 횟수를 지정하는 max_iter를 사용한다.
from sklearn.experimental import enable_hist_gradient_boosting
from sklearn.ensemble import HistGradientBoostingClassifier
hgb = HistGradientBoostingClassifier(random_state=42)
score = cross_validate(hgb, train_input, train_target, return_train_score=True)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.9464595437171814 0.8780082549788999
과대 적합을 잘 억제하면서 그레이디언트 부스팅보다 조금 더 높은 성능을 제공한다. 히스토그램 기반 그레이디언트 부스팅의 특성 중요도를 계산하기 위해 permutation_importace() 함수를 사용하자. 이 함수는 특성을 하나씩 랜덤하게 섞어서 모델의 성능이 변화하는지를 관찰하여 어떤 특성이 중요한지를 계산한다. 먼저 히스토그램 기반 그레이디언트 부스팅 모델을 훈련하고 훈련 세트에서 특성 중요도를 계산해보자. n_repeats 매개변수는 랜덤하게 섞을 횟수를 지정한다. 여기선 10으로 지정하자. 기본값은 5이다.
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)
print(result.importances_mean)
[0.08876275 0.23438522 0.08027708]
permutation_importance() 함수가 반환하는 객체는 반복하여 얻은 특성 중요도, 평균, 표준편차를 담고있다. 평균을 출력해보면 랜덤 포레스트와 비슷한 비율임을 알 수 있다. 이번엔 테스트 세트에서 특성 중요도를 계산해 보자.
result = permutation_importance(hgb, test_input, test_target, n_repeats=10, random_state=42, n_jobs=-1)
print(result.importances_mean)
[0.05969231 0.20238462 0.049 ]
테스트 세트의 결과를 보면 그레이디언트 부스팅과 비슷하게 조금 더 당도에 집중하고 있다는 것을 알 수 있다. 그럼 HistGradientBoostingClassifier를 사용하여 테스트 세트에서의 성능을 최종적으로 확인해 보자.
hgb.score(test_input, test_target)
0.8723076923076923
출처 : 혼자 공부하는 머신러닝 + 딥러닝
'혼자공부하는 머신러닝+딥러닝' 카테고리의 다른 글
혼자 공부하는 머신러닝+딥러닝(ch6-2 k-평균) (0) | 2021.05.06 |
---|---|
혼자 공부하는 머신러닝+딥러닝(ch6-1 군집 알고리즘) (0) | 2021.05.03 |
혼자 공부하는 머신러닝+딥러닝(ch5-2 교차 검증과 그리드서치) (0) | 2021.05.02 |
혼자 공부하는 머신러닝+딥러닝(ch5-1 결정 트리) (0) | 2021.05.02 |
혼자 공부하는 머신러닝+딥러닝(ch4-2 확률적 경사 하강법) (0) | 2021.05.01 |