05-10. 회귀 실습 - 캐글 주택 가격: 고급 회귀 기법

Park Jong Hun·2021년 3월 2일
0

위키북스의 파이썬 머신러닝 완벽 가이드 책을 토대로 공부한 내용입니다.


1. 주택 가격 dataset


캐글의 주택 가격 dataset은 미국 아이오와 주의 Ames 지방의 주택 가격 정보를 가지고 있다. 성능 평가는 자전거 대여 예측 예제와 동일한 RMSLE(Root Mean Square Log Error)를 기반으로 한다. 가격이 비싼 주택일수록 예측 결과 오류가 전체 오류에 미치는 비중이 높으므로 이를 상쇄하기 위해 오류 값을 로그 변환한 RMSLE를 이용한다.


2. 데이터 사전 처리(Preprocessing)


필요한 모듈과 데이터를 로딩하고 데이터를 확인해 보겠다.

import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline

house_df_org = pd.read_csv('house_price.csv')
house_df = house_df_org.copy()

print('데이터 세트의 Shape:', house_df.shape)
print('\n전체 feature 들의 type \n',house_df.dtypes.value_counts())
isnull_series = house_df.isnull().sum()
print('\nNull 컬럼과 그 건수:\n ', isnull_series[isnull_series > 0].sort_values(ascending=False))

[output]
dataset의 feature 타입은 숫자형은 물론 문자형도 많이 있다. Target을 제외하고 80개의 feature 중에서 43개가 문자형이며 나머지가 숫자형이다. 그리고 데이터 양에 비해 Null 값이 많은 feature도 있다. Null 값이 너무 많은 feature는 드롭할 것이고, Target 값의 분포를 확인해보겠다.

plt.title('Original Sale Price Histogram')
sns.distplot(house_df['SalePrice'])

[output]
중심이 왼쪽으로 치우친 형태로 정규분포에서 벗어나 있다. 따라서 로그 변환(Log Transformation)을 적용하여 다시 분포도를 확인해보겠다.

plt.title('Log Transformed Sale Price Histogram')
log_SalePrice = np.log1p(house_df['SalePrice'])
sns.distplot(log_SalePrice)

[output]
로그 변환 후 정규 분포 형태로 분포하는 것을 볼 수 있다. 이제 DataFrame에 반영할텐데 Id feature도 단순한 식별자이므로 삭제하고, LotFrontage는 Null 값이 259개로 많으나 평균값으로 대체하고 나머지 Null 값이 많지 않은 숫자형은 평균값으로 대체하겠다.

# SalePrice 로그 변환
original_SalePrice = house_df['SalePrice']
house_df['SalePrice'] = np.log1p(house_df['SalePrice'])

# Null 이 너무 많은 컬럼들과 불필요한 컬럼 삭제
house_df.drop(['Id','PoolQC' , 'MiscFeature', 'Alley', 'Fence','FireplaceQu'], axis=1 , inplace=True)
# Drop 하지 않는 숫자형 Null컬럼들은 평균값으로 대체
house_df.fillna(house_df.mean(),inplace=True)

# Null 값이 있는 피처명과 타입을 추출
null_column_count = house_df.isnull().sum()[house_df.isnull().sum() > 0]
print('## Null 피처의 Type :\n', house_df.dtypes[null_column_count.index])

[output]
문자형 feature를 제외하고는 Null 값이 없는 것을 볼 수 있다. 문자형 feature는 모두 one-hot 인코딩으로 변환하겠다. pandas의 get_dummies()를 이용하면 자동으로 one-hot 인코딩으로 변환해주는데 Null 값은 'None' column으로 대체해주기 때문에 별도로 Null 값을 대체하는 로직은 필요없다.

print('get_dummies() 수행 전 데이터 Shape:', house_df.shape)
house_df_ohe = pd.get_dummies(house_df)
print('get_dummies() 수행 후 데이터 Shape:', house_df_ohe.shape)

null_column_count = house_df_ohe.isnull().sum()[house_df_ohe.isnull().sum() > 0]
print('## Null 피처의 Type :\n', house_df_ohe.dtypes[null_column_count.index])

[output]
ont-hot 인코딩 후 feature가 75개에서 271개로 증가했으며, Null 값을 가진 feature는 이제 존재하지 않는다.


3. 선형 회귀 모델 학습/예측/평가


