데이콘 대회 참여 - 06 교차검증과 파라미터 튜닝
Page content
공지
제 수업을 듣는 사람들이 계속적으로 실습할 수 있도록 강의 파일을 만들었습니다. 늘 도움이 되기를 바라며. 참고했던 교재 및 Reference는 꼭 확인하셔서 교재 구매 또는 관련 Reference를 확인하시기를 바랍니다.
사전작업
- 먼저 구글 코랩 내에서
pandas_profiling을 확인하기 위해master.zip을 설치한다. - 설치가 끝나면 구글코랩에서 런타임 다시 시작 한다.
!pip install https://github.com/pandas-profiling/pandas-profiling/archive/master.zip
Collecting https://github.com/pandas-profiling/pandas-profiling/archive/master.zip
Using cached https://github.com/pandas-profiling/pandas-profiling/archive/master.zip
.
.
.
Successfully built pandas-profiling
I. 빅쿼리 연동
- 지난 시간에 데이콘에서 내려받은 데이터를 빅쿼리에 넣는 작업을 진행하였다.
- 빅쿼리에 저장된 데이터를 구글 코랩으로 불러오려면 다음과 같이 진행한다.
(1) 사용자 계정 인증
구글 코랩을 사용해서 인증 절차를 밟도록 한다. 아래 소스코드는 변경시키지 ���는다. 아래 절차대로 진행하면 된다. Gmail 인증 절차와 비슷하다.
from google.colab import auth
auth.authenticate_user()
print('Authenticated')
Authenticated
(2) 데이터 불러오기
- 이번에는 빅쿼리에서
Random Sampling을 활용하여 데이터를 가져온다. - 층화추출도 할 수 있는데, 쿼리상 조금 복잡할 수 있어서, 이부분은 추후 포스팅 하는 것으로 남겨둔다.
- 이번에는 데이터 갯수를 기존
10000개$\rightarrow$100000개로 확장해서 추출한다.
from google.cloud import bigquery
from tabulate import tabulate
import pandas as pd
project_id = 'your_project_id'
client = bigquery.Client(project=project_id)
train = client.query('''
SELECT
*
FROM `your_project_id.jeju_data_ver1.201901_202003_train`
WHERE RAND() < 100000 / (SELECT COUNT(*) FROM `your_project_id.jeju_data_ver1.201901_202003_train`)
''').to_dataframe()
(3) 데이터 시각화 하기
Random Sampling과 결과와 기존 결과와 어떻게 다른지 확인해 본다.- 대용량에서
Sampling은 정말 중요하다! - 02 GBM을 활용한 머신러닝 예제, 상위 1000개 추출
- 03 데이터 샘플링과 종속변수 로그변환, 랜덤 1000개 추출
- 대용량에서
from pandas_profiling import ProfileReport
profile = ProfileReport(train, title='Pandas Profiling Report', explorative=True)
profile.to_notebook_iframe()
- 데이터 시각화를 통해서 전처리를 진행하는 것이 순리이다. 그러나, 본 과정에서는
GBM의 개념과 원리에 집중해야하기 때문에,Raw-Data로 그대로 적용하도록 한다.
II. GBM의 개요 및 실습
- 부스팅 알고리즘은 여러 개의 약한 학습기(
Weak Learner)를 순차적으로 학습-예측하면서 잘못 예측한 데이터에 가중치 부여를 통해 오류 개선하며 학습하는 방식.
- 위 그림에서 첫번째 그림과 마지막 그림의 차이점에서 결국 성능입니다.
- 첫번째에서 오분류는 3개가 발생되었지만, 마지막 그림에서는 오분류가 발생하지 않음을 확인할 수 있다.
- 산술적으로 가중치는 다음과 같이 부여할 수 있습니다.
- 첫번째 학습기에 0.3, 두번째 학습기에 0.5, 세번째 학습기에 0.8을 부여합니다.
- 가중치를 업데이트 방법은 경사하강법을 이용합니다.
- 오류값은 실제값 - 예측값이다.
- 경사하강법의 기본 원리는 반복 수행을 통해 오류를 최소화할 수 있도록 가중치의 업데이트 값을 도출하는 기법 정도로 이해하면 좋습니다.
(1) 머신러닝 & 시각화 & 통계 패키지 Loading
- 종속변수와 독립변수로 구분할 필요가 있다.
import pandas as pd
import numpy as np
import sklearn
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.preprocessing import LabelEncoder
# 03 Chapter에서 추가
# 평가 메트릭
from sklearn.metrics import mean_squared_error, mean_absolute_error
# 시각화
import seaborn as sns
color = sns.color_palette()
sns.set_style('darkgrid')
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt # Matlab-style plotting
# 통계
from scipy import stats
from scipy.stats import norm, skew #for some statistics
# 06 Chapter 추가
from sklearn.base import BaseEstimator, TransformerMixin, RegressorMixin, clone
from sklearn.model_selection import KFold, cross_val_score, train_test_split, GridSearchCV
from sklearn.metrics import mean_squared_error
/usr/local/lib/python3.6/dist-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
import pandas.util.testing as tm
print('Pandas : %s'%(pd.__version__))
print('Numpy : %s'%(np.__version__))
print('Scikit-Learn : %s'%(sklearn.__version__))
print('seaborn : %s'%(sns.__version__))
print('matplotlib : %s'%(matplotlib.__version__))
!python --version
Pandas : 1.0.5
Numpy : 1.18.5
Scikit-Learn : 0.22.2.post1
seaborn : 0.10.1
matplotlib : 3.2.2
Python 3.6.9
(2) 날짜 데이터 전처리
- 데이터 전처리 기본함수 작성
- 우선 날짜 처리 진행
year&month로 구분한다.
def grap_year(data):
data = str(data)
return int(data[:4])
def grap_month(data):
data = str(data)
return int(data[4:])
# 날짜 처리
data = train.copy()
data = data.fillna('')
print(data['REG_YYMM'].head())
0 201906
1 201902
2 201911
3 202002
4 202001
Name: REG_YYMM, dtype: int64
data['year'] = data['REG_YYMM'].apply(lambda x: grap_year(x))
data['month'] = data['REG_YYMM'].apply(lambda x: grap_month(x))
data = data.drop(['REG_YYMM'], axis=1)
data.head()
(3) 시군구 컬럼 제거
submission제출 파일 목록에서시/도는 해당되나,시군구항목은 해당되지 않는다.- 따라서, 해당 컬럼은 삭제한다.
# 데이터 정제
df = data.drop(['CARD_CCG_NM', 'HOM_CCG_NM'], axis=1)
columns = ['CARD_SIDO_NM', 'STD_CLSS_NM', 'HOM_SIDO_NM', 'AGE', 'SEX_CTGO_CD', 'FLC', 'year', 'month']
df = df.groupby(columns).sum().reset_index(drop=False)
(4) 라벨 인코딩
- 사이킷런의
ML알고리즘은 결측치가 허용되지 않는다. - 사이킷런의 머신러닝 알고리즘은 문자열 값을 입력 값으로 허용하지 않는다.
- 따라서, 이를 숫자형으로 변환해야 한다.
- 이를 데이터 인코딩이라 부른다.
- 데이터 인코딩에는 크게 두가지 있다.
- 레이블 인코딩 VS. 원-핫 인코딩
- 레이블 인코딩은 카테고리 피처를 코드형 숫자 값으로 변환한다.
- 이 때, 일괄적으로 숫자로 변환이 되면 선형회귀와 같은 ML 알고리즘에는 적용하지 않는다. 이유는 숫자 값의 경우 크고 작음에 대한 특성이 작용한다.
- 그러나, 본 예제에서는 주로 트리 계열을 알고리즘을 사용할 것이기 때문에 크게 상관은 없다.
- 원핫 인코딩은 피처 값의 유형에 따라 새로운 피처를 추가해 고유 값에 해당하는 칼럼에만 1을 표시하고 나머지 칼럼에는 0을 표시한다.
- 본 실습에서는 주로 라벨 인코딩만 사용하고 진행해본다.
# 인코딩
dtypes = df.dtypes
encoders = {}
for column in df.columns:
if str(dtypes[column]) == 'object':
encoder = LabelEncoder()
encoder.fit(df[column])
encoders[column] = encoder
df_num = df.copy()
for column in encoders.keys():
encoder = encoders[column]
df_num[column] = encoder.transform(df[column])
(5) 데이터셋 분리
Train데이터를 분리해서Validate데이터셋을 생성한다.
from sklearn.model_selection import train_test_split
X_data, y_data = df_num.loc[:, df_num.columns != 'AMT'], df.loc[:, df_num.columns == 'AMT']
y_target = y_data['AMT']
X_data = X_data.drop(['CSTMR_CNT', 'CNT'], axis=1)
X_train, X_test, y_train, y_test = train_test_split(X_data, y_target, test_size=0.3, random_state=126, shuffle=True)
- test_size: 훈련데이터와 테스트 데이터로 나누는 비율이다. 디폴트는 0.25이며, 이는 25%에 해당한다.
- random_state: 동일한 학습/테스트용 데이터 세트를 생성하기 위해 주어지는 난수 값. (실험의 재현성)
- shuffle: 데이터 분리 전 데이터를 섞을지 결정함. 디폴트는
True이며, 데이터를 분산시켜서 좀 더 효율적인 학습 및 테스트 데이터 세트를 만드는 데 사용함.
(6) 종속 변수 확인
- 라벨 인코딩을 진행하기 전에 타겟변수(=종속변수)의 모양을 확인한다.
Target값의 분포가 왜곡되면, 예측 성능이 저하되는 경우가 발생한다.- 빠르게 시각화를 진행해서 확인해보자.
sns.distplot(y_train , fit=norm);
(mu, sigma) = norm.fit(y_train)
print( '\n mu = {:.2f} and sigma = {:.2f}\n'.format(mu, sigma))
plt.legend(['Normal dist. ($\mu=$ {:.2f} and $\sigma=$ {:.2f} )'.format(mu, sigma)],
loc='best')
plt.ylabel('Frequency')
plt.title('AMT distribution')
fig = plt.figure()
res = stats.probplot(y_train, plot=plt)
plt.show()
mu = 3415650.33 and sigma = 21861749.83


