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

woneze·2024년 10월 4일

Hands-On Machine Learning

목록 보기
7/18

일련의 예측기로부터 예측을 수집하면 가장 좋은 모델 '하나'보다 더 좋은 예측을 얻을 수 있다. 일련의 예측기를 앙상블이라고 부르기 때문에 이를 앙상블 학습(ensemble learning)이라고 하며, 앙상블 학습 알고리즘을 앙상블 방법(ensemble method)라고 한다.

개별 트리의 예측을 모아 가장 많은 선택을 받은 클래스를 앙상블의 예측으로 삼고, 결정 트리의 앙상블을 랜덤 포레스트라고 한다.

7.1 투표 기반 분류기

직접 투표(hard voting) 분류기 : 가장 많은 표를 얻은 클래스가 앙상블의 예측이 되는 분류기

각 분류기가 약한 학습기일지라도 앙상블에 있는 약한 학습기가 충분하게 많고 다양하다면 앙상블은 강한 학습기가 될 수 있음. → 큰 수의 법칙 때문에 가능한 것

  • 앙상블 방법은 예측기가 가능한 한 서로 독립적일 때 최고의 성능
  • 각기 다른 알고리즘으로 다양한 분류기를 얻으면 매우 다른 종류의 오차를 만들 가능성이 높기 때문에 앙상블 모델의 정확도가 향상

사이킷런에서는 이름/예측기 쌍의 리스트를 제공하면, 일반 분류기처럼 쉽게 사용가능한 VotingClassifier 클래스를 제공함

from sklearn.datasets import make_moons
from sklearn.ensemble import RandomForestClassifier, VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC

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)

voting_clf = VotingClassifier(
    estimators=[
        ('lr', LogisticRegression(random_state=42)),
        ('rf', RandomForestClassifier(random_state=42)),
        ('svc', SVC(random_state=42))
    ]
)
voting_clf.fit(X_train, y_train)

VotingClassifier를 훈련할 때, 모든 추정기를 복제하여 복제된 추정기를 훈련함

for name, clf in voting_clf.named_estimators_.items():
  print(name, "=", clf.score(X_test, y_test))
# lr = 0.864
# rf = 0.896
# svc = 0.896

테스트 세트에서 훈련된 각 분류기의 정확도

voting_clf.predict(X_test[:1])
# array([1])

[clf.predict(X_test[:1]) for clf in voting_clf.estimators_]
# [array([1]), array([1]), array([0])]
  • 투표 기반 분류기의 predict() 메서드로 직접 투표를 수행함
  • 테스트 세트의 첫 번째 샘플에 대해 클래스 1을 예측하는데, 이는 세 분류기 중 두 분류기가 해당 클래스를 예측하기 때문
voting_clf.score(X_test, y_test)
# 0.912
  • 투표 기반 분류기가 다른 개별 분류기보다 조금 더 높은 성능을 보임

간접 투표 : 모든 분류기가 클래스 확률을 예측할 수 있으면, 개별 분류기의 예측을 평균 내어 확률이 가장 높은 클래스를 예측하는 것

간접 투표는 확률이 높은 투표에 비중을 더 두기 때문에 직접 투표 방식보다 성능이 더 높음

voting_clf.voting = "soft"
voting_clf.named_estimators["svc"].probability = True
voting_clf.fit(X_train, y_train)
voting_clf.score(X_test, y_test)
# 0.92
  • 투표 기반 분류기의 voting 매개변수를 "soft"로 바꾸어 간접 투표 사용
  • SVC는 클래스 확률을 제공하지 않으므로 probability=True로 지정
  • 92%의 정확도를 보임

7.2 배깅과 페이스팅

배깅(bagging, bootstrap + aggregating) : 훈련 세트에서 중복을 허용하여 샘플링하는 방식
페이스팅(pasting) : 중복을 허용하지 않고 샘플링 하는 방식

