强化学习,策略迭代算法中的策略评估详解

1. 动态规划算法

       算法背景:策略迭代算法是一种典型的动态规划。

  • 动态规划应用于马尔可夫决策过程的规划问题而不是学习问题,我们必须对环境是完全已知的,才能做动态规划,也就是要知道状态转移概率和对应的奖励
  • 在白盒环境中,不需要通过智能体和环境的大量交互来学习,可以直接用动态规划求解状态价值函数。但是,现实中的白盒环境很少,这也是动态规划算法的局限之处,我们无法将其运用到很多实际场景中。另外,策略迭代和价值迭代通常只适用于有限马尔可夫决策过程,即状态空间和动作空间是离散且有限的。
  • 使用动态规划完成预测问题和控制问题的求解,是解决马尔可夫决策过程预测问题和控制问题的非常有效的方式。

        策略迭代算法有两个步骤:即策略评估和策略迭代。本文主要结合代码,详细讲解第一个步骤:策略评估。

2 策略评估公式

        当前我们在优化策略 π,在优化过程中得到一个最新的策略。我们先保证这个策略不变,然后估计它的价值,即给定当前的策略函数来估计状态价值函数V。策略评估通过对价值函数的不断迭代,从而估计出每个状态的价值函数,如下式所示。把贝尔曼期望备份转换成动态规划的迭代。

V^{t+1}(s)=\sum_{a \in A} \pi(a \mid s)\left(R(s, a)+\gamma \sum_{s^{\prime} \in S} p\left(s^{\prime} \mid s, a\right) V^{t}\left(s^{\prime}\right)\right)-(0)

       看到这个公式可能会有点懵,其实它的推导也非常简单。首先在马尔克夫决策过程中,给出了Q函数的贝尔曼方程:

Q_{\pi}(s, a)=R(s,a)+\gamma \sum_{s^{\prime} \in S} p\left(s^{\prime} \mid s, a\right) V_{\pi}\left(s^{\prime}\right)-(1)

       然后,我们知道,对策略函数\pi进行求和后,我们就能将Q函数转化为V函数。

V_{\pi}(s)=\sum_{a \in A} \pi(a \mid s) Q_{\pi}(s, a)-(2)

       将(1)式代入(2)式中便可得:

V_{\pi}(s)=\sum_{a \in A} \pi(a \mid s)\left(R(s, a)+\gamma \sum_{s^{\prime} \in S} p\left(s^{\prime} \mid s, a\right) V_{\pi}\left(s^{\prime}\right)\right)-(4)

        将(4)式写成迭代形式,便是式(0)啦!

3 策略评估代码

3.1. 背景介绍

       网格世界(Grid World) 规则:网格中的每一个小格都对应于环境中的状态. 在一个小格上, 有 4 种可能的动作: 北移, 南移,东移, 西移, 其中各个动作都确定性地使智能体在网格上沿对应的方向移动一格. 如果所采取的动作将令智能体脱离网格, 那么该动作的结果为智能体的位置保持不变, 且造成 −1 的奖赏. 除了上述动作与将智能体移出特殊状态 A 与 B 的动作外, 其他的动作只会造成 0 的奖赏.在状态 A 上, 所有的 4 个动作会产生 +10 的奖赏, 并将智能体带至 A’. 在状态 B 上, 所有的 4个动作会产生 +5 的奖赏, 并将智能体带至 B’.

3.2 代码

import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.table import Table

matplotlib.use('Agg')

WORLD_SIZE = 5
A_POS = [0, 1]
A_PRIME_POS = [4, 1]
B_POS = [0, 3]
B_PRIME_POS = [2, 3]
DISCOUNT = 0.9

# left, up, right, down
ACTIONS = [np.array([0, -1]),
           np.array([-1, 0]),
           np.array([0, 1]),
           np.array([1, 0])]
ACTIONS_FIGS = ['←', '↑', '→', '↓']


ACTION_PROB = 0.25



