見出し画像

[Python]機械学習でスロットの設定予測をしてみた

はじめに

こんにちは、機械学習勉強中のつのつきです。
学習の成果として、投稿します。
初学者ゆえ、至らないところが多いかと思いますが、アドバイス等いただけると幸いです。

目的

スロットの設定を機械学習で予想し、大儲けする。
対象店舗:エスパス日拓上野新館
対象機種:マイジャグラーⅣ

スロットの設定って?

パチスロにおける設定(せってい)とは、パチスロ機のボーナス・小役等の確率を(あらかじめプログラムされた範囲内で)変更し、最終的な出玉率(機械割)を上下させることができる機能のこと。(Wikipedia)

スロットというと絶対に負けるもののイメージがありますが、設定が良いスロットを打てば理論上客が絶対に勝てます。(=多くの玉が出る)
スロット店はこの設定機能を利用して、店全体でどのくらい収益を得るか、または客に還元をするか設定しています。
もちろん、すべてのスロットに良い設定を使うと赤字になってしまうので設定が入る日や機種は限られます。
今回は、機械学習でスロット店の傾向を把握しどのスロットに設定が入るか予測しようとするものです。

手順

1.データの収集
2.説明変数の作成・追加
3.データの特徴を把握する
4.データを読み込んで設定判別機を作成する
5.精度を上げるためにもがく
6.2値分類でモデル作成
言語はPython、環境はGoogle Colaboratoryを使用しました。

1.データの収集

スロットの設定予測用のデータ収集に当たり、スクレイピングの方法について検討しました。

収集サイト

サイトセブン
スロットの情報サイトといえば、サイトセブンが有名ですが、規約でスクレイピングなどでの情報抽出が禁止されているため断念。また、ログインの仕組みが特殊でどうすればよいかわからなかったため今後学習していきたいです。


台データオンライン
サイトセブンに比べ、掲載店舗は少ないですが、同様の情報が掲載されているサイト。ログインもなく、平易にデータ収集できることから、こちらでデータを収集することに。

収集方法

当初beautiful soup4で収集をしようと試みたが、平易に書けることからpandasのread_html関数を使用することに。
台データオンラインは、各店舗の対象機種のURLの数字(hist_num)を変えることで過去のデータを確認できる。
サイト上では、一週間しか遡れないが、URLを直接変更することで99日前まで遡れます。

#データの取得
! pip install lxml html5lib beautifulsoup4
import pandas as pd

cols = ["Unnamed: 0" ,"台番号" ,"累計スタート" ,"BB回数" ,"RB回数" ,"ART回数" ,"最大持玉", "BB確率", "RB確率" ,"ART確率", "合成確率", "前日最終スタート","date"] #列名
main_data = pd.DataFrame(columns= cols)

#for文で99日前までのデータを取得
for i in range(99):
 d_today = datetime.date.today() #データ取得日
 data_day = d_today - datetime.timedelta(days=i) #データの日付
 target_url = "https://daidata.goraggio.com/100196/unit_list?hist_num="f"{i}&model=%EF%BE%8F%EF%BD%B2%EF%BD%BC%EF%BE%9E%EF%BD%AC%EF%BD%B8%EF%BE%9E%EF%BE%97%EF%BD%B0IV&ballPrice=21.70&disp=1&graph=1"
 fetched_dataframes = pd.read_html(target_url,encoding='utf-8')[0]
 fetched_dataframes["date"] = data_day
 main_data = pd.concat([main_data,fetched_dataframes])
main_data.reset_index() 
print(main_data)
main_data.to_csv('/content/main_data.csv')

取得データイメージ

画像1

参考
Python, pandasでwebページの表(htmlのtable)をスクレイピング |

2.説明変数の作成・追加

