4. 蒙特卡洛方法

蒙特卡罗方法是我们第一个用于估计价值函数和发现最优策略的学习方法。与之前动态规划DP不同的是,这里我们不假设对环境的完全了解。蒙特卡洛方法只需要状态、动作和与环境实际或模拟交互的奖励的经验样本序列。从实际经验中学习是引人注目的,因为它不需要事先了解环境的动态,但仍然可以达到最佳行为。从模拟经验中学习也很强大。虽然需要一个模型,但模型只需要生成样本转换,而不是动态规划(DP)所需要的所有可能转换的完整概率分布。在许多情况下,根据所需的概率分布采样生成经验很容易,但以显式形式获得分布却不可行。

蒙特卡洛方法是基于平均样本收益来解决强化学习问题的方法。为了保证有明确的回报,这里我们只对偶发任务定义蒙特卡洛方法。也就是说,我们假设经验被划分为若干个事件,并且无论选择什么动作,所有事件最终都会终止。只有在一个事件完成时,价值估计和策略才会改变。因此,蒙特卡洛方法可以在逐个完整事件的意义上是增量的,但不能在每个单步(online方法)的意义上是增量的。术语蒙特卡洛通常更广泛地用于任何操作涉及重要随机成分的估计方法。在这里,我们将其专门用于基于完整收益率平均的方法(相对于从部分收益率学习的方法,将在之后考虑)。

蒙特卡洛方法对每个状态 - 动作对的回报进行采样和平均,就像bandit方法(老虎机方法)对每个动作的回报进行采样和平均一样。主要的区别是,现在有多个状态,每个状态的作用就像一个不同的老虎机问题(如关联搜索或上下文老虎机问题),而且不同的老虎机问题是相互关联的。也就是说,在一个状态下采取动作后的回报取决于同一事件中以后状态下的动作。由于所有的动作选择都是经过学习的,从早期状态的角度来看,问题变得非平稳。

关于老虎机问题(Bandit Problem)的介绍可以看此处

为了解决这一非平稳性问题,我们采用了通用策略迭代(GPI) 的思想。在这里,我们通过了解MDP来计算价值函数,而在这里,我们通过使用MDP返回的样本来学习价值函数。价值函数和相应的策略仍然以本质上相同的方式相互作用以获得最优性(GPI)。在上个部分动态规划DP中,我们首先考虑了预测问题(一个固定的任意策略 π \pi π v π v_\pi vπ q π q_\pi qπ的计算),然后是策略改进,最后是控制问题及其通过GPI的求解。从DP中提取的每一个想法都被扩展到蒙特卡洛案例中,其中只有样本经验是可用的。

4.1 蒙特卡洛预测

我们首先考虑蒙特卡罗方法来学习给定策略的状态价值函数。回想一下,一个状态的价值就是从该状态开始的预期收益,即预期累积未来折现回报。那么,根据经验来估计它的一种明显的方法就是简单地平均访问该状态后观察到的回报。当观察到更多的收益时,平均值应该收敛于期望值。这个思想是所有蒙特卡罗方法的基础。

特别地,假设我们希望估计 v π ( s ) v_\pi(s) vπ(s),即策略 π \pi π下状态 s s s的值,给定由下面得到的一组集。通过 s s s,状态 s s s在一个事件中的每一次发生都被称为对 s s s的访问,当然, s s s可能在同一个事件中被访问多次;我们把第一次访问一个事件称为 s s s首次访问(first visit)。首次访问MC方法估计 v π ( s ) v_\pi(s) vπ(s)的平均回报后第一个访问, 而每次访问(every visit) MC方法平均每次访问 s s s后的收益。这两个蒙特卡罗(MC)方法非常相似但略有不同的理论属性。首次访问MC的研究最为广泛,可追溯到20世纪40年代,这是我们在这里重点关注的一个。每次访问MC方法都更自然地扩展到函数近似和合格性跟踪。首次访问MC方法以程序形式显示在方框内。每次访问MC方法都是一样的,除了 S t S_t St的检查已经发生在一个事件的早些时候。


Frist-visit MC prediction, for estimating V ≈ v π V \approx v_\pi Vvπ
在这里插入图片描述


当访问 s s s的次数(或首次访问)趋于无穷时,首次访问MC方法和每次访问MC方法都收敛于 v π ( s ) v _\pi(s) vπ(s)。这对于首次访问MC方法来说是很容易看到的,在这种情况下,每个收益是一个独立的,同分布的估计 v π ( s ) v _\pi(s) vπ(s)与有限的方差。根据大数定律,这些估计的平均值序列收敛于它们的期望值。每个平均值本身是一个无偏估计,其误差的标准差下降为 1 / n 1/\sqrt{n} 1/n ,其中n是平均收益的数量。每次访问MC方法都不那么直接,但它的估计也会收敛到 v π ( s ) v_\pi(s) vπ(s)的平方 (Singh和Sutton, 1996)。

通过一个例子很好地说明了蒙特卡罗方法的使用。

例4.1:Blackjack(21点)

赌场上流行的21点纸牌游戏的目的是获得其数值之和尽可能大而不超过21的牌。所有的人头牌(扑克中的J,Q,K)都算作10,而A可以算作1或11。我们考虑每个玩家独立与庄家竞争的版本。游戏开始时,庄家和玩家都有两张牌。庄家的一张牌面朝上,另一张牌面朝下。如果玩家立即有21张牌(一张A和一张10牌),这被称为自然牌。他就赢了,除非庄家也有一张自然牌,在这种情况下,游戏是平局。如果玩家没有自然牌,那么他可以要求额外的牌,一个接一个要牌(hits),直到他停止(sticks)或超过21(破产(gose bust))。如果他gose bust,他就输了;如果他要牌,然后就轮到庄家的回合。庄家根据一个固定的策略确定要不要牌:如果牌面不大于17则继续要牌,否则停止。如果庄家破产,那么玩家赢了,否则,结果赢,输,或平局是由其最终的总和更接近21决定。

玩21点自然而然地被表述为一个偶发的有限MDP。每一局21点游戏都是一个事件。赢、输和平局的奖励分别为+1、1和0。一个游戏内的所有奖励都是零,没有折扣率( γ = 1 \gamma =1 γ=1),因此这些终端奖励也是回报。玩家的动作是要牌或者停止。这些状态取决于玩家的牌和庄家的牌。我们假设牌是从一副无限的牌中发出的(即,有替换),因此,跟踪已经发出的牌没有好处。如果玩家持有一张A,他可以在不破产的情况下算作11,那么这张A被认为是可用的。在这种情况下,它总是被算作11,因为将它算作1会使总和为11或更少,在这种情况下,没有任何决定可做,因为,显然,玩家应该总是打。因此,玩家根据三个变量做出决定:他的当前和值(12 21),庄家的一张牌(A 10),以及他是否持有可用的A。这使得总共有200种状态。

如果玩家的和值是20或21,则考虑该策略,否则继续要牌。为了通过蒙特卡洛方法找到这个策略的状态值函数,我们使用该策略模拟许多21点游戏,并对每个状态后的收益进行平均。通过这种方式,我们得到了图4.1所示的状态值函数的估计值。对于牌A的状态的估计值不太确定,也不太规则,因为这些状态不太常见。无论如何,在500,000次游戏之后,价值函数是非常好的近似值。
在这里插入图片描述

图4.1:仅停留在20或21的blackjack策略的近似状态值函数,由蒙特卡洛策略评估计算。

虽然我们对21点任务中的环境有完整的了解,但要应用DP方法来计算值函数并不容易。DP方法特别需要下一个事件的分布,它们需要由四参数函数 p p p给出的环境动态,而对于21点来说,确定这个并不容易。例如,假设玩家s的和是14,他选择要牌。作为庄家出示牌的函数,他以奖励+1结束的概率是多少?在应用DP之前,所有的概率都必须计算出来,而且这种计算通常很复杂,容易出错。相比之下,产生蒙特卡洛方法所需的样本游戏是很容易的。蒙特卡洛方法能够单独处理样本事件,即使在人们完全了解环境动态的情况下,这也是一个显著的优势。

我们能把备份图的思想推广到蒙特卡洛算法吗?备份图的一般思想是在顶部显示要更新的根节点,在下面显示所有的转换和叶节点,它们的回报和估计值对更新有贡献。对于 v π v_\pi vπ的蒙特卡罗估计,起始是一个状态节点,起始节点的下面是沿某一特定单事件的整个过渡轨迹,结束于终端状态,DP图显示了所有可能的转换,而蒙特卡洛图只显示了在一个场景中采样的那些转换。DP图只包含一步转换,而蒙特卡洛图展示了整个事件。这些差异准确地反映了两种算法之间的基本差异。