def step(state, action):
    if state == A_POS:
        return A_PRIME_POS, 10
    if state == B_POS:
        return B_PRIME_POS, 5

    next_state = (np.array(state) + action).tolist()
    x, y = next_state
    if x < 0 or x >= WORLD_SIZE or y < 0 or y >= WORLD_SIZE:
        reward = -1.0
        next_state = state  # 如果碰壁,则位置不变
    else:
        reward = 0
    return next_state, reward


def draw_image(image):
    fig, ax = plt.subplots()
    ax.set_axis_off()
    tb = Table(ax, bbox=[0, 0, 1, 1])

    nrows, ncols = image.shape
    width, height = 1.0 / ncols, 1.0 / nrows

    # Add cells
    for (i, j), val in np.ndenumerate(image):

        # add state labels
        if [i, j] == A_POS:
            val = str(val) + " (A)"
        if [i, j] == A_PRIME_POS:
            val = str(val) + " (A')"
        if [i, j] == B_POS:
            val = str(val) + " (B)"
        if [i, j] == B_PRIME_POS:
            val = str(val) + " (B')"
        
        tb.add_cell(i, j, width, height, text=val,
                    loc='center', facecolor='white')
        

    # Row and column labels...
    for i in range(len(image)):
        tb.add_cell(i, -1, width, height, text=i+1, loc='right',
                    edgecolor='none', facecolor='none')
        tb.add_cell(-1, i, width, height/2, text=i+1, loc='center',
                    edgecolor='none', facecolor='none')

    ax.add_table(tb)



def draw_policy(optimal_values):
    fig, ax = plt.subplots()
    ax.set_axis_off()
    tb = Table(ax, bbox=[0, 0, 1, 1])

    nrows, ncols = optimal_values.shape
    width, height = 1.0 / ncols, 1.0 / nrows

    # Add cells
    for (i, j), val in np.ndenumerate(optimal_values):
        next_vals=[]
        for action in ACTIONS:
            next_state, _ = step([i, j], action)
            next_vals.append(optimal_values[next_state[0],next_state[1]])

        best_actions=np.where(next_vals == np.max(next_vals))[0]
        val=''
        for ba in best_actions:
            val+=ACTIONS_FIGS[ba]
        
        # add state labels
        if [i, j] == A_POS:
            val = str(val) + " (A)"
        if [i, j] == A_PRIME_POS:
            val = str(val) + " (A')"
        if [i, j] == B_POS:
            val = str(val) + " (B)"
        if [i, j] == B_PRIME_POS:
            val = str(val) + " (B')"
        
        tb.add_cell(i, j, width, height, text=val,
                loc='center', facecolor='white')

    # Row and column labels...
    for i in range(len(optimal_values)):
        tb.add_cell(i, -1, width, height, text=i+1, loc='right',
                    edgecolor='none', facecolor='none')
        tb.add_cell(-1, i, width, height/2, text=i+1, loc='center',
                   edgecolor='none', facecolor='none')

    ax.add_table(tb)


def figure_3_2():
    value = np.zeros((WORLD_SIZE, WORLD_SIZE))
    while True:
        # keep iteration until convergence
        new_value = np.zeros_like(value)
        for i in range(WORLD_SIZE):
            for j in range(WORLD_SIZE):
                for action in ACTIONS:
                    (next_i, next_j), reward = step([i, j], action)
                    # bellman equation
                    new_value[i, j] += ACTION_PROB * (reward + DISCOUNT * value[next_i, next_j])
        if np.sum(np.abs(value - new_value)) < 1e-4:
            draw_image(np.round(new_value, decimals=2))
            plt.savefig('../images/figure_3_2.png')
            plt.close()
            break
        value = new_value


if __name__ == '__main__':
    figure_3_2()

3.3 代码解释

       代码比较长,但是大部分都是可视化的代码,核心部分其实只有一行。

new_value[i, j] += ACTION_PROB * (reward + DISCOUNT * value[next_i, next_j])

       再对比一下式(0),是不是一下子就豁然开朗了呢!注意这是确定性的环境,所以状态概率p恒等于1。