収集したデータを確認すると、実際に設定予測に使えるデータ・特徴量は少ない。例えば、BB回数、RB回数など目的変数となるデータが多く、説明変数となるデータは多くないです。
そのため、日付データや台番号から収集したデータを確認し、必要な特徴量を作成しました。また、直接店舗で台の設置している箇所を確認し、角台かどうかも説明変数とすることとしました。
併せて、今回の目的は設定推測のため、RB(=レギュラーボーナス)確率から設定を算出し設定列を作成します。

・日付データから説明変数作成(曜日末尾・ゾロ目)

スロットの設定は、土日などの曜日やラッキーセブンで7日、ゾロ目日などで影響される可能性があるため説明変数としてみました。

import pandas as pd

main_data = pd.read_csv('/content/main_data.csv')

main_data['year'] = main_data['date'].dt.year
main_data['month'] = main_data['date'].dt.month
main_data['day'] = main_data['date'].dt.day
main_data['weekday'] = main_data['date'].dt.weekday
main_data['day_end'] = main_data['day'].astype(str).str[-1:].astype(int) #日付末尾
main_data['day_end_2'] = main_data['day'].astype(str).str[:1].astype(int) #日付2桁目
main_data['day_zoro'] = 0 
main_data.loc[main_data['day_end']==main_data['day_end_2'],'day_zoro'] = 1 #ゾロ目を1とする

・台番号から説明変数作成(末尾・ゾロ目)

曜日と同じく、末尾やゾロ目が影響する可能性があるため説明変数とします。

main_data['machine_end'] = main_data['台番号'].astype(str).str[-1:].astype(int) #台番号末尾
main_data['machine_end_2'] = main_data['台番号'].astype(str).str[:1].astype(int) #台番号2桁目
main_data['machine_zoro'] = 0
main_data.loc[main_data['machine_end']==main_data['machine_end_2'],'machine_zoro'] = 1 #台番号ゾロ目を1とする

・設定列の作成
今回対象としているマイジャグラーⅣの設定ごとのボーナス確率は以下のとおりです。

設定  BIG  REG  合算
1   1/287 1/431 1/172
2   1/282 1/364 1/159
3   1/273 1/341 1/152
4   1/264 1/293 1/139
5   1/252 1/278 1/132
6   1/241 1/241 1/120
このように、REGの設定差が大きいため、REG確率(RB確率)から設定を割り出します。なお、回転数が極端に少ないものは設定0として扱いました。(この取扱が正しいかは要検討だと思っています。)

#分数を変換
main_data['RB確率'] = main_data['RB確率'] .str.replace('1/','')
main_data['RB確率']=main_data['RB確率'].astype('float64')

#一旦すべて設定1とする
main_data['setting'] = 1
main_data.loc[main_data['RB確率'] <= 364.1,'setting'] = 2
main_data.loc[main_data['RB確率'] <= 341.3,'setting'] = 3
main_data.loc[main_data['RB確率'] <= 292.6,'setting'] = 4
main_data.loc[main_data['RB確率'] <= 277.7,'setting'] = 5
main_data.loc[main_data['RB確率'] <= 240.9,'setting'] = 6
main_data.loc[main_data['累計スタート'] < 2000,'setting'] = 0 #回転数が2000未満は設定0として扱う
print(main_data['setting'])

・角台データの結合
実際にホールに行き、角台、角2台、島中にある台に分けて以下のようなイメージでデータ収集をしました。

画像2

このデータを台のデータに結合させる

kado_df = pd.read_csv('/content/ueno_shinkan_myjg_kadodata.csv')

train_df =pd.merge(main_data,kado_df,
                       how= 'left',
                       on = '台番号')

完成データ

画像3

3.データの特徴を把握する

・設定の比率の確認
設定1~6までどのくらいの割合でお店が投入しているのか確認しました。

