まだまだいろいろと検証しないといけないことが多いのですが、終わった後に記事にしようとしてもおそらく細かいところを覚えていないので、いまわかっている段階でメモ
ゲーム
趣味でゲームを作っています。といってもロジックだけで絵やUIに関してはほとんど手を付けられていませんが
- カードがあって
- それを召喚して
- 同じカードを重ねて進化して
- 相手のライフを削り切ったら勝ち
みたいなゲームです。
相変わらず完成させるモチベがなく、ふと強化学習をやってみたい気分になり、やってみることにしました。
ただ、強いAIを作ればよいのではなく、パラメータ調整に利用したいので以下のようなこだわりがあります
- 人間のプレイデータによる教師あり学習は行わない(自動生成ならセーフ)
- 学習時間はできるだけ短くする
この二つの条件を満たせるように強化学習の仕組みを作ろうとしています
読んだ本
「強化学習」を学びたい人が最初に読む本
「強化学習」を学びたい人が最初に読む本 | 伊藤 真 | 工学 | Kindleストア | Amazon
この本をじっくり読みました。これ以外の本は特に読んでません。ちょっと手詰まり感もなきにしもあらずなので、強化学習系の他の本も読みつつ、より賢くしていこうかなと思います
ゲームにおける強化学習では、実際にゲームが動いている環境と、それを用いて学習するエージェントがあります。
流れとしては
- エージェントは環境に対して、今の環境情報(ターン数、HP、盤面情報)を受け取ります
- そして、その環境情報をもとに、エージェントが行動を選択します
- 行動した選択をもとに、環境は報酬を与えます
- エージェントはもらった報酬をもとに、行動が妥当かどうかを学習します
といった形で行い、報酬ベースでエージェント自ら学習していくという特徴があります。
今回はDQNと呼ばれる手法をメインに採用しました。Q学習のQテーブル、Q値を深層学習に置き換えたものです。
Q値とは何ぞやとなると思うので軽く説明すると、環境が与えられたときに各行動でどれくらい将来的に報酬がもらえるかを返すものです。詳しく説明すると長くなるので書籍を参照してください。
行動の細分化
このDQNを自分が開発しているゲームに適用するにあたって問題になるのは行動の多さです。
例えば、モンスターの攻撃を行うとすると、
どのタイルのモンスターか(タイルの数23通り) * モンスターの行動パターン(攻撃、進化など5通り) * どのタイルの攻撃先モンスター(タイルの数23通り)
23 * 5 * 23 = 2645
で2645 通りになります。これだけでも多いのですが、タイルが複数選択になったりすると、どんどん行動空間が膨れ上がってしまい、学習が現実的ではなくなります。
そこで、モンスターの攻撃のときは
- タイルを選択する
- 攻撃を選択する
- タイルを選択する
という風に格ゲーのコマンドの要領で細分化することで、行動空間を小さくすることにしました。このやり方の場合行動の数は
タイルの数 + 手札の数 + モンスターの行動の数 + ターンエンド(1)
で40通りくらいで済みます
それに合わせて、環境からはタイル選択など直前に行った行動と、「行動可能リスト」というものを与えるようにしました。行動可能リストはその状態で選択できる行動をリスト化したもので、もしモンスターのいるタイルを選択した場合は
- 「攻撃」
- 「移動」
- 「進化」
- 「ターンエンド(これは色々あって常に選択できるようになっている)」
を行動可能リストとしてエージェントに与えるようにしています。こうすることで、エージェントはどの行動を選択できるのか判断できるようになります
不可能な行動にペナルティを与えるのではだめなのか
こういった強化学習を実装するにあたって、一般的なパターンはエージェントが不可能な行動をしたときに敗北処理を行ったりすることだと思いますが、そのやり方は行っていません
- 全行動に対して不可能な行動の比率が高いので、ランダムで行動したとき可能な行動を選択できない
- 魔法カードなどはカード選択後の行動にバリエーション(カードを使うだけ、タイルを選択する等)があり、これを学習させるのは難しい
といった理由からエージェントはランダム行動を行う時も行動可能リストから選んで行うようにしています。
実装
行動の組み合わせを定義する
- 召喚 手札カード選択→召喚選択→タイル選択
- 進化 タイル選択→進化選択→手札カード選択
- 移動 タイル選択→移動選択→タイル選択
- 攻撃 タイル選択→攻撃選択→タイル選択
という風に定義しました。
HTTPサーバーを立てる
エージェントから環境へアクセスするためのサーバーを立てます。私のゲームはロジックがUnity非依存なので、ASP.NET Coreで鯖を立てました。どの道オンラインゲームで提供するときはUnityを使わずにサーバーを立てたいので、こういう作りにしています。
モンキーテストのプレイデータを学習させる
ランダムに行動したプレイデータをC#で作成し、エージェントに学習させます。単にランダムに行動しても戦いが終わらない(というかデッキが切れて負ける)ので、モンスターの移動選択をしたときは前側のタイルを選択する確率を上げたりして対戦に決着がつくようにしました。また、Q値の更新は通常選択した行動に対してのみ行われますが、モンキーテスト時は行動可能リストにない行動に対するQ値は-99999(敗北時の報酬)を入れて学習させています。
モンキーテストが落ちまくったので直すのが結構大変でした。
一定確率でランダム行動するエージェントで、強化学習を行います。モンキーテストのプレイデータを学習させているので、ある程度報酬をもらうように動いてくれます。ターンエンドするだけの相手に対しては安定して勝てるようになりました。現時点で学習にかかっている時間は10分なのでそこそこ上出来かなと思っています。
モンキーテストも同様ですが、タイル選択後の行動数的にターンエンドが選ばれる確率が高いので、ターンエンドが選ばれにくくなるよう実装の工夫が必要です。
今後
魔法カードなどいろんな要素を増やして学習できるか調べようかなと思っています。後、デッキ編成もAIが自分で考えてできるようにしてほしいのですが、それをどのように実装するかは考えているところです。