데이콘 대회 참여 - 04 데이터셋 분리

Page content

공지

제 수업을 듣는 사람들이 계속적으로 실습할 수 있도록 강의 파일을 만들었습니다. 늘 도움이 되기를 바라며. 참고했던 교재 및 Reference는 꼭 확인하셔서 교재 구매 또는 관련 Reference를 확인하시기를 바랍니다.

사전작업

!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을 활용하여 데이터를 가져온다.
  • 층화추출도 할 수 있는데, 쿼리상 조금 복잡할 수 있어서, 이부분은 추후 포스팅 하는 것으로 남겨둔다.
  • 이번에는 데이터 갯수를 기존 1000개 $\rightarrow$ 10000개로 확장해서 추출한다.
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() < 10000 / (SELECT COUNT(*) FROM `your_project_id.jeju_data_ver1.201901_202003_train`)
  ''').to_dataframe()

(3) 데이터 시각화 하기

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
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    201912
1    201902
2    201906
3    201904
4    201905
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 = 2752198.05 and sigma = 19988586.84

png

png

  • 위 결과가 말해주듯이 타겟변수를 정규화 하는 작업이 필요합니다.
  • 정규화 작업은 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.69 and sigma = 1.74

png

png

  • 로그를 Target값을 변환한 후에 정규분포를 이루는 것을 확인하였습니다.

(7) 하이퍼 파라미터 및 튜닝

  • 1차적으로 모형은 GBM만 사용한다.
  • 이 때, 보통 모형 알고리즘에 대한 하이퍼 파라미터 튜닝도 같이 진행하게 된다.
    • n_estimators: weak learner가 순차적으로 오류를 보정함. 개수가 많아지면 성능이 좋아지지만, 수행시간이 오래 발생될 수 있음.
    • learning_rate: 오차를 얼마나 강하게 보정할 것인지 제어
    • max_depth: 복잡도를 너무 높이지 말고 트리의 깊이가 5보다 깊어지지 않게 함.
# 훈련
gbm = GradientBoostingRegressor(n_estimators=4000);
gbm.fit(X_train, y_train)
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=4000,
                          n_iter_no_change=None, presort='deprecated',
                          random_state=None, subsample=1.0, tol=0.0001,
                          validation_fraction=0.1, verbose=0, warm_start=False)

(8) 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))
  • 위 함수를 사용해서 모형을 평가한다.
eval_pred = gbm.predict(X_test)
eval_pred = np.expm1(eval_pred)
rmsle(y_test, eval_pred)
RMSLE: 1.494
  • 위 모형을 평가한 결과 1.494가 나왔다.
  • 그러나, 위 평가모형 결과가 실제와 똑같을 거라 생각하면 안된다.
  • 다만, 최종 제출폼을 작성하기에 앞서서, 중간에 평가 측정표를 검증하여 마지막 모형을 선정하는 중간 지표로 삼기에는 적절하다.

(9) 예측 템플릿 작성

  • 예측 템플릿을 작성한다.
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.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           강원            건강보조식품 소매업  1.098294e+09
1    202004           강원               골프장 운영업  8.251103e+08
2    202004           강원           과실 및 채소 소매업  1.797290e+08
3    202004           강원     관광 민예품 및 선물용품 소매업  5.458807e+07
4    202004           강원  그외 기타 분류안된 오락관련 서비스업  5.458807e+07

(10) 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           강원            건강보조식품 소매업  1.098294e+09
1   1    202004           강원               골프장 운영업  8.251103e+08
2   2    202004           강원           과실 및 채소 소매업  1.797290e+08
3   3    202004           강원     관광 민예품 및 선물용품 소매업  5.458807e+07
4   4    202004           강원  그외 기타 분류안된 오락관련 서비스업  5.458807e+07
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
  • 중간 결과표를 보면 알 수 있듯이, 2.2GB Raw 데이터에서 추출을 이미 Random Sampling을 통해서 가져와서 진행하였기 때문에 훈련 데이터와 테스트 데이터를 나누는 것 자체가 성능 향상에는 큰 향상이 없음을 알 수 있다.
  • 이 때, 중요한 것은 종속변수에 log_transformation을 통해 데이터 정규화를 진행하는 것이 오히려 성능 향상에 더 좋은 결과를 가져 왔음을 알 수 있다.

IV. What’s Next

  • 이제 성능을 끌어올리기 위해서 무엇을 해야 할까?
  • 크게 세가지 방향성이 있다.
    • 기존 모형 파라미터
    • 다른 알고리즘 적용
    • 데이터 전처리 (EDA)
  • 이제 순차적으로 접근하도록 한다.