배깅과 페이스팅에서는 같은 훈련 샘플을 여러 개의 예측기에 걸쳐 사용할 수 있음. 단, 배깅만이 한 예측기를 위해 같은 훈련 샘플을 여러 번 샘플링 할 수 있음.

  • 모든 예측기가 훈련을 마치면 앙상블은 모든 예측을 모아서 새로운 샘플에 대한 예측을 생성함.
  • 집계 함수는 일반적으로 분류일 때는 통계적 최빈값, 회귀일 때는 평균을 계산함.
  • 개별 예측기는 훨씬 편향되어 있지만 집계 함수를 통과하면 편향과 분산이 모두 감소.
  • 일반적인 앙상블의 결과 : 원본 데이터셋으로 하나의 예측기를 훈련시킬 때와 비교해 편향은 비슷하지만 분산은 감소.

예측기는 병렬로 학습시킬 수 있고 병렬로 예측할 수 있음. → 확장성 덕분에 배깅과 페이스팅의 인기가 높음

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

  • 사이킷런은 BaggingClassifier를 제공
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier

bag_clf = BaggingClassifier(DecisionTreeClassifier(), n_estimators=500,
                            max_samples=100, n_jobs=-1, random_state=42)
bag_clf.fit(X_train, y_train)
  • 결정 트리 분류기 500개의 앙상블을 훈련시키는 코드
  • 훈련 세트에서 중복을 허용하여 랜덤으로 선택된 100개의 샘플로 훈련
  • n_jobs : CPU 코어 수 지정
  • 앙상블의 예측이 훨씬 일반화가 잘 됨
  • 앙상블은 비슷한 편향에서 더 작은 분산
  • 배깅은 서브셋에 다양성을 추가함
    → 배깅이 페이스팅보다 편향이 조금 더 높음
    → 다양성을 추가한다는 것은 예측기들의 상관관계를 줄임
    → 앙상블의 분산이 감소
    → 전반적으로 배깅이 더 나은 모델을 만들기 때문에 일반적으로 더 선호
  • 시간과 CPU에 여유가 있다면 교차 검증

7.2.2 OOB 평가

배깅을 사용하면 어떤 샘플은 한 예측기를 위해 여러 번 샘플링되고, 어떤 것은 전혀 선택되지 않을 수 있음.

BaggingClassifier는 중복을 허용하여 훈련 세트의 크기만큼인 m개 샘플 선택 → 평균적으로 63% 정도만 샘플링


OOB 샘플(out-of-bag) : 선택되지 않은 나머지 37%. 예측기마다 남겨진 37%는 모두 다름.

예측기의 평가에 OOB 샘플을 사용할 수 있음. 앙상블의 평가는 각 예측기의 OOB 평가를 평균하여 얻을 수 있음.

bag_clf = BaggingClassifier(DecisionTreeClassifier(), n_estimators=500,
                            oob_score=True, n_jobs=-1, random_state=42)
bag_clf.fit(X_train, y_train)
bag_clf.oob_score_
# 0.896
from sklearn.metrics import accuracy_score
y_pred = bag_clf.predict(X_test)
accuracy_score(y_test, y_pred)
# 0.92
bag_clf.oob_decision_function_[:3]
# array([[0.32352941, 0.67647059],
#        [0.3375    , 0.6625    ],
#        [1.        , 0.        ]])
  • 처음 3개의 샘플에 대한 결정 함수
  • [음성 클래스에 속할 확률, 양성 클래스에 속할 확률]

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

BaggingClassifier는 특성 샘플링을 지원함

  • max_features, bootstrap_features 매개변수를 조절
  • 고차원의 데이터셋을 다룰 때 유용

랜덤 패치 방식 : 훈련 특성과 샘플을 모두 샘플링
랜덤 서브스페이스 방식 : 훈련 샘플을 모두 사용하고 특성을 샘플링

7.4 랜덤 포레스트

랜덤 포레스트 : 결정 트리의 앙상블

from sklearn.ensemble import RandomForestClassifier

rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16,
                                 n_jobs=-1, random_state=42)
rnd_clf.fit(X_train, y_train)

