저번 포스팅에서 LGBM 모델을 하이퍼 파라미터 튜닝하여 얻은 스코어는 약 0.407이었다.
이번에는 이 score에서 성능을 더 끌어올리기 위해 내가 시도한 여러 가지 방법들에 대해 포스팅하였다.
과연 이 스코어는 얼마까지 좋아질 수 있을까? 🤔
참고로, 이번 포스팅은 저번 포스팅과 중복되는 코드가 많을 예정이기에, 달라진 부분들만 중점적으로 포스팅하려고 한다. 전체 코드가 궁금하신 분들께서는 깃허브에 ML v.2 부터 v.5까지 올라와 있으므로 참고하세요 :)
✔Table of Contents
ver.2 count 변수를 타겟으로
앞의 LGBM 모델링은, casual에 대한 예측과 registered에 대한 예측을 따로 한 다음, 두 예측치를 합하여 count 예측치를 구했다.
이렇게 한 이유는 EDA 결과를 통해, casual일 때와 registered일 때의 변수 간 양상이 조금 차이가 있기 때문이었다.
이번에는 이렇게 따로 나누지 않고, count에 대한 에측을 한 번에 하려고 한다.
그렇게 하려고 한 이유는 :
- registered가 count 변수의 대다수(80%)를 차지하고 있기에, casual 변수는 영향력이 높지 않다. 굳이 casual과 registered를 나눌 필요가 있을까?
- 또한 casual과 registered의 양상이 좀 다르게 나타나는 부분이 있지만, 이 차이를 보여줄 수 있는 변수가 머신러닝 변수에 포함되어 있다고 판단하였다.
- 예를 들어 주말에 casual 이용량이 더 많아진다는 것은 workingday, holiday 변수로 설명이 가능하다.
그래서 직접적으로 count 변수에 대해서만 예측해보기로 하였다. (casual, registered 변수는 drop)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
train = pd.read_csv('data/train.csv', parse_dates=['datetime'])
test = pd.read_csv('data/test.csv', parse_dates=['datetime'])
#casual, registered 삭제
del train['casual']
del train['registered']
먼저 train, test 데이터를 불러온다. train data에 casual과 registered 변수는 삭제하였다.
따로 결측치나 중복값은 없으며, 여기에 해주는 데이터 전처리는 내가 앞서 포스팅한 Baseline Model 1편의 내용과 같으므로 따로 언급하지 않고 넘어가도록 하자.
필요한 열을 만들고, weather가 4인 값 및 count의 아웃라이어를 제거하였으며, 범주형 변수들의 타입을 category화 해주었다.
count 열의 분포를 살펴보자.
# count의 분포 파악
sns.distplot(train['count'])
count 컬럼도 왼쪽으로 치우친 분포를 보인다. 그러므로 로그변환을 해주었다.
# 로그변환 후
sns.distplot(np.log1p(train['count']))
완벽한 정규분포 형태는 아니나, 좀 더 정규분포에 가까워졌다!
왜도를 확인해보면,
train['count'] = np.log1p(train['count'])
print(train['count'].skew())
이렇게 ±2의 범위에 속하는 왜도 값이 나오므로 로그변환한 count 열은 치우침이 없다고 볼 수 있다.
이제는 count 열을 예측하기 위한 모델링을 해주었다. 과정도 앞에서 했던 것과 동일하다.
# X, y 나누기
X_df = train.drop(['count'], axis = 1)
y_df = train['count']
from sklearn.preprocessing import MinMaxScaler
#선형회귀 모델
lr_reg = LinearRegression()
#피처에 대해 표준화 진행과 k-fold를 함께 함
pipe = make_pipeline(MinMaxScaler(), lr_reg)
scores = cross_validate(pipe, X_df, y_df, cv=5, scoring='neg_mean_squared_error',return_train_score=True)
print("MSLE: {0:.3f}".format(np.mean(-scores['test_score']))) #디폴트가 음수라 -를 붙여야 제대로 된 결과가 나옴.
수치형 변수에는 MinMax 스케일러를 적용하고,
baseline 모델인 선형 회귀에 대해 MSLE 값을 뽑아보면 1.052라는 조금 안 좋은 결과가 나온다.
앞 포스팅에서 랜덤 포레스트와 LGBM 모델을 적용했을 때 성능이 좋았으므로 이 모델들을 적용해보자.
#rf
np.random.seed(0)
rf = RandomForestRegressor(n_estimators=300)
#피처에 대해 표준화 진행과 k-fold를 함께 함
pipe = make_pipeline(MinMaxScaler(), rf)
scores = cross_validate(pipe, X_df, y_df, cv=5, scoring='neg_mean_squared_error', return_train_score=True)
print("MSLE: {0:.3f}".format(np.mean(-scores['test_score'])))
#LGBM
lgbm = LGBMRegressor(n_estimators = 500, objective = 'regression')
#피처에 대해 표준화 진행과 k-fold를 함께 함
pipe = make_pipeline(MinMaxScaler(), lgbm)
scores = cross_validate(pipe, X_df, y_df, cv=5, scoring='neg_mean_squared_error', return_train_score=True)
print("MSLE: {0:.3f}".format(np.mean(-scores['test_score'])))
랜덤포레스트 적용 시 0.215(하이퍼파라미터 튜닝 시 0.214),
LGBM 모델 적용 시 0.185(하이퍼파라미터 튜닝 시 0.174)까지 MSLE 값이 줄어들었다.
앞에서 casual/registered로 나누어 예측했을 때 train MSLE 값보다 더욱 좋은 성능이다.
물론 train MSLE이기 때문에, test MSLE 값도 이렇게 좋게 나오리라는 보장은 없다. 그래도 기대하는 마음으로! test data의 count를 예측해보았다.
test data를 가공하는 부분은 앞의 포스팅과 똑같으므로 생략한다.
이제 test data를 train data의 모수 분포에 맞춰 MinMax 스케일링하고, 최종 파라미터 튜닝된 LGBM 모델로 학습시켜보자.
# 피처 표준화
minmax = MinMaxScaler()
minmax.fit(X_df) #훈련셋 모수 분포 저장
X_df_scaled = minmax.transform(X_df)
X_test_scaled = minmax.transform(test)
# 최종 파라미터 튜닝된 모델로 학습
lgbm = LGBMRegressor(n_estimators = 300, objective = 'regression',
learning_rate = 0.1, max_depth = 3, reg_lambda = 1, subsample = 0.5, random_state = 99)
# 학습
lgbm.fit(X_df_scaled, y_df)
# test에 대해 예측
pred = lgbm.predict(X_test_scaled)
fpred = np.expm1(pred) #로그변환 값을 풀어줌
#lgbm 모델의 feature importance
imp = pd.DataFrame({'feature': test.columns,
'coefficient': lgbm.feature_importances_})
imp = imp.sort_values(by = 'coefficient', ascending = False)
plt.barh(imp['feature'], imp['coefficient'])
plt.show()
피처의 중요도를 뽑아봤을 때, 역시나 hour가 가장 높은 기여도로 나왔다.
이전과는 다른 점이 있다면 x축의 값이다. 이전에는 hour의 기여도가 4000을 넘어갈 정도였는데, 이번엔 700 정도로 다소 안정화되었다. 다른 변수들과의 기여도 차이도 좀 줄어들었다.
이제 최종 결과를 확인하자!
sub = pd.read_csv('data/sampleSubmission.csv')
del sub['count']
sub['count'] = fpred
sub.to_csv('Submission_sy_1.csv', index=False)
test score는 0.40439로 처음 만들었던 성능 0.407보다 RMSLE 값이 0.03 하락하였다.
엄청 줄어든 성능은 아니지만, target만 바꿨는데도 성능 향상을 이루었다!
다음 버전부터는 target을 count로 고정하여 진행하였다.
ver.3 season과 month 중 어느 변수를 선택할까?
이번에는 ver.2의 결과를 바탕으로 count에 대해 예측하되,
season과 month 변수가 나타내는 값이 겹치므로 둘 중 하나만 사용했을 때 어떤 것이 더 성능이 좋은지를 알아보자.
앞의 전처리부분 까지는 앞부분과 같으므로 생략한다.
# 1) X, y 나누기
X_df = train.drop(['count'], axis = 1)
y_df = train['count']
# 2) 변수에서 season, month를 각각 제거
X_df_s = X_df.drop(['month'], axis = 1)
X_df_m = X_df.drop(['season'], axis = 1)
3-1. season 변수만 남겼을 때
X_df_s와 y_df를 사용하여 모델링한다.
모델링 코드 또한 앞에서 썼던 것과 똑같으므로 전체 코드는 깃허브에 업로드한 주피터 노트북을 참고하시길!
나는 가장 성능이 좋았던 LGBM 모델을 최종적으로 사용하였다.
#하이퍼 파라미터 튜닝
pipeline = Pipeline([('scaler', MinMaxScaler()), ('lgbm',LGBMRegressor(objective='regression', learning_rate = 0.1, subsample = 0.5))])
params={'lgbm__max_depth': [3, 5, 7],
'lgbm__reg_lambda':[0.1, 1],
'lgbm__n_estimators': [200, 300]}
grid_model = GridSearchCV(pipeline, param_grid=params, scoring='neg_mean_squared_error', cv=5, n_jobs = 5, verbose=True)
grid_model.fit(X_df_s, y_df)
print("MSLE: {0:.3f}".format( -1*grid_model.best_score_))
print('optimal hyperparameter: ', grid_model.best_params_)
하이퍼파라미터 튜닝 결과는 train MSLE가 0.174로 ver.2의 train MSLE 값이 같다.
과연 test MSLE 값은 더 좋아졌을까?
test data를 가공할 때는, month는 제거하고 season만 남긴다.
나머지 과정은 앞 부분과 똑같으므로 생략하고, 최종 score를 보면..
test score는 0.37992로, 지금까지 했던 것 중 최고 성능이 나왔다!!
결과는 3천여 팀 중 110등이다 :)
이제 season만 남기고 모델링하였으니, 반대로 month만 남기고 모델링을 하면 어떻게 될까?
3-2. month 변수만 남겼을 때
X_df_m와 y_df를 사용하여 모델링한다.
역시 모델링 코드 또한 앞에서 썼던 것과 똑같으므로 생략한다. 나는 가장 성능이 좋았던 LGBM 모델을 최종적으로 사용하였다.
#하이퍼 파라미터 튜닝
pipeline = Pipeline([('scaler', MinMaxScaler()), ('lgbm',LGBMRegressor(objective='regression', learning_rate = 0.1, subsample = 0.5))])
params={'lgbm__max_depth': [3, 5, 7],
'lgbm__reg_lambda':[0.1, 1],
'lgbm__n_estimators': [200, 300]}
grid_model = GridSearchCV(pipeline, param_grid=params, scoring='neg_mean_squared_error', cv=5, n_jobs = 5, verbose=True)
grid_model.fit(X_df_m, y_df)
print("MSLE: {0:.3f}".format( -1*grid_model.best_score_))
print('optimal hyperparameter: ', grid_model.best_params_)
train MSLE도 0.174로, 앞에서 season 변수만 남기고 구한 train MSLE와 동일하다.
그렇다면 test MSLE도 앞에서 구한 것과 같을까?
이번엔 반대로 test data를 가공할 때는, season은 제거하고 month만 남긴다.
나머지 과정은 앞 부분과 똑같으므로 생략하고, 최종 score를 보면..
이런...ㅠ.ㅠ month만 남겼을 때는 0.437로, 가장 최악의 성능이 나왔다.
아무래도 month를 분기별로 나눈 것이 season이고, month는 1~12, season은 1~4로 season이 좀 더 간단한 범주를 가지고 있어서 더 좋은 성능이 나오지 않았나 싶다.
게다가 많은 대여량이 분기별(season별)로도 좀 잘 나눠지기도 했고! month 변수만 남기면 과적합이 발생함을 파악하였다.
이번 결과로 인해, 여러 변수들 중 month는 사용하지 않고 season만 갖고 가는 것으로 결정~!
이렇게 오늘은 성능 개선을 위해 2가지 방법을 사용해보았고,
결론적으로 원래 0.407이었던 스코어를 → 0.379로 향상시킬 수 있었다.
앞으로는 target을 count로 하고, season 변수를 선택하는 방법을 고정한다. (month는 drop)
다음 포스팅이 Bike sharing demand 프로젝트의 마지막 포스팅이 될 예정이다. 다음 포스팅에선 EDA 편에서 봤던, 0 값이 너무 많은 windspeed 변수를 처리해본다.
과연 얼마까지 더 성능이 좋아질 수 있을까? 기대해보자! 🧐
'Data Science > Kaggle' 카테고리의 다른 글
[kaggle] 범주형 데이터 분석 프로젝트 - EDA 1편 (2) | 2022.07.14 |
---|---|
[kaggle] Bike Sharing Demand: ML 성능 개선 3편 (머신러닝 결측치 처리) (2) | 2022.07.03 |
[kaggle] Bike Sharing Demand: ML 성능 개선 1편 (Ridge, Random Forest, LGBM) (0) | 2022.07.01 |
[kaggle] Bike Sharing Demand: Baseline Model 2편 (pipeline, k-fold, scaling) (0) | 2022.06.30 |
[kaggle] Bike Sharing Demand: Baseline Model 1편 (데이터 전처리) (2) | 2022.06.30 |