見出し画像

Mjxで作った麻雀AIの打牌の判断根拠を可視化する

以前の記事で、天鳳の牌譜をMjxを使って特徴量に変換し、ニューラルネットワークの麻雀AIを作成する手順を紹介しました。
今回は、この麻雀AIの打牌判断をSHAPを使って可視化し、なぜその牌を選択したのかを見てみます。

特徴量の解説

特徴量には、2022/10時点でMjxのmainブランチに実装されている mjx-large-v0 を使用します。(v0.1.0には入っていないため、導入にはgithubのurlを指定してpip installする必要があります)

説明変数の実装はこの部分です。
https://github.com/mjx-project/mjx/blob/1a120f748151b64becd41448cd43165e23302cc2/mjx/observation.py#L141

局面ごとに 132行 x 34列 = 4488 の次元数があり、0か1のbinary値です。
列は麻雀の牌の種類に対応しており、行はざっくり以下のような内容です。

  1. current_hand … 現在の手配。1-4行目は通常の牌、5-7行目は赤ドラ

  2. target_tile … (ツモ、副露や打牌などの直前のイベントの牌?)

  3. under_riichis … 誰がリーチしているか。自家から反時計回りに4行分。

  4. discarded_tiles … 全員の捨て牌とその段数。4人 x 捨て牌の段(1-3)で12行。

  5. discarded_from_hand … discarded_tilesのうち、ツモ切りだったもの

  6. opened_tiles … 全員の副露牌

  7. dealer … 誰が親か

  8. doras … ドラ

  9. shanten … 自分のシャンテン数

  10. effective_discards … 捨てるときに有効な牌の種類

  11. effective_draws … ツモると有効な牌の種類

  12. ignored_tiles … 他家が捨てた牌の種類(安全牌?)

  13. kyotaku … 供託本数

  14. rankings … 全員の順位

  15. round … 何局目か

  16. honba … 何本場か

  17. dora_num_of_target … (副露や打牌などの直前のイベントの牌に含まれるドラ枚数?)

  18. dora_num_in_hand … 手牌にドラが何枚あるか

誰が親か、など牌の種類が関係ないものは、その行の34列すべてに1がはいります。

特徴量を見る

まずは適当な局面で、特徴量を見てみます。
対面のリーチを受けて、自分は一向聴です。AIの打牌は一発目で通っていない4mを切る選択です。

ここから4mを切る局面

予測上位から順にこのような確率になりました。
4m … 43%
5mツモ切り … 17%
・4p … 12%
・8m … 8%

4mは一向聴から一向聴の中で、受け入れが最も多い牌です。
比較対象としては、5mをツモ切りすると安全に一向聴が維持できます。「4mを切る理由」「5mを切る理由」をそれぞれ可視化してみましょう。

特徴量を可視化するとこのように見えます。黄色が1、紫が0です。
縦方向を牌の種類34行、横方向を特徴量の種類132列にしてプロットしました。

この局面での特徴量(mjx-large-v0)

打牌根拠の可視化

打牌根拠をSHAPを使って可視化します。
まずは4mに対応する判断根拠を見てみます。
SHAPのDeepExplainerを使用しており、SHAPには判断根拠を計算するためにバックグラウンドデータが必要なため、10000件与えています。
動くコードではないですが、雰囲気は以下のようなコードです。

import shap
import numpy as np
import matplotlib.pyplot as plt
from torch import Tensor

feature = np.load("feature.npy")
# shape=(10000, 4488)

e = shap.DeepExplainer(model, Tensor(feature))

target_idx = 122
shap_values = e.shap_values(Tensor(feature[target_idx].reshape(1, -1))
# shape=(1, 4488)

action_idx = 3 # 打4m
plt.imshow(shap_values[action_idx][0])

バックグラウンド10000件、GPU利用で1件の可視化に2~3分かかりました。
DeepExplainerは気軽に使うには少し重い印象です。

4mを切る(43%)理由

まずはshap_valuesを特徴量と同じ形式でプロットします。
黄色がプラス(切った理由)、黒はマイナス(切りたくない理由)です。

4mを切った理由

黄色い部分が2pを打った理由で、以下のような感じです。
・手牌として持っている牌すべて
直前のイベントで5mを引いた
・他家のいくつかの捨て牌(4m、5p、6p、2s、5s)
親であること
ドラが9mであること
・3, 4m切りが有効であること
対面の着順が1位であること

リーチされているというのがネガティブな理由になることを期待しましたが、この局面ではリーチを無視していることも分かります。
親であることも理由になっているので、親だからリーチ関係なく押しますということでしょうか。

5mをツモ切る(17%)理由

5mを切れば安全に一向聴維持ができます。これが二番目の予測スコアの高い選択肢でした。

5m切りの理由

まっすぐアガろうとする選択ではないからか、もしくは予測確率が低めだからか分からないですが、根拠の可視化はいまひとつ読み取りづらい感じになりました。
気になったところをピックアップすると、

4m以外の手牌は切らない理由(黒)になる。持っていない牌が切る理由(黄色)になる
・他家が親ではないこと。自分が親であることは切らない理由になる
・ドラが9mであることが切らない理由になる
4m切りと比べ、安牌の情報が全体的に切る理由になっている
・着順情報が全体的に強くみられている

5mツモ切りの理由でもリーチ情報(10-13列目)が注目されていないのは期待通りではないですね…。
逆にリーチで降りている局面を見てみるとよさそうですが、まずその局面を探すことがむずかしく一旦ここまでにしています。

活用

4m切りの方はある程度納得感があり、牌譜の振り返りなどで使えるかもしれません。
今回特徴量のSHAP値マップを人力で読み解いて言語化していましたが、この特徴量のSHAP値マップを良い感じに可視化できるようになると使いやすくなるはずです。

冒頭に貼った画像のように、Mjxには局面のすばらしい可視化機能があるので、これを改造してSHAP値をヒートマップ的に表せると面白そうです。
visualizerはここで実装されており、牌の枠の色を変えるぐらいはできそうに見えます。
https://github.com/mjx-project/mjx/blob/master/mjx/visualizer/visualizer.py

また、今回は麻雀AIにSHAPを適用したので「自分の打牌の根拠」を可視化しましたが、どちらかというと「相手の危険牌読みの根拠」を可視化できると、麻雀の勉強としては役に立ちそうな気がしています。
相手の危険牌読みは麻雀AIとしても強さにつながりそうなので、打牌予測と危険牌予測を同時に学習するようなことも今後試してみたいです。

↓この方がLightGBM+SHAPで危険牌の予測と判断根拠の可視化を記事にされており、非常に詳しい内容でおすすめです。


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