머신러닝·딥러닝 문제해결 전략 책을 읽으면서
Kaggle 경진대회 코드와 문제해결 전략을 정리한 글
import pandas as pd
# 데이터 경로
data_path = '/kaggle/input/bike-sharing-demand/'
train = pd.read_csv(data_path + 'train.csv')
test = pd.read_csv(data_path + 'test.csv')
submission = pd.read_csv(data_path + 'sampleSubmission.csv')
# 훈련 데이터에서 weather가 4가 아닌 데이터만 추출
train = train[train['weather'] != 4]
훈련 데이터 : 10,886 행
테스트 데이터 : 6,493 행
총 데이터 : 17,379 행
weather가 4인 데이터를 제거했으니(1개 있음) 최종적으로 17,378행
all_data_temp = pd.concat([train, test])
all_data_temp
datetime | season | holiday | workingday | weather | temp | atemp | humidity | windspeed | casual | registered | count | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2011-01-01 00:00:00 | 1 | 0 | 0 | 1 | 9.84 | 14.395 | 81 | 0.0000 | 3.0 | 13.0 | 16.0 |
1 | 2011-01-01 01:00:00 | 1 | 0 | 0 | 1 | 9.02 | 13.635 | 80 | 0.0000 | 8.0 | 32.0 | 40.0 |
2 | 2011-01-01 02:00:00 | 1 | 0 | 0 | 1 | 9.02 | 13.635 | 80 | 0.0000 | 5.0 | 27.0 | 32.0 |
3 | 2011-01-01 03:00:00 | 1 | 0 | 0 | 1 | 9.84 | 14.395 | 75 | 0.0000 | 3.0 | 10.0 | 13.0 |
4 | 2011-01-01 04:00:00 | 1 | 0 | 0 | 1 | 9.84 | 14.395 | 75 | 0.0000 | 0.0 | 1.0 | 1.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
6488 | 2012-12-31 19:00:00 | 1 | 0 | 1 | 2 | 10.66 | 12.880 | 60 | 11.0014 | NaN | NaN | NaN |
6489 | 2012-12-31 20:00:00 | 1 | 0 | 1 | 2 | 10.66 | 12.880 | 60 | 11.0014 | NaN | NaN | NaN |
6490 | 2012-12-31 21:00:00 | 1 | 0 | 1 | 1 | 10.66 | 12.880 | 60 | 11.0014 | NaN | NaN | NaN |
6491 | 2012-12-31 22:00:00 | 1 | 0 | 1 | 1 | 10.66 | 13.635 | 56 | 8.9981 | NaN | NaN | NaN |
6492 | 2012-12-31 23:00:00 | 1 | 0 | 1 | 1 | 10.66 | 13.635 | 65 | 8.9981 | NaN | NaN | NaN |
17378 rows × 12 columns
# 원래 데이터의 인덱스를 무시하고 이어붙이려면 ignore_index=True를 전달
all_data = pd.concat([train, test], ignore_index=True)
all_data
datetime | season | holiday | workingday | weather | temp | atemp | humidity | windspeed | casual | registered | count | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2011-01-01 00:00:00 | 1 | 0 | 0 | 1 | 9.84 | 14.395 | 81 | 0.0000 | 3.0 | 13.0 | 16.0 |
1 | 2011-01-01 01:00:00 | 1 | 0 | 0 | 1 | 9.02 | 13.635 | 80 | 0.0000 | 8.0 | 32.0 | 40.0 |
2 | 2011-01-01 02:00:00 | 1 | 0 | 0 | 1 | 9.02 | 13.635 | 80 | 0.0000 | 5.0 | 27.0 | 32.0 |
3 | 2011-01-01 03:00:00 | 1 | 0 | 0 | 1 | 9.84 | 14.395 | 75 | 0.0000 | 3.0 | 10.0 | 13.0 |
4 | 2011-01-01 04:00:00 | 1 | 0 | 0 | 1 | 9.84 | 14.395 | 75 | 0.0000 | 0.0 | 1.0 | 1.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
17373 | 2012-12-31 19:00:00 | 1 | 0 | 1 | 2 | 10.66 | 12.880 | 60 | 11.0014 | NaN | NaN | NaN |
17374 | 2012-12-31 20:00:00 | 1 | 0 | 1 | 2 | 10.66 | 12.880 | 60 | 11.0014 | NaN | NaN | NaN |
17375 | 2012-12-31 21:00:00 | 1 | 0 | 1 | 1 | 10.66 | 12.880 | 60 | 11.0014 | NaN | NaN | NaN |
17376 | 2012-12-31 22:00:00 | 1 | 0 | 1 | 1 | 10.66 | 13.635 | 56 | 8.9981 | NaN | NaN | NaN |
17377 | 2012-12-31 23:00:00 | 1 | 0 | 1 | 1 | 10.66 | 13.635 | 65 | 8.9981 | NaN | NaN | NaN |
17378 rows × 12 columns
from datetime import datetime
# 날짜 피처 생성
all_data['date'] = all_data['datetime'].apply(lambda x: x.split()[0])
# 연도 피처 생성
all_data['year'] = all_data['datetime'].apply(lambda x: x.split()[0].split('-')[0])
# 월 피처 생성
all_data['month'] = all_data['datetime'].apply(lambda x: x.split()[0].split('-')[1])
# 시 피처 생성
all_data['hour'] = all_data['datetime'].apply(lambda x: x.split()[1].split(':')[0])
# 요일 피처 생성
all_data['weekday'] = all_data['date'].apply(lambda dateString: datetime.strptime(dateString, '%Y-%m-%d').weekday())
drop_features = ['casual', 'registered', 'datetime', 'date', 'windspeed', 'month']
all_data = all_data.drop(drop_features, axis=1)
피처 선택(feature selection) : 모델링 시 데이터의 특징을 잘 나타내는 주요 피처만 선택하는 작업
# 훈련 데이터와 테스트 데이터 나누기
X_train = all_data[~pd.isnull(all_data['count'])] # 타깃값이 있으면 훈련 데이터
X_test = all_data[pd.isnull(all_data['count'])] # 타깃값이 없으면 테스트 데이터
# 타깃값 count 제거
X_train = X_train.drop(['count'], axis=1)
X_test = X_test.drop(['count'], axis=1)
y = train['count'] # 타깃값
# 피처 엔지니어링 후의 훈련 데이터
X_train.head()
season | holiday | workingday | weather | temp | atemp | humidity | year | hour | weekday | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 1 | 9.84 | 14.395 | 81 | 2011 | 00 | 5 |
1 | 1 | 0 | 0 | 1 | 9.02 | 13.635 | 80 | 2011 | 01 | 5 |
2 | 1 | 0 | 0 | 1 | 9.02 | 13.635 | 80 | 2011 | 02 | 5 |
3 | 1 | 0 | 0 | 1 | 9.84 | 14.395 | 75 | 2011 | 03 | 5 |
4 | 1 | 0 | 0 | 1 | 9.84 | 14.395 | 75 | 2011 | 04 | 5 |
import numpy as np
def rmsle(y_true, y_pred, convertExp=True):
# 지수변환
if convertExp:
y_true = np.exp(y_true)
y_pred = np.exp(y_pred)
# 로그변환 후 결측값을 0으로 변환
log_true = np.nan_to_num(np.log(y_true+1))
log_pred = np.nan_to_num(np.log(y_pred+1))
# RMSLE 계산
output = np.sqrt(np.mean((log_true - log_pred)**2))
return output
실제 타깃값 y_true와 예측값 y_pred를 인수로 전달하면 RMSLE 수치를 반환하는 함수
# 릿지 모델 생성
from sklearn.linear_model import Ridge
from sklearn.model_selection import GridSearchCV
from sklearn import metrics
ridge_model = Ridge()
alpha
로, 값이 클수록 규제 강도가 커짐. 즉, alpha
를 적당한 크기로 하면 과대적합 문제를 개선할 수 있음# 하이퍼파라미터 값 목록
ridge_params = {'max_iter': [3000],
'alpha': [0.1, 1, 2, 3, 4, 10, 30, 100, 200, 300, 400, 800, 900, 1000]}
# 교차 검증용 평가 함수(RMSLE 점수 계산)
rmsle_scorer = metrics.make_scorer(rmsle, greater_is_better=False)
# make_scorer는 평가지표 계산 함수와 평가지표 점수가 높으면 좋은지 여부 등을 인수로 받는 교차 검증용 평가 함수
# 그리드서치(with 릿지) 객체 생성
gridsearch_ridge_model = GridSearchCV(estimator=ridge_model, # 릿지 모델
param_grid=ridge_params, # 하이퍼파라미터 값 목록
scoring=rmsle_scorer, # 평가지표
cv=5) # 교차 검증 분할 수
그리드서치 객체를 생성하는 GridSearchCV()함수의 주요 파라미터
log_y = np.log(y) # 타깃값 로그변환
gridsearch_ridge_model.fit(X_train, log_y) # 훈련(그리드서치)
GridSearchCV(cv=5, estimator=Ridge(),
param_grid={'alpha': [0.1, 1, 2, 3, 4, 10, 30, 100, 200, 300, 400,
800, 900, 1000],
'max_iter': [3000]},
scoring=make_scorer(rmsle, greater_is_better=False))
fit()을 실행하면 객체 생성 시 paramgrid에 전달된 값들을 순회하면서 교차 검증으로 평가지표 점수를 계산함. 이때 가장 좋은 성능을 보인 값을 **best_params 속성에 저장하며, 이 최적 값으로 훈련한 모델(최적 예측기)을 bestestimator** 속성에 저장함.
print('최적 하이퍼파라미터 :', gridsearch_ridge_model.best_params_)
최적 하이퍼파라미터 : {'alpha': 0.1, 'max_iter': 3000}
# 예측
preds = gridsearch_ridge_model.best_estimator_.predict(X_train)
# 평가
print(f'릿지 회귀 RMSLE 값 : {rmsle(log_y, preds, True):.4f}')
릿지 회귀 RMSLE 값 : 1.0205
참 값(log_y)와 예측값(preds) 사이의 RMSLE는 1.02로, 선형 회귀 모델의 결과와 다르지 않음
alpha
는 규제 강도를 조정하는 파라미터from sklearn.linear_model import Lasso
# 모델 생성
lasso_model = Lasso()
# 하이퍼파라미터 값 목록
lasso_alpha = 1/np.array([0.1,1,2,3,4,10,30,100,200,300,400,800,900])
lasso_params = {'max_iter':[3000], 'alpha':lasso_alpha}
# 그리드서치(with 라쏘) 객체 생성
gridsearch_lasso_model = GridSearchCV(estimator=lasso_model,
param_grid=lasso_params,
scoring=rmsle_scorer,
cv=5)
# 그리드서치 수행
log_y = np.log(y)
gridsearch_lasso_model.fit(X_train, log_y)
print('최적 하이퍼파라미터 :', gridsearch_lasso_model.best_params_)
최적 하이퍼파라미터 : {'alpha': 0.00125, 'max_iter': 3000}
# 예측
preds = gridsearch_lasso_model.best_estimator_.predict(X_train)
# 평가
print(f'라쏘 회귀 RMSLE 값: {rmsle(log_y, preds, True):.4f}')
라쏘 회귀 RMSLE 값: 1.0205
결과를 보면 RMSLE 값은 1.02로 여전히 개선되지 않음
from sklearn.ensemble import RandomForestRegressor
# 모델 생성
randomforest_model = RandomForestRegressor()
# 그리드서치 객체 생성
rf_params = {'random_state':[42], 'n_estimators':[100, 120, 140]}
gridsearch_random_forest_model = GridSearchCV(estimator=randomforest_model,
param_grid=rf_params,
scoring=rmsle_scorer,
cv=5)
# 그리드서치 수행
log_y = np.log(y)
gridsearch_random_forest_model.fit(X_train, log_y)
print('최적 하이퍼파라미터 :', gridsearch_random_forest_model.best_params_)
최적 하이퍼파라미터 : {'n_estimators': 140, 'random_state': 42}
# 예측
preds = gridsearch_random_forest_model.best_estimator_.predict(X_train)
# 평가
print(f'랜덤 포레스트 회귀 RMSLE 값 : {rmsle(log_y, preds, True):.4f}')
랜덤 포레스트 회귀 RMSLE 값 : 0.1126
랜덤 포레스트 회귀 모델을 사용하니 RMSLE 값이 큰 폭으로 개선됨
선형 회귀, 릿지 회귀, 라쏘 회귀 모델의 RMSLE 값은 모두 1.02인 반면
랜덤 포레스트 회귀 모델은 0.11 → 네 모델 중 성능이 가장 좋은 모델
훈련 데이터 타깃값과 테스트 데이터 타깃 예측값의 분포
import seaborn as sns
import matplotlib.pyplot as plt
randomforest_preds = gridsearch_random_forest_model.best_estimator_.predict(X_test)
figure, axes = plt.subplots(ncols=2)
figure.set_size_inches(10, 4)
sns.histplot(y, bins=50, ax=axes[0])
axes[0].set_title('Train Data Distribution')
sns.histplot(np.exp(randomforest_preds), bins=50, ax=axes[1])
axes[1].set_title('Predicted Test Data Distribution')
Text(0.5, 1.0, 'Predicted Test Data Distribution')
submission['count'] = np.exp(randomforest_preds) # 지수변환
submission.to_csv('submission.csv', index=False)