RL メモ: CliffWaking-v0 の最適なソリューションを見つけるためのポリシーの反復に基づく (python 実装)

目次

1. 概要

2.実現する

3. 走行結果


1. 概要

        CliffWalking-v0 は、ジム ライブラリ [1] の例であり、Sutton-RLbook-2020 の Example6.6 から改作されています。ただし、この記事は CliffWalking-v0 をジムでプレイする方法についてではなく、戦略の反復に基づいてこの問題の最適解を見つける実装例についてです。

        CliffWalking-v0 のゲーム環境は 4*12 グリッドです (上記の [1] を参照)。ゲームのルールは次のとおりです。

        エージェントは左下隅から開始し、各グリッドで {UP、DOWN、RIGHT、LEFT} のいずれかのアクションを実行できます。ただし、アクションが境界を越える場合は、後退してください。右下隅のグリッドに到達すると、ゲームオーバーです。

        グリッドの一番下の列には、左下隅(出発グリッド)と右下隅(ゴール グリッド)を除いて、いわゆる崖グリッドがあり、アクションを実行して崖グリッドに落ちると、 -100 ポイントの報酬 (または罰) を取得し、開始点に直接スローされます。それ以外の場合は、アクションごとに -1 のボーナス (またはペナルティ) があります。エージェントは、ゴール グリッドに到達するためのコストを最小化する必要があります (報酬を最大化するか、ペナルティを最小化します)。

        このゲームは非常に単純で、計算する必要がなく、直感的に知ることができます。最適な戦略は、開始点で 1 つのグリッドを移動し、3 番目の行を右に移動し、右端に到達した後、 1 グリッド下に移動して、ターゲット グリッドに到達します。合計報酬は-13ポイントです。

        以下は、この問題を解決するための戦略反復アルゴリズムに基づく最適戦略であり、上記の直感的な最適戦略が得られるかどうかを確認します。

