通过代码学Sutton强化学习第四章动态规划

时间:2022-07-27
本文章向大家介绍通过代码学Sutton强化学习第四章动态规划,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

经典教材Reinforcement Learning: An Introduction 第二版由强化领域权威Richard S. Sutton 和 Andrew G. Barto 完成编写,内容深入浅出,非常适合初学者。本篇详细讲解第四章动态规划算法,我们会通过Grid World示例来结合强化学习核心概念,用python代码实现在OpenAI Gym的模拟环境中第四章基于动态规划的算法:策略评价(Policy Evaluation)、策略提升(Policy Improvment)、策略迭代(Policy Iteration)、值迭代(Value Iteration)和异步迭代方法(Asynchronous DP)。

Grid World 问题

第四章例子4.1提出了一个简单的离散空间状态问题:Grid World,其大致意思是在4x4的网格世界中有14个格子是非终点状态,在这些非终点状态的格子中可以往上下左右四个方向走,直至走到两个终点状态格子,则游戏结束。每走一步,Agent收获reward -1,表示Agent希望在Grid World中尽早出去。另外,Agent在Grid World边缘时,无法继续往外只能呆在原地,reward也是-1。

Finite MDP 模型

先来回顾一下强化学习的建模基础:有限马尔可夫决策过程(Finite Markov Decision Process, Finite MDP)。如下图,强化学习模型将世界抽象成两个实体,强化学习解决目标的主体Agent和其他外部环境。它们之间的交互过程遵从有限马尔可夫决策过程:若Agent在t时间步骤时处于状态

S_t

,采取动作

A_t

,然后环境根据自身机制,产生Reward

R_{t+1}

并将Agent状态变为

S_{t+1}

环境自身机制又称为dynamics,工程上可以看成一个输入(S, A),输出(S, R)的方法。由于MDP包含随机过程,某个输入并不能确定唯一输出,而会根据概率分布输出不同的(S, R)。Finite MDP简化了时间对于模型的影响,因为(S, R)只和(S, A)有关,不和时间t有关。另外,有限指的是S,A,R的状态数量是有限的。

数学上dynamics可以如下表示

pleft(s^{prime}, r mid s, aright) doteq operatorname{Pr}left{S_{t}=s^{prime}, R_{t}=r mid S_{t-1}=s, A_{t-1}=aright}

即是四元组作为输入的概率函数

p: S times R times S times A rightarrow [0, 1]

满足

sum_{s^{prime} in mathcal{S}} sum_{r in mathcal{R}} pleft(s^{prime}, r mid s, aright)=1, text { for all } s in mathcal{S}, a in mathcal{A}(s)

以Grid World为例,当Agent处于编号1的网格时,可以往四个方向走,往任意方向走都只产生一种 S, R,因为这个简单的游戏是确定性的,不存在某一动作导致stochastic状态。例如,在1号网格往左就到了终点网格(编号0),得到Reward -1这个规则可以如下表示

pleft(s^{prime}=0, r=-1 mid s=1, a=text{L}right) = 1

因此,状态s=1的所有dynamics概率映射为

begin{aligned} pleft(s^{prime}=0, r=-1 mid s=1, a=text{L}right) &=& 1 \ pleft(s^{prime}=2, r=-1 mid s=1, a=text{R}right) &=& 1 \ pleft(s^{prime}=1, r=-1 mid s=1, a=text{U}right) &=& 1 \ pleft(s^{prime}=5, r=-1 mid s=1, a=text{D}right) &=& 1 end{aligned}

强化学习的目的

在给定了问题以及定义了强化学习的模型之后,强化学习的目的当然是通过学习让Agent能够学到最佳策略

pi_{*}

,也就是在某个状态下的行动分布,记成

pi(a|s)

。对应在数值上的优化目标是Agent在一系列过程中采取某种策略的reward总和的期望(Expected Return)。下面公式定义了t步往后的reward总和,其中

gamma

为discount factor,用于权衡短期和长期reward对于当前Agent的效用影响。等式最后一步的意义是t步后的reward总和等价于t步所获的立即reward

R_{t+1}

,加上t+1步后的reward总和

gamma G_{t+1}

begin{aligned} G_{t} & doteq R_{t+1}+gamma R_{t+2}+gamma^{2} R_{t+3}+gamma^{3} R_{t+4}+cdots \ &=R_{t+1}+gammaleft(R_{t+2}+gamma R_{t+3}+gamma^{2} R_{t+4}+cdotsright) \ &=R_{t+1}+gamma G_{t+1} end{aligned}

