TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

麻雀AIを深層強化学習で作る その2(特徴量設計)

前回はこれから作成する麻雀AIの方針を立てた。
今回は、麻雀AIの特徴量を検討する。

方針

畳み込みニューラルネットワーク

麻雀の牌は、萬子、筒子、索子、字牌の4種類で構成されており、萬子、筒子、索子は1~9の数字を持っている。
面子は、順子は隣合った数値、刻子は同じ数値、同じ字牌で構成されるため、連続する数字の位置と、いくつ同じ牌を持っているかに意味がある。
したがって、牌種をチャンネルとして、牌を9(数値または字牌の種類)×4(牌数)の2次元の面で表した、畳み込みニューラルネットワークで構成することには合理性がある。
なお、字牌の並び順は意味を持たないが、畳み込みニューラルの制約上、並びの順序を与えることになる。

Suphxでは、チャンネルをすべての牌を並べて34×4で表現しているが、萬子、筒子、索子、字牌の位置関係は意味を持っていないため、牌種ごとにチャンネルを分ける方が合理的である。

トランスフォーマーを使って構成することも考えられるが、ひとまず先行研究で実績のある畳み込みニューラルネットワークを使うことにする。

デュアルヘッド

アルゴリズムにはPPOを使う予定のため、方策と価値の2つのモデルが必要になる。
盤面の特徴抽出は、方策と価値で共通するため、1つのニューラルネットワークで方策ヘッドと価値ヘッドを持つ構成とする。

価値関数には非公開情報も使う

前回考察した通り、麻雀はランダム要素が強いため、価値は行動による影響よりも、配牌の影響が強い。
そのため、価値ヘッドには、非公開の他家の手牌と山牌の情報も入力し、他プレイヤーとの相対的な価値を推定する。

打牌、副露は同じモデルで扱う

Suphxでは、打牌、チー、ポン、カンでモデルを分けているが、盤面の特徴抽出は共通化した方が効率が良いため、1つのモデルで扱うことにする。

報酬

麻雀の目標は、1ゲーム(半荘または一荘)を通して1位になることであるが、まずは1局の点数を目標として学習することにする。

マルチエージェント

不完全情報ゲームでは、単一のモデルでは搾取されやすくなるため、マルチエージェントで学習するのが効果的である。
エージェントごとにモデルを分けると特徴抽出をそれぞれで学習するため効率が悪い。
そこで、AZdbで使われていた手法を参考に、エージェント番号をモデルに入力してエージェントを区別することにする。

入力特徴量

方策

方策の入力特徴量は以下の通りとする。

  • 状態 4チャンネル(打牌、副露x3他家)
  • 手牌 7チャンネル(牌種4+赤牌3)
  • 副露 4チャンネル(面子) x 4(牌種) x 4(プレイヤー)
  • 暗槓 4チャンネル(牌種) x 4(プレイヤー)
  • 副露の赤牌 3チャンネル(牌種) x 4(プレイヤー)
  • 自摸牌 7チャンネル(牌種4+赤牌3)
  • 他家捨て牌 7チャンネル(牌種4+赤牌3)
  • 聴牌 1チャンネル
  • 立直 4チャンネル
  • 河牌 7チャンネル(牌種4+赤牌3) x 4(プレイヤー)
  • 立直後捨て牌 4チャンネル(牌種) x 4(プレイヤー)
  • 他家の直前の捨て牌 4チャンネル(牌種)
  • ドラ 7チャンネル(牌種4+赤牌3)
  • 自風 4チャンネル
  • 場風 4チャンネル
  • 残り牌数 1チャンネル
  • エージェント番号 4チャンネル

打牌と副露を1つのモデルとして扱うため、現在の状態を入力する。副露はどこから鳴くかをチャンネルを分けて表現する。

手牌の赤牌は、5としても表し、赤牌であるかを別チャンネルで表現する。

副露は、面子ごとにチャンネルを分けて、構成する牌の位置を1にして表現する。
面子ごとにチャンネルを分けない場合、順子か刻子か区別できなくなる。
副露した順番には意味がないはずだが、畳み込みニューラルの都合で副露順にチャンネルを使うことになる。
どこから鳴いたかは副露のチャンネルでは表現しないで、河牌から鳴いた牌を除かないことで他家の捨て牌の情報を保持する。

