【ポケモン AI】超簡易ポケモンバトルをゼロから学習させる【強化学習】

追記 10/07～ 見やすいように全体的に修正中

追記 2020/04/18 この記事の都築に当たる動画をアップしました。→https://www.youtube.com/watch?v=jlmJcXUKzIY

この記事の修正はまだです＾＾；

##今回のポケモンバトルのルール/環境
|使用できるポケモン名|使用できる技1|使用できる技2|
|---|---|---|
|フシギダネ|たいあたり|つるのムチ|
|ゼニガメ|みずでっぽう|なし|
|ヒトカゲ|ひのこ|なし|
|ピカチュウ|でんきショック|なし|

- シングルバトル
- アイテムの効果はなし
- 技を使用してもPPは減らない
- ダメージ乱数は常に100％
- 急所無し
- 特性は発動しない
- 技の追加効果は発動しない
- その他は現行のシングルバトルと同じルール

##学習する内容
・パーティ作成時の期待勝率
・ポケモン選出時期待勝率
・ゲーム終了までの期待勝率
・バトル時の技選択/交代などのコマンド推移確率

##ネットワーク構造
![network_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240088/fdd13e0d-b55a-f905-3b6a-4c72cc36f5a8.png)

・AlphaGoZeroでも使われたデュアルネットワーク

<b>※出力層の前に活性化関数がないのはミスである(画像のミスという意味ではない)</b>
<b>※評価値を出力するネットワークは以下ValueNetworkとする</b>
<b>※コマンド推移確率を出力するネットワークは以下PolicyNetworkとする</b>
<b>※画像が雑なのは気にしたら負け</b>

##出力層の詳細
|ネットワーク名|ValueNetwork|
|---|---|
|出力関数|Softmax|
|出力サイズ|2|
|0番目の出力|自分の期待勝率 (0～1で表現)|
|1番目の出力|相手の期待勝率 (0～1で表現)|

|ネットワーク名|PolicyNetwork|
|---|---|
|出力関数|Softmax|
|出力サイズ|6|
|0番目の出力|技1を使用する確率|
|1番目の出力|技2を使用する確率|
|2番目の出力|技3を使用する確率(※今回のルールでは意味なし)|
|3番目の出力|技4を使用する確率(※今回のルールでは意味なし)|
|4番目の出力|2番目のポケモンに交代する確率|
|5番目の出力|3番目のポケモンに交代する確率|

##学習時に与える入力データ
|ネットワーク名|ValueNetwork / PolicyNetwork 共通|
|---|---|
|使用する入力データ|局面をバイナリで表現した入力値<br>(自己対局中に現れた全ての局面)<br>(プレイヤーが行動する直前の局面のみ)|

##学習時に与える正解ラベル
|ネットワーク名|ValueNetwork / PolicyNetwork 共通|
|---|---|
|ラベル付けする入力値|上記を参照|
|ラベルサイズ|出力サイズと同じ|

|ネットワーク名|ValueNetwork|
|---|---|
|勝敗|勝利した時|
|0番目の値|1  (自分の期待勝率を1に近づける)|
|1番目の値|0  (相手の期待勝率を0に近づける)|

|ネットワーク|ValueNetwork|
|---|---|
|勝敗|敗北した時|
|0番目の値|0 (自分の期待勝率を0に近づける)|
|1番目の値|1 (相手の期待勝率を1に近づける)|

|ネットワーク|PolicyNetwork|
|---|---|
|勝敗|勝利 / 敗北 共通|
|0～5番目の値|モンテカルロUCTの訪問回数に応じた値<br><b>※後に詳細を記載する</b>|


