Developer's Development

3.2.11 [머신러닝] 앙상블 본문

데이터 분석과 머신러닝, 딥러닝/머신러닝

3.2.11 [머신러닝] 앙상블

mylee 2025. 8. 3. 14:27

앙상블 학습(Ensemble Learning)이란 여러 개의 모델(약한 학습기)을 결합하여 하나의 강력한 모델을 만드는 방법이다.

 

투표 기반 분류기 (Voting Classifier)

 

여러 분류기의 예측 결과를 기반으로 최종 결과를 결정한다.

즉, 서로 다른 여러 개의 머신 러닝 모델을 사용하여 최대 성능을 내도록 하는 방식이다.

 

  • 종류

👉🏻 다수결 투표(Hard Voting) : 각 분류기의 예측 중 다수결로 최종 클래스를 선택한다.

👉🏻 확률 평균 투표(Soft Voting) : 각 분류기가 예측한 확률값의 평균을 계산하여 가장 높은 확률의 클래스를 선택한다.

 

  • 위스콘신 유방암 데이터셋

유방암의 악성과 양성을 분류하기 위해 자주 사용되는 데이터셋

(의학적인 이미지를 바탕으로 유방암 종양의 특징을 수치화한 데이터)

- 목적 : 유방암 종양이 악성인지, 양성인지 분류

- 샘플 수 : 569개

- 특징 수 : 30개

- 타겟 : 0(악성) 또는 1(양성)

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 출력 경고문 제거 (무시)
import warnings
warnings.filterwarnings('ignore')
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

data = load_breast_cancer()
# print(data.DESCR)

df = pd.DataFrame(data.data, columns=data.feature_names)
df['target'] = data.target

X = data.data
y = data.target

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

 

👉🏻 hard voting

from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import VotingClassifier

knn_clf = KNeighborsClassifier()
lr_clf = LogisticRegression()
dt_clf = DecisionTreeClassifier(random_state=0)  # 학습시마다 동일한 결과 유지하기 위해 random_state 고정 

# Voting 모델 생성
voting_clf = VotingClassifier(
    estimators=[
        ('knn_clf', knn_clf),
        ('lr_clf', lr_clf),
        ('dt_clf', dt_clf)
    ],
    voting='hard'
)

# 학습
voting_clf.fit(X_train, y_train)

# 평가
print("학습 점수", voting_clf.score(X_train, y_train))  # 학습 점수 0.9647887323943662
print("평가 점수", voting_clf.score(X_test, y_test))  # 평가 점수 0.958041958041958
# hard voting 작동 원리 == 다수결
start, end = 40, 50

# voting 모델 예측값
voting_pred = voting_clf.predict(X_test[start:end])
print(f'앙상블 예측값: {voting_pred}')

for classifier in [knn_clf, lr_clf, dt_clf]:
    # 개별 학습 및 예측
    classifier.fit(X_train, y_train)
    pred = classifier.predict(X_test[start:end])
    score = classifier.score(X_test, y_test)

    class_name = classifier.__class__.__name__  # 클래스 이름 속성
    print(f"{class_name} 개별 정확도: {score}")
    print(f"{class_name} 예측값: {pred}")
    
"""
앙상블 예측값: [0 1 0 1 0 0 1 1 1 0]
KNeighborsClassifier 개별 정확도: 0.9370629370629371
KNeighborsClassifier 예측값: [0 1 0 1 0 0 1 1 1 0]
LogisticRegression 개별 정확도: 0.9440559440559441
LogisticRegression 예측값: [0 1 0 1 0 0 1 1 1 0]
DecisionTreeClassifier 개별 정확도: 0.9020979020979021
DecisionTreeClassifier 예측값: [0 1 0 1 0 0 1 0 1 0]  # -3 인덱스의 예측값은 다 다름
"""

 

👉🏻 soft voting

voting_clf = VotingClassifier(
    estimators=[
        ('knn_clf', knn_clf),
        ('lr_clf', lr_clf),
        ('dt_clf', dt_clf)
    ],
    voting='soft'  # 이 부분만 hard와 다름!
)