在这里插入图片描述

图4.2:蒙特卡洛图

关于蒙特卡洛方法的一个重要事实是,每个状态的估计是独立的。对一个状态的估计并不像在DP中那样建立在任何其他状态的估计之上。换句话说,蒙特卡洛方法并不像我们在之前所定义的那样进行推导。

特别要注意的是,估计一个状态值的计算与状态的数量无关。当人们只需要一个或一个子集的状态值时,这可以使蒙特卡洛方法特别有吸引力。人们可以从感兴趣的状态开始生成许多样本事件,只对这些状态的收益进行平均,而忽略所有其他状态。

这是蒙特卡洛方法相对于DP方法的第三个优势(继从实际经验和模拟经验中学习的能力之后)。

4.2 动作价值的蒙特卡洛估计

如果没有模型,那么估计动作值(状态-动作对 的值)而不是状态值就特别有用。有了模型,状态值本身就足以决定一个策略;我们只需向前看一步,然后选择哪一个动作导致了奖励和下一个状态的最佳组合,就像我们在之前DP中所做的那样。然而,如果没有一个模型,仅有状态值是不够的。我们必须明确地估计每个动作的价值,以使这些价值在建议策略时有用。因此,我们蒙特卡洛方法的主要目标之一是估计 q ∗ q_* q

为了实现这一目标,我们首先考虑动作值的策略评价问题。动作值的策略评价问题是估计 q π ( s , a ) q_\pi(s,a) qπ(s,a),即从状态 s s s开始,采取动作 a a a,此后遵循策略 π \pi π时的预期收益。这方面的蒙特卡洛方法与刚才介绍的状态值基本相同,只是现在我们讨论的是对一个状态-动作对 的访问,而不是对一个状态的访问。一个状态-动作对 ( s , a ) (s, a) (s,a)被认为是在一个事件中被访问的,如果状态s被访问并且动作a被采取。每次访问MC方法估计一个状态-动作对 的价值是每次访问后的回报的平均值。首次访问MC方法是将每个事件中第一次访问该状态并选择动作后的返回值的平均值。这些方法和之前一样,随着每个状态-动作对的访问次数接近无穷大,会二次收敛到真实的期望值。

唯一的复杂之处在于,许多状态-动作对可能从未被访问过。如果 π \pi π是一个确定性策略,那么在跟随 π \pi π的过程中,人们将只观察到每个状态下的一个动作的回报。在没有回报率的情况下,其他动作的蒙特卡洛估计将不会随着经验的积累而改善。这是一个严重的问题,因为学习动作值的目的是帮助在每个状态下的可用动作中进行选择。为了比较备选方案,我们需要估计每个状态下所有动作的价值,而不仅仅是我们当前喜欢的动作。

这就是持续探索(maintaining exploration) 的一般问题,在之前的老虎机问题中讨论过。为了使策略评价对动作值起作用,我们必须保证持续的探索。一种方法是规定事件从一个状态 -动作对 开始,并且每一个状态-动作对 都有一个非零的概率被选为开始。这就保证了所有的状态-动作对在无限多的剧情中会被无限次的访问。我们称这种假设为探索开始(exploring starts)

探索开始的假设有时是有用的,但当然在一般情况下不能依赖它,特别是当直接从与环境的实际交互中学习时。在这种情况下,起始条件不太可能有这么大的帮助。最常见的另一种方法是只考虑在每个状态下选择所有动作的非零概率的随机策略,以保证所有的状态-动作对 都能遇到。我们在后面讨论这种方法的两个重要变体。现在,我们保留探索开始的假设,并完成一个完整的蒙特卡洛控制方法的介绍。

4.3 Monte Carlo 控制

我们现在准备考虑如何在控制中使用蒙特卡罗估计,即近似最优策略。总体思路是按照与动态规划DP相同的模式进行,即按照广义策略迭代(GPI)的思路进行。在GPI中,一个保持一个近似策略和一个近似值函数。反复修改价值函数,使其更接近当前策略的价值函数,并根据当前的价值函数反复改进策略,如图所示。这两种变化在一定程度上是相互作用的,它们各自为对方创造了一个移动的目标,但它们共同导致策略和价值函数都趋向最优。

在这里插入图片描述

图4.3 Monte Carlo 控制

首先,让我们考虑经典策略迭代的蒙特卡洛版本。在此方法中,我们执行交替的完整步骤的策略评估和策略改进,开始于一个任意的策略 π 0 \pi_0 π0,结束于最优策略和最优动作-价值函数

在这里插入图片描述
其中 E → \underrightarrow{E} E表示完全的策略评估, I → \underrightarrow{I} I表示完全的策略改进。策略评估完全按照上一节的描述进行。经历了许多事件,近似的动作-值函数渐近地接近真实的函数。现在,让我们假设我们确实观察到了无限多的事件,而且,这些事件是伴随着探索的开始而产生的。在这些假设下,蒙特卡罗方法将精确地计算每一个 q π k q_{\pi_k} qπk,对于任意的 π k \pi_k πk

策略改进是通过使策略对当前值函数变得贪婪(贪心)来实现的。在这种情况下,我们有一个动作-价值函数,因此不需要模型来构建贪心策略。对于任意一个动作值函数 q q q,其对应的贪心策略就是对于每一个 s ∈ S s \in \mathcal{S} sS,确定地选择一个动作价值最大的动作:
π ≐ arg max ⁡ a q ( s , a ) (4.1) \pi_\doteq \argmax_{a} q(s,a) \tag{4.1} πaargmaxq(s,a)(4.1)

那么策略改进可以通过将每个 π k + 1 \pi_{k+1} πk+1构造为相对于 q π k q_{\pi_k} qπk的贪婪策略来完成。策略改进定理就适用于 π k \pi_k πk π k + 1 \pi_{k+1} πk+1,因为,对于所有 s ∈ S s\in \mathcal{S} sS,
在这里插入图片描述
正如我们在前面所讨论的,这个定理保证我们每个 π k + 1 \pi_{k + 1} πk+1均匀比 π k \pi_{k} πk,或者只是与 π k \pi_{k} πk一样好,在这种情况下,他们都是最优策略。这反过来保证我们整个过程收敛于最优策略和最优值函数。在这种情况下,蒙特卡罗方法可以用来寻找最优策略,只给样本集,而没有其他的环境动力。

为了使蒙特卡罗方法的收敛性得到保证,我们在上面做了两个不太可能的假设。一个是事件有探索的开始,另一个是策略评估可以用无数的事件来完成。为了得到一个实用的算法,我们必须去除这两个假设。

现在我们关注的是策略评估对无限多的事件进行操作的假设。这个假设相对容易消除。事实上,同样的问题出现在经典的DP方法,如迭代策略评估,也只渐近收敛于真值函数。在DP和蒙特卡洛两种情况下,都有两种方法来解决问题。一是坚持在每次策略评估中近似 q π k q_{\pi_k} qπk的想法。通过测量和假设来获得估计误差的大小和概率的界限,然后在每次策略评估中采取足够的步骤来确保这些界限足够小。在保证正确收敛到某种近似水平的意义上,这种方法可能是完全令人满意的。然而,除了最小的问题之外,它也可能需要太多的事件来在实践中发挥作用。

为了避免名义上需要进行策略评估的无数章节,还有第二种方法,即在返回到策略改进之前,放弃试图完成策略评估。在每一步计算中,我们都将值函数移向 q π k q_{\pi_k} qπk,但我们并不期望实际上能接近它,除非经过许多步。该思想的一种极端形式是价值迭代,即在策略改进的每一步之间只执行迭代策略评估的一次迭代。价值迭代的in-place版本甚至更极端;对于单个状态,我们在改进和评估步骤之间交替进行。

对于蒙特卡洛策略评估,很自然地在评估和改进之间交替进行。在每个事件之后,观察到的返回值将用于策略评估,然后在事件中访问的所有状态对策略进行改进。沿着这些路线的一个完整的简单算法,我们称之为蒙特卡洛ES(Monte Carlo with Exploring Starts,带有起始探索的蒙特卡洛方法)


Monte Carlo ES (Exploring Starts), for estimating π ≈ π ∗ \pi \approx \pi_* ππ

在这里插入图片描述

例4.2: 21点的解法

