ADPRL - 近似动态规划和强化学习 - Note 10 - 蒙特卡洛法和时序差分学习及其实例 (Monte Carlo and Temporal Difference)

Note 10 蒙特卡洛法和时序差分学习 Monte Carlo and Temporal Difference

前面几章重点讨论了解决DP中的维度诅咒问题,并强假设系统动态,即MDP模型中的状态转换概率,对智能体来说是可以完全访问的。不幸的是,这样的假设在大多数工程应用中是不现实的。因此,这种情况是 R L \mathrm{RL} RL的主要焦点,有时被称为DP中的模型诅咒(curse of model)。

本章研究了可以说是最基本的无模型RL算法,即时序差分(Temporal Difference, TD)学习。具体来说,我们首先在第10.1节中重温了TD学习的传统启发式观点,在第10.2节中扩展了TD的概念以发展著名的 S A R S A SARSA SARSA Q Q Q学习算法,并在第10.3节中发展了资格追踪的概念以提高TD算法的计算效率。

10.1 蒙特卡洛法和时序差分学习 (Monte Carlo and Temporal Difference)

时差的概念一直是研究和应用RL的中心。它的早期成功可以在1959年的计算机检查程序中找到。经过几十年的发展,它的理论开始趋于一致,并由Richard Sutton在1980年代在计算机科学的背景下提出。尽管TD学习的传统发展从学习的行为理论中得到了强烈的启发,但本节的重点是为这个概念提供一个直观的、但技术上简洁的介绍。

让我们从有限范围问题开始,回顾一下给定非确定性策略的有限范围问题的总成本函数的定义(在RL中被称为回报函数,或者奖励函数)。我们将一条轨迹的所有成本之和定义为

G π ( x 0 ) : = g N ( x N ) + ∑ k = 0 N − 1 g k ( x k , u k , x k + 1 ) (10.1) G_{\pi}\left(x_{0}\right):=g_{N}\left(x_{N}\right)+\sum_{k=0}^{N-1} g_{k}\left(x_{k}, u_{k}, x_{k+1}\right) \tag{10.1} Gπ(x0):=gN(xN)+k=0N1gk(xk,uk,xk+1)(10.1)

这可以被视为一个随机变量。那么总成本函数可以重写为

J π ( x 0 ) : = E p π ( χ , μ ) [ G π ( x 0 ) ] (10.2) J^{\pi}\left(x_{0}\right):=\mathbb{E}_{p_{\pi}(\chi, \mu)}\left[G_{\pi}\left(x_{0}\right)\right] \tag{10.2} Jπ(x0):=Epπ(χ,μ)[Gπ(x0)](10.2)
当概率密度函数 p π ( χ , μ ) p_{\pi}(\chi, \mu) pπ(χ,μ)没有给出时,一个实用的解决方案是采用蒙特卡洛方法。也就是说, 公式(10.2)中的期望值可以通过样本的经验平均值来近似为
J π ( x 0 ) ≈ J ^ π ( x 0 ) : = 1 N ∑ i = 1 N G π ( i ) ( x 0 ) (10.3) J^{\pi}\left(x_{0}\right) \approx \widehat{J}^{\pi}\left(x_{0}\right):=\frac{1}{N} \sum_{i=1}^{N} G_{\pi}^{(i)}\left(x_{0}\right) \tag{10.3} Jπ(x0)J π(x0):=N1i=1NGπ(i)(x0)(10.3)

这里,上标 ( i ) (i) (i)指的是采样轨迹的数量。根据大数定律,对期望值的准确估计需要样本的数量变得足够大。显然,当范围 N N N很大时,计算这样一个算术平均值会变得非常昂贵。因此,实际要求有一个在线更新,这样经验平均数可以以增量方式有效计算。

大数定理:
{ x i } i = 1 ∞ \left\{x_{i}\right\}_{i=1}^{\infty} { xi}i=1是一串独立同分布的随机变量,从而 E [ x i ] = μ \mathbb{E}\left[x_{i}\right]=\mu E[xi]=μ and Var ⁡ ( x i ) < ∞ \operatorname{Var}\left(x_{i}\right)<\infty Var(xi)< , 那么几乎可以肯定 1 n ( x 1 + … + x n ) → μ ,  when  n → ∞ \frac{1}{n}\left(x_{1}+\ldots+x_{n}\right) \rightarrow \mu, \quad \text { when } n \rightarrow \infty n1(x1++xn)μ, when n

