tomohxxの日記

麻雀プログラミング

麻雀AI開発DL編(3)

はじめに

前回記事で課題としていた押し引き判断と降り手順が改善しました。原因はプログラムのバグで、安牌を正しく認識できていませんでした。これは致命的なミスですね。というわけでバグを修正したプログラムで前回と同様に対Manue一局戦1000試合による評価を行いました。

項目 データ
試合数 1000
局数 1000
和了 0.261
放銃率 0.091
リーチ率 0.207
副露率 0.381
平均得点 6500
平均失点 6042
平均順位 2.306
1位率 0.348
2位率 0.217
3位率 0.216
4位率 0.219

放銃率が10%を下回っています。その他の項目も特に問題はなさそうです。ではいつものように牌譜を見ていきます。

牌譜検討

回し打ち1

f:id:tomohxx:20210706021633p:plain
牌譜1

対面と下家のリーチを受けての打7sです。共通安牌は6sですが6sを切ったところで後が続かなそうなので、聴牌維持かつ暗刻落としにできる7sですかね。

回し打ち2

f:id:tomohxx:20210706023100p:plain
牌譜2

現物5mを切っての回し打ちです。待ちの枚数は少なくなりますが安全度を重視しました。

降り

f:id:tomohxx:20210706023651p:plain
牌譜3

打4pとした場面です。残り枚数的に和了れないので前巡に対面が切った牌を合わせ打ちしました。それまでの手順も問題なさそうです。

押し1

f:id:tomohxx:20210706025612p:plain
牌譜5

ツモ西打8pとした場面です。前々巡で7pを手出しにしているので安全度を見ての逆切りです。前巡で5mをツモりましたがより危険な方を先に切っています。

ホンイツ仕掛け

f:id:tomohxx:20210706025032p:plain
牌譜4

打2pとした場面です。タンヤオドラ1でケイテンも近いですが、宣言牌のそばの2pを切っての押しがいいのか私にはわかりません……。ちなみにこの後下家から7mで出和了りました。

押し2

f:id:tomohxx:20210706031103p:plain
牌譜6

高目ホンイツ一通が見える状況での打4pです。リーチしてから5巡目だから無筋を押せるということでしょうか?私は押し引きの加減がよくわからないので自信がありません。

ダブリー

f:id:tomohxx:20210706031707p:plain
牌譜7

ダブリーツモの場面です。珍しかったので掲載しました。

おわりに

今回の評価で放銃率が改善して安心しました。今後も引き続きAIの評価を進めていきたいと思います。

麻雀AI開発DL編(2)

今回は麻雀AI開発の進捗について書きます。前回の記事で書いた課題は以下の2つです。

  • 鳴き判断モデルの取り込む
  • 放銃率を下げる

1つ目は解決できたのですが、今は2つ目で苦戦している状況です。詳細な説明の前に、まずは最新のモデルを使ったManueとの対戦成績について見てみます。

項目 データ
試合数 1000
局数 1000
和了 0.25
放銃率 0.14
リーチ率 0.22
副露率 0.41
平均得点 6435
平均失点 6199
平均順位 2.40
1位率 34.3
2位率 19.8
3位率 17.2
4位率 28.7

鳴き判断モデルを取り込んだことで副露率が下がり平均得点が6000点を超えるようになりました。しかし放銃率を下げることができませんでした。放銃率は11%以下にしたいと思っています。またリーチ者への放銃が多いせいか平均失点が6000点を超えています。これを6000点未満にしたいです。和了率、リーチ率は問題なさそうです。よって放銃率(とそれに付随する平均失点、平均順位)が現状の課題です。次に牌譜を見てみます。

始めにいいと思った場面から見てみます。

f:id:tomohxx:20210526224105p:plain
牌譜1

打9mとした場面です。9mと発どちらも1枚見えですが発で待つ方が上がれそうです。

f:id:tomohxx:20210526224612p:plain
牌譜2

打北とした場面です。ここまで安牌として北と中を抱えているだけも素晴らしいです。中は全員に対しての安牌なので北から切ったのはいい判断です。

f:id:tomohxx:20210526225528p:plain
牌譜3

