[핸즈온 머신러닝] 7. 앙상블 학습, 랜덤 포레스트

박경민·2023년 5월 5일
0

여러 예측기에서 예측을 수집하면 가장 좋은 모델 하나보다 더 좋은 예측을 얻을 수 있다. 이런 일련의 예측기를 앙상블이라 부르며, 학습 과정은 앙상블 학습이라 하고, 앙상블 학습 알고리즘은 앙상블 방법이라고 한다.

앙상블 방법은

  • 훈련 세트로부터 무작위로 다른 서브셋을 만들어 훈련
  • 개별 트리의 예측을 구해
  • 가장 많은 선택을 받은 클래스를 예측으로
  • 결정트리 앙상블을 랜덤 포레스트라 한다. (나무가 모였으니 숲이다.)

배깅, 부스팅, 스태킹 등이 가장 인기있는 앙상블 방법!

7.1 투표 기반 분류기

여러 분류기를 훈려시켰다고 가정하자.

더 좋은 분류기를 만드는 방법은 예측을 모아서 가장 많이 선택된 클래스로 예측을 확정하는 것이다. 이렇게 다수결 투표로 정해지는 분류기를 직접 투표 분류기라 한다.

이렇게 모인 다수결 투표 분류기는 대부분 개별 분류보다 정확도가 높으며, 각 분류기가 약한 학습기일지라도 충분히 많고 다양하다면 앙상블은 강한 학습기가 될 수 있다.

동전 하나가 던질 때마다 앞면이 51%, 뒷면이 49%가 나오는 균형이 맞지 않는 동전이라고 가정하자. 한 번 던질 때는 51%지만 이를 여러 번 반복하면 점점 앞면이 다수가 될 확률이 증가한다. (1000번을 던지면 앞면이 다수가 될 확률이 75%가 된다.) 이는 큰 수의 법칙 때문이며, 시행을 반복할수록 앞면이 나오는 비율(실제)이 앞면이 나올 확률(계산)에 가까워진다는 것이다. 수렴의 개념.

51%의 정확도를 가진 1000개의 분류기에서도 마찬가지다. 가장 많은 클래스를 예측으로 하면 75%나 맞아떨어진다. (같은 훈련세트를 사용한 경우 이 결론이 지켜지기 어렵다, 독립적일 때 같은 오차를 반복하지 않으며 성능이 가장 좋음.)

그럼 분류기를 조합하고 투표 기반 분류기를 훈련시키는 코드를 보자.

from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

log_clf = LogisticRegression(solver="lbfgs", random_state=42)
rnd_clf = RandomForestClassifier(n_estimators=100, random_state=42)
svm_clf = SVC(gamma="scale", random_state=42)

voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
    voting='hard'
    
voting_clf.fit(X_train, y_train)

모든 것을 조합하는 분류기 voting_clf 안에 객체를 선언하고 estimators 로 로지스틱, 랜덤포레스트, SVM 을 넣었다. 훈련은 최종 분류기 하나만 해주면 된다.

이번엔 각 분류기의 정확도를 확인해보자. (사실은 voting_clf 한 번만 해주면 되지만,)

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))

예상대로 투표 기반 분류기가 가장 좋은 성능을 기록했다.

✅ 간접투표란?
직접투표처럼 가장 많이 나온 클래스로 확정하는 것이 아니라 개별 분류기의 예측을 평균 내어 예측하는 것이다. 위의 코드에서 voting = 'soft' 로 바꾸고, 모든 예측기에 predict_proba() 메서드가 있으면 된다. SVC 는 선언시 probability = True 를 지정해주자.

7.2 배깅과 페이스팅

분류기를 만드는 또다른 방법은 같은 알고리즘 여러 개 + 훈련 세트의 서브셋을 무작위로 구성하는 것이다. 배깅bootstrap aggregating이란 훈련 세트에서 중복을 허용하여 샘플링하는 것을, 페이스팅pasting이란 중복을 허용하지 않는 샘플링 방식을 말한다.
배깅과 페이스팅은 모두 샘플링 방식으로 같은 훈련 샘플을 여러 예측기에 걸쳐 사용할 수 있고, 배깅만 한 예측기에 같은 샘플을 여러 번 훈련시킬 수 있다.

여기에서도 하나의 예측을 만들 때 통게적 최빈값(투표) 또는 평균을 사용한다.

7.2.1 배깅, 페이스팅 In 사이킷런

배깅은 (페이스팅) BaggingClassifier (bootstrap = False) 로 사이킷런 내에서 제공한다.

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)

배깅은 한 종류의 분류기만 사용하므로

  • DecisionTreeClassifier 를 사용
  • n_estimators 는 앙상블하는 분류기 개수
  • max_samples = 100은 100개의 샘플로 훈련(배깅이니 중복 허용)
  • bootstrap = True 이므로 배깅
  • n_jobs 는 사용할 코어 수, -1이면 모두 사용

  • 편향은 비슷, 더 작은 분산을 확인(불규칙한 결정경계)
  • 배깅이 페이스팅보다 조금 높은 편향, 분산은 감소.