- 위 결과가 말해주듯이 타겟변수를 정규화 하는 작업이 필요합니다.
- 정규화 작업은
1줄이면 가능합니다.
y_train = np.log1p(y_train)
sns.distplot(y_train , fit=norm);
(mu, sigma) = norm.fit(y_train)
print( '\n mu = {:.2f} and sigma = {:.2f}\n'.format(mu, sigma))
plt.legend(['Normal dist. ($\mu=$ {:.2f} and $\sigma=$ {:.2f} )'.format(mu, sigma)],
loc='best')
plt.ylabel('Frequency')
plt.title('AMT distribution')
fig = plt.figure()
res = stats.probplot(y_train, plot=plt)
plt.show()
mu = 12.83 and sigma = 1.82


- 로그를
Target값을 변환한 후에 정규분포를 이루는 것을 확인하였습니다.
III. 교차검증 및 하이퍼 파라미터 튜닝
- 본 장에서는 교차검증을 실시하는 목적과 파라미터 튜닝에 대해 배울 것이다.
(1) 교차검증의 목적
- 가장 중요한 목적은 과적합(
Overfitting)을 방지하기 위해서다. - 과적합은 무엇인가?
- 가령, 교과서 범위 내의 있는 문제는 잘 풀지만, 모의고사 또는 수능처럼 약간 응용된 문제가 나오면 잘 풀지 못하는 것과 유사하다.
- 즉, 고정된 학습데이터와 테스트 데이터로 평가 하다 보니, 테스트 데이터에만 최적의 성능을 발휘할 수 있도록 편향되게 모델이 학습하게 되는 현상을 말한다.
- 이러한 문제점을 개선하기 위해 교차 검증을 이용해 더 다양한 학습과 평가를 한다.
- 즉, 본 게임에 앞서서 여러번의 사전 모의평가를 진행하는 것과 유사하다.
(2) K폴드 교차 검증
- K개의 데이터 세트를 만들고, 검증 평가를 반복적으로 수행하는 방법이다.
- 이 때, 5개의 평가를 평균한 결과를 가지고 예측 성능을 평가한다.
- 아래 그림을 보자.

