機械学習周りのプログラミング中心。 イベント情報
ポケモンバトルAI本電子書籍通販中

3体構成での山登り法試行【PokéAI】

パーティに3体のポケモンを入れるという条件でのパーティ構成の最適化に着手しました。 手法は1体のみの時と同様の山登り法で、3体対応のための最低限のコードを足して動かしてみました。

山登り法は、パーティXがあるときに、その近傍となる少し変更されたパーティX1, X2, ...を生成し、それらの中で最良のものを採用するという処理を繰り返します。 1体のみの場合は、ポケモンの技をランダムに変更することをもって近傍を生成しました。ポケモン自体を変更すると、覚えられる技が違う都合で近傍ではない全く別物になってしまうため変更は行いませんでした。 3体への拡張では、3体のうち1体のポケモンの種類を変更しても近傍と呼べるため、ポケモンの変更も含めた近傍を生成するようにしました。 これにより、1体の時に問題となった、「初期値としてトランセルを選択したため最適化できない」という状況を脱することができます。

近傍パーティの生成部分をparty_generator.pyから抜粋します。

    def generate_neighbor_party(self, party: Party) -> Party:
        """
        近傍の(1匹の技を1個だけ変更するか、別のポケモンにする)パーティを生成する。
        :param party:
        :return:
        """
        pokests = [copy.deepcopy(poke.poke_static) for poke in party.pokes]
        target_idx = np.random.randint(len(pokests))
        if np.random.random() < self.neighbor_poke_change_rate:
            # ポケモンを新しいものに変更
            exclude_dexnos = {pokest.dexno for pokest in pokests}
            new_pokest = self._generate_poke(pokests[target_idx].lv, exclude_dexnos)
            pokests[target_idx] = new_pokest
        else:
            # 技を変更
            pokest = pokests[target_idx]
            moves = pokest.moves
            learnable_moves = self.db.get_leanable_moves(pokest.dexno, pokest.lv)
            for m in moves:
                learnable_moves.remove(m)
            if len(learnable_moves) == 0 and len(moves) == 1:
                # 技を1つしか覚えないポケモン(LV15未満のコイキング等)
                # どうしようもない
                pass
            elif len(learnable_moves) == 0 or (np.random.random() < self.neighbor_move_remove_rate and len(moves) > 1):
                # 技を消す
                moves.pop(randint_len(moves))
            elif np.random.random() < self.neighbor_move_add_rate and len(moves) < 4:
                # 技を足す
                moves.append(learnable_moves[randint_len(learnable_moves)])
            else:
                # 技を変更する
                new_move = learnable_moves[randint_len(learnable_moves)]
                moves[randint_len(moves)] = new_move
        return Party(pokests)

変更対象のポケモンをランダムに選択したのち、ある確率でポケモンの種類を変更、それ以外の場合は技を変更という仕組みです。 なお、ポケモンのレベルは55,50,50の3体としていますが、ポケモンの種類を変更する場合でもレベルは変えないようにしています。 パーティの初期値生成の際に、レベル55が先頭になるか否かはランダムに決定し、そこだけは最適化を経ても変更できない箇所ということになります。

コードを走らせて動作確認しました。 ランダムなパーティ1000個から上位20個を選択、山登り法で生成したパーティ1000個から上位20個を選択し、合計40パーティでレーティング戦を行いました。 バトル中の行動はすべてランダム(選択肢から等確率に選択)です。

ランダム生成のうち最大レート5パーティ

mean rate=1342.691788688345
1494.527433165117
* カビゴン (LV 50 HP 267/267)
  とっしん いわなだれ サイコキネシス じごくぐるま 
  モルフォン (LV 55 HP 193/193)
  しびれごな サイケこうせん はかいこうせん ソーラービーム 
  ゲンガー (LV 50 HP 167/167)
  すてみタックル メガドレイン 10まんボルト ナイトヘッド 

1461.9702040920563
* ドガース (LV 50 HP 147/147)
  だいばくはつ かみなり ねむる だいもんじ 
  フリーザー (LV 55 HP 215/215)
  ふぶき ゴッドバード れいとうビーム みずでっぽう 
  ガラガラ (LV 50 HP 167/167)
  れいとうビーム ちきゅうなげ しっぽをふる じごくぐるま 

1393.4091097140881
* ベトベトン (LV 50 HP 212/212)
  10まんボルト だいもんじ かみなり どくどく 
  フリーザー (LV 55 HP 215/215)
  リフレクター はかいこうせん ねむる れいとうビーム 
  オムスター (LV 50 HP 177/177)
  ふぶき ロケットずつき れいとうビーム とげキャノン 