예측 평가 지표로 RMSLE를 사용할 것이다. 하지만 이미 target 값이 로그 변환되었다. 따라서 예측 결과 오류에 RMSE만 적용하면 RMSLE가 자동으로 측정된다. 이제 선형 회귀 모델을 학습하고 예측, 평가해보겠다.

def get_rmse(model):
    pred = model.predict(X_test)
    mse = mean_squared_error(y_test , pred)
    rmse = np.sqrt(mse)
    print('{0} 로그 변환된 RMSE: {1}'.format(model.__class__.__name__,np.round(rmse, 3)))
    return rmse

def get_rmses(models):
    rmses = [ ]
    for model in models:
        rmse = get_rmse(model)
        rmses.append(rmse)
    return rmses
   
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

y_target = house_df_ohe['SalePrice']
X_features = house_df_ohe.drop('SalePrice',axis=1, inplace=False)

X_train, X_test, y_train, y_test = train_test_split(X_features, y_target, test_size=0.2, random_state=156)

# LinearRegression, Ridge, Lasso 학습, 예측, 평가
lr_reg = LinearRegression()
lr_reg.fit(X_train, y_train)

ridge_reg = Ridge()
ridge_reg.fit(X_train, y_train)

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

models = [lr_reg, ridge_reg, lasso_reg]
get_rmses(models)

[output]
hyper parameter 튜닝이 되지 않아 라쏘 회귀의 성능이 비교적 떨어지는 결과가 나왔다. 우선 hyper parameter 튜닝을 하기 전에 feature 별로 회귀 계수를 시각화하여 모델별로 어떠한 feature의 회귀 계수로 구성되어 있는지 상위 10개, 하위 10개를 확인해보겠다.

def get_top_bottom_coef(model):
    # coef_ 속성을 기반으로 Series 객체를 생성. index는 컬럼명. 
    coef = pd.Series(model.coef_, index=X_features.columns)

    # + 상위 10개 , - 하위 10개 coefficient 추출하여 반환.
    coef_high = coef.sort_values(ascending=False).head(10)
    coef_low = coef.sort_values(ascending=False).tail(10)
    return coef_high, coef_low

def visualize_coefficient(models):
    # 3개 회귀 모델의 시각화를 위해 3개의 컬럼을 가지는 subplot 생성
    fig, axs = plt.subplots(figsize=(24,10),nrows=1, ncols=3)
    fig.tight_layout() 
    # 입력인자로 받은 list객체인 models에서 차례로 model을 추출하여 회귀 계수 시각화. 
    for i_num, model in enumerate(models):
        # 상위 10개, 하위 10개 회귀 계수를 구하고, 이를 판다스 concat으로 결합. 
        coef_high, coef_low = get_top_bottom_coef(model)
        coef_concat = pd.concat( [coef_high , coef_low] )
        # 순차적으로 ax subplot에 barchar로 표현. 한 화면에 표현하기 위해 tick label 위치와 font 크기 조정. 
        axs[i_num].set_title(model.__class__.__name__+' Coeffiecents', size=25)
        axs[i_num].tick_params(axis="y",direction="in", pad=-120)
        for label in (axs[i_num].get_xticklabels() + axs[i_num].get_yticklabels()):
            label.set_fontsize(22)
        sns.barplot(x=coef_concat.values, y=coef_concat.index , ax=axs[i_num])

# 앞 예제에서 학습한 lr_reg, ridge_reg, lasso_reg 모델의 회귀 계수 시각화.    
models = [lr_reg, ridge_reg, lasso_reg]
visualize_coefficient(models)

[output]
모델별 회귀 계수를 보면 OLS(Ordinary Least Squares) 기반의 LinearRegression과 Ridge의 경우는 회귀 계수가 유사한 형태로 분포되어 있지만, Lasso는 전체적으로 회귀 계수 값이 작으며, 그 중 YearBuilt이 가장 크고 다른 feature는 너무 작은 값을 가진다. 학습 데이터의 분할이 잘못되어 있을 수 있으니 5개의 CV로 평균 RMSE를 측정해보겠다.

from sklearn.model_selection import cross_val_score

