멀티캠퍼스 데이터분석 5월 14일 수업 내용 — Bagging으로 호텔 예약 취소 예측, BaggingRegressor로 차량 가격 예측, AdaBoost 분류/회귀 실습
이론
1. 앙상블이란?
2. 배깅 (Bagging) — 이론 & 매개변수
3. 부스팅 (Boosting) — 이론 & 대표 알고리즘
4. AdaBoost — 이론 & 매개변수
실습
단일 결정 트리(DecisionTree)의 단점을 극복하기 위해 여러 머신러닝 모델을 연결하여 더 강력한 모델을 만드는 과정입니다.
여러 예측 모형 생성 → 예측 모형 조합 → 하나의 최종 모형 완성
대표 기법: 배깅, 부스팅, 랜덤 포레스트
주어진 자료를 모집단으로 간주하고 부트스트랩(중복 허용 샘플링) 으로 여러 개의 데이터를 생성한 뒤 각각 모델을 학습시켜 결합합니다.
| 매개변수 | 기본값 | 설명 |
|---|---|---|
estimator | None | 기본 모델 설정 |
n_estimators | 10 | 생성할 모델 수 |
max_samples | 1.0 | 각 모델이 사용할 샘플 비율 |
max_features | 1.0 | 각 모델이 사용할 피처 비율 |
bootstrap | True | 중복 샘플링 허용 여부 |
bootstrap_features | False | 중복 컬럼 허용 여부 |
oob_score | False | OOB 데이터로 성능 평가 여부 |
n_jobs | None | 병렬 처리 CPU 수 (-1: 전체 사용) |
| 속성 | 설명 |
|---|---|
estimators_ | 학습된 모델 리스트 |
estimators_samples_ | 각 모델이 학습한 샘플 인덱스 |
estimators_features_ | 각 모델이 학습한 컬럼 인덱스 |
oob_score_ | OOB 데이터 기반 정확도(분류) / R²(회귀) |
💡 OOB(Out-Of-Bag) — 부트스트랩에서 선택되지 않은 데이터로, 별도의 test 셋 없이 성능 평가가 가능합니다.
모델을 순차적으로 학습하면서 이전 모델이 틀린 데이터에 가중치를 높여 다음 모델에서 집중 학습합니다.
| 내용 | |
|---|---|
| 장점 | 편향 감소 → 예측 확률 향상, 단순 모델들로 강력한 모델 생성 |
| 단점 | 순차 학습으로 병렬화 어려움(속도 느림), 과적합 위험, 파라미터 튜닝 중요 |
| 알고리즘 | 설명 |
|---|---|
| AdaBoost | 오차가 큰 샘플에 높은 가중치 부여, 가중치 합산으로 최종 결정 |
| Gradient Boosting (GBM) | 이전 모델의 잔차를 예측하는 모델 추가, 손실 함수 직접 최적화 |
| XGBoost | GBM 개선, 규제+병렬 학습 지원, 대표적인 부스팅 알고리즘 (별도 설치 필요) |
| LightGBM | 히스토그램 기반 학습, 대용량 데이터에 특화, XGBoost보다 속도 우수 |
| CatBoost | 범주형 데이터 자동 처리, 튜닝 비교적 간단 |
| 매개변수 | 기본값 | 설명 |
|---|---|---|
estimator | DecisionTreeClassifier(max_depth=1) | 기본 모델 |
n_estimators | 50 | 모델 개수 (많을수록 느림, 과적합 위험) |
learning_rate | 1.0 | 각 단계별 기여도 (작을수록 모델 수 증가 필요) |
algorithm | 'SAMME.R' | 분류 알고리즘 (SAMME.R: 확률 기반 / SAMME: 점수 기반) |
💡 learning_rate & n_estimators 상호 보완 관계
- learning_rate ↓ → 각 단계 영향 줄어듦 → n_estimators ↑ 필요
- 데이터가 크고 복잡 → learning_rate ↓ + n_estimators ↑
- 데이터가 작음 → learning_rate ↑ + n_estimators ↓
| 속성 | 설명 |
|---|---|
estimators_ | 학습된 모델 리스트 |
estimator_weights_ | 각 모델의 가중치 |
estimator_errors_ | 각 단계별 오차율 |
feature_importances_ | 피처별 중요도 (가중치 합) |
loss 매개변수| 값 | 설명 |
|---|---|
'linear' | 기본 선형 업데이트 |
'square' | 제곱 — 큰 오차에 더 민감 |
'exponential' | 지수 — 매우 큰 오차에 강한 패널티 |
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.metrics import f1_score
hotel = pd.read_csv('../data/hotel_bookings.csv')
| 컬럼 | 설명 |
|---|---|
| is_canceled | 예약 취소 여부 (1: 취소, 0: 체크인) ← target |
| deposit_type | 보증금 유형 |
| lead_time | 예약 시차 (예약일~체크인일) |
| stays_in_weekend_nights | 주말 숙박 일수 |
| stays_in_week_nights | 평일 숙박 일수 |
| is_repeated_guest | 재방문 고객 여부 |
| previous_cancellation | 과거 취소 횟수 |
| previous_bookings_not_canceled | 과거 정상 투숙 횟수 |
| booking_changes | 예약 변경 횟수 |
| days_in_waiting_list | 대기 명단에 있었던 횟수 |
| adr | 1박당 평균 객실 요금 |
# adr 음수 데이터 제거
hotel = hotel.loc[~(hotel['adr'] < 0)]
# 결측치 처리
hotel['lead_time'] = hotel['lead_time'].fillna(hotel['lead_time'].mean())
hotel['adr'] = hotel['adr'].fillna(hotel['adr'].mean())
hotel['is_repeated_guest'] = hotel['is_repeated_guest'].fillna(
hotel['is_repeated_guest'].value_counts().index[0] # 최빈값
)
# deposit_type 더미 변수 생성
df = pd.get_dummies(hotel, columns=['deposit_type'], drop_first=True)
x = df.drop('is_canceled', axis=1)
y = df['is_canceled']
X_train, X_test, y_train, y_test = train_test_split(
x, y, test_size=0.3, stratify=y, random_state=42
)
base_model = DecisionTreeClassifier(class_weight='balanced', max_depth=3)
model = BaggingClassifier(base_model, n_estimators=500)
model.fit(X_train, y_train)
pred = model.predict(X_test)
print(round(f1_score(y_test, pred), 4))
from imblearn.over_sampling import RandomOverSampler, SMOTE
# 2:1 비율로 오버샘플링
oversample = RandomOverSampler(sampling_strategy=0.5)
x_over, y_over = oversample.fit_resample(x, y)
X_train, X_test, y_train, y_test = train_test_split(
x_over, y_over, test_size=0.3, random_state=42, stratify=y_over
)
clf = BaggingClassifier(
estimator = DecisionTreeClassifier(),
n_estimators = 100,
max_samples = 0.8 # 각 모델이 80% 샘플만 사용
)
clf.fit(X_train, y_train)
pred = clf.predict(X_test)
print(round(f1_score(y_test, pred), 4))
smote = SMOTE()
x_sm, y_sm = smote.fit_resample(x, y)
X_train, X_test, y_train, y_test = train_test_split(
x_sm, y_sm, test_size=0.2, random_state=42, stratify=y_sm
)
clf2 = BaggingClassifier(estimator=DecisionTreeClassifier(), n_estimators=100)
clf2.fit(X_train, y_train)
pred2 = clf2.predict(X_test)
print(round(f1_score(y_test, pred2), 4))
clf_oob = BaggingClassifier(
estimator = DecisionTreeClassifier(),
n_estimators = 100,
oob_score = True
)
clf_oob.fit(x_over, y_over)
print(clf_oob.oob_score_)
from sklearn.ensemble import BaggingRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
car = pd.read_csv('../data/CarPrice_Assignment.csv')
# 타입별 분리
df_str = car.select_dtypes('object')
df_int = car.select_dtypes('number')
# doornumber — 문자 → 숫자
df_str['doornumber'] = df_str['doornumber'].map(lambda x: 2 if x == 'two' else 4)
# cylindernumber — 문자 → 숫자
df_str['cylindernumber'] = df_str['cylindernumber'].map({
'four': 4, 'six': 6, 'five': 5, 'three': 3,
'twelve': 12, 'two': 2, 'eight': 8
})
# 공백 기준으로 분리 후 brand, modelName 컬럼 생성
vals = df_str['CarName'].map(lambda x: x.split()).values
df_str[['brand', 'modelName']] = vals
# CarName, modelName 제거
df_str.drop(['CarName', 'modelName'], axis=1, inplace=True)
# object 타입 컬럼 더미 변수 생성
cols = df_str.select_dtypes('object').columns
df_str2 = pd.get_dummies(df_str, columns=cols, drop_first=True)
df_int.drop('car_ID', axis=1, inplace=True)
# 열 결합
df = pd.concat([df_str2, df_int], axis=1)
x = df.drop('price', axis=1)
y = df['price']
X_train, X_test, y_train, y_test = train_test_split(
x, y, test_size=0.3, random_state=42
)
reg = BaggingRegressor(
estimator = DecisionTreeRegressor(),
n_estimators = 100,
oob_score = True
)
reg.fit(X_train, y_train)
print('OOB Score:', reg.oob_score_)
pred = reg.predict(X_test)
print('MAE :', round(mean_absolute_error(y_test, pred), 2))
print('MSE :', round(mean_squared_error(y_test, pred), 2))
print('R2 :', round(r2_score(y_test, pred), 2))
import numpy as np
from sklearn.ensemble import AdaBoostClassifier
from sklearn.metrics import classification_report
body = pd.read_csv('../data/bodyPerformance.csv')
body['gender'] = body['gender'].map({'M': 0, 'F': 1})
body['class'] = body['class'].map({'A': 1, 'B': 2, 'C': 3, 'D': 4})
x = body.drop('class', axis=1)
y = body['class']
X_train, X_test, y_train, y_test = train_test_split(
x, y, test_size=0.2, random_state=42
)
# 기본 AdaBoost
clf = AdaBoostClassifier()
clf.fit(X_train, y_train)
print(classification_report(y_test, clf.predict(X_test)))
# n_estimators 증가 + learning_rate 감소
clf2 = AdaBoostClassifier(n_estimators=500, learning_rate=0.1)
clf2.fit(X_train, y_train)
print(classification_report(y_test, clf2.predict(X_test)))
# 기본 모델을 DecisionTree(max_depth=4)로 변경
from sklearn.tree import DecisionTreeClassifier
clf3 = AdaBoostClassifier(
estimator = DecisionTreeClassifier(max_depth=4),
n_estimators = 500,
learning_rate= 0.1
)
clf3.fit(X_train, y_train)
print(classification_report(y_test, clf3.predict(X_test)))
💡
classification_report()— 클래스별 precision, recall, f1-score, support를 한 번에 출력합니다.
importances = clf3.feature_importances_
feature_df = pd.DataFrame(
zip(x.columns, importances),
columns=['feature_name', 'importance']
)
feature_df.sort_values('importance', ascending=False).head()
import matplotlib.pyplot as plt
from sklearn.ensemble import AdaBoostRegressor
from sklearn.metrics import mean_absolute_error, r2_score
car = pd.read_csv('../data/CarPrice_Assignment.csv')
df = car.select_dtypes('number').drop('car_ID', axis=1)
x = df.drop('price', axis=1)
y = df['price']
X_train, X_test, y_train, y_test = train_test_split(
x, y, test_size=0.3, random_state=42
)
# 기본 모델
reg = AdaBoostRegressor()
reg.fit(X_train, y_train)
pred = reg.predict(X_test)
print('MAE :', round(mean_absolute_error(y_test, pred), 2))
print('R2 :', round(r2_score(y_test, pred), 2))
# 하이퍼파라미터 조정
reg2 = AdaBoostRegressor(n_estimators=500, learning_rate=0.1)
reg2.fit(X_train, y_train)
pred2 = reg2.predict(X_test)
print('MAE :', round(mean_absolute_error(y_test, pred2), 2))
print('R2 :', round(r2_score(y_test, pred2), 2))
mae_list = []
for stage_pred in reg2.staged_predict(X_test):
# 각 단계별 예측값으로 MAE 계산
mae_list.append(mean_absolute_error(y_test, stage_pred))
plt.figure(figsize=(16, 8))
plt.plot(mae_list)
plt.xlabel('Count')
plt.ylabel('MAE')
plt.show()
💡
staged_predict(X)— 각 단계별 누적 모델의 예측값을 순차적으로 반환합니다.
학습 진행에 따라 MAE가 어떻게 변화하는지 학습 곡선으로 확인할 수 있습니다.
importance_df = pd.DataFrame(
zip(x.columns, reg2.feature_importances_),
columns=['feature_name', 'importance']
)
importance_df.sort_values('importance', ascending=False, inplace=True)
plt.figure(figsize=(16, 8))
plt.barh(importance_df['feature_name'].tail(), importance_df['importance'].tail())
plt.show()
| 개념 | 설명 |
|---|---|
| 앙상블 | 여러 모델을 결합해 강력한 모델 생성 |
| 배깅 | 병렬 학습 → 분산 감소, 과적합 완화 |
| 부스팅 | 순차 학습 → 편향 감소, 이전 오차 집중 학습 |
| OOB Score | 부트스트랩에서 선택 안 된 데이터로 성능 평가 |
oob_score=True | 별도 Test 셋 없이 일반화 성능 확인 |
n_estimators | 생성할 모델 수 |
max_samples | 각 모델이 사용할 샘플 비율 |
learning_rate | 각 단계 기여도 — n_estimators와 상호 보완 |
feature_importances_ | 피처별 중요도 (합계 = 1) |
staged_predict() | 단계별 누적 예측값 반환 → 학습 곡선 확인 |
classification_report() | 클래스별 precision/recall/f1 한번에 출력 |
select_dtypes('object') | 특정 타입의 컬럼만 선택 |
pd.concat([df1, df2], axis=1) | 열 방향 결합 |