https://datawhalechina.github.io/easy-rl/#/chapter4/chapter4
https://zhuanlan.zhihu.com/p/141269134
https://zhuanlan.zhihu.com/p/106006748
策略一般写成 π \pi π。假设用深度学习的技术来做强化学习的话,策略就是一个网络。网络里面就有一堆参数,我们用 θ \theta θ 来代表 π \pi π 的参数。
网络的输入就是现在机器看到的东西,如果让机器打电玩的话,机器看到的东西就是游戏的画面。
输出的就是机器要采取什么样的行为。
首先,环境 是一个函数,游戏的主机也可以把它看作是一个函数,虽然它不一定是神经网络,可能是基于规则的(rule-based)的规则,但你可以把它看作是一个函数。这个函数一开始就先得到一个状态,也就是游戏的画面,接下来actor看到这个游戏画面 s 1 s_1 s1 以后,它吐出 a 1 a_1 a1,然后环境把 a 1 a_1 a1当作它的输入,然后它再吐出 s 2 s_2 s2,吐出新的游戏画面。演员看到新的游戏画面,再采取新的行为 a 2 a_2 a2,然后 环境再看到 a 2 a_2 a2,再吐出 s 3 s_3 s3。这个过程会一直持续下去,达到终止条件。
在一场游戏里面,我们把环境输出的 s s s 跟演员输出的行为 a a a,把 s s s 跟 a a a全部串起来, 叫做一个 Trajectory(轨迹),如下式所示。
Trajectory τ = { s 1 , a 1 , s 2 , a 2 , ⋯ , s t , a t } \text { Trajectory } \tau=\left\{s_{1}, a_{1}, s_{2}, a_{2}, \cdots, s_{t}, a_{t}\right\} Trajectory τ={s1,a1,s2,a2,⋯,st,at}
我们可以计算每一个轨迹发生的概率。假设现在演员的参数 θ \theta θ已经被给定了, 根据 θ \theta θ,我们可以计算某一个轨迹发生的概率。
p θ ( τ ) = p ( s 1 ) p θ ( a 1 ∣ s 1 ) p ( s 2 ∣ s 1 , a 1 ) p θ ( a 2 ∣ s 2 ) p ( s 3 ∣ s 2 , a 2 ) ⋯ = p ( s 1 ) ∏ t = 1 T p θ ( a t ∣ s t ) p ( s t + 1 ∣ s t , a t ) \begin{aligned} p_{\theta}(\tau) &=p\left(s_{1}\right) p_{\theta}\left(a_{1} | s_{1}\right) p\left(s_{2} | s_{1}, a_{1}\right) p_{\theta}\left(a_{2} | s_{2}\right) p\left(s_{3} | s_{2}, a_{2}\right) \cdots \\ &=p\left(s_{1}\right) \prod_{t=1}^{T} p_{\theta}\left(a_{t} | s_{t}\right) p\left(s_{t+1} | s_{t}, a_{t}\right) \end{aligned} pθ(τ)=p(s1)pθ(a1∣s1)p(s2∣s1,a1)pθ(a2∣s2)p(s3∣s2,a2)⋯=p(s1)t=1∏Tpθ(at∣st)p(st+1∣st,at)
在假设演员的参数就是 θ \theta θ 的情况下,某一个轨迹 τ \tau τ 的概率就是这样算的:先算环境输出 s 1 s_1 s1 的概率,再计算根据 s 1 s_1 s1 执行 a 1 a_1 a1 的概率,这是由策略里面的网络参数 θ \theta θ所决定的(它是一个概率的原因是因为策略的网络的输出是一个分布,演员是根据这个分布去做采样,决定现在实际上要采取的动作是哪一个)。接下来环境根据 a 1 a_1 a1 跟 s 1 s_1 s1 产生 s 2 s_2 s2,因为 s 2 s_2 s2 跟 s 1 s_1 s1 还是有关系的,下一个游戏画面跟前一个游戏画面通常还是有关系的,至少要是连续的, 所以给定前一个游戏画面 s 1 s_1 s1 和现在演员采取的行为 a 1 a_1 a1,就会产生 s 2 s_2 s2。
环境的行为 。环境的函数内部的参数或内部的规则长什么样子。 p ( s t + 1 ∣ s t , a t ) p(s_{t+1}|s_t,a_t) p(st+1∣st,at)这一项代表的是环境,环境这一项通常是无法控制的。
agent 的行为。你能控制的是 p θ ( a t ∣ s t ) p_\theta(a_t|s_t) pθ(at∣st)。给定一个 s t s_t st,演员要采取什么样的 a t a_t at 会取决于演员的参数 θ \theta θ, 所以这部分是演员可以自己控制的。随着演员的行为不同,每个同样的轨迹, 它就会有不同的出现的概率。
在强化学习里面,除了环境跟演员以外, 还有奖励函数(reward function)。
奖励函数根据在某一个状态采取的某一个动作决定说现在这个行为可以得到多少的分数。 它是一个函数,给它 s 1 s_1 s1, a 1 a_1 a1,它告诉你得到 r 1 r_1 r1。给它 s 2 s_2 s2 , a 2 a_2 a2,它告诉你得到 r 2 r_2 r2。 把所有的 r r r 都加起来,我们就得到了 R ( τ ) R(\tau) R(τ) ,代表某一个轨迹 τ \tau τ 的奖励。
在某一场游戏里面, 某一个回合里面,我们会得到 R。我们要做的事情就是调整演员内部的参数 θ \theta θ, 使得 R 的值越大越好。 但实际上奖励并不只是一个标量,奖励其实是一个随机变量。R 其实是一个随机变量,因为演员在给定同样的状态会做什么样的行为,这件事情是有随机性的。环境在给定同样的观测要采取什么样的动作,要产生什么样的观测,本身也是有随机性的,所以 R 是一个随机变量。能够计算的是 R 的期望值。即在给定某一组参数 θ \theta θ 的情况下,我们会得到的 R θ R_{\theta} Rθ 的期望值是多少。
R ˉ θ = ∑ τ R ( τ ) p θ ( τ ) \bar{R}_{\theta}=\sum_{\tau} R(\tau) p_{\theta}(\tau) Rˉθ=τ∑R(τ)pθ(τ)
我们可以根据 θ \theta θ 算出某一个轨迹 τ \tau τ 出现的概率,接下来计算这个 τ \tau τ 的总奖励是多少。总奖励使用这个 τ \tau τ 出现的概率进行加权,对所有的 τ \tau τ 进行求和,就是期望值。
R ˉ θ = ∑ τ R ( τ ) p θ ( τ ) = E τ ∼ p θ ( τ ) [ R ( τ ) ] \bar{R}_{\theta}=\sum_{\tau} R(\tau) p_{\theta}(\tau)=E_{\tau \sim p_{\theta}(\tau)}[R(\tau)] Rˉθ=τ∑R(τ)pθ(τ)=Eτ∼pθ(τ)[R(τ)]
取梯度之后,根据公式:
∇ f ( x ) = f ( x ) ∇ log f ( x ) \nabla f(x)=f(x)\nabla \log f(x) ∇f(x)=f(x)∇logf(x)
我们对 ∇ p θ ( τ ) \nabla p_{\theta}(\tau) ∇pθ(τ) 使用这个公式,然后会得到 ∇ p θ ( τ ) = p θ ( τ ) ∇ log p θ ( τ ) \nabla p_{\theta}(\tau)=p_{\theta}(\tau) \nabla \log p_{\theta}(\tau) ∇pθ(τ)=pθ(τ)∇logpθ(τ),进一步地,我们可以得到下式:
∇ p θ ( τ ) p θ ( τ ) = ∇ log p θ ( τ ) \frac{\nabla p_{\theta}(\tau)}{p_{\theta}(\tau)}=\nabla \log p_{\theta}(\tau) pθ(τ)∇pθ(τ)=∇logpθ(τ)
如下式所示,对 τ \tau τ 进行求和,把 R ( τ ) R(\tau) R(τ) 和 log p θ ( τ ) \log p_{\theta}(\tau) logpθ(τ) 这两项使用 p θ ( τ ) p_{\theta}(\tau) pθ(τ) 进行加权, 既然使用 p θ ( τ ) p_{\theta}(\tau) pθ(τ)进行加权 ,它们就可以被写成期望的形式。也就是你从 p θ ( τ ) p_{\theta}(\tau) pθ(τ) 这个分布里面采样 τ \tau τ 出来, 去计算 R ( τ ) R(\tau) R(τ) 乘上 ∇ log p θ ( τ ) \nabla\log p_{\theta}(\tau) ∇logpθ(τ)),然后把它对所有可能的 τ \tau τ 进行求和,就是这个期望的值(expected value)。
∇ R ˉ θ = ∑ τ R ( τ ) ∇ p θ ( τ ) = ∑ τ R ( τ ) p θ ( τ ) ∇ p θ ( τ ) p θ ( τ ) = ∑ τ R ( τ ) p θ ( τ ) ∇ log p θ ( τ ) = E τ ∼ p θ ( τ ) [ R ( τ ) ∇ log p θ ( τ ) ] \begin{aligned} \nabla \bar{R}_{\theta}&=\sum_{\tau} R(\tau) \nabla p_{\theta}(\tau)\\&=\sum_{\tau} R(\tau) p_{\theta}(\tau) \frac{\nabla p_{\theta}(\tau)}{p_{\theta}(\tau)} \\&= \sum_{\tau} R(\tau) p_{\theta}(\tau) \nabla \log p_{\theta}(\tau) \\ &=E_{\tau \sim p_{\theta}(\tau)}\left[R(\tau) \nabla \log p_{\theta}(\tau)\right] \end{aligned} ∇Rˉθ=τ∑R(τ)∇pθ(τ)=τ∑R(τ)pθ(τ)pθ(τ)∇pθ(τ)=τ∑R(τ)pθ(τ)∇logpθ(τ)=Eτ∼pθ(τ)[R(τ)∇logpθ(τ)]
实际上这个期望值没有办法算,所以用采样的方式来采样一大堆的 τ \tau τ。采样 N N N 笔 τ \tau τ, 然后去计算每一笔的这些值,再把它全部加起来,就可以得到梯度,如下式所示:
E τ ∼ p θ ( τ ) [ R ( τ ) ∇ log p θ ( τ ) ] ≈ 1 N ∑ n = 1 N R ( τ n ) ∇ log p θ ( τ n ) = 1 N ∑ n = 1 N ∑ t = 1 T n R ( τ n ) ∇ log p θ ( a t n ∣ s t n ) \begin{aligned} E_{\tau \sim p_{\theta}(\tau)}\left[R(\tau) \nabla \log p_{\theta}(\tau)\right] &\approx \frac{1}{N} \sum_{n=1}^{N} R\left(\tau^{n}\right) \nabla \log p_{\theta}\left(\tau^{n}\right) \\ &=\frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}} R\left(\tau^{n}\right) \nabla \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right) \end{aligned} Eτ∼pθ(τ)[R(τ)∇logpθ(τ)]≈N1n=1∑NR(τn)∇logpθ(τn)=N1n=1∑Nt=1∑TnR(τn)∇logpθ(atn∣stn)
下面给出 ∇ log p θ ( τ ) \nabla \log p_{\theta}(\tau) ∇logpθ(τ)的具体计算过程,如下式所示。
∇ log p θ ( τ ) = ∇ ( log p ( s 1 ) + ∑ t = 1 T log p θ ( a t ∣ s t ) + ∑ t = 1 T log p ( s t + 1 ∣ s t , a t ) ) = ∇ log p ( s 1 ) + ∇ ∑ t = 1 T log p θ ( a t ∣ s t ) + ∇ ∑ t = 1 T log p ( s t + 1 ∣ s t , a t ) = ∇ ∑ t = 1 T log p θ ( a t ∣ s t ) = ∑ t = 1 T ∇ log p θ ( a t ∣ s t ) \begin{aligned} \nabla \log p_{\theta}(\tau) &= \nabla \left(\log p(s_1)+\sum_{t=1}^{T}\log p_{\theta}(a_t|s_t)+ \sum_{t=1}^{T}\log p(s_{t+1}|s_t,a_t) \right) \\ &= \nabla \log p(s_1)+ \nabla \sum_{t=1}^{T}\log p_{\theta}(a_t|s_t)+ \nabla \sum_{t=1}^{T}\log p(s_{t+1}|s_t,a_t) \\ &=\nabla \sum_{t=1}^{T}\log p_{\theta}(a_t|s_t)\\ &=\sum_{t=1}^{T} \nabla\log p_{\theta}(a_t|s_t) \end{aligned} ∇logpθ(τ)=∇(logp(s1)+t=1∑Tlogpθ(at∣st)+t=1∑Tlogp(st+1∣st,at))=∇logp(s1)+∇t=1∑Tlogpθ(at∣st)+∇t=1∑Tlogp(st+1∣st,at)=∇t=1∑Tlogpθ(at∣st)=t=1∑T∇logpθ(at∣st)
注意, p ( s 1 ) p(s_1) p(s1)和 p ( s t + 1 ∣ s t , a t ) p(s_{t+1}|s_t,a_t) p(st+1∣st,at) 来自于环境, p θ ( a t ∣ s t ) p_\theta(a_t|s_t) pθ(at∣st)是来自于 agent。 p ( s 1 ) p(s_1) p(s1) 和 p ( s t + 1 ∣ s t , a t ) p(s_{t+1}|s_t,a_t) p(st+1∣st,at) 由环境决定,所以与 θ \theta θ 无关,因此
∇ log p ( s 1 ) = 0 , ∇ ∑ t = 1 T log p ( s t + 1 ∣ s t , a t ) = 0 \nabla \log p(s_1)=0,\nabla \sum_{t=1}^{T}\log p(s_{t+1}|s_t,a_t)=0 ∇logp(s1)=0,∇t=1∑Tlogp(st+1∣st,at)=0
∇ R ˉ θ = ∑ τ R ( τ ) ∇ p θ ( τ ) = ∑ τ R ( τ ) p θ ( τ ) ∇ p θ ( τ ) p θ ( τ ) = ∑ τ R ( τ ) p θ ( τ ) ∇ log p θ ( τ ) = E τ ∼ p θ ( τ ) [ R ( τ ) ∇ log p θ ( τ ) ] ≈ 1 N ∑ n = 1 N R ( τ n ) ∇ log p θ ( τ n ) = 1 N ∑ n = 1 N ∑ t = 1 T n R ( τ n ) ∇ log p θ ( a t n ∣ s t n ) \begin{aligned} \nabla \bar{R}_{\theta}&=\sum_{\tau} R(\tau) \nabla p_{\theta}(\tau)\\&=\sum_{\tau} R(\tau) p_{\theta}(\tau) \frac{\nabla p_{\theta}(\tau)}{p_{\theta}(\tau)} \\&= \sum_{\tau} R(\tau) p_{\theta}(\tau) \nabla \log p_{\theta}(\tau) \\ &=E_{\tau \sim p_{\theta}(\tau)}\left[R(\tau) \nabla \log p_{\theta}(\tau)\right]\\ &\approx \frac{1}{N} \sum_{n=1}^{N} R\left(\tau^{n}\right) \nabla \log p_{\theta}\left(\tau^{n}\right) \\ &=\frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}} R\left(\tau^{n}\right) \nabla \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right) \end{aligned} ∇Rˉθ=τ∑R(τ)∇pθ(τ)=τ∑R(τ)pθ(τ)pθ(τ)∇pθ(τ)=τ∑R(τ)pθ(τ)∇logpθ(τ)=Eτ∼pθ(τ)[R(τ)∇logpθ(τ)]≈N1n=1∑NR(τn)∇logpθ(τn)=N1n=1∑Nt=1∑TnR(τn)∇logpθ(atn∣stn)
我们可以直观地来理解上面这个式子,也就是在采样到的数据里面, 你采样到在某一个状态 s t s_t st要执行某一个动作 a t a_t at, 这个 s t s_t st 跟 a t a_t at 是在整个轨迹 τ \tau τ 的里面的某一个状态和动作的对。
我们可以套下面这个公式来把梯度计算出来:
∇ R ˉ θ = 1 N ∑ n = 1 N ∑ t = 1 T n R ( τ n ) ∇ log p θ ( a t n ∣ s t n ) \nabla \bar{R}_{\theta}=\frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}} R\left(\tau^{n}\right) \nabla \log p_{\theta}\left(a_{t}^{n} | s_{t}^{n}\right) ∇Rˉθ=N1n=1∑Nt=1∑TnR(τn)∇logpθ(atn∣stn)
也就是把每一个 s 跟 a 的对拿进来,算一下它的对数概率(log probability),然后对它取梯度,然后这个梯度前面会乘一个权重,权重就是这场游戏的奖励。 有了这些以后,就可以更新模型。更新完模型以后,要重新去收集数据,再更新模型。注意,一般 policy gradient(PG) 采样的数据就只会用一次。把这些数据采样起来,然后拿去更新参数,这些数据就丢掉了。接着再重新采样数据,才能够去更新参数。
如图所示,我们可以把它想成一个分类的问题,在分类里面就是输入一个图像,然后输出决定说是 10 个类里面的哪一个。在做分类时,我们要收集一堆训练数据,要有输入跟输出的对。
在实现的时候,把状态当作是分类器的输入。 当作在做图像分类的问题,只是现在的类不是说图像里面有什么东西,而是说看到这张图像我们要采取什么样的行为,每一个行为就是一个类。比如说第一个类叫做向左,第二个类叫做向右,第三个类叫做开火。
在一般的分类问题里面,其实在实现分类的时候,目标函数都会写成最小化交叉熵(cross entropy),其实最小化交叉熵就是最大化对数似然(log likelihood)。
做分类的时候,目标函数就是最大化或最小化对象, 因为我们现在是最大化似然(likelihood),所以其实是最大化,如下式所示:
1 N ∑ n = 1 N ∑ t = 1 T n log p θ ( a t n ∣ s t n ) \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}} \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right) N1n=1∑Nt=1∑Tnlogpθ(atn∣stn)
这是一般的分类问题,RL 唯一不同的地方是 loss 前面乘上一个权重:整场游戏得到的总奖励 R,它并不是在状态 s 采取动作 a 的时候得到的奖励,如下式所示:
1 N ∑ n = 1 N ∑ t = 1 T n R ( τ n ) log p θ ( a t n ∣ s t n ) \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}} R\left(\tau^{n}\right) \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right) N1n=1∑Nt=1∑TnR(τn)logpθ(atn∣stn)
每一笔训练数据,都使用这个 R 进行加权。然后用 TensorFlow 或 PyTorch 算梯度就结束了,跟一般分类差不多。
标准的策略梯度算法(Vanilla Policy Gradient, VPG)属于on-policy算法
VPG算法可以被用到离散和连续动作空间中
伪代码
第一个 tip 是添加基线(baseline)。 如果给定状态 s 采取动作 a 会给你整场游戏正的奖励,就要增加它的概率。如果状态 s 执行动作 a,整场游戏得到负的奖励,就要减少这一项的概率。
假设你在某一个状态有 3 个动作 a/b/c可以执行。根据下式,
∇ R ˉ θ ≈ 1 N ∑ n = 1 N ∑ t = 1 T n R ( τ n ) ∇ log p θ ( a t n ∣ s t n ) \nabla \bar{R}_{\theta} \approx \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}} R\left(\tau^{n}\right) \nabla \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right) ∇Rˉθ≈N1n=1∑Nt=1∑TnR(τn)∇logpθ(atn∣stn)
你要把这 3 项的概率,对数概率都拉高。 但是它们前面权重的 R 是不一样的。 R 是有大有小的,权重小的,它上升的就少,权重多的,它上升的就大一点。 因为这个对数概率是一个概率,所以动作 a、b、c 的对数概率的和要是 0。 所以上升少的,在做完归一化(normalize)以后, 它其实就是下降的,上升的多的,才会上升。
这是一个理想上的状况,但是实际上,做采样本来应该是对所有可能的 s 跟 a 的对进行求和后的一个期望(expectation)。 但真正在learning的时候不可能是这么做的,我们只是采样了少量的 s 跟 a 的对而已。 **因为我们做的是采样,有一些动作可能从来都没有采样到。**在某一个状态,虽然可以执行的动作有 a/b/c,但可能只采样到动作 b,可能只采样到动作 c,没有采样到动作 a。但现在所有动作的奖励都是正的,所以根据这个式子,它的每一项的概率都应该要上升。**这会遇到的问题是,因为 a 没有被采样到,其它动作的概率如果都要上升,a 的概率就要下降。 但是 a 不一定是一个不好的动作, 它只是没被采样到。**只是因为a没被采样到, 它的概率就会下降,这个显然是有问题的,要怎么解决这个问题呢?你会希望你的奖励不要总是正的。
为了解决奖励总是正的问题,可以把奖励减掉一项 b,这项 b 叫做基线。减掉这项 b 以后,就可以让 R ( τ n ) − b R(\tau^n)-b R(τn)−b这一项有正有负。 所以如果得到的总奖励 R ( τ n ) R(\tau^n) R(τn)大于 b 的话,就让它的概率上升。如果这个总奖励小于 b,就算它是正的,正的很小也是不好的,就要让这一项的概率下降。 如果 R ( τ n ) < b R(\tau^n)R(τn)<b , 就要让这个状态采取这个动作的分数下降 。这个 b 怎么设呢?一个最简单的做法就是:你把 τ n \tau^n τn 的值取期望, 算一下 τ n \tau^n τn 的平均值,即:
b ≈ E [ R ( τ ) ] b \approx E[R(\tau)] b≈E[R(τ)]
所以在实现训练的时候,通过不断地把 R ( τ ) R(\tau) R(τ)的分数记录下来 然后不断地去计算 R ( τ ) R(\tau) R(τ) 的平均值,当作 b 来用。 这样就可以在训练的时候, ∇ log p θ ( a t n ∣ s t n ) \nabla \log p_{\theta}\left(a_{t}^{n} | s_{t}^{n}\right) ∇logpθ(atn∣stn) 乘上前面这一项, 是有正有负的。
第二个 tip:给每一个动作合适的分数(credit)。
如果我们看下面这个式子的话,
∇ R ˉ θ ≈ 1 N ∑ n = 1 N ∑ t = 1 T n ( R ( τ n ) − b ) ∇ log p θ ( a t n ∣ s t n ) \nabla \bar{R}_{\theta} \approx \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}}\left(R\left(\tau^{n}\right)-b\right) \nabla \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right) ∇Rˉθ≈N1n=1∑Nt=1∑Tn(R(τn)−b)∇logpθ(atn∣stn)
我们原来会做的事情是,在某一个状态,假设你执行了某一个动作 a,它得到的奖励,它前面乘上的这一项 R ( τ n ) − b R(\tau^n)-b R(τn)−b。
只要在同一个回合里面,在同一场游戏里面, 所有的状态跟动作的对都会使用同样的奖励项(term)进行加权,这件事情显然是不公平的,因为在同一场游戏里面 也许有些动作是好的,有些动作是不好的。 假设整场游戏的结果是好的, 并不代表这个游戏里面每一个行为都是对的。若是整场游戏结果不好, 但不代表游戏里面的所有行为都是错的。所以我们希望可以给每一个不同的动作前面都乘上不同的权重。每一个动作的不同权重, 它反映了每一个动作到底是好还是不好。
在采样的次数不够多的情况下,你要给每一个状态跟动作对合理的分数,你要让大家知道它合理的贡献。怎么给它一个合理的贡献呢?
一个做法是计算这个状态动作对的奖励的时候,**不把整场游戏得到的奖励全部加起来,只计算从这一个动作执行以后所得到的奖励。**如下式所示:
本来的权重是整场游戏的奖励的总和,现在改成从某个时间 t t t 开始,假设这个动作是在 t t t 这个时间点所执行的,从 t t t 这个时间点一直到游戏结束所有奖励的总和,才真的代表这个动作是好的还是不好的。
接下来再更进一步,我们把未来的奖励做一个折扣(discount),由此得到的回报被称为 Discounted Return(折扣回报)。为什么要把未来的奖励做一个折扣呢?因为虽然在某一个时间点,执行某一个动作,会影响接下来所有的结果,有可能在某一个时间点执行的动作,接下来得到的奖励都是这个动作的功劳。但在比较真实的情况下, 如果时间拖得越长,影响力就越小。 所以我们实际上在做的时候,你会在 R 前面乘上一个 discount factor γ \gamma γ, γ ∈ [ 0 , 1 ] \gamma \in [0,1] γ∈[0,1] ,一般会设个 0.9 或 0.99, γ = 0 \gamma=0 γ=0 : 只关心即时奖励; γ = 1 \gamma = 1 γ=1: 未来奖励等同于即时奖励。
把 R − b R-b R−b 这一项与Assign Suitable Credit合起来,我们统称为 优势函数(advantage function), 用 A 来代表优势函数。优势函数取决于 s 和 a,我们就是要计算的是在某一个状态 s 采取某一个动作 a 的时候,优势函数有多大。
在算优势函数时,要计算 ∑ t ′ = t T n r t ′ n \sum_{t^{\prime}=t}^{T_{n}} r_{t^{\prime}}^{n} ∑t′=tTnrt′n ,需要有一个模型去跟环境做互动,才知道接下来得到的奖励会有多少。优势函数 A θ ( s t , a t ) A^{\theta}\left(s_{t}, a_{t}\right) Aθ(st,at)的上标是 θ \theta θ,代表用 θ \theta θ 这个模型跟环境去做互动,然后才计算出这一项。从时间 t 开始到游戏结束为止,所有 r 的加和减掉 b,这个就叫优势函数。
优势函数的意义就是,假设我们在某一个状态 s t s_t st 执行某一个动作 a t a_t at,相较于其他可能的动作,它有多好。它在意的不是一个绝对的好,而是相对的好,即相对优势(relative advantage)。 A θ ( s t , a t ) A^{\theta}\left(s_{t}, a_{t}\right) Aθ(st,at) 通常可以是由一个网络估计出来的,这个网络叫做 critic。
REINFORCE 用的是回合更新的方式,属于Monte Carlo更新方式。它在代码上的处理是先拿到每个步骤的奖励,然后计算每个步骤的未来总收益 G t G_t Gt是多少,然后拿每个 G t G_t Gt代入公式,去优化每一个动作的输出。所以编写代码时会有这样一个函数:输入每个步骤拿到的奖励,把这些奖励转成每一个步骤的未来总收益。因为未来总收益是这样计算的:
G t = ∑ k = t + 1 T γ k − t − 1 r k = r t + 1 + γ G t + 1 \begin{aligned} G_{t} &=\sum_{k=t+1}^{T} \gamma^{k-t-1} r_{k} \\ &=r_{t+1}+\gamma G_{t+1} \end{aligned} Gt=k=t+1∑Tγk−t−1rk=rt+1+γGt+1
上一个步骤和下一个步骤的未来总收益有这样一个关系,所以在代码的计算上,就是一步一步地从后往前推,先算 G t G_t Gt,一直算到 G 1 G_1 G1。
REINFORCE 的伪代码主要看最后四行:先产生一个回合的数据,比如 ( s 1 , a 1 , G 1 ) , ( s 2 , a 2 , G 2 ) , ⋯ , ( s T , a T , G T ) (s_1,a_1,G_1),(s_2,a_2,G_2),\cdots,(s_T,a_T,G_T) (s1,a1,G1),(s2,a2,G2),⋯,(sT,aT,GT)。然后针对每个动作来计算梯度。 在代码上计算时,我们要拿到神经网络的输出。神经网络会输出每个动作对应的概率值,然后我们还可以拿到实际的动作,把它转成 one-hot 向量乘一下,我们可以计算出 ln π ( A t ∣ S t , θ ) \ln \pi(A_t|S_t,\theta) lnπ(At∣St,θ)。
独热编码(one-hot Encoding)通常用于处理类别间不具有大小关系的特征。例如血型,一共有4个取值(A型、B型、AB型、O型),独热编码会把血型变成一个4维稀疏向量,A型血表示为(1,0,0,0),B型血表示为(0,1,0,0),AB型会表示为(0,0,1,0),O型血表示为(0,0,0,1)。
policy gradient 预测每一个状态下面应该要输出的这个行动的概率,就是输入状态 s t s_t st,然后输出动作的概率,比如 0.02,0.08,0.9。实际上输出给环境的动作是随机选了一个动作,比如说我选了右这个动作,它的 one-hot 向量就是 0,0,1。
我们把神经网络的输出和实际动作带入交叉熵的公式就可以求出输出的概率和实际的动作之间的差距。
但这个实际的动作 a t a_t at 只是我们输出的真实的动作,它并不一定是正确的动作,它不能像手写数字识别一样作为一个正确的标签去指导神经网络朝着正确的方向去更新,所以我们需要乘以一个奖励回报 G t G_t Gt。这个奖励回报相当于是对这个真实动作 的评价。
实际上我们在计算这个 loss 的时候,我们要取得 ln π ( A t ∣ S t , θ ) \ln \pi(A_t|S_t,\theta) lnπ(At∣St,θ)。拿实际执行的动作,先取个 one-hot 向量,然后再跟神经网络预测的动作概率相乘,就可以计算出算法里面的 ln π ( A t ∣ S t , θ ) \ln \pi(A_t|S_t,\theta) lnπ(At∣St,θ)。因为我们会拿到整个回合的所有的轨迹,所以我们可以对这一整条轨迹里面的每个动作都去计算一个 loss。把所有的 loss 加起来之后,再扔给 adam 的优化器去自动更新参数。
答:
答: gradient ascent(梯度上升),因为要让它越大越好,所以是 gradient ascent。Gradient ascent 在 update 参数的时候要加。要进行 gradient ascent,我们先要计算 expected reward R ˉ \bar{R} Rˉ 的 gradient 。我们对 R ˉ \bar{R} Rˉ 取一个 gradient,这里面只有 p θ ( τ ) p_{\theta}(\tau) pθ(τ)是跟 θ \theta θ 有关,所以 gradient 就放在 p θ ( τ ) p_{\theta}(\tau) pθ(τ)这个地方。
答:
E τ ∼ p θ ( τ ) [ R ( τ ) ∇ log p θ ( τ ) ] ≈ 1 N ∑ n = 1 N R ( τ n ) ∇ log p θ ( τ n ) = 1 N ∑ n = 1 N ∑ t = 1 T n R ( τ n ) ∇ log p θ ( a t n ∣ s t n ) \begin{aligned} E_{\tau \sim p_{\theta}(\tau)}\left[R(\tau) \nabla \log p_{\theta}(\tau)\right] &\approx \frac{1}{N} \sum_{n=1}^{N} R\left(\tau^{n}\right) \nabla \log p_{\theta}\left(\tau^{n}\right) \\ &=\frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}} R\left(\tau^{n}\right) \nabla \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right) \end{aligned} Eτ∼pθ(τ)[R(τ)∇logpθ(τ)]≈N1n=1∑NR(τn)∇logpθ(τn)=N1n=1∑Nt=1∑TnR(τn)∇logpθ(atn∣stn)
p θ ( τ ) p_{\theta}(\tau) pθ(τ) 里面有两项, p ( s t + 1 ∣ s t , a t ) p(s_{t+1}|s_t,a_t) p(st+1∣st,at) 来自于 environment, p θ ( a t ∣ s t ) p_\theta(a_t|s_t) pθ(at∣st) 是来自于 agent。 p ( s t + 1 ∣ s t , a t ) p(s_{t+1}|s_t,a_t) p(st+1∣st,at)由环境决定从而与 θ \theta θ 无关,因此 ∇ log p ( s t + 1 ∣ s t , a t ) = 0 \nabla \log p(s_{t+1}|s_t,a_t) =0 ∇logp(st+1∣st,at)=0。因此 ∇ p θ ( τ ) = ∇ log p θ ( a t n ∣ s t n ) \nabla p_{\theta}(\tau)= \nabla \log p_{\theta}\left(a_{t}^{n} | s_{t}^{n}\right) ∇pθ(τ)=∇logpθ(atn∣stn)。
具体来说:
答:首先我们目的是最大化reward函数,即调整 θ \theta θ ,使得期望回报最大,可以用公式表示如下
J ( θ ) = E τ ∼ p θ ( T ) [ ∑ t r ( s t , a t ) ] J(\theta)=E_{\tau \sim p_{\theta(\mathcal{T})}}[\sum_tr(s_t,a_t)] J(θ)=Eτ∼pθ(T)[t∑r(st,at)]
对于上面的式子, τ \tau τ 表示从从开始到结束的一条完整路径。通常,对于最大化问题,我们可以使用梯度上升算法来找到最大值,即
θ ∗ = θ + α ∇ J ( θ ) \theta^* = \theta + \alpha\nabla J({\theta}) θ∗=θ+α∇J(θ)
所以我们仅仅需要计算(更新) ∇ J ( θ ) \nabla J({\theta}) ∇J(θ) ,也就是计算回报函数 J ( θ ) J({\theta}) J(θ) 关于 θ \theta θ 的梯度,也就是策略梯度,计算方法如下:
∇ θ J ( θ ) = ∫ ∇ θ p θ ( τ ) r ( τ ) d τ = ∫ p θ ∇ θ l o g p θ ( τ ) r ( τ ) d τ = E τ ∼ p θ ( τ ) [ ∇ θ l o g p θ ( τ ) r ( τ ) ] \begin{aligned} \nabla_{\theta}J(\theta) &= \int {\nabla}_{\theta}p_{\theta}(\tau)r(\tau)d_{\tau} \\ &= \int p_{\theta}{\nabla}_{\theta}logp_{\theta}(\tau)r(\tau)d_{\tau} \\ &= E_{\tau \sim p_{\theta}(\tau)}[{\nabla}_{\theta}logp_{\theta}(\tau)r(\tau)] \end{aligned} ∇θJ(θ)=∫∇θpθ(τ)r(τ)dτ=∫pθ∇θlogpθ(τ)r(τ)dτ=Eτ∼pθ(τ)[∇θlogpθ(τ)r(τ)]
接着我们继续讲上式展开,对于 p θ ( τ ) p_{\theta}(\tau) pθ(τ),即 p θ ( τ ∣ θ ) p_{\theta}(\tau|{\theta}) pθ(τ∣θ) :
p θ ( τ ∣ θ ) = p ( s 1 ) ∏ t = 1 T π θ ( a t ∣ s t ) p ( s t + 1 ∣ s t , a t ) p_{\theta}(\tau|{\theta}) = p(s_1)\prod_{t=1}^T \pi_{\theta}(a_t|s_t)p(s_{t+1}|s_t,a_t) pθ(τ∣θ)=p(s1)t=1∏Tπθ(at∣st)p(st+1∣st,at)
取对数后为:
l o g p θ ( τ ∣ θ ) = l o g p ( s 1 ) + ∑ t = 1 T l o g π θ ( a t ∣ s t ) p ( s t + 1 ∣ s t , a t ) logp_{\theta}(\tau|{\theta}) = logp(s_1)+\sum_{t=1}^T log\pi_{\theta}(a_t|s_t)p(s_{t+1}|s_t,a_t) logpθ(τ∣θ)=logp(s1)+t=1∑Tlogπθ(at∣st)p(st+1∣st,at)
继续求导:
∇ l o g p θ ( τ ∣ θ ) = ∑ t = 1 T ∇ θ l o g π θ ( a t ∣ s t ) \nabla logp_{\theta}(\tau|{\theta}) = \sum_{t=1}^T \nabla_{\theta}log \pi_{\theta}(a_t|s_t) ∇logpθ(τ∣θ)=t=1∑T∇θlogπθ(at∣st)
带入第三个式子,可以将其化简为:
∇ θ J ( θ ) = E τ ∼ p θ ( τ ) [ ∇ θ l o g p θ ( τ ) r ( τ ) ] = E τ ∼ p θ [ ( ∇ θ l o g π θ ( a t ∣ s t ) ) ( ∑ t = 1 T r ( s t , a t ) ) ] = 1 N ∑ i = 1 N [ ( ∑ t = 1 T ∇ θ l o g π θ ( a i , t ∣ s i , t ) ) ( ∑ t = 1 N r ( s i , t , a i , t ) ) ] \begin{aligned} \nabla_{\theta}J(\theta) &= E_{\tau \sim p_{\theta}(\tau)}[{\nabla}_{\theta}logp_{\theta}(\tau)r(\tau)] \\ &= E_{\tau \sim p_{\theta}}[(\nabla_{\theta}log\pi_{\theta}(a_t|s_t))(\sum_{t=1}^Tr(s_t,a_t))] \\ &= \frac{1}{N}\sum_{i=1}^N[(\sum_{t=1}^T\nabla_{\theta}log \pi_{\theta}(a_{i,t}|s_{i,t}))(\sum_{t=1}^Nr(s_{i,t},a_{i,t}))] \end{aligned} ∇θJ(θ)=Eτ∼pθ(τ)[∇θlogpθ(τ)r(τ)]=Eτ∼pθ[(∇θlogπθ(at∣st))(t=1∑Tr(st,at))]=N1i=1∑N[(t=1∑T∇θlogπθ(ai,t∣si,t))(t=1∑Nr(si,t,ai,t))]
实现REINFORCE的离散形式,连续形式,以及REINFORCE with Baseline。代码详见github仓库。