##最適化手法
|名前|AdaBound|
|---|---|
|特徴|Adamのように初期学習が早い / SGDやMomentumのように汎化性能も高い|
|使わせて頂いたコード|[リンク(github)](https://github.com/taki0112/AdaBound-Tensorflow)|

##入力値の詳細
|入力サイズ|12537|
|---|---|
|表現方法|バイナリ表現(0, 1のみ)|

1が入力されている場所を見れば現在の状態がわかるようになっている

##パーティの特徴入力の詳細
<b>※入力番号・入力範囲という項目は、入力値に1を入力した時に対応する特徴量の場所である。<br>(プログラム的に言うとリストにアクセスする時の番号)</b>

<b>※1～3匹目までを「なし」にするという事は不可能なので、1が入力される事がない入力番号である</b>

|入力番号|ポケモン名|パーティの枠|
|---|---|---|
|0|なし|1|
|1|フシギダネ|1|
|2|ヒトカゲ|1|
|3|ゼニガメ|1|
|4|ピカチュウ|1|

|入力番号|ポケモン名|パーティの枠|
|---|---|---|
|5|なし|2|
|6|フシギダネ|2|
|7|ヒトカゲ|2|
|8|ゼニガメ|2|
|9|ピカチュウ|2|

|入力番号|ポケモン名|パーティの枠|
|---|---|---|
|10|なし|3|
|11|フシギダネ|3|
|12|ヒトカゲ|3|
|13|ゼニガメ|3|
|14|ピカチュウ|3|

|入力番号|ポケモン名|パーティの枠|
|---|---|---|
|15|なし|4|
|16|フシギダネ|4|
|17|ヒトカゲ|4|
|18|ゼニガメ|4|
|19|ピカチュウ|4|

|入力番号|ポケモン名|パーティの枠|
|---|---|---|
|20|なし|5|
|21|フシギダネ|5|
|22|ヒトカゲ|5|
|23|ゼニガメ|5|
|24|ピカチュウ|5|

|入力番号|ポケモン名|パーティの枠|
|---|---|---|
|25|なし|6|
|26|フシギダネ|6|
|27|ヒトカゲ|6|
|28|ゼニガメ|6|
|29|ピカチュウ|6|

|入力範囲|レベル|パーティの枠|
|---|---|---|
|30～79|1～50|1|
|80～129|1～50|2|
|130～179|1～50|3|
|180～229|1～50|4|
|230～279|1～50|5|
|280～329|1～50|6|

<b>※特性リスト</b>
- しんりょく
- ようりょくそ
- もうか
- サンパワー
- げきりゅう
- あめうけざら

<b>※今回のルールでは意味のない特徴量である</b>

|入力範囲|特性|パーティの枠|
|---|---|---|
|330～335|しんりょく～あめうけざら|1|
|336～341|しんりょく～あめうけざら|2|
|342～347|しんりょく～あめうけざら|3|
|348～353|しんりょく～あめうけざら|4|
|354～359|しんりょく～あめうけざら|5|
|360～365|しんりょく～あめうけざら|6|

<b>※性格リスト</b>
- さみしがり
- いじっぱり
- やんちゃ
- ゆうかん
- ずぶとい
- わんぱく
- のうてんき
- のんき
- ひかえめ
- おっとり
- うっかりや
- れいせい
- おだやか
- おとなしい
- しんちょう
- なまいき
- おくびょう
- せっかち
- ようき
- むじゃき
- てれや
- がんばりや
- すなお
- きまぐれ
- まじめ

|入力範囲|性格|パーティの枠|
|---|---|---|
|366～390|さみしがり～まじめ|1|
|391～415|さみしがり～まじめ|2|
|416～440|さみしがり～まじめ|3|
|441～465|さみしがり～まじめ|4|
|466～490|さみしがり～まじめ|5|
|491～515|さみしがり～まじめ|6|

<b>※性別リスト</b>
- ♂
- ♀
- 不明

<b>※今回のルールでは意味のない特徴量である</b>

|入力範囲|性別|パーティの枠|
|---|---|---|
|516～518|♂～不明|1|
|519～521|♂～不明|2|
|522～524|♂～不明|3|
|525～527|♂～不明|4|
|528～530|♂～不明|5|
|531～533|♂～不明|6|

<b>※持ち物リスト</b>
- なし
- くろいヘドロ
- でんきだま

<b>※今回のルールでは意味のない特徴量である。</b>

|入力範囲|特徴|パーティの枠|
|---|---|---|
|534～536|なし～でんきだま|1|
|537～539|なし～でんきだま|2|
|540～542|なし～でんきだま|3|
|543～545|なし～でんきだま|4|
|546～548|なし～でんきだま|5|
|549～551|なし～でんきだま|6|

<b>※1枠目の技を「なし」にする事は出来ないので、1が入力される事がない入力番号である</b>

|入力範囲|技|パーティの枠|技の枠|
|---|---|---|---|
|549～554|なし|1～6|1|
|555～560|たいあたり|1～6|1|
|561～566|つるのムチ|1～6|1|
|567～572|ひのこ|1～6|1|
|573～578|みずでっぽう|1～6|1|
|579～584|でんきショック|1～6|1|
|585～590|なし|1～6|2|
|591～596|たいあたり|1～6|2|
|597～602|つるのムチ|1～6|2|
|603～608|ひのこ|1～6|2|
|609～614|みずでっぽう|1～6|2|
|615～620|でんきショック|1～6|2|
|621～626|なし|1～6|3|
|627～632|たいあたり|1～6|3|
|633～638|つるのムチ|1～6|3|
|639～644|ひのこ|1～6|3|
|645～650|みずでっぽう|1～6|3|
|651～656|でんきショック|1～6|3|
|657～662|なし|1～6|4|
|663～668|たいあたり|1～6|4|
|669～674|つるのムチ|1～6|4|
|675～680|ひのこ|1～6|4|
|681～686|みずでっぽう|1～6|4|
|687～692|でんきショック|1～6|4|

|入力範囲|ポイントアップ使用回数|パーティの枠|技の枠|
|---|---|---|---|
|693～698|0|1～6|1|
|699～704|1|1～6|1|
|705～710|2|1～6|1|
|711～716|3|1～6|1|
|717～722|4|1～6|1|
|723～728|0|1～6|2|
|729～734|1|1～6|2|
|735～740|2|1～6|2|
|741～746|3|1～6|2|
|747～752|4|1～6|2|
|753～758|0|1～6|3|
|759～764|1|1～6|3|
|765～770|2|1～6|3|
|771～776|3|1～6|3|
|777～782|4|1～6|3|
|783～788|0|1～6|4|
|789～794|1|1～6|4|
|795～800|2|1～6|4|
|801～806|3|1～6|4|
|807～812|4|1～6|4|

|入力範囲|HP個体値|パーティの枠|
|---|---|---|
|813～844|0～31|1|
|845～876|0～31|2|
|877～908|0～31|3|
|909～940|0～31|4|
|941～972|0～31|5|
|973～1004|0～31|6|

|入力範囲|攻撃個体値|パーティの枠|
|---|---|---|
|1005～1036|0～31|1|
|1037～1068|0～31|2|
|1069～1100|0～31|3|
|1101～1132|0～31|4|
|1133～1164|0～31|4|
|1165～1196|0～31|6|

|入力範囲|防御個体値|パーティの枠|
|---|---|---|
|1197～1228|0～31|1|
|1229～1260|0～31|2|
|1261～1292|0～31|3|
|1293～1324|0～31|4|
|1325～1356|0～31|5|
|1357～1388|0～31|6|

|入力範囲|特攻個体値|パーティの枠|
|---|---|---|
|1389～1420|0～31|1|
|1421～1452|0～31|2|
|1453～1484|0～31|3|
|1485～1516|0～31|4|
|1517～1548|0～31|5|
|1549～1580|0～31|6|

|入力範囲|特防個体値|パーティの枠|
|---|---|---|
|1581～1612|0～31|1|
|1613～1644|0～31|2|
|1645～1676|0～31|3|
|1677～1708|0～31|4|
|1709～1740|0～31|5|
|1741～1772|0～31|6|

|入力範囲|素早さ個体値|パーティの枠|
|---|---|---|
|1773～1804|0～31|1|
|1805～1836|0～31|2|
|1837～1868|0～31|3|
|1869～1900|0～31|4|
|1901～1932|0～31|5|
|1933～1964|0～31|6|

|入力範囲|HP努力値|パーティの枠|
|---|---|---|
|1965～2217|0～252|1|
|2218～2470|0～252|2|
|2471～2723|0～252|3|
|2724～2976|0～252|4|
|2977～3229|0～252|5|
|3230～3482|0～252|6|

|入力範囲|攻撃努力値|パーティの枠|
|---|---|---|
|3483～3735|0～252|1|
|3736～3988|0～252|2|
|3989～4241|0～252|3|
|4242～4494|0～252|4|
|4495～4747|0～252|5|
|4748～5000|0～252|6|

|入力範囲|防御努力値|パーティの枠|
|---|---|---|
|5001～5253|0～252|1|
|5254～5506|0～252|2|
|5507～5759|0～252|3|
|5760～6012|0～252|4|
|6013～6265|0～252|5|
|6266～6518|0～252|6|

|入力範囲|特攻努力値|パーティの枠|
|---|---|---|
|6519～6771|0～252|1|
|6772～7024|0～252|2|
|7025～7277|0～252|3|
|7278～7530|0～252|4|
|7531～7783|0～252|5|
|7784～8036|0～252|6|

|入力範囲|特防努力値|パーティの枠|
|---|---|---|
|8037～8289|0～252|1|
|8290～8542|0～252|2|
|8543～8795|0～252|3|
|8796～9048|0～252|4|
|9049～9301|0～252|5|
|9302～9554|0～252|6|

|入力範囲|素早さ努力値|パーティの枠|
|---|---|---|
|9555～9807|0～252|1|
|9808～10060|0～252|2|
|10061～10313|0～252|3|
|10314～10566|0～252|4|
|10567～10819|0～252|5|
|10820～11072|0～252|6|

## 見せあい～選出までに関係する特徴入力の詳細
ここから先は主に、互いにパーティを見せあいして、ポケモンを選出するまでに使用する特徴量になります。

- 相手のポケモンの名前
- 相手のポケモンのレベル
- 相手のポケモンの性別
- 相手のアイテム所持の有無(なし/あり)
- 選出する先頭のポケモン(長さ6)
- 選出する控え1のポケモン
- 選出する控え2のポケモン

名前・レベル・性別に関してはパーティ構築と同じ種類数です。アイテム所持に関しては、見せあい段階では、持っているか持っていないかしかわからないので、2種類になります。

選出するポケモンは、自分のパーティの位置を元に特量入力します。
例えば、パーティのポケモン名の特徴量の並びが以下の通りとします。

- ゼニガメ
- ピカチュウ
- フシギダネ
- ヒトカゲ

そして以下の並びで選出した場合の評価値が知りたいとします。

- ピカチュウ
- フシギダネ
- ゼニガメ

この場合、選出する先頭のポケモンの1番目・選出する控え1のポケモンの2番目・選出する控え2のポケモンの0番目に1を入力します。
その為、入力の長さは 6種類 * 3 の計18になります。

ここまでが見せあい～選出に関する特徴入力になります。

最後は、バトル開始～バトル終了までの特徴入力です。

- 自分の現在の先頭のポケモン(パーティの位置を元にする)
- 控え1のポケモン
- 控え2のポケモン
- 自分の先頭のポケモンの現在のHP(長さ152)
- 控え1のポケモンの現在のHP
- 控え2のポケモンの現在のHP
- 相手の先頭のポケモン(相手のパーティの位置を元にする(長さ6))
- 相手の控えのポケモン(一度でも場に出た場合のみ入力)(相手のパーティの位置を元にする)
- 相手の現在のHP%(一度でも場に出たポケモンのみ入力)(相手のパーティの位置を元にする)(長さ6 * 100)

自分の先頭のポケモン・控えのポケモンに関しては選出の特徴入力と同じです。
今回のルールではあり得る最大HPが152なので、味方の現HPを表す特徴入力の長さは、152 * 3 の計456になります。

相手の控えのポケモンの情報は一度でも場に出た場合のみ入力し、相手のポケモン控え1・相手のポケモン控え2と入力範囲分けずに、同じ範囲に入力します。

何故かという理由は以下の通りです。

- 自分視点から見て、相手の控え1・控え2はどちらであるかがわからない
- わかったとしても、入力をわける意味は薄い(どの順番で控えていようが同じである為)
- 同じポケモンが使えないルールであれば、同じ場所に入力してしまう事がないので、一つの入力範囲にまとめれば、計算量を削れる。

などが挙げられます。
その為、相手の先頭のポケモン + 控えのポケモンの特徴入力の長さは、6 + 6 の計12になります。

ちなみに、自分の控えのポケモンの位置はpolicyで行動価値を示す場合に必要となってきます。

## 自己対局の流れ
※<b>valueにはパーティ構築～バトル終了までの評価を学習させていますが、今回の場合は、バトル時の評価値はただ学習させるだけであってないようなものです。同様にpolicyも学習させるだけで、対局時に使用する事はありません。</b>

初めに、1つのネットワークで2つのパーティを作成します。
その際、1手先の評価値を全て探索し、パーティ構築を進めていきます。

例えば、パーティの最初のポケモンを選択する場合は

- 0番目にのみ1を入力した入力値
- 1番目にのみ1を入力した入力値
- 2番目にのみ1を入力した入力値
- 3番目にのみ1を入力した入力値
- 4番目にのみ1を入力した入力値

の計5つの入力値をバッチにまとめてネットワークに流し込み、評価が一番良かった(実際には乱数が含まれている)入力値を採用します。
ここでは1番目に1を入力した入力値(フシギダネ)が最大値だったとします。

次に、2番目のポケモンを選択する場合は(同じポケモンは採用出来ない事に注意)

- 1番目と5番目に1を入力した入力値
- 1番目と7番目に1を入力した入力値
- 1番目と8番目に1を入力した入力値
- 1番目と9番目に1を入力した入力値

の計4つ入力値を用意し、同じように、バッチにまとめてネットワークに流し込み評価を見て、また採用するポケモンを決めます。

ポケモン6匹を採用したら、今度はレベルの評価値を見て、レベルを決める...という事を繰り返していきます。
ポケモンのパラメーターを決めていく順番は

- レベル
- 特性
- 性格
- 性別
- 持ち物
- 技1
- 技2
- 技3
- 技4
- 技1のポイントアップ
- 技2のポイントアップ
- 技3のポイントアップ
- 技4のポイントアップ
- HP個体値
- 攻撃個体値
- 防御個体値
- 特攻個体値
- 特防個体値
- 素早さ個体値
- HP努力値
- 攻撃努力値
- 防御努力値
- 特攻努力値
- 特防努力値
- 素早さ努力値

になります。

但し、努力値だけは、HP～素早さをランダムにシャッフルして順番を決めます。何故なら努力値は合計510までと決められているので、上から順番に決めていくと、偏りが出てしまう恐れがあるからです。

また、同じ技を覚えさせてはいけないなどのポケモンのルールに従って行動します。

もしポケモンを採用しない場合(なしを選択した場合)は、その枠のポケモンのパラメーターには何も入力しません。

特徴入力の詳細に記述した、なしを含むかどうかについて意味は、なしが含むものは、選択しないという選択を自らの意志で選んだものであり、何も入力されていない部分に関しては、ポケモンがそもそも選択されなかったから何も選べない、技を1つしか選んでないからポイントアップも1つしか選択出来ないといった具合に、それが自分の意志で選択しなかったのか、ルール的に選択不能だから選択しなかったのかを明確に区別する為のものです。

パーティ構築が完了したら、今度は互いの入力値に互いのパーティの情報を入力します。
次に、選出する先頭のポケモン・選出する控え1のポケモン・選出する控え2のポケモンの入力範囲に全て同時かつ総当たりで入力します。
そしてバッチにまとめて評価値を出力し、選出するポケモンを決めます。

バトルが開始したらモンテカルロシュミレーション(uctアルゴリズム)を行い最大訪問回数を選択しながら対戦を進めていきます。
ポケモンは不完全情報公開ゲームである為、実際の対局では、モンテカルロをそのまま使う事は難しいですが、policyに学習させる分には、問題ないだろうという事で、モンテカルロを使用しました。

要は、学習時はカンニングした状態の最善手を教えてもらい、実際の対局で、その最善手を予測するのが狙いです。

※モンテカルロuctについては各自で調べていただくか[僕の記事(笑)](https://qiita.com/nanakrk965/items/6477911a13972c437ec5)を参照。

モンテカルロの設定は以下の通りです。

|試行回数|定数X|ノード展開の有無|
|:-----------|------------:|--:|
|その局面の合法手 * 128|1|なし|

モンテカルロ探索は、ノードを展開しながらシュミレーションを進めるのが普通ですが、ポケモンでは互いに同じ手を選んでも別の局面に辿りつく場合があり、実装するのがかなり面倒...という理由で今回はノード展開はなしにしました。

一応、展開無しでも原始モンテカルロよりかは強い事は確認出来ました。

対戦が終了したら、パーティ構築～バトル終局までに現れた局面(入力値)全てを教師データとして学習させます。

valueには、勝った場合は[1, 0] 負けた場合は[0, 1]のone hot labelを与えます。

policyには、勝ち負け関係なく、モンテカルロで訪問した回数に応じた値(訪問回数を温度パラメーター1のボルツマン分布に変換した値)をそのまま正解ラベルとして与えます。

```python
"""とある方からツイッターで頂いたコード"""
def boltzmann_distribution(array, temperature_parameter):
    return array ** (1 / temperature_parameter) / np.sum(array ** (1 / temperature_parameter))
```

また直近64試合分のデータを記録しておき、64試合毎に
バッチサイズ16、64試合分のデータを1.5回見る割合でミニバッチ学習をさせます。

おおまかな流れはこんな感じですが、これに加えて以下のような事も行っています。

その1. パーティ構築は、一定の確率で温度パラメータ0.075のボルツマン分布に従って行動する。

パーティ構築のランダム行動は、ランダムで行動する/しないを一定確率(初期は50%)でランダム選択し、ランダム行動すると選択した場合は、パーティ構築の全ての行動をボルツマン分布でランダム行動します。

その2. 勝利した側は同じパーティは続けて使用する。

勝利した側のパーティを続けて使用する事で、そのパーティが本当に強いパーティであれば、強いパーティであると素早く学習する事が可能です。
もしそのパーティが弱い場合は、すぐに負けるはずなので、弱いパーティを強いパーティとして使い続ける事はありません。また、私の環境下では、パーティを構築させるのに3秒から3.5秒かかってしまうのですが、この時間を省く事が出来ます。

## 学習の結果(2)
うまく学習出来ているかどうかを確認する指標はいくつかあると思いますが、今回は、勝敗の結果に最も大きく関わるであろう選出したポケモンの平均レベルと、policyがモンテカルロの最大訪問数をどの程度予知出来ているのかをグラフで見ます。

特にレベルは、ゼロから学習させる上では、最初の壁であるように思います。
何故ならいくらバトルコマンドが正確であったとしてもレベル50 * 3匹 vs レベル1 * 3匹 のように極端にレベル差開いてしまうと勝つ事はほぼ不可能であり、逆にレベル差が開いていれば多少雑なプレイをしても勝ててしまいます。
その為、とりあえず高レベルなポケモンを使用するという事を学習出来ないと始まらないように思えます。

![0～325.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240088/e3083c80-e8e8-5ffe-ddca-f397eb99132d.png)

![321～641.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240088/65fc5cc3-466c-7663-9d51-56e2bfe6d2c2.png)

![642～1282.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240088/733483f6-783b-7b49-a63d-e843c273e828.png)

![1283～2563.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240088/e8d0986a-7a0e-d5ec-d417-f646adba4b7b.png)

![2564～2884.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240088/4abb37a0-a84a-6bc4-0264-e2cfbb4de509.png)

グラフを分けている意味は特にありませんが、パーティ構築時のランダム選択は上から順に、50% 50% 20% 20% 5%になっています。
平均レベルは最大50から最大1.0に変換しています。

最終モデル+ランダム行動なしのパーティ構築
※<b>今回のルールで意味があるパラメーターのみ記載

|ポケモン名|レベル|性格|個体値|努力値|技1|技2|
|--:|--:|--:|--:|--:|--:|--:|
|フシギダネ|48|すなお|17, 30, 30, 20, 30, 31|195, 9, 51, 209, 23, 12|たいあたり|なし|
|ヒトカゲ|49|きまぐれ|1, 7, 18, 10, 28, 3|182, 12, 60, 64, 127, 9|ひのこ|
|ピカチュウ|50|おくびょう|0, 27, 5, 25, 17, 23|13, 2, 174, 40, 205, 0|でんきショック|
|ゼニガメ|49|おっとり|30, 23, 8, 10, 18, 25|0, 186, 142, 118, 36, 4|みずでっぽう|

レベルに関してはそこそこ、他はまだまだと言った所ですね...

## 改善点/将来に向けて
考えられる改善点はいくつかあります。

まずは、モンテカルロを展開ありにする事ですね。
その方が明らかに強くなるので。

次にチーム構築時のランダム行動です。
強化学習のランダム行動は、greedyもよく使われますが、学習が進むにつれてランダム行動を減らしていく、というチューニングが面倒であった為、ボルツマン選択であれば、勝手に行動価値に応じてランダム行動してくれる→チューニング不要!やったぜ!とか思っていたんですが、どうやら甘かったようです。

今回のグラフがガタガタなのはランダム行動のチューニングがうまくいっていないのが原因だと思います。
パーティ構築時にランダム行動をすると決定した場合は、パーティ構築の全ての行動にランダム要素を入れるという事をしていましたが、これもよろしくなく、N回ランダム要素を取り入れるとかにした方が、細かく調整出来ますし、より最適化しようと思ったらその微調節のようなランダムが必要になってくると思います。

次に考えられる点は、入力層の次元削減です。
例えば、今回はポケモン1～6を別々の入力範囲としてましたが、そもそも同じポケモンを選択する事は出来ない為、入力範囲は同じでも被る事はありません。なしを選択する場合は、同じ場所になってしまう為、なしの数を入力する場所は必要になってきますが。

同様に同じ技も選択する事が出来ないので、入力範囲を一つにまとめる事が出来そうです。順番は固定になってしまいますが、パーティ構築の際のポケモン・技の並びはどの順番であっても出来る事は変わりません。

パーティ構築のおけるポケモン1～3や技の1はルール上なしを選択する事が出来ないので、この部分も削れそうです。
このあたりは実装のしやすさやわかりやすさを優先しました。

バトル時の特徴量にも抜け目があります。
今回は、味方の並び、味方のHP、相手の並び、相手のHPを特徴量としていましたが
、他にも、味方の先頭のポケモンの技1が相手の先頭のポケモンに今までどのくらいダメージを与えたのかとか、逆に相手からどのくらいダメージを受けたのとかの情報も本来であれば必要ですし、もっと言うのであれば、先頭同士の情報だけではなく、過去の局面から洗い出せる情報は全て特徴量にすべきです。(先手後手の結果や相手がどの技を所持していたのかとか、逆に自分は相手に対してどの情報を公開しているのかなどなど)
結局この辺りも実装が面倒でしませんでしたが、本格的に学習させたいのであれば、やはり必要です。

今回は、モンテカルロの結果を学習させましたが、元々はポリシー同士の対局で勝ったらの勝ちの報酬を与えて、負けたら負けの報酬を与えるという方法で学習させて強くしていくつもりでした。その方が0からの学習感がしていいので。
しかしうまく学習出来ず、色々考えてもよさげな方法が見つからなかったので、諦めていたんですが、いい感じの方法を思いついたのでそれなりにうまくいったらその事についても書こうと思います。

最後にバトル時の探索についてです。
しばらくの間はpolicyの強化でバトルコマンドの精度を上げる事を考えてるのですが、最終的にはバトル時の探索も必要になってくるかもしれません。
前にも書いたように、ポケモンは不完全情報ゲームである為、通常の探索手法は使えません。そこで、私が考えているのは、現局面でわかっている相手の情報を元に、ネットワークに何種類かの有望なパーティを構築させて、その作成したパーティをランダムでサンプリングし、モンテカルロなどのシュミレーションを行うという手法です。この方法であれば、パーティの評価値の精度さえ上がれば、割とうまくいくんじゃないかなと思っています。ただし、これを本格的に試そうと思ったらそれなりの計算資源が必要になりそうです...手元のPCがせいぜいミドルスペックなので、気軽には試せないというのが現状です。

というわけでずっとしたい思っていたポケモンバトルの強化学習の始めの一歩を何とか踏み出せました。今の人生の目標は、ポケモンソード・シールドが発売するまでに、基盤を作る事です。もし興味がいる人がいれば一緒に作って、コンピューター将棋みたいに盛り上がったら嬉しいなと思っています。