蒙特卡洛方法:
任何使用随机性和大数法则来计算某个数量的方法可能与随机性无关的方法都是蒙特卡洛方法。

为了开发这个技巧,我们用下面这个简单的例子来说明。假设 x ∈ R x\in \mathbb{R} xR是一个随机变量,具有固定分布 p ( x ) p(x) p(x) x i x_{i} xi是从 p ( x ) p(x) p(x)中抽取的独立同分布的样本。那么, n n n样本的经验平均数可计算为以下算术平均数

x ^ n = 1 n ∑ i = 1 n x i (10.4) \widehat{x}_{n}=\frac{1}{n} \sum_{i=1}^{n} x_{i} \tag{10.4} x n=n1i=1nxi(10.4)

给定一个新的样本 x n + 1 x_{n+1} xn+1 n + 1 n+1 n+1样本的经验平均值可以进一步重新计算为
x ^ n + 1 = 1 n + 1 ∑ i = 1 n + 1 x i = 1 n + 1 ( ∑ i = 1 n x i + x n + 1 ) = 1 n + 1 ( n x ^ n + x ^ n + x n + 1 − x ^ n ) = x ^ n + 1 n + 1 ( x n + 1 − x ^ n ) . (10.5) \begin{aligned} \widehat{x}_{n+1} &=\frac{1}{n+1} \sum_{i=1}^{n+1} x_{i} \\ &=\frac{1}{n+1}\left(\sum_{i=1}^{n} x_{i}+x_{n+1}\right) \\ &=\frac{1}{n+1}\left(n \widehat{x}_{n}+\widehat{x}_{n}+x_{n+1}-\widehat{x}_{n}\right) \\ &=\widehat{x}_{n}+\frac{1}{n+1}\left(x_{n+1}-\widehat{x}_{n}\right) . \end{aligned} \tag{10.5} x n+1=n+11i=1n+1xi=n+11(i=1nxi+xn+1)=n+11(nx n+x n+xn+1x n)=x n+n+11(xn+1x n).(10.5)

显然, n + 1 n+1 n+1样本的经验平均数 x ^ n + 1 \widehat{x}_{n+1} x n+1可以简单地利用之前对 n n n样本的估计 x ^ n \widehat{x}_{n} x n和新的观测值 x n + 1 x_{n+1} xn+1来更新。因此,对于有限范围问题,总成本函数 J π J^{\pi} Jπ可以通过应用以下更新规则来在线估计
J k + 1 ( x 0 ) = J k ( x 0 ) + 1 k ( G π ( k + 1 ) ( x 0 ) − J k ( x 0 ) ) (10.6) J_{k+1}\left(x_{0}\right)=J_{k}\left(x_{0}\right)+\frac{1}{k}\left(G_{\pi}^{(k+1)}\left(x_{0}\right)-J_{k}\left(x_{0}\right)\right) \tag{10.6} Jk+1(x0)=Jk(x0)+k1(Gπ(k+1)(x0)Jk(x0))(10.6)

其中指数 k k k指的是以 x 0 x_{0} x0开始的样本轨迹的数量。这一更新规则被称为有限范围问题的蒙特卡洛策略评估(Monte Carlo policy evaluation) 算法。

显然,对于无限范围问题来说,对完整轨迹进行抽样的概念已经不可能了。然而,通过探索同样的技巧,我们提出了无限范围背景下的的TD学习算法,并略带启发式的思考方法。我们没有利用价值函数的定义,而是将每个单独状态的贝尔曼方程替换为

J π ( x ) = E p π ( x ′ ∣ x ) [ g ( x , π ( x ) , x ′ ) + γ J π ( x ′ ) ] . (10.7) J^{\pi}(x)=\mathbb{E}_{p_{\pi}\left(x^{\prime} \mid x\right)}\left[g\left(x, \pi(x), x^{\prime}\right)+\gamma J^{\pi}\left(x^{\prime}\right)\right] . \tag{10.7} Jπ(x)=Epπ(xx)[g(x,π(x),x)+γJπ(x)].(10.7)

