Programming

캐글 주택가격 예측 : 고급 회귀 기법 (3편) : 회귀 트리 모델 · 예측 혼합 · 스태킹(Stacking) 앙상블

Lucas.Kim 2026. 1. 9. 14:55
반응형

1. 회귀 트리 기반 모델 개요

앞선 1·2편에서는 선형 회귀 + 규제(Ridge/Lasso)
로그 변환, 왜도 보정, 이상치 제거를 통해 성능을 지속적으로 개선했습니다.

이번 3편에서는
👉 비선형 관계를 학습할 수 있는 트리 기반 회귀 모델
👉 여러 모델의 예측을 결합하는 앙상블 전략
👉 최종적으로 Stacking 모델까지 적용합니다.

2. XGBoost / LightGBM 회귀 모델

트리 기반 모델을 사용하기 전 컬럼명 정제

# XGBoost는 특수문자가 포함된 컬럼명을 허용하지 않음
# One-Hot Encoding 과정에서 생성된 [, ], < 문자를 제거/치환
from xgboost import XGBRegressor

X_features.columns = (
    X_features.columns
    .str.replace('[', '_', regex=False)
    .str.replace(']', '_', regex=False)
    .str.replace('<', '_', regex=False)
)

# 혹시 중복된 컬럼명이 있다면 제거
X_features = X_features.loc[:, ~X_features.columns.duplicated()]

왜 필요한가?

  • XGBoost 내부는 C++ 기반 → 특수문자 컬럼명에서 에러 발생
  • 모델 학습 이전 반드시 정제 필요

XGBoost 회귀 모델 + GridSearchCV

from xgboost import XGBRegressor
from sklearn.model_selection import GridSearchCV

# XGBoost 회귀 모델 정의
xgb_reg = XGBRegressor(
    n_estimators=1000,            # 트리 개수
    learning_rate=0.05,           # 학습률
    colsample_bytree=0.5,         # 피처 샘플링 비율
    subsample=0.8,                # 데이터 샘플링 비율
    objective='reg:squarederror', # 회귀용 손실 함수
    random_state=156
)

# 튜닝할 파라미터
xgb_params = {'n_estimators': [1000]}

# GridSearchCV를 이용해 교차검증 기반 성능 평가
best_xgb = print_best_params(xgb_reg, xgb_params)

# 결과 5 폴드시 최적 평균 RMSE : 0.1174

GridSearchCV 공통 함수 (재정의)

def print_best_params(model, params):
    grid_model = GridSearchCV(
        model,
        param_grid=params,
        scoring='neg_mean_squared_error',
        cv=5
    )
    
    # LightGBM/XGBoost 안정성을 위해 numpy array로 변환
    grid_model.fit(X_features.values, y_target.values)

    rmse = np.sqrt(-grid_model.best_score_)
    print(f'5 폴드시 최적 평균 RMSE : {np.round(rmse,4)}')
    print(f'최적 파라미터 : {grid_model.best_params_}')

    return grid_model.best_estimator_

LightGBM 회귀 모델

from lightgbm import LGBMRegressor

lgbm_params = {'n_estimators':[1000]}

lgbm_reg = LGBMRegressor(
    n_estimators=1000,
    learning_rate=0.05,
    num_leaves=4,
    subsample=0.6,
    colsample_bytree=0.4,
    reg_lambda=10,
    n_jobs=-1
)

best_lgbm = print_best_params(lgbm_reg, lgbm_params)

# 결과 : 5 폴드시 최적 평균 RMSE : 0.1159

3. 트리 모델 피처 중요도 분석 

# 상위 20개 중요 피처 추출 함수
def get_top_features(model):
    ftr_importances_values = model.feature_importances_
    ftr_importances = pd.Series(ftr_importances_values, index=X_features.columns)
    return ftr_importances.sort_values(ascending=False)[:20]

# XGBoost / LightGBM 피처 중요도 시각화
def visualize_ftr_importances(models):
    fig, axs = plt.subplots(figsize=(24,10), nrows=1, ncols=2)
    
    for i_num, model in enumerate(models):
        ftr_top20 = get_top_features(model)
        axs[i_num].set_title(model.__class__.__name__+' Feature Importances', size=25)
        sns.barplot(x=ftr_top20.values, y=ftr_top20.index, ax=axs[i_num])

