앙상블 학습과 랜덤 포레스트

u8cnk1·2023년 7월 20일
0

일련의 예측기(분류나 회귀 모델)로부터 예측을 수집 → 앙상블 ⇒ 여러 개의 분류기를 생성하고, 그 예측을 결합하여 더욱 정확한 예측을 도출하는 기법
결정트리의 앙상블 → 랜덤 포레스트
(decision tree는 overfitting 될 확률이 높기 때문에 랜덤 포레스트를 활용한다.)

앙상블 방법을 사용하여 이미 만든 여러 예측기를 연결하여 더 좋은 예측기를 만든다.

앙상블 방법 : 배깅, 부스팅, 스태킹 등


투표 기반 분류기


여러 분류기를 훈련시켰을때, 각 분류기의 예측을 모아 가장 많이 선택된 클래스를 예측하여 더 좋은 분류기를 만들 수 있다.
이렇게 다수결 투표로 정해지는 분류기를 직접 투표(hard voting) 분류기라고 한다.

*hard voting : 최대 득표를 받은 예측값으로 결론 도출

각 분류기가 약한 학습기일지라도 충분하게 많고 다양하다면 앙상블은 강한 학습기가 될 수 있다.

앙상블 방법은 예측기가 가능한 한 서로 독립적일 때 최고의 성능을 발휘한다. 다양한 분류기를 얻는 한 가지 방법은 각기 다른 알고리즘으로 학습시키는 것이다. 이렇게 하면 매우 다른 종류의 오차를 만들 가능성이 높기 때문에 앙상블 모델의 정확도를 향상시킨다.


여러 분류기를 조합하여 사이킷런의 투표 기반 분류기(VotingClassifier)를 만들고 훈련시키기

from sklearn.model_selection import train_test_split
from sklearn.datasets import make_moons  # moon 데이터셋 활용

X, y = make_moons(n_samples=500, noise=0.30, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
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)

각 분류기의 테스트셋 정확도 확인

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

투표 기반 분류기가 다른 개별 분류기보다 성능이 더 좋았다.

모든 분류기가 클래스의 확률을 예측할 수 있으면(predict_proba() 메서드가 있으면), 개별 분류기의 예측을 평균 내어 확률이 가징 높은 클래스를 예측할 수 있다. ⇒ 간접 투표(soft voting)

*soft voting : 각 분류값별 확률을 합한 값을 점수로 사용해 최대 점수를 가진 분류값을 결론으로 도출

이 방식은 확률이 높은 투표에 더 비중을 두기 때문에 직접 투표 방식보다 성능이 높다.
(이 방식을 사용하기 위해서는 voting="hard"voting="soft"로 바꾸고 모든 분류기가 클래스의 확률을 추정할 수 있어야 한다. SVC는 기본값에서는 클래스 확률을 제공하지 않으므로 probability 매개변수를 True로 지정한다.)

# 위의 코드를 간접 투표 방식으로 변경

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

voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
    voting='soft')
voting_clf.fit(X_train, y_train)
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))

위의 코드를 간접 투표 방식으로 변경한 결과 91.2%의 정확도를 얻을 수 있었다.


배깅과 페이스팅

다양한 분류기를 만드는 한 가지 방법은 각기 다른 훈련 알고리즘을 사용하는 것이다. 또 다른 방법으로는 같은 알고리즘을 사용하고 훈련 세트의 서브셋을 무작위로 구성하여 분류기를 각기 다르게 학습시키는 방법이 있다.

  • 훈련 세트에서 중복을 허용하여 샘플링하는 방식 ⇒ 배깅 bagging(bootstrap과 aggregating을 합친 단어)
    • bootstrap : 데이터를 약간 편향되도록 샘플링하는 기법, 의사결정트리처럼 과대적합되기 쉬운 모델을 앙상블할 때 많이 사용
    • aggregating : 여러 분류 모델이 예측한 값들을 조합해서 하나의 결론을 도출하는 과정, 결론은 투표(voting)를 통해 결정
  • 중복을 허용하지 않고 샘플링하는 방식 ⇒ 페이스팅 pasting

모든 예측기가 훈련을 마치면 앙상블은 모든 예측기의 예측을 모아서 새로운 샘플에 대한 예측을 만든다. 위 그림과 같이 예측기는 모두 동시에 다른 CPU 코어나 서버에서 병렬로 학습시키고, 예측 또한 병렬로 수행할 수 있다.


사이킷런의 배깅과 페이스팅

사이킷런은 배깅과 페이스팅을 위해 간편한 API로 구성된 BaggingClassifier (회귀의 경우 BaggingRegressor)를 제공한다.

from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier

bag_clf = BaggingClassifier(
    DecisionTreeClassifier(), n_estimators=500,
    max_samples=100, bootstrap=True, random_state=42)
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)

위는 결정 트리 분류기 500개의 앙상블을 훈련시키는 코드이다.
각 분류기는 훈련 세트에서 중복을 허용하여 무작위로 선택된 100개의 샘플로 훈련된다. (페이스팅을 사용하려면 bootstrap=False로 지정)
n_jobs 매개변수는 사이킷런이 훈련과 예측에 사용할 CPU 코어 수를 지정한다. (-1로 지정하면 가용한 모든 코어를 사용)