V^{t+1}(s)=\sum_{a \in A} \pi(a \mid s)\left(R(s, a)+\gamma \sum_{s^{\prime} \in S} p\left(s^{\prime} \mid s, a\right) V^{t}\left(s^{\prime}\right)\right)-(0)

       如果还不清楚,那么接着看下去。为了更清楚的看出每个格子中的价值是如何更新的,笔者结合公式,取其中的几个格子进行分析。

3.4 第一次更新

      我们对着公式,一步步看价值是怎么更新的。

      首先,给定动作以后,状态间的转移是确定的,即p\left(s^{\prime} \mid s, a\right)=1。比如如果智能体从1号状态出发,向右走,那么它会到达横向的2号格子。很多时候有些环境是概率性的,比如智能体在第 1 号状态,它选择往右走的时候,地板可能是滑的,可能会滑到纵向的2号格子。

V^{t+1}(s)=\sum_{a \in A} \pi(a \mid s)\left(R(s, a)+\gamma \sum_{s^{\prime} \in S} p\left(s^{\prime} \mid s, a\right) V^{t}\left(s^{\prime}\right)\right)

对(1,1)格子。

  • 向左 = 0.25 * (-1 + 0.9 * 1 * 0) = -0.25。向左走会碰壁,所以reward为-1,且智能体继续待在原地。最后一项V^{t}\left(s^{\prime}\right)为0
  • 向上 = 0.25 * (-1 + 0.9 * 1 * 0)  = -0.25。向上走也会碰壁,所以reward为-1,且智能体继续待在原地。最后一项V^{t}\left(s^{\prime}\right)为0
  • 向右 = 0.25 * (0 + 0.9 * 1 * 0) = 0
  • 向下 = 0.25 * (0 + 0.9 * 1 * 0) = 0
  • 将这四项相加,便得到了(1,1)格子的价值0.5。

       计算(2,2)格子。这时智能体无论做任何动作都会转移到第五行第二列的格子R(s, a)=1,V^{t}\left(s^{\prime}\right)= V(5,2)=0

  • 向左 = 0.25 * (10 + 0.9 * 1 * 0) = 2.5
  • 向上 = 0.25 * (10 + 0.9 * 1 * 0)  = 2.5
  • 向右 = 0.25 * (10 + 0.9 * 1 * 0) = 2.5
  • 向下 = 0.25 * (10 + 0.9 * 1 * 0) = 2.5
  • 将这四项相加,便得到了(2,2)格子的价值10。

       读者可以试着自己计算一下其它格子的价值,然后和图上的结果,进行校对。

3.5 第二次更新

       接下来是第二轮的更新。其实初学的时候是很容易算错的,大家可以一起来试试哦!

       对(1,1)格子。

  • 向左 = 0.25 * (-1 + 0.9 * 1 * -0.5) = -0.3625,V^{t}\left(s^{\prime}\right)=V(1,1)=-0.5
  • 向上 = 0.25 * (-1 + 0.9 * 1 * -0.5)  = -0.3625,V^{t}\left(s^{\prime}\right)=V(1,1)=-0.5
  • 向右 = 0.25 * (0 + 0.9 * 10) = 2.25,V^{t}\left(s^{\prime}\right)=V(1,2)=10
  • 向下 = 0.25 * (0 + 0.9 * 1 * -0.25) = -0.05625,V^{t}\left(s^{\prime}\right)=V(2,1)=-0.25
  • 相加得1.46875。

      计算(2,2)格子。

  • 向左 = 0.25 * (10+ 0.9 * 1 * -0.25),V^{t}\left(s^{\prime}\right)=V(5,2)=-0.25
  • 向上 = 0.25 * (10 + 0.9 * 1 * -0.25) ,V^{t}\left(s^{\prime}\right)=V(5,2)=-0.25
  • 向右 = 0.25 * (10 + 0.9 * 1 * -0.25) ,V^{t}\left(s^{\prime}\right)=V(5,2)=-0.25
  • 向下 = 0.25 * (10 + 0.9 * 1 * -0.25) ,V^{t}\left(s^{\prime}\right)=V(5,2)=-0.25
  • 相加得9.775。

猜你喜欢

转载自blog.csdn.net/tortorish/article/details/132774667