数学的にどうなのさ?

大学時代にちょっと長く数学を勉強した人の雑記。数学のこと(主に統計)や趣味、メモなどが多くなります

時系列予測を自前のパチスロデータでやってみる

概要

やりたいことはとあるパチ屋のとあるパチスロ台の翌日の差枚がどの程度になるのかを予測することである.最近どこかでAIを活用してこういう予測をやっている,というようなことを聞いた気がするが,将来的にやりたいことはまさにコレ.ただ個人的な予想では高精度な予測ができるとは思っていない.とはいえ既存の手法だけでどこまで予測できるのかは気になるのでやってみる.
ただしデータ分析事態は仕事としてやっているが,時系列データの将来予測に関しては完全に素人なのでツッコミどころがあるかもしれない記事になっている,ということを頭に入れておいてください.

実行環境

利用するデータ

今回扱うデータはその辺に転がっているサンプルデータではなく,私が個人的に集めているとある店のデータ約半年分のデータを使う.具体的には2019年11月30日から2020年7月3日までのデータとなる.
データの構成は台の名前,台番号,日付,差枚などがあるが,今回扱うのは日付と差枚のみ.
予測するにあたり本来であれば他の台の状況を加味する必要がある,店への取材や来店の有無,強い日なのかどうかとか加味する必要がある.また打ち手が勝ちやすい設定であっても気づかれないまま放置されたり,逆に低設定でも終日フル稼働したりとかなりのバラツキがあることが予想される.また新台導入による島配置の変化や減台など影響があると思われる要素は多くあり,これが原因で高精度な予測はできないと思われる.
とりあえず,まずは今回はある1機種のデータだけに絞り込み,このデータだけで将来のデータを予測できるかをやってみる.

データ準備

時系列予測と言えば一番有名と思われるのはARIMAモデル.まずはこちらを使って行ってみる.まずはパッケージの読み込みとJupyterでのグラフ表示の設定.

import statsmodels.api as sm
from statsmodels.tsa.arima_model import ARIMA
import pandas as pd
import numpy as np
import requests
import io
from matplotlib import pylab as plt
%matplotlib inline
# グラフを横長にする
from matplotlib.pylab import rcParams
rcParams['figure.figsize'] = 15, 6

データの読み込み部分はある程度省略するとして,気を付ける必要があるのが日付部分.これはcsvファイルから読み込んだ場合,日付は文字列として読み込まれるのだが,いったん日付型に変化させる必要がある.なので

df = df..assign(
    yyyymmdd = lambda df: pd.DatetimeIndex(df['yyyymmdd'])
)

といった感じで型を変えておく必要がある.これをset_indexを使って行名称にセットしておく.で,最終的に学習データとなるものは以下のようになった.

data_s
#yyyymmdd
#2019-11-30      62.0
#2019-12-01    1104.0
#2019-12-02    -541.0
#2019-12-03    2229.0
#2019-12-04    -312.0
#               ...  
#2020-02-25   -1770.0
#2020-02-26    1083.0
#2020-02-27    2895.0
#2020-02-28    -604.0
#2020-02-29   -1104.0
#Name: 最終差枚, Length: 92, dtype: float64

これはある機種の差枚データを2019年11月30日から2020/2/29までのデータ.この機種に関しては2019年11月30日以降同じ場所にあり続けている.これを使って3月のデータを予測し,制度を確認することを行う.なぜ7月までデータがあるのに分析に使うのは3月までなのかというと,4月以降はコロナウイルス騒動が社会的に大きな影響を与えており,パチ屋もその例外ではありません.そもそも休みの期間があるので,データが少ないのです….なので4月以降のデータは平常時とは異なる異常な期間であると考えられるので,学習にも精度確認にも使えないということになる.

まずはARIMAモデル

まずは自己相関の確認.とりあえず曜日毎に特色があると思われる節があるのでlagsは7あたりを設定してみる。

fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(211)
fig = sm.graphics.tsa.plot_acf(data_s1['最終差枚'], lags=7, ax=ax1)
ax2 = fig.add_subplot(212)
fig = sm.graphics.tsa.plot_pacf(data_s1['最終差枚'], lags=7, ax=ax2)

f:id:lua0810:20200704170733p:plain
特に周期性はなさそう.とりあえずARIMAを行ってみる.まずは次数の最適値を求める.

res = sm.tsa.arma_order_select_ic(data_s1, ic='aic', trend='nc')
#{'aic':              0            1            2
# 0          NaN  1566.452252  1567.900794
# 1  1566.572125  1567.946135  1569.773868
# 2  1568.059867  1567.350800  1568.655904
# 3  1568.287658  1568.759582  1570.462734
# 4  1568.988960  1570.381890  1572.189104,
# 'aic_min_order': (0, 1)}

次数は'aic_min_order': (0, 1)が良いようなので,これを使う.

arima_model = ARIMA(data_s1, order=(0, 1, 1)).fit(dist=False)