분산과 편향

7.2.2 oob 평가

중복을 허용하며 선택하므로 선택되지 않는 샘플이 존재하는데, 이를 out-of-bag 샘플이라 한다.

따라서 훈련에 사용되지 않은 oob 샘플을 통해 모델을 평가할 수 있다. 앙상블의 평가는 각 예측기의 oob 평가를 평균하여 얻는다!

bag_clf = BaggingClassifier(
    DecisionTreeClassifier(), n_estimators=500,
    bootstrap=True, oob_score=True, random_state=40)
bag_clf.fit(X_train, y_train)
bag_clf.oob_score_

선언시 oobscore=True 를 지정해주는 것만으로 oob_score 변수를 확인할 수 있다.

실제 테스트 셋에서의 정확도와 비슷하다.

7.3 랜덤 패치와 랜덤 서브스페이스

배깅 시 특성 샘플링도 할 방법이 있다. max_features 와 bootstrop_features 로!
bootstrap_features = True 를 주면 특성을 랜덤으로 뽑는다. max_features 를 1.0보다 작게 주는 것도 같은 효과.

예측기는 무작위로 선택한 특성의 일부분으로 훈련하는 것이다.

랜덤 패치 방식이란 훈련 특성과 샘플을 모두 샘플링하는 것이고, (bootstrap=True, bootstrap_features = True)
랜덤 서브스페이스 방식이란 특성은 샘플링, 특성 내의 훈련 샘플은 모두 사용하는 것이다. (bootstrap=False, max_samples = 1.0, bootstrap_features = True)

특성 샘플링 또한 더 다양한 예측기를 만들어 편향을 늘리고, 분산을 낮춘다.

7.4 랜덤 포레스트

랜덤 포레스트는 일반적으로 배깅(또는 페이스팅)을 적용한 결정 트리의 앙상블이다. 따라서 BaggingClassifier 에 DecisionTreeClassifier 을 넣어 사용했다면 이제 그냥 RandomForestClassfier 하나만 사용한다.

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)
  • n_estimators = 500 이므로 500개의 트리 사용
  • max_leaf_nodes 는 한 트리 당 최대 16개의 리프노드를 갖는 것을 의미
  • 모든 코어 사용

랜덤포레스트 알고리즘은 한 트리의 노드 분할 시 전체 특성 중 최선의 특성을 찾는 것이 아닌 무작위로 선택된 특성 후보 중 최적의 특성을 찾는다. (편향은 높아지고, 분산은 낮아진다.)

7.4.1 엑스트라 트리 (참고)

랜덤 포레스트에서 각 트리를 만들 때 각 노드는 무작위로 특성을 선택하는 과정을 거친다. 이후 최상의 분할을 해주는 특성을 하나 선택하는 것.
이렇게 극단적으로 무작위한 랜덤 포레스트를 익스트림 랜덤트리 앙상블(엑스트라 트리) 이라 한다. 역시나 무작위가 증가하므로 편향은 늘어나고, 분산은 낮아진다. 모든 노드에서는 특성마다 임곗값을 찾는 과정을 거치는데 따라서 일반적인 랜덤포레스트보다 탐색의 범위를 줄여주어 엑스트라가 훨씬 빠르다.

7.4.2 특성 중요도

랜덤포레스트는 특성의 상대적 중요도를 측정하기 쉽다. 특성의 중요도는 해당 특성이 평균적으로 감소시킨 불순도가 높을수록 높다. (= 가중치 평균)

이 값은 featureimportances 변수에 저장되어 있다. 훈련 데이터를 불러오는 것부터 훈련, 특성 중요도까지 짧은 코드를 보자.

from sklearn.datasets import load_iris
iris = load_iris()
rnd_clf = RandomForestClassifier(n_estimators=500, random_state=42)
rnd_clf.fit(iris["data"], iris["target"])
for name, score in zip(iris["feature_names"], rnd_clf.feature_importances_):
    print(name, score)

이럴 경우 과감히 sepal 특성을 버리기도 한다. 도움이 되지 않는 특성을 버리는 것을 feature_selection 이라고 하며 중요한 과정이니 알아두자.

7.5 부스팅

부스팅이란 약한 학습기를 여러 개 연결하여 강한 학습기를 만드는 앙상블 방법을 말한다. 에이다부스트, 그라디언트 부스팅이 있다.

7.5.1 에이다부스트

이전 예측기를 보완하는 한 가지 방법?
이전 모델이 과소적합했던 훈련 샘플의 가중치를 더 높이자! (아이디어)

첫 번째 분류기에 대해 잘못 분류된 훈련 샘플의 가중치를 상대적으로 높인다. > 다시 예측 > 다시 가중치 업데이트 > 반복...

사진에서는 어두운 색에 더 가중치를 준다고 해석하면 된다.

