ローカルシミュレーターでAIに対戦させると、すぐに勝率が出ます。
最初は、この勝率を見て、強いデッキや方針を残していけばよいと思っていました。ところが、ローカルで勝つものが実際のラダーで勝つとは限りません。むしろ、手元の評価が良いものほど実戦結果が悪い、というケースもありました。
まず提出までの道を作る
最初にやったのは、Kaggle上で動かすためのランタイムと提出スクリプトを揃えることでした。
ローカルでエージェントを動かす、対戦ログを保存する、提出する、提出結果を記録する、という流れを先に通しました。環境更新後は Docker に寄せるより、ネイティブ実行に切り替えた方が扱いやすかったです。
この段階では、まだ強い戦略を作ったというより、測定できる場所まで持っていっただけでした。
ローカル評価と実戦評価が逆になる
ローカルの総当たりや重み付きの対戦表を作り、実際のリプレイ結果とも比較しました。
すると、ローカル勝率をそのまま実戦の強さとして扱うのは危ないことが分かりました。ローカルシミュレーターでは、合法手を選べるか、事故らずに動けるか、決めた展開を再現できるかは見えます。
一方で、実際のラダーには相手の分布、プレイのテンポ、未知のデッキ、ログには残りにくい選択の偏りがあります。ここをローカルだけで再現しようとすると、シミュレーター向けの最適化になってしまいます。
なので、ローカル評価の役割を「強さの順位付け」から「明らかなバグや合法性の確認」に下げました。強さの最終判定は、提出してリプレイを見るしかなさそうです。
複雑なエージェントを小さくする
次に、対戦中の判断をたくさん詰め込んだエージェントを見直しました。
最初は、相手の状況に応じて細かく分岐する、1000行近いパイロットを書いていました。ローカルでは賢そうに見えます。でも実戦に出すと、判断が増えた分だけ変な分岐も増えます。
そこで、セットアップを優先して、攻撃は最後にする単純な方針へ置き換えました。コードは170行程度まで小さくなりました。
これを実ラダーに出したところ、複雑な方より良いスコアになりました。さらにリプレイ上の12,204個の判断点で、デッキを変えても同じテンプレートの挙動になることを確認しました。
もちろん、単純な方針がいつでも勝つわけではありません。ただ、細かい判断を増やすことと、実戦で強くなることは別だと分かりました。ああ、判断を詰め込む方向は違ったんだ、という感じです。
エラーとタイムアウトを数える
勝率だけを見ると、たまたま勝ったのか、実装が安定しているのか分かりません。
そのため、ローカルのラダー形式ベンチマークでは、勝敗とは別にエラーとタイムアウトを記録しました。880ゲームを回した時点では、エラーもタイムアウトも0件でした。
勝てるかどうか以前に、止まらず最後まで動くかは重要なので、これは別の指標として残しておくことにしました。
7月に入ってからは、実際の上位帯の分布も調べ、上位アーキタイプを追加したローカル総当たりへ広げました。ここでも、ローカルの順位をそのまま信じるのではなく、実戦へ出す候補を選ぶための材料として使っています。
おわり
AI対戦では、シミュレーターを良くすると、シミュレーターに対して良いものが作れてしまいます。
ローカル評価は必要だけど、役割を限定した方が良かったです。合法性、再現性、エラーの確認には向いています。実際の強さは、実戦に出して結果とリプレイを見ます。
いったん複雑にしたエージェントを、単純なテンプレートに戻したのも面白かったです。たくさん考えるより、測れるところまで出して、実際の失敗を見た方が次の手がかりになるみたいです。今は、この単純テンプレートをベースに、どこから判断を足すと実戦で効くのかを探しています。