y_pred_rf = rnd_clf.predict(X_test)
  • RandomForestClassifier는 DecisionTreeClassifier / BaggingClassifier의 매개변수를 모두 가지고 있음
  • 랜덤으로 특성 후보 중에서 최적의 특성을 찾는 식으로 무작위성을 주입
    → 기본적으로 n\sqrt{n}개의 특성 선택(n = 전체 특성 개수)
    → 트리를 다양하게 만들고, 편향을 손해보지만 분산을 낮추어 더 훌륭한 모델 생성

7.4.1 엑스트라 트리

엑스트라 트리 : 극단적으로 랜덤한 트리의 랜덤 포레스트.

  • 편향이 늘어나는 대신 분산이 낮아짐.
  • 일반적인 랜덤 포레스트보다 훈련 훨씬 빠름
  • 사이킷런 ExtraTreesClassifier 사용

7.4.2 특성 중요도

랜덤 포레스트는 특성의 상대적 중요도를 측정하기 쉬움.

  • 어떤 특성을 사용한 노드가 평균적으로 불순도를 얼마나 감소시키는지 확인하여 특성의 중요도 측정
  • feature_importances_ 변수에 저장
from sklearn.datasets import load_iris
iris = load_iris(as_frame=True)
rnd_clf = RandomForestClassifier(n_estimators=500, random_state=42)
rnd_clf.fit(iris.data, iris.target)
for score, name in zip(rnd_clf.feature_importances_, iris.data.columns):
  print(round(score, 2), name)
  
# 0.11 sepal length (cm)
# 0.02 sepal width (cm)
# 0.44 petal length (cm)
# 0.42 petal width (cm)
  • iris 데이터셋에 적용해봤을 때, 꽃잎의 길이와 너비가 가장 중요한 것으로 나타남
  • MNIST 데이터셋에 랜덤 포레스트 분류기를 훈련시키고 각 픽셀의 중요도를 그래프로 나타냄

랜덤 포레스트는 특성을 선택할 때, 어떤 특성이 중요한지 빠르게 확인할 수 있어 편리함

7.5 부스팅

부스팅 : 약한 학습기를 여러 개 연결하여 강한 학습기를 만드는 앙상블 방법

앞의 모델을 보완해 나가면서 일련의 예측기를 학습시키는 것으로, AdaBoost(adaptive boosting) / 그레이디언트 부스팅이 가장 널리 쓰임

7.5.1 AdaBoost

이전 모델이 과소적합했던 훈련 샘플의 가중치를 더 높이면, 새로운 예측기는 학습하기 어려운 샘플에 점점 더 맞춰짐 → 이전 예측기를 보완하는 새로운 예측기

1. 알고리즘이 기반이 되는 첫 번째 분류기를 훈련 세트에서 훈련시키고 예측을 생성
2. 잘못 분류된 훈련 샘플의 가중치를 상대적으로 높임
3. 두 번째 분류기는 업데이트된 가중치를 사용해 훈련하고 다시 예측 생성
4. 다시 가중치 업데이트
5. 반복

  • 다섯 개의 연속된 예측기의 결정 경계
  • 첫 번째 분류기의 오류로 인해 가중치가 높아짐
  • 두 번째 분류기는 더 정확하게 예측하게 됨

SAMME / SAMME.R

  • 사이킷런은 SAMME라는 AdaBoost의 다중 클래스 버전을 사용
  • 클래스가 두 개뿐이라면 SAMME는 AdaBoost와 동일
  • 확률을 추정할 수 있다면 SAMME.R을 사용
  • SAMME.R은 확률을 기반으로 하며 일반적으로 성능이 더 좋음
from sklearn.ensemble import AdaBoostClassifier

ada_clf = AdaBoostClassifier(
    DecisionTreeClassifier(max_depth=1), n_estimators=30,
    learning_rate=0.5, random_state=42)