models = [best_xgb, best_lgbm]
visualize_ftr_importances(models)

트리 모델은 선형 회귀와 달리 비선형 상호작용을 자동 학습

4. 예측값 혼합 (Blending)

# RMSE 계산 함수
def get_rmse_pred(preds):
    for key in preds.keys():
        mse = mean_squared_error(y_test , preds[key])
        rmse = np.sqrt(mse)
        print('{0} 모델의 RMSE: {1}'.format(key, rmse))

# 개별 모델 학습
ridge_reg = Ridge(alpha=8)
ridge_reg.fit(X_train, y_train)

lasso_reg = Lasso(alpha=0.001)
lasso_reg.fit(X_train, y_train)

# 예측
ridge_pred = ridge_reg.predict(X_test)
lasso_pred = lasso_reg.predict(X_test)

# 예측값 가중 평균
pred = 0.4 * ridge_pred + 0.6 * lasso_pred

preds = {
    '최종 혼합': pred,
    'Ridge': ridge_pred,
    'Lasso': lasso_pred
}

get_rmse_pred(preds)


# 결과 : 최종 혼합 RMSE : 0.0993

5. 스태킹(Stacking) 앙상블

스태킹 개념

  • 기반 모델(Base Model) 의 예측값을
  • 메타 모델(Meta Model) 이 다시 학습

스태킹용 데이터 생성 함수

from sklearn.model_selection import KFold

def get_stacking_base_datasets(model, X_train_n, y_train_n, X_test_n, n_folds ):
    kf = KFold(n_splits=n_folds, shuffle=False)
    
    train_fold_pred = np.zeros((X_train_n.shape[0], 1))
    test_pred = np.zeros((X_test_n.shape[0], n_folds))
    
    for folder_counter, (train_index, valid_index) in enumerate(kf.split(X_train_n)):
        X_tr = X_train_n[train_index]
        y_tr = y_train_n[train_index]
        X_te = X_train_n[valid_index]

        model.fit(X_tr, y_tr)
        train_fold_pred[valid_index, :] = model.predict(X_te).reshape(-1,1)
        test_pred[:, folder_counter] = model.predict(X_test_n)
    
    return train_fold_pred, np.mean(test_pred, axis=1).reshape(-1,1)

스태킹 데이터 구성

X_train_n = X_train.values
X_test_n = X_test.values
y_train_n = y_train.values

ridge_train, ridge_test = get_stacking_base_datasets(ridge_reg, X_train_n, y_train_n, X_test_n, 5)
lasso_train, lasso_test = get_stacking_base_datasets(lasso_reg, X_train_n, y_train_n, X_test_n, 5)
xgb_train, xgb_test = get_stacking_base_datasets(xgb_reg, X_train_n, y_train_n, X_test_n, 5)
lgbm_train, lgbm_test = get_stacking_base_datasets(lgbm_reg, X_train_n, y_train_n, X_test_n, 5)

# 기반 모델 예측값 결합
Stack_final_X_train = np.concatenate(
    (ridge_train, lasso_train, xgb_train, lgbm_train), axis=1
)

Stack_final_X_test = np.concatenate(
    (ridge_test, lasso_test, xgb_test, lgbm_test), axis=1
)

메타 모델 학습 및 최종 평가

meta_model_lasso = Lasso(alpha=0.0005)

meta_model_lasso.fit(Stack_final_X_train, y_train)
final = meta_model_lasso.predict(Stack_final_X_test)

rmse = np.sqrt(mean_squared_error(y_test, final))
print('스태킹 회귀 모델의 최종 RMSE 값은:', rmse)

# RMSE : 0.0972

정리 (3편 요약)

단계 RMSE
기본 선형회귀 ~0.16
로그 + 튜닝 ~0.13
이상치 제거 ~0.10
예측 혼합 ~0.099
스태킹 앙상블 0.097
반응형