import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings(action='ignore')
from IPython.display import set_matplotlib_formats
matplotlib.rc('font', family='AppleGothic')
matplotlib.rc('axes', unicode_minus=False)
set_matplotlib_formats('retina')
pd.options.display.max_rows=100
pd.options.display.max_columns=100
pd.set_option('display.float_format', '{:.2f}'.format)
7. 앙상블 학습과 랜덤 포레스트¶
7.1 투표 기반 분류기¶
- 각 분류기의 예측을 모아서 가장 많이 선택된 클래스를 예측
- 이렇게 다수결 투표로 정해지는 분류기를 직접 투표(hard voting) 분류기 라고 함
- 각 분류기가 약한 학습기(weak learner)(즉, 랜덤 추측보다 조금 더 높은 성능을 내는 분류기)일지라도 충분하게 많고 다양하다면 앙상블은 (높은 정확도를 내는) 강한 학습기(strong learner)가 될 수 있다
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
X, y = make_moons(n_samples=5000, noise=0.3, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
log_clf = LogisticRegression()
rnd_clf = RandomForestClassifier()
svm_clf = SVC()
voting_clf = VotingClassifier(
estimators=[('lr',log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
voting='hard')
voting_clf.fit(X_train, y_train)
VotingClassifier(estimators=[('lr', LogisticRegression()), ('rf', RandomForestClassifier()), ('svc', SVC())])
from sklearn.metrics import accuracy_score
for clf in (log_clf, rnd_clf, svm_clf, voting_clf):
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print(clf.__class__.__name__, accuracy_score(y_test, y_pred))
LogisticRegression 0.865 RandomForestClassifier 0.898 SVC 0.903 VotingClassifier 0.899
모든 분류기가 클래스의 확률을 예측할 수 있으면(즉, predict_proba() 메서드가 있으면), 개별 분류기의 예측을 평균 내어 확률이 가장 높은 클래스를 예측할 수 있는데 이를 간접 투표(soft voting)라고 한다. 이 방식은 확율이 높은 투표에 비중을 더 두기 때문에 직접 투표 방식보다 성능이 높다.
- voting="hard" 대신 voting="soft"로 바꾸고 모든 분류기가 클래스의 확률을 추정할 수 있으면 된다.
- SVC는 기본값에서는 클래스 확률을 제공하지 않으므로 probability 매개변수를 True로 지정해야 한다.
svm_clf = SVC(probability=True)
voting_clf_soft = VotingClassifier(
estimators=[('lr',log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
voting='soft')
voting_clf_soft.fit(X_train, y_train)
y_pred = voting_clf_soft.predict(X_test)
print(clf.__class__.__name__, accuracy_score(y_test, y_pred))
VotingClassifier 0.902
7.2 배깅과 페이스팅¶
같은 알고리즘을 사용하지만 훈련 세트의 서브셋을 무작위로 구성하여 분류기를 각기 다르게 학습시킨다.
- 배깅(bagging) = 훈련 세트에서 중복을 허용하여 샘플링하는 방식
- 페이스팅(pasting) = 중복을 허용하지 않고 샘플링하는 방식
수집 함수
- 분류 => 통계적 최빈값(statistical mode), 직접 투표 분류기처럼 가장 많은 예측 결과
- 회귀 => 평균 계산
개별 예측기는 원본 훈련 세트로 훈련시킨 것보다 훨씬 크게 편향되어 있지만 수집 함수를 통과하면 편향과 분상이 모두 감소한다.
일반적으로 앙상블의 결과는 원본 데이터셋으로 하나의 예측기를 훈련시킬때와 비교해 편향은 비슷하지만 분산은 줄어든다.
- 예측기는 모두 동시에 다른 CPU코어나 서버에서 병렬로 학습시킬 수 있다. 이와 유사하게 예측도 병렬로 수행할 수 있다. 이런 확장성 덕분에 배깅과 페이스팅의 인기가 높다.
7.2.1 사이킷런의 배깅과 페이스팅¶
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier
bag_clf = BaggingClassifier(
DecisionTreeClassifier(), n_estimators=500,
max_samples=100, bootstrap=True, n_jobs=-1)
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)
- bootstrap=False로 지정하면 페이스팅을 사용함
- n_jobs 매개변수는 사이킷런이 훈련과 예측에 사용할 CPU 코어 수를 지정함 (-1은 가용한 모든 코어 사용)
- BaggingClassifier는 기반이 되는 분류기가 결정 트리 분류기처럼 클래스 확률을 추정할 수 있으면(predict_proba() 함수가 있으면) 직접 투표 대신 자동으로 간접 투표 방식을 사용함
- 앙상블의 예측이 결정 트리 하나의 예측보다 일반화가 훨씬 잘 된다.
- 앙상블은 비슷한 평향에서 더 작은 분산을 만든다. (훈련 세트의 오차 수가 거의 비슷하지만 결정 경계는 덜 불규칙하다)
- 부트스트래핑은 각 예측기가 학습하는 서브셋에 다양성을 증가시키므로 배깅이 페이스팅보다 편향이 조금 더 높다.
- 하지만, 이는 예측기들의 상관관계를 줄이므로 앙상블의 분산을 감소시킨다.
- 전반적으로 배깅이 더 나은 모델을 만들기 떄문에 일반적으로 더 선호한다.
- 그러나 CPU 파워에 여유가 있다면 교차 검증으로 배깅과 페이스팅을 모두 평가해서 더 나은 쪽을 선택하는 것이 좋다.
oob 평가¶
배깅을 사용하면 어떤 샘플은 한 예측기를 위해 여러 번 샘플링되고 어떤 것은 전혀 선택되지 않을 수 있습니다. 평균적으로 각 예측기에 훈련 샘플의 63% 정도만 샘플링 된다는 것을 의미합니다. 선택되지 않은 훈련 샘플의 나머지 37%를 obb(out-of-bag)샘플이라고 부릅니다. 예측기마다 남겨진 37%는 모두 다릅니다.
예측기가 훈련되는 동안에 oob샘플을 사용하지 않으므로 검증 세트나 교차 검증을 사용하지 않고 oob샘플을 사용해 평가할 수 있습니다. 앙상블의 평가는 각 예측기의 oob 평가를 평균하여 얻습니다.
bag_clf = BaggingClassifier(
DecisionTreeClassifier(), n_estimators=500,
bootstrap=True, n_jobs=-1, oob_score=True)
bag_clf.fit(X_train, y_train)
bag_clf.oob_score_
0.89725
from sklearn.metrics import accuracy_score
y_pred = bag_clf.predict(X_test)
accuracy_score(y_test, y_pred)
0.894
bag_clf.oob_decision_function_
array([[1. , 0. ], [0.12429379, 0.87570621], [1. , 0. ], ..., [1. , 0. ], [0. , 1. ], [0.47058824, 0.52941176]])
7.3 랜덤 패치와 랜덤 서브스페이스¶
BaggingClassifier는 특성 샘플링도 지원한다. max_features, bootstrap_features 두 매개변수로 조절된다.
작동 방식은 max_samples, bootstrap과 동일하지만 샘플이 아니고 특성에 대한 샘플링이다. 따라서 각 예측기는 무작위로 선택한 입력 특성의 일부분으로 훈련된다.
특히 (이미지와 같은) 매우 고차원의 데이터셋을 다룰 때 유용한데, 훈련 특성과 샘플을 모두 샘플링하는 것을 랜덤 패치 방식(random patches method) 라고 한다. 훈련 샘플을 모두 사용하고 (즉, bootstrap=False이고 max_samples=1.0) 특성은 샘플링하는(즉, bootstrap_features=True이고 max_features는 1.0보다 작은) 것을 랜덤 서브스페이스 방식(random subspaces method) 라고 한다.
특성 샘플링은 더 다양한 예측기를 만들며 편향을 늘리는 대신 분산을 낮춘다.
7.4 랜덤 포레스트¶
from sklearn.ensemble import RandomForestClassifier
rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, n_jobs=-1)
rnd_clf.fit(X_train, y_train)
y_pred_rf = rnd_clf.predict(X_test)
RandomForestClassifier는 (트리 성장의 조절을 위한) DecisionTreeClassifier의 매개변수와 앙상블 자체를 제어하는 데 필요한 BaggingClassifier의 매개변수를 모두 가지고 있다.
- 예외) splitter(무조건 "best"), presort(무조건 False), max_samples(무조건 1.0), base_estimator(무조건 지정된 매개변수를 사용한 DecisionTreeClassifier)
# BaggingClassifier를 사용해 RandomForestClassifier와 거의 유사하게 만듬
bag_clf = BaggingClassifier(
DecisionTreeClassifier(splitter="random", max_leaf_nodes=16),
n_estimators=500, max_samples=1.0, bootstrap=True, n_jobs=-1)
랜덤 포레스트 알고리즘은 트리의 노드를 분할할 때 전체 특성 중에서 최선의 특성을 찾는 대신 무작위로 선택한 특성 후보 중에서 최적의 특성을 찾는 식으로 무작위성을 더 주입한다. 이는 결국 트리를 더욱 다양하게 만들고 편향을 손해 보는 대신 분산을 낮추어 전체적으로 더 훌륭한 모델을 만들어낸다.
7.4.1 엑스트라 트리¶
랜덤 포레스트에서 트리를 만들 때 각 노드는 무작위로 특성의 서브셋을 만들어 분할에 사용한다. 트리를 더욱 무작위하게 만들기 위해 최적의 임곗값을 찾는 대신 후보 특성을 사용해 무작위로 분할한 다음 그중에서 최상의 분할을 선택한다. 이와 같이 극단적으로 무작위한 트리의 랜덤 포레스트를 익스트림 랜덤 트리(Extremely Randomized Trees) 앙상블(Extra-Trees)라고 부른다. 여기서도 역시 편향이 늘어나지만 대신 분산을 낮추게 된다. 모든 노드에서 특성마다 가장 최적의 임곗값을 찾는 것이 트리 알고리즘에서 가장 시간이 많이 소요되는 작업 중 하나이므로 일반적인 랜덤 포레스트보다 엑스트라 트리가 훨씬 빠르다.
from sklearn.ensemble import ExtraTreesClassifier
et_clf = ExtraTreesClassifier(n_estimators=500, max_leaf_nodes=16, n_jobs=-1)
et_clf.fit(X_train, y_train)
y_pred_rf = et_clf.predict(X_test)
7.4.2 특성 중요도¶
사이킷런은 어떤 특성을 사용한 노드가 평균적으로 불순도를 얼마나 감소시키는지 확인하여 특성의 중요도를 측정한다. 정확히 말하면 가중치 평균이며 각 노드의 가중치는 연관된 훈련 샘플 수와 같다.
from sklearn.datasets import load_iris
iris = load_iris()
rnd_clf = RandomForestClassifier(n_estimators=500, n_jobs=-1)
rnd_clf.fit(iris["data"], iris["target"])
for name, score in zip(iris["feature_names"], rnd_clf.feature_importances_):
print(name, score)
sepal length (cm) 0.09887246476627054 sepal width (cm) 0.024505398099199674 petal length (cm) 0.4398014460218305 petal width (cm) 0.4368206911126993
7.5 부스팅¶
부스팅(boosting)은 약한 학습기를 여러 개 연결하여 강한 학습기를 만드는 앙상블 방법이다. 부스팅 방법의 아이디어는 앞의 모델을 보완해나가면서 일련의 예측기를 학습시키는 것이다.
- 아다부스트(AdaBoost) = Adaptive Boosting
- 그래디언트 부스팅(Gradient Boosting)
7.5.1 아다부스트¶
이전 모델이 과소적합했던 훈련 샘플의 가중치를 더 높여 이전 예측기를 보완하는 새로운 예측기를 만든다. 이렇게 하면 새로운 예측기는 학습하기 어려운 샘플에 점점 더 맞춰지게 된다. 이것이 아다부스트에서 사용하는 방법이다.
예를 들어 아다부스트 분류기를 만들려면 기반이 되는 첫 번째 분류기를 훈련 세트에서 훈련시키고 예측을 만든다. 그 다음에 잘못 분류된 훈련 샘플의 가중치를 상대적으로 높인다. 두 번째 분류기는 업데이트된 가중치를 사용해 훈련 세트에서 훈련하고 다시 예측을 만든다. 그다음에 다시 가중치를 업데이트한다....
- 각 예측기는 이전 예측기가 훈련되고 평가된 후에 학습될 수 있기 때문에 병렬화(또는 분할)를 할 수 없다. 결국 배깅이나 페이스팅만큼 확장성이 높지 않다.
from sklearn.ensemble import AdaBoostClassifier
ada_clf = AdaBoostClassifier(
DecisionTreeClassifier(max_depth=1), n_estimators=200,
algorithm="SAMME.R", learning_rate=0.5)
ada_clf.fit(X_train, y_train)
AdaBoostClassifier(base_estimator=DecisionTreeClassifier(max_depth=1), learning_rate=0.5, n_estimators=200)
사이킷런은 SAMME라는 아다부스트의 다중 클래스 버전을 사용한다. 클래스가 두 개 뿐일 때는 SAMME가 아다부스트와 동일하다.
SAMME.R에서 R은 'real'을 말한다.
AdaBoostRegressor도 존재한다.
7.5.2 그래디언트 부스팅¶
아다부스트처럼 반복마다 샘플의 가중치를 수정하는 대신 이전 예측기가 만든 잔여 오차(residual error)에 새로운 예측기를 학습시킨다.
그래디언트 트리 부스팅(Gradient Tree Boosting) 또는 그래디언트 부스티드 회귀 트리(Gradient Boosted Regression Tree = GBRT)
from sklearn.tree import DecisionTreeRegressor
tree_reg1 = DecisionTreeRegressor(max_depth=2)
tree_reg1.fit(X, y)
DecisionTreeRegressor(max_depth=2)
y2 = y - tree_reg1.predict(X)
tree_reg2 = DecisionTreeRegressor(max_depth=2)
tree_reg2.fit(X, y2)
DecisionTreeRegressor(max_depth=2)
y3 = y2 - tree_reg2.predict(X)
tree_reg3 = DecisionTreeRegressor(max_depth=2)
tree_reg3.fit(X, y3)
DecisionTreeRegressor(max_depth=2)
y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-48-e9801bfe54ea> in <module> ----> 1 y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3)) <ipython-input-48-e9801bfe54ea> in <genexpr>(.0) ----> 1 y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3)) NameError: name 'X_new' is not defined
from sklearn.ensemble import GradientBoostingRegressor
gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0)
gbrt.fit(X, y)
GradientBoostingRegressor(learning_rate=1.0, max_depth=2, n_estimators=3)
learning_rate 매개변수가 각 트리의 기여 정도를 조절한다. 이를 0.1처럼 낮게 설정하면 앙상블을 훈련 세트에 학습시키기 위해 많은 트리가 필요하지만 일반적으로 예측의 성능은 좋아진다. 이는 축소(shrinkage) 라고 부르는 규졔 방법이다. 최적의 트리 수를 찾기 위해서는 조기 종료 기법을 사용할 수 있다. 간단하게 구현하려면 staged_predict() 메서드를 사용한다. 이 메서드는 훈련의 각 단계에서 앙상블에 의해 만들어진 예측기를 순회하는 반복자(iterator)를 반환한다.
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
X_train, X_val, y_train, y_val = train_test_split(X, y)
gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=120)
gbrt.fit(X_train, y_train)
errors = [mean_squared_error(y_val, y_pred)
for y_pred in gbrt.staged_predict(X_val)]
bst_n_estimators = np.argmin(errors)
gbrt_best = GradientBoostingRegressor(max_depth=2, n_estimators=bst_n_estimators)
gbrt_best.fit(X_train, y_train)
GradientBoostingRegressor(max_depth=2, n_estimators=118)
위 코드는 120개의 트리로 GBRT 앙상블을 훈련시키고 최적의 트리 수를 찾기 위해 각 훈련 단계에서 검증 오차를 측정한다. 마지막에 최적의 트리 수를 사용해 새로운 GBRT 앙상블을 훈련시킨다.
gbrt = GradientBoostingRegressor(max_depth=2, warm_start=True)
min_val_error = float("inf")
error_going_up = 0
for n_estimators in range(1, 120):
gbrt.n_estimators = n_estimators
gbrt.fit(X_train, y_train)
y_pred = gbrt.predict(X_val)
val_error = mean_squared_error(y_val, y_pred)
if val_error < min_val_error:
min_val_error = val_error
error_going_up = 0
else:
error_going_up += 1
if error_going_upn== 5:
break # 조기 종료
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-52-a06ab8f3a8af> in <module> 13 else: 14 error_going_up += 1 ---> 15 if error_going_upn== 5: 16 break # 조기 종료 NameError: name 'error_going_upn' is not defined
(많은 수의 트리를 먼저 훈련시키고 최적의 수를 찾기 위해 살펴보는 대신) 실제로 훈련을 중지하는 방법으로 조기 종료를 구현할 수도 있다. warm_start=True로 설정하면 사이킷런이 fit()메서드가 호출될 때 기존 트리를 유지하고 훈련을 추가할 수 있도록 해준다. 위 코드는 연속해서 다섯 번의 반복 동안 검증 오차가 향상되지 않으면 훈련을 멈춘다.
GradientBoostingRegressor는 각 트리가 훈련할 때 사용할 훈련 샘플의 비율을 지정할 수 있는 subsample 매개변수도 지원한다. 예를 들어 subsample=0.25라고 하면 각 트리는 무작위로 선택된 25%의 훈련 샘플로 학습된다. 이는 편향이 높아지는 대신 분산이 낮아진다. 또한 훈련 속도를 상당히 높인다. 이런 기법을 확률적 그래디언트 부스팅(Stochastic Gradient Boosting)이라고 한다.
- 그래디언트 부스팅에 다른 비용 함수를 사용할 수도 있다. loss 매개변수를 이용해 지정한다.
7.6 스태킹¶
스태킹(stacking = stacked generalization의 줄임말)은 '앙상블에 속한 모든 예측기의 예측을 취합하는 간단한 함수(직접 투표 같은)를 사용하는 대신 취합하는 모델을 훈련시킬 수 없을까요?'라는 기본 아이디어로 출발한다.
(예측을 통해 얻어낸 예측 결과들을 훈련 세트로 블렌딩하여 예측들을 합쳐서 훈련다는 블렌더를 만들 수도 있다.)
7.7 연습문제¶
'Data Analysis > Hands On ML' 카테고리의 다른 글
[Hands On ML] 8. 차원 축소 (0) | 2021.11.04 |
---|---|
[Hands On ML] 6. 결정 트리 (0) | 2021.11.04 |
[Hands On ML] 5. 서포트 벡터 머신 (0) | 2021.11.04 |
[Hands On ML] 4. 모델 훈련 (0) | 2021.11.04 |
[Hands On ML] 3. 분류 (0) | 2021.11.04 |