打中リーチとした場面です。七対子なので字牌切りリーチはいいと思います。1枚見えの中で待ったほうがいい気もします。

f:id:tomohxx:20210526230004p:plain
牌譜4

打4mとした場面です。三色同順を狙っています。

続いて悪いと思った場面を見てみます。ほぼ押し引き絡みです。

f:id:tomohxx:20210526230345p:plain
牌譜5

打1mとした場面です。打5sではだめなんですか?愚形含み2シャンテンなのでおそらく降りることになります。打5sの後3s, 2s, 1sの順に切っていけばいいと思います。

f:id:tomohxx:20210526231039p:plain
牌譜6

打9pとした場面です。この場面はなかなか苦しいです。南を切ると押し返せなくなるので仕方がないですね。

f:id:tomohxx:20210526231907p:plain
牌譜7

打3pとした場面です。なぜ4mを切らないのでしょうか?

f:id:tomohxx:20210526232358p:plain
牌譜8

打9pとした場面です。一般的に端の牌は当たりにくく、この場合は9pを2枚持っているのでここで9pを通せば次巡でも9pを切れるというメリットがあります。しかし、この手は良形になるかもわからない2シャンテンなので今通りそうな筋3mで降りた方がいいと思います。あるいは打8mですかね。

以上、いくつか牌譜を見てきました。押し引き判断と降り手順がいまいちです。それ以外の場面ではいい感じなのですが押し引きと降りが他の良いところを台無しにしています。正直なところこの問題の解決策がわかっていません。例えば牌の危険度をディープラーニングで予測するということは簡単にできて、一定の危険度以上の牌を切らせないようにするといったことはできそうです。ただ、ディープラーニングを使う目的が押し引きである以上、押し引き判断に介入したくありません。ディープラーニングを使ったとしても押し引きは難しい問題なのでしょうか?ここまで読んでくれた方、もし改善案があったらぜひ教えてください。

あと余談ですが最新の打牌選択モデルの牌譜一致率は74.1%です。Suphxが76.7%なのでもう少しで届きそうですね。まあ、牌譜一致率ではどのような打ち方をするのかわからないのと、データを増やしたり計算資源を増やしたりすることで牌譜一致率を上げられるので、牌譜一致率に対する興味は薄れつつあります。今はデータと計算資源の両方を増やす余力があるので、本気を出せばSuphxを超えらるのかなと期待しています(お金がかかるのでやりたくはないですが)。

麻雀AI開発DL編(1)

はじめに

以前の記事でディープラーニングを使った麻雀AIを開発すると書きました。今回はその途中結果についての記事です。

なぜディープラーニングを使うのか

なぜディープラーニングを使うのか念のため振り返っておくと、それは押し引きを明示的に作りこむのが難しいからです。つまり押し引き判断の部分をニューラルネットワークに行わせることを目的としています。

モデル

Suphxの論文を参考にしながら一部で独自の工夫を施しました。Suphxでは100層、フィルタ数256のResNetをすべての行動(打牌・リーチ・ポン・チー・カン)に共通して用いています。ここでSuphxモデルの特徴について簡単に解説します。

一般的にCNNを使って手牌をエンコードする場合、34 x 4 x 1の配列の高さ(幅)方向を使って各牌の枚数をエンコードすることが多いです。ところがSuphxは34 x 1 x 4の配列の奥行(チャネル)方向を使って手牌をエンコードしています(
図があるとわかりやすいです。気が向いたら用意します。)。これによりデータの密度を上げられるというメリットがあります。例えばカテゴリカルデータをエンコードする場合、1つのシートを0か1で埋める必要がありますが、前者では136個の要素を使うのに対し後者では34個の要素を使うだけで済みます。そのため学習中に使うメモリを節約することができます。ちなみにResNetはニューラルネットワークを多層化するテクニックとしてよく用いられるみたいです。

では、特徴量についても説明します。Suphxの論文では、どの特徴量を使っているのか、特徴量をどのようにエンコードするのか、を完全には明らかにしていないと思います。主な特徴量は以下です。

  1. 牌の集合型の特徴量:手牌、ドラ
  2. シーケンス型の特徴量:河
  3. 整数型の特徴量:持ち点、壁牌の枚数
  4. カテゴリカルデータ:局番号、親、連荘、リー棒