#設定の比率
n_target0, n_target1,n_target2,n_target3,n_target4,n_target5,n_target6= len(train_df[train_df['setting_0'] == 0]), len(train_df[train_df['setting_0'] == 1]) , len(train_df[train_df['setting_0'] == 2]), len(train_df[train_df['setting_0'] == 3]), len(train_df[train_df['setting_0'] == 4]), len(train_df[train_df['setting_0'] == 5]), len(train_df[train_df['setting_0'] == 6])
n_all = n_target0+n_target1+n_target2+n_target3+n_target4+n_target5+n_target6
print('回転数不足 の割合 :', n_target0/n_all) # target0の割合
print('設定1 の割合 :', n_target1/n_all) # target1の割合
print('設定2 の割合 :', n_target2/n_all) # target2の割合
print('設定3 の割合 :', n_target3/n_all) # target3の割合
print('設定4 の割合 :', n_target4/n_all) # target4の割合
print('設定5 の割合 :', n_target5/n_all) # target5の割合
print('設定6 の割合 :', n_target6/n_all) # target6の割合

#データの均衡確認
train_df['setting_0'].hist()
plt.xlabel("setting")
plt.ylabel("number of setting")

回転数不足 の割合 : 0.1961926961926962
設定1 の割合 : 0.37606837606837606
設定2 の割合 : 0.07148407148407149
設定3 の割合 : 0.1794871794871795
設定4 の割合 : 0.046620046620046623
設定5 の割合 : 0.08896658896658896
設定6 の割合 : 0.041181041181041184

画像8

…案の定、設定1と思われる割合が高いですね笑

4.データを読み込んで設定判別機を作成する

作成したデータをLightGBMに読み込んで設定判別機を作成します。LightGBMは専用のデータセットに入れることで精度が上がりますが、トレーニングデータとテストデータで設定の比率が異なっていると精度に影響がでる可能性があるため、設定の比率を考慮してデータを分割します。

X = train_df.drop(['setting_0','date_0'],axis=1).values
y = train_df['setting_0'].values
columns_name = train_df.drop(['setting_0','date_0'],axis=1).columns
sss = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=0) 

def data_split(X,y):
   for train_index, test_index in sss.split(X, y):
       X_train, X_test = X[train_index], X[test_index]
       y_train, y_test = y[train_index], y[test_index]

   X_train = pd.DataFrame(X_train, columns=columns_name)
   X_test = pd.DataFrame(X_test, columns=columns_name)

   return X_train, y_train, X_test, y_test

X_train, y_train, X_test, y_test = data_split(X, y)
X_train, y_train, X_val, y_val = data_split(X_train.values, y_train)

print("train shape", X_train.shape)
print("test shape", X_test.shape)
print("validation shape", X_val.shape)

plt.figure(figsize=(20,5))
plt.subplot(1,3,1)
plt.hist(y_train)

plt.subplot(1,3,2)
plt.hist(y_test)

plt.subplot(1,3,3)
plt.hist(y_val)

同じ比率で分割できたためLightGBMにかけます。


import lightgbm as lgb
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold

lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)

lgb_params = {
             'task': 'train',
             'boosting_type': 'gbdt',
             'objective': 'multiclass', # 目的 : 多クラス分類 
             'num_class': 7, # クラス数 : 7
             'metric': {'multi_error'}, # 評価指標 : 誤り率(= 1-正答率) # 他には'multi_logloss'など
             
             }


model = lgb.train(lgb_params,
                 train_set=lgb_train, # トレーニングデータの指定
                 valid_sets=lgb_eval, # 検証データの指定
                 )

# テストデータの予測 ((各クラスの予測確率 を返す))
y_pred_prob = model.predict(X_test)
# テストデータの予測 (予測クラスを返す)
y_pred = np.argmax(y_pred_prob, axis=1) # 一番大きい予測確率のクラスを予測クラスに


lgb.plot_importance(model, height=0.5, figsize=(8,16))
# 真値と予測値の表示
df_pred = pd.DataFrame({'target':y_test,'target_pred':y_pred})
display(df_pred)

# 真値と予測確率の表示
df_pred_prob = pd.DataFrame({'y':y_test, 'target0_prob':y_pred_prob[:,0], 'target1_prob':y_pred_prob[:,1], 'target2_prob':y_pred_prob[:,2], 'target3_prob':y_pred_prob[:,3]
                            , 'target4_prob':y_pred_prob[:,4], 'target5_prob':y_pred_prob[:,5], 'target6_prob':y_pred_prob[:,6]})