将蒙特卡洛ES应用于21点是很直接的。因为所有的事件都是模拟游戏,所以很容易安排包括所有可能性的探索性开始,在这种情况下,我们只需选择庄家的牌,玩家的和,以及玩家是否有牌。在这种情况下,我们只需选择庄家的牌,玩家的和,以及玩家是否有可用的A,所有这些都是随机的,概率相等。作为初始策略,我们使用在前面21点例子中评估的策略,即只在20或21上坚持。对于所有的状态动作对,初始动作值函数可以为零。图4.3显示了由蒙特卡洛ES找到的21点的最优策略。该策略与Thorp(1966)的基本策略相同,唯一的例外是策略中最左边的缺口为可用A,这在Thorp的策略中是不存在的。我们不确定这种差异的原因,但相信这里显示的确实是我们所描述的21点版本的最佳策略。

在这里插入图片描述

图4.4 21点的最优策略和状态值函数,由Monte Carlo ES得出。所示的状态值函数是由Monte Carlo ES发现的动作值函数计算出来的。

4.4 蒙特卡洛控制的无探索启动

我们如何避免探索开始的不可能假设?确保所有动作被无限频繁地选择的唯一一般方法是让智能体继续选择它们。有两种方法可以确保这一点,由此产生了我们所说的 on-policy(有些文章翻译成"同策") 方法和 off-policy(“异策”) 方法,在之后还是使用其原来的英语名称,即"on-policy"与"off-policy"。on-policy方法试图评估或改进用于决策的策略,而off-policy方法评估或改进与用于生成数据策略的不同。上面开发的蒙特卡洛ES方法就是一个on-policy方法的例子。在本节中,我们将展示如何设计一种不使用不切实际的探索启动假设的策略性蒙特卡罗控制方法。之后将考虑Off-policy方法。

在on-policy控制方法中,策略一般是柔性(soft) 的,即对所有 s ∈ S , a ∈ A ( s ) s \in \mathcal{S}, a \in \mathcal{A}(s) sS,aA(s)来说,意味着 π ( a ∣ s ) > 0 \pi(a|s)>0 π(as)>0,但逐渐向确定性最优策略靠拢。在本节中介绍的on-policy方法使用了 ε \varepsilon ε-贪婪的策略,这意味着大多数时候他们会选择一个具有最大估计动作值的动作,但在概率 ε \varepsilon ε上他们反而会随机选择一个动作。也就是说,所有的非贪婪动作都被赋予了最小的选择概率, ∣ A ( s ) ∣ ε \frac{|A(s)|}{\varepsilon} εA(s) ,而剩下的大部分概率, 1 − ε + ∣ ε A ( s ) ∣ 1-{\varepsilon}+|\frac{{\varepsilon}}{A(s)}| 1ε+A(s)ε,被赋予了贪婪动作。 ε \varepsilon ε-贪婪策略是 ε \varepsilon ε-柔性策略的一个例子,它被定义为,对于 ε > 0 \varepsilon >0 ε>0,对所有的状态和动作,有 π ( a ∣ s ) ≥ ε ∣ A ( s ) ∣ \pi(a|s) \geq \frac{\varepsilon}{|A(s)|} π(as)A(s)ε的策略。 ε \varepsilon ε-柔性策略中, ε \varepsilon ε-贪婪策略在某种意义上是最接近于贪婪的策略。

