時系列データにおける交差検証用のデータ分割注意点

『前処理大全』第5章:分割より


時系列データでは、単純な交差検証は有効でない。未来のデータを使って予測モデルを作成し、過去のデータを検証検証しているケースが混ざり、不当にモデル精度が高くなってしまうため。

例えば、物件価格を予測するモデルを考える時、本来は過去のデータから予測モデルを作成し未来の価格を予測する必要がある。地区年数や物件の広さといった物件のスペックと価格の関係だけでなく、物件相場の長期変動といった要素も考えなければならない。しかし、ランダムに分割した交差検証時の学習データには未来と過去のデータが混ざっており、物件相場の長期変動を考慮しなくても精度悪化が生じない。その結果、物件相場の長期変動の変化が考慮されていない予測モデルが生成されてしまう。


対策1:

学習データと検証データを時間軸に対してスライドしながら検証する。

    • 1回目の検証:1月〜6月を学習データ、7〜8月を検証データ
    • 2回目の検証:3月〜8月を学習データ、9〜10月を検証データ
    • 3回目の検証:5月〜10月を学習データ、11〜12月を検証データ

こうすることで、未来のデータを使って予測することがなくなる。この方法の課題として、通年評価になっていないので、季節により傾向が変わる問題は検証できない。その場合は、検証データが通年にする必要がある。なので、データ量がいる。


対策2:

スライド時に学習データを徐々に増やす。

    • 1回目の検証:1月〜6月を学習データ、7〜8月を検証データ
    • 2回目の検証:1月〜8月を学習データ、9〜10月を検証データ
    • 3回目の検証:1月〜10月を学習データ、11〜12月を検証データ

この方法だと、データ期間が足りなくても検証できるが、検証期間により学習データ量が異なるため、1回めと3回めの検証時のモデル精度が異なる場合もあり、検証によって運用時のモデル精度を正確に把握することが難しい。しかし、データ量が少ない場合、この検証方法を選択することもある。その場合、モデル運用時の精度を把握するには、データ量と精度向上の関係も合わせて把握する必要がある。


例)時系列データにおける学習 / 検証データの準備

月ごとの経営指標データに対して、学習データと検証データを時間軸に対して1ヶ月ごとにスライドしながら生成する。学習期間は24ヶ月、検証期間は12ヶ月、スライドする期間は12ヶ月とする。


# train_window_startに、最初の学習データの開始行番号を指定

train_window_start = 1

# train_window_endに、最初の学習データの終了行番号を指定

train_window_end = 24

# horizonに、検証データのデータ数を指定

horizon = 12

# skipにスライドするデータ数を指定

skip = 12


# 年月に基づいてデータを並べ替え

monthly_index_tb.sort_values(by='year_month')


while True:

  # 検証データの終了行番号を計算

  test_window_end = train_window_end + horizon

  # 行番号を指定して、元データから学習データを取得

  # train_window_startの部分を1に固定すれば、学習データを増やしていく検証に変更可能

  train = monthly_index_tb[train_window_start:train_window_end]

  # 行番号を指定して、元データから検証データを取得

  test = monthly_index_tb[(train_window_end + 1):test_window_end]

  検証データの終了番号が元データの行数以上になっているか判定

  if test_window_end >= len(monthly_index_tb.index):

    # 全データを対象にした場合は終了

    break

  # データをスライドさせる

  train_window_start += skip

  train_window_end += skip

# 交差検証の結果をまとめる

〜〜省略〜〜


注:

学習データ数を固定せずに増やしていく分割は、sklearnのTimeSeriesSplitクラスで実現できる。tscv=TimeSeriesSplit(n_splits=分割数)でオブジェクトを生成し、tscv.split(monthly_index_tb)のようにsplit関数を呼び出し、分割したデータを取り出す。

split関数の返り値はリストなどに格納せず、for文の中などで逐次呼び出し処理するように記述する。一度リストに格納すると分割したデータの全てのパターンをメモリ上に保持することになるが、逐次呼び出し処理することでメモリの使用量を節約できる。



補:

  • ホールドアウト検証:交差検証用のデータとは別にプライベートなデータを予め準備しておき、最後にこのデータを使ってモデルの精度を検証する方法。


例)製造レコードのデータを用いて、データ分割。20%をホールドアウト検証用のテストデータとして確保し、残りのデータを交差数4の交差検証を行う。

from sklearn.model_selection import train_test_sprit
from sklearn.model_selection import KFold


# ホールドアウト検証用のデータ分割

# 予測モデルの入力値と予測対象の値を別々にtrain_test_splitに設定

# test_sizeは検証データの割合

train_data, test_data, train_target, test_target = \

  train_test_split(production_tb.drop('faul_flg', axis=1),
         production_tb[['fault_flg']],

                               test_size=0.2)

# 行名を現在の行番号に振り直す

train_data.reset_index(inplace=True, drop=True)

test_data.reset_index(inplace=True, drop=True)

train_target.reset_index(inplace=True, drop=True)

test_target.reset_index(inplace=True, drop=True)


# 対象の行番号リストを生成

row_no_list = list(range(len(train_target))


# 交差検証用のデータ分割

k_fold = KFold(n_splits=4, shuffle=True)

# 交差数分繰り返し処理、並列処理も可能な部分

for train_cv_no, test_cv_no in k_fold.split(row_no_list):

  # 交差検証における学習データ抽出

  train_cv = train_data.iloc[train_cv_no, :]

  # 解答データ抽出

  train_target_cv = train_target.iloc[train_cv_no, :]

  # 交差検証における検証データ抽出

  test_cv = train_data.iloc[test_cv_no, :]

  # 解答データ抽出

  test_target_cv = test_target.iloc[text_cv_no, :]


  # train_dataとtrain_targetを学習データ、

  # test_dataとtest_targetを検証データとして機械学習モデルの構築、検証

  svc = SVC()

  svc.fit(train_cv, train_target_cv)

  print("Accuracy on training set: {:3f}'.format(svc.score(train_cv, train_target_cv)))
       print("Acuuracy on test set: {:.3f}".format(svc.score(test_cv, test_target_cv)))


# 交差検証の結果をまとめる

# trainを学習データ、private_testを検証データとして機械学習モデルの構築、検証

~~省略~~


機械学習Tips保管庫

データ解析、機械学習のための学習内容の保管庫。復習用。

0コメント

  • 1000 / 1000