display(df_pred_prob)
---------------------------------------------


target	target_pred
0	2	0
1	3	3
2	6	2
3	3	1
4	0	1
...	...	...
510	3	3
511	1	0
512	1	3
513	3	1
514	1	1
515 rows × 2 columns

y	target0_prob	target1_prob	target2_prob	target3_prob	target4_prob	target5_prob	target6_prob
0	2	0.480413	0.286865	0.006834	0.131844	0.083005	0.004156	0.006884
1	3	0.038130	0.156935	0.015151	0.383138	0.338236	0.054975	0.013434
2	6	0.128672	0.245906	0.452244	0.107031	0.005746	0.051226	0.009175
3	3	0.054992	0.528387	0.210062	0.138029	0.023992	0.039878	0.004662
4	0	0.001040	0.633474	0.139998	0.066133	0.030508	0.127240	0.001607
...	...	...	...	...	...	...	...	...
510	3	0.203742	0.157528	0.036073	0.320320	0.181503	0.098114	0.002720
511	1	0.499835	0.266536	0.003051	0.156390	0.013484	0.042635	0.018070
512	1	0.049924	0.330893	0.023030	0.350241	0.001498	0.239929	0.004485
513	3	0.145888	0.434112	0.259860	0.017157	0.001428	0.140386	0.001169
514	1	0.265602	0.469055	0.059789	0.053346	0.004056	0.138549	0.009602
515 rows × 8 columns
# モデル評価
# acc : 正答率
acc = accuracy_score(y_test,y_pred)
print('Acc :', acc)

------------------------------------------

Acc : 0.3106796116504854

正答率では30%の精度が出ました。回転数不足を含めて7クラス分類のためなかなかの精度かも知れません。

参考
機械学習実践】LightGBMで回帰してみる【Python】
【初心者向け】LightGBM (多クラス分類編)【Python】【機械学習】

5.精度を上げるためにもがく

精度を上げるためにどのような特徴量を用意したらよいか考える中で、自己相関が見られないか確認しました。
とても簡単に言えば、昨日や一昨日、もしくは7日前など過去の設定が今日の設定に影響を与えていないかということです。(据え置きや一週間周期で設定を入れてる台があるのでは…?)

df_check = pd.read_csv('/content/train_df.csv')
df_check_dup = df_check[~df_check.duplicated(subset='Number')]
print(df_check_dup.head())

all_dfs = []
for index,item in df_check_dup.iterrows():
 df_plot= df_check[df_check['Number'] == item['Number']][['Number','date','setting']].sort_values('date')
 all_dfs.append(df_plot)

autocorrelation_plot(all_dfs[1]['setting'])
plot = [autocorrelation_plot(all_dfs[i]['setting']) for i in range(15)]

画像6

…どれも相関係数が0.25を超えていないため、自己相関はあまり見られないですね…
ただ、直近の日付のほうが若干ですが相関係数が高いため、直近一週間のデータを説明変数に入れてみることに(前日までの回転数や大当たり回数も説明変数に加えられる)

main_data = pd.read_csv('/content/drive/MyDrive/Aidemy_data/エスパス上野/main_data.csv')

#分数を変換
main_data['RB確率'] = main_data['RB確率'] .str.replace('1/','')
main_data['BB確率'] = main_data['BB確率'] .str.replace('1/','')
main_data['RB確率']=main_data['RB確率'].astype('float64')

#一旦すべて設定1とする
main_data['setting'] = 1
main_data.loc[main_data['RB確率'] <= 364.1,'setting'] = 2
main_data.loc[main_data['RB確率'] <= 341.3,'setting'] = 3
main_data.loc[main_data['RB確率'] <= 292.6,'setting'] = 4
main_data.loc[main_data['RB確率'] <= 277.7,'setting'] = 5
main_data.loc[main_data['RB確率'] <= 240.9,'setting'] = 6
main_data.loc[main_data['累計スタート'] < 2000,'setting'] = 0 #回転数が2000未満は設定0として扱う