有了reward总和的定义,评价Agent策略

pi

就可以定义成Agent在状态 s 时采用此策略的Expected Return。

v_{pi}(s) doteq mathbb{E}_{pi}left[G_{t} mid S_{t}=sright]

下面公式推导了

v_{pi}(s)

数值上和相关状态

s{prime}

的关系:

begin{aligned} v_{pi}(s) &doteq mathbb{E}_{pi}left[G_{t} mid S_{t}=sright] \ &=mathbb{E}_{pi}left[sum_{k=0}^{infty} gamma^{k} R_{t+k+1} mid S_{t}=sright]\ &=mathbb{E}_{pi}left[R_{t+1}+gamma G_{t+1} mid S_{t}=sright] \ &=sum_{a} pi(a mid s) sum_{s^{prime}} sum_{r} pleft(s^{prime}, r mid s, aright)left[r+gamma mathbb{E}_{pi}left[G_{t+1} mid S_{t+1}=s^{prime}right]right] \ &=sum_{a} pi(a mid s) sum_{s^{prime}, r} pleft(s^{prime}, r mid s, aright)left[r+gamma v_{pi}left(s^{prime}right)right] quad text { for all } s in mathcal{S} end{aligned}

注意到如果将

v_{pi}(s)

看成未知数,上式即形成

mid mathcal{S} mid

个未知变量的方程组,可以在数值上解得各个

v_{pi}(s)

书中用Backup Diagram来表示递推关系,下图是

v_{pi}(s)

的backup diagram。

尽管v值可以来衡量策略,但由于

v_{pi}(s)

是Agent在策略

pi(a|s)

的Expected Return,将不同的action拆出来单独计算Expected Return,这样的做法有时更为直接,这就是著名的Q Learning中的q 值,记成

q_{pi}(s, a)

q_{pi}(s, a) doteq mathbb{E}_{pi}left[G_{t} mid S_{t}=s, A_{t}=aright]

下面是

q_{pi}(s, a)

的递推 backup diagram。

Bellman 最佳原则

对于所有状态集合

mathcal{S}

,策略

{pi}

的评价指标

v_{pi}(s)

