캐글 스터디 4주차 모델평가

6 minute read

슬라이드4_1

목차

슬라이드4_2

검증 방법

층화 K-겹 검증

분류 문제에서 폴드마다 포함되는 클래스의 비율을 서로 맞출 때가 자주 있는데 이것을 층화추출이라고 부른다.

가정) 테스트 데이터에 포함되는 각 클래스의 비율은 학습 데이터에 포함되는 각 클래스의 비율과 거의 같을 것이라는 가정에 근거하여 검증의 평가를 안정화하려는 방법이다.

  • StratifiedKFold 클래스로 층화추출을 통한 검증을 수행할 수 있다.
  • Kfold 클래스와 달리 층화추출을 위해 split 메서드의 인수에 목적변수를 입력해야 한다.
  • 홀드아웃 검증으로 층화추출을 하고 싶을 때는 train_test_split 함수의 stratify인수에 목적변수를 지정한다.
#데이터 등의 사전 준비

import numpy as np
import pandas as pd

#train_x는 학습 데이터, train_y는 목적 변수, test_x는 테스트 데이터
#pandas의 DataFrame, Series로 유지합니다.(numpy의 array로 유지하기도 합니다)

train = pd.read_csv('C:/Users/Administrator/Desktop/2021년 1학기/tave동아리/후반기 활동/ppt/input/train_preprocessed.csv')
train_x = train.drop(['target'], axis=1)
train_y = train['target']
test_x = pd.read_csv('C:/Users/Administrator/Desktop/2021년 1학기/tave동아리/후반기 활동/ppt/input/test_preprocessed.csv')
from sklearn.model_selection import StratifiedKFold

#StratifiedKFold 클래스를 이용하여 층화추출로 데이터 분할
kf = StratifiedKFold(n_splits=4, shuffle=True, random_state=71)
for tr_idx, va_idx in kf.split(train_x, train_y):
    tr_x, va_x = train_x.iloc[tr_idx], train_x.iloc[va_idx]
    tr_y, va_y = train_y.iloc[tr_idx], train_y.iloc[va_idx]

그룹 K-겹 검증

경진 대회에 따라서는 학습 데이터와 테스트 데이터가 랜덤으로 분할되지 않을 때도 있습니다.

즉 학습 데이터와 테스트 데이터에 동일한 고객 데이터가 포함되지 않도록 분할된다.

이러한 경우 단순히 랜덤하게 데이터를 분할하여 검증하면 본래의 성능보다 과대 평가될 우려가 있어서 고객 단위로 데이터를 분할해야한다.

#GroupKFold

#4건씩 같은 유저가 있는 데이터였다고 가정한다.
train_x['user_id'] = np.arange(0, len(train_x)) // 4

from sklearn.model_selection import KFold, GroupKFold

#user_id열의 고객 ID 단위로 분할
user_id = train_x['user_id']
unique_user_ids = user_id.unique()

#KFold 클래스를 이용하여 고객 ID 단위로 분할
scores = []
kf = KFold(n_splits=4, shuffle=True, random_state=71)
for tr_group_idx, va_group_idx in kf.split(unique_user_ids):
    #고객 ID를 train/valid(학습에 사용하는 데이터, 검증 데이터)로 분할
    tr_groups, va_groups = unique_user_ids[tr_group_idx], unique_user_ids[va_group_idx]

    #각 샘플의 고객 ID가 train/valid 중 어느 쪽에 속해 있느냐에 따라 분할
    is_tr = user_id.isin(tr_groups)
    is_va = user_id.isin(va_groups)
    tr_x, va_x = train_x[is_tr], train_x[is_va]
    tr_y, va_y = train_y[is_tr], train_y[is_va]

#(참고)GroupKFold 클래스에서는 셔플과 난수 시드를 지정할 수 없으므로 사용하기 어려움
kf = GroupKFold(n_splits=4)
for tr_idx, va_idx in kf.split(train_x, train_y, user_id):
    tr_x, va_x = train_x.iloc[tr_idx], train_x.iloc[va_idx]
    tr_y, va_y = train_y.iloc[tr_idx], train_y.iloc[va_idx]

LOO(Leave-one-out) 검증

경진 대회에서는 드문 경우이지만 학습 데이터의 데이터 수가 극히 적을 때가 있다.

데이터가 적으면 가능한 한 많은 데이터를 사용하려 하고 학습에 걸리는 연산 시간도 짧으므로 폴드 수를 늘리는 방법을 고려할 수 있다.

  • Kfold 클래스에서 n_splits 인수에 데이터 행의 수를 지정하기만 하면 되지만 LOO검증을 수행하는 LeaveOneOut 클래스도 있다.
#leave-one-out

#데이터가 100건밖에 없는 것으로 간주
train_x = train_x.iloc[:100, :].copy()

from sklearn.model_selection import LeaveOneOut