#カラム名を英語にしておく
from googletrans import Translator

eng_columns = {}
columns = main_data.columns
translator = Translator()

for column in columns:
   eng_column = translator.translate(column).text
   eng_column = eng_column.replace(' ', '_')
   eng_columns[column] = eng_column

main_data.rename(columns=eng_columns, inplace=True)
print(main_data.head())

#前日データ等を入れる
test = main_data.drop(main_data.columns[[0,1]], axis = 1)
df_check_dup = test[~test.duplicated(subset='Number')] #台番号すべて抽出

all_dfs = []
for index,item in df_check_dup.iterrows():
 df_test= test[test['Number'] == item['Number']].sort_values('date')

 arr =[]
 for i in range(7):
   x = df_test.shift(i).add_suffix('_{0}'.format(i))
   arr.append(x)
 merge_df = pd.concat(arr,axis = 1)
 all_dfs.append(merge_df)
 
all_data = pd.concat(all_dfs,join='outer')
all_data.to_csv('/content/all_data.csv')

main_data = pd.read_csv('/content/drive/MyDrive/Aidemy_data/エスパス上野/all_data.csv')
main_data = main_data.drop(train_df.columns[22::20],axis=1)
main_data['machine_end'] = main_data['Number_0'].astype(str).str[-1:].astype(int) #台番号末尾
main_data['machine_end_2'] = main_data['Number_0'].astype(str).str[-2].astype(int) #台番号2桁目
main_data['machine_zoro'] = 0
main_data.loc[main_data['machine_end']==main_data['machine_end_2'],'machine_zoro'] = 1 #台番号ゾロ目を1とする

kado_df = pd.read_csv('/content/drive/MyDrive/Aidemy_data/エスパス上野/ueno_shinkan_myjg_kadodata.csv')

#角台データの結合
kado_merge_df =pd.merge(main_data,kado_df,
                       how= 'left',
                       on = 'Number_0')
kado_merge_df.to_csv('/content/kado_merge_df.csv')

#不要なデータ削除
train_df = pd.read_csv('/content/kado_merge_df.csv')
train_df = train_df.drop(train_df.columns[[0,1,3,4,5,6,7,8,9,10,11,12]], axis=1)
train_df = train_df.sort_values( "date_0")
train_df.to_csv('/content/drive/MyDrive/Aidemy_data/エスパス上野/train_df.csv')
print(train_df.head())
#トレーニングデータ作成
X = train_df.drop(['setting_0','date_0'],axis=1).values
y = train_df['setting_0'].values
columns_name = train_df.drop(['setting_0','date_0'],axis=1).columns
sss = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=0) 

def data_split(X,y):
   for train_index, test_index in sss.split(X, y):
       X_train, X_test = X[train_index], X[test_index]
       y_train, y_test = y[train_index], y[test_index]

   X_train = pd.DataFrame(X_train, columns=columns_name)
   X_test = pd.DataFrame(X_test, columns=columns_name)

   return X_train, y_train, X_test, y_test

X_train, y_train, X_test, y_test = data_split(X, y)
X_train, y_train, X_val, y_val = data_split(X_train.values, y_train)

print("train shape", X_train.shape)
print("test shape", X_test.shape)
print("validation shape", X_val.shape)

plt.figure(figsize=(20,5))
plt.subplot(1,3,1)
plt.hist(y_train)

plt.subplot(1,3,2)
plt.hist(y_test)

plt.subplot(1,3,3)
plt.hist(y_val)

X_train.to_csv('/content/X_train.csv')
X_test.to_csv('/content/X_test.csv')
 #LightGBM 

import lightgbm as lgb
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold
X_train = pd.read_csv('/content/X_train.csv')
X_test = pd.read_csv('/content/X_test.csv')