鉴于对总成本函数 J ∈ R K J \in \mathbb{R}^{K} JRK的估计,期望下的和 g ( x , u , x ′ ) + γ J ( x ′ ) g\left(x, u, x^{\prime}\right)+\gamma J\left(x^{\prime}\right) g(x,u,x)+γJ(x)可以被类似地当作一个 “随机变量”。让我们用 x x x x ′ x^{\prime} x分别表示一个初始状态和它的连续状态。然后,通过增量更新规则的相同技巧,可以得出以下更新规则
J k + 1 ( x ) = J k ( x ) + α k ( g ( x , u , x ′ ) + γ J k ( x ′ ) − J k ( x ) ) , (10.8) J_{k+1}(x)=J_{k}(x)+\alpha_{k}\left(g\left(x, u, x^{\prime}\right)+\gamma J_{k}\left(x^{\prime}\right)-J_{k}(x)\right), \tag{10.8} Jk+1(x)=Jk(x)+αk(g(x,u,x)+γJk(x)Jk(x)),(10.8)

其中 u = π ( x ) u=\pi(x) u=π(x), α k > 0 \alpha_{k}>0 αk>0被称为学习率。该更新规则被称为TD学习规则,详见算法11。

在这里插入图片描述

递增式更新
δ ( x , u , x ′ ) : = g ( x , u , x ′ ) + γ J k ( x ′ ) − J k ( x ) (10.9) \delta\left(x, u, x^{\prime}\right):=g\left(x, u, x^{\prime}\right)+\gamma J_{k}\left(x^{\prime}\right)-J_{k}(x) \tag{10.9} δ(x,u,x):=g(x,u,x)+γJk(x)Jk(x)(10.9)

通常被称为TD误差。

需要注意的是,公式(10.8)中定义的TD更新不是蒙特卡洛方法,因为总成本函数 J k J_{k} Jk只是总成本函数 J π J^{\pi} Jπ的估计,因此 g ( x , u , x ′ ) + γ J k ( x ′ ) g\left(x, u, x^{\prime}\right)+\gamma J_{k}\left(x^{\prime}\right) g(x,u,x)+γJk(x)不是某个随机变量的即定样本。换句话说,TD更新规则是在另一个估计的基础上产生一个估计。这种技巧在RL的背景下被称为自助法(bootstrapping )。作为续集,学习率通常被选择为 α k ∈ ( 0 , 1 ) \alpha_{k}\in(0,1) αk(0,1),以补偿自助法引起的潜在波动。更具体地说,学习率通常满足Robbins-Monro条件,这是适应学习率的最常见策略[33],即。
∑ k = 1 ∞ α k = ∞ ,  and  ∑ k = 1 ∞ α k 2 < ∞ (10.10) \sum_{k=1}^{\infty} \alpha_{k}=\infty, \text { and } \sum_{k=1}^{\infty} \alpha_{k}^{2}<\infty \tag{10.10} k=1αk=, and k=1αk2<(10.10)
一个满足Robbins-Monro条件的学习率的简单例子是
α k = 1 k ,  for  k ∈ N (10.11) \alpha_{k}=\frac{1}{k}, \quad \text { for } k \in \mathbb{N} \tag{10.11} αk=k1, for kN(10.11)

那么公式(10.10)中的第一个和成为谐波数列的和,已知是有界的,而公式(10.10)中的第二个和是指巴塞尔问题,已知是有界的。

图16描述了TD学习算法和蒙特卡洛策略评估算法之间的区别。

在这里插入图片描述
图16:蒙特卡洛和TD算法的比较。TD算法访问了所有的状态,并更新了过去访问过的所有状态,而Monte Carlo算法只在达到终端状态时才更新了所有的状态。

显然,TD学习算法更新了过去访问过的所有状态,而蒙特卡洛算法只更新了起始状态。尽管TD学习不是蒙特卡洛方法,但有趣的是注意到TD学习算法渐进地收敛于总成本函数 J π J^{\pi} Jπ。这种神秘的理论依据将在下一章提供。