# 학습
voting_clf.fit(X_train, y_train)

# 평가
print("학습 점수", voting_clf.score(X_train, y_train))  # 학습 점수 0.9859154929577465
print("평가 점수", voting_clf.score(X_test, y_test))  # 평가 점수 0.951048951048951
# soft voting 작동 원리 == 각 예측기의 확률값 평균
start, end = 40, 50

# voting 모델 예측 확률
voting_pred = voting_clf.predict_proba(X_test[start:end])
print(f'앙상블 예측 확률: {voting_pred}')

# 평균값을 구하기 위해 합계를 저장할 배열
averages = np.full_like(voting_pred, 0)

for classifier in [knn_clf, lr_clf, dt_clf]:
    # 개별 학습 및 예측
    classifier.fit(X_train, y_train)
    pred = classifier.predict_proba(X_test[start:end])
    score = classifier.score(X_test, y_test)

	# 평균값을 구하기 위한 합계
    averages += pred

    class_name = classifier.__class__.__name__
    # print(f"{class_name} 개별 정확도: {score}")
    # print(f"{class_name} 예측 확률: {pred}")

print("각 모델별 예측 확률의 평균:", averages / 3)
print(np.array_equal(voting_pred, averages / 3))  # True

 

 

베깅 (Bootstrap Aggregating)과 페이스팅 (Pasting)

 

앙상블 학습의 데이터 샘플링 방법으로, 같은 머신 러닝 모델을 여러 개 사용하여 최대 성능을 내도록 하는 방식이다.

 

  • 베깅과 페이스팅의 차이

👉🏻 베깅 (Bagging) : 데이터 샘플을 복원 추출하여 학습 데이터로 사용한다.

👉🏻 페이스팅 (Pasting) : 데이터 샘플을 비복원 추출하여 학습 데이터로 사용한다.

 

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

👉🏻 랜덤 패치 (Random Patches) : 샘플과 특성 모두를 랜덤하게 선택하여 학습에 사용한다.

👉🏻 랜덤 서브스페이스 (Random Subspaces) : 전체 데이터를 사용하지만, 특성의 일부만 무작위로 선택하여 학습한다.

→ 이 기법은 과대적합 방지모델 다양성 증가에 유리하다.

 

 

랜덤 포레스트 (Random Forest)

 

여러 개의 결정 트리를 생성하여 평균(또는 다수결)으로 결과를 도축하는 앙상블 학습 방법이다.

각 트리는 랜덤 패치 또는 랜덤 서브스페이스 방식을 사용하여 학습한다.

 

  • 장점

여러 트리의 결과를 평균화하여 최대적합 위험을 줄인다.

특성의 중요도를 계산할 수 있어 해석 가능성이 높다.

비선형 데이터나 복잡한 데이터에서도 강력한 성능을 보인다.

 

  • Bagging

다양한 모델을 결합하여 예측 성능을 향상시키는 방법

- Bootstrap 방식의 샘플링 : 각 estimate 마다 훈련 데이터를 뽑을 때, 중복 값을 허용하는 방식

- 분류 모델의 경우, 각 tree(estimator)의 예측값을 다수결(hard voting) 결정

- 회귀 모델의 경우, 각 tree(estimator)의 예측값을 평균내어 결정

- 기본적으로 100개의 tree 사용

# load_breast_cancer와 train_test_split 까지는 모두 동일
from sklearn.ensemble import RandomForestClassifier

# 모델 생성
rf_clf = RandomForestClassifier(
    n_estimators=101,
    max_depth=7,
    random_state=0
)

# 학습
rf_clf.fit(X_train, y_train)

# 평가
print(f"학습 점수: {rf_clf.score(X_train, y_train)}")  # 학습 점수: 0.9976525821596244
print(f"테스트 점수: {rf_clf.score(X_test, y_test)}")  # 테스트 점수: 0.9790209790209791

# 101개의 DecisionTree 확인
# print(rf_clf.estimators_)