ada_clf.fit(X_train, y_train)
  • AdaBoostClassifier를 사용하여 200개의 결정 트리를 기반으로 하는 분류기 훈련
  • 결정 트리 max_depth=1. (=결정 노드 하나와 리프 노드 두 개로 이뤄진 트리)

AdaBoost 가 overfitting 되면 추정기 수를 줄이거나 규제를 더 강하게!

7.5.2 그레이디언트 부스팅

AdaBoost와 같이 앙상블에 이전까지의 오차를 보정하도록 예측기를 추가하는 부스팅 알고리즘

AdaBoost는 반복마다 샘플의 가중치를 수정하지만, 그레이디언트 부스팅은 이전 예측기가 만든 잔여 오차에 새로운 예측기를 학습시킴

import numpy as np
from sklearn.tree import DecisionTreeRegressor
np.random.seed(42)

# 2차 방정식으로 noise가 포함된 데이터셋을 생성
X = np.random.rand(100, 1) - 0.5
y = 3 * X[:, 0] ** 2 + 0.05 * np.random.randn(100)

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

# 첫 번째 예측기에서 생긴 잔여 오차에 두 번째 DecisionTreeRegressor 훈련
y2 = y - tree_reg1.predict(X)
tree_reg2 = DecisionTreeRegressor(max_depth=2, random_state=43)
tree_reg2.fit(X, y2)

# 두 번째 예측기가 만든 잔여 오차에 세 번째 회귀 모델 훈련
y3 = y2 - tree_reg2.predict(X)
tree_reg3 = DecisionTreeRegressor(max_depth=2, random_state=44)
tree_reg3.fit(X, y3)

# 모든 트리의 예측을 더해서 새로운 샘플에 대한 예측 생성
X_new = np.array([[-0.4], [0.], [0.5]])
sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3)) 
# array([0.49484029, 0.04021166, 0.75026781])

  • 왼쪽 열은 세 트리의 예측 / 오른쪽 열은 앙상블의 예측
  • 첫 번째 행에서는 앙상블에 트리가 하나만 있어서 예측이 동일
  • 두 번째 행에서는 새로운 트리가 첫 번째 트리의 잔여 오차에 대해 학습
  • 세 번재 행에서는 두 번째 트리의 잔여 오차에 훈련
  • 트리가 앙상블에 추가될수록 앙상블의 예측이 점차 좋아짐
from sklearn.ensemble import GradientBoostingRegressor

gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3,
                                 learning_rate=1.0, random_state=42)
gbrt.fit(X, y)
  • GradientBoostingRegressor를 활용해 GBRT 앙상블 훈련
  • learning_rate : 각 트리의 기여도 조절
  • learning_rate를 낮게 설정하면 많은 트리가 필요하지만, 예측의 성능은 좋아짐 → 축소(shrinkage)라는 규제 방법
  • 트리를 더 많이 추가하면 GBRT가 훈련 세트에 과대적합
gbrt_best = GradientBoostingRegressor(
    max_depth=2, learning_rate=0.05, n_estimators=500,
    n_iter_no_change=10, random_state=42)
gbrt_best.fit(X, y)

gbrt_best.n_estimators_
# 92
  • n_iter_no_change : 정숫값으로(n)으로 설정하면 마지막 n개의 트리가 도움이 되지 않는 경우, 트리 추가를 자동으로 중지. 설정하면 새 트리를 추가할 때마다 모델의 성능을 평가할 수 있음
  • validation_fraction : 검증 세트의 크기 조절. 기본값은 10%
  • tol : 무시할 수 있는 최대 성능 향상을 결정. 기본값은 0.0001
  • subsample : 각 트리가 훈련할 때 사용할 훈련 샘플의 비율 지정

예를 들어, subsample = 0.25 라고 지정하면 각 트리는 랜덤으로 선택된 25%의 훈련 샘플로 학습 → 편향이 높아지는 대신 분산이 낮아지게 되고, 속도도 상당히 빨라짐. 이 기법을 확률적 그레이디언트 부스팅이라고 함.

7.5.3 히스토그램 기반 그레이디언트 부스팅