これらに加えて先読み特徴量といって自分が何点であがれそうかを表す特徴量があります。私も基本的にこれらの特徴量を使いました。

レーニン

天鳳鳳凰卓の牌譜を使って打牌選択モデルと立直判断モデルを作りました。前者の牌譜一致率は73.3%、後者の牌譜一致率は79.0%でした。ちなみにSuphxではそれぞれ76.7%、85.7%になっています。

実験

自作の麻雀AIをManueと対戦させました。例のように一局戦1000試合です。なお、鳴きの判断の部分は手違いがあってヒューリスティックにしました。今後、鳴き判断もニューラルネットワークして実験したいと思います。

項目 データ
試合数 1000
局数 1000
和了 0.28
放銃率 0.13
リーチ率 0.18
副露率 0.50
平均得点 5641
平均失点 6054
平均順位 2.28
1位率 37.4
2位率 20.4
3位率 19.0
4位率 23.2

平均順位が以前に開発したAIよりもかなり良いです(以前は2.35でした)。副露率が高すぎる気がしますがこれは私の調整によるものです(鳴かないよりは鳴いた方がいいかなぐらいのざっくりとした調整です)。以降で具体的な打ち筋について牌譜のスクリーンショットを交えて考察していきます。

牌譜検討

守備意識

f:id:tomohxx:20210503164354p:plain
牌譜1

打9sとした場面です。手があまりよくないので字牌安牌を抱えています。攻撃がない場面でも守備を意識しているようです。

f:id:tomohxx:20210503165141p:plain
牌譜2

打3pとした場面です。守備を意識して3pから逆切りしています。

押しすぎ

f:id:tomohxx:20210503165923p:plain
牌譜3

打7pとした場面です。平和赤ドラ2が見えますがここから押すのは厳しそうです。

降り

f:id:tomohxx:20210503171018p:plain
牌譜4

打8sとした場面です。あがる見込みのない場面では降ります。その際に発ではなく直前に上家が切った8sを選択しています。手牌の安全度を理解しているようです。

5ブロック理論

f:id:tomohxx:20210503172005p:plain
牌譜5

打7pとした場面です。現在のブロック数は6ですが強いブロックを残すというわけで打7pとしたようです。三色同順に関係する部分に手をつけないのもポイントです。

立直判断

f:id:tomohxx:20210503180524p:plain
牌譜6

打8s立直とした場面です。特に立直をかけない理由はないです。

f:id:tomohxx:20210503173202p:plain
牌譜7

打4mとした場面です。タンヤオドラ3で打点十分なので立直をかけませんでした。

待ち取り

f:id:tomohxx:20210503173613p:plain
牌譜8

打4sとした場面です。この局では最初から七対子に向かっています。テンパイしたときにあがりやすいように一九字牌を多く抱えています。ここではツモ5pなのですが、2p8pがすでに河にあるので他家から見て無筋4sよりも中筋5pの方があがりやすいと判断したようです。

まとめ

目的の押し引き判断についてある程度はできているようです。ただ立直に対して押しすぎな面があり、これが平均失点が6000点超えと高くなっている理由だと思われます。今回のように牌譜一致率を高めるというトレーニングではすべての局面を同じ重みで評価してしまうので、例えば対立直での牌譜一致率を重視するということができません。ちなみにこのうなトレーニングは強化学習の範疇なのではないかと思います。今後は放銃率を下げる方向で改善していきたいと思います。

おわりに

切り順や待ち取りは今回の目的ではなかったのですが、思いがけず学習してくれたみたいでディープラーニングのすごさを思い知りました。ディープラーニングの特徴として、人間が明示的に特徴を教えなくても勝手に特徴を見出す、とどこかで読んだ気がするのですがまさにその通りだなと思いました。いくつか課題はありますがとにかく麻雀の基本的な行動を学習できたのでよかったです。次回は鳴き判断にもニューラルネットワークを使ってAIを作ろうと思います。

Mjaiイベントログ管理ツールの開発

ここ2週間くらい、Mjaiイベントログ管理ツールMjai Recoderを作っています。Mjaiとは麻雀AI対戦環境のことです。

gimite.net