1390.3376731078024
* ベロリンガ (LV 50 HP 197/197)
  ロケットずつき どくどく ちきゅうなげ ちょうおんぱ 
  ギャラドス (LV 55 HP 221/221)
  だいもんじ とっしん ふぶき れいとうビーム 
  ピクシー (LV 50 HP 202/202)
  フラッシュ 10まんボルト れいとうビーム ロケットずつき 

1383.3421505910833
* スターミー (LV 50 HP 167/167)
  でんじは すてみタックル バブルこうせん ひかりのかべ 
  フシギソウ (LV 50 HP 167/167)
  どくのこな ねむりごな すてみタックル メガドレイン 
  カビゴン (LV 55 HP 292/292)
  サイコキネシス どくどく いわなだれ ふぶき 

山登り法で生成のうち最大レート5パーティ

mean rate=1657.308211311655
1809.781500071635
* フーディン (LV 55 HP 177/177)
  サイケこうせん サイコキネシス 
  サイドン (LV 50 HP 212/212)
  じごくぐるま いわなだれ かみなり すてみタックル 
  サンダー (LV 50 HP 197/197)
  でんきショック ドリルくちばし どくどく かみなり 

1770.5616980873972
* ラプラス (LV 55 HP 259/259)
  ふぶき なみのり ねむる 10まんボルト 
  サンダース (LV 50 HP 172/172)
  10まんボルト すなかけ かみなり すてみタックル 
  ニドキング (LV 50 HP 188/188)
  ふぶき あばれる 

1743.8845340129867
* サンダー (LV 50 HP 197/197)
  10まんボルト かみなり ドリルくちばし 
  フーディン (LV 55 HP 177/177)
  とっしん サイケこうせん サイコキネシス はかいこうせん 
  ゴースト (LV 50 HP 152/152)
  10まんボルト サイコキネシス メガドレイン 

1742.8628701909133
* サンダー (LV 55 HP 215/215)
  ドリルくちばし 10まんボルト 
  ニョロボン (LV 50 HP 197/197)
  かげぶんしん れいとうビーム さいみんじゅつ 
  ガルーラ (LV 50 HP 212/212)
  のしかかり かいりき とっしん ちきゅうなげ 

1710.7054974571538
* サンダー (LV 55 HP 215/215)
  10まんボルト 
  ジュゴン (LV 50 HP 197/197)
  みずでっぽう どくどく ネコにこばん れいとうビーム 
  ピジョット (LV 50 HP 190/190)
  はかいこうせん そらをとぶ とっしん 

従来のふぶき無双とは異なり、サンダーが一番人気のようです。 最適化の際に技だけでなくポケモンの入れ替えもあるので、覚える技の選択肢が多いポケモンは技が十分最適化されずにほかのポケモンに入れ替えられてしまうのかもしれません。 1体のみの実験で最強クラスだったルージュラよりもサンダーのほうが覚える技が若干少ないので、もしかしたら影響があるのかもしれませんが何とも言えないところです。

また、上位20パーティでもヒトカゲナゾノクサなどの明らかに最適化不足のポケモンが入っている場合がありました。 1体のみの実験と同じように、山登り法で1回あたり10個の近傍パーティ生成、30回反復するようにしたのですが不十分かもしれません。 探索空間が広くなったので、パラメータを見直す必要がありそうです。

コマンド (git 0349f6a)

# グループA(ランダム)の生成
python -m pokeai.agent.make_random_pool group_a.bin 1000 --rule LVSUM155_3
python -m pokeai.agent.rate_random_policy group_a_rate.bin group_a.bin
# グループXの生成
python -m pokeai.agent.make_random_pool group_x.bin 1000 --rule LVSUM155_3
python -m pokeai.agent.rate_random_policy group_x_rate.bin group_x.bin
# グループBシードの生成
python -m pokeai.agent.make_random_pool group_b_seed.bin 1000 --rule LVSUM155_3
# グループBの最適化
python -m pokeai.agent.hill_climbing group_b.bin group_b_seed.bin group_x.bin group_x_rate.bin  --rule_params rule_params.yaml --neighbor 10 --iter  30 --history -j 3
python -m pokeai.agent.rate_random_policy group_b_rate.bin group_b.bin
# 各パーティの上位を選択
python -m pokeai.agent.filter_party group_a_top group_a --count 20
python -m pokeai.agent.filter_party group_b_top group_b --count 20
# レーティング
python -m pokeai.agent.rate_random_policy group_ab_top_rate.bin group_a_top.bin group_b_top.bin