# 101개의 DecisionTree가 사용한 샘플데이터 확인
# print(rf_clf.estimators_samples_)

# Bootstrap 방식의 샘플링 확인
for i, sample_idxs in enumerate(rf_clf.estimators_samples_):
    print(f'{i}번째 DecisionTree의 샘플 인덱스: {sorted(sample_idxs)}')
    
# 특성 중요도 확인
feat_imptc_ser = pd.Series(rf_clf.feature_importances_, index=data.feature_names).sort_values(ascending=False)

# 특성 중요도 시각화
plt.figure(figsize=(8, 6))
sns.barplot(
    x=feat_imptc_ser,
    y=feat_imptc_ser.index,
    hue=feat_imptc_ser.index
)
plt.xlabel('feature importance')
plt.ylabel('feature')
plt.show()

 

 

부스팅 (Boosting)

 

약한 학습기를 순차적으로 학습시켜 강력한 학습기를 만드는 방법이다.

각 단계에서 이전 모델의 오류를 보완하도록 설계된다.

 

다양한 모델을 결합하여 예측 성능을 향상시키는 방법

- 깊이가 얕은 결정트리를 사용해 이진 트리의 오차를 보장하는 방식

- 순차적으로 경사하강법을 사용해 이진 트리의 오차를 줄여나감

  - 분류모델에서는 손실함수 Logloss를 사용해 오차를 줄임

  - 회귀모델에서는 손실함수 MSE를 사용해 오차를 줄임

- Boosting 계열은 일반적으로 결정트리 개수를 늘려도 과적합에 강함

- 대표적인 알고리즘(모델) : GradientBoosting, HistGradientBoosting, XGBoost(DMLC), LightGBM(MS), CatBoost

 

  • 대표적인 부스팅 알고리즘

AdaBoost : 이전 모델이 잘못 분류한 샘플에 더 많은 가중치를 부여한다.

Gradient Boosting : 모델의 예측 오차(잔차)에 기반하여 다음 모델을 학습한다.

 

  • Gradient Boosting 구현
from sklearn.tree import DecisionTreeRegressor

class SimpleGradientBoostingClassifier:
    def __init__(self, n_estimators=100, learning_rate=0.1, max_depth=3):
        self.n_estimators = n_estimators
        self.learning_rate = learning_rate
        self.max_depth = max_depth
        self.initial_log_odds = 0   # 초기 예측값
        self.trees = []             # estimator 모음 배열

    def log_oods(self, p):
        # 확률값 -> 로직 변환 (0~1 사이의 값을 펼쳐 -무한대~+무한대 사이의 값으로 보정)
        return np.log(p / (1 - p))

    def sigmoid(self, z):
        # z값 -> 0-1 사이의 확률 값 변환
        return 1 / (1 + np.exp(-z))

    def fit(self, X, y):
    	# 초기 예측값 설정
        y_mean = np.mean(y)
        self.initial_log_odds = self.log_oods(y_mean)
        y_pred_log_odds = np.full_like(y, self.initial_log_odds, dtype=np.float64)

		# 각 모델 생성 및 학습
        for _ in range(self.n_estimators):
        	# 현재 상태에서 예측된 확률값 계산
            y_pred_proba = self.sigmoid(y_pred_log_odds)
            
            # 잔차 계산
            residual = y - y_pred_proba

			# 결정트리 생성 및 잔차를 라벨로 학습
            tree = DecisionTreeRegressor(max_depth=self.max_depth)
            tree.fit(X, residual)
            self.trees.append(tree)
            
            # 예측값 업데이트 (예측값의 점진적 개선)
            y_pred_log_odds += self.learning_rate * tree.predict(X)

    def predict(self, X):
        return (self.predict_proba(X) >= 0.5).astype(int)

    def predict_proba(self, X):
        y_pred_log_odds = np.full((X.shape[0],), self.initial_log_odds)

		# 트리의 예측값을 누적하여 최종 로짓값 계산
        for tree in self.trees:
            y_pred_log_odds += self.learning_rate * tree.predict(X)

        return self.sigmoid(y_pred_log_odds)