このブログでは過去に自作麻雀AIの評価に利用させてもらっています。gimiteさん、ありがとうございます。

Mjaiは対戦機能だけでなく各クライアントの平均順位とその信頼区間を計算するという成績管理機能が備わっています。ただ、クライアントの成績を知るとなると平均順位だけでは不満です。例えば和了率や放銃率、順位分布は知りたいところです。実のところこれらのデータを知りたければログファイル(*.mjson)を解析すれば簡単に済んでしまいます。過去の自作麻雀AIではPythonスクリプトファイルでログファイルを解析していました。

ではなぜ今回Mjaiイベントログ管理ツールを作るに至ったのかというと、複合イベントの割合を簡単に知ることができるようにするためです。例えば和了率を知りたければ(和了回数)/(局数)のように計算しますよね。またリーチ率を知りたければ(リーチ回数)/(局数)で計算します。ではリーチをかけた状態から和了した割合はどのように計算するでしょうか?(リーチから和了した回数)/(リーチ回数)で計算しますね。さて、(リーチから和了した回数)のような複数のイベントが組み合わさった変数を安易に受け入れてよいのでしょうか?和了、リーチ、鳴きといったイベントの組合せの数は有限ではあるものの、複合イベントが発生した割合を知るためにイベントの組合せに対応する変数を定義していくと、スクリプトファイルが読みにくくなりバグの混入の原因にもなりかねます。

そこで、複合イベントの割合を知るためにSQLを利用することにしました。事前にDBを用意しておいて、各イベントテーブルに各イベントが発生したときの状況を記録しておきます。(複合)イベントの割合を知るために関係するテーブルに問い合わせて発生回数を数えるようにします。SQLの内部結合、副問合せなどの構文を使えば、複合イベント用のテーブルを用意することなく発生回数を数えられるようになります。ただイベントの組合せによってはSQL文が難解になってしまう可能性があります。

今回開発したMjai Recoderは、Mjaiサーバー機能を提供しつつ生成されたログファイルを解析してイベントの発生状況をDBに記録するツールです。Mjai Recorederはあくまでイベントログ管理ツールなので、成績を知るためには別途DBのクライアントを用意してSQL文を実行する必要があります。ユースケースとして、シェルでSQL文を実行するのではなく、MetabaseのようなDBと連携してデータを可視化するツールを利用することを想定しています。

github.com

www.metabase.com

簡単に使い方を説明します。Mjai Recoderは以下のようなテーブルを管理します。

f:id:tomohxx:20210327232918p:plain
ER図

例えば和了率は次のSQL文で知ることができます。

select count(*) from winnings inner join players on winnings.actor = players.id where players.name = 'player_name';

今後はテーブルの数を増やしていろいろな複合イベントの割合を計算できるようにする予定です。

お知らせ

はじめに

明けましておめでとうございます。昨年の麻雀AI開発はアルゴリズムで進展があったものの、自動打ちでは期待していた成績を残すことができませんでした。なので今年も麻雀AIの開発を続け、今年中の完成を目指します。さて、今回は2点お知らせがあります。

「麻雀で学ぶアルゴリズム」について

昨年このテーマで2回記事を書きましたが、ブログではやりにくいと感じたので別の場所に書きたかったことをまとめました。

tomohxx.github.io

今後の計画について

今年はDeep Leaningを使ったAIを開発しようと思っています。Deep Learnigを使う理由は、打牌選択で押し引きを手動で調整することが難しいからです。押す場合の何切る、降りる場合の何切るはそれなりの精度で選択できるのですが、その中間の行動、「押し気味に打つ」とか「降り気味に打つ」という行動は簡単ではありません。天鳳自動打ちでは和了率が24%の一方、放銃率が15%という結果に終わってしまいました。それで押し引きの調整のためDeep Learningを使おうというわけです。ちなみに今挙げた理由はルールベースではなく機械学習を使う理由にしかなっていないのですが、詳細は今後明らかにする予定です。もっとも先行研究ではCNNを使う例が多いですが、それだけではDeep Learningを使う理由にはならないと思っています。ルールベースでは不十分とする理由が欲しかったのです。

