【お勉強】ポケモン対戦の自動シミュレーションと勝敗予測
お勉強記事に近い。けどせっかく分かりやすい結果があるので軽くブログを書いてみる。
最近流行りの機械学習(ただのマイブーム)を使って、ポケモンの組み合わせからシングルバトルの勝敗を予測する予測器を作成してみたお話です。
まだ実用性のあるものはできていませんが…
背景
Percymonというポケモンの簡単なAIを使って対面相性の計算や、選出予測などをしていた。
今まではとりあえず1匹vs1匹 の対面相性値だけ計算して、その他(PTバランスとか)は独自でアルゴリズムを考えてやりくりしていたのだが、普通に3匹vs3匹でシミュレーション対戦させまくればいいんじゃね?と思ったので対戦データの収集と、機械学習を使った予測をやろうと思った。
対戦データの収集
(1)あらかじめ型を定義したポケモンリストの中からランダムで3匹ずつ選ぶ
(2)Percymon上で、[自ポケ1, 自ポケ2, 自ポケ3] vs [相手ポケ1, 相手ポケ2, 相手ポケ3]の対戦インスタンスを作る。
(3)対戦インスタンスの各ターンで、自分視点のMinimax計算を行う
(4)Minimax計算の結果からお互いの最善手を選び実行する。
(5)以上を対戦が終了するまで繰り返し
(6)勝敗データをPTポケモンの情報とともにDB保存
(7)以上を気が済むまで自動で繰り返し
PTバランス計算用にPostgresSQL(DBソフトウェアのことです)にポケモンの種族・型のデータ構造を作ってあったので、勝敗データ保存用のテーブルだけ新規作成してやる。
ちなみにポケモンの型情報の方は単にPokemon Showdownでエクスポートできる文字列を整理して保存してあるだけ。最終的にPercymonに入力してShowdownライブラリを流用したシミュレーション対戦ができる。
(4)のMinimax計算結果からお互いの最善手を抽出するのはちょっと理論的におかしい部分があるのだが、今回はまだテスト目的なのでそんなに気にしないことにした。
ちなみにもっと適当にやるならコマンド選択は全部ランダムで、というやり方になるがこれだとさすがにPT同士の有利不利が全然現れなかった(ほぼ5分5分)のでやめた。
[ウインディ, シャンデラ, ドラパルト] vs [ホルード, ウオノラゴン, パッチラゴン]
お互いランダム選択の場合
プレイヤー1 Wins: 48
プレイヤー2 Wins: 52
平均ステップ(≒ターン)数: 13.74
お互いMinimax(深さ1)の場合
プレイヤー1 Wins: 90
プレイヤー2 Wins: 10
平均ステップ(≒ターン)数: 12.6
今回は、
・全7匹からランダムで3匹vs3匹を選択
・63232試合分
データを作成しておいた。ポケモンの種類はもっと増やせるが、機械学習の計算量を考えてとりあえず今回は7匹に絞った。
決定的識別モデル ~うまくいかない~
最初に作ったのは
入力: 自分PTのポケモンの組み合わせ
出力: 予測される勝敗(0か1か)
となる予測器である。機械学習の分野では初歩的な教師あり学習だと思う。
この分野ではおなじみの、pythonとscikit-learnを使った。ライブラリの充実さにビビり倒してしまった。Pandasでエクセルシート読み込みからカラムの切り出しまで数行で実現できるのは便利すぎる。
Python公式リファレンスには非プログラマー向けの簡単なチュートリアルが置いてあったり、そもそも公式リファレンス読まなくても日本語でググってるだけである程度できそうなのは初心者に優しいですね。
ということでほぼライブラリ関数に突っ込んだだけなのだが、入力データ(特徴量)の整形だけちょっと書いておく。図を見ればわかりそうなので文字はちっちゃくしておく。
特定のポケモンの種類の組み合わせを入力として、勝ち負けの値が答えとしてあるデータなので、カテゴリカル変数から2値のクラス分類をするモデルになる。
順序や量を持たないカテゴリカルな変数なので、One-Hotエンコーディングと呼ばれる、対応する成分だけ1となるようなベクトルの形に変換する。
ただ、今回は同じリストから選択されるポケモンが3匹まで存在するので、同じ次元のベクトルで、1となる成分が3個存在するベクトルを入力値とした。(それぞれのOne-Hotベクトルを足し合わせる)
(一般的になんというエンコード手法かわからないけど、たぶんこれでいいと思う)
このベクトルを自分PT、相手PTからそれぞれ作成できるのだが、
最初は同一の相手PTに対する勝敗データを使い、自分PTのみを入力値をした。
スモールステップは大事
あとは、このベクトル1つ1つを対応する勝敗と組み合わせて、教師データとして学習関数に突っ込む。
そして何も考えずにSVC(Support Vector Classifier)に突っ込んだ結果の学習曲線がこちら。
Scoreが正解率を表す。その他は機械学習の用語になるが雰囲気で感じ取っていただきたい。
正解率が0.75付近で早々に収束してしまうのである。
正解率が100%にならない理由は、想像がついた。今回PercymonとMinimax計算を使って行った対戦シミュレーションには勝敗にある程度の揺らぎがあり、同じPTの組み合わせでも3回に1回くらいは勝敗が逆転したりする。
それで正解率は0.75ぐらいが限界だと。実際それは正しいと思う。
しかし、翌朝ふと思いついて予測器にある入力を入れてみると、
対戦シミュレーションで100%勝利していたPTに対しても、「負け」と分類している!
このときの分類器の気持ちはこうである
「この相手PT強くて、大抵負けるから、どの自分PTが来ても負けって言っておけばだいたい当たるんじゃね」
上は混同行列と言われるもの。
教師データに混在する誤り(勝敗の揺らぎ)と全体としての勝敗の偏りのせいで、とりあえず「負け」と宣言するだけの予測器になってしまった?と考えている。
確率的識別モデルとROC曲線
最初に作った分類器は少し無理があったのにお気づきだろうか。
予測する勝敗を0か1かで答えろというのは、少し雑すぎる。
ということで機械学習の教材を少し復習して、タイトルにあるようなものを出力するようにした。
確率的識別モデルとは、予測を(0か1かではなく)確率で表現する。今回だと「勝つ確率が0.34で、負ける確率が0.66」といった具合。分類器の中で「確信の度合い」を表すなんらかの連続値が出てくるということ。
そしてこのモデルの性能を評価するのにはよくROC曲線という評価方法が使われる。
ROC曲線についてはネット上にもたくさん情報があるが、最近PCR検査の性能を論じるときにもちょっと話題になった。今回の予測モデルで説明すると、次の相反する性能を同時に評価する。
縦軸: 本当に負けな試合を負けと判定できる割合(感度)
横軸: 本当は勝ちな試合を負けと判定してしまう割合(偽陽性率)
負けがPositive、勝ちがNegativeになってしまったのでわかりにくい
分類器の気持ちになって考えると、たとえば、
・全部に負けと言っておけば感度は100%になるが偽陽性率も100%になる(グラフの一番右上に対応する)
・全部に勝ちと言っておけば偽陽性率は0%だが感度も0%になる(グラフ左下)
→グラフ左上にプロットされる性能をもつ分類器が最高。
確率的識別モデルの場合、出力は0から1までの連続値が出てくるので、どこからどこまでを負け/勝ちと解釈するかは後から決められる。ということでそのしきい値を連続的に変化させていって、それぞれの(感度, 偽陽性率)をプロットしていったものがROC曲線になる。
学習したモデル全体としては、この曲線より右下の領域の面積を計算して評価値としたりする。
直感的にはこのROC曲線の真ん中ぐらいの感度/偽陽性率 (となるしきい値)を採用したいところだが、今回のようにテストデータに偏りがある場合、単純に正解率だけを追い求めるとROC曲線上で端っこの点に対応するような分類器が出来上がってしまう。(で合ってる?)
ということでこのROC曲線を使って性能評価した結果が今回の最終結果です。
右下のarea = 0.XX の数字が大きいほど良いと思ってもらっていいです。
SVC (Support Vector Classifier) のprobabilityオプションをTrueにして計算しました。
[SVC、相手PT固定のデータ、N=7242]
[SVC、C調整、相手PT固定のデータ、N=7242]
ここまでの学習は、同一相手PTに対するデータで行っていた。
だいたいうまくいったので、満を辞して自分PT、相手PT両方を入力値として学習させてみる。
[SVC、C調整、相手PT変動、N=20000]
特に問題なし。相手PTが変動するので、データの偏りも少なく学習しやすかったはず。
これを今回のまとめにしたいと思う。つまり、たとえば図の赤丸に対応するしきい値を採用して最終的な勝敗を解釈したとすると、
負け試合を70%の確率で正解できて、
勝ち試合を60%の確率で正解できる分類器ができる。
微妙すぎるって??
教師データをきれいにすればちゃんと予測できるんですよ
次回へつづく (たぶん)
[SVC、C調整、相手PT固定、勝敗の矛盾を削除※、N=7242]
※同じ(自分PT、相手PT)の組み合わせについてはつねに同じ勝ち/負けの結果となるように、勝敗ラベルを多数決で決定し直した
【ポケモンAI】パーティーバランス計算ツール Ver. 2.0
ポケモン剣盾 パーティバランス計算機
https://pokemon-tp-eval.herokuapp.com/
アップデート内容
UIデザインの全体的な改良
フォントサイズ、余白などを見直して画面の小さなスマホでも見やすいように改良しました。また、各所に説明文を設け、簡単な使い方をその場で確認できるようにしました。
候補ポケモンの追加(39匹)
冠の雪原解禁ポケモンを含む39匹が新たにパーティーポケモン、仮想敵として選択できるようになりました。
パーティ設定 ポケモン名の直接入力機能を追加
今までパーティのポケモンはリストからの選択でしか設定できませんでしたが、キーボードからの直接入力に対応しました。
※同じポケモンに複数の型が存在する場合、入力するポケモン名は同じとなってしまいます。サジェスト結果も複数表示されるため、どちらをタップするかによって区別することができますが、この扱いについては検討中です。
パーティ設定 個別評価グラフの追加
パーティ設定の各スロットの右横に「グラフ」ボタンを配置し、ポケモン単体での対面評価値グラフを確認できるようにしました。
各仮想敵への強さ評価方法の変更
従来「各仮想敵への強さ評価」という名前で表示されていたものを、新しい評価値グラフに置き換えました。グラフとしての表示内容は同じですが、計算方法が異なります。
以前の記事で「最大値最小法」と紹介したアルゴリズムに基づいています。
※検索タブから利用できるいくつかの評価メソッドの中身も、この結果を利用するように変化しています(「PTが最も苦手なポケモンへの強さ」など)。
各選出の平均評価グラフの追加
新機能です。パーティから3匹を選ぶ20通りの選出それぞれに対して、有利不利の期待値を表示します。これは十分な数のランダムPTに対して選出計算機能(未公開)を用い、その選出が1試合あたりに見込める有効度(相対値として考えてください)を平均的に算出しています。大まかに書くと、次のようになります。
(選出が採用された試合の評価値の合計) / (選出が採用された試合数)×(選出の採用率)
※その選出が最善選出と判定されることを「採用」と表記しています
各ポケモンの平均評価グラフの追加
新機能です。上記と同様のランダムPT相手の計算結果を用いて、そのポケモンがPTに存在することで1試合あたりに見込める有効度を算出しています。
(最善選出に含まれた試合の評価値の合計) / (最善選出に含まれた試合数)×(最善選出に含まれる確率)
注意点としては、ポケモン単体の対面評価値(の合計)とは一致しません。PTの中の他のポケモンとの3匹の組み合わせの中で、どれだけ有利を重ねられるかという指標で、今のPTの中での総合的な活躍の度合いを表します。
がりゅう、イタリアでバイクを買う
この記事ではがりゅうがイタリアでバイクを購入するまでのサクセスストーリーを紹介します。
(前提) イタリアのトリノという街に仕事の都合で住んでます。
もくじ
- 近所にできた謎のバイク屋 (9月頭)
- 運転免許証の申請 (9月中旬)
- 謎のバイク屋で話を聞く (9月中旬)
- 店選び (9月中旬)
- 価格見積もり (10月頭)
- 仮免許証(Recevuta)の入手 (10月中旬)
- 購入 (10月中旬)
- バイク用品の購入 (10月中旬)
- 駐車場契約 (10月下旬)
- バイク保険契約 (10月下旬)
- 納車 (10月29日)
近所にできた謎のバイク屋 (9月頭)
たぶんすべてのきっかけ。
8月中旬ぐらいにイタリアに帰ってきた(一時帰国してた)のだが、自主隔離が明けた9月頭ぐらいにこの店 (Errebi) を見つけた。自宅から歩いて5分ぐらいのところにある。このあたりにバイク屋なんてなかったので珍しい。
なんだこの店?
運転免許証の申請 (9月中旬)
バイクを買いたくなるちょっと前のできごと。過去記事でまとめた通り、運転が全くできないのは困るということで、今から1ヶ月ほど前に免許証の申請を始め(てい)た。
これが完了するタイミングが後々の納車時期を左右することになる…
謎のバイク屋で話を聞く (9月中旬)
入店してみた。チャオ
どんなバイクが置いてあるのか、チラ見する程度のつもりだったが、予想以上に店員さんが丁寧に対応してくれる。聞くと、このErrebiというバイク屋はつい2ヶ月前に開店したばかりとのこと。
てっきり外国人(かつ言葉もあんまり通じないやつ)は対応に時間がかかるし門前払いされるかと思ったが、買ってくれるなら頑張ってサポートするよとのことで、「あれ意外と普通に買える?」と思った。
主力製品は、イタリアFantic Motor社製 Caballero Scramber 125/250/500 である。
http://www.caballerofantic.com/en/scrambler
店選び (9月中旬)
この時点でもう既に、免許もいずれ手に入りそうだし、どこかしらでバイクを買ってみようと思っていた。たださっきの店(Errebi)で即断しなかったのは、そこまで好きな車種がなかったから。
まあなんにせよ高い買い物なので、選択肢は多くしておきたいと思って、他のバイク屋も回ってみようと思い立った。詳しい知り合いもいなかったのでとりあえずGoogleマップで検索。
バスで20分+歩いて20分とかの場所にしかなかったので、効率良くこなすならマジでバイク屋を回るためのバイクが必要
KawasakiとかBenelliとかApriliaとかが置いてあった。
ちなみに日本のレッドバロンみたいな、巨大な全国チェーンはたぶんイタリアにはない。そもそもチェーン店の文化が…
ここで重要な情報を得る。
外国人がバイクを購入するためには、以下の身分証明書が必要であると。
この時点で、Carta d'identitaだけは持っていなかった。ただ、並行して進めていた免許証申請の過程で入手できそうな気がしていた。
ということで、Carta d'identitaが入手できることを祈りながら、しばらく(2週間ぐらい)待機することにした。
価格見積もり (10月頭)
Carta d'identitaは届いた。車両だけならもう購入できると思ったので、本格的に購入の話を進めることにした。
店選びについては、結局
最初の店(Errebi)で買うことにした。
理由は何かと親身に対応してくれそうだったから。それと
いつも暇そうだから気軽に話にいける
点である。(最近は店の知名度が上がってきて他のお客さんもよく見かけるようになった)
イタリア語も英語もそんなに得意じゃないからどうしても、忙しそうな店員に話しかけるの勇気いるんですよね。
ということでScramber 250の見積もり書(Preventivo※)を発行してもらう。ああそうかどこの国でも見積もり書ぐらいあるよなと謎の感動をした。
日本のように高額な手数料がなくて分かりやすい。どうやら、ナンバープレート申請代行手数料は310EUR(250ccの場合)と法律で決まっているらしく、基本的に店頭の値札の価格にこの金額しか足されない。
※このPreventivoという単語はバイク保険の見積もりのときにも目にすることになるので、嫌でも覚えます。
ここらへんから割とガチめの質問を繰り返していたのだが、一番の懸念点となったのが
免許証がいつ届くかわからない
点だった。
ちなみになぜ免許証もまだ持ってないのに急いでバイクを買おうとしていたのかというと、一つには冬が来る前に納車したかったから。もう一つは僕のイタリアでの任期も残りわずかなので、(残りわずかなのに新車でバイクを買うのは意味がわからないが、)とにかく時間を有効に使いたかった。
今この記事を書いている時点(10/31)ではイタリアの2回目のロックダウンが現実的になりそうで、それまでのタイムリミットを有効に使うという第三の理由も生まれた。
とにかく免許証の現物がないと色々とリスクもあるので、また少し考えることにした。
仮免許証(Recevuta)の入手 (10月中旬)
仮免許証と書いたが日本の教習所で第一段階を終えてもらえるアレではない。本物が支給されるまでの、仮のレシートである(過去記事参照)。
仮のレシートで、その日から運転できるようになった。
これで免許証についての障害はなくなった!
※日本でも同じだが、車両の購入自体は免許証がなくてもできる。色々なリスクというのは購入後しばらくバイクを引き取れないこと、免許区分に思い違いがあり乗れないバイクを買ってしまうことなどで、路上での運転資格がはっきりしていればよかった。
ただ一つ思い違いが発覚し、125ccまでしか乗れない免許であることに気づいたので、Cabarello Scramber の125ccモデルを買うことにして翌日店に向かった。
え?バイク免許A1なんだけど
— がりゅう10月 (@shingaryu) October 12, 2020
購入 (10月中旬)
購入しますと言う。ちなみに支払い方法は
・一括払い
・分割支払い
のどちらも選べたが一括にした。
数十万円を一気に相手先口座に振り込んだわけですが、取引手数料0円だったのでビビりました。26歳以下だか27歳以下だか若者向けの、全手数料無料の口座を使っています。
「ありがとう!Carta di Circolazioneが発行されて、車両が店に届いたら連絡するね!」と言われて納車を待つことになりました。店員さんからすると"1ヶ月間ずっと質問し続けてる得体の知れない異人"に対してやっと利益を確定できて、ホっとしたことでしょう。
在庫ある車両とのことで、10日ほどで納車されるのはラッキー。
バイク用品の購入 (10月中旬)
ただのサイドクエストですが、たぶんヨーロッパはヨーロッパなりのヘルメット安全規格があるので余裕があれば現地でヘルメットを買い直したほうがいいです。
その他グローブとかも持ってなければ納車前に用意しておきましょう。
ワイの答えはコレや!
イタリア(本国)のGiviはヘルメットも作ってるらしい。日本では箱しか売ってないけど
駐車場契約 (10月下旬)
駐車場付きのアパートにしておけばよかった。
といっても仕方がないので他の選択肢をネットで探した。
(1)料金制の路上駐車
(2)公共駐車場
(1)は新車のバイクを路上に置きっぱなしにするのが危険すぎるのでやめた。
でここにした。1ヶ月70ユーロ。
Parking Torino Centro – The site of Parking Torino Centro
振り返ると、ネットに頼らず街中で探してその場で受付に聞いたりした方が早かったと思います。
※日本語のブログを読んでイタリアまでバイクを盗みに来る人はそんなにいないと思うので、気にせず写真上げてます
(納車後)
バイク保険契約 (10月下旬)
ラスボスと見せかけて見掛け倒し。
ネット申し込みができる時代に生まれてよかった。
今回は簡単に申し込める通販型(ネット申し込み)のバイク保険にした。簡単と言ってもさすがにイタリア語が難しすぎたのでこれだけは職場のイタリア人に手伝ってもらった。
これに限らず最近返信が遅いなと思っていたら知らないうちに彼女ができていたらしい。
--イタリアでバイクを買おうと思ってる人向け情報 ここから--
-----------------------------------------------------------
保険料は年間710EUR、保険会社はGenialloydです。
Assicurazioni on line per auto, moto e casa Genialloyd
オンライン上でだいたい以下の流れで完了できます。Carta di Circolazione (下記)が必要です。
・個人情報入力
・車両情報入力
・見積内容表示
・保障プランを取捨選択
・アカウント登録
・オンライン支払い
・証券がメールで送られてくる
-----------------------------------------------------------
--イタリアでバイクを買おうと思ってる人向け情報 ここまで-
納車 (10月29日)
納車したぜ!
あれ、エンジンがかからないよ?
コックが閉まってるのに気づくまで20分かかって、その後一回エンジンかかったけどアイドリングが保てなくて電話口でメカニックにブチギレてるのを聞いてたら20分経ってて、俺が軽くアクセル煽りながら始動したら2秒でかかった
— がりゅう10月 (@shingaryu) October 29, 2020
店員さん「すげぇ!ぼくバイク乗ったことないんだよ」 https://t.co/3xoz6Tsa9o
エンジンはかかりました。多少のトラブルはご愛嬌です。
無事納車しました。良い店員さんに恵まれて、イタリアでイタリアのバイクを買うという夢を叶えられました。免許の都合で選んだScramber 125ですが、250ccと車格も変わらないので不自由は感じていません。ここトリノの都市部は凸凹の石畳と路面電車のレールがあちこちにあるので、オフロードタイヤを履いたスクランブラータイプでよかったと思っています。一応日本でも代理店販売しているようなので、興味があったらどうぞ。
近隣にちょっとした山道があるので、休日はしばらくそこで走り回っていたいと思います。
改めて
— キャバレロがりゅう (@shingaryu) October 29, 2020
イタリア移住から1年半、ついにイタリアのバイク納車しました🏍
Fanfic Motor Caballero 125
石畳でこけないようにしたいですね pic.twitter.com/WlHx0ZhDNN
あとがき
ロックダウンされる前に新車何km乗れるかチャレンジやってます。