明槓と加槓は、副露のチャンネルで表現するが、暗槓は別のチャンネルで表現する。

副露の赤牌は、どの面子に含まれるかは情報として価値がないため、面子を区別しないで1チャンネルで表現する。

自摸牌は、自摸切りの判断に必要なため、手牌には含めず別チャンネルとする。
チー、ポン、カンの行動のタイミングでは、他家捨て牌のチャンネルを設定する。

立直、流局の判断に必要になるため、聴牌の状態を表現する。
牌の構成のみで聴牌しているかをニューラルネットワークが判断するのはおそらく困難なため、明示的に与える。

河牌には、鳴かれた牌も含める。
手出し、自摸切り、捨てた順は一旦考慮しない。
立直後の捨て牌は重要な情報になるため、別チャンネルで入力する。
他家の直前の捨て牌もフリテンの判断に必要なため、別チャンネルで入力する。

ドラは、ダブル、トリプルも表現できるように、チャンネル内で、ドラの数も表現する。

自風、場風は、それぞれ4チャンネルで、値を全て1にしたワンホットで表す。
自風の東は親であることも表現している。
それぞれ1チャンネルで行を風牌にして、列を全て1で埋めることでも表現できるが、行動全体に影響を与える特徴量のため、位置に意味を持たせない方がよいと考える。

残り牌数は、スカラ値で与える。

マルチエージェントで学習するためエージェント番号をワンホットで与える。
ひとまず4エージェントで学習する。

ルールはcmajiangのデフォルトとする。将来的にはルールもモデルの入力にしたい。

価値関数の入力

価値ヘッドは、方策と共通の部分で抽出した特徴マップに、非公開情報のチャンネルを連結する。
非公開情報のチャンネルは、以下の通りとする。

  • 他家の手牌 7チャンネル(牌種4+赤牌3) x 3(プレイヤー)
  • 聴牌 1チャンネルx 3(プレイヤー)
  • 残り牌 7チャンネル(牌種4+赤牌3)
  • 裏ドラ 4チャンネル(牌種)

残り牌の順番は、ポンによって自摸牌が変わることがあるので、悪影響がでる可能性があるため扱わないことにする。

方策の出力

方策ヘッドは、行動の確率を出力する。
行動の種類は以下の通りとする。

  • 打牌 34+3(赤牌)
  • 自摸切り 1
  • チー 3(パターン) x 2(赤牌有無)
  • ポン 1 x 2(赤牌有無)
  • カン 1
  • 鳴かない 1
  • 暗槓 34
  • 加槓 34
  • 立直 1
  • 自摸和了 1
  • ロン 1

自摸切りは、牌を区別しないでも一意に決まるため1つの行動とする。

チーは、3パターンがある(牌は決まっているので、どの牌を面子にするかは一意に決まる)。手牌の赤牌を含めるかを考慮すると6パターンになる。

ポン、カンは牌を区別しないでも一意に決まるため1つの行動とする。
副露の面子に赤牌を含めるかは選択の余地があるため、手牌の赤牌を含めるかで行動を分ける。

副露選択時は、鳴かない選択肢も必要になる。

明槓、加槓は、ほとんどの場合は牌は1種類だが、複数ある場合もあるので牌別にする。

立直は、打牌時に選択して、立直した場合、あらためて打牌を選択する。

ルールにより、流局の選択の行動があるが、一旦除外しておく。

価値の出力

和了の点数を出力する。
立直棒の考慮は一旦除外しておく。
将来的には最終順位を予想するようにする。

ニューラルネットワークの構成

方策と価値で共通の部分は、ResNetで構成する。
位置の情報が重要なためプーリングは使用しない。

方策ヘッドは、全結合層とする。

価値ヘッドは、非公開情報を入力するため、共通部分の出力と連結して、畳み込み層に入力した後、ResNetに入力する。
その後に、全結合層に入力する。

まとめ

作成予定の麻雀AIのニューラルネットワークの構成と入力特徴量、出力の設計を行った。
ニューラルネットワークの気持ちを考えながら、行動の判断に必要な情報を漏れなく含めたつもりである。
実装しながら間違いに気づくかもしれないので、都度修正していくことにする。

次は、ニューラルネットワークと特徴量作成の実装を行いたい。