麻雀の手牌から打牌を予測するLSTM

# ※この記事は2020年に作成しました
#概要
鳴きなしで、親の人の手牌のみの14枚の手牌と打牌を使いLSTMに入れて打牌を予測するようにした。
手牌の変化を表すために、14×18の配列に手前から1打目の手牌、2打目の手牌、となるようにした。また、各打の間に0をいれた。
0～13が1打目の手牌、14は0、15～28は2打目の手牌、29は0...というふうになる。

#結果
正則化、LSTMのブロック数、ユニット数、ドロップアウト率、Embeddingのハイパーパラメーターをどうしたらいいかわからない。
テストデータに対する正解率は約7%になった。
手牌の変化パターンはたくさんあり、局によっても全然違ってくるのであまり精度がよくなかったと考えられる。データ数は手牌の数が約3万個で、局数は4649個である。しかし、九種九牌や親が2打目をするまえに誰かが和了ったり、鳴いたりすれば手牌の変化はない。データ数を大量に増やせばもう少し精度が上がると考えられるが、実行に時間がかかる。

#作成したコード

```python
# coding: UTF-8
import numpy as np
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers import InputLayer
from keras.layers.core import Dense
from keras.layers.recurrent import LSTM
from keras.layers.embeddings import Embedding
from keras import optimizers,regularizers

np.set_printoptions(linewidth=70)

num = 30041
col = 34
tehai = np.zeros((num,col), dtype='float32') #手牌読み込み
with open('C:/sqlite/tehai_lstm.csv', 'r') as fr:
    for i,row in enumerate(fr.readlines(),start=0):
        tehai[i] += np.array(list(map(np.float,row[:col])))

dahai = np.loadtxt("C:/sqlite/dahai_lstm.csv",delimiter=",")#打牌読み込み

tehai2 = np.zeros((num,14), dtype='float32') #34の配列を14へ、局の区切りは先頭要素が99へ
for i in range(num):
    cnt = 0
    for j in range(34):
        L = 0
        if tehai[i,j] == 9:
            tehai2[i,0]= 99
        else:
            while(L<4):
                if tehai[i,j] >= 1:
                    tehai2[i,cnt] = j
                    cnt +=1
                    tehai[i,j] -= 1
                L += 1

tehai2 += 1 #0のための+1
tehai3 = np.zeros((25392,270), dtype='float32')#時系列のデータになるようにしている
cnt = 0
cnti = 0 
for i in range(num):
    if tehai2[i,0] != 100:
        if cnt!= 0:
            kyoku_cnt = int(cnt/15)
            for k in range(kyoku_cnt):
                for l in range(14):
                    tehai3[i-cnti,l+(k*15)] = tehai2[i-(k+1),l]
        for j in range(14):
            tehai3[i-cnti,j+cnt] = tehai2[i,j]
        #tehai3[i-cnti,cnt+14] = 0
        cnt += 15
    else:
        cnt = 0
        cnti += 1

tehai3 = np.reshape(tehai3, (-1, 15, 18))
tehai3 = tehai3 / 35.
dahai =np_utils.to_categorical(dahai,34) #One-hot表現へ

model = Sequential()

model.add(
    InputLayer(input_shape=(15,18))
    )

## 中間層
# LSTMブロック（ユニット数＝128）
weight_decay = 1e-4                  # ハイパーパラメーター
model.add(LSTM(units=270, dropout=0.25, return_sequences=True))
#model.add(LSTM(units=270, dropout=0.25, return_sequences=True))
model.add(LSTM(units=270, dropout=0.25, return_sequences=False,
               kernel_regularizer=regularizers.l2(weight_decay)) # 正則化
)

## 出力層
model.add(
    Dense(units=34,                  # 出力層のニューロン数は34
          activation='softmax')      # 活性化はシグモイド関数
    )

# Squentialオブジェクをコンパイル
model.compile(
    loss='categorical_crossentropy', # 誤差関数はクロスエントロピー
    optimizer=optimizers.Adam(),     # Adamオプティマイザー
    metrics=['accuracy']             # 学習評価として正解率を指定
    )

#model.summary()                      # RNNのサマリ（概要）を出力

history = model.fit(tehai3,dahai, # 訓練データ、正解ラベル
                    batch_size=30,     # ミニバッチのサイズ
                    epochs=1,          # 学習回数
                    verbose=1,          # 学習の進捗状況を出力する
                    validation_split=0.2
                    )
```