では次にARIMAを使った結果の予測が周期性がないか確認する.

resid = arima_model.resid
fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(211)
fig = sm.graphics.tsa.plot_acf(resid.values.squeeze(), lags=7, ax=ax1)
ax2 = fig.add_subplot(212)
fig = sm.graphics.tsa.plot_pacf(resid, lags=7, ax=ax2)

f:id:lua0810:20200704174405p:plain
大丈夫そう.では予測をプロットしてみる.

pred = arima_model.predict('2020-02-20', '2020-03-31')
plt.plot(data_s1)
plt.plot(pred, "r")

f:id:lua0810:20200704174607p:plain
全然当たってるようには見えない…というか後半の予想が0になってる.実際に3月分の出玉データと並べてプロットしてみる.
f:id:lua0810:20200704174749p:plain
3月1日の出玉データはほぼ一致しているが次の日以降がすべて0と予想している.一応次の日の予測自体はできるということだろうか?

LightGBMを使って予測できるか試してみる

会社の上司から,「ARIMAもSARIMAも精度悪い.これなら過去数日分から適当なモデル使って予測モデル立てた方がいいんじゃないのか?」と言われたことを思い出し,試しにRightGBMを使ってやってみる.データはARIMAで使ったデータではなく,以下のようなデータを準備して行った.
f:id:lua0810:20200704175328p:plain
最終差枚が予測したい値,つまりは目的変数.x1からx7まではx1なら1日前の出玉,x2なら2日前の出玉となっている.空がないようにデータを作ったので,日付は2019年12月7日以降のデータとなっている.これで過去7日分のデータから次の日のデータが予測できるのかを確認する.

デフォルトのハイパーパラメータの場合

評価関数はMSEで.

import lightgbm
from sklearn.metrics import r2_score

model = lightgbm.LGBMRegressor(objective='mse')
model.fit(df.drop('最終差枚', axis=1), df['最終差枚'])

次に学習に使ったデータの予測精度確認.

df.assign(
    y_predict=model.predict(df.drop('最終差枚', axis=1))
)[['最終差枚', 'y_predict']].plot()

f:id:lua0810:20200704182018p:plain
乖離はそこそこあるが,なんとなく波形は似たような感じになっている.では3月以降のデータを使って確かめてみる.

df_test_1.assign(
    y_predict=model.predict(df_test_1.drop('最終差枚', axis=1))
)[['最終差枚', 'y_predict']].plot()

f:id:lua0810:20200704182202p:plain
これダメじゃないか?

Grid Searchかけてやってみた場合

下記のような感じでとりあえずGrid Search.

from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score, KFold

grid_param ={
    'n_estimators':[1000,2000],
    'max_depth':[5,10,15, 20],
    'num_leaves':[31,15,7,3],
    'learning_rate':[0.1,0.05,0.01]
}

fit_params={
    'early_stopping_rounds':10, 
    'eval_metric' : 'r2', 
    'eval_set' : [(df.drop('最終差枚', axis=1), df['最終差枚'])]
}

bst_gs_cv = GridSearchCV(
    model,
    grid_param,
    cv = KFold(n_splits=3, shuffle=True),
    scoring = 'neg_mean_squared_error',
    verbose = 0
)

bst_gs_cv.fit(
    df.drop('最終差枚', axis=1), 
    df['最終差枚'],
    **fit_params,
    verbose = 0
)

先ほどと同じように学習データだけでの予測精度確認.

df.assign(
    y_predict = bst_gs_cv.predict(df.drop('最終差枚', axis=1))
)[['最終差枚', 'y_predict']].plot()

f:id:lua0810:20200704182944p:plain
気持ちマシになったような気がする.では3月のデータで確認.

df_test_1.assign(
    y_predict=bst_gs_cv.predict(df_test_1.drop('最終差枚', axis=1))
)[['最終差枚', 'y_predict']].assign(n=1).plot()

f:id:lua0810:20200704183108p:plain
ダメっぽい…

考察

今回予測があまりうまくいっていないことについては以下のことが考えられる.

  • 使っているデータが少ない
  • 週1回以上のペースで高設定になるわけではない
  • 仮に高設定であっても結果が出るとは限らない

特にデータが少ないというのが痛い.
またRightGBMに関しては過去10日以上前のデータも参照する必要があるかもしれない.
さらに序盤でも触れたとおり,差枚に影響する要素が今回持っているデータ内にはすべて揃っていないと考えられるのも原因の一つだと思われる.

今後

やってみたいことは

  1. 差枚数での予測ではなく,機械割を使ってみる
  2. 前日の全台の差枚,機械割から次の日の特定台の差枚,機械割の予測
  3. 島単位で差玉の和,平均を使って島単位での予測を行ってみる

特に3は精度が出るかもしれないと期待している部分でもあります.さすがにピンポイントでこの台番号の台,と出る台を予言するのまでは難しい気がします.