loo = LeaveOneOut()
for tr_idx, va_idx in loo.split(train_x):
    tr_x, va_x = train_x.iloc[tr_idx], train_x.iloc[va_idx]
    tr_y, va_y = train_y.iloc[tr_idx], train_y.iloc[va_idx]
  • 추가로, LOO검증의 경우 GBDT나 신경망과 같이 순서대로 학습을 진행하는 모델에서 조기종료를 사용하면 검증 데이터에 가장 최적의 포인트에서 학습을 멈출 수 있어 모델의 성능이 과대 평가된다.

시계열 데이터의 검증 방법

시계열 데이터의 홀드아웃 검증

  • 시계열을 고려하여 검증하는 간단한 방법은 학습데이터 중에 테스트 데이터와 가장 가까운 기간을 검증 데이터로 삼는 방법이다.

슬라이드4_6_image

Train으로 학습한 모델에서 valid를 예측하고 그 점수로 평가한다.

  • Train: 학습 데이터 중에 검증에서의 학습에 사용하는 데이터
  • valid: 검증 데이터

  • 테스트 데이터에 가장 가까운 기간의 데이터를 검증 데이터로 삼음으로써 테스트 데이터에 대한 예측 성능이 높아질 것을 기대한다. 다만!!! 주기성을 갖는다면 데이터 나눌 때 이를 고려해야한다.

  • 시계열 데이터의 홀드아웃 검증을 실행할 때는 함수 등이 특별히 준비되어 있지는 않으므로 다음 예제 코드처럼 직접 지정하고 나눕니다.
#데이터 등의 사전 준비

import numpy as np
import pandas as pd

#train_x는 학습 데이터, train_y는 목적 변수, test_x는 테스트 데이터
#pandas의 DataFrame, Series로 유지합니다.(numpy의 array로 유지하기도 합니다)

train = pd.read_csv('C:/Users/Administrator/Desktop/2021년 1학기/tave동아리/후반기 활동/ppt/input/train_preprocessed.csv')
train_x = train.drop(['target'], axis=1)
train_y = train['target']
test_x = pd.read_csv('C:/Users/Administrator/Desktop/2021년 1학기/tave동아리/후반기 활동/ppt/input/test_preprocessed.csv')

#시계열 데이터이며, 시간에 따라 변수 period를 설정했다고 함
train_x['period'] = np.arange(0, len(train_x)) // (len(train_x) // 4)
train_x['period'] = np.clip(train_x['period'], 0, 3)
test_x['period'] = 4
#시계열 데이터의 홀드아웃(hold-out)방법
#변수 period를 기준으로 분할하기로 함(0부터 2까지 학습 데이터, 3이 테스트 데이터)
#변수 period가 1, 2, 3의 데이터를 각각 검증 데이터로 하고 그 이전 데이터를 학습에 사용
is_tr = train_x['period'] < 3
is_va = train_x['period'] == 3
tr_x, va_x = train_x[is_tr], train_x[is_va]
tr_y, va_y = train_y[is_tr], train_y[is_va]

시계열 데이터의 교차 검증(1)

슬라이드4_8_image

  • 각 폴드에서의 학습 데이터 기간은 제공된 학습데이터의 가장 처음부터 지정할 수 도 있고 검증 데이터 직전의 1년간으로 지정할 수도 있다.

만약!! 제공된 학습 데이터의 처음부터 지정할 때는 폴드마다 학습 데이터의 길이가 다르다는 점에 유의해야 한다.

슬라이드4_9_image

  • 이 방법에서 우려되는 문제는 일정 시점 이상의 오래된 데이터를 검증 데이터로 삼으면 해당 검증 데이터보다 과거의 학습 데이터만 쓸 수 있는 만큼 실제로 사용할 학습 데이터가 적어진다는 점이다.

  • 또한 오래된 데이터는 테스트 데이터와 성질이 달라 참고가 되지 않을 수도 있다.

  • 사이킷런에는 TimeSeriesSplit 클래스가 준비되어 있으나 데이터의 나열 순서만으로 나눌 뿐 시간 정보로 나눠주지는 않으므로 사용할 수 있는 부분은 한정적이다.

#시계열 데이터의 교차 검증(시계열에 따라 시행하는 방법)

#변수 period를 기준으로 분할(0부터 2까지가 학습 데이터, 3이 테스트 데이터)
#변수 period가 1, 2, 3의 데이터를 각각 검증 데이터로 하고 그 이전 데이터를 학습에 사용

va_period_list = [1, 2, 3]
for va_period in va_period_list:
    is_tr = train_x['period'] < va_period
    is_va = train_x['period'] == va_period
    tr_x, va_x = train_x[is_tr], train_x[is_va]
    tr_y, va_y = train_y[is_tr], train_y[is_va]

#(참고)periodSeriesSplit의 경우, 데이터 정렬 순서밖에 사용할 수 없으므로 쓰기 어려움
from sklearn.model_selection import TimeSeriesSplit

tss = TimeSeriesSplit(n_splits=4)
for tr_idx, va_idx in tss.split(train_x):
    tr_x, va_x = train_x.iloc[tr_idx], train_x.iloc[va_idx]
    tr_y, va_y = train_y.iloc[tr_idx], train_y.iloc[va_idx]