두 모델의 결정 경계를 비교한 결과, 앙상블의 예측이 결정 트리 하나의 예측보다 일반화가 잘 되었다. → 앙상블은 비슷한 편향에서 더 작은 분산을 만든다. (훈련 세트의 오차 수가 거의 비슷하지만 결정 경계는 덜 불규칙)


oob 평가

배깅을 사용하면 어떤 샘플은 여러 번 샘플링되고 어떤 것은 전혀 선택되지 않을 수 있다. BaggingClassifier는 기본값으로 중복을 허용하여(bootstrap=True) 훈련 세트의 크기만큼인 m개 샘플을 선택한다. 선택되지 않은 훈련 샘플의 나머지를 oob(out-of-bag) 샘플이라고 부른다.

예측기가 훈련되는 동안에는 oob 샘플을 사용하지 않으므로 별도의 검증 세트를 사용하지 않고 oob 샘플을 사용해 평가할 수 있다. → 앙상블의 평가는 각 예측기의 oob 평가를 평균하여 얻는다.

사이킷런에서 BaggingClassifier를 만들 때 oob_score=True로 지정하면 훈련이 끝난 후 자동으로 oob 평가를 수행한다. 다음 코드는 이 과정을 보여준다. 평가 점수 결과는 oob_score_ 변수에 저장되어 있다.

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_   # 0.8986666666666666

oob 샘플에 대한 결정 함수의 값은 oob_decision_function_ 변수에서 확인할 수 있다.

bag_clf.oob_decision_function_


위 예시를 보면 oob 평가는 첫 번째 훈련 샘플이 양성 클래스에 속할 확률을 67.72%, 음성 클래스에 속할 확률을 31.75%로 추정하고 있음을 알 수 있다.


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

BaggingClassifier 는 특성 샘플링도 지원한다. 샘플링은 'max_features', 'bootstrap_features' 두 매개변수로 조절된다.
작동 방식은 'max_samples', 'bootstrap'과 동일하지만, 샘플이 아니고 특성에 대한 샘플링이다. 따라서 각 예측기는 무작위로 선택한 입력 특성의 일부분으로 훈련된다.

이 기법은 특히 이미지와 같은 매우 고차원의 데이터셋을 다룰 때 유용하다. 훈련 특성과 샘플을 모두 샘플링하는 것을 랜덤 패치 방식이라고 하고, 훈련 샘플을 모두 사용하고 특성은 샘플링하는 것을 랜덤 서브스페이스 방식이라고 한다.

특성 샘플링은 더 다양한 예측기를 만들며 편향을 늘리는 대신 분산을 낮춘다.


랜덤 포레스트

랜덤 포레스트는 일반적으로 배깅(또는 페이스팅)을 적용한 결정 트리의 앙상블이다.

  • 전형적으로 'max_samples'를 훈련 세트의 크기로 지정한다.

  • RandomForestClassifier 또는 RandomForestRegressor를 사용할 수 있다.

랜덤 포레스트 알고리즘은 트리의 노드를 분할할 때 전체 특성 중에서 최선의 특성을 찾는 대신 무작위로 선택한 특성 후보 중에서 최적의 특성을 찾는 식으로 무작위성을 더 주입한다. 이는 결국 트리를 더욱 다양하게 만들고, 편향을 손해보는 대신 분산을 낮추어 전체적으로 더 훌륭한 모델을 만들어낸다.


엑스트라 트리

랜덤 포레스트에서 트리를 만들 때 각 노드는 무작위로 특성의 서브셋을 만들어 분할에 사용한다. 트리를 더욱 무작위하게 만들기 위해 최적의 임곗값을 찾는 대신 후보 특성을 사용해 무작위로 분할한 다음 그중에서 최상의 분할을 선택한다.

이와 같이 극단적으로 무작위한 트리의 랜덤 포레스트를 '익스트림 랜덤 트리 앙상블'(엑스트라 트리)라고 부른다.

모든 노드에서 특성마다 가장 최적의 임곗값을 찾는 것이 트리 알고리즘에서 가장 시간이 많이 소요되는 작업 중 하나이므로 일반적인 랜덤 포레스트보다 엑스트라 트리가 훨씬 빠르다.
엑스트라 트리를 만들려면 ExtraTreesClassifier를 사용한다.


특성 중요도

랜덤 포레스트의 또 다른 장점은 특성의 상대적 중요도를 측정하기 쉽다는 것이다.
사이킷런은 어떤 특성을 사용한 노드가 평균적으로 불순도를 얼마나 감소시키는지 확인하여 특성의 중요도롤 측정한다. 사이킷런은 훈련이 끝난 뒤 특성마다 자동으로 이 점수를 계산하고 중요도의 전체 합이 1이 되도록 결괏값을 정규화한다. → feature_importances_ 변수에 저장

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)