2.実現する

        CliffWalking-v0 ゲームの環境設定は GridWorld と似ているため、ここでは GridWorld と同様の状態表現方法を採用しています。環境クラス オブジェクトが作成されると、グリッド環境の各セルのタイプを示すために 2 次元配列が使用されます。「1」は終了セルを示し、「-1」は Cliff セルを示し、「0」はその他のセルを示します。次のように:

    grid = [
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    ]
    grid[3][11] = 1 # Terminate cell
    for k in range(1,11):
        grid[3][k] = -1  # Cliff cells

        環境の遷移状態関数 P(s',r|s,a) は、次のように Environment::transit_func() で実装されます。

    def transit_func(self, state, action):
        """
        Prob(s',r|s,a) stored in one dict[(s',reward)].
        """
        transition_probs = {}
        if not self.can_action_at(state):
            # Already on the terminal cell.
            return transition_probs

        opposite_direction = Action(action.value * -1)

        for a in self.actions:
            prob = 0
            if a == action:
                prob = self.move_prob
            elif a != opposite_direction:
                prob = (1 - self.move_prob) / 2

            next_state = self._move(state, a)
            if next_state.row == (self.row_length - 1) and 0 < next_state.column < (self.column_length - 1):
                reward = -100
                next_state = State(self.row_length - 1, 0) # Return to start grid when falls into cliff grid.
            else:
                reward = -1
            
            if (next_state,reward) not in transition_probs:
                transition_probs[(next_state,reward)] = prob
            else:
                transition_probs[(next_state,reward)] += prob

        return transition_probs

    def can_action_at(self, state):
        '''
        Assuming:
            grid[i][j] = 1: Terminate grid
            grid[i][j] =-1: Cliff grids
            grid[i][j] = 0: Other grids
        '''
        if self.grid[state.row][state.column] == 0:
            return True
        else:
            return False

    def _move(self, state, action):
        """
        Predict the next state upon the combination of {state, action}
        {state, action} --> next_state
        Called in transit_func()
        """
        if not self.can_action_at(state):
            raise Exception("Can't move from here!")

        next_state = state.clone()

        # Execute an action (move).
        if action == Action.UP:
            next_state.row -= 1
        elif action == Action.DOWN:
            next_state.row += 1
        elif action == Action.LEFT:
            next_state.column -= 1
        elif action == Action.RIGHT:
            next_state.column += 1

        # Check whether a state is out of the grid.
        if not (0 <= next_state.row < self.row_length):
            next_state = state
        if not (0 <= next_state.column < self.column_length):
            next_state = state

        # Entering into cliff grids is related to the correspong penalty and 
        # reset to start grid, hence will be handled upper layer.

        return next_state

        Planner クラスは計画の基本クラスを実装し、PolicyIterationPlanner クラスはさらに Planner のサブクラスとしてポリシー反復に基づくプランナーを実装します。そのコアは PolicyIterationPlanner::policy_evaluation() と PolicyIterationPlanner::plan() です。戦略反復アルゴリズムは、以前の記事 ( RL Notes: Dynamic Programming (2): Strategy Iteration )で紹介されているので、ここでは繰り返しません。

        PolicyIterationPlanner:: policy_evaluation() は、次のようにポリシー評価を実装します。

    def policy_evaluation(self, gamma, threshold):
        V = {}
        for s in self.env.states:
            # Initialize each state's expected reward.
            V[s] = 0

        while True:
            delta = 0
            for s in V:
                expected_rewards = []
                for a in self.policy[s]:
                    action_prob = self.policy[s][a]
                    r = 0
                    for prob, next_state, reward in self.transitions_at(s, a):
                        r += action_prob * prob * \
                             (reward + gamma * V[next_state])
                    expected_rewards.append(r)
                value = sum(expected_rewards)
                delta = max(delta, abs(value - V[s]))
                V[s] = value
            if delta < threshold:
                break

        return V

        PolicyIterationPlanner::plan() は、完全なポリシー反復アルゴリズムを実装します (ポリシー評価部分は policy_evaluation() を呼び出します)。コードは次のとおりです。

   def plan(self, gamma=0.9, threshold=0.0001):
        """
        Implement the policy iteration algorithm
        gamma    : discount factor
        threshold: delta for policy evaluation convergency judge.
        """
        self.initialize()
        states  = self.env.states
        actions = self.env.actions

        def take_max_action(action_value_dict):
            return max(action_value_dict, key=action_value_dict.get)

        while True:
            update_stable = True
            # Estimate expected rewards under current policy.
            V = self.policy_evaluation(gamma, threshold)
            self.log.append(self.dict_to_grid(V))

            for s in states:
                # Get an action following to the current policy.
                policy_action = take_max_action(self.policy[s])

                # Compare with other actions.
                action_rewards = {}
                for a in actions:
                    r = 0
                    for prob, next_state, reward in self.transitions_at(s, a):
                        r += prob * (reward + gamma * V[next_state])
                    action_rewards[a] = r
                best_action = take_max_action(action_rewards)
                if policy_action != best_action:
                    update_stable = False

                # Update policy (set best_action prob=1, otherwise=0 (greedy))
                for a in self.policy[s]:
                    prob = 1 if a == best_action else 0
                    self.policy[s][a] = prob

            # Turn dictionary to grid
            self.V_grid = self.dict_to_grid(V)
            self.iters = self.iters + 1
            print('PolicyIteration: iters = {0}'.format(self.iters))
            self.print_value_grid()
            print('******************************')

            if update_stable:
                # If policy isn't updated, stop iteration
                break

3. 走行結果

        実行結果は次のとおりです (右下隅に到達するとゲームが終了し、それ以上のアクションがないため、右下隅は無視できます)。

        上記の実装は、直感と同じ最適な戦略を考え出すことがわかります。

完全なコードを参照してください:reinforcement-learning/CliffWalking-v0.py

集中学習用の一連の学習ノートの総合カタログは、集中学習ノートの総合カタログにあります。

[1] クリフ ウォーキング - ジムのドキュメント (gymlibrary.dev)

おすすめ

転載: blog.csdn.net/chenxy_bwave/article/details/129349086