그러면 경사하강법이랑 무엇이 다른가? 경사하강법은 비용함수를 최소화하는 목적으로 하나의 예측기의 모델 파라미터를 조정하고, 에이다부스트는 앙상블에 예측기를 추가하는 방식이다. (따라서 예측기마다 다른 가중치를 지닌다.)

✅ 에이다부스트 알고리즘

에이다부스트를 알아보자

예측기 j번째의 가중치가 적용된 에러율을 계산하는 식이다.

  • 샘플 가중치 w는 초기에 1/m (샘플 수) 로 초기화
  • 첫 번째 예측기 학습
  • 가중치 적용, 에러율 r이 훈련 세트에 대해 계산

이것을 가지고 예측기 j번째의 가중치를 게산하자.

  • 예측기가 정확하다면 r값이 낮아지고
  • 분모가 작아지고 분자가 커지며
  • 가중치가 높아진다.

이제 i번째 각 샘플의 가중치를 업데이트하자. 예측이 맞으면 그대로, 틀리면 예측기의 가중치를 더 주는 식으로 계산하자.

후에 샘플의 가중치를 정규화해준다. (모든 가중치의 합으로 나눈다.)

훈련이 끝났다면 예측을 만드는 방법을 알아보자. 가중치 알파에 대해 가중치 합이 가장 큰 클래스가 예측 결과가 된다.

7.5.2 그레이디언트 부스팅

에이다부스트와 마찬가지로 그레이디언트 역시나 예측기를 순차적으로 추가하는 방식이다. 그러나 에이다부스트는 샘플의 가중치가 다른 예측기를 추가하는 반면 그레이디언트는 이전 예측기의 잔여오차를 학습시킨 새로운 예측기를 추가한다.

이번엔 결정 트리 기반의 회귀 문제를 풀어보자. 그레이디언트 트리 부스팅, 그레이디언트 부스티드 회귀 트리이다.

결정트리를 생성하고, 학습시키자.

from sklearn.tree import DecisionTreeRegressor

tree_reg1 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg1.fit(X, y)

첫 번째 트리에서 생긴 잔여오차에 두 번째 트리를 훈련시킨다. (X값은 그대로, y값은 오차를 넣는다.)

y2 = y - tree_reg1.predict(X)
tree_reg2 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg2.fit(X, y2)

반복한다.

y3 = y2 - tree_reg2.predict(X)
tree_reg3 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg3.fit(X, y3)

예측을 만드려면 모든 트리의 예측을 단순히 더하는 방식을 사용한다.

y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))

첫 번째를 제외한 모델들은 오차를 학습시키며, 이를 앙상블 했을 때 이전 앙상블보다 모델이 좋아지는 것을 볼 수 있다. 그러나 위의 같은 과정을 사이킷런의 GradientBoostingRegressor 을 사용하면 간단히 할 수 있다.

from sklearn.ensemble import GradientBoostingRegressor

gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0, random_state=42)
gbrt.fit(X, y)

트리깊이 2, 트리 수 3, 학습률 1.0 의 앙상블 모델이다. 학습률은 각 트리의 기여 정도를 조절하는 것이다. 학습률이 낮으면, 많은 트리가 필요하지만 충족될 경우 성능이 좋아진다.

그렇다면 최적의 트리 수는 어떻게 결정할까? 모델.staged_predict() 를 사용할 수도 있고, 모델 선언 시 warm_start = True 를 줄 수도 있다. 후자의 경우 연속해서 다섯번 검증 오차가 향상되지 않으면 조기 종료한다.

🤔 확률적 그레이디언트 부스팅?
각 트리가 훈련할 때 훈련 샘플의 비율을 subsample 매개변수로 주는 방법. = 0.25 이면 각 트리는 무작위로 25% 를 훈련 샘플을 선택해 학습한다. 편향이 높아지고 분산은 낮아진다.

🤔 XGBoost?
최적화된 그레이디언트 부스팅 구현 라이브러리이다. 빠른 속도, 확장성, 이식성이 강점.

XGboost를 알아보자

7.6 스태킹

역시나 앙상블 모델이다. 앙상블에 속한 예측기의 예측을 취합하는 모델을 훈련시킬 순 없을까? (아이디어)

세 예측기는 각각 예측을 수행하고, 마지막 예측기가 최종 예측을 만드는 구조로 구성하자! (블렌더, 메타 학습기)

블렌더를 학습시키는 일반적인 방법은 홀드아웃 세트를 사용하는 것이다.

  • 훈련 세트를 두 개로 쪼개고, 첫번째 반쪽은 3개의 예측기를 훈련시키는 데 사용
  • 나머지 반쪽으로는 입력으로 주고 예측을 만듦
  • 해당 예측은 다시 입력으로 블렌더에 들어감
  • 반복하여 여러 레이어를 만들 수도 있다
  • 마치 신경망처럼!
profile
Mathematics, Algorithm, and IDEA for AI research🦖

0개의 댓글