はじめに
麻雀AIの記事は、いきなり「Transformer」「Attention」「埋め込み」といった言葉が出てきて、麻雀だけやってきた人には読みにくいと思います。この記事は、麻雀の知識だけで読めるように、私が作った麻雀AI(v3)の中身を、最初から順に説明します。
先に結論です。
- v3 は「ある局面で、人間は何を切ったか」を当てるAIです。天鳳の牌譜16年分で学習し、人間の打牌との一致率(Top-1)は 77.81% でした
- 局面を、牌と出来事を並べた「文章」のようなものに直して、Transformer という仕組みに読ませています
- Suphx(Microsoft の麻雀AI)とは、入力の作り方・使っている仕組み・学習のしかたが違います。違いは、後半の表にまとめました
- NAGA との違いも、公開されている記述の範囲で書きました
この記事のシリーズ
全体の結果は網羅編に、牌譜データの読み方は天鳳の牌譜(mjlog)をPythonで読み切るにあります。この記事は、それらの前提になる「モデルの中身」の解説です。
1. AIがやっていること: 「何切る」を当てる問題
v3 がやっているのは、麻雀の「何切る」問題を、天鳳の実際の打牌で答え合わせする学習です。
- 局面を入力する(自分の手牌、河、ドラ、点数、リーチの有無など)
- 切る牌の候補ごとに点数をつける
- 人間が実際に切った牌の点数が高くなるように、AIを調整する
これを、天鳳の牌譜の数十億回分くり返します。AIは強さを直接教わるわけではなく、人間の上手な打牌をまねすることで打ち方を覚えます(模倣学習)。
候補は37種類です。34種類の牌に、赤5(萬・筒・索)の3種類を加えています。赤5を別の牌として数えるのは、赤を切るかどうかで、麻雀の判断が変わるためです。
2. Transformer とは何か(麻雀の言葉で)
Transformer は、もともと文章を読むために作られた仕組みです。ChatGPT のような文章AIも、同じ仲間です。文章を読むとき、一語ずつ順番に追うのではなく、文中のすべての語の関係を同時に見て、意味をつかみます。
麻雀に置き換えると、次のようになります。
| 文章の世界 | 麻雀の世界 |
|---|---|
| 単語(トークン) | 牌、あるいは「誰が何を切った」という出来事 |
| 文章 | 1つの局面(手牌・河・ドラ・出来事の流れ) |
| 次の単語を当てる | 人間が切った牌を当てる |
Attention: 「どこを見るか」を学ぶ仕組み
Transformer の中心にあるのが Attention(注意) です。1つのトークンが、ほかのトークンのうち「どれに注目するか」を、重みで決めます。
図は、東を切るか迷っている場面のイメージです。東が場にすでに2枚見えているなら、残りは少なく、安全度が高い、と人間は考えます。Attention は、こうした情報を、離れた位置にある出来事(他家の打牌など)から集めて、自分の表現に混ぜる働きをします。
仕組みを少しだけ書くと、次のとおりです。
- 各トークンは、「探している情報(質問)」と「自分が持っている情報(見出し)」と「中身」の3つを持つ
- 質問と見出しが似ているほど、そのトークンを強く見る。重みを全部足すと1になる
- 見た相手の「中身」を、重みをつけて足し合わせ、自分の新しい表現にする
どこを見るかは、人が決めません。牌譜から学習で決まります。見る視点を8つ(ヘッド)用意して、別々の見方を同時に持たせています。これを6層重ねたものが、v3 の本体です。
CNN との違い
Suphx のような従来の麻雀AIは、CNN(畳み込みニューラルネットワーク)という仕組みを使うことが多いです。画像を読むための仕組みで、小さな窓を少しずつずらして、近くにあるものの関係を見ます。
CNN は、数字が隣り合う牌(三・四・五など)の関係を見るのが得意です。一方、遠い牌どうしの関係(河のリーチ宣言牌と、手牌の安全度など)は、層を重ねて少しずつ伝える必要があります。Transformer は、離れていても直接つなげます。
ただし、どちらが麻雀に向いているかは、この記事の実験では比べていません。同じ条件で CNN を作って比べたわけではないためです。
3. v3 の入力: 局面を「トークン列」にする
ここから、v3 の具体的な作りです。局面を、次の3種類のトークンを1本に並べたものにして、Transformer に渡します。
局情報トークン(1個)
局の状況を、まとめて1個にしたものです。場風・自風・親、何局目・何本場・供託、巡目、山の残り枚数、自分の点数と他家との点差、順位、他家のリーチ(何巡目に宣言したか)と副露の数など、41個の数字が入っています。
牌トークン(37個)
打牌の候補そのものです。37種類の牌それぞれに、1個ずつトークンがあります。各トークンには、その牌についての次の情報が乗っています。
- 自分の手牌に何枚あるか、自分の副露に何枚あるか
- ドラとしての価値
- 場に何枚見えているか、あと何枚残っているか
- 他家3人それぞれについて、河に何枚あるか、そのうち手出しは何枚か、現物か、リーチ宣言牌か、副露に含まれるか
「この牌を切ってよいか」を判断するための材料を、牌ごとに集めたものです。シャンテン数や受け入れ枚数といった、人間が計算した特徴は、入れていません。牌の枚数や河の情報から、AIが自分で導けるようになっているかを見たかったためです。
シャンテン数・受け入れ・安全度(スジなど)を派生特徴として足す実験もしましたが、Top-1 の差は 0.1ポイント未満でした(seed 1本の結果です)。強いAIの学習では、これらを入れていません。
イベント系列(最大128個)
局の最初から、いまの判断までに起きた出来事を、時系列で並べたものです。ドラ表示、自分のツモ、全員の打牌、チー・ポン・カン、リーチが入ります。他家のツモは、牌が伏せられていて分からないので入れていません(打牌の「ツモ切りか手出しか」で、必要な情報は足りるためです)。いまの判断より後の出来事が混ざらないことは、テストで確かめています。
1つの出来事は、次の6つの意味を足し合わせて、1個のトークンにしています。
「誰が」は自分から見た相対席(自分・下家・対面・上家)です。「その人の何巡目」(その人が何回目に切った牌か)まで入れています。打牌には、ツモ切りか、リーチ宣言牌か、リーチ後の打牌か、鳴かれたか、というフラグも付けています。
トークンを足し算で作る方法は、文章AIが単語に「位置」の情報を足すのと同じ考え方です。
4. 全体の流れ
- 3種類の入力を、それぞれ256個の数字(ベクトル)に変換する
- 「局」「牌」「出来事」のどれかを表す印を足して、1本に並べる(1 + 37 + イベント数。イベント数は平均約36、全データでの最大は114で、上限は128)
- Transformer を6層通す。各層で、全トークンが全トークンを見て、情報を混ぜ合わせる
- 牌トークン37個の出力だけを取り出し、それぞれに「256個の数字 → 1個の数字(スコア)」の変換をかける
- 切れない牌(手牌にない牌など)を除いて確率にし、いちばん高い牌を AI の答えにする
出力のしかたがポイント
多くのAIは、最後に「37個の答えを一度に出す」層を置きます。v3 は、牌トークンごとに、同じ変換でスコアを1つずつ出します。「この牌を切るスコア」が、牌の側で計算されるわけです。
この作りのおかげで、手牌にない牌のスコアを無視する処理(合法手のマスク)が、そのまま使えます。また、牌ごとの情報(見えている枚数など)が、その牌のトークンに直接乗るので、「牌とその特徴の対応」を学びやすくなります。
最初に作った版は、特徴を1本のベクトルにまとめて全結合層で混ぜていました。牌と特徴の対応をうまく学べなかったので、この「牌ごとのトークン」の構造に作り直しました。同じ条件の比較で、一致率は 68.9% から 77.1% に上がっています(詳しくは詳細①)。
コードにすると
中核は、これだけです(要点だけ抜き出した簡略版で、入力トークンの作り方と dropout は省いています)。
import torch
import torch.nn as nn
class EncoderLayer(nn.Module):
def __init__(self, d=256, heads=8, d_hid=1024):
super().__init__()
self.attn = nn.MultiheadAttention(d, heads, batch_first=True)
self.norm1, self.norm2 = nn.LayerNorm(d), nn.LayerNorm(d)
self.ffn = nn.Sequential(nn.Linear(d, d_hid), nn.GELU(), nn.Linear(d_hid, d))
def forward(self, x, pad): # x: (B, N, d), pad: (B, N) Trueは空き
h = self.norm1(x)
x = x + self.attn(h, h, h, key_padding_mask=pad)[0] # 全トークンが全トークンを見る
return x + self.ffn(self.norm2(x))
class MiniV3(nn.Module):
def __init__(self, d=256, layers=6):
super().__init__()
self.layers = nn.ModuleList(EncoderLayer(d) for _ in range(layers))
self.final_norm = nn.LayerNorm(d)
self.score = nn.Linear(d, 1) # 牌トークンごとに、同じ重みで1つスコアを出す
def forward(self, round_tok, tile_toks, event_toks, pad, legal):
x = torch.cat([round_tok, tile_toks, event_toks], dim=1) # (B, 1+37+L, d)
for layer in self.layers:
x = layer(x, pad)
tile_h = self.final_norm(x)[:, 1:38] # 牌トークン37個だけ取り出す
logits = self.score(tile_h).squeeze(-1) # (B, 37)
return logits.masked_fill(~legal, float("-inf")) # 切れない牌は選ばない
実際のモデルは、これに入力の変換(局情報・牌特徴・イベントを256次元にする部分)が付いたものです。パラメータ数は、全体で約482万(1層あたり約79万 × 6層が大部分)です。
5. 学習のしかた
- データ: 天鳳の牌譜、2009〜2024年の16年分
- 1サンプル = 1回の打牌判断。正解は、人間が実際に切った牌
- 損失: 正解の牌の確率を上げる(交差エントロピー)
- 最適化: AdamW(学習率 3e-4、weight decay 0.01、2,000 step のウォームアップ、最後は 1e-6 まで減衰)、dropout 0.1
- 規模: バッチ2048、121.8万 step(3エポック)。見たサンプルは延べ約25億
- 環境: RTX 4080 SUPER 1枚、約7.3日
結果は、validation で Top-1 が 77.81%、Top-3 が 97.65% です(強制ツモ切りを除いた約670万局面)。一致率は人間の打牌との一致であり、強さそのものではありません。
6. Suphx との違い
Suphx は、Microsoft Research Asia が 2020年に発表した麻雀AIで、天鳳で安定段位 8.74段に達したと報告されています。論文(arXiv:2003.13590)の記述と、v3 を並べます。
| 項目 | Suphx(論文の記述) | v3(この記事) |
|---|---|---|
| 仕組み | CNN(畳み込み)。打牌モデルは約100層 | Transformer 6層 |
| 入力 | 34種類の牌 × 838個の特徴の表 | 局情報1 + 牌37 + イベント最大128 の系列 |
| 先読み特徴 | あり。100個以上。「この牌を切るとN枚入れ替えで○点の和了に届くか」を、探索で人が計算して入力 | なし。入れても差は0.1ポイント未満 |
| パラメータ数 | 約2,000万(論文の図から私が概算) | 約482万 |
| 打牌モデルの学習データ | 1,500万サンプル | 延べ約25億サンプル(3エポック) |
| 打牌以外 | リーチ・チー・ポン・カンを、別々の4モデルで | 同じ骨格の判断モデルを別に作成(対局エンジンには未接続) |
| 強化学習 | あり(打牌モデル) | なし。人間の模倣のみ |
| 実戦での強さ | 天鳳で安定段位 8.74段 | 未測定(一致率だけ) |
| 打牌の一致率 | 76.7% | 77.81% |
違いの読み方
入力の作り方。 Suphx は、牌の種類(34個)を並べた表を、画像のように CNN で読みます。v3 は、牌と出来事を1本の系列にして、全部を互いに見られる形にしています。
人が作る特徴。 Suphx は、先読み特徴など、人が設計した特徴を大量に入力しています。v3 は、枚数・河・現物といった生に近い情報だけを入れました。派生特徴を足しても精度が変わらなかったので、この入力量なら、Transformer が自分で導いているようです。
学習のしかた。 ここが一番大きな違いです。Suphx は、人間の牌譜で学ぶ段階(教師あり学習)のあと、自己対戦の強化学習で打牌モデルを強くしています。報酬の予測や、他家の手牌を見られるAIを先に作って情報を減らしていく手法(oracle guiding)なども使い、44GPU・2日・150万ゲームの規模です。v3 は、最初の段階(人間の模倣)だけです。
つまり、v3 は「Suphx の、人間の牌譜で学ぶ段階に当たる部分」を、別の仕組みで作ったものです。強化学習をしていないので、人間より強くなる方向の学習はしていません。
一致率の 77.81% と 76.7% は、条件がそろった比較ではありません。学習・評価に使ったデータが違い、強制ツモ切りを除くかどうかなど、数え方も違う可能性があります(Suphx 側は論文に明記がありません)。また、Suphx の一致率は教師あり学習の段階の値で、最終的な強さは強化学習で上がっています。「v3 のほうが強い」という意味ではありません。
7. NAGA との違い
NAGA は、ドワンゴが開発した麻雀AIです。牌譜の解析サービスとして使われています。公開されている記述の範囲で、v3 との違いを書きます。
NAGA の構造は、ドワンゴの技術記事(Dwango Media Village の紹介記事)に、概要だけが書かれています。私が読んだ範囲では、次のとおりです。
- 打牌・鳴き・リーチ・カンの4つの CNN モデルで構成される
- 学習には、天鳳の最上位卓の牌譜を使う
- 入力は、ほぼすべての卓の情報を使う
- 予測の確信度を学習に使う仕組みがある
- 層の数・ユニット数、損失関数、データ量、強化学習の有無は、記事には書かれていない
ここから言える違いは、次のとおりです。
- 骨格: NAGA は CNN、v3 は Transformer です(Suphx と同じ関係です)
- 目的: NAGA は、強さを目的に作られたAIだと理解しています(強さの根拠は、私は確認していません)。v3 は、人間の打牌をまねするAIで、強さを目指したものではありません
- 比較できないところ: NAGA の層数や学習データ量などの詳細は公開されていないので、規模や精度を直接比べることはできません
NAGA の出力や解析結果は、この記事では扱っていません。
8. v3 の中身を覗く(次の記事へ)
Transformer は、中で何をしているかを調べる手がかりが多い仕組みです。たとえば、層4の牌トークンの表現を3次元に落として見ると、「端からの距離」が似た牌(1と9、2と8など)が近くに集まっていました。
ただし、これは探索的な可視化です(層4の、手牌にあるときの局面平均を PCA で見たもので、因果の証拠ではありません)。Attention がどこを見ているか、中間層から「シャンテン数」などの概念を読み出せるか、といった解析は、詳細③に書きました。
9. 限界
- 一致率は、人間の打牌との一致です。強さは測っていません
- 実際に対局するAIにはなっていません。打牌以外の判断モデルは別に作りましたが、対局エンジンにはまだ組んでいません
- 強化学習をしていないので、人間のくせまでまねします(悪い打牌もまねします)
- CNN と Transformer を、同じ条件で比べてはいません。比較したのは、入力の混ぜ方(最初の設計と今の設計)だけです
- Suphx との比較は、データも評価条件も違う、参考値です
まとめ
- v3 は、局面(局情報・牌37・出来事の系列)を1本のトークン列にして、Transformer 6層で読む、打牌予測のAIです
- 牌トークンごとにスコアを出す作りで、合法手のマスクもそのまま使えます
- Suphx との大きな違いは、CNN か Transformer か、人が作る特徴の量、そして強化学習の有無です
- NAGA とは、公開されている範囲では、骨格(CNN か Transformer か)と目的の違いが言えます
- 人間の打牌との一致率は 77.81%。ただし強さの指標ではありません
質問や「ここを詳しく」があれば、コメントで教えてください。
参考
- Li, J. et al. (2020). Suphx: Mastering Mahjong with Deep Reinforcement Learning. arXiv:2003.13590
- Dwango Media Village: 麻雀AI NAGA
- Vaswani, A. et al. (2017). Attention Is All You Need. arXiv:1706.03762
- 牌画像: FluffyStuff/riichi-mahjong-tiles(CC0)






