何切る問題
まずは、何切る問題。他にもリーチ判断や鳴き判断など考慮すべきことはあるが、とりま一歩ずついこう。
鳳凰卓すべての局面について、"自分の牌、自分の河、他家の河、ドラ牌、点棒、他家のリーチ、点数状況、今の場と局"に対する"捨てた牌"の組み合わせを教師データとして学習させればできあがり。
何局面データがあるのか
機械学習には膨大な量のデータが必要である。何をもって膨大かというのは解析したいものの複雑さに依存すると思うが、麻雀の場合はどの程度なのだろうか?やってみなくちゃわからないが、手元にある鳳凰卓の三麻の牌譜についてデータ数を調べてみる。
ダウンロードした鳳凰卓の三麻の牌譜については以下。
| 年 | ファイル数(戦数) | 容量 |
|---|---|---|
| 2009 | 21994 | 242 MB |
| 2010 | 38209 | 510 MB |
| 2011 | 51150 | 708 MB |
| 2012 | 60854 | 839 MB |
| 2013 | 48664 | 667 MB |
| 2014 | 54540 | 749 MB |
| 2015 | 51310 | 708 MB |
| 2016 | 47769 | 660 MB |
| 2017 | 51958 | 716 MB |
| 2018 | 57734 | 795 MB |
| 2019 | 62392 | 859 MB |
| 2020 | 74320 | 1.1 GB |
| 2021 | 71829 | 992 MB |
| 合計 | 692723 | 9.3 GB |
2009年は鳳凰卓ができたばかりということで、データ数も少ない。おそらく参戦している人数も少なかったのではないかと推察する。参戦している人数が多いほどレベルも高いという全く根拠のない勝手な偏見から、それなりにデータ数の多い2011年以降のデータを用いるのが良いかと思う。
ということで、2011年以降のデータを対象に検討をすすめる。
BYEのあるファイルを除外
<BYE>タグは接続が切れて退室したことを示しており、その後ツモ切りになってしまう。場が乱れるので、BYEのあった試合は省くことにする。
三鳳南喰赤戦以外を除外
東風戦だったり、喰いタンありなし、赤ありなしで打ち方が変わりそうなので三鳳南喰赤戦以外を除外。
三鳳南喰赤戦のファイル数
上記を除外して、最終的に600,274戦分となった。
総局数
三麻の鳳凰卓600,274戦に含まれる総局数は、5,418,171局である。
総局面数
すべての局面はいくつあるであろうか。何切る問題は打牌の選択だから、牌譜に含まれる打牌の数だけ局面があると考えていいだろう。下記のコマンドで集計すると、173,755,995局面あることになる。もちろんリーチ後にはツモ切り以外の選択肢はないし、鳴き局面もあるので厳密には違うが、今は細かいことは気にしない。概算でどの程度のデータが有るか知りたいのだ。
1.7億局面のデータが十分な量かどうかはわからないがこれで進めよう。
mjlogから何切る問題のデータセットを作る。
何切る問題のデータセットを作成する。mjlogを上から順に処理していって、打牌時に見えている牌をインプット、打牌をアウトプットとするデータセットを作る。
打牌時に見えている牌は、"手牌"、"自分の鳴き牌"、"自分の河"、"上家の河"、"上家の鳴き牌"、"下家の河"、"下家の鳴き牌"、"ドラ表示牌"となる。その他、東1局などの場・局の情報、何本場、供託されているリーチ棒、点数、相手がリーチしているか、を打牌選択のためのインプットとして学習させる。
mjlogを解析して各局面の情報を出力したのが以下。