ε \varepsilon ε-贪婪策略,
π ( a ∣ s ) = { 1 − ε + ε ∣ A ( s ) ∣ , a = a ∗ ε ∣ A ( s ) ∣ ,   a ≠ a ∗ \pi(a|s)=\begin{cases}1-\varepsilon + \frac{\varepsilon}{|\mathcal{A}(s)|}, & a= a^*\\ \frac{\varepsilon}{|\mathcal{A}(s)|}, & \ a\not=a^*\end{cases} π(as)={1ε+A(s)ε,A(s)ε,a=a a=a

这个 ε \varepsilon ε贪婪测类比其中的概率平均分配在各个动作上,将剩下 ( 1 − ε ) (1-\varepsilon) (1ε)的概率分配给动作 a ∗ a^* a.基于柔性策略的回合更新的要点就是在策略提升环节用 ε \varepsilon ε贪婪策略的表达式来更新策略.

策略上蒙特卡洛控制的总体思想仍然是GPI的思想。与蒙特卡洛ES一样,我们使用首次访问MC方法来估计当前策略的动作值函数。然而,在没有探索开始的假设下,我们不能简单地通过使策略对当前价值函数变得贪婪来改进策略,因为这将阻止对非贪婪动作的进一步探索。幸运的是,GPI并不要求将策略一直采取贪婪的策略,只要求将其向贪婪的策略发展。在我们的on-policy方法中,我们将只把它移动到一个 ε \varepsilon ε-贪婪策略。对于任何 ε \varepsilon ε-柔性策略, 任何关于 q π q_\pi qπ ε \varepsilon ε-贪婪策略都保证优于或等于 π \pi π


On-policy first-visit MC control (for "-soft policies), estimates π ≈ π ∗ \pi \approx \pi_* ππ
在这里插入图片描述


策略改进定理保证了任何关于 q π q_\pi qπ的贪心策略都是对任意 ε \varepsilon ε-柔性策略 π \pi π的改进。使 π ′ \pi' π成为 ε \varepsilon ε贪心策略。策略改进定理的条件适用于任何 s ∈ S s\in \mathcal{S} sS:
q π ( s , π ′ ( s ) ) = ∑ a π ′ ( a ∣ s ) q π ( s , a ) = ε ∣ A ( s ) ∣ ∑ a q π ( s , a ) + ( 1 − ε ) max ⁡ a q π ( s , a ) ≥ ε ∣ A ( s ) ∣ ∑ a q π ( s , a ) + ( 1 − ε ) ∑ a π ( a ∣ s ) − ε ∣ A ( s ) ∣ 1 − ε q π ( s , a ) = ε ∣ A ( s ) ∣ ∑ a q π ( s , a ) + ε ∣ A ( s ) ∣ ∑ a q π ( s , a ) + ∑ π ( a ∣ s ) q π ( s , a ) = v π ( s ) (4.2) \begin{aligned} q_\pi(s,\pi'(s))& =\sum_{a}\pi'(a|s)q_\pi(s,a)\\ &=\frac{\varepsilon}{|\mathcal{A}(s)|}\sum_{a}q_\pi(s,a)+(1 - \varepsilon)\max_{a}q_\pi(s,a)\\ &\geq\frac{\varepsilon}{|\mathcal{A}(s)|}\sum_{a}q_\pi(s,a)+(1 -\varepsilon)\sum_{a}\frac{\pi(a|s)- \frac{\varepsilon}{|\mathcal{A}(s)|}}{1-\varepsilon}q_\pi(s,a)\\ &=\frac{\varepsilon}{|\mathcal{A}(s)|}\sum_{a}q_\pi(s,a)+\frac{\varepsilon}{|\mathcal{A}(s)|}\sum_{a}q_\pi(s,a)+\sum\pi(a|s)q_\pi(s,a)\\ &=v_\pi(s) \end{aligned}\tag{4.2} qπ(s,π(s))=aπ(as)qπ(s,a)=A(s)εaqπ(s,a)+(1ε)amaxqπ(s,a)A(s)εaqπ(s,a)+(1ε)a1επ(as)A(s)εqπ(s,a)=A(s)εaqπ(s,a)+A(s)εaqπ(s,a)+π(as)qπ(s,a)=vπ(s)(4.2)
因此,根据策略改进定理, π ′ ≥ π ( i . e . , v π ′ ≥ v π ( s ) , for all  s ∈ S ) \pi' \geq\pi(i.e.,v_{\pi'}\geq v_\pi(s),\text{for all } s\in\mathcal{S}) ππ(i.e.,vπvπ(s),for all sS)。我们现在证明只有当 π ′ \pi' π π \pi π都是最优的 ε \varepsilon ε-柔性策略才可以相等,也就是说,当他们都优于或等于其他 ε \varepsilon ε-柔性策略。

考虑一个与原始环境类似的新环境,除了需要将 ε \varepsilon ε-柔性策略移动到环境中之外。新环境具有与原始环境相同的操作和状态设置,其行为如下。如果处于状态s,采取动作a,那么新环境和旧环境完全一样的概率为 1 − ε 1-\varepsilon 1ε。“它以相等的概率 ε \varepsilon ε随机地重新选择动作,然后像旧环境中有新的随机动作一样动作。”在这个新环境中使用一般策略所能达到的最佳效果与在原始环境中使用 ε \varepsilon ε-柔性策略所能达到的最佳效果是一样的。设 v ~ ∗ \widetilde{v}_* v q ~ ∗ \widetilde{q}_* q 为新环境的最优值函数。当且仅当 v π = v ~ ∗ v_\pi = \widetilde{v}_* vπ=v 时,在 ε \varepsilon ε-柔性策略”中某一策略 π \pi π是最优的。由 v ~ ∗ \widetilde{v}_* v 的定义可知,它是以下方程的唯一解,

在这里插入图片描述

ε \varepsilon ε-柔性策略不再改进时,我们知道
在这里插入图片描述

但是,除了将 v π v_\pi vπ替换为 v ~ ∗ \widetilde{v}_* v 之外,这个方程与前一个相同。因为 v ~ ∗ \widetilde{v}_* v 是唯一解,必须有 v π = v ~ ∗ v_\pi=\widetilde{v}_* vπ=v

实际上,我们在之前已经说明了策略迭代适用于 ε \varepsilon ε-柔性策略。将贪婪策略的自然概念用于 ε \varepsilon ε-柔性策略”,就可以保证每一步都能得到改进,除非在 ε \varepsilon ε-柔性策略中找到了最佳策略。这种分析与在每个阶段如何确定动作-价值函数无关,但它假定它们是精确计算出来的。现在我们只选择柔性策略中最好的一个,但另一方面,我们已经消除了有"开始探索"的设想。

4.5 通过重要性采样进行Off-policy预测

所有的学习控制方法都面临着一个两难的问题:它们试图学习以后续最优行为为条件的动作值,但为了探索所有动作(找到最优动作),它们需要做出非最优行为。他们如何在根据探索性策略进行行为的同时学习最优策略呢?on-policy方法其实是一种折中的方法,它学习的动作值不是最优策略,而是仍然探索的接近最优策略。更直接的方法是使用两个策略,一个是被学习到的策略,成为最优策略,另一个是探索性更强的策略,用于产生行为。被学习的策略称为目标策略(target policy),而用于产生行为的策略称为行为策略(behavior policy)。在这种情况下,我们说学习是根据目标策略以外的数据,整个过程被称为off-policy学习。

在其余部分,我们同时考虑on-policy和off-policy方法。策略上的方法一般比较简单,我们首先考虑的是on-policy方法。off-policy方法需要额外的概念和符号,而且由于数据是由于不同的策略,所以off-policy方法通常有较大的差异,收敛速度较慢。另一方面,off-policy方法的功能更强大,更通用。它们将on-policy方法作为目标和行为策略相同的特殊情况。off-policy方法在应用中也有各种额外的用途。例如,它们经常可以应用于从传统的非学习控制器生成的数据中学习,或者从人类专家那里学习。off-policy学习也被一些人视为学习世界动态的多步骤预测模型的关键。

本节我们首先研究off-policy方法,考虑预测问题,其中目标和行为策略都是固定的。也就是说,假设我们希望估计 v π v_\pi vπ q π q_\pi qπ ,但我们所拥有的都是跟随另一个策略 b b b的事件,其中 b ≠ π b \not = \pi b=π 。 在这种情况下, π \pi π是目标策略, b b b是行为策略,两个策略都被认为是固定的和给定的。

为了使用来自 b b b的事件来估计 π \pi π的值,我们要求在 π \pi π下采取的每一个动作也至少偶尔在 b b b下采取。也就是说,我们需要使得满足 π ( a ∣ s ) > 0 \pi(a|s)>0 π(as)>0 s ∈ S , a ∈ A ( s ) s\in\mathcal{S}, a\in\mathcal{A}(s) sS,aA(s),均有 b ( a ∣ s ) > 0 b(a|s)>0 b(as)>0,这就是所谓的覆盖率(coverage) 假设。从覆盖率可以看出, b b b在与 π \pi π不相同的状态下一定是随机的。另一方面,目标策略 π \pi π可能是确定性的,事实上,这在控制应用中是一个特别值得关注的案例。在控制中,目标策略通常是相对于动作价值函数的当前估计的确定性贪婪策略。这种策略成为确定性最优策略,而行为策略仍然是随机的、更具有探索性的,例如, ε \varepsilon ε-贪婪策略"。然而,我们考虑的是预测问题,其中 π \pi π是不变的,并且是给定的。

几乎所有的非策略方法都利用了重要性采样,这是一种通用的技术,用于估计一种分布下给定的另一种分布的预期值。我们将重要性采样应用于非策略学习,根据目标和行为策略下其轨迹发生的相对概率对收益进行加权,称为重要性采样比率(importance-sampling ratio)。给定一个起始状态 S t S_t St,后续状态动作轨迹 A t , S t + 1 , A t + 1 , . . . . . . , S T A_t,S_{t+1},A_{t+1},......,S_T AtSt+1At+1......,ST ,在任意策略 π \pi π下发生的概率为

在这里插入图片描述

其中,p为状态转移概率函数。因此,目标和行为策略下轨迹的相对概率(重要采样比率)为
ρ t : T − 1 ≐ ∏ k = t T − 1 π ( A k ∣ S k ) p ( S k + 1 ∣ S k , A k ) ∏ k = t T − 1 b ( A k ∣ S k ) p ( S k + 1 ∣ S k , A k ) = ∏ k = t T − 1 π ( A k ∣ S k ) b ( A k ∣ S k ) (4.3) \rho_{t:T-1} \doteq \frac{\prod_{k=t}^{T-1}\pi(A_k|S_k)p(S_{k+1}|S_k, A_k)}{\prod_{k=t}^{T-1}b(A_k|S_k)p(S_{k+1}|S_k, A_k)} = \prod_{k=t}^{T-1}\frac{\pi(A_k|S_k)}{b(A_k|S_k)}\tag{4.3} ρt:T1k=tT1b(AkSk)p(Sk+1Sk,Ak)k=tT1π(AkSk)p(Sk+1Sk,Ak)=k=tT1b(AkSk)π(AkSk)(4.3)

虽然轨迹概率取决于一般未知的MDP的转移概率,但它们在分子和分母上都是相同的,因此可以消去。重要性采样比率最终只取决于两个策略和序列,而不取决于MDP。

回想一下,我们希望估计目标策略下的预期收益(值),但我们所拥有的是行为策略下的收益 G t G_t Gt。这些收益具有错误的期望值 E [ G t ∣ S t = s ] = v b ( s ) \mathbb{E}[G_t|S_t=s] = v_b(s) E[GtSt=s]=vb(s),因此不能对其进行平均以获得 v π v_\pi vπ 。这就是重要性采样的作用。比率 ρ t : T − 1 \rho_{t:T-1} ρt:T1将收益转化为具有正确期望值的收益。

E [ ρ t : T − 1 G t ∣ S t = s ] = v π ( s ) (4.4) \mathbb{E}[\rho_{t:T-1}G_t|S_t=s] = v_\pi(s)\tag{4.4} E[ρt:T1GtSt=s]=vπ(s)(4.4)

现在我们准备给出一个蒙特卡洛算法,该算法按照策略 b b b对一批观察到的事件的收益进行平均,以估计 v π ( s ) v_\pi(s) vπ(s)。这里很方便地将时间步数以跨事件边界的方式增加。也就是说,如果该批次的第一个事件在时间100时结束于终端状态,那么下一个事件在时间t=101时开始。这使得我们能够使用时间步数来指代特定事件中的特定步骤。特别是,我们可以定义状态 s s s被访问的所有时间步数的集合,表示为 T ( s ) \mathcal{T}(s) T(s)。这是对每次访问方法而言的;对于首次访问方法, T ( s ) \mathcal{T}(s) T(s)将只包括在其事件中首次访问s的时间步骤。另外,让 T ( s ) T(s) T(s)表示时间 t t t之后的第一次终止时间, G t G_t Gt表示 t t t之后直至 T ( t ) T(t) T(t)的回报。那么 { G t } t ∈ T ( s ) \{G_t\}_{t \in \mathcal{T}(s)} {Gt}tT(s)是与状态 s s s有关的返回,而 { ρ t : T ( t ) − 1 } t ∈ T ( s ) \{\rho _{t:T(t)-1}\} _{t\in\mathcal{T}(s)} {ρt:T(t)1}tT(s)是相应的重要性采样比率。为了估计 v π ( s ) v_\pi(s) vπ(s),我们只需将回报率按比率进行缩放,并将结果进行平均即可
V ( s ) ≐ ∑ t ∈ T ( s ) ρ t : T ( t ) − 1 G t ∣ T ( s ) ∣ (4.5) V(s)\doteq\frac{\sum_{t\in\mathcal{T}(s)}\rho_{t:T(t)-1}G_t}{|\mathcal{\mathcal{T}(s)}|} \tag{4.5} V(s)T(s)tT(s)ρt:T(t)1Gt(4.5)

当重要性采样以简单平均数的方式进行时,它被称为普通重要性采样。

一个重要的替代方法是加权重要性采样,它使用加权平均值,定义为

V ( s ) ≐ ∑ t ∈ T ( s ) ρ t : T ( t ) − 1 G t ∑ t ∈ T ( s ) ρ t : T ( t ) − 1 (4.6) V(s)\doteq\frac{\sum_{t\in\mathcal{T}(s)}\rho_{t:T(t)-1}G_t}{\mathcal{\sum_{t\in\mathcal{T}(s)}\rho_{t:T(t)-1}}}\tag{4.6} V(s)tT(s)ρt:T(t)1tT(s)ρt:T(t)1Gt(4.6)

或如果分母为零,则为零。为了理解这两个种类的重要性采样,可以考虑观察到状态 s s s的单次回归后对其首次访问方法的估计。在加权平均估计中,单次回归的比率 ρ t : T ( t ) − 1 \rho_{t:T(t)-1} ρt:T(t)1在分子和分母中取消,因此估计等于观察到的回归,与比率无关(假设比率非零)。考虑到这个回报率是唯一一个被观察到的回报率,这是一个合理的估计,但它的期望值是 v b ( s ) v_b(s) vb(s)而不是 v ( s ) v_(s) v(s),在这个统计意义上它是有偏差的。相反,普通重要性采样估计的首次访问版本的期望值总是 v π ( s ) v_\pi(s) vπ(s)(它是无偏的),但它可能是极端的。假设比值为10,说明在目标策略下观察到的轨迹是行为策略下的十倍。在这种情况下,普通重要性采样估计将是观察到的收益的十倍。也就是说,它将与观察到的收益相去甚远,即使该事件的轨迹被认为是目标策略的特殊代表。

从形式上看,两种重要性采样的首次访问法之间的差异表现在它们的偏差和方差上。普通重要性采样是无偏的,而加权重要性采样则是有偏的(尽管偏度渐进地趋于零)。另一方面,普通重要性采样的方差在一般情况下是无界的,因为比率的方差可以是无界的,而在加权估计器中,任何一个收益的最大权重都是1。事实上,假设收益是有界的,即使比率本身的方差是无限的,加权重要性采样估计的方差也会趋近于零(Precup,Sutton,and Dasgupta 2001)。在实践中,加权估计器的方差通常会大大降低,并被强烈推荐。尽管如此,我们不会完全放弃普通重要性采样,因为它更容易扩展到以后会讨论的用函数逼近的近似方法。

普通重要性采样和加权重要性采样的每次访问方法都是有偏差的,不过,随着样本数量的增加,偏差同样会渐进地降为零。在实践中,每次访问方法通常是首选,因为它们消除了跟踪哪些状态已被访问的需要,而且它们更容易扩展到近似。

4.6 增量实现

对于off-policy蒙特卡洛方法,我们需要分别考虑使用普通重要性采样和使用加权重要性采样的方法。

在普通重要性采样中,收益按重要性采样比 ρ t : T ( t ) − 1 \rho_{t:T(t)-1} ρt:T(t)1进行缩放(4.3),然后简单求平均,如(4.5)。对于这些方法,我们又可以使用增量法,但用缩放后的收益来代替该章的奖励。这就剩下使用加权重要性采样的off-policy方法的情况了。在这里,我们必须形成收益的加权平均值,并且需要一种稍微不同的增量算法。

假设我们有一个返回序列 G 1 , G 2 , … , G n − 1 , G_{1},G_{2},…, G_{n−1}, G1,G2,Gn1,开始在同一个状态和每一个都有相应的随机 W i W_i Wi权重(例如, W i = ρ t : T ( t ) − 1 W_i =\rho_{t:T(t)-1} Wi=ρt:T(t)1)。我们希望作出估计
V n ≐ ∑ k = 1 n − 1 W k G k ∑ k = 1 n − 1 W k , n ≥ 2 (4.7) V_n \doteq \frac{\sum_{k=1}^{n-1}W_kG_k}{\sum_{k=1}^{n-1}W_k}, n \geq2 \tag{4.7} Vnk=1n1Wkk=1n1WkGk,n2(4.7)

并保持它的最新,因为我们获得一个额外的回报 G n G_n Gn。除了跟踪 V n V_n Vn之外,我们还必须为每个状态维护前 n n n个返回的权值的累积和 C n C_n Cn V n V_n Vn的更新规则为

V n + 1 ≐ V n + W n C n [ G n − V n ] , n ≥ 1 (4.8) V_{n+1} \doteq V_n + \frac{W_n}{C_n}[G_n-V_n], n\geq 1 \tag{4.8} Vn+1Vn+CnWn[GnVn],n1(4.8)

C n + 1 ≐ C n + W n + 1 C_{n+1}\doteq C_n +W_{n+1} Cn+1Cn+Wn+1

其中 C 0 ≐ 0 C_0 \doteq 0 C00( V 1 V_1 V1是任意的,因此不需要指定)。以下列出了一个完整的蒙特卡洛策略评估的逐集递增算法。off-policy算法是名义上的情况下,使用加权重要性采样,但也适用于对on-policy的情况,通过选择目标和行为的策略是相同的(在这种情况下( π = b π= b π=b), W W W总是 1 1 1)。近似 Q Q Q收敛于 q π q_\pi qπ(所有遇到状态动作对),而根据一个潜在的不同的策略选择行为, b b b

在这里插入图片描述

4.7 Off-policy蒙特卡洛控制

回想一下,on-policy方法的显著特征是,它们在使用策略进行控制时评估策略的价值。在off-policy方法中,这两个函数是分开的。用于生成行为的策略(称为行为策略)实际上可能与被评估和改进的策略(称为目标策略)无关。这种分离的一个好处是,目标策略可能是确定性的(例如,贪婪的),而行为策略可以继续对所有可能的动作进行采样。

off-policy蒙特卡罗控制方法使用前面两部分中介绍的技术之一。他们在学习和改进目标策略的同时遵循行为策略。这些技术要求行为策略选择目标策略(覆盖率)可能选择的所有操作的概率为非零。为了探索所有的可能性,我们要求行为策略是柔性的(例如,即以非零概率选择所有状态下的所有动作)。

以下展示了一种基于GPI和加权重要性采样的off-policy蒙特卡洛控制方法,用于估计 π ∗ \pi_* π q ∗ q_* q。目标策略 π ≈ π ∗ \pi \approx\pi_* ππ是针对 Q Q Q的贪心策略,是对 q π q_\pi qπ的估计。可以采取任何行为策略 b b b,但为了保证收敛到最优策略,每对状态和行为必须获得无限的回报。可以通过选择 b b b为“柔性”来确保这一点。尽管根据不同的柔性策略 b b b选择了动作,该策略在所有遇到的状态下都会收敛到最优,柔性策略 b b b可能在事件之间甚至在事件内部发生变化。

在这里插入图片描述
一个潜在的问题是,这种方法只从事件的结束开始学习,当事件中所有剩余的动作都是贪婪的。如果非贪婪的动作很常见,那么学习就会很慢,特别是对于长期事件的早期部分出现的状态。潜在地,这可能会大大减慢学习速度。off-policy蒙特卡洛方法的经验不足,则无法评估这个问题有多严重。

4.8 案例: 21点游戏

本节考虑纸牌游戏“21点”(Blackjack-v0),为其实现游戏AI。

21点的游戏规则是这样的:游戏里有一个玩家(player)和一个庄家(dealer),每个回合的结果可能是玩家获胜、庄家获胜或打成平手。回合开始时,玩家和庄家各有两张牌,玩家可以看到玩家的两张牌和庄家的其中一张牌。接着,玩家可以选择是不是要更多的牌。如果选择要更多的牌(称为“hit”),玩家可以再得到一张牌,并统计玩家手上所有牌的点数之和。各牌面对应的点数见表4-1,其中牌面A代表1点或11点。如果点数和大于21,则称玩家输掉这一回合,庄家获胜;如果点数和小等于21,那么玩家可以再次决定是否要更多的牌,直到玩家不再要更多的牌。如果玩家在总点数小等于21的情况下不要更多的牌,那么这时候玩家手上的总点数就是最终玩家的点数。接下来,庄家展示其没有显示的那张牌,并且在其点数小于17的情况下抽取更多的牌。如果庄家在抽取的过程中总点数超过21,则庄家输掉这一回合,玩家获胜;如果最终庄家的总点数小于等于21,则比较玩家的总点数和庄家的总点数。如果玩家的总点数大于庄家的总点数,则玩家获胜;如果玩家和庄家的总点数相同,则为平局;如果玩家的总点数小于庄家的总点数,则庄家获胜。

牌面点数
A1或11
22
33
99
10, J ,Q, K10
4.8.1 实验环境的使用

Gym库的环境’Blackjack-v0’实现了上述21点游戏。21点游戏环境和Gym库中其他环境的用法大致相同,我们可以用以下语句取得环境对象:

import gym
env = gym.make("Blackjack-v0")

用以下语句初始化环境。

env.reset()

用以下语句进入下一步。

env.step(action)

env.step()函数的参数是动作,它可以是int型数值0或1,其中0表示玩家不再要更多的牌,1表示玩家再要一张牌。env.step()的第0个返回值是观测,它是一个有3个元素的tuple值,其3个元素依次为:

  • 范围为3~21的int型数值,表示玩家的点数和;
  • 范围为1~10的int型数值,表示庄家可见牌的点数;
  • bool型数值,表示在计算玩家点数和的时候,是否有将1张A牌计算为11点。

在计算玩家点数时,如果玩家手上有A牌,那么会用以下规则确定A牌的点数:首先要保证玩家手上牌的总点数小于21(所以至多有一张A牌会算作11),在此基础上让总点数尽量大。在计算庄家点数时,总是将A计算为1点(当然总是计算为11点也是完全等价的)。

在以下代码中,用Numpy库的np.random.choice()函数选择动作。这个函数的第0个参数表示要从哪些数据里选择。它还可能有一个关键字参数p,表示选择各数据的概率。以下代码没有指定关键字参数p,则表示等概率选择数据。

if __name__ == '__main__':
    env = gym.make("Blackjack-v0")
    observation = env.reset()
    print('观测 = {}'.format(env.player, env.dealer))
    while True:
        print('玩家 = {}, 庄家 = {}'.format(env.player, env.dealer))
        action = np.random.choice(env.action_space.n)
        print('动作 = {}'.format(action))
        observation, reward, done, _ = env.step(action)
        print('观测 = {}, 奖励 = {}, 结束指示 = {}'.format(observation, reward, done))

        if done:
            break
4.8.2 策略评估

21点游戏的交互可以看作一个Markov决策过程。我们将观测略做修改,将观测tuple的最后一个元素改为int值,就可以得到用3个int值表示的状态,见以下代码。

# 从观测到状态
def ob2state(observation):
    return(observation[0], observation[1], int(observation[2]))

在21点游戏中的轨迹有以下特点。

  • 在一个轨迹中不可能出现重复的状态。原因在于,在一个回合中,玩家在每一步都比上一步多了一张牌,所以总点数往往会增大,或者原来可以算作11点的A牌只能算作1点了。
  • 在一个轨迹中只有最后的一个奖励值是非零值。所以,在折扣因子 γ = 1 γ=1 γ=1的情况下(回合制任务可这样设定),最后的奖励值就是回合的总奖励值。

考虑到21点游戏具有以上特点,其on-policy回合更新算法可以做出以下简化:

  • 同一回合中每个状态肯定都是首次访问,不需要区分首次访问和每次访问;
  • 在折扣因子 γ = 1 γ=1 γ=1的情况下,只要将回合最后一个奖励值作为回报值,on-policy更新不需要逆序求回报值。利

用以上两个简化,下给出了on-policy回合更新策略评估的算法。函数evaluate_action_monte_carlo()根据环境env和策略policy,求得动作价值函数 q q q并返回。在这个函数中,不区分首次访问和每次访问,限定 γ = 1 γ=1 γ=1并直接用最后的奖励值作为回报值 g g g,而且在更新状态时是顺序更新的。

# 回合更新策略评估
def evaluate_action_monte_carlo(env, policy, episode_num=50000):
    q = np.zeros_like(policy)
    c = np.zeros_like(policy)
    for _ in range(episode_num):
        # 玩一回合
        state_actions = []
        observation = env.reset()
        while True:
            state = ob2state(observation)
            action = np.random.choice(env.action_space.n, p=policy[state])
            state_actions.append((state, action))
            observation, reward, done, _ = env.step(action)
            if done:
                break
        g = reward
        for state, action in state_actions:
            c[state][action] += 1.
            q[state][action] += (g - q[state][action]) / c[state][action]
    return q

下面来看一个evaluate_action_monte_carlo()函数的用法。下面这段代码评估了一个确定性算法policy。算法policy在总点数≥20时不再要牌,在总点数<20时要牌。通过调用evaluate_action_monte_carlo()函数,求得其动作价值函数为 q q q。接着,利用动作价值函数求出了状态价值函数 v v v

policy = np.zeros((22, 11, 2, 2))
policy[20:, :, :, 0] = 1  # >20时不再要牌
policy[:20, :, :, 1] = 1  # <20时不再要牌
q = evaluate_action_monte_carlo(env, policy)  # 动作价值
v = (q * policy).sum(axis=-1)

接下来考虑价值函数的可视化。考虑到q是一个4维的数组,而v是一个3维的数组,所以可视化v比可视化q容易。这里仅可视化v。下面代码给出了可视化最后一维指标为0或1的3维数组的函数plot()。函数plot()绘制了含有两个子图的图像,两个子图分别绘制最后一维指标为0和最后一维指标为1的数组值。每个子图的X轴表示玩家点数和,Y轴表示庄家显示的牌面。值得一提的是,这里显示的玩家点数和范围只有12~21,比实际可能出现的范围3~21小。实际上,12~21这个范围是我们最为关心的范围。这是因为,如果玩家的点数和小于等于11,那么再抽一张牌肯定不会超过21点,并且总是能得到更大的点数。所以,玩家在点数和小于等于11的情况下一定会选择继续抽牌,直到玩家总点数和大于等于12。所以,我们更关心玩家的点数和范围是12~21的情况。

# 绘制最后一维的指标为0或1的3维数组
def plot(data):
    fig, axes = plt.subplots(1, 2, figsize=(9, 4))
    titles = ['without ace', 'with ace']
    have_aces = [0, 1]
    extent = [12, 22, 1, 11]
    for title, have_ace, axis in zip(titles, have_aces, axes):
        dat = data[extent[0]:extent[1], extent[2]:extent[3], have_ace].T
        axis.imshow(dat, extent=extent, origin='lower')
        axis.set_xlabel('player sum')
        axis.set_ylabel('dealer showing')
        axis.set_title(title)
    plt.show()

利用代码

plot(v)

可以绘制得到状态价值函数的图像.
在这里插入图片描述

4.8.3 on-policy最优策略求解

本节考虑利用on-policy回合更新求解最优策略和最优价值函数。

以下给出了带起始探索的on-policy回合更新算法。正如4.8.2节介绍的,我们最为关心玩家的点数和在12~21这个范围内的状态,所以以下代码中的起始探索也只覆盖这个范围内的状态。在函数内部,每个游戏回合前都随机产生一个状态动作对。利用产生的状态,可以反推出一种玩家的持牌可能性和庄家持有的明牌。考虑所有对应到相同状态的玩家持牌都是等价的,所以这里只需任意指定一种玩家的持牌即可。计算得到玩家的持牌和庄家持有的明牌后,可以直接将牌面赋值给env.playerenv.dealer[0],告知环境当前状态。这样,该回合游戏就可以从给定的起始状态开始了。

# 带起始探索的回合更新
# 带起始探索的回合更新
def monte_carlo_with_exploring_start(env, episode_num=500000):
    policy = np. zeros((22, 11, 2, 2))
    policy[:, :, :, 1] = 1.
    q = np.zeros_like(policy)
    c = np.zeros_like(policy)
    for _ in range(episode_num):
        # 随机选择起始状态和起始动作
        state = (np.random.randint(12, 22),
                 np.random.randint(1, 11),
                 np.random.randint(2))
        action = np.random.randint(2)
        # 玩一回合
        env.reset()
        if state[2]:  # 有A
            env.player = [1, state[0] - 11]
        else:  # 没有A
            if state[0] == 21:
                env.player = [10, 9, 2]
            else:
                env.player = [10, state[0] - 10]
        env.dealer[0] = state[1]
        state_actions = []
        while True:
            state_actions. append((state, action))
            observation, reward, done, _ = env. step(action)
            if done:
                break  # 回合结束
            state = ob2state(observation)
            action = np.random.choice(env.action_space.n, p=policy[state])
        g = reward  # 回报
        for state, action in state_actions:
            c[state][action] += 1.
            q[state][action] += (g - q[state][action]) / c[state][action]
            a = q[state].argmax()
            policy[state] = 0.
            policy[state][a] = 1.
    return policy, q

利用monte_carlo_with_exploring_start()函数,我们可以计算最优价值函数和最优策略,并绘制最优策略和最优价值函数。

    # 带起始探索的更新策略
    print('带起始探索的更新策略'.center(20, '-'))
    policy_wes, q = monte_carlo_with_exploring_start(env)
    v = q.max(axis=-1)
    plot(policy_wes.argmax(-1))
    plot(v)

最优策略图像,
在这里插入图片描述
最优价值图像,
在这里插入图片描述

4.8.4 off-policy策略评估

接下来考虑off-policy算法。以下代码给出了基于重要性采样的策略评估求解动作价值函数。函数evaluate_action_monte_carlo_importance_resample()不仅有表示目标策略的参数policy,还有表示行为策略的参数behavior_policy。在回合更新的过程中,为了有效地更新重要性采样比率,所以需要采用逆序更新。

# 重要性采样策略评估
def evaluate_monte_carlo_importance_resample(env, policy, behavior_policy, episode_num=500000):
    q = np.zeros_like(policy)
    c = np.zeros_like(policy)
    for _ in range(episode_num):
        # 用行为策略玩一回合
        state_actions = []
        observation = env.reset()
        while True:
            state = ob2state(observation)
            action = np.random.choice(env. action_space.n, p=behavior_policy[state])
            state_actions.append((state, action))
            observation, reward, done, _ = env.step(action)
            if done:
                break  # 玩好了
        g = reward  # 回报
        rho = 1.  # 重要性采样比率
        for state, action in reversed(state_actions):
            c[state][action] += rho
            q[state][action] += (rho / c[state][action]*(g - q[state][action]))
            rho *= (policy[state][action]/ behavior_policy[state][action])
            if rho == 0:
                break  # 提前终止
    return q

evaluate_action_monte_carlo_importance_resample()函数的用法如下列代码所示。其中的行为策略是 π ( a ∣ s ) = 0.5 π(a|s)=0.5 π(as)=0.5 s ∈ S , a ∈ A s∈\mathcal{S},a∈\mathcal{A} sS,aA)。该代码可以生成与on-policy回合更新一致的结果。

policy = np.zeros((22, 11, 2, 2))
    policy[20:, :, :, 0] = 1  # >= 20时收手
    policy[:20, :, :, 1] = 1  # <20时继续
    behavior_policy = np.ones_like(policy) * 0.5
    q = evaluate_monte_carlo_importance_resample(env, policy, behavior_policy)
    v = (q * policy).sum(axis=-1)
    plot(v)

在这里插入图片描述

4.8.5 off-policy最优策略求解

最后来看off-policy回合更新的最优策略求解。代码清单4-8给出了基于重要性采样的最优策略求解。在函数的初始化阶段确定了行为策略为 π ( a ∣ s ) = 0.5 ( s ∈ S , a ∈ A ) π(a|s)=0.5(s∈\mathcal{S},a∈\mathcal{A}) π(as)=0.5sS,aA,这是一个柔性策略。在后续的回合更新中,无论目标策略如何更新,都使用这个策略作为行为策略。在更新阶段,同样使用逆序来有效更新重要性采样比率。

# 柔性策略重要性采样最优策略求解
def monte_carlo_importance_resample(env, episode_num=500000):
    policy = np.zeros((22, 11, 2, 2))
    policy[:, :, :, 0] = 1.
    behavior_policy = np.ones_like(policy) * 0.5  # 柔性策略
    q = np.zeros_like(policy)
    c = np.zeros_like(policy)
    for _ in range(episode_num):
        # 用行为策略玩一回合
        state_actions = []
        observation = env.reset()
        while True:
            state = ob2state(observation)
            action = np.random.choice(env.action_space.n, p=behavior_policy[state])
            state_actions.append((state, action))
            observation, reward, done, _= env.step(action)
            if done:
                break  # 玩好了
        g = reward  # 回报
        rho = 1.  # 重要性采样比率
        for state, action in reversed(state_actions):
            c[state][action] += rho
            q[state][action] += (rho / c[state][action]*(g - a[state][action]))
            # 策略改进
            a = q[state].argmax()
            policy[state] = 0.
            policy[state][a] = 1.
            if a != action:  # 提前终止
                break
        rho /= behavior_policy[state][action]
    return policy, q

利用monte_carlo_importance_resample()函数求解最优策略和最优价值函数的用法如下所示。该代码可以生成和on-policy回合更新一致的结果。

    policy, q = monte_carlo_importance_resample(env)
    v = q.max(axis=-1)
    plot(policy.argmax(-1))
    plot(v)
4.8.6 蒙特卡洛方法完整代码

完整代码如下

import gym
import numpy as np
import matplotlib.pyplot as plt


# 从观测到状态
def ob2state(observation):
    return(observation[0], observation[1], int(observation[2]))


# 回合更新策略评估
def evaluate_action_monte_carlo(env, policy, episode_num=50000):
    q = np.zeros_like(policy)
    c = np.zeros_like(policy)
    for _ in range(episode_num):
        # 玩一回合
        state_actions = []
        observation = env.reset()
        while True:
            state = ob2state(observation)
            action = np.random.choice(env.action_space.n, p=policy[state])
            state_actions.append((state, action))
            observation, reward, done, _ = env.step(action)
            if done:
                break
        g = reward
        for state, action in state_actions:
            c[state][action] += 1.
            q[state][action] += (g - q[state][action]) / c[state][action]
    return q


# 绘制最后一维的指标为0或1的3维数组
def plot(data):
    fig, axes = plt.subplots(1, 2, figsize=(9, 4))
    titles = ['without ace', 'with ace']
    have_aces = [0, 1]
    extent = [12, 22, 1, 11]
    for title, have_ace, axis in zip(titles, have_aces, axes):
        dat = data[extent[0]:extent[1], extent[2]:extent[3], have_ace].T
        axis.imshow(dat, extent=extent, origin='lower')
        axis.set_xlabel('player sum')
        axis.set_ylabel('dealer showing')
        axis.set_title(title)
    plt.show()


# 带起始探索的回合更新
def monte_carlo_with_exploring_start(env, episode_num=500000):
    policy = np. zeros((22, 11, 2, 2))
    policy[:, :, :, 1] = 1.
    q = np.zeros_like(policy)
    c = np.zeros_like(policy)
    for _ in range(episode_num):
        # 随机选择起始状态和起始动作
        state = (np.random.randint(12, 22),
                 np.random.randint(1, 11),
                 np.random.randint(2))
        action = np.random.randint(2)
        # 玩一回合
        env.reset()
        if state[2]:  # 有A
            env.player = [1, state[0] - 11]
        else:  # 没有A
            if state[0] == 21:
                env.player = [10, 9, 2]
            else:
                env.player = [10, state[0] - 10]
        env.dealer[0] = state[1]
        state_actions = []
        while True:
            state_actions. append((state, action))
            observation, reward, done, _ = env. step(action)
            if done:
                break  # 回合结束
            state = ob2state(observation)
            action = np.random.choice(env.action_space.n, p=policy[state])
        g = reward  # 回报
        for state, action in state_actions:
            c[state][action] += 1.
            q[state][action] += (g - q[state][action]) / c[state][action]
            a = q[state].argmax()
            policy[state] = 0.
            policy[state][a] = 1.
    return policy, q


# 重要性采样策略评估
def evaluate_monte_carlo_importance_resample(env, policy, behavior_policy, episode_num=500000):
    q = np.zeros_like(policy)
    c = np.zeros_like(policy)
    for _ in range(episode_num):
        # 用行为策略玩一回合
        state_actions = []
        observation = env.reset()
        while True:
            state = ob2state(observation)
            action = np.random.choice(env. action_space.n, p=behavior_policy[state])
            state_actions.append((state, action))
            observation, reward, done, _ = env.step(action)
            if done:
                break  # 玩好了
        g = reward  # 回报
        rho = 1.  # 重要性采样比率
        for state, action in reversed(state_actions):
            c[state][action] += rho
            q[state][action] += (rho / c[state][action]*(g - q[state][action]))
            rho *= (policy[state][action]/ behavior_policy[state][action])
            if rho == 0:
                break  # 提前终止
    return q


# 柔性策略重要性采样最优策略求解
def monte_carlo_importance_resample(env, episode_num=500000):
    policy = np.zeros((22, 11, 2, 2))
    policy[:, :, :, 0] = 1.
    behavior_policy = np.ones_like(policy) * 0.5  # 柔性策略
    q = np.zeros_like(policy)
    c = np.zeros_like(policy)
    for _ in range(episode_num):
        # 用行为策略玩一回合
        state_actions = []
        observation = env.reset()
        while True:
            state = ob2state(observation)
            action = np.random.choice(env.action_space.n, p=behavior_policy[state])
            state_actions.append((state, action))
            observation, reward, done, _= env.step(action)
            if done:
                break  # 玩好了
        g = reward  # 回报
        rho = 1.  # 重要性采样比率
        for state, action in reversed(state_actions):
            c[state][action] += rho
            q[state][action] += (rho / c[state][action]*(g - q[state][action]))
            # 策略改进
            a = q[state].argmax()
            policy[state] = 0.
            policy[state][a] = 1.
            if a != action:  # 提前终止
                break
        rho /= behavior_policy[state][action]
    return policy, q


# 主函数
if __name__ == '__main__':
    env = gym.make("Blackjack-v0")
    observation = env.reset()
    print('观测 = {}'.format(env.player, env.dealer))
    while True:
        print('玩家 = {}, 庄家 = {}'.format(env.player, env.dealer))
        action = np.random.choice(env.action_space.n)
        print('动作 = {}'.format(action))
        observation, reward, done, _ = env.step(action)
        print('观测 = {}, 奖励 = {}, 结束指示 = {}\n'.format(observation, reward, done))

        if done:
            break
    '''
    # 定义一个确定性算法并评估
    print('评估一个确定的随机算法'.center(20, '-'))
    policy_certain = np.zeros((22, 11, 2, 2))
    policy_certain[20:, :, :, 0] = 1  # >20时不再要牌
    policy_certain[:20, :, :, 1] = 1  # <20时不再要牌
    q = evaluate_action_monte_carlo(env, policy_certain)  # 动作价值
    v = (q * policy_certain).sum(axis=-1)
    plot(v)

    # 带起始探索的更新策略
    print('带起始探索的更新策略'.center(20, '-'))
    policy_wes, q = monte_carlo_with_exploring_start(env)
    v = q.max(axis=-1)
    plot(policy_wes.argmax(-1))
    plot(v)

    '''
    print('off-policy策略评估'.center(20, '-'))
    policy = np.zeros((22, 11, 2, 2))
    policy[20:, :, :, 0] = 1  # >= 20时收手
    policy[:20, :, :, 1] = 1  # <20时继续
    behavior_policy = np.ones_like(policy) * 0.5
    q = evaluate_monte_carlo_importance_resample(env, policy, behavior_policy)
    v = (q * policy).sum(axis=-1)
    plot(v)

    policy, q = monte_carlo_importance_resample(env)
    v = q.max(axis=-1)
    plot(policy.argmax(-1))
    plot(v)

4.9 21点的其它示例程序

以下提供了另一种使用蒙特卡洛方法解决21点的程序.

import sys
import gym
import numpy as np
from collections import defaultdict  # default函数返回一个默认值

from plot_utils import plot_blackjack_values, plot_policy


# Part1: MC Prediction
def generate_spisode_from_limit_stochastic(bj_env):
    episode = []
    state = bj_env.reset()
    while True:
        probs = [0.8, 0.2] if state[0] else [0.2, 0.8]
        action = np.random.choice(np.arange(2), p=probs)  # 以prods的概率分布选择动作
        next_state, reward, done, info = bj_env.step(action)
        # 返回状态, 动作, 回报
        episode.append((state, action, reward))
        state = next_state
        if done:
            break
    return episode


def mc_prediction_q(env, num_episodes, generate_episode, gamma=1.0):
    # initialize empty dictionaries fo arrays
    returns_sum = defaultdict(lambda: np.zeros(env.action_space.n))  # lambda定义了一个匿名函数
    N = defaultdict(lambda: np.zeros(env.action_space.n))
    Q = defaultdict(lambda: np.zeros(env.action_space.n))
    # loop over episodes
    for i_episode in range(1, num_episodes+1):
        # 过程跟踪
        if i_episode % 1000 == 0:
            print("\rEpisode {}/{}".format(i_episode, num_episodes), end="")
            sys.stdout.flush()  # 使缓存区显示输出
        # 生成一个事件
        episode = generate_episode(env)
        # 获得状态、行动和奖励
        states, actions, rewards = zip(*episode)  # 解压episode
        # 返回折扣率
        discounts = np.array([gamma**i for i in range(len(rewards)+1)])
        # 更新事件中返回的总和、访问次数和对所有状态-动作对的动作-价值函数估计
        for i, state in enumerate(states):
            returns_sum[state][actions[i]] += sum(rewards[i:]*discounts[:-(1+i)])
            N[state][actions[i]] += 1.0
            Q[state][actions[i]] = returns_sum[state][actions[i]] / N[state][actions[i]]
    return Q


# Part2: MC Control
def get_probs(Q_s, epsilon, nA):
    """ 得到与epsilon-贪婪策略对应的动作概率"""
    policy_s = np.ones(nA) * epsilon / nA
    best_a = np.argmax(Q_s)
    policy_s[best_a] = 1 - epsilon + (epsilon/nA)
    return policy_s


def update_Q(env, episode, Q, alpha, gamma):
    """ 使用最近的事件更新动作-价值函数评估"""
    states, actions, rewards = zip(*episode)
    # prepare for discounting
    discounts = np.array([gamma**i for i in range(len(rewards)+1)])
    for i, state in enumerate(states):
        old_Q = Q[state][actions[i]]
        Q[state][actions[i]] = old_Q + alpha*(sum(rewards[i:]*discounts[:-(1+i)]) - old_Q)
    return Q


def generate_episode_from_Q(env, Q, epsilon, nA):
    """ 根据执行epsilon-贪婪策略生成一个事件"""
    episode = []
    state = env.reset()
    while True:
        action = np.random.choice(np.array(nA), p=get_probs(Q[state], epsilon, nA)) \
            if state in Q else env.action_space.sample()
        next_state, reward, done, info = env.step(action)
        episode.append((state, action, reward))
        state = next_state
        if done:
            break
    return episode


def mc_control(env, num_episodes, alpha, gamma=1.0, eps_start=1.0, eps_decay=.9999, eps_min=0.05):
    # nA = 动作数
    nA = env.action_space.n
    # 初始化数组的空字典
    Q = defaultdict(lambda: np.zeros(nA))
    epsilon = eps_start
    for i_episode in range(1, num_episodes+1):
        # 监控进程
        if i_episode % 1000 == 0:
            print("\rEpisode {}/{}".format(i_episode, num_episodes), end="")
            sys.stdout.flush()
        # set the value of epsilon
        epsilon = max(epsilon*eps_decay, eps_min)
        # 根据epsilon-贪婪政策生成一个事件
        episode = generate_episode_from_Q(env, Q, epsilon, nA)
        # update the action-value function estimate using the episode
        Q = update_Q(env, episode, Q, alpha, gamma)
    # determine the policy corresponding to the final action-value function estimate
    policy = dict((k, np.argmax(v))for k, v in Q.items())

    return policy, Q


if __name__ == "__main__":
    env = gym.make('Blackjack-v0')

    print(env.observation_space)
    print(env.action_space)

    '''Part1: MC Prediction'''
    # 获得动作价值函数
    Q = mc_prediction_q(env, 50000, generate_spisode_from_limit_stochastic)

    # 获得相应的状态价值函数
    V_to_plot = dict((k, (k[0] > 18)*(np.dot([0.8, 0.2], v)) + (k[0] <= 18) * (np.dot([0.2, 0.8], v)))
                     for k, v in Q.items())

    # 状态价值函数图
    plot_blackjack_values(V_to_plot)

    '''Part2: MC Control'''
    # 获得估计的最优策略和行动-价值函数
    policy, Q = mc_control(env, 50000, 0.02)

    # 得到相应的状态值函数
    V = dict((k, np.max(v)) for k, v in Q.items())

    # 绘制状态值函数
    plot_blackjack_values(V)

    # 绘制相应的策略
    plot_policy(policy)

MC Prediction的状态价值为
在这里插入图片描述

MC Control的状态价值为
在这里插入图片描述

策略输出如下

在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