【Python】LSTMによるサザエさんのじゃんけん予想


# 目次
* [はじめに](#はじめに)
* [ソースコード](#ソースコード)
* [簡単に解説](#簡単に解説)
* [結果](#結果)
* [最後に](#最後に)
* [参考リンク](#参考リンク)


# はじめに
数日前に、サザエさんじゃんけん研究所様が過去のサザエさんのじゃんけんの手をまとめたPDFを公開したことが話題になっていました。さらに同研究所がそのデータを解析し、通算で７割以上の勝率を誇っていると知り、まだ未熟ではありますが機械学習をかじる者としてじっとしてはいられなかったわけです。

ということで、まずはLSTMを使って簡単に予測モデルを作ることにしました。


# ソースコード
別記事「[Kerasで多変量LSTM](https://qiita.com/tizuo/items/b9af70e8cdc7fb69397f)」を参考にしています。

```py
import numpy as np
import pandas as pd
from keras.layers import LSTM, Activation, Dense
from keras.models import Sequential

data_file = 'サザエさんじゃんけん.tsv'
look_back = 13  # 遡る時間
res_file = 'lstm'


def shuffle_lists(list1, list2):
    '''リストをまとめてシャッフル'''
    seed = np.random.randint(0, 1000)
    np.random.seed(seed)
    np.random.shuffle(list1)
    np.random.seed(seed)
    np.random.shuffle(list2)


def get_data():
    '''データ作成'''
    df = pd.read_csv(data_file, sep='\t',
                     usecols=['rock', 'scissors', 'paper'])
    dataset = df.values.astype(np.float32)

    X_data, y_data = [], []
    for i in range(len(dataset) - look_back - 1):
        x = dataset[i:(i + look_back)]
        X_data.append(x)
        y_data.append(dataset[i + look_back])

    # X_data = np.array(X_data)
    # y_data = np.array(y_data)
    X_data = np.array(X_data[-500:])
    y_data = np.array(y_data[-500:])
    last_data = np.array([dataset[-look_back:]])

    # シャッフル
    shuffle_lists(X_data, y_data)

    return X_data, y_data, last_data


def get_model():
    model = Sequential()
    model.add(LSTM(16, input_shape=(look_back, 3)))
    model.add(Dense(3))
    model.add(Activation('softmax'))
    model.compile(optimizer='adam',
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])
    return model


def pred(model, X, Y, label):
    '''正解率 出力'''
    predictX = model.predict(X)
    correct = 0
    for real, predict in zip(Y, predictX):
        if real.argmax() == predict.argmax():
            correct += 1
    correct = correct / len(Y)
    print(label + '正解率 : %02.2f ' % correct)


def main():
    # データ取得
    X_data, y_data, last_data = get_data()

    # データ分割
    mid = int(len(X_data) * 0.7)
    train_X, train_y = X_data[:mid], y_data[:mid]
    test_X, test_y = X_data[mid:], y_data[mid:]

    # 学習
    model = get_model()
    hist = model.fit(train_X, train_y, epochs=50, batch_size=16,
                     validation_data=(test_X, test_y))

    # 正解率出力
    pred(model, train_X, train_y, 'train')
    pred(model, test_X, test_y, 'test')

    # 来週の手
    next_hand = model.predict(last_data)
    print(next_hand[0])
    hands = ['グー', 'チョキ', 'パー']
    print('来週の手 : ' + hands[next_hand[0].argmax()])


if __name__ == '__main__':
    main()
```


#簡単に解説
データはあらかじめtsvファイルにまとめておきました。

```
year	month	day	rock	scissors	paper
1991	11	10	0	1	0
1991	11	17	1	0	0
1991	11	24	1	0	0
1991	12	1	0	0	1
...
```

そこから, データを以下のように整形（look_back=2 のとき）

```
[[[0. 0. 1.]
  [0. 1. 0.]]
 [[1. 0. 0.]
  [0. 1. 0.]]
 [[1. 0. 0.]
  [1. 0. 0.]]
 ...
```


# 結果
パラメータを変えてみて一番良かった結果はこのようになりました。

* 出力

```
train正解率 : 0.60
test正解率 : 0.59
[0.6323466  0.12851575 0.23913768]
来週の手 : グー
```

* グラフ

![lstm.png](https://qiita-image-store.s3.amazonaws.com/0/231104/383f7ee9-e637-fe44-fecb-17b5ad4a66ec.png)


# 最後に
残念ながら今回はサザエさんじゃんけん研究所様の勝率には及びませんでしたが、使用する素性やモデル次第でまだまだ改善の余地があるかと思います。また時間があるときに挑戦したいと思います。皆さんもぜひチャレンジしてみてください。

今週のサザエさんを楽しみにしてます。

# 追記
(18/08/18)
コードが汚すぎたので整理しました。
別のモデルでも挑戦してみました。
→ [ニューラルネットによるサザエさんのじゃんけん予想](https://qiita.com/derodero24/items/65d710960c6fbeda16ef)

(18/08/19)
見事（運良く）正解できました！


# 参考リンク
[サザエさんじゃんけん研究所](http://park11.wakwak.com/~hkn/)
[Kerasで多変量LSTM](https://qiita.com/tizuo/items/b9af70e8cdc7fb69397f)
[ニューラルネットによるサザエさんのじゃんけん予想](https://qiita.com/derodero24/items/65d710960c6fbeda16ef)