10.2 Q中的TD学习(TD Learning in Q Q Q

T D ( 0 ) \mathrm{TD}(0) TD(0)算法的发展类似,两个对应的带有 Q Q Q函数的TD学习算法也可以直接发展为
Q k + 1 ( x , u ) = Q k ( x , u ) + α k ( g ( x , u , x ′ ) + γ Q k ( x ′ , u ′ ) − Q k ( x , u ) ) , (10.12) Q_{k+1}(x, u)=Q_{k}(x, u)+\alpha_{k}\left(g\left(x, u, x^{\prime}\right)+\gamma Q_{k}\left(x^{\prime}, u^{\prime}\right)-Q_{k}(x, u)\right), \tag{10.12} Qk+1(x,u)=Qk(x,u)+αk(g(x,u,x)+γQk(x,u)Qk(x,u)),(10.12)

其中 u ′ = π ( x ′ ) u^{\prime}=\pi\left(x^{\prime}\right) u=π(x),并且
Q k + 1 ( x , u ) = Q k ( x , u ) + α k ( g ( x , u , x ′ ) + γ min ⁡ u ′ Q k ( x ′ , u ′ ) − Q k ( x , u ) ) . (10.13) Q_{k+1}(x, u)=Q_{k}(x, u)+\alpha_{k}\left(g\left(x, u, x^{\prime}\right)+\gamma \min _{u^{\prime}} Q_{k}\left(x^{\prime}, u^{\prime}\right)-Q_{k}(x, u)\right) . \tag{10.13} Qk+1(x,u)=Qk(x,u)+αk(g(x,u,x)+γuminQk(x,u)Qk(x,u)).(10.13)

前者的更新规则被称为 S A R S A S A R S A SARSA算法,它执行策略 π \pi π的策略评估,而后者被称为Q-学习算法(算法12),它计算最佳状态-行动总成本函数 Q ∗ Q^{*} Q
在这里插入图片描述

下一章将提供关于这些TD学习算法的渐进收敛的更多讨论。

10.3 资格迹(Eligibility trace)

在第10.1节,我们提出了TD学习概念的启发式直觉。尽管算法简单,但原始的TD框架仍有一个主要弱点。

也就是说,当一个TD代理与它的环境互动时,智能体只更新相应状态的总成本函数。特别是,当状态的数量变得巨大时,由于维度的诅咒,TD算法的效率往往非常低。因此,需要一种实用的技术来更新智能体与其环境之间一次互动中多个状态的总成本函数。

资格迹的概念可以通过将第10.1节中开发TD算法的相同策略应用于 m m m步贝尔曼算子来发展。让我们回顾一下公式(4.33)中的 m m m步贝尔曼算子 T π m \mathrm{T}_{\pi}^{m} Tπm,并用以下方式表示一个有限轨迹

h x 0 ( m ) : = { x 0 , u 0 , … , x m − 1 , u m − 1 , x m } . (10.14) h_{x_{0}}^{(m)}:=\left\{x_{0}, u_{0}, \ldots, x_{m-1}, u_{m-1}, x_{m}\right\} . \tag{10.14} hx0(m):={ x0,u0,,xm1,um1,xm}.(10.14)

那么, m m m步贝尔曼算子的计算方法为

T π m J ( x 0 ) = E p π ( h ( m ) ) [ ∑ k = 0 m − 1 γ k g ( x k , u k , x k + 1 ) + γ m J ( x m ) ⏟ = : G π ( h x 0 ( m ) ) ] , (10.15) \mathrm{T}_{\pi}^{m} J\left(x_{0}\right)=\mathbb{E}_{p_{\pi}\left(h^{(m)}\right)}[\underbrace{\sum_{k=0}^{m-1} \gamma^{k} g\left(x_{k}, u_{k}, x_{k+1}\right)+\gamma^{m} J\left(x_{m}\right)}_{=: G_{\pi}\left(h_{x_{0}}^{(m)}\right)}], \tag{10.15} TπmJ(x0)=Epπ(h(m))[=:Gπ(hx0(m)) k=0m1γkg(xk,uk,xk+1)+γmJ(xm)],(10.15)

其中和 G π ( h x 0 ( m ) ) G_{\pi}\left(h_{x_{0}}^{(m)}\right) Gπ(hx0(m))在RL的背景下通常被称为 m m m步的返回。这样就可以直接得到一个 m m m步的TD学习算法,即
J k + 1 ( x 0 ) = J k ( x 0 ) + α k ( G π ( h x 0 ( m ) ) − J k ( x 0 ) ) (10.16) J_{k+1}\left(x_{0}\right)=J_{k}\left(x_{0}\right)+\alpha_{k}\left(G_{\pi}\left(h_{x_{0}}^{(m)}\right)-J_{k}\left(x_{0}\right)\right) \tag{10.16} Jk+1(x0)=Jk(x0)+αk(Gπ(hx0(m))Jk(x0))(10.16)

很明显,这样的算法可能是相当低效的。也就是说,如果步长 m m m很大,在交互过程中会有很长一段时间没有更新。现在让我们仔细看看 m m m步TD更新或 m m m步TD误差为

G π ( h x 0 ( m ) ) − J ( x 0 ) = ∑ k = 0 m − 1 γ k g ( x k , u k , x k + 1 ) + γ m J ( x m ) − J ( x 0 ) = ∑ k = 0 m − 2 γ k g ( x k , u k , x k + 1 ) + γ m − 1 ( g ( x m − 1 , u m − 1 , x m ) + γ J ( x m ) ) − − γ m − 1 J ( x m − 1 ) + γ m − 1 J ( x m − 1 ) − J ( x 0 ) = ∑ k = 0 m − 2 γ k g ( x k , u k , x k + 1 ) + γ m − 1 J ( x m − 1 ) − J ( x 0 ) + γ m − 1 δ ( x m − 1 , u m − 1 , x m ) = ∑ k = 0 m − 1 γ k δ ( x k , u k , x k + 1 ) , (10.17) \begin{aligned} &G_{\pi}\left(h_{x_{0}}^{(m)}\right)-J\left(x_{0}\right)\\ &=\sum_{k=0}^{m-1} \gamma^{k} g\left(x_{k}, u_{k}, x_{k+1}\right)+\gamma^{m} J\left(x_{m}\right)-J\left(x_{0}\right)\\ &=\sum_{k=0}^{m-2} \gamma^{k} g\left(x_{k}, u_{k}, x_{k+1}\right)+\gamma^{m-1}\left(g\left(x_{m-1}, u_{m-1}, x_{m}\right)+\gamma J\left(x_{m}\right)\right)-\\ &-\gamma^{m-1} J\left(x_{m-1}\right)+\gamma^{m-1} J\left(x_{m-1}\right)-J\left(x_{0}\right)\\ &=\sum_{k=0}^{m-2} \gamma^{k} g\left(x_{k}, u_{k}, x_{k+1}\right)+\gamma^{m-1} J\left(x_{m-1}\right)-J\left(x_{0}\right)+\gamma^{m-1} \delta\left(x_{m-1}, u_{m-1}, x_{m}\right)\\ &=\sum_{k=0}^{m-1} \gamma^{k} \delta\left(x_{k}, u_{k}, x_{k+1}\right), \end{aligned}\tag{10.17} Gπ(hx0(m))J(x0)=k=0m1γkg(xk,uk,xk+1)+γmJ(xm)J(x0)=k=0m2γkg(xk,uk,xk+1)+γm1(g(xm1,um1,xm)+γJ(xm))γm1J(xm1)+γm1J(xm1)J(x0)=k=0m2γkg(xk,uk,xk+1)+γm1J(xm1)J(x0)+γm1δ(xm1,um1,xm)=k=0m1γkδ(xk,uk,xk+1),(10.17)

其中 m m m步TD更新可以被计算为随时间变化的折扣的TD误差之和。作为续篇,公式 ( 10.16 ) (10.16) (10.16)中的 m m m步TD学习更新可以重写为
J ( x 0 ) = J ( x 0 ) + α ( ∑ k = 0 m − 1 γ k δ ( x k , u k , x k + 1 ) ) (10.18) J\left(x_{0}\right)=J\left(x_{0}\right)+\alpha\left(\sum_{k=0}^{m-1} \gamma^{k} \delta\left(x_{k}, u_{k}, x_{k+1}\right)\right) \tag{10.18} J(x0)=J(x0)+α(k=0m1γkδ(xk,uk,xk+1))(10.18)

其中 m m m步的更新被表示为随时间变化的几何折扣的TD误差。让我们考虑一个 x 0 , x 1 , x 2 , … , x m x_{0}, x_{1}, x_{2}, \ldots, x_{m} x0,x1,x2,,xm的历史。一个直接的实现将简单地执行更新,一旦达到 m m m步。显然,这样的策略仍然是不切实际的。通过观察每个TD误差的大小取决于起始时间步骤和当前过渡之间的时间步骤,很容易构建一个所有状态随时间变化的标量值函数,称为资格迹(eligibility trace)

让我们初始化时间步长 t = 0 t=0 t=0,并设定 e t ( x ) = 0 e_{t}(x)=0 et(x)=0用于所有 x ∈ X x \in \mathcal{X} xX。在从 x t x_{t} xt x t + 1 x_{t+1} xt+1的一次转换之后,所有的资格迹都被更新为
e t ( x ) : = { γ e t − 1 ( x ) ,  if  x ≠ x t 1 ,  if  x = x t (10.18) e_{t}(x):= \begin{cases}\gamma e_{t-1}(x), & \text { if } x \neq x_{t} \\ 1, & \text { if } x=x_{t}\end{cases} \tag{10.18} et(x):={ γet1(x),1, if x=xt if x=xt(10.18)

这种策略通常被称为替换迹(replacing trace)。相应的算法通常被称为 TD ⁡ ( 1 ) \operatorname{TD}(1) TD(1)算法。如果我们假设由交互作用引起的马尔科夫链是各态历经的,那么重复访问的状态的资格迹将被更频繁地重置。换句话说,当智能体和环境之间的互动继续进行时, T D ( 1 ) \mathrm{TD}(1) TD(1)算法会同时更新所有被访问状态的价值函数。

最后,这样的概念可以进一步扩展到公式(5.10)中定义的 λ \lambda λ-几何平均的贝尔曼算子。通过结合公式(10.17)中给出的 m m m步TD误差和公式(5.10)中定义的 λ \lambda λ几何平均,在初始状态 x 0 x_{0} x0 λ \lambda λ几何平均TD更新可以被表示为
J ( x 0 ) = J ( x 0 ) + α k ( ∑ k = 0 ∞ λ k ∑ t = 0 k γ t δ ( x t , u t , x t + 1 ) ) (10.19) J\left(x_{0}\right)=J\left(x_{0}\right)+\alpha_{k}\left(\sum_{k=0}^{\infty} \lambda^{k} \sum_{t=0}^{k} \gamma^{t} \delta\left(x_{t}, u_{t}, x_{t+1}\right)\right) \tag{10.19} J(x0)=J(x0)+αk(k=0λkt=0kγtδ(xt,ut,xt+1))(10.19)

更具体地说,让我们考虑 { x 0 , u 0 , … , x m − 2 , u m − 2 , x m − 1 } \left\{x_{0}, u_{0}, \ldots, x_{m-2}, u_{m-2}, x_{m-1}\right\} { x0,u0,,xm2,um2,xm1}的历史。在 ( x m − 1 , u m − 1 , x m ) \left(x_{m-1}, u_{m-1}, x_{m}\right) (xm1,um1,xm)的转换之后,总成本函数 J ( x 0 ) J\left(x_{0}\right) J(x0) 的估计得到 λ m γ m δ ( x m − 1 , u m − 1 , x m ) \lambda^{m} \gamma^{m} \delta\left(x_{m-1}, u_{m-1}, x_{m}\right) λmγmδ(xm1,um1,xm)的更新, 作为从当前第 m m m个过渡到初始状态 x 0 x_{0} x0的少部分贡献。因此,我们可以进一步引入一个额外的参数来调整得到的TD误差

e t ( x ) : = { λ γ e t − 1 ( x ) ,  if  x ≠ x t 1 ,  if  x = x t (10.20) e_{t}(x):= \begin{cases}\lambda \gamma e_{t-1}(x), & \text { if } x \neq x_{t} \\ 1, & \text { if } x=x_{t}\end{cases} \tag{10.20} et(x):={ λγet1(x),1, if x=xt if x=xt(10.20)

TD ⁡ ( λ ) \operatorname{TD}(\lambda) TD(λ) 算法在算法13中给出。
在这里插入图片描述

图17说明了TD ( λ ) (\lambda) (λ)更新的机制。
在这里插入图片描述

图17: TD ⁡ ( λ ) \operatorname{TD}(\lambda) TD(λ)更新的说明。让我们用 δ k : = \delta_{k}:= δk:= δ ( x k , u k , x k + 1 ) \delta\left(x_{k}, u_{k}, x_{k+1}\right) δ(xk,uk,xk+1)表示时间差。TD对过去相应状态的更新幅度取决于当前时间戳和要更新的状态之间的时间步骤。

10.4 实例:网格世界中的蒙特卡洛策略评估和 T D ( 0 ) \mathrm{TD}(0) TD(0)算法

如下图所示,一个基准环境是一个有11个状态和一个障碍物的网格。智能体从左下角的 "开始 "状态出发,停在两个终端状态。
在这里插入图片描述

有四个可用的动作:上、下、左、右。每个动作都是随机的,一步的概率为0.8,两步的概率为0.2,都是期望的方向。所有转换的局部成本为0.04,终端状态的局部成本为1。

任务1:一旦给定了过渡概率,我们就可以像以前一样实现DP算法。对这个网格世界中所有状态的总成本实施价值迭代。

任务2:给定一个固定的策略,我们可以做MC策略评估。对所有状态的总成本实施MC PE。
任务3:给定一个固定策略,对所有状态的总成本实施TD ( 0 ) (0) (0)
任务4(奖励):实施SARSA和Q-learning。

字典(Dictionary) :keys() 函数以列表返回一个字典所有的键。

集合(set): 是一个无序的不重复元素序列。可以使用大括号 { } 或者 set() 函数创建集合,注意:创建一个空集合必须用 set() 而不是 { },因为 { } 是用来创建一个空字典。

reversed() 函数返回一个反转的迭代器。

reverse() 函数用于反向列表中元素。

10.4.1 环境搭建

首先对整个环境建立一个class。

class GridWorld: 
    def __init__(self, width=4, height=3, obstacle=[(1,1)]):
        self.width = width
        self.height = height

        self.obstacle = obstacle
        self.terminal = [(0, width-1), (1, width-1)]  # the terminal states are always at right top

        # the current state
        self.row = height - 1  # the start point is always at left bottom
        self.col = 0

        # define the MDP
        self.actions = self.act_space()
        self.states = set(self.actions.keys()) | set(self.terminal)
        self.J = self.init_J()
        self.local_cost = 0.04

    def act_space(self):
        """
        :return: generate all possible actions for each state, dictionary
        """
        act_space = {
    
    }

        for row in range(self.height):
            for col in range(self.width):
                possible_acts = []
                if (row, col) not in self.obstacle + self.terminal:
                    if row - 1 >= 0 and (row-1, col) not in self.obstacle:
                        possible_acts.append('U')
                    if row + 1 < self.height and (row+1, col) not in self.obstacle:
                        possible_acts.append('D')
                    if col - 1 >=0 and (row, col-1) not in self.obstacle:
                        possible_acts.append('L')
                    if col + 1 < self.width and (row, col+1) not in self.obstacle:
                        possible_acts.append('R')
                    act_space[(row, col)] = possible_acts
        return act_space

    def init_J(self, init_J_value=0):
        """
        initialise the cost function
        :param init_J_value:
        :return: cost in each state, dictionary
        """
        J = {
    
    }
        for row in range(self.height):
            for col in range(self.width):
                if (row, col) not in self.obstacle + self.terminal:
                    J[(row, col)] = init_J_value
        J[self.terminal[0]] = -1  # J(x_N) = g(x_N)
        J[self.terminal[1]] = +1
        return J

    def move(self, action, deterministic=False):
        """
        return the current cost after the move
        :param action: actions
        :param deterministic: if the action is deterministic
        :return: current cost
        """
        # check if legal move first
        if action in self.actions[(self.row, self.col)]:
            if action == 'U':
                # probablistic transition
                if deterministic or random.uniform(0,1) > 0.8 or (self.row-2, self.col) not in self.states:
                    self.row -= 1
                else:
                    self.row -= 2
            elif action == 'D':
                if deterministic or random.uniform(0,1) > 0.8 or (self.row+2, self.col) not in self.states:
                    self.row += 1
                else:
                    self.row += 2
            elif action == 'R':
                if deterministic or random.uniform(0,1) > 0.8 or (self.row, self.col+2) not in self.states:
                    self.col += 1
                else:
                    self.col += 2
            elif action == 'L':
                if deterministic or random.uniform(0,1) > 0.8 or (self.row, self.col-2) not in self.states:
                    self.col -= 1
                else:
                    self.col -= 2
        
        if (self.row, self.col) == self.terminal[0]:
            return -1
        elif (self.row, self.col) == self.terminal[1]:
            return +1
        else:
            return self.local_cost

    def set_state(self, s):
        self.row = s[0]
        self.col = s[1]

    def current_state(self):
        return (self.row, self.col)

    def game_over(self):  # MC
        return (self.row, self.col) not in self.actions

    def print_J(self):
        for row in range(self.height):
            print("---------------------------")
            for col in range(self.width):
                J = env.J.get((row, col), 0)
                if J >= 0:
                    print(" %.2f|" % J, end="")
                else:
                    print("%.2f|" % J, end="") 
            print("")
        print("---------------------------")

10.4.2 Task 1: DP 算法

## %% Task 1: Value Iteration
env = GridWorld()
gamma = 0.9

while True:
    infty_norm = 0
    for x in env.actions:  # update total costs for all the states except terminals
        # actions is a dict whose keys are states except terminals
        old_J = env.J[x]
        J_dict = {
    
    }
        for u in env.actions[x]:  # find the optimal action for T_g
            env.set_state(x)
            g = env.move(u, deterministic=True)
            J_ = 0.8 * (g + gamma * env.J[env.current_state()])
            if env.current_state() not in env.terminal:  # considering the SDM
                if u in env.actions[env.current_state()]:
                    g = env.move(u, deterministic=True)
                    J_ += 0.2 * (g + gamma * env.J[env.current_state()])
                else:
                    J_ += 0.2 * (g + gamma * env.J[env.current_state()])
            else:
                J_ += 0.2 * (g + gamma * env.J[env.current_state()])

            J_dict[u] = J_
        optimal_act = min(J_dict.keys(), key=(lambda k: J_dict[k]))  # optimal Bellman Eq
        env.J[x] = J_dict[optimal_act]

        infty_norm = max(infty_norm, abs(old_J - env.J[x]))

    if infty_norm < 1e-3:
        break

print("\nTotal cost by VI:")
env.print_J()

输出为:

Total cost by VI:
---------------------------
-1.54|-1.72|-1.90|-1.00|
---------------------------
-1.34| 0.00|-1.67| 1.00|
---------------------------
-1.20|-1.27|-1.50|-1.27|
---------------------------

10.4.3 Task 2: 蒙特卡洛MC算法

## Task 2: Monte Carlo Policy Evaluation
# %% Monte Carlo
def play_game(grid, policy):
    # start at random position to measure value for all states 
    start_states = list(grid.actions.keys())
    start_idx = np.random.choice(len(start_states))
    grid.set_state(start_states[start_idx])

    # generate the trajectroy
    x = grid.current_state()
    traj = [(x, 0)]
    while not grid.game_over():
        u = policy[x]
        g = grid.move(u)
        x = grid.current_state()
        traj.append((x, g))  # save the trajectory

    # calculate G_pi for each states along the trajectory
    G = 0
    G_x = []
    first = True
    for x, g in reversed(traj):
        if first:
            first = False
        else:
            G_x.append((x, G))
        G = g + gamma * G

    G_x.reverse()
    return G_x


env = GridWorld()

policy = {
    
    
    (2, 0): 'U',
    (1, 0): 'U',
    (0, 0): 'R',
    (0, 1): 'R',
    (0, 2): 'R',
    (1, 2): 'R',
    (2, 1): 'R',
    (2, 2): 'R',
    (2, 3): 'U',
}

# Under a fixed policy, the trajectory can be easily generated. Then calculate the expected cost for each state.
# set a dictionary to store the G of each state
G_pi_x = {
    
    }
for x in env.actions:
    G_pi_x[x] = []

for n in range(1, 200):
    G_pi_history = play_game(env, policy)
    for s, G in G_pi_history:
        G_pi_x[s].append(G)
        env.J[s] = np.mean(G_pi_x[s])

print("\nTotal cost by MC:")
env.print_J()

输出为

Total cost by MC:
---------------------------
-0.86|-0.98|-1.00|-1.00|
---------------------------
-0.73| 0.00| 1.00| 1.00|
---------------------------
-0.70|-0.53|-0.67|-0.73|
---------------------------

10.4.4 Task 3: 时序查分算法TD(0)

## task 3 : TD(0) algorithm
# %% TD
def play_game(grid, policy):
    start_states = list(grid.actions.keys())
    start_idx = np.random.choice(len(start_states))
    grid.set_state(start_states[start_idx])

    # generate traj
    x = grid.current_state()
    traj = [(x,0)]
    while not grid.game_over():
        u = policy[x]
        g = grid.move(u)
        x = grid.current_state()
        traj.append((x, g))  # save the trajectory
    return traj


gamma = 0.9
alpha = 0.1

policy = {
    
    
    (2, 0): 'U',
    (1, 0): 'U',
    (0, 0): 'R',
    (0, 1): 'R',
    (0, 2): 'R',
    (1, 2): 'R',
    (2, 1): 'R',
    (2, 2): 'R',
    (2, 3): 'U',
}

env = GridWorld()
for _ in range(200):
    traj_all = play_game(env, policy)

    for idx in range(len(traj_all) - 1):
        x, _ = traj_all[idx]
        x_, g = traj_all[idx+1]

        env.J[x] = env.J[x] + alpha * (g + gamma * env.J[x_] - env.J[x])

print ("\nTotal cost by TD")
env.print_J()

输出为

Total cost by TD
---------------------------
-1.63|-1.80|-1.90|-1.00|
---------------------------
-1.17| 0.00| 1.75| 1.00|
---------------------------
-0.86|-0.86|-0.85|-1.31|
---------------------------

10.4.5 SARSA 与Q-learning

待补。

猜你喜欢

转载自blog.csdn.net/qq_37266917/article/details/122484082