- 기존 처럼, 훈련/테스트 데이터로 분리 한다.
- 그리고, 해당 모델을 처음에 분할하였던
Test Set을 활용하여 평가 한다. 층화추출 K폴드도 있다. 데이터의 분포가 불균형을 이룰 때 적용하면 모형의 예측 성능보다는 보다 안정성 있게 모델이 만들어질 수 있다.
(3) RMSLE 성능 평가 방법
- 회귀 모형의 대표적인 평가 지표이다.
RMSLE: Root Mean Square Log Error는 기존RMSE에 로그를 적용해준 지표다.
- 아쉽게도
sklearn에는 해당 옵션이 제공되지 않기 때문에RMSLE를 수행하는 성능 평가 함수를 직접 만들어 본다. - log값 변환 시
NaN등의 이슈로log가 아닌log1p()를 이용해 계산한다. - 이 때,
log1p()로 변환된 값은np.expm1()함수로 쉽게 원래의 스케일로 복원될 수 있다.
def rmsle(y, pred):
log_y = np.log1p(y)
log_pred = np.log1p(pred)
squared_error = (log_y - log_pred)**2
rmsle = np.sqrt(np.mean(squared_error))
return print('RMSLE: {0:.3f}'.format(rmsle))
(4) 하이퍼 파라미터 및 튜닝
-
1차적으로 모형은
GBM만 사용한다. -
이 때, 보통 모형 알고리즘에 대한 하이퍼 파라미터 튜닝도 같이 진행하게 된다.
- n_estimators: weak learner가 순차적으로 오류를 보정함. 개수가 많아지면 성능이 좋아지지만, 수행시간이 오래 발생될 수 있음.
- learning_rate: 오차를 얼마나 강하게 보정할 것인지 제어
- max_depth: 복잡도를 너무 높이지 말고 트리의 깊이가 정해진 숫자보다 깊어지지 않게 함.
- min_samples_leaf:
Leaf Node가 되기 위한 최소 샘플 개수 - loss:
huber, 이상치에 민감하지 않도록 보정해주는 함수. - max_features: 다차원 독립 변수 중 선택할 차원의 수를 의미함
-
이 때, 보통 최적의 하이퍼 파라미터 튜닝을 위해 값을 조정해서 알고리즘의 예측 성능을 개선한다고 했다.
-
GridSearchCV와RandomizedSearchCV로 구분되는데, 이 부분에 대한 설명은 다음Chapter에서 진행하도록 한다.- 간단하게 설명하면, 파라미터를 순차적으로 변경하면서 최고의 성능을 가지는 파라미터 조합을 찾는 과정이다.
(5) 교차검증 + 하이퍼 파라미터
- 지금까지 설명한 내용을 코드로 작성한다.
- 이 때, 최적의 파라미터를 찾기 위한 과정도 추가했다.
# 모델 선언
gbm = GradientBoostingRegressor(random_state=0)
# 파라미터 지정
gbm_parameters = {'max_depth':[2, 3, 5, 10],
'min_samples_split':[2, 3, 5],
'min_samples_leaf':[1,5,8]}
# 교차검증 수행
grid_gbm = GridSearchCV(gbm, param_grid=gbm_parameters, scoring="neg_mean_squared_error", cv = 5)
grid_gbm.fit(X_train, y_train)
GridSearchCV(cv=5, error_score=nan,
estimator=GradientBoostingRegressor(alpha=0.9, ccp_alpha=0.0,
criterion='friedman_mse',
init=None, learning_rate=0.1,
loss='ls', max_depth=3,
max_features=None,
max_leaf_nodes=None,
min_impurity_decrease=0.0,
min_impurity_split=None,
min_samples_leaf=1,
min_samples_split=2,
min_weight_fraction_leaf=0.0,
n_estimators=100,
n_iter_no_change=None,
presort='deprecated',
random_state=0, subsample=1.0,
tol=0.0001,
validation_fraction=0.1,
verbose=0, warm_start=False),
iid='deprecated', n_jobs=None,
param_grid={'max_depth': [2, 3, 5, 10],
'min_samples_leaf': [1, 5, 8],
'min_samples_split': [2, 3, 5]},
pre_dispatch='2*n_jobs', refit=True, return_train_score=False,
scoring='neg_mean_squared_error', verbose=0)
print("GridSearchCV 최적 하이퍼 파라미터 :", grid_gbm.best_params_)
rmse = np.sqrt(-1*grid_gbm.best_score_)
print("GridSearchCV 최적 평균 RMSE값 :", np.round(rmse, 4))
GridSearchCV 최적 하이퍼 파라미터 : {'max_depth': 10, 'min_samples_leaf': 8, 'min_samples_split': 2}
GridSearchCV 최적 평균 RMSE값 : 1.4327
gbm_best = grid_gbm.best_estimator_
eval_pred = gbm_best.predict(X_test)
eval_pred = np.expm1(eval_pred)
rmsle(y_test, eval_pred)
RMSLE: 1.421
- 위 모형을 평가한 결과
1.421이 나왔다. - 그러나, 위 평가모형 결과가 실제와 똑같을 거라 생각하면 안된다.
- 다만, 최종 제출폼을 작성하기에 앞서서, 중간에 평가 측정표를 검증하여 마지막 모형을 선정하는 중간 지표로 삼기에는 적절하다.
(6) 예측 템플릿 작성
- 예측 템플릿을 작성한다.
from itertools import product
# 예측 템플릿 만들기
CARD_SIDO_NMs = df_num['CARD_SIDO_NM'].unique()
STD_CLSS_NMs = df_num['STD_CLSS_NM'].unique()
HOM_SIDO_NMs = df_num['HOM_SIDO_NM'].unique()
AGEs = df_num['AGE'].unique()
SEX_CTGO_CDs = df_num['SEX_CTGO_CD'].unique()
FLCs = df_num['FLC'].unique()
years = [2020]
months = [4, 7]
comb_list = [CARD_SIDO_NMs, STD_CLSS_NMs,HOM_SIDO_NMs, AGEs, SEX_CTGO_CDs, FLCs, years, months]
temp = np.array(list(product(*comb_list)))
train_features = df_num.drop(['CSTMR_CNT', 'AMT', 'CNT'], axis=1)
temp = pd.DataFrame(data=temp, columns=train_features.columns)
- 예측된 결과를 데이터프레임에 제출한다.
# 예측
pred = gbm_best.predict(temp)
pred = np.expm1(pred)
temp['AMT'] = np.round(pred, 0)
temp['REG_YYMM'] = temp['year']*100 + temp['month']
temp = temp[['REG_YYMM', 'CARD_SIDO_NM', 'STD_CLSS_NM', 'AMT']]
temp = temp.groupby(['REG_YYMM', 'CARD_SIDO_NM', 'STD_CLSS_NM']).sum().reset_index(drop=False)
- 라벨 인코딩 했던 부분을 제출을 위해 다시 디코딩하는 작업을 진행한다.
# 디코딩
temp['CARD_SIDO_NM'] = encoders['CARD_SIDO_NM'].inverse_transform(temp['CARD_SIDO_NM'])
temp['STD_CLSS_NM'] = encoders['STD_CLSS_NM'].inverse_transform(temp['STD_CLSS_NM'])
print(temp.head())
REG_YYMM CARD_SIDO_NM STD_CLSS_NM AMT
0 202004 강원 건강보조식품 소매업 290440460.0
1 202004 강원 골프장 운영업 460111841.0
2 202004 강원 과실 및 채소 소매업 137966925.0
3 202004 강원 관광 민예품 및 선물용품 소매업 72254352.0
4 202004 강원 그외 기타 분류안된 오락관련 서비스업 74292633.0
(7) Submission 파일 작업 및 내보내기
submission = client.query('''
SELECT
*
FROM `your_project_id.jeju_data_ver1.submission`
''').to_dataframe()
submission = submission.drop(['AMT'], axis=1)
submission = submission.merge(temp, left_on=['REG_YYMM', 'CARD_SIDO_NM', 'STD_CLSS_NM'], right_on=['REG_YYMM', 'CARD_SIDO_NM', 'STD_CLSS_NM'], how='left')
submission['AMT'] = submission['AMT'].fillna(0)
print(submission.head())
id REG_YYMM CARD_SIDO_NM STD_CLSS_NM AMT
0 0 202004 강원 건강보조식품 소매업 290440460.0
1 1 202004 강원 골프장 운영업 460111841.0
2 2 202004 강원 과실 및 채소 소매업 137966925.0
3 3 202004 강원 관광 민예품 및 선물용품 소매업 72254352.0
4 4 202004 강원 그외 기타 분류안된 오락관련 서비스업 74292633.0
submission.to_csv('submission.csv', encoding='utf-8-sig', index=False)
III. 평가지표
| Chapter No. | 제출일시 | RMSLE Score in Test | Final Score |
|---|---|---|---|
| 02 | 2020-06-28 23:28:27 | - | 15.6796527331 |
| 03 | 2020-06-29 17:48:23 | - | 6.xxx |
| 04 | 2020-06-30 15:11:28 | 1.494 | 7.xxx |
| 05 | 2020-07-01 00:00:16 | 1.398 | 6.xxx |
| 06 | 2020-07-01 01:50:57 | 1.421 | 7.xxx |
- 중간 결과표를 보면 알 수 있듯이, 2.2GB
Raw 데이터에서 추출을 이미Random Sampling을 통해서 가져와서 진행하였기 때문에 훈련 데이터와 테스트 데이터를 나누는 것 자체가 성능 향상에는 큰 향상이 없음을 알 수 있다. - 이 때, 중요한 것은
종속변수에 log_transformation을 통해 데이터 정규화를 진행하는 것이 오히려 성능 향상에 더 좋은 결과를 가져 왔음을 알 수 있다. - 교차검증과 하이퍼 파라미터를 수행한다고 해도 결과가 크게 달라지지 않음을 볼 수 있다.
- 그럼 이번에는 최신기법인
xgboost으로 모형을 바꿔서 제출하면 결과가 달라질까?- 정말, 이러한 기법들을 사용하면 평가지표는 달라질까?
Kaggle노트북을 보더라도 다양한 테크닉과 솔루션, 그리고 알고리즘을 소개하는 여러 기법들이 있다.
IV. What’s Next
- 교차검증과, 최적의 하이퍼 파라미터를 조정하는 방법에 대해 배웠다.
- 긴 시간 동안 학습을 한다고 할지라도 모형에는 큰 변화가 없음을 확인했다.
- 이 쯤 되면 감이 빨리 오신 분이 있으면 좋겠다.
(데이터 전처리의 중요성!) - 그러나, 우선 데이터 전처리를 하기에 앞서서 다른 알고리즘을 써보자.
xgboost를 같이 사용하도록 해본다.데이터 전처리를 하지 않고 진행할 에정이다.