def get_avg_rmse_cv(models):
    for model in models:
        # 분할하지 않고 전체 데이터로 cross_val_score( ) 수행. 모델별 CV RMSE값과 평균 RMSE 출력
        rmse_list = np.sqrt(-cross_val_score(model, X_features, y_target,
                                             scoring="neg_mean_squared_error", cv = 5))
        rmse_avg = np.mean(rmse_list)
        print('\n{0} CV RMSE 값 리스트: {1}'.format( model.__class__.__name__, np.round(rmse_list, 3)))
        print('{0} CV 평균 RMSE 값: {1}'.format( model.__class__.__name__, np.round(rmse_avg, 3)))

# 앞 예제에서 학습한 lr_reg, ridge_reg, lasso_reg 모델의 CV RMSE값 출력           
models = [lr_reg, ridge_reg, lasso_reg]
get_avg_rmse_cv(models)

[output]
여전히 라쏘 모델이 성능이 떨어진다. 이제 릿지와 라쏘의 alpha hyper parameter를 최적화하여 확인해보겠다.

from sklearn.model_selection import GridSearchCV

def print_best_params(model, params):
    grid_model = GridSearchCV(model, param_grid=params, 
                              scoring='neg_mean_squared_error', cv=5)
    grid_model.fit(X_features, y_target)
    rmse = np.sqrt(-1* grid_model.best_score_)
    print('{0} 5 CV 시 최적 평균 RMSE 값: {1}, 최적 alpha:{2}'.format(model.__class__.__name__,
                                        np.round(rmse, 4), grid_model.best_params_))
    return grid_model.best_estimator_

ridge_params = { 'alpha':[0.05, 0.1, 1, 5, 8, 10, 12, 15, 20] }
lasso_params = { 'alpha':[0.001, 0.005, 0.008, 0.05, 0.03, 0.1, 0.5, 1,5, 10] }
best_rige = print_best_params(ridge_reg, ridge_params)
best_lasso = print_best_params(lasso_reg, lasso_params)

[output]
hyper parameter를 최적화한 후 예측 성능이 많이 좋아졌다. 이 상태로 다시 모델들을 학습하고 모델별 회귀 계수를 다시 시각화해보겠다.

# 앞의 최적화 alpha값으로 학습데이터로 학습, 테스트 데이터로 예측 및 평가 수행. 
lr_reg = LinearRegression()
lr_reg.fit(X_train, y_train)
ridge_reg = Ridge(alpha=12)
ridge_reg.fit(X_train, y_train)
lasso_reg = Lasso(alpha=0.001)
lasso_reg.fit(X_train, y_train)

# 모든 모델의 RMSE 출력
models = [lr_reg, ridge_reg, lasso_reg]
get_rmses(models)

# 모든 모델의 회귀 계수 시각화 
models = [lr_reg, ridge_reg, lasso_reg]
visualize_coefficient(models)

[output]
결과를 보면 릿지와 라쏘 모델에서 비슷한 feature의 회귀 계수가 높은 값을 가지는 것을 볼 수 있다. 하지만 라쏘 모델의 경우는 릿지에 비해 동일한 feature라도 회귀 계수의 값이 상당히 작게 나온다.


4. 데이터 왜곡 완화 및 이상치 제거


지나치게 왜곡된 feature가 존재할 경우 회귀 예측 성능을 저하시킬 수 있다. Scipy 모듈의 skew() 함수를 이용해 column별로 dataset의 왜곡된 정도를 쉽게 추출할 수 있다. 일반적으로 skew() 함수의 반환값이 1이상일 경우 왜곡 정도가 높다고 판단하지만 상황에 따라 편차가 존재한다. 여기서는 1 이상의 왜곡 정도를 보이는 feature에 로그 변환을 적용하도록 하겠다. skew() 함수를 사용할 때 주의할 점으로는 one-hot 인코딩된 카테고리 숫자형 feature는 제외기켜야한다.

from scipy.stats import skew

# object가 아닌 숫자형 피쳐의 컬럼 index 객체 추출.
features_index = house_df.dtypes[house_df.dtypes != 'object'].index
# house_df에 컬럼 index를 [ ]로 입력하면 해당하는 컬럼 데이터 셋 반환. apply lambda로 skew( )호출 
skew_features = house_df[features_index].apply(lambda x : skew(x))
# skew 정도가 1 이상인 컬럼들만 추출. 
skew_features_top = skew_features[skew_features > 1]
house_df[skew_features_top.index] = np.log1p(house_df[skew_features_top.index])