X_train = X_train.drop(['date_1', 'date_2', 'date_3', 'date_4', 'date_5', 'date_6'],axis=1)
X_test = X_test.drop(['date_1', 'date_2', 'date_3', 'date_4', 'date_5', 'date_6'],axis=1)

lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)

lgb_params = {
             'task': 'train',
             'boosting_type': 'gbdt',
             'objective': 'multiclass', # 目的 : 多クラス分類 
             'num_class': 7, # クラス数 : 7
             'metric': 'multi_error', # 評価指標 : 誤り率(= 1-正答率) # 他には'multi_logloss'など
             
             }


model = lgb.train(lgb_params,
                 train_set=lgb_train, # トレーニングデータの指定
                 valid_sets=lgb_eval, # 検証データの指定
                 )

# テストデータの予測 ((各クラスの予測確率 を返す))
y_pred_prob = model.predict(X_test)
# テストデータの予測 (予測クラスを返す)
y_pred = np.argmax(y_pred_prob, axis=1) # 一番大きい予測確率のクラスを予測クラスに


lgb.plot_importance(model, height=0.5, figsize=(8,16))

df_pred = pd.DataFrame({'target':y_test,'target_pred':y_pred})
display(df_pred)

# モデル評価
# acc : 正答率
acc = accuracy_score(y_test,y_pred)
print('Acc :', acc)

------------------------------------------------
Acc : 0.3983402489626556

正答率では39%ほどの精度が出ました。正答率だけ見ると、かなりいい精度に見えます。
ここで正答率だけでなく、複合的な要素から精度をチェックしたほうがいいのではないかとアドバイスをもらい、混合行列を見てみることにしました。

混合行列とは
クラス分類問題の結果から混同行列(confusion matrix)を生成したり、真陽性(TP: True Positive)・真陰性(TN: True Negative)・偽陽性(FP: False Positive)・偽陰性(FN: False Negative)のカウントから適合率(precision)・再現率(recall)・F1値(F1-measure)などの評価指標を算出したりすると、そのモデルの良し悪しを判断できる。

scikit-learnで混同行列を生成、適合率・再現率・F1値などを算出 | note.nkmk.me https://note.nkmk.me/python-sklearn-confusion-matrix-score/

最近だとコロナウイルスの偽陽性や偽陰性についてニュースなどでも取り上げられていましたが、正答率だけでなく、いうなれば真陽性率や偽陽性率についても精度判定にするということです。

まずは、視覚的に図示してみることに。

from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt

cm = confusion_matrix(y_test,y_pred)

print('cm :', cm)
sns.heatmap(cm)

画像7

縦軸が実際の設定、横軸が予想した設定となります。明度の明るい箇所が
やはり、設定1と予想している数が圧倒的に多いことがひと目で分かります。
数値でも算出してみます。

from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_score
from sklearn.metrics import classification_report

print(precision_score(y_test,y_pred,average = None))

print(classification_report(y_test,y_pred))

-------------------------------------------------------------

          precision    recall  f1-score   support

          0       0.41      0.38      0.40        95
          1       0.39      0.81      0.53       180
          2       0.00      0.00      0.00        33
          3       0.41      0.10      0.16        89
          4       0.00      0.00      0.00        22
          5       1.00      0.02      0.05        43
          6       0.00      0.00      0.00        20

   accuracy                           0.40       482
  macro avg       0.32      0.19      0.16       482
weighted avg       0.39      0.40      0.31       482

どの数値を見ても設定1を当てているため正答率が高くなっていることがわかります。実際に使えるモデルとしたい場合、設定4.5.6を当てる必要があるためこのままでは使い物になりません。

そのため、モデル学習の難易度を下げるために設定1.2.3を「勝てない台」、設定4.5.6を「勝てる台」とした2値分類でモデル作成を行ってみました。

#設定1.2.3を0、4.5.6を1とする。
#一旦すべて設定1とする
main_data['setting'] = 0
main_data.loc[main_data['RB確率'] <= 292.6,'setting'] = 1