# SimpleGradientBoostingClassifier로 유방암 데이터 예측
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, random_state=0)

# 모델 생성
simple_gb_clf = SimpleGradientBoostingClassifier(
    n_estimators=100,
    learning_rate=0.01,
    max_depth=3
)

# 학습
simple_gb_clf.fit(X_train, y_train)

# 예측 및 정확도 평가
y_pred_train = simple_gb_clf.predict(X_train)
y_pred_test = simple_gb_clf.predict(X_test)
print(f"학습 정확도: {accuracy_score(y_train, y_pred_train)}")  # 학습 정확도: 0.9507042253521126
print(f"평가 정확도: {accuracy_score(y_test, y_pred_test)}")  # 평가 정확도: 0.9370629370629371

 

  • GradientBoosting
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, random_state=0)

gb_clf = GradientBoostingClassifier(
    n_estimators=101,
    learning_rate=0.01,
    max_depth=3
)

gb_clf.fit(X_train, y_train)

y_pred_train = gb_clf.predict(X_train)
y_pred_test = gb_clf.predict(X_test)

print(f"학습 정확도: { accuracy_score(y_train, y_pred_train) }")  # 학습 정확도: 0.9882629107981221
print(f"예측 정확도: { accuracy_score(y_test, y_pred_test) }")  # 예측 정확도: 0.958041958041958

 

  • HistGradientBoosting

고성능 GradientBoosting 모델로 대규모 데이터셋 처리에 적합

Histogram 기반으로 256개의 구간으로 나누어 처리 병합하는 방식

결측치가 있어도 전처리가 필요 없음

LightGBM의 영향을 받아 만들어진 scikit-learn의 모델

from sklearn.ensemble import HistGradientBoostingClassifier

hist_gb_clf = HistGradientBoostingClassifier(
    learning_rate=0.1,
    max_depth=3,
    max_bins=255,        # 255개의 구간으로 나누어 처리 (1개는 결측치 전용)
    early_stopping=True, # 반복 중 '일정 횟수' 이상 성능 향상이 없으면 학습 종료
    n_iter_no_change=5   # '일정 횟수' 지정 (기본값: 10)
)

hist_gb_clf.fit(X_train, y_train)

y_pred_train = hist_gb_clf.predict(X_train)
y_pred_test = hist_gb_clf.predict(X_test)
print(f'학습 정확도: { accuracy_score(y_train, y_pred_train) }')  # 학습 정확도: 0.9953051643192489
print(f'예측 정확도: { accuracy_score(y_test, y_pred_test) }')  # 예측 정확도: 0.958041958041958
# permutation_importance == 특성 중요도
from sklearn.inspection import permutation_importance

result = permutation_importance(
    hist_gb_clf,
    X_train,
    y_train,
    n_repeats=5,
    random_state=0
)

# importances_mean: 평균 중요도
# importances_std: 중요도 표준편차
# importances: 특성 중요도
result.importances

 

  • 회귀 모델

sklearn.datasets.load_diabetes 데이터셋

: 당뇨병 환자의 진단 자료를 바탕으로 만들어진 회귀용 데이터셋

 

👉 데이터셋 설명

- 목적 : 당뇨병 진행 정도(1년 후)를 예측

- 데이터 수 : 442개의 샘플

- 특성 수 : 10개의 특성 (10개의 입력 변수)

- 타겟 : 연속형 값, 당뇨병의 1년 후 진행 상황을 나타냄

from sklearn.datasets import load_diabetes
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.metrics import mean_squared_error, r2_score

# 1. 데이터 로드
datasets = load_diabetes()

# 2. 데이터 분리
X_train, X_test, y_train, y_test = \
train_test_split(datasets.data, datasets.target, random_state=0)

# 3. HistGradientBoostingRegressor 모델 생성
hist_gb_reg = HistGradientBoostingRegressor(
    max_iter=100,
    max_depth=3,
    learning_rate=0.05,
    random_state=0,
    l2_regularization=0.5,
    min_samples_leaf=5
)