是一个向量,本质上是无法相互比较的。但由于存在Bellman 最佳原则(Bellman's principle of optimality):在有限状态情况下,一定存在一个或者多个最好的策略

{pi}_{*}

,它在所有状态下的v值都是最好的,即

v_{pi_{*}}(s) ge v_{pi^{prime}}(s) text { for all } s in mathcal{S}

因此,最佳v值定义为最佳策略

{pi}_{*}

对应的 v 值

v_{*}(s) doteq max_{pi} v_{pi}(s)

同理,也存在最佳q值,记为

begin{aligned} q_{*}(s, a) &doteq max_{pi} q_{pi}(s,a) end{aligned}

v_{*}(s)

改写成递推形式,称为 Bellman Optimality Equation,推导如下

begin{aligned} v_{*}(s) &=max _{a in mathcal{A}(s)} q_{pi_{*}}(s, a) \ &=max _{a} mathbb{E}_{pi_{*}}left[G_{t} mid S_{t}=s, A_{t}=aright] \ &=max _{a} mathbb{E}_{pi_{*}}left[R_{t+1}+gamma G_{t+1} mid S_{t}=s, A_{t}=aright] \ &=max _{a} mathbb{E}left[R_{t+1}+gamma v_{*}left(S_{t+1}right) mid S_{t}=s, A_{t}=aright] \ &=max _{a} sum_{s^{prime}, r} pleft(s^{prime}, r mid s, aright)left[r+gamma v_{*}left(s^{prime}right)right] end{aligned}

直觉上可以理解为状态 s 对应的最佳v值是只采取此状态下的最佳动作后的Expected Return。

最佳q值递归形式的意义为最佳策略下状态s时采取行动 a 的Expected Return,等于所有可能后续状态 s' 下采取最优行动的Expected Return的均值。推导如下:

begin{aligned} q_{*}(s, a) &=mathbb{E}left[R_{t+1}+gamma max _{a^{prime}} q_{*}left(S_{t+1}, a^{prime}right) mid S_{t}=s, A_{t}=aright] \ &=sum_{s^{prime}, r} pleft(s^{prime}, r mid s, aright)left[r+gamma max _{a^{prime}} q_{*}left(s^{prime}, a^{prime}right)right] end{aligned}
v_{*}(s), q_{*}(s, a)

的backup diagram 如下图

Grid World 最佳策略和V值

Grid World 的最佳策略如下:尽可能快的走出去

Grid World最佳策略上面的2D图中不同颜色表示不同V值,终点格子的红色表示0,隔着一步的黄色为-1,隔两步的绿色为-2,最远的紫色为-3。下面是立体图示。

Grid World最佳策略V值

Grid World OpenAI Gym 环境

下面是OpenAI Gym框架下Grid World环境的代码实现。本质是在GridWorldEnv构造函数中构建MDP,类型定义如下

MDP = Dict[State, Dict[Action, List[Tuple[Prob, State, Reward, bool]]]]

# P[state][action] = [
#    (prob1, next_state1, reward1, is_done),
#    (prob2, next_state2, reward2, is_done), ...]

class Action(Enum):
    UP = 0
    DOWN = 1
    LEFT = 2
    RIGHT = 3

State = int
Reward = float
Prob = float
Policy = Dict[State, Dict[Action, Prob]]
Value = List[float]
StateSet = Set[int]
NonTerminalStateSet = Set[int]
MDP = Dict[State, Dict[Action, List[Tuple[Prob, State, Reward, bool]]]]
# P[s][a] = [(prob, next_state, reward, is_done), ...]

class GridWorldEnv(discrete.DiscreteEnv):
    """
    Grid World environment described in Sutton and Barto Reinforcement Learning 2nd, chapter 4.
    """

    def __init__(self, shape=[4,4]):
        self.shape = shape
        nS = np.prod(shape)
        nA = len(list(Action))
        MAX_R = shape[0]
        MAX_C = shape[1]
        self.grid = np.arange(nS).reshape(shape)
        isd = np.ones(nS) / nS

        # P[s][a] = [(prob, next_state, reward, is_done), ...]
        P: MDP = {}
        action_delta = {Action.UP: (-1, 0), Action.DOWN: (1, 0), Action.LEFT: (0, -1), Action.RIGHT: (0, 1)}
        for s in range(0, MAX_R * MAX_C):
            P[s] = {a.value : [] for a in list(Action)}
            is_terminal = self.is_terminal(s)
            if is_terminal:
                for a in list(Action):
                    P[s][a.value] = [(1.0, s, 0, True)]
            else:
                r = s // MAX_R
                c = s % MAX_R
                for a in list(Action):
                    neighbor_r = min(MAX_R-1, max(0, r + action_delta[a][0]))
                    neighbor_c = min(MAX_C-1, max(0, c + action_delta[a][1]))
                    s_ = neighbor_r * MAX_R + neighbor_c
                    P[s][a.value] = [(1.0, s_, -1, False)]

        super(GridWorldEnv, self).__init__(nS, nA, P, isd)

4.1 策略评估(Policy Evaluation)

策略评估需要解决在给定环境dynamics和Agent策略

pi

下,计算策略的v值

v_{pi}

。由于所有数量关系都已知,可以通过解方程组的方式求得,但通常会通过数值迭代的方式来计算,即通过一系列

v_{0}, v_{1}, ..., v_{k}

收敛至

v_{pi}

。如下迭代方式已经得到证明,当

k rightarrow infty

一定收敛至

v_{pi}

begin{aligned} v_{k+1}(s) & doteq mathbb{E}_{pi}left[R_{t+1}+gamma v_{k}left(S_{t+1}right) mid S_{t}=sright] \ &=sum_{a} pi(a mid s) sum_{s^{prime}, r} pleft(s^{prime}, r mid s, aright)left[r+gamma v_{k}left(s^{prime}right)right] end{aligned}

书中具体伪代码如下

begin{align*} &textbf{Iterative Policy Evaluation, for estimating } Vapprox v_{pi} \ & text{Input } {pi}, text{the policy to be evaluated} \ & text{Algorithm parameter: a small threshold } theta >0 text{ determining accuracy of estimation} \ & text{Initialize } V(s), text{for all } s in mathcal{S}^{+} text{, arbitrarily except that } V (terminal) = 0\ & \ &1: text{Loop:}\ &2: quad quad Delta leftarrow 0\ &3: quad quad text{Loop for each } s in mathcal{S}:\ &4: quad quad quad quad v leftarrow V(s) \ &5: quad quad quad quad V(s) leftarrow sum_{a} pi(a mid s) sum_{s^{prime}, r} pleft(s^{prime}, r mid s, aright)left[r+gamma Vleft(s^{prime}right)right] \ &6: quad quad quad quad Delta leftarrow max(Delta, |v-V(s)|) \ &7: text{until } Delta < theta end{align*}

下面是python 代码实现,注意这里单run迭代时,新的v值直接覆盖数组里的旧v值,这种做法在书中被证明不仅有效,甚至更为高效。这种做法称为原地(in place)更新。

def policy_evaluate(policy: Policy, env: GridWorldEnv, gamma=1.0, theta=0.0001):
    V = np.zeros(env.nS)
    while True:
        delta = 0
        for s in range(env.nS):
            v = 0
            for a, action_prob in enumerate(policy[s]):
                for prob, next_state, reward, done in env.P[s][a]:
                    v += action_prob * prob * (reward + gamma * V[next_state])
            delta = max(delta, np.abs(v - V[s]))
            V[s] = v
        if delta < theta:
            break
    return np.array(V)

输入策略为随机选择方向,运行上面的policy_evaluate最终多轮收敛后的V值输出为

[[  0.         -13.99931242 -19.99901152 -21.99891199]
 [-13.99931242 -17.99915625 -19.99908389 -19.99909436]
 [-19.99901152 -19.99908389 -17.99922697 -13.99942284]
 [-21.99891199 -19.99909436 -13.99942284   0.        ]]

在3D V值图中可以发现,由于是随机选择方向的策略, Agent在每个格子的V值绝对数值要比最佳V值大,意味着随机策略下Agent在Grid World会得到更多的负reward。

Grid World随机策略V值

4.2 策略迭代

上一篇中,我们知道如何evaluate 给定policy

pi

v_{pi}

值,那么是否可能在此基础上改进生成更好的策略

pi^{prime}

。如果可以,能否最终找到最佳策略

{pi}_{*}

?答案是肯定的,因为存在策略提升定理(Policy Improvement Theorem)。

策略提升定理

在 4.2 节 Policy Improvement Theorem 可以证明,利用

v_{pi}

信息对于每个状态采取最 greedy 的 action (又称exploitation)能够保证生成的新

{pi}^{prime}

是不差于旧的policy

{pi}

,即

q_{pi}(s, {pi}^{prime}(s)) gt v_{pi}(s)
v_{{pi}^{prime}}(s) gt v_{pi}(s)

因此,可以通过在当前policy求得v值,再选取最greedy action的方式形成如下迭代,就能够不断逼近最佳策略。

pi_{0} stackrel{mathrm{E}}{longrightarrow} v_{pi_{0}} stackrel{mathrm{I}}{longrightarrow} pi_{1} stackrel{mathrm{E}}{longrightarrow} v_{pi_{1}} stackrel{mathrm{I}}{longrightarrow} pi_{2} stackrel{mathrm{E}}{longrightarrow} cdots stackrel{mathrm{I}}{longrightarrow} pi_{*} stackrel{mathrm{E}}{longrightarrow} v_{*}

4.3 策略迭代算法

以下为书中4.3的policy iteration伪代码。其中policy evaluation的算法在上一篇中已经实现。Policy improvement 的精髓在于一次遍历所有状态后,通过policy 的最大Q值找到该状态的最佳action,并更新成最新policy,循环直至没有 action 变更。

begin{align*} &textbf{Policy Iteration (using iterative policy evaluation) for estimating } piapprox {pi}_{*} \ &1. quad text{Initialization} \ & quad quad V(s) in mathbb Rtext{ and } pi(s) in mathcal A(s) text{ arbitrarily for all }s in mathcal{S} \ & \ &2. quad text{Policy Evaluation} \ & quad quad text{Loop:}\ & quad quad Delta leftarrow 0\ & quad quad text{Loop for each } s in mathcal{S}:\ & quad quad quad quad v leftarrow V(s) \ & quad quad quad quad V(s) leftarrow sum_{s^{prime}, r} pleft(s^{prime}, r mid s, aright)left[r+gamma Vleft(s^{prime}right)right] \ & quad quad quad quad Delta leftarrow max(Delta, |v-V(s)|) \ & quad quad text{until } Delta < theta text{ (a small positive number determining the accuracy of estimation)}\ & \ &3. quad text{Policy Improvement} \ & quad quad policytext{-}stableleftarrow true \ & quad quad text{Loop for each } s in mathcal{S}:\ & quad quad quad quad oldtext{-}actionleftarrow pi(s) \ & quad quad quad quad pi(s) leftarrow operatorname{argmax}_{a} sum_{s^{prime}, r} pleft(s^{prime}, r mid s, aright)left[r+gamma Vleft(s^{prime}right)right] \ & quad quad quad quad text{If } oldtext{-}action neq pitext{,then }policytext{-}stableleftarrow false \ & quad quad text{If } policytext{-}stable text{, then stop and return }V approx v_{*} text{ and } piapprox {pi}_{*}text{; else go to 2} end{align*}

注意到状态Q值

q_{pi}(s, a)

会被多处调用,将其封装为单独的函数。

begin{aligned} q_{pi}(s, a) &=sum_{s^{prime}, r} pleft(s^{prime}, r mid s, aright)left[r+gamma v_{pi}left(s^{prime}right)right] end{aligned}

Q值函数实现如下:

def action_value(env: GridWorldEnv, state: State, V: StateValue, gamma=1.0) -> ActionValue:
    q = np.zeros(env.nA)
    for a in range(env.nA):
        for prob, next_state, reward, done in env.P[state][a]:
            q[a] += prob * (reward + gamma * V[next_state])
    return q

有了 action_value 和上期的 policy_evaluate,policy iteration 实现完全对应上面的伪代码。

def policy_improvement(env: GridWorldEnv, policy: Policy, V: StateValue, gamma=1.0) -> bool:
    policy_stable = True

    for s in range(env.nS):
        old_action = np.argmax(policy[s])
        Q_s = action_value(env, s, V)
        best_action = np.argmax(Q_s)
        policy[s] = np.eye(env.nA)[best_action]

        if old_action != best_action:
            policy_stable = False
    return policy_stable


def policy_iteration(env: GridWorldEnv, policy: Policy, gamma=1.0) -> Tuple[Policy, StateValue]:
    iter = 0
    while True:
        V = policy_evaluate(policy, env, gamma)
        policy_stable = policy_improvement(env, policy, V)
        iter += 1

        if policy_stable:
            return policy, V

Grid World 例子通过两轮迭代就可以收敛,以下是初始时随机策略的V值和第一次迭代后的V值。

初始随机策略 V 值

第一次迭代后的 V 值

4.4 值迭代

值迭代( Value Iteration)的本质是,将policy iteration中的policy evaluation过程从不断循环到收敛直至小于theta,改成只执行一遍,并直接用最佳Q值更新到状态V值,如此可以不用显示地算出

{pi}

而直接在V值上迭代。具体迭代公式如下:

begin{aligned} v_{k+1}(s) & doteq max _{a} mathbb{E}left[R_{t+1}+gamma v_{k}left(S_{t+1}right) mid S_{t}=s, A_{t}=aright] \ &=max_{a} q_{{pi}_k}(s, a) \ &=max _{a} sum_{s^{prime}, r} pleft(s^{prime}, r mid s, aright)left[r+gamma v_{k}left(s^{prime}right)right] end{aligned}

完整的伪代码为:

begin{align*} &textbf{Value Iteration, for estimating } piapprox pi_{*} \ & text{Algorithm parameter: a small threshold } theta >0 text{ determining accuracy of estimation} \ & text{Initialize } V(s), text{for all } s in mathcal{S}^{+} text{, arbitrarily except that } V (terminal) = 0\ & \ &1: text{Loop:}\ &2: quad quad Delta leftarrow 0\ &3: quad quad text{Loop for each } s in mathcal{S}:\ &4: quad quad quad quad v leftarrow V(s) \ &5: quad quad quad quad V(s) leftarrow operatorname{max}_{a} sum_{s^{prime}, r} pleft(s^{prime}, r mid s, aright)left[r+gamma Vleft(s^{prime}right)right] \ &6: quad quad quad quad Delta leftarrow max(Delta, |v-V(s)|) \ &7: text{until } Delta < theta \ & \ & text{Output a deterministic policy, }piapprox pi_{*} text{, such that} \ & quad quad pi(s) leftarrow operatorname{argmax}_{a} sum_{s^{prime}, r} pleft(s^{prime}, r mid s, aright)left[r+gamma Vleft(s^{prime}right)right] end{align*}

代码实现也比较直接,可以复用上面已经实现的 action_value 函数。

def value_iteration(env:GridWorldEnv, gamma=1.0, theta=0.0001) -> Tuple[Policy, StateValue]:
    V = np.zeros(env.nS)
    while True:
        delta = 0
        for s in range(env.nS):
            action_values = action_value(env, s, V, gamma=gamma)
            best_action_value = np.max(action_values)
            delta = max(delta, np.abs(best_action_value - V[s]))
            V[s] = best_action_value
        if delta < theta:
            break

    policy = np.zeros([env.nS, env.nA])
    for s in range(env.nS):
        action_values = action_value(env, s, V, gamma=gamma)
        best_action = np.argmax(action_values)
        policy[s, best_action] = 1.0

    return policy, V

4.5 异步迭代

在第4.5节中提到了DP迭代方式的改进版:异步方式迭代(Asychronous Iteration)。这里的异步是指每一轮无需全部扫一遍所有状态,而是根据上一轮变化的状态决定下一轮需要最多计算的状态数,类似于Dijkstra最短路径算法中用 heap 来维护更新节点集合,减少运算量。下面我们通过异步值迭代来演示异步迭代的工作方式。

下图表示状态的变化方向,若上一轮

V(s)

发生更新,那么下一轮就要考虑状态 s 可能会影响到上游状态的集合( p1,p2),避免下一轮必须遍历所有状态的V值计算。

Async 反向传播要做到部分更新就必须知道每个状态可能影响到的上游状态集合,上图对应的映射关系可以表示为

begin{align*} s'_1 &rightarrow {s} \ s'_2 &rightarrow {s} \ s &rightarrow {p_1, p_2} end{align*}

建立映射关系的代码如下,build_reverse_mapping 返回类型为 Dict[State, Set[State]]。

def build_reverse_mapping(env:GridWorldEnv) -> Dict[State, Set[State]]:
    MAX_R, MAX_C = env.shape[0], env.shape[1]
    mapping = {s: set() for s in range(0, MAX_R * MAX_C)}
    action_delta = {Action.UP: (-1, 0), Action.DOWN: (1, 0), Action.LEFT: (0, -1), Action.RIGHT: (0, 1)}
    for s in range(0, MAX_R * MAX_C):
        r = s // MAX_R
        c = s % MAX_R
        for a in list(Action):
            neighbor_r = min(MAX_R - 1, max(0, r + action_delta[a][0]))
            neighbor_c = min(MAX_C - 1, max(0, c + action_delta[a][1]))
            s_ = neighbor_r * MAX_R + neighbor_c
            mapping[s_].add(s)
    return mapping

有了描述状态依赖的映射 dict 后,代码也比较简洁,changed_state_set 变量保存了这轮必须计算的状态集合。新的一轮迭代时,将下一轮需要计算的状态保存到 changed_state_set_ 中,本轮结束后,changed_state_set 更新成changed_state_set_,开始下一轮循环直至没有状态需要更新。

def value_iteration_async(env:GridWorldEnv, gamma=1.0, theta=0.0001) -> Tuple[Policy, StateValue]:
    mapping = build_reverse_mapping(env)

    V = np.zeros(env.nS)
    changed_state_set = set(s for s in range(env.nS))

    iter = 0
    while len(changed_state_set) > 0:
        changed_state_set_ = set()
        for s in changed_state_set:
            action_values = action_value(env, s, V, gamma=gamma)
            best_action_value = np.max(action_values)
            v_diff = np.abs(best_action_value - V[s])
            if v_diff > theta:
                changed_state_set_.update(mapping[s])
                V[s] = best_action_value
        changed_state_set = changed_state_set_
        iter += 1

    policy = np.zeros([env.nS, env.nA])
    for s in range(env.nS):
        action_values = action_value(env, s, V, gamma=gamma)
        best_action = np.argmax(action_values)
        policy[s, best_action] = 1.0

    return policy, V

比较值迭代和异步值迭代方法后发现,值迭代用了4次循环,每次涉及所有状态,总计算状态数为 4 x 16 = 64。异步值迭代也用了4次循环,但是总计更新了54个状态。由于Grid World 的状态数很少,异步值迭代优势并不明显,但是对于状态数众多并且迭代最终集中在少部分状态的环境下,节省的计算量还是很可观的。