あと、自動打ち関係で入出力についての記事も書いてみようかなと思っています。天鳳自動打ちでは入出力の部分を自分では作っていないのですが、Flashの動作保証が終了した関係で入出力の部分を一新する予定です。入出力についてはインターネットにほとんど情報がないので、まとめておくのもいいかなと思います。

おわりに

今年もよろしくお願いします。

麻雀AI開発(6) 天鳳自動打ちの結果

はじめに

今回は天鳳自動打ちの結果を報告します。前回記事はこちら。
tomohxx.hatenablog.com

結果

参加した卓
四般東喰赤
対戦数
182
ID
Oct2320
牌譜の公開
Dropbox - 天鳳自動打ちログ.txt - Simplify your life
f:id:tomohxx:20201129133607p:plain
通算成績

感想

一言で言うと弱いですね。牌譜を見ると良くない振る舞いは以下かと思いました。

  1. 対副露、対門前の押し引き
  2. 得点状況を意識したリーチ判断
  3. リーチ時の出やすさを意識した待ち選択

特に押し引きをどうにかしたいです。リーチを受けたから降り、そうではない場合は完全に押しではなく、例えば親の二副露を受けたから対親の危険牌を安易に切らないようにしつつ手を進めるというように押し引きのグレーゾーンを設けたいです。これまでは押し引きを手動で調整していましたが無理がありそうです。

おわりに

同卓していただいた皆様ありがとうございました。なお今後の計画は未定です。

WSL2にmitmproxyをインストールする

はじめに

最近はWebページを見る際にほぼHTTPSを利用するようになりました。HTTPSでは通信内容が暗号化されているわけですが、これを見たいと思ったことはないでしょうか?それを実現するためにmitmproxyが利用できます。

mitmproxyについて

mitmproxyとは仮想的なプロキシサーバのようなもので、サーバーとクライアントの間に入って平文の通信内容を取り出すプログラムです。以下の動作イメージは公式サイトからの引用です。

https://docs.mitmproxy.org/stable/schematics/how-mitmproxy-works-explicit.png

インストール手順

Windows上のUbuntu-20.04 (WSL2)でmitmproxyを動作させます。適当に作業ディレクトリとPython仮想環境を作ってpipでmitmproxyをインストールします。

$ mkdir work
$ cd work
$ virtualenv venv
$ . venv/bin/activate
$ pip install mitmproxy

使い方

いろいろな使い方があるので詳しくは公式サイトを見てください。ここではWebブラウザでパケットを見る方法を説明します。まずシェルでmitmwebを実行します。

$ mitmweb --web-host=0.0.0.0

すると、mitmproxyが127.0.0.1:8080でリクエストを受け付けるようになります。またこのとき自動的にGUIが起動すると思います。

次にmitmproxyの証明書をインポートします。クライアントから見るとmitmproxyは突如現れた得体の知れない存在なので、mitmproxyを経由してインターネットにアクセスしようとするとWebブラウザが危険と判断してブロックしてしまいます。Webブラウザにmitmproxyが信頼できる存在と教えておく必要があります。http://mitm.itにアクセスしてWindows用の手順に従って証明書をインポートします。[Show Instractions]ボタンをクリックすると以下のように詳しい手順を見られます。

f:id:tomohxx:20201119234315p:plain

証明書のインポートが終わったら、Windowsの設定から[プロキシサーバを使う]をオンにします。ここで入力するアドレスは127.0.0.1ではなくWSL2のIPアドレスです。これはipコマンドで調べられます。注意点としてWSL2のIPアドレスは起動の度に変わるので、その都度プロキシサーバのアドレスを書き換える必要があります。[プロキシサーバを使う]をオンにしたら[保存]ボタンをクリックするのを忘れないようにしましょう。

$ ip a

この状態でWebブラウザから適当なページを開くと、GUIでHTMLやJavaScriptPNGなどのパケットをやりとりしているのが見えると思います。

WebSocketについて

WebSocketのパケットを見たい場合は、
How can I capture websocket traffic · Issue #899 · mitmproxy/mitmproxy · GitHub
が参考になります。

おわりに

mitmproxyの使い方を簡単に説明しました。一応、mimproxyを利用する構図は中間者攻撃のものと同じなんですよね。くれぐれも悪用しないでください。