# 4~6. 학습 > 예측 > mse, r2_score 계산
hist_gb_reg.fit(X_train, y_train)

y_pred_train = hist_gb_reg.predict(X_train)
y_pred_test = hist_gb_reg.predict(X_test)

print(f'학습 MSE: {mean_squared_error(y_train, y_pred_train)}')
print(f'학습 R2: {r2_score(y_train, y_pred_train)}')
print(f'평가 MSE: {mean_squared_error(y_test, y_pred_test)}')
print(f'평가 R2: {r2_score(y_test, y_pred_test)}')
# 교차검증
from sklearn.model_selection import GridSearchCV

hist_gb_reg = HistGradientBoostingRegressor(random_state=0)
param_grid = {
    'max_iter': [100, 200, 300],
    'max_depth': [1, 3, 5],
    'learning_rate': [0.01, 0.05, 0.1],
    'min_samples_leaf': [10, 20, 30],
    'l2_regularization': [0.0, 0.1, 1.0],
    'max_bins': [255, 127]
}

grid_search = GridSearchCV(hist_gb_reg, param_grid, cv=3, 
                           scoring='neg_mean_squared_error')
grid_search.fit(X_train, y_train)

# param_grid로 전달해준 하이퍼 파라미터 중 최고의 성능을 내는 하이퍼 파라미터 조합
grid_search.best_params_
# 최고의 성능을 내는 하이퍼 파라미터 조합으로 학습된 모델
best_hist_gb_reg = grid_search.best_estimator_

y_pred_train = best_hist_gb_reg.predict(X_train)
y_pred_test = best_hist_gb_reg.predict(X_test)

print(f'학습 MSE: {mean_squared_error(y_train, y_pred_train)}')
print(f'학습 R2: {r2_score(y_train, y_pred_train)}')
print(f'평가 MSE: {mean_squared_error(y_test, y_pred_test)}')
print(f'평가 R2: {r2_score(y_test, y_pred_test)}')

 

 

[참고] Boosting model

 

  • GBM (Gradient Boosting Machine)

가중치 부여를 통해 오류를 개선해 나가면서 학습하는 방식이다.

 

  • XGBoost

트리 기반의 앙상블 학습에서 가장 인기 있는 모델 중 하나이다.

https://xgboost.readthedocs.io/en/stable/index.html

 

👉 핵심 파라미터

1. learning_rate: 각 트리의 기여도를 조절하는 학습률로, 값이 작을수록 모델의 복잡도가 낮아지지만 더 많은 트리를 필요로 한다.

2. n_estimators: 트리의 개수를 의미하며, 많을수록 복잡한 모델이 된다.

3. max_depth: 각 트리의 최대 깊이로, 트리가 너무 깊으면 과적합될 수 있다.

4. objective: 손실 함수의 종류로, 회귀 문제의 경우 'reg:squarederror', 분류 문제의 경우 'binary:logistic' 등을 사용한다.

pip install xgboost
# 아이리스 데이터셋 분류 활용
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
from xgboost import XGBClassifier   # XGBoost는 scikit-learn과 비슷한 API 제공

iris_data = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, random_state=0)

xgb_clf = XGBClassifier(
    n_estimators=100,
    max_depth=3,
    learning_rate=0.1,
    random_state=0
)
xgb_clf.fit(X_train, y_train)

y_pred_train = xgb_clf.predict(X_train)
y_pred_test = xgb_clf.predict(X_test)

print(accuracy_score(y_train, y_pred_train))
print(accuracy_score(y_test, y_pred_test))

print(confusion_matrix(y_test, y_pred_test))
print(classification_report(y_test, y_pred_test))
# 유방암 데이터셋 이진분류 활용
from sklearn.datasets import load_breast_cancer

data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, random_state=0)

xgb_clf = XGBClassifier(
    n_estimators=100,
    max_depth=3,
    learning_rate=0.1,
    random_state=0
)
xgb_clf.fit(X_train, y_train)

y_pred_train = xgb_clf.predict(X_train)
y_pred_test = xgb_clf.predict(X_test)

