일련의 예측기로부터 예측을 수집하면 가장 좋은 모델 '하나'보다 더 좋은 예측을 얻을 수 있다. 일련의 예측기를 앙상블이라고 부르기 때문에 이를 앙상블 학습(ensemble learning)이라고 하며, 앙상블 학습 알고리즘을 앙상블 방법(ensemble method)라고 한다.
개별 트리의 예측을 모아 가장 많은 선택을 받은 클래스를 앙상블의 예측으로 삼고, 결정 트리의 앙상블을 랜덤 포레스트라고 한다.
직접 투표(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])]
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
배깅(bagging, bootstrap + aggregating) : 훈련 세트에서 중복을 허용하여 샘플링하는 방식
페이스팅(pasting) : 중복을 허용하지 않고 샘플링 하는 방식
배깅과 페이스팅에서는 같은 훈련 샘플을 여러 개의 예측기에 걸쳐 사용할 수 있음. 단, 배깅만이 한 예측기를 위해 같은 훈련 샘플을 여러 번 샘플링 할 수 있음.

예측기는 병렬로 학습시킬 수 있고 병렬로 예측할 수 있음. → 확장성 덕분에 배깅과 페이스팅의 인기가 높음
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)

배깅을 사용하면 어떤 샘플은 한 예측기를 위해 여러 번 샘플링되고, 어떤 것은 전혀 선택되지 않을 수 있음.
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. ]])
BaggingClassifier는 특성 샘플링을 지원함
랜덤 패치 방식 : 훈련 특성과 샘플을 모두 샘플링
랜덤 서브스페이스 방식 : 훈련 샘플을 모두 사용하고 특성을 샘플링
랜덤 포레스트 : 결정 트리의 앙상블
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)
엑스트라 트리 : 극단적으로 랜덤한 트리의 랜덤 포레스트.
랜덤 포레스트는 특성의 상대적 중요도를 측정하기 쉬움.
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)

랜덤 포레스트는 특성을 선택할 때, 어떤 특성이 중요한지 빠르게 확인할 수 있어 편리함
부스팅 : 약한 학습기를 여러 개 연결하여 강한 학습기를 만드는 앙상블 방법
앞의 모델을 보완해 나가면서 일련의 예측기를 학습시키는 것으로, AdaBoost(adaptive boosting) / 그레이디언트 부스팅이 가장 널리 쓰임
이전 모델이 과소적합했던 훈련 샘플의 가중치를 더 높이면, 새로운 예측기는 학습하기 어려운 샘플에 점점 더 맞춰짐 → 이전 예측기를 보완하는 새로운 예측기

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

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)
AdaBoost 가 overfitting 되면 추정기 수를 줄이거나 규제를 더 강하게!
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)
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
예를 들어, subsample = 0.25 라고 지정하면 각 트리는 랜덤으로 선택된 25%의 훈련 샘플로 학습 → 편향이 높아지는 대신 분산이 낮아지게 되고, 속도도 상당히 빨라짐. 이 기법을 확률적 그레이디언트 부스팅이라고 함.
사이킷런은 대규모 데이터셋에 최적화된 GBRT 구현으로 히스토그램 기반 그레이디언트 부스팅(HGB)를 제공
계산 복잡도는
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)
XGBoost, CatBoost, LightGBM 등의 그레이디언트 부스팅이 있음
모두 그레이디언트 부스팅에 특화되어 있고, 다양한 기능을 제공함
블렌더를 훈련하려면 먼저 블렌딩 훈련 세트를 만들어야 함
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)