😉

強化学習で作る最強のCCレモンAI~修行編~

2024/12/19に公開

本記事は、ルール編強化学習基礎編の続きです。ぜひ前回までの記事もご覧ください。

前回までのあらすじ

ランダムにコマンドを実行するエージェントに微妙に勝ち越しという結果でした。
今回ではこのAIを強くします。

勝率
1回目 51.5%
2回目 50.2%
3回目 51.0%

コマンドの変化

episodeを経るにつれてどのようなコマンドを打つのか観察してみました。
縦が自分のチャージ数で行が相手のチャージ数です。

episode = 100

0 1 2 3 4 5
0 チャージ チャージ チャージ チャージ チャージ チャージ
1 チャージ チャージ ガード ガード ガード チャージ
2 ビーム ビーム ビーム ビーム ビーム ビーム
3 ビーム ビーム ビーム ビーム ビーム ビーム
4 ビーム ビーム ビーム ビーム ビーム ビーム
5 ビーム ビーム ビーム ビーム ビーム ビーム

episode = 10000

0 1 2 3 4 5
0 チャージ ガード ガード ガード ガード ガード
1 チャージ チャージ ガード ガード ガード チャージ
2 ビーム ビーム ビーム ビーム ビーム ビーム
3 ビーム ビーム ビーム ビーム ビーム ビーム
4 ビーム ビーム ビーム ビーム ビーム ビーム
5 ビーム ビーム ビーム ビーム ビーム ビーム

上記から「ビームがかなり優先的に選択される」「チャージが選択されにくい」ことがわかります。
またチャージはepisodeを経ることに減少しています。

現状の問題点の考察

報酬関数

今の報酬関数は勝利もしくは敗北したときのみに報酬が発生します。
したがって「ビームして勝利した」or「チャージして敗北した」ときのみ報酬が発生します。
そのためビームが選択されやすく、チャージが選択されにくいことが考えられます。

探索率

また、探索率が固定で10%とかなり低く設定されています。
探索率とはAIがQ Tableに従わずにランダムにコマンドを選択しより良い選択肢を探す確率です。
探索率が低いと一度報酬を得た行動から抜け出すことが難しくなってしまいます。
おそらく「ビーム」を発射して勝利してしまったことを忘れられずに同じ状態では高い確率でビームを打ってしまうことから抜け出せなくなっていると思われます。

改善方法

イプシロンの減衰

イプシロンの減衰とは探索率をepisode全体の最初の方は高く最後の方は低く設定する方法です。
ゲームを学びたてのころはいろいろ挑戦して勉強してもらって最後の方は自分が学んだ方法を試してもらおうということですね。
今回は残っているepisodeの割合だけ探索してもらおうとおもいます。
100試合やるとして、10試合完了し90試合残っているのであれば90%探索しようということですね。
εを探索率、完了済のepisode数をx、全体のepisode数をaとすると

ε = 1 - x/a

と表せられます。

    def select_action(self, state: Tuple[int, int],episode : int, all_episode_num : int, show_flag = False) -> int:
        """ε-グリーディ方策による行動選択"""
        """ε-グリーディ方策とは一定確率でランダムなアクションを選択し、現在のQテーブルよりより良い行動を探索する手法"""
       
        charge = state[0]  # プレイヤーのチャージ数
+       epsilon = 1 - episode / all_episode_num
        # 有効なアクションのリストをチャージ数に基づいて絞り込む
        available_actions = []
        # チャージ数に基づくアクション制限

        if charge >= 1:
            available_actions.append(1)  # ビーム
        if charge >= specium_cost:
            available_actions.append(2)  # スペシウム光線
        available_actions.append(0)  # チャージ(常に選択可能)
        available_actions.append(3)  # ガード(常に選択可能)
        # ε-グリーディ方策で行動選択
        if show_flag:
            valid_q_values = [self.get_q_value(state, action) for action in available_actions]
            return available_actions[np.argmax(valid_q_values)]  # 最大Q値のアクションを選択
+        elif random.uniform(0, 1) < epsilon :
-        elif random.uniform(0, 1) < self.epsilon :
            # 探索: 有効なアクションからランダムに選択
            return random.choice(available_actions)
        else:
            # スペシウム光線を打てるなら必ず打つ
            if charge == specium_cost:
                return 2
            # 利用: Q値に基づいて有効なアクションの中から選択
            # 有効なアクションのQ値を取得
            valid_q_values = [self.get_q_value(state, action) for action in available_actions]
            return available_actions[np.argmax(valid_q_values)]  # 最大Q値のアクションを選択

報酬関数

報酬関数の問題点は勝敗が決したタイミングでのみしか報酬が発生せず、チャージが過小評価されてしまうことでした。
CCレモンというゲームにおいてチャージは重要な点で相手よりチャージが多い状況は有利と言えます。
そこで相手とのチャージ数の差を報酬とすることにしました。

chargereward = a * ( mycharge - yourcharge )
def cal_reward(state : Tuple[int,int], win : int) -> float:
    # 勝利報酬
    win_reward = 0
    if win == 1:
        win_reward = 100000
    elif win == -1:
        win_reward = -100000

+    # チャージ報酬
+    charge_reward = 1000 * ( state[0] - state[1] )
    
+    return win_reward + charge_reward 
-    return win_reward

精度評価

学習とランダムエージェントとの対戦を3回繰り返した結果は以下のようでした。

勝率
1回目 58.5%
2回目 55.8%
3回目 58.6%

前回が50% ~ 51%の勝率ということを考えると間違いなく強くなっています。
が、60%は超えたかった...
次回に続く...!!(かも)

株式会社ZOZO

Discussion