print(accuracy_score(y_train, y_pred_train))
print(accuracy_score(y_test, y_pred_test))

print(confusion_matrix(y_test, y_pred_test))
print(classification_report(y_test, y_pred_test))
# 조기종료 적용 -> 과적합 방지/훈련시간 단축
xgb_clf = XGBClassifier(
    n_estimators=500,
    learning_rate=0.1,
    max_depth=3,
    random_state=0,
    early_stopping_rounds=10,   # 성능이 일정 횟수 이상 향상되지 않으면 조기종료
    eval_metric='logloss'
)

X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train, random_state=0)
eval_set = [(X_tr, y_tr), (X_val, y_val)]

xgb_clf.fit(X_tr, y_tr, eval_set=eval_set, verbose=True)
# eval_set: 검증 데이터
# verbose: 학습 중 평과 결과 출력 여부
# 훈련 과정 시각화 (학습 곡선)
import matplotlib.pyplot as plt

result = xgb_clf.evals_result()
train_loss = result['validation_0']['logloss']
val_loss = result['validation_1']['logloss']

plt.plot(train_loss, label='train')
plt.plot(val_loss, label='validation')
plt.legend()
plt.xlabel('nth round')
plt.ylabel('logloss')
plt.show()

# 특성 중요도 시각화
from xgboost import plot_importance

fig, ax = plt.subplots(figsize=(10,12))
plot_importance(xgb_clf, ax=ax)
plt.show()

 

  • LightGBM

학습시간이 오래 걸리는 XGBoost의 단점을 보완하기 위해서 나온 모델이다.

LightGBM은 XGBoost보다 학습에 걸리는 시간이 훨씬 적고, 메모리 사용량도 적다는 장점이 있다.

 

👉 핵심 파라미터

num_leaves: 한 트리에서 사용할 수 있는 리프의 최대 수를 지정한다. 모델의 복잡도를 결정하며, 값을 크게 하면 과적합 가능성이 높아진다.

max_depth: 트리의 최대 깊이를 제한한다. num_leaves와 함께 과적합을 방지하기 위해 조정된다.

learning_rate: 각 단계에서 트리의 기여도를 조정하는 학습률이다. 작은 값을 설정하면 모델 학습이 느리지만 성능이 더 좋을 수 있다.

n_estimators: 생성할 트리의 수를 지정한다. 보통 learning_rate가 작을수록 큰 값을 설정한다.

pip install lightgbm
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from lightgbm import LGBMClassifier

data = load_breast_cancer()

# 학습-테스트 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, random_state=0)

# 학습데이터 > 학습-검증 데이터 분리
X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train, random_state=0, test_size=0.1)

lgbm = LGBMClassifier(
    n_estimators=400,
    learning_rate=0.7,
    early_stopping_rounds=100,
    verbose=1
)

eval_set = [(X_tr, y_tr), (X_val, y_val)]
lgbm.fit(X_tr, y_tr, eval_set=eval_set)

lgbm.score(X_train, y_train), lgbm.score(X_test, y_test)

 

  • [참고] 언더 샘플링과 오버 샘플링

언더 샘플링 : 편차가 큰 레이블에서 많은 레이블을 가진 데이터셋을 적은 레이블을 가진 데이터셋 수준으로 감소시키는 것이다.

오버 샘플링 : 편차가 큰 레이블에서 적은 레이블을 가진 데이터셋을 많은 레이블을 가진 데이터셋 수준으로 증식시키는 것이다.

→ 보통 데이터를 줄일 일은 거의 없기 때문에 오버 샘플링을 주로 하게 된다. 특히, 분류문제에서는 데이터를 늘려주는 것이 필요하고, 데이터를 실제로 맞춰서 하는 것이 이상적인 학습이다.

 

 

스태킹 (Stacking)

 

여러 학습기의 예측값을 기반으로 메타 모델(Meta Model)을 학습시키는 방법이다.

각 모델의 강점을 결합하여 성능을 극대화할 수 있다는 장점이 있다.