위와 같이 iris 데이터셋에 RandomForestClassifier를 훈련시키고 각 특성의 중요도를 출력하면, 가장 중요한 특성은 꽃잎의 길이(44%)와 너비(42%)이고 꽃받침의 길이와 너비는 각각 11%와 2%로 비교적 덜 중요함을 알 수 있다.

⇒ 랜덤 포레스트는 특히 특성을 선택해야 할 때 어떤 특성이 중요한지 빠르게 확인할 수 있어 매우 편리하다.


부스팅

  • 이전 분류기의 학습 결과를 바탕으로 다음 분류기의 학습 데이터의 샘플 가중치를 조정하여 학습하는 방법 (순차적으로 학습)
  • 동일한 알고리즘의 분류기를 순차적으로 학습해서 여러 개의 분류기를 만든 후, 테스트할 때 가중 투표를 통해 예측값을 결정
  • 대표적 모델 : XGBoost, AdaBoost, GradientBoost

부스팅은 약한 학습기를 여러 개 연결하여 강한 학습기를 만드는 앙상블 방법이다.
부스팅 방법에는 여러 가지가 있지만, AdaBoostGradient boosting이 가장 인기가 있다.


AdaBoost

에이다부스트는 이전 모델이 잘못분류한 훈련 샘플의 가중치를 더 높여 이전 예측기를 보완하는 새로운 예측기를 만든다. 이렇게 하면 새로운 예측기는 학습하기 어려운 샘플에 점점 더 맞춰지게 된다.

모든 예측기가 훈련을 마치면 이 앙상블은 배깅이나 페이스팅과 비슷한 방식으로 예측을 만든다. 하지만 가중치가 적용된 훈련 세트의 전반적인 정확도에 따라 예측기마다 다른 가중치가 적용된다.

에이다부스트는 모든 예측기의 예측을 계산하고 예측기 가중치를 더해 예측 결과를 만든다. 가중치 합이 가장 큰 클래스가 예측 결과가 된다.

(에이다부스트 앙상블이 훈련 세트에 과대적합되면 예측기 수를 줄이거나 예측기의 규제를 더 강하게 한다.)


Gradient boosting

gradient boosting은 Ada boost처럼 앙상블에 이전까지의 오차를 보정하도록 예측기를 순차적으로 추가하지만, 에이다부스트처럼 반복마다 샘플의 가중치를 수정하는 대신 이전 예측기가 만든 잔여 오차에 새로운 예측기를 학습시킨다.

사이킷런의 GradientBoostingRegressor를 사용하면 gradient boosted regression tree 앙상블을 간단하게 훈련시킬 수 있다.

from sklearn.ensemble import GradientBoostingRegressor

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

learning_rate 매개변수는 각 트리의 기여 정도를 조절한다.
(이를 0.1처럼 낮게 설정하면 앙상블을 훈련 세트에 학습시키기 위해 많은 트리가 필요하지만 일반적으로 예측의 성능이 좋아진다.)

최적의 트리 수를 찾기 위해서는 조기 종료 기법을 사용할 수 있다. → staged_predict() 메서드 사용
(최적의 트리 수를 찾기 위해 각 훈련 단계에서 검증 오차를 측정한다.)

GradientBoostingRegressor는 각 트리가 훈련할 때 사용할 훈련 샘플의 비율을 지정할 수 있는 'subsample' 매개변수도 지원한다. 예를 들어 'subsample=0.25'라고 하면 각 트리는 무작위로 선택된 25%의 훈련 샘플로 학습된다. ⇒ 확률적 gradient boosting

최적화된 그레디언트 부스팅 구현으로는 XGBoost 파이썬 라이브러리가 유명하다.


스태킹

스태킹 stacking (stacked generalization의 줄임말)은 '앙상블에 속한 모든 예측기의 예측을 취합하는 직접 투표같은 함수를 사용하는 대신 취합하는 모델을 훈련시킬 수 없을까요?'라는 기본 아이디어로 출발한다.

위 그림은 새로운 샘픙에 회귀 작업을 수행하는 앙상블의 예시이다. 세 예측기는 각각 다른 값(3.1, 2.7, 2.9)을 예측하고 마지막 예측기(블렌더)가 이 예측을 입력으로 받아 최종 예측(3.0)을 만든다.

일반적으로 홀드 아웃(hold-out) 세트를 사용하여 블렌더를 학습시킨다.
먼저 훈련 세트를 두 개의 서브세트로 나눈다.

첫 번째 서브셋은 첫 번째 레이어의 예측을 훈련시키기 위해 사용된다.

그런 다음 첫 번째 레이어의 예측기를 사용해 두 번째 (홀드 아웃) 세트에 대한 예측을 만든다. 즉, 첫 번째 레이어의 예측을 가지고 타깃값을 예측하도록 학습된다.

사이킷런은 스태킹을 직접 지원하지 않지만 DESlib 같은 오픈 소스 구현을 사용할 수 있다.

1개의 댓글

comment-user-thumbnail
2023년 7월 20일

소중한 정보 잘 봤습니다!

답글 달기