オズのメソッド!

目標はパソコン大先生

XGBClassifierでサザエさんのじゃんけんの手を予測する

機械学習というとirisをやってkaggleをやって、という流れだと思うのですが、すでにやっている人がいっぱいいるので、他のことをやります。
テーマは「サザエさんじゃんけん」です。

実はこのテーマもすでに取り組んでおられる先人の方々がいます。
sucrose.hatenablog.com
こちらのサイトによくまとまっています。
ルールベースの手法、確率統計的な手法、機械学習手法などがあります。

以下ではじゃんけんの手、グー、チョキ、パーを、数字の0,1,2に対応させ、じゃんけんの手の時系列データを0,1,2からなる数列と考えます。

サザエさんじゃんけん研究所のよくある質問にある通り、じゃんけんの手は、アニメ制作会社の担当者の方が個人的な思いつきで決めているそうです。
サザエさんじゃんけん研究所公式ウェブサイト(Sazaesan-janken laboratory official website)
これは予測という観点からすると、2つの点でよい題材です。

話が飛びますが、真性乱数の生成が難しい問題であることはよく知られています。
人為的にランダムな数列をつくることは極めて困難です。
プログラム言語の乱数も、擬似乱数(乱数っぽい数列を生成する数式がベース)や、環境ノイズ(ハードウェアが拾う電圧などの環境情報)を利用して生成された乱数を使用します。
逆に、こういった数式や環境ノイズに頼らず、人間が頭の中だけで乱数列をつくろうとすると、多くの場合に意図せず数列に一定の法則性を与えてしまいます。

加えて、じゃんけんの手が、個人的な思いつきであるという点がポイントです。
乱数の話に戻りますが、一般に環境ノイズを利用した乱数のほうが、真性乱数に近いはずです。
しかし、サザエさんのじゃんけんの手は、担当者の方の「個人的な思いつき」なので、環境ノイズは意図的には利用していないことになります。
例えば、サイコロを振って決めている場合は、予測できません。
サイコロは厳密には環境ノイズではありませんが、予測不能な変数に対して従属の関係にある変数の予測はできません。
もちろん極論を言えば、担当者の方の気分はある種の環境ノイズに従属であるはずですが、今回の場合は、意図的には利用していない、というのがポイントです。

分析を始めた当初は、時系列データということで、RNN, LSTM, GRUなど試していたのですが、計算に時間がかかる割に精度は0.45~0.5くらいがせいぜいだったので、xgboostに乗り換えました。

ちなみにサザエさんじゃんけん研究所では、ルールベースの手法や確率統計的な手法で平均7割くらいの勝率をあげられており、もう機械学習いらないんじゃないかというところまで来ていますが、このままやります。

以下で利用するじゃんけんのデータはサザエさんじゃんけん学からいただきました。
サザエさんじゃんけん研究所公式ウェブサイト(Sazaesan-janken laboratory official website)

では、前置きはこのくらいにして、予測コードと予測結果を見ていきましょう。
当たり前ですが、サザエさんの手を予測できれば勝てるので、予測の正解率が即ち勝率ということになります。

import numpy as np
from xgboost import XGBClassifier
from sklearn.grid_search import GridSearchCV

data = np.loadtxt("sazae.csv",delimiter=",", dtype=np.int)

length_of_sequence=4

X=[]
y=[]

for i in range(len(data)-length_of_sequence):
    X.append(data[i:i+length_of_sequence])
    y.append(data[i+length_of_sequence])
    
X=np.array(X)
y=np.array(y)

parameters  = {'max_depth': np.arange(1,6), 'min_child_weight': np.arange(1,6)}
clf = GridSearchCV(XGBClassifier(), parameters, cv=5, verbose=1)
clf.fit(X, y)

print(clf.best_score_)
print(clf.best_estimator_)
print(clf.best_params_)

sazae.csvの中身は0, 1, 2, ..., 2, 0, 0のようなフォーマットになっている必要があります。上記のサイトからコピペなりスクレイピングなりしてデータを頂戴します。
途中で時系列データを加工して、説明変数と目的変数に分けています。
length_of_sequenceが説明変数の数です。
コードでは4になっていますので、4回前までの手が説明変数になります。逆に言うと4回前までのデータを下に次の手を予測するモデルになります。
ここもパラメータなので、2とか10とか変えてみてください。

そして結果がこちら。

0.5377431906614786
XGBClassifier(base_score=0.5, colsample_bylevel=1, colsample_bytree=1,
       gamma=0, learning_rate=0.1, max_delta_step=0, max_depth=4,
       min_child_weight=4, missing=None, n_estimators=100, nthread=-1,
       objective='multi:softprob', reg_alpha=0, reg_lambda=1,
       scale_pos_weight=1, seed=0, silent=True, subsample=1)
{'max_depth': 4, 'min_child_weight': 4}

0.5377431906614786が正答率なので、約54%の精度です。
上記のサイトにある通り、他の方の分析では、(交差検定の分割数などの条件は違うものの)SVMやRandomForestで、だいたい54%くらいの精度が出ているので、ほぼ一緒の結果ですね。

ただルールベースの手法に負けてようでは、はっきり言ってわざわざ計算資源を割く価値がありません。
計算資源を多く消費する機械学習は、従来手法に比べて高い精度を出すことで、そのバリューをアピールしていく必要があります。

まぁフォローしておくと、じゃんけんの純粋な勝率は33%なので、この数値をベンチマークと考えれば、適当に出すよりはよい結果が得られます。
それに面白いテーマなので、またいい精度がでたらここで報告しようと思います。

最後に余興として今日のサザエさんの手を予測してみましょう。

サザエさんじゃんけん学のサイトに7月分のデータがまだ上がっていなかったので、サザエさんじゃんけん研究所から直近4回分のデータをもらいました。
7/2 チョキ
7/9 パー
7/16 グー
7/23 パー

したがって説明変数は[1,2,0,2]になります。
これを上記で学習したモデルに入れてみましょう。

print(clf.predict([1,2,0,2]))

結果は1だったので、今日の手はチョキということになります。
なのでグーを出しましょう。
たしかに直近ではチョキが一番出ていないので、次に出そうな感じはあり、直感にも反しない予想となっています。
もちろん当たる確率は平均で54%でしかないので外れても知りませんよ(笑)

7/31 Mon 追記
当たりました!