-
주택 가격: 고급 회귀 기법 실습머신러닝 2024. 2. 2. 12:28
필요 라이브러리¶
In [1]:import warnings warnings.filterwarnings('ignore') import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt %matplotlib inline from sklearn.linear_model import LinearRegression, Ridge, Lasso from sklearn.model_selection import train_test_split, cross_val_score from sklearn.model_selection import GridSearchCV, KFold from sklearn.metrics import mean_squared_error, mean_absolute_error from scipy.stats import skew from xgboost import XGBRegressor from lightgbm import LGBMRegressor from IPython.core.interactiveshell import InteractiveShell InteractiveShell.ast_node_interactivity = 'all'
데이터 전처리¶
In [2]:house_df_org = pd.read_csv('./datasets/house_price.csv') house_df = house_df_org.copy() house_df.sample(3, random_state=0)
Out[2]:Id MSSubClass MSZoning LotFrontage LotArea Street Alley LotShape LandContour Utilities ... PoolArea PoolQC Fence MiscFeature MiscVal MoSold YrSold SaleType SaleCondition SalePrice 529 530 20 RL NaN 32668 Pave NaN IR1 Lvl AllPub ... 0 NaN NaN NaN 0 3 2007 WD Alloca 200624 491 492 50 RL 79.0 9490 Pave NaN Reg Lvl AllPub ... 0 NaN MnPrv NaN 0 8 2006 WD Normal 133000 459 460 50 RL NaN 7015 Pave NaN IR1 Bnk AllPub ... 0 NaN NaN NaN 0 7 2009 WD Normal 110000 3 rows × 81 columns
여기서 Target 값은 맨 마지막 칼럼인 SalePrice
In [3]:print('데이터 세트의 Shape:', house_df.shape) print('\n전체 피처의 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))
데이터 세트의 Shape: (1460, 81) 전체 피처의 type object 43 int64 35 float64 3 Name: count, dtype: int64 Null 칼럼과 그 건수: PoolQC 1453 MiscFeature 1406 Alley 1369 Fence 1179 MasVnrType 872 FireplaceQu 690 LotFrontage 259 GarageType 81 GarageYrBlt 81 GarageFinish 81 GarageQual 81 GarageCond 81 BsmtFinType2 38 BsmtExposure 38 BsmtFinType1 37 BsmtCond 37 BsmtQual 37 MasVnrArea 8 Electrical 1 dtype: int64
In [4]:# 회귀 모델 적용 전, 타깃 값의 분포도가 정규 분포인지 확인 plt.title('Original Sale Price Histogram') plt.xticks(rotation=45) sns.histplot(house_df['SalePrice'], kde=True) plt.show()
Out[4]:Text(0.5, 1.0, 'Original Sale Price Histogram')
Out[4]:(array([0. , 0.2, 0.4, 0.6, 0.8, 1. ]), [Text(0.0, 0, '0.0'), Text(0.2, 0, '0.2'), Text(0.4, 0, '0.4'), Text(0.6000000000000001, 0, '0.6'), Text(0.8, 0, '0.8'), Text(1.0, 0, '1.0')])
Out[4]:<Axes: title={'center': 'Original Sale Price Histogram'}, xlabel='SalePrice', ylabel='Count'>
In [5]:# 결괏값이 왼쪽에 치우쳐져 있어 로그 변환 적용 plt.title('Log Transformed Sale Price Histogram') log_SalePrice = np.log1p(house_df['SalePrice']) sns.histplot(log_SalePrice, kde=True) plt.show()
Out[5]:Text(0.5, 1.0, 'Log Transformed Sale Price Histogram')
Out[5]:<Axes: title={'center': 'Log Transformed Sale Price Histogram'}, xlabel='SalePrice', ylabel='Count'>
분포도를 살펴보니 정규 분포 형태로 결괏값이 분포함을 확인했으므로, SalePrice에 로그 변환한 뒤 DataFrame에 반영
In [6]:# 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) # 드롭하지 않은 숫자형 Null 칼럼은 평균값으로 대체 numeric_cols = house_df.select_dtypes(include=np.number) house_df.fillna(numeric_cols.mean(), inplace=True) # Null 값이 있는 피처명과 타입 추출 - Null 값 처리가 아직 안 된 피처명이 있는지 확인 null_column_count = house_df.isnull().sum()[house_df.isnull().sum() > 0] print('## Null 피처의 Type:\n', house_df.dtypes[null_column_count.index])
## Null 피처의 Type: MasVnrType object BsmtQual object BsmtCond object BsmtExposure object BsmtFinType1 object BsmtFinType2 object Electrical object GarageType object GarageFinish object GarageQual object GarageCond object dtype: object
남아 있는 문자형 피처는 모두 원-핫 인코딩으로 변환, 원-핫 인코딩을 적용하면 칼럼이 증가
In [7]: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])
get_dummies() 수행 전 데이터 Shape: (1460, 75) get_dummies() 수행 이후 데이터 Shape: (1460, 270) ## Null 피처의 Type: Series([], dtype: object)
- 원-핫 인코딩 후 피처가 75개에서 270개로 증가
- Null 값을 가진 피처 모두 처리되었다
선형 회귀 모델 학습 / 예측 / 평가¶
In [8]:def get_rmse(model): pred = model.predict(X_test) mse = mean_squared_error(y_test, pred) rmse = np.sqrt(mse) print(model.__class__.__name__, '로그 변환된 RMSE:', np.round(rmse, 3)) return rmse def get_rmses(models): rmses = [] for model in models: rmse = get_rmse(model) rmses.append(rmse) return rmses
get_rmse(model)은 단일 모델의 RMSE 값을, get_rmses(models)는 get_rmse()를 이용해 여러 모델의 RMSE 값을 반환
In [9]:y_target = house_df_ohe['SalePrice'] X_features = house_df_ohe.drop('SalePrice', axis=1) 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)
Out[9]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.LinearRegression()
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.LinearRegression()
Out[9]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.Ridge()
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.Ridge()
Out[9]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.Lasso()
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.Lasso()
LinearRegression 로그 변환된 RMSE: 0.132 Ridge 로그 변환된 RMSE: 0.127 Lasso 로그 변환된 RMSE: 0.176
Out[9]:[0.13183184688250485, 0.12740582836266195, 0.17628250556471395]
- 라쏘 회귀의 경우 회귀 성능이 타 회귀 방식보다 많이 떨어지는 결과(숫자가 작을수록 성능이 좋은 것)
- 라쏘의 경우 최적 하이퍼 파라미터 튜닝이 필요해 보인다
모델 튜닝¶
In [10]:def get_top_bottom_coef(model, n=10): # coef_ 속성을 기반으로 Series 객체를 생성, index는 칼럼명 coef = pd.Series(model.coef_, index=X_features.columns) # + 상위 10개, - 하위 10개의 회귀 계수를 추출해 반환 coef_high = coef.sort_values(ascending=False).head(n) coef_low = coef.sort_values(ascending=False).tail(n) return coef_high, coef_low
In [11]: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개 회귀 계수를 구하고, 이를 결합 coef_high, coef_low = get_top_bottom_coef(model) coef_concat = pd.concat([coef_high, coef_low]) # ax subplot에 barchar로 표현 axs[i_num].set_title(model.__class__.__name__+'Coefficients', 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)
- 모델별 회귀 계수를 보면 LinearRegression과 Ridge의 경우는 회귀 계수가 유사한 형태로 분포되어 있다
- Lasso의 경우 전체적으로 회귀 계수 값이 매우 작고, YearBuilt 피처를 제외한 다른 피처의 회귀 계수는 너무 작다
- 라쏘의 경우 다른 두 개의 모델과 다른 회귀 계수 형태를 보이고 있다
- -> 데이터 분할에 문제가 있었는지 확인하기 위해 데이터를 train_test_split()으로 분할하지 않고, 전체 데이터 세트인 X_features와 y_target을 5개 교차 검증 폴드 세트로 분할해 평균 RMSE 측정
In [12]: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))) models = [ridge_reg, lasso_reg] get_avg_rmse_cv(models)
Ridge CV RMSE 값 리스트: [0.117 0.154 0.142 0.117 0.189] Ridge CV 평균 RMSE 값: 0.144 Lasso CV RMSE 값 리스트: [0.161 0.204 0.177 0.181 0.265] Lasso CV 평균 RMSE 값: 0.198
- 5개의 폴드로 학습한 후 평가해도 여전히 릿지 모델보다 성능이 떨어진다
- 릿지와 라쏘 모델에 대해 alpha 하이퍼 파라미터를 변화시키면서 최적 값 도출
In [13]: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)
Ridge 5 CV 시 최적 평균 RMSE 값: 0.1418, 최적 alpha: {'alpha': 12} Lasso 5 CV 시 최적 평균 RMSE 값: 0.142, 최적 alpha: {'alpha': 0.001}
- Lasso 모델의 경우, alpha 값 최적화 이후 예측 성능이 많이 좋아졌다
In [14]:# 앞의 최적화 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) # 모든 모델의 회귀 계수 시각화 visualize_coefficient(models)
Out[14]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.LinearRegression()
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.LinearRegression()
Out[14]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.Ridge(alpha=12)
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.Ridge(alpha=12)
Out[14]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.Lasso(alpha=0.001)
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.Lasso(alpha=0.001)
LinearRegression 로그 변환된 RMSE: 0.132 Ridge 로그 변환된 RMSE: 0.124 Lasso 로그 변환된 RMSE: 0.12
Out[14]:[0.13183184688250485, 0.12386974257131543, 0.11995999858899732]
- alpha 값 최적화 후 테스트 데이터 세트의 예측 성능이 좋아졌고, 모델별 회귀 계수고 많이 달라졌다
- 라쏘 모델의 회귀 계수가 나머지 두 개 모델과 많은 차이가 있었지만, 이번에는 릿지와 라쏘 모델에서 비슷한 피처의 회귀 계수가 높게 나타났다
- -> 하지만 라쏘 모델의 경우 릿지에 비해 동일한 피처라도 회귀 계수의 값이 상당히 작아, 데이터를 추가적으로 가공해 모델 튜닝을 더 진행해 볼 것
데이터 왜곡 완화 및 이상치 제거¶
In [15]:# object 가 아닌 숫자형 피처의 칼럼 index 객체 추출 features_index = house_df.dtypes[house_df.dtypes != 'object'].index # house_df에 칼럼 index를 []로 입력하면 해당하는 칼럼 데이터 세트 반환, apply lambda로 skew() 호출 # 사이파이의 stats 모듈의 skew() 함수를 이용하면 칼럼의 데이터 세트의 왜곡된 정도를 쉽게 추출 가능 skew_features = house_df[features_index].apply(lambda x : skew(x)) # skew(왜곡) 정도가 1 이상인 칼럼만 추출 skew_features_top = skew_features[skew_features > 1] print(skew_features_top.sort_values(ascending=False))
MiscVal 24.451640 PoolArea 14.813135 LotArea 12.195142 3SsnPorch 10.293752 LowQualFinSF 9.002080 KitchenAbvGr 4.483784 BsmtFinSF2 4.250888 ScreenPorch 4.117977 BsmtHalfBath 4.099186 EnclosedPorch 3.086696 MasVnrArea 2.673661 LotFrontage 2.382499 OpenPorchSF 2.361912 BsmtFinSF1 1.683771 WoodDeckSF 1.539792 TotalBsmtSF 1.522688 MSSubClass 1.406210 1stFlrSF 1.375342 GrLivArea 1.365156 dtype: float64
In [16]:house_df[skew_features_top.index] = np.log1p(house_df[skew_features_top.index])
- 로그 변환 후 피처들의 왜곡 정보를 다시 확인해 보면 여전히 높은 왜곡 정도를 가진 피처가 있지만, 더 이상 로그 변환을 하더라도 개선이 어렵기 때문에 유지
In [17]:# 왜곡 정도가 높은 피처를 로그 변환했으므로 다시 원-핫 인코딩 적용, 피처 / 타겟 데이터 세트 생성 house_df_ohe = pd.get_dummies(house_df) y_target = house_df_ohe['SalePrice'] X_features = house_df_ohe.drop('SalePrice', axis=1) 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_rige = print_best_params(ridge_reg, ridge_params) best_lasso = print_best_params(lasso_reg, lasso_params)
Ridge 5 CV 시 최적 평균 RMSE 값: 0.1275, 최적 alpha: {'alpha': 10} Lasso 5 CV 시 최적 평균 RMSE 값: 0.1252, 최적 alpha: {'alpha': 0.001}
- 릿지 모델의 경우 최적 alpha 값이 12 > 10으로 변경됐다
- 두 모델 모두 피처의 로그 변환 이전과 비교해 5 폴드 교차 검증의 평균 RMSE 값이 향상됐다
- 릿지 모델 5 폴드 교차 검증의 평균 RMSE 값: 0.1418 > 0.1275
- 라쏘 모델 5 폴드 교차 검증의 평균 RMSE 값: 0.142 > 0.1252
In [18]:# 앞의 최적화 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) models = [lr_reg, ridge_reg, lasso_reg] get_rmses(models) # 모든 모델의 회귀 계수 시각화 visualize_coefficient(models)
Out[18]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.LinearRegression()
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.LinearRegression()
Out[18]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.Ridge(alpha=10)
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.Ridge(alpha=10)
Out[18]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.Lasso(alpha=0.001)
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.Lasso(alpha=0.001)
LinearRegression 로그 변환된 RMSE: 0.128 Ridge 로그 변환된 RMSE: 0.122 Lasso 로그 변환된 RMSE: 0.119
Out[18]:[0.12814976686433627, 0.1224955872818429, 0.11925537715945182]
세 모델 모두 GrLivArea(주거 공간 크기)가 회귀 계수가 가장 높은 피처가 됐다
In [19]:plt.scatter(x = house_df_org['GrLivArea'], y = house_df_org['SalePrice']) plt.ylabel('SalePrice', fontsize=15) plt.xlabel('GrLivArea', fontsize=15) plt.show()
Out[19]:<matplotlib.collections.PathCollection at 0x1c4a2780150>
Out[19]:Text(0, 0.5, 'SalePrice')
Out[19]:Text(0.5, 0, 'GrLivArea')
주거 공간이 큰 집일수록 가격이 비싸기 때문에 두 피처는 양의 상관도가 매우 높음을 알 수 있으나, 우측 하단 2개의 데이터는 일반적인 SalePrice와 GrLivArea 관계에서 너무 어긋나 있다
두 데이터의 GrLivArea가 가장 큰 데도 가격이 매우 낮다
-> GrLivArea가 4000평방피트 이상임에도 가격이 500,000 달러 이하인 데이터는 모두 이상치로 간주 후 삭제
In [20]:# GrLivArea와 SalePrice 모두 로그 변환됐으므로 이를 반영한 조건 생성 cond1 = house_df_ohe['GrLivArea'] > np.log1p(4000) cond2 = house_df_ohe['SalePrice'] < np.log1p(500000) outliner_index = house_df_ohe[cond1 & cond2].index print('이상치 레코드 index:', outliner_index.values) print('이상치 삭제 전 house_df_ohe shape:', house_df_ohe.shape) # DataFrame의 인덱스를 이용해 이상치 레코드 삭제 house_df_ohe.drop(outliner_index, axis=0, inplace=True) print('이상치 삭제 후 house_df_ohe shape:', house_df_ohe.shape)
이상치 레코드 index: [ 523 1298] 이상치 삭제 전 house_df_ohe shape: (1460, 270) 이상치 삭제 후 house_df_ohe shape: (1458, 270)
In [21]:y_target = house_df_ohe['SalePrice'] X_features = house_df_ohe.drop('SalePrice', axis=1) 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_rige = print_best_params(ridge_reg, ridge_params) best_lasso = print_best_params(lasso_reg, lasso_params)
Ridge 5 CV 시 최적 평균 RMSE 값: 0.1125, 최적 alpha: {'alpha': 8} Lasso 5 CV 시 최적 평균 RMSE 값: 0.1122, 최적 alpha: {'alpha': 0.001}
- 릿지 모델의 경우 최적 alpha 값이 10 > 8로 변경됐다
- 두 개의 이상치 데이터 제거를 통해 두 모델 모두 예측 수치가 매우 크게 향상됐다
- 릿지 모델 5 폴드 교차 검증의 평균 RMSE 값: 0.1275 > 0.1125
- 라쏘 모델 5 폴드 교차 검증의 평균 RMSE 값: 0.1252 > 0.1122
In [22]:# 앞의 최적화 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) models = [lr_reg, ridge_reg, lasso_reg] get_rmses(models) # 모든 모델의 회귀 계수 시각화 visualize_coefficient(models)
Out[22]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.LinearRegression()
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.LinearRegression()
Out[22]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.Ridge(alpha=8)
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.Ridge(alpha=8)
Out[22]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.Lasso(alpha=0.001)
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.Lasso(alpha=0.001)
LinearRegression 로그 변환된 RMSE: 0.129 Ridge 로그 변환된 RMSE: 0.103 Lasso 로그 변환된 RMSE: 0.1
Out[22]:[0.12865360466046374, 0.10340697165289345, 0.10024171179335353]
회귀 트리 모델 학습 / 예측 / 평가¶
In [23]:# XGBoost 회귀 트리 적용 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)
XGBRegressor 5 CV 시 최적 평균 RMSE 값: 0.1174, 최적 alpha: {'n_estimators': 1000}
In [24]:# LightGBM 회귀 트리 적용 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)
LGBMRegressor 5 CV 시 최적 평균 RMSE 값: 0.1179, 최적 alpha: {'n_estimators': 1000}
In [25]:# 모델의 중요도 상위 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)
회귀 모델의 예측 결과 혼합을 통한 최종 예측¶
In [26]: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)
Out[26]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.Ridge(alpha=8)
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.Ridge(alpha=8)
Out[26]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.Lasso(alpha=0.001)
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.Lasso(alpha=0.001)
최종 혼합 모델의 RMSE: 0.10006075517615198 Ridge 모델의 RMSE: 0.10340697165289345 Lasso 모델의 RMSE: 0.10024171179335353
최종 혼합 모델의 RMSE가 개별 모델보다 성능 면에서 약간 개선되었다
릿지 모델 예측값에 0.4, 라쏘 모델 예측값에 0.6을 둔 것은 두 개 중 성능이 조금 더 좋은 쪽에 가중치를 둔 것
In [27]: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)
Out[27]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.XGBRegressor(base_score=None, booster=None, callbacks=None, colsample_bylevel=None, colsample_bynode=None, colsample_bytree=0.5, early_stopping_rounds=None, enable_categorical=False, eval_metric=None, feature_types=None, gamma=None, gpu_id=None, grow_policy=None, importance_type=None, interaction_constraints=None, learning_rate=0.05, max_bin=None, max_cat_threshold=None, max_cat_to_onehot=None, max_delta_step=None, max_depth=None, max_leaves=None, min_child_weight=None, missing=nan, monotone_constraints=None, n_estimators=1000, n_jobs=None, num_parallel_tree=None, predictor=None, random_state=None, ...)
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.XGBRegressor(base_score=None, booster=None, callbacks=None, colsample_bylevel=None, colsample_bynode=None, colsample_bytree=0.5, early_stopping_rounds=None, enable_categorical=False, eval_metric=None, feature_types=None, gamma=None, gpu_id=None, grow_policy=None, importance_type=None, interaction_constraints=None, learning_rate=0.05, max_bin=None, max_cat_threshold=None, max_cat_to_onehot=None, max_delta_step=None, max_depth=None, max_leaves=None, min_child_weight=None, missing=nan, monotone_constraints=None, n_estimators=1000, n_jobs=None, num_parallel_tree=None, predictor=None, random_state=None, ...)
Out[27]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.LGBMRegressor(colsample_bytree=0.4, learning_rate=0.05, n_estimators=1000, num_leaves=4, reg_lambda=10, subsample=0.6)
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.LGBMRegressor(colsample_bytree=0.4, learning_rate=0.05, n_estimators=1000, num_leaves=4, reg_lambda=10, subsample=0.6)
최종 혼합 모델의 RMSE: 0.10230054833466655 XGBM 모델의 RMSE: 0.10682829901857346 LGBM 모델의 RMSE: 0.10363891833477148
XGBoost와 LightGBM의 혼합 모델의 RMSE가 개별 모델의 RMSE보다 조금 향상되었다
스태킹 앙상블 모델을 통한 회귀 예측¶
In [28]:# 개별 기반 모델에서 최종 메타 모델이 사용할 학습 및 테스트용 데이터를 생성하기 위한 함수 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) # 추후에 메타 모델이 사용할 학습 데이터 반환을 위한 넘파이 배열 초기화 train_fold_pred = np.zeros((X_train_n.shape[0], 1)) test_pred = np.zeros((X_test_n.shape[0], n_folds)) print(model.__class__.__name__, 'model 시작') for folder_counter, (train_index, valid_index) in enumerate(kf.split(X_train_n)): # 입력된 학습 데이터에서 기반 모델이 학습 / 예측할 폴드 데이터 세트 추출 print('\t 폴드 세트: ', folder_counter, '시작') 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
In [29]:# 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)
Ridge model 시작 폴드 세트: 0 시작 폴드 세트: 1 시작 폴드 세트: 2 시작 폴드 세트: 3 시작 폴드 세트: 4 시작 Lasso model 시작 폴드 세트: 0 시작 폴드 세트: 1 시작 폴드 세트: 2 시작 폴드 세트: 3 시작 폴드 세트: 4 시작 XGBRegressor model 시작 폴드 세트: 0 시작 폴드 세트: 1 시작 폴드 세트: 2 시작 폴드 세트: 3 시작 폴드 세트: 4 시작 LGBMRegressor model 시작 폴드 세트: 0 시작 폴드 세트: 1 시작 폴드 세트: 2 시작 폴드 세트: 3 시작 폴드 세트: 4 시작
In [30]:# Lasso 모델의 최적화 수행 lasso_params = {'alpha':[0.0005, 0.001, 0.005, 0.008, 0.05, 0.03, 0.1, 0.5, 1, 5, 10]} best_lasso = print_best_params(lasso_reg, lasso_params)
Lasso 5 CV 시 최적 평균 RMSE 값: 0.11, 최적 alpha: {'alpha': 0.0005}
In [31]:# 개별 모델이 반환한 학습 및 테스트용 데이터 세트를 스태킹 형태로 결합 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)
Out[31]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.Lasso(alpha=0.0005)
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.Lasso(alpha=0.0005)
스태킹 회귀 모델의 최종 RMSE 값은: 0.09769360527528641
스태킹 회귀 모델을 적용한 결과, 테스트 데이터 세트에서 RMSE가 약 0.0979로 적용한 회귀 모델들 중 가장 좋은 성능 평가를 보여 준다
'머신러닝' 카테고리의 다른 글
자전거 대여 수요 예측 (0) 2024.01.31 보스턴 주택 가격 회귀 (0) 2024.01.26 사용자 행동 인식 데이터 세트 (0) 2024.01.17 피마 인디언 당뇨병 예측 (0) 2024.01.16 타이타닉 생존자 예측 (0) 2024.01.15