全局面は確認できないが、数局は正しいことを確認している。(これが間違っていたら間違ったことを学習させることになるので大変。)
本データ整理において、以下は仕様である。
- 立直後は強制ツモ切りのため学習局面から削除。局面数がおよそ1.6億局面となった。
- 鳴き牌について。誰から鳴いたかは記録しないが、誰かの河に鳴いた牌が記録として存在するので、AIがうまいことその情報を拾い上げるかもしれない。
- 大明槓と加槓は区別しない。場に見えている牌という意味で同じ。ただ、河の情報には現れるので、AIがうまいことその情報を拾い上げるかもしれない。
- 暗槓は手配と鳴き牌のどちらにも表示される。門前だが相手からは見えているという状況が必要。AIがうまいことその情報を拾い上げるだろう。
- 北を抜く場合、3枚抜いた場合と鳴いてポンした場合、鳴き牌だけみても区別できないが、手配の枚数で区別が可能。AIがうまいことその情報を拾い上げるだろう。
インプットデータ、アウトプットデータの形式
多分、学習効率とか正答率に影響を大きく及ぼす部分の一つ。全結合モデルとCNNモデルの2つを今の所想定しているので、どちらにも使えるようにデータを組むと楽ではある。ただ全結合モデルの場合に無駄なデータが増えることにはなる。悩ましい。
全結合学習
全結合モデル用データセット
全結合用のデータセットは以下の通り。154の入力に対して一つのラベルを1セットとする。

1.6億局面のデータセットを作るとなるとファイルサイズが半端ないので、1byteの整数(0~255) x 155 のバイナリーデータとして保存する。100万局面を一つのファイルにしておよそ160個ファイルができる計算。np.tobytes()で書いてnp.frombuffer()で読み込むのが高速。
上図の牌番号というのはmjlogの牌IDとは別で、1種類に対して1つの番号を振っている。mjlogの牌ID[0, 1, 2 ,3]は1萬を表すが、牌番号ではすべて[1]で表す。赤を区別できないがとりあえず無視。連続型にならないところで1つ以上値を開けているが、これがいい方向に働くかはわからない。
def getHaiNum(hai_ID):
hai = [1, 2, 3, 4, 5, 6, 7, 8, 9, #萬子
11, 12, 13, 14, 15, 16, 17, 18, 19, #筒子
21, 22, 23, 24, 25, 26, 27, 28, 29, #索子
31, 33, 35, 37, 41, 43, 45]
return hai[hai_ID >> 2]
モデル
モデルは、以下。

プレテスト
いきなり1.6億局面回す前に、試しに100万局面学習、100万局面検証で全結合学習してみた。プログラムはPytorchで組んでいる。
結果
100epoch回したときの損失と精度は以下。


testの損失、精度共に50epochを超えると悪くなっており、過学習の気配を見せている。trainの損失、精度共にまだまだ向上していることを見ると、モデル自体の可能性は感じるところではある。たまにtestの精度が著しく悪くなる部分があるが、何に起因しているのだろう。100万局面でもデータの偏りが大きく十分な量になっていない。なにかの入力値にシビアに反応している。あたりか。ぶっちゃけ、最初の1打に白発中のどれから切るかなんて気分だったりするだろうから、このあたりを学習させるのはあまり良くない気もする。要検討。
速度計測
1epochあたりにかかる時間は以下。
| device | batch_size | time (sec) |
|---|---|---|
| CPU | 100 | 178 |
| GPU | 100 | 25 |
| GPU | 200 | 17 |
| GPU | 500 | 15 |
| GPU | 1000 | 15 |
GPUを使うことで、約7倍早くなっている。バッチサイズを上げると更に早くなるが、500くらいで頭打ち。バッチサイズは精度にも効いてくるので一概に速度を求めて大きくするのが良いとは限らない。このあたりは要検討。
バッチサイズが200の場合、GPUを使って100万局面の1epochが20秒程度ということは、1.6億局面だと、単純計算20 x 160 = 3200秒 = 53 min ~ 1時間。2日ほど回すと50epochになるので結果の方向性は見えてきそう。とりあえず回してみるか。
本学習
1.6億局面から、ランダムに1億局面を学習データ、3000万局面を検証データとして用いる。残りの3000万局面は最終検証用として取り分けておく。
結果
100 epoch学習後の結果が以下。


なんと、精度が70%を超えてきた。Suphxの何切る問題の学習精度が76%程度なので1、こんなシンプルな特徴量とモデルでかなりいい線いってると言っていいだろう。以外なのは訓練データよりも検証データのほうが精度がいいことである。普通訓練データのほうが精度は良いのだが。
まだまだ過学習の気配は見せていないし、若干の伸びしろがありそうなので、もう少し学習を継続してみようと思う。