시계열 데이터의 교차 검증(2)

  • 데이터에 따라서는 행 데이터의 시간적인 전후 관계보다는 행 데이터 간의 시간상 가까운 정도에만 주의해도 충분할 때가 있다. 그런 경우 검증 데이터보다 미래의 데이터를 학습 데이터에 포함해도 문제가 없으므로 단순히 시간상으로 구분해 분할하는 방법을 채택할 수 있다.

슬라이드4_11_image

#시계열에 따라 실행하는 방법과의 차이점은 검증 데이터 이전의 데이터가 아닌, 검증 데이터 이외의 학습 데이터 전체를 사용한다는 점이다.

#시계열 데이터의 교차 검증(단순하게 시간으로 분할하는 방법)

#변수 period를 기준으로 분할(0부터 3까지가 학습 데이터, 3이 테스트 데이터).
#변수 period가 0, 1, 2, 3인 데이터를 각각 검증 데이터로 하고, 그 이외의 학습 데이터를 학습에 사용

va_period_list = [0, 1, 2, 3]
for va_period in va_period_list:
    is_tr = train_x['period'] != va_period
    is_va = train_x['period'] == va_period
    tr_x, va_x = train_x[is_tr], train_x[is_va]
    tr_y, va_y = train_y[is_tr], train_y[is_va]

시계열 데이터 검증의 주의점

  • 시계열 데이터에서는 문제의 설계나 데이터 성질, 분할되는 방법에 따라 수행할 검증이 달라진다.

Note!!

‘시계열에 따라 시행하는 방법’과 ‘단순히 시간으로 분할하는 방법’ 중 어느 쪽을 선택해야 할까?

만약 ‘단순히 시간으로 분할하는 방법'을 택한다면

  1. 과거 정보로부터 미래 경향을 예측할 수 있어서 모델 성능이 높은 모델
  2. 단순히 과거와 미래의 목적변수와의 평균적인 예측을 수행함으로써 모델 성능이 높은 모델간의 구별이 어려울 수 있다.

즉!! ‘시계열에 따라 시행하는 방법‘이 더 안전하므로 이 방법을 권장한다.

다만) 목적변수가 과거의 목적변수 정보를 많이 갖지 않는 데이터일 때나,

이 방법으로는 사용 가능한 데이터가 적어 충분한 데이터로 학습하지 못할 때는 ‘단순히 시간으로 분할하는 방법'이 더 효과적일 수 있다.

  • 시계열 데이터 검증의 대략적인 방침

데이터가 충분하다면 ‘시계열에 따라 시행하는 방법'의 교차 검증이 좋다.

캐글의 Recruit Restaurant Visitor Forecasting 대회

  • 음식점의 미래 방문객 수를 예측하는 문제 학습데이터 기간: 2016/1/1~2017/4/22 테스트 데이터 기간: 2017/4/23~2017/5/31

주의)) 필자는 기본적으로 학습 데이터의 마지막 4주간 데이터 중에 예측일의 요일과 일치하는 날짜만 검증 데이터로써 이용했다. 예) 2017/4/23을 예측하는 모델을 구축할 때는 3/26, 4/9, 4/16을 검증 데이터로 삼아 모델을 평가했다.

다만!! 시간적인 경향 변화가 큰 데이터에서는 지나치게 먼 과거의 데이터까지 검증 대상에 포함하면 과거 데이터에 대한 평가 비중이 커지므로 테스트 데이터에서의 일반화 성능이 떨어질 우려가 있다.

최종: 테스트 데이터의 예측 모델을 최종 구현할 때는 예측 대상에 더 가까운 날짜도 학습 데이터에 포함하기 위해 검증 데이터로 정한 기간의 데이터도 포함하여 모델을 다시 구현했다.

캐글의 Santander Product Recommendation 대회

산탄데르 은행(Santander Bank)의 고객별 구매 금융 상품을 예측하는 문제이다.

학습 데이터 기간: 2015/2~2016/5 예측 대상 월: 2016/6

필자의 방법: 과거 데이터를 한꺼번에 사용하는 대신 2016년 4월,3월의 월별 데이터를 활용해 여러 개의 모델을 만들고 그들을 앙상블 하였다. 이때 예측 대상에 가장 가까운 2016년 5월 데이터를 검증 데이터로 이용했다.

필자는 시간적인 순서가 역전되는 것을 알면서도 2016년 4월 데이터를 검증 데이터로 삼아 2016년 5월 데이터로 학습하고 앙상블에 추가했다. 얼핏 위험해 보이기도 하지만 데이터 정보 누출이 일어나지 않도록 주의하면 이런 작업도 가능하다.

  • 이 경진 대회의 가장 큰 포인트는 복수의 금융 상품을 각각 예측해야 하며 상품에 따라서는 연단위의 주기성이 강하게 나타나기도 한다는 점이다.

Leave a comment