#以下中略
#2値分類でモデル作成

import lightgbm as lgb
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold
X_train = pd.read_csv('/content/drive/MyDrive/Aidemy_data/エスパス上野/X_train_2values.csv')
X_test = pd.read_csv('/content/drive/MyDrive/Aidemy_data/エスパス上野/X_test_2values.csv')

X_train = X_train.drop(['date_1', 'date_2', 'date_3', 'date_4', 'date_5', 'date_6'],axis=1)
X_test = X_test.drop(['date_1', 'date_2', 'date_3', 'date_4', 'date_5', 'date_6'],axis=1)

lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)

lgb_params = {
             'task': 'train',
             'boosting_type': 'gbdt',
             'objective': 'binary', # 目的 : 2値分類 
             'metric': 'auc', # AUC の最大化を目指す
             
             }


model = lgb.train(lgb_params,
                 train_set=lgb_train, # トレーニングデータの指定
                 valid_sets=lgb_eval, # 検証データの指定
                 )

# テストデータの予測 ((各クラスの予測確率 を返す))
y_pred_prob = model.predict(X_test)
# テストデータの予測 (予測クラスを返す)
y_pred = np.where(y_pred_prob < 0.5, 0, 1)

lgb.plot_importance(model, height=0.5, figsize=(8,16))
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
from sklearn.metrics import balanced_accuracy_score
import seaborn as sns
import matplotlib.pyplot as plt

cm = confusion_matrix(y_test,y_pred)
print('cm :', cm)
sns.heatmap(cm)

print(classification_report(y_test,y_pred))
print("balanced_accuracy_score:", balanced_accuracy_score(y_test,y_pred))
-----------------------------------------------------------------------------
                precision    recall  f1-score   support

          0       0.77      0.96      0.86       370
          1       0.33      0.06      0.11       112

   accuracy                           0.75       482
  macro avg       0.55      0.51      0.48       482
weighted avg       0.67      0.75      0.68       482
balanced_accuracy_score:0.5348548510313216

画像8

結果を見ると残念な結果となってしまいました。
balanced_accuracy_scoreを見ると5割となっていてまったく予測ができていないことになります。
2値平均でこの結果であるということは、台データオンラインのデータだけでは一台一台の設定までは予想できない、もしくはお店の設定の入れ方に日付や台番号などの法則性はないのかも知れないです。
何れにせよ、一台一台の設定判別という方向性は難しいという結論に至りました。

最後に

今回機械学習を学習して初めて実装を行ったが、機械学習はデータ集めや前処理が大切で、最も時間がかかるというのを痛感しました。
また、思ってた以上にドメイン知識の有無で、どんな説明変数を用意すれば精度が上がりそうか?というのを効率よく探すことができるため、重要だと実感しました。
結果として、正答率としては高い数値で設定予測ができましたが、混合行列を見ると、あくまで設定1と予想することで見かけの正答率が高くなっているだけでした。
また思っていた以上にスロット店の台の配置が入れ替わるので、一台一台の設定推測ではなく、島単位や機種単位で推測をするほうが現実的かもしれないです。

今後の検討事項として、説明変数の候補として以下のような要素がまだまだあると考えています。
・店内の掲示内容(直接見に行く必要があるため難しいか?)
・LINE通知
・最終差枚数(スクレイピングの手法でなんとかなる?)
・TwitterなどSNSの投稿(ユーザー、店)
・各種メディア
・有名人の来店情報

また、予測する内容は
・勝てる店(抽選人数が何人いるのかも考慮できると良い…)
・勝てる機種
をの2つを予想する方向で今後も検討したいです。

今回、時間や知識の不足からこれらの要素を取り込むことができなかったので今後学習し取り込めるようにしていきたいです。
特に、Twitterなどの投稿から自然言語処理を活用して行くべき店を決めるツールは現実性、実現性が高いように思うので作成していきたいです。

この記事が気に入ったらサポートをしてみませんか?