사이킷런은 대규모 데이터셋에 최적화된 GBRT 구현으로 히스토그램 기반 그레이디언트 부스팅(HGB)를 제공

  • 입력 특성으로 구간으로 나누어 정수로 대체하는 방식
  • max_bins : 구간의 개수를 제어. 기본값은 255이고, 이보다 높아질 수 없음
  • 구간 분할을 사용하면 학습 알고리즘이 평가해야 하는 가능한 임곗값의 수를 크게 줄임
  • 정수로 작업하면 더 빠르고 효율적인 데이터 구조 사용 가능

계산 복잡도는 O(b×m)O(b\times m)
b : 구간의 개수
m : 훈련 샘플의 개수
n : 특성의 개수

HGB는 대규모 데이터셋에서 일반 GBRT보다 수백 배 빠르게 훈련할 수 있지만, 구간 분할은 규제처럼 작동하여 정밀도 손실을 유발함. 데이터셋에 따라 과대적합을 줄이는 데 도움이 될 수도 있고, 과소적합을 유발할 수도 있음.

from sklearn.pipeline import make_pipeline
from sklearn.compose import make_column_transformer
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.preprocessing import OrdinalEncoder

hgb_reg = make_pipeline(
    make_column_transformer((OrdinalEncoder(), ["ocean_proximity"]),
                            remainder="passthrough"),
    HistGradientBoostingRegressor(categorical_features=[0], random_state=42)
)
hgb_reg.fit(housing, housing_labels)
  • 캘리포니아 주택 데이터셋에 대한 파이프라인
  • HGB는 범주형 특성과 누락된 값을 지원 → 전처리 간소화
  • 범주형 특성은 0~max_bins 사이의 정수로 표현 → OrdinalEncoder 사용
  • 범주형 열의 인덱스로 categorical_features를 설정

XGBoost, CatBoost, LightGBM 등의 그레이디언트 부스팅이 있음
모두 그레이디언트 부스팅에 특화되어 있고, 다양한 기능을 제공함

7.6 스태킹

  • 앙상블에 속한 모든 예측기를 취합하는 모델을 훈련시키는 방식
  • 세 예측기는 각각 다른 값을 예측하고 마지막 예측기(블렌더)는 그 예측을 입력 받아 최종 예측을 만들어 냄

블렌더를 훈련하려면 먼저 블렌딩 훈련 세트를 만들어야 함

  1. 앙상블의 모든 예측기에서 cross_val_predict()로 원본 훈련 세트에 있는 각 샘플에 대한 표본 외 예측을 얻음
  2. 이를 블렌더를 훈련하기 위한 입력 특성으로 사용하고, 타깃은 원본 훈련 세트에서 복사함. 원본 훈련 세트의 특성 개수에 관계없이 블렌딩 훈련 세트에는 예측기 당 하나의 입력 특성을 포함.
  3. 블렌더가 학습되면 기본 예측기는 전체 원본 훈련 세트로 마지막에 한 번 더 재훈련
from sklearn.ensemble import StackingClassifier

stacking_clf = StackingClassifier(
    estimators=[
        ('lr', LogisticRegression(random_state=42)),
        ('rf', RandomForestClassifier(random_state=42)),
        ('svc', SVC(probability=True, random_state=42))
    ],
    final_estimator=RandomForestClassifier(random_state=43),
    cv=5
)
stacking_clf.fit(X_train, y_train)

  • 앙상블 방법은 다재다능하고, 강력하며, 사용법이 매우 간단함.
  • RandomForest, AdaBoost, GBRT는 대부분의 경우에 가장 먼저 테스트해야 하는 모델이며, 서로 다른 종류로 구성된 표 형식 데이터에서 매우 유용하게 사용할 수 있음.
  • 전처리가 거의 필요하지 않아 프로토타입을 빠르게 구축하는 데 적합함.
  • 투표 기반 분류기와 스태킹 분류기 같은 앙상블 방법은 시스템 성능을 한계까지 끌어올리는 데 도움이 됨.

0개의 댓글