# Skew가 높은 피처들을 로그 변환 했으므로 다시 원-핫 인코딩 적용 및 피처/타겟 데이터 셋 생성,
house_df_ohe = pd.get_dummies(house_df)
y_target = house_df_ohe['SalePrice']
X_features = house_df_ohe.drop('SalePrice',axis=1, inplace=False)
X_train, X_test, y_train, y_test = train_test_split(X_features, y_target, test_size=0.2, random_state=156)

# 피처들을 로그 변환 후 다시 최적 하이퍼 파라미터와 RMSE 출력
ridge_params = { 'alpha':[0.05, 0.1, 1, 5, 8, 10, 12, 15, 20] }
lasso_params = { 'alpha':[0.001, 0.005, 0.008, 0.05, 0.03, 0.1, 0.5, 1,5, 10] }
best_ridge = print_best_params(ridge_reg, ridge_params)
best_lasso = print_best_params(lasso_reg, lasso_params)

[output]
로그 변환 후에도 왜곡 정도를 확인해 보면 높은 왜곡 정도를 보이는 feature가 있지만 한번 더 로그 변환을 하더라도 개선하기는 어렵기 때문에 그대로 유지하고 학습한다. 두 모델 모두 RMSE가 향상된 것을 볼 수 있다. 이 모델들로 회귀 계수를 다시 시각화해보겠다.

# 앞의 최적화 alpha값으로 학습데이터로 학습, 테스트 데이터로 예측 및 평가 수행. 
lr_reg = LinearRegression()
lr_reg.fit(X_train, y_train)
ridge_reg = Ridge(alpha=10)
ridge_reg.fit(X_train, y_train)
lasso_reg = Lasso(alpha=0.001)
lasso_reg.fit(X_train, y_train)

# 모든 모델의 RMSE 출력
models = [lr_reg, ridge_reg, lasso_reg]
get_rmses(models)

# 모든 모델의 회귀 계수 시각화 
models = [lr_reg, ridge_reg, lasso_reg]
visualize_coefficient(models)

[output]
시각화 결과를 보면 3개의 모델 모두 GrLivArea라는 주거 공간 크기에 해당하는 feature의 회귀 계수가 가장 높게 나오는 것을 볼 수 있다. 주거 공간의 크기가 주택 가격에 미치는 영향이 당연히 가장 클 것이라는 상식의 결과가 도출된 것을 알 수 있다. 다음으로 이상치 데이터에 대해 분석할 것이다. 특히 예측에 많은 영향을 미치는 회귀 계수가 높은 feature의 이상치 데이터 처리가 중요하다. 가장 큰 회귀 계수를 가지는 GrLivArea feature의 데이터 분포를 시각화하여 확인해 보겠다.

plt.scatter(x = house_df_org['GrLivArea'], y = house_df_org['SalePrice'])
plt.ylabel('SalePrice', fontsize=15)
plt.xlabel('GrLivArea', fontsize=15)
plt.show()

[output]
일반적으로 주거 공간이 큰 집일수록 가격이 비싸기 때문에 GrLivArea feature는 SalePrice와 양의 상관 관계를 가지는 것을 직관적으로 알 수 있다. 하지만 위 이미지에서 오른쪽 아래의 2개의 데이터는 가장 큰 집임에도 불구하고 가격이 매우 낮다. 따라서 이 2개의 데이터를 이상치로 간주하고 삭제하겠다.

# GrLivArea와 SalePrice 모두 로그 변환되었으므로 이를 반영한 조건 생성. 
cond1 = house_df_ohe['GrLivArea'] > np.log1p(4000)
cond2 = house_df_ohe['SalePrice'] < np.log1p(500000)
outlier_index = house_df_ohe[cond1 & cond2].index

print('아웃라이어 레코드 index :', outlier_index.values)
print('아웃라이어 삭제 전 house_df_ohe shape:', house_df_ohe.shape)
# DataFrame의 index를 이용하여 아웃라이어 레코드 삭제. 
house_df_ohe.drop(outlier_index , axis=0, inplace=True)
print('아웃라이어 삭제 후 house_df_ohe shape:', house_df_ohe.shape)

[output]
전체 데이터가 바뀌었으니 학습 dataset과 테스트 dataset을 다시 생성한 후 릿지와 라쏘 모델을 다시 최적화하겠다.

y_target = house_df_ohe['SalePrice']
X_features = house_df_ohe.drop('SalePrice',axis=1, inplace=False)
X_train, X_test, y_train, y_test = train_test_split(X_features, y_target, test_size=0.2, random_state=156)

ridge_params = { 'alpha':[0.05, 0.1, 1, 5, 8, 10, 12, 15, 20] }
lasso_params = { 'alpha':[0.001, 0.005, 0.008, 0.05, 0.03, 0.1, 0.5, 1,5, 10] }
best_ridge = print_best_params(ridge_reg, ridge_params)
best_lasso = print_best_params(lasso_reg, lasso_params)

[output]
2개의 이상치 데이터만 삭제했음에도 예측 수치가 매우 상승하였다. hyper parameter의 튜닝을 아무리 해도 이 정도의 성능 향상은 어렵지만 GrLivArea 속성이 회귀 모델에서 차지하는 영향이 크기 때문에 이상치를 제거하는 것이 성능 향상에 큰 의미를 가지게 되었다. 개발에 있어서 ML 알고리즘을 적용하기 이전에 완벽한 전처리 작업을 할 수는 없다. 따라서 완벽하게 전처리를 수행하기 위해 시간을 쓰는 것보다 우선 대략적인 데이터 가공과 모델 최적화를 수행한 후 그 결과를 기반으로 다시 여러 가지 기법의 데이터 가공과 모델 최적화를 반복적으로 수행하는 것이 바람직한 ML 모델 생성 과정이다. 마지막으로 각 모델별로 RMSE과 회귀 계수를 시각화한 결과를 출력하겠다.

# 앞의 최적화 alpha값으로 학습데이터로 학습, 테스트 데이터로 예측 및 평가 수행. 
lr_reg = LinearRegression()
lr_reg.fit(X_train, y_train)
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)

# 모든 모델의 RMSE 출력
models = [lr_reg, ridge_reg, lasso_reg]
get_rmses(models)

# 모든 모델의 회귀 계수 시각화 
models = [lr_reg, ridge_reg, lasso_reg]
visualize_coefficient(models)

[output]


5. 회귀 트리 모델 학습/예측/평가


회귀 트리를 이용해 회귀 모델을 만들어 보겠다. XGBoost, LightGBM 순서로 학습, 예측을 수행해보겠다.

from xgboost import XGBRegressor

xgb_params = {'n_estimators':[1000]}
xgb_reg = XGBRegressor(n_estimators=1000, learning_rate=0.05, 
                       colsample_bytree=0.5, subsample=0.8)
best_xgb = print_best_params(xgb_reg, xgb_params)

[output]

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)

[output]
두 모델들의 feature 중요도를 시각화해보겠다.

# 모델의 중요도 상위 20개의 피처명과 그때의 중요도값을 Series로 반환.
def get_top_features(model):
    ftr_importances_values = model.feature_importances_
    ftr_importances = pd.Series(ftr_importances_values, index=X_features.columns  )
    ftr_top20 = ftr_importances.sort_values(ascending=False)[:20]
    return ftr_top20

def visualize_ftr_importances(models):
    # 2개 회귀 모델의 시각화를 위해 2개의 컬럼을 가지는 subplot 생성
    fig, axs = plt.subplots(figsize=(24,10),nrows=1, ncols=2)
    fig.tight_layout() 
    # 입력인자로 받은 list객체인 models에서 차례로 model을 추출하여 피처 중요도 시각화. 
    for i_num, model in enumerate(models):
        # 중요도 상위 20개의 피처명과 그때의 중요도값 추출 
        ftr_top20 = get_top_features(model)
        axs[i_num].set_title(model.__class__.__name__+' Feature Importances', size=25)
        #font 크기 조정.
        for label in (axs[i_num].get_xticklabels() + axs[i_num].get_yticklabels()):
            label.set_fontsize(22)
        sns.barplot(x=ftr_top20.values, y=ftr_top20.index , ax=axs[i_num])

# 앞 예제에서 print_best_params( )가 반환한 GridSearchCV로 최적화된 모델의 피처 중요도 시각화    
models = [best_xgb, best_lgbm]
visualize_ftr_importances(models)

[output]


6. 회귀 모델의 예측 결과 혼합을 통한 최종 예측


개별 회귀 모델의 예측 결과값을 혼합해 이를 기반으로 최종 회귀 값을 예측해보겠다. 기본적으로 예측 결과 혼합은 매우 간단하다. 두 모델의 예측값이 주어지면 두 모델의 예측값이 차지할 비율을 나눈 다음 합하여 최종 회귀 값으로 예측하는 것이다. 우선 릿지와 라쏘 모델을 서로 혼합해보겠다.

def get_rmse_pred(preds):
    for key in preds.keys():
        pred_value = preds[key]
        mse = mean_squared_error(y_test , pred_value)
        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}
#최종 혼합 모델, 개별모델의 RMSE 값 출력
get_rmse_pred(preds)

[output]
개별 모델보다 약간의 성능 개선이 있었다. 다음으로 XGBoost와 LightGBM을 혼합하여 결과를 살펴보겠다.

xgb_reg = XGBRegressor(n_estimators=1000, learning_rate=0.05, 
                       colsample_bytree=0.5, subsample=0.8)
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)
xgb_reg.fit(X_train, y_train)
lgbm_reg.fit(X_train, y_train)
xgb_pred = xgb_reg.predict(X_test)
lgbm_pred = lgbm_reg.predict(X_test)

pred = 0.5 * xgb_pred + 0.5 * lgbm_pred
preds = {'최종 혼합': pred,
         'XGBM': xgb_pred,
         'LGBM': lgbm_pred}
  
get_rmse_pred(preds)

[output]
이 혼합 모델 또한 개별 모델보다 성능이 조금 향상되었다.


7. 스태킹 앙상블 모델을 통한 회귀 예측


스태킬 모델을 회귀에 적용할 수 있다. 스태킹 모델의 핵심은 여러 개별 모델의 예측 데이터를 각각 스태킹 형태로 결합해 최종 메타 모델의 학습 dataset과 테스트 dataset을 만드는 것이다. 적용할 개별 모델은 릿지, 라쏘, XGBoost, LightGBM으로 4개이고, 메타 모델은 라쏘 모델을 사용하여 학습하고 예측을 수행해보겠다.

from sklearn.model_selection import KFold
from sklearn.metrics import mean_absolute_error

# 개별 기반 모델에서 최종 메타 모델이 사용할 학습 및 테스트용 데이터를 생성하기 위한 함수. 
def get_stacking_base_datasets(model, X_train_n, y_train_n, X_test_n, n_folds ):
    # 지정된 n_folds값으로 KFold 생성.
    kf = KFold(n_splits=n_folds, shuffle=False, random_state=0)
    #추후에 메타 모델이 사용할 학습 데이터 반환을 위한 넘파이 배열 초기화 
    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)
       
    # 폴드 세트 내에서 원본 테스트 데이터를 예측한 데이터를 평균하여 테스트 데이터로 생성 
    test_pred_mean = np.mean(test_pred, axis=1).reshape(-1,1)    
   
    #train_fold_pred는 최종 메타 모델이 사용하는 학습 데이터, test_pred_mean은 테스트 데이터
    return train_fold_pred , test_pred_mean

# get_stacking_base_datasets( )은 넘파이 ndarray를 인자로 사용하므로 DataFrame을 넘파이로 변환. 
X_train_n = X_train.values
X_test_n = X_test.values
y_train_n = y_train.values

# 각 개별 기반(Base)모델이 생성한 학습용/테스트용 데이터 반환. 
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)

# 개별 모델이 반환한 학습 및 테스트용 데이터 세트를 Stacking 형태로 결합.  
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)

#기반 모델의 예측값을 기반으로 새롭게 만들어진 학습 및 테스트용 데이터로 예측하고 RMSE 측정.
meta_model_lasso.fit(Stack_final_X_train, y_train)
final = meta_model_lasso.predict(Stack_final_X_test)
mse = mean_squared_error(y_test , final)
rmse = np.sqrt(mse)
print('스태킹 회귀 모델의 최종 RMSE 값은:', rmse)

[output]
최종적으로 스태킹 회귀 모델을 적용한 결과를 보면 현재까지 가장 좋은 성능 평가를 보여준다. 스태킹 모델은 분류뿐만 아니라 회귀에서 특히 효과적으로 사용될 수 있는 모델이다.

profile
NLP, AI, LLM, MLops

0개의 댓글