avatar


7.连续控制

之前的所有章节,都有一个特点,我们的动作空间是离散的。
这一章,我们讨论动作空间是连续的情况,比如在骑电动车的时候,我们每拧一点,车速就会快一点,这时候动作空间就是连续的。

对于这种问题,一种解决方案是连续的动作空间离散化。
我们对连续的动作空间进行抽样,然后我们只选择抽样出来的动作,这就是连续的动作空间离散化。在把连续的动作空间离散化之后,我们就可以利用之前的章节所讨论的内容了。

在我读高中时期,我就已经把连续的动作空间进行离散化了,并且进行了很好的实践。那时候,对于我来说,骑电动车只有两个动作,急刹车和全速前进。但是呢,如果想让车技更上一层楼,我们不能只有急刹车和全速前进两个动作,我们还需要有二分之一速度、四分之一速度、四分之三速度等等,我们需要离散(抽样)出更多的动作。但是之前的方法,当动作太多的时候,训练会更困难,效果也不好。这时候就不能再强行把连续的动作空间离散化了,要真正的连续控制,这就涉及到我们这一章讨论的内容了。

确定策略梯度方法,Deterministic Policy Gradient,简写作DPG。除了DPG,我们还听过一个名词,DDPG,Deep Deterministic Policy Gradient,深度策略梯度方法。但本质上,只是把策略函数和价值函数用深度学习的神经网络来替代,所以这时候的策略函数,我们称其为策略网络,这时候的价值函数,我们称其为价值网络。

我们直接讨论DDPG,所以接下来的名词都是策略网络,目标网络。而不是策略函数,目标函数。

Actor-Critic

在上一章,我们讨论了一个话题,“Actor-Critic”。
Actor-Critic
当时,我们策略网络的输入是状态ss,输出是一个向量,向量中的每个元素表示一个动作的概率,然后我们会抽样出其中一个动作aa并执行,得到奖励rr。价值网络的输入是状态ss,和基于策略网络的输出而抽样得到的动作aa。把rr给价值网络的原因,是因为价值网络也需要训练。

现在呢,我们策略网络的输出可以是一个具体的数字,也可以是多个具体的数字(向量)。总之,不再是概率了。比如说,对于开电动车,输出可以是[0,40][0,40]之间的数字;再比如控制机械手臂

机械手臂

有两个地方可以转动,输出的就是一个向量。

输出不同,这个,我们在《深度学习初步及其Python实现:2.神经网络基础》有过讨论。如果输出的都是各个动作的概率,输出层用Softmax。那么,这一章呢,我们的输出层可以用Sigmoid,再乘以一个数,比如对于电动车,车速是[0,40][0,40],那就再乘以4040

对于价值网络,输出依旧是一个数字,价值,没有具体的区别。

你大爷永远是你大爷

所以呢,确定策略梯度和上一章的随机策略梯度,区别只是在于策略网络的输出层?
这的确是区别之一,但不是只有这个区别。一个更为关键性的区别是,一般而言,确定策略梯度都属于离线策略。
接下来,我们看具体过程。

用行为策略收集经验

在之前的章节中,我们讨论过离线策略(Off Policy)方法。即我们有目标策略(Target Policy)和行为策略(Behavior Policy)。
目标策略是我们最后希望得到的策略,在这里,目标策略是确定策略,表示成μ(s;θ)\mu(s;\theta_{\text{新}})θ\theta_{\text{新}}的含义是说这个是最新的参数,与之相对应的有θ\theta_{\text{旧}},在我们的行为策略中。行为策略:a=μ(s;θ)+εa = \mu(s;\theta_{\text{旧}}) + \varepsilonε\varepsilon的我们人为添加的噪声。

我们用行为策略控制智能体和环境交互,把智能体的轨迹整理成(st,at,rt,st+1)(s_t,a_t,r_t,s_{t+1})这样的四元组,存入经历库D\mathcal{D}
(当然,也是可以是st,at,rt,st+1,at+1,rt+1,st+2s_t,a_t,r_t,s_{t+1},a_{t+1},r_{t+1},s_{t+2},我们这里以(st,at,rt,st+1)(s_t,a_t,r_t,s_{t+1})为例。)

然后在训练的时候,我们从经历库中再进行抽样。
例如,我们抽样出来的一个元组是(sj,aj,rj,sj+1)(s_j, a_j, r_j, s_{j+1})。在训练策略网络μ(s;θ)\mu(s;\theta)的时候,只用状态sjs_j,在训练价值网络q(s,a;ω)q(s,a;\omega)的时候,要用到四元组中全部四个元素:(sj,aj,rj,sj+1)(s_j, a_j, r_j, s_{j+1})
我这么说是不是略有疑虑?
接下来,我们讨论训练过程。

训练策略网络

我们给定状态ss,策略网络输出一个动作a=μ(s;θ)a = \mu(s;\theta),然后价值网络会给aa打一个分数:q^=Q(s,a;ω)\hat{q} = Q(s,a;\omega)。所以,在这个过程中,参数θ\theta会影响aa,进而影响q^\hat{q}
即,价值网络的评分是:

q^=Q(s,μ(s;θ);ω)\hat{q} = Q(s,\mu(s;\theta);\omega)

这个分数q^\hat{q}可以反映出策略网络参数θ\theta的好坏程度。训练策略网络的目标就是改进参数θ\theta,使q^\hat{q}变得更大。把策略网络看做演员,价值网络看做评委。训练演员(策略网络)的目的就是让他迎合评委(价值网络)的喜好,改变自己的表演技巧(即参数θ\theta),使得评委打分q^\hat{q}均值更高。

需要注意的是,这里还有一个东西,ss是状态,而且这个ss是一个随机变量,那么这样的话,评委的打分也非常受状态ss的影响。所以,我们要消除ss的影响,

复习一下,在第一章《1.基础概念》的开头,我们举了一个例子,g(X,Y)=XYg(X,Y) = XY是一个关于随机变量XX和随机变量YY的二元函数,然后我们求g(X,Y)g(X,Y)关于随机变量XX的期望,消除了XX的影响。

那么,现在一样的,我们求关于SS的期望,这就是我们的目标函数,即

J(θ)=ES[q(S,μ(S;θ);ω)]J(\theta) = \mathbb{E}_{S}\bigg[q(S,\mu(S;\theta);\omega)\bigg]

现在提一个问题,我们是要最大化还是最小化?
我们的目标是什么?是为了让评委(价值网络)的打分尽量高。
所以,这是一个最大化的问题

θ=arg maxθJ(θ)\theta_{\star} = \argmax_{\theta} J(\theta)

  • 在训练策略网络的时候,价值网络的参数ω\omega暂时固定。

那么,接下来的内容,就很简单了,根据梯度上升和链式法则:

θθ+βQ(sj,a^j;ω)aμ(sj;θ)θ\theta \leftarrow \theta + \beta \frac{\partial Q(s_j,\hat{a}_j;\omega)}{\partial a} \frac{\partial \mu(s_j;\theta)}{\partial \theta}

训练价值网络

训练价值网络的目标是让价值网络Q(s,a;ω)Q(s,a;\omega)的预测越来越接近真实价值函数Qπ(s,a)Q_{\pi}(s,a)。如果把价值网络看做评委,那么训练评委的目标就是让他的打分越来越准确。每一轮训练都要用到一个实际观测的奖励rr,可以把rr看做"真理",用它来校准评委的打分。

我们每次从经验回放数组中取出一个四元组sj,aj,rj,sj+1s_j,a_j,r_j,s_{j+1},用它更新一次参数ω\omega
首先让价值网络预测q^j\hat{q}_jq^j+1\hat{q}_{j+1}

q^j=Q(sj,aj;ω)q^j+1=Q(sj+1,μ(sj+1;θ);ω)\begin{aligned} \hat{q}_j & = Q(s_j,a_j;\omega) \\ \hat{q}_{j+1} & = Q(s_{j+1},\mu(s_{j+1};\theta);\omega) \end{aligned}

然后,我们来计算一个略微带点"真理"的TD目标

y^j=rj+γq^j+1\hat{y}_j = r_j + \gamma \hat{q}_{j+1}

所以,有目标函数

L(ω)=12[Q(sj,aj;ω)y^j]2L(\omega) = \frac{1}{2}\bigg[Q(s_j,a_j;\omega) - \hat{y}_j\bigg]^2

计算梯度

L(ω)ω=(q^jy^j)Q(sj,aj;ω)ω\frac{\partial L(\omega)}{\partial \omega}= (\hat{q }_j - \hat{y}_j) \frac{\partial Q(s_j,a_j;\omega)}{\partial \omega}

提一个问题,接下来是梯度上升,还是梯度下降?
我们的目的是什么?是让目标函数的值越来越小,即损失函数的值越来越小,所以梯度下降。

ωωαL(ω)ω\omega \leftarrow \omega - \alpha \frac{\partial L(\omega)}{\partial \omega}

确定策略梯度方法的训练

在训练开始之前,我们需要收集足够多的经历,这个过程就是用相对旧的策略去和环境做交互,然后把轨迹以元祖的形式,存在经历库D\mathcal{D}中。
接下来,正式开始训练。
我们从经历库中随机抽取一个元祖,记作(sj,aj,rj,sj+1)(s_j,a_j,r_j,s_{j+1})

1、让策略网络做预测:

a^j=μ(sj;θ)a^j+1=μ(sj+1;θ)\begin{aligned} \hat{a}_j & = \mu(s_j;\theta_{\text{新}}) \\ \hat{a}_{j+1} & = \mu(s_{j+1};\theta_{\text{新}}) \end{aligned}

  • 注意:a^j\hat{a}_j不同于抽样出来的元祖中的aja_ja^j\hat{a}_j根据当前的策略网络μ(sj;θ)\mu(s_j;\theta_{\text{新}})所得到的。而抽样出来的元祖中的aja_j来自过时的策略网络的μ(sj;θ)\mu(s_j;\theta_\text{旧})。用aja_j来更新ω\omega,请注意a^j\hat{a}_jaja_j的区别。

2、让价值网络做预测:

q^j=Q(sj,aj;ω)q^j+1=Q(sj+1,a^j+1;ω)\begin{aligned} \hat{q}_j & = Q(s_j,a_j;\omega) \\ \hat{q}_{j+1} & = Q(s_{j+1},\hat{a}_{j+1};\omega) \end{aligned}

3、计算TD目标和TD误差

y^j=rj+γq^j+1δj=q^jy^j\begin{aligned} \hat{y}_j & = r_j + \gamma \hat{q}_{j+1} \\ \delta_j & = \hat{q}_j - \hat{y}_j \end{aligned}

4、更新价值网络:

ωωαδjQ(sj,aj;ω)ω\omega \leftarrow \omega - \alpha \delta_j \frac{\partial Q(s_j,a_j;\omega)}{\partial \omega}

5、更新策略网络:

θθ+βQ(sj,a^j;ω)aμ(sj;θ)θ\theta_{\text{新}} \leftarrow \theta_{\text{新}} + \beta \frac{\partial Q(s_j,\hat{a}_j;\omega)}{\partial a} \frac{\partial \mu(s_j;\theta_{\text{新}})}{\partial \theta_{\text{新}}}

关于价值网络的讨论

价值网络是在动作价值函数做近似,还是对最优动作价值函数做近似

确定策略梯度中有一个确定策略网络μ(s;θ)\mu(s;\theta)和一个价值网络Q(s,a;ω)Q(s,a;\omega)。请问价值网络Q(s,a;ω)Q(s,a;\omega)是对动作价值函数Qπ(s,a)Q_{\pi}(s,a)的近似,还是对最优动作价值函数Q(s,a)Q_{\star}(s,a)的近似?

是对动作价值函数Qπ(s,a)Q_{\pi}(s,a)的近似

在确定策略梯度的训练流程中,更新价值网络用到TD目标:

y^j=rj+γQ(sj+1,μ(sj+1;θ);ω)\hat{y}_j = r_j + \gamma Q(s_{j+1},\mu(s_{j+1};\theta);\omega)

策略μ(s,θ)\mu(s,\theta)会直接影响价值网络QQ。策略不同,得到的价值网络QQ就不同。

不过,虽然价值网络Q(s,a;ω)Q(s,a;\omega)是对动作价值函数Qπ(s,a)Q_{\pi}(s,a)的近似,但我们在具体操作过程中,也的确是让Q(s,a;ω)Q(s,a;\omega)趋近于最优动作价值函数Q(s,a)Q_{\star}(s,a)。但是!需要注意的是!这只是在整体的训练过程中,在为了让μ(s;θ)\mu(s;\theta)趋近于最优策略π\pi_{\star}中顺带的。我们对于价值网络Q(s,a;ω)Q(s,a;\omega)依旧是想让其趋近于动作价值函数Qπ(s,a)Q_{\pi}(s,a)

价值网络是在行为策略做评估,还是在对目标策略做评估

确定策略梯度的训练中有行为策略μ(s;θ)\mu(s;\theta_{\text{旧}})和目标策略μ(s;θ)\mu(s;\theta_{\text{新}}),价值网络Q(s,a;ω)Q(s,a;\omega)近似动作价值函数Qπ(s,a)Q_{\pi}(s,a)。请问此处的π\pi指的是行为策略还是目标策略?

是目标策略μ(s;θ)\mu(s;\theta_{\text{新}})

行为策略对价值网络几乎没有影响。
我们用TD算法训练价值网络,TD算法的目的在于鼓励价值网络的预测趋近于TD目标。

Q(sj,aj;ω)=rj+γQ(sj+1,μ(sj+1;θ);ω)Q(s_j,a_j;\omega) = r_j + \gamma Q(s_{j+1},\mu(s_{j+1};\theta_{\text{新}});\omega)

在收集经验的过程中,行为策略决定了如何基于sjs_j生成aja_j,然而这不重要。上面的公式只希望等式左边去拟合等式右边,而不在乎aja_j是如何生成的。

高估的问题及办法

上述过程有没有问题?
我们不断的梯度上升去更新θ\theta,为的是什么?为了我们计算出来的动作a^\hat{a}得到尽量高的评价,换言之为了Q(s,μ(s;θ);ω)Q(s,\mu(s;\theta);\omega)尽量大。即

对于所有的sSμ(s;θ)=arg maxaAQ(s,a;ω)\text{对于所有的}s\in\mathcal{S} \qquad \mu(s;\theta_{\star}) = \argmax_{a \in \mathcal{A}} Q(s,a;\omega)

这是我们的目标。
回忆一下,在《5.函数近似》这一章,DQN的决策方式是aj=arg maxaQ(sj,a;ω)a_j = \argmax_a Q(s_j,a;\omega)。而且,当时我们举了一个例子,来说明DQN会高估。
高估
s中间s_{\text{中间}}s终止s_{\text{终止}}有很多动作可以做,所有的动作都会让状态转移到s终止s_{\text{终止}},并且所有的奖励服从均值为00,方差为11的正态分布。
s中间s_{\text{中间}}出发采取的某些动作会采样到比较大的奖励值,从而导致maxaA(s中间)Q(s中间,a)\max_{a \in \mathcal{A}(s_{\text{中间}})} Q(s_{\text{中间}},a)的值较大。
即:TD目标y^j\hat{y}_j高估真实最优动作价值Q(sj,aj)Q_{\star}(s_j,a_j)
那么,现在一样的。

y^j=rj+γQ(sj+1,μ(sj+1;θ);ω)rj+γmaxaj+1Q(sj+1,aj+1;ω)\begin{aligned} \hat{y}_j & = r_j + \gamma Q(s_{j+1},\mu(s_{j+1};\theta);\omega) \\ & \approx r_j + \gamma \max_{a_{j+1}} Q(s_{j+1},a_{j+1};\omega) \end{aligned}

也是高估真实动作价值。然后我们又鼓励价值网络Q(sj,aj;ω)Q(s_j,a_j;\omega)去接近高估的y^j\hat{y}_j,自然也就导致价值网络Q(sj,aj;ω)Q(s_j,a_j;\omega)高估真实动作价值了。

当时,我们提出了一个方案,DoubleDQN。现在,我们的思路也类似。

首先,我们用价值网络来计算tt时刻的价值qtq_t

qt=Q(st,at;ω)q_t = Q(s_t,a_t;\omega)

但是,计算TD目标的方式有所不同,与上文不一样了。对于t+1t+1时刻的价值qt+1q_{t+1},我们利用目标价值网络(Target Value Network),这个网络和价值网络的结构一样,但是参数不一样,目标价值网络的参数记作ω\omega^-

qt+1=Q(st+1,at+1;ω)q_{t+1} = Q(s_{t+1},a'_{t+1};\omega^-)

其中,at+1a'_{t+1}来自目标策略网络(Target Policy Network),这个网络和策略网络的结构一样,但是参数不一样,目标策略网络的参数记作θ\theta^-

at+1=π(st+1;θ)a'_{t+1} = \pi(s_{t+1};\theta^-)

ω\omega^-θ\theta^-的更新方式如下:

ωkω+(1k)ωθkω+(1k)θ\begin{aligned} \omega^- & \leftarrow k \omega + (1 - k) \omega^- \\ \theta^- & \leftarrow k \omega + (1 - k) \theta^- \end{aligned}

然后,我们通过这种方式得到的qt+1q_{t+1}来计算我们的TD误差δt\delta_t

δt=qt(rt+γqt+1)\delta_t = q_t - (r_t + \gamma q_{t+1})

  • 注意qt+1q_{t+1}的来源

DDPG的组织关系

我们把上文讨论的解决方案绘制成组织关系图,这其实也就是DDPG的组织关系图。

DDPG

DDPG的过程

我们来讨论一下DDPG的过程。

DDPG算法
输入
  环境
输出
  最优策略参数θ\theta
参数
  策略网络的参数θ\theta的学习率η\eta
  价值网络的参数ω\omega的学习率β\beta
  折扣因子γ\gamma
  更新目标网络的参数kk
初始化
  初始化策略网络θ\theta为任意值(随机初始化)
  初始化目标策略网络θθ\theta^{-}\leftarrow\theta
  初始化价值网络ω\omega为任意值(随机初始化)
  初始化价值网络ωω\omega^{-}\leftarrow\omega
循环执行以下操作
  累积经验:从起始状态ss出发,执行以下操作
    对π(s;θ)\pi(s;\theta)加扰动,以确定动作aa
    执行动作aa,得到奖励rr和下一个状态ss'
    将(s,a,r,s)(s,a,r,s')存入经历库D\mathcal{D}
  更新
    从经历库D\mathcal{D}随机抽样一个经历,记作(sj,aj,rj,sj+1)(s_j,a_j,r_j,s_{j+1})
    目标策略网络做预测:a^j+1=μ(sj+1;θ)\hat{a}^{-}_{j+1}=\mu(s_{j+1};\theta^-)
    目标价值网络做预测:q^j+1=q(sj+1,a^j+1;ω)\hat{q}^{-}_{j+1}=q(s_{j+1},\hat{a}^{-}_{j+1};\omega^{-})
    价值网络做预测:qj=q(sj,aj,ω)q_j=q(s_j,a_j,\omega)
    计算TD目标:y^j=rj+γq^j+1\hat{y}_j=r_j+\gamma\hat{q}^{-}_{j+1}
    计算TD误差:δj=q^jy^j\delta_j=\hat{q}_j-\hat{y}_j
    更新价值网络:ωωαδjq(sj,aj;ω)ω\omega\leftarrow\omega-\alpha\delta_{j} \frac{\partial q(s_j,a_j;\omega)}{\partial \omega}
    每隔kk轮更新一次策略网络和目标网络:
      策略网络做预测:a^j=μ(sj;θ)\hat{a}_j = \mu(s_j;\theta)
      更新策略网络:θθ+βQ(sj,a^j;ω)ωμ(sj;θ)θ\theta\leftarrow\theta+\beta\frac{\partial Q(s_j,\hat{a}_j;\omega)}{\partial \omega}\frac{\partial \mu(s_j;\theta)}{\partial \theta}
      更新目标策略网络:θkθ+(1k)θ\theta^-\leftarrow k\theta+(1-k)\theta^-
      更新目标价值网络:ωkω+(1k)ω\omega^-\leftarrow k\omega+(1-k)\omega^-


注意:从存储空间D\mathcal{D}中采样经历,在具体实现中,可以采用了Batch批量的方法,每次采样一批的经历。

DDPG的实现

接下来,我们来实现一个DDPG。
以倒立摆控制问题为例。

该例子来自《强化学习:原理与Python实现(肖智清著)》这本书的第九章。

倒立摆控制问题描述

倒立摆控制

我们以倒立摆控制问题为例。
如图所示,在二维垂直面上有根长为11的棍子。棍子的一端固定在原点(0,0)(0,0),另一端在垂直面上(注意:二维垂直面的XX轴是垂直向下的,YY轴是水平向右的)。在任一时刻t(t=0,1,2,)t(t=0,1,2,\cdots),可以观测到棍子活动端的坐标(Xt,Yt)=(cosΘt,sinΘt)(Θt[π,+π))(X_t,Y_t)=(\cos\Theta_t,\sin\Theta_t)(\Theta_t\in[-\pi,+\pi))和角速度Θt^(Θ^t[8,+8])\hat{\Theta_t}(\hat{\Theta}_t\in[-8,+8])。这时,可以在活动段上施加一个力矩At(At[2,+2])A_t(A_t\in[-2,+2]),得到收益rt+1r_{t+1}和下一观测(cosΘt+1,sinΘt+1,Θ^)(\cos\Theta_{t+1},\sin\Theta_{t+1},\hat{\Theta})。我们希望在给定的时间内(200步)总收益越大越好。

  • 特别注意,动作空间At(At[2,+2])A_t(A_t\in[-2,+2]),是一个连续空间。

环境描述

示例代码:

1
2
3
4
5
6
import gym

env = gym.make('Pendulum-v0')
env.seed(0)
print('状态空间 = {}'.format(env.observation_space))
print('动作空间 = {}'.format(env.action_space))

运行结果:

1
2
状态空间 = Box(-8.0, 8.0, (3,), float32)
动作空间 = Box(-2.0, 2.0, (1,), float32)
  • 特别注意,这里的动作空间,是连续的动作空间。

经历回放

复用《5.函数近似》的经历回放代码。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd
import numpy as np
np.random.seed(0)

# 经历回放
class DQNReplayer:
def __init__(self, capacity):
self.memory = pd.DataFrame(index=range(capacity),columns=['observation', 'action', 'reward','next_observation', 'done'])
self.i = 0
self.count = 0
self.capacity = capacity

def store(self, *args):
self.memory.loc[self.i] = args
self.i = (self.i + 1) % self.capacity
self.count = min(self.count + 1, self.capacity)

def sample(self, size):
indices = np.random.choice(self.count, size=size)
return (np.stack(self.memory.loc[indices, field]) for field in self.memory.columns)

随机扰动

随机扰动用奥恩斯坦-乌伦贝克过程(Ornstein-Uhlenbeck Process, OU)。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class OrnsteinUhlenbeckProcess:
def __init__(self, size, mu=0., sigma=1., theta=.15, dt=.01):
self.size = size
self.mu = mu
self.sigma = sigma
self.theta = theta
self.dt = dt

def __call__(self):
n = np.random.normal(size=self.size)
self.x += (self.theta * (self.mu - self.x) * self.dt +self.sigma * np.sqrt(self.dt) * n)
return self.x

def reset(self, x=0.):
self.x = x * np.ones(self.size)

DDPG

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import tensorflow as tf
tf.random.set_seed(0)
from tensorflow import keras

class DDPGAgent:
def __init__(self, env, actor_kwargs, critic_kwargs,replayer_capacity=20000, replayer_initial_transitions=2000,gamma=0.99, batches=1, batch_size=64,net_learning_rate=0.005, noise_scale=0.1, explore=True):
observation_dim = env.observation_space.shape[0]
action_dim = env.action_space.shape[0]
observation_action_dim = observation_dim + action_dim
self.action_low = env.action_space.low
self.action_high = env.action_space.high
self.gamma = gamma
self.net_learning_rate = net_learning_rate
self.explore = explore

self.batches = batches
self.batch_size = batch_size
self.replayer = DQNReplayer(replayer_capacity)
self.replayer_initial_transitions = replayer_initial_transitions

self.noise = OrnsteinUhlenbeckProcess(size=(action_dim,),sigma=noise_scale)
self.noise.reset()

self.actor_evaluate_net = self.build_network(input_size=observation_dim, **actor_kwargs)
self.actor_target_net = self.build_network(input_size=observation_dim, **actor_kwargs)
self.critic_evaluate_net = self.build_network(input_size=observation_action_dim, **critic_kwargs)
self.critic_target_net = self.build_network(input_size=observation_action_dim, **critic_kwargs)

self.update_target_net(self.actor_target_net,
self.actor_evaluate_net)
self.update_target_net(self.critic_target_net,
self.critic_evaluate_net)

def update_target_net(self, target_net, evaluate_net,learning_rate=1.):
target_weights = target_net.get_weights()
evaluate_weights = evaluate_net.get_weights()
average_weights = [(1. - learning_rate) * t + learning_rate * e
for t, e in zip(target_weights, evaluate_weights)]
target_net.set_weights(average_weights)

def build_network(self, input_size, hidden_sizes, output_size=1,activation=tf.nn.relu, output_activation=None,loss=tf.losses.mse, learning_rate=0.001):
model = keras.Sequential()
for layer, hidden_size in enumerate(hidden_sizes):
kwargs = {'input_shape' : (input_size,)} if layer == 0 else {}
model.add(keras.layers.Dense(units=hidden_size,activation=activation, **kwargs))
model.add(keras.layers.Dense(units=output_size,activation=output_activation))
optimizer = tf.optimizers.Adam(learning_rate)
model.compile(optimizer=optimizer, loss=loss)
return model

def decide(self, observation):
if self.explore and self.replayer.count < self.replayer_initial_transitions:
return np.random.uniform(self.action_low, self.action_high)

action = self.actor_evaluate_net.predict(observation[np.newaxis])[0]
if self.explore:
noise = self.noise()
action = np.clip(action + noise, self.action_low, self.action_high)
return action

def learn(self, observation, action, reward, next_observation, done):
self.replayer.store(observation, action, reward, next_observation,done)

if self.replayer.count >= self.replayer_initial_transitions:
if done:
self.noise.reset() # 为下一回合重置噪声过程

for batch in range(self.batches):
observations, actions, rewards, next_observations,dones = self.replayer.sample(self.batch_size)

# 训练执行者网络
observation_tensor = tf.convert_to_tensor(observations,dtype=tf.float32)
with tf.GradientTape() as tape:
action_tensor = self.actor_evaluate_net(observation_tensor)
input_tensor = tf.concat([observation_tensor,action_tensor], axis=1)
q_tensor = self.critic_evaluate_net(input_tensor)
loss_tensor = -tf.reduce_mean(q_tensor)
grad_tensors = tape.gradient(loss_tensor,self.actor_evaluate_net.variables)
self.actor_evaluate_net.optimizer.apply_gradients(zip(grad_tensors, self.actor_evaluate_net.variables))

# 训练评论者网络
next_actions = self.actor_target_net.predict(next_observations)
observation_actions = np.hstack([observations, actions])
next_observation_actions = np.hstack([next_observations, next_actions])
next_qs = self.critic_target_net.predict(next_observation_actions)[:, 0]
targets = rewards + self.gamma * next_qs * (1. - dones)
self.critic_evaluate_net.fit(observation_actions, targets,verbose=0)

self.update_target_net(self.actor_target_net,self.actor_evaluate_net, self.net_learning_rate)
self.update_target_net(self.critic_target_net,self.critic_evaluate_net, self.net_learning_rate)

play_qlearning

复用play_qlearning的代码。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def play_qlearning(env, agent, train=False, render=False):
episode_reward = 0
observation = env.reset()
while True:
if render:
env.render()
action = agent.decide(observation)
next_observation, reward, done, _ = env.step(action)
episode_reward += reward
if train:
agent.learn(observation, action, reward, next_observation, done)
if done:
break
observation = next_observation
return episode_reward

训练

示例代码:

1
2
3
4
5
6
7
8
9
10
11
actor_kwargs = {'hidden_sizes' : [32, 64], 'learning_rate' : 0.0001}
critic_kwargs = {'hidden_sizes' : [64, 128], 'learning_rate' : 0.001}
agent = DDPGAgent(env, actor_kwargs=actor_kwargs,critic_kwargs=critic_kwargs)

# 训练
episodes = 50
episode_rewards = []
for episode in range(episodes):
episode_reward = play_qlearning(env, agent, train=True)
episode_rewards.append(episode_reward)
print('{} {}'.format(episode,episode_reward))

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
0  -1771.6710473932135
1 -855.8647011844732
2 -1547.6336582197753
3 -879.8661867384465
4 -1684.3831016441832

【部分运行结果略】

45 -126.7990780607278
46 -118.68338360149839
47 -532.1535582045923
48 -11.971887950776678
49 -145.32406988179974

测试

示例代码:

1
2
3
4
# 测试
agent.explore = False # 取消探索
episode_rewards = [play_qlearning(env, agent) for _ in range(100)]
print('平均回合奖励 = {} / {} = {}'.format(sum(episode_rewards),len(episode_rewards), np.mean(episode_rewards)))

运行结果:

1
平均回合奖励 = -129641.6907660435 / 100 = -1296.4169076604344

双延时确定策略梯度

有一种和DDPG差不多,但是结构更复杂的方法,双延时确定策略梯度(Twin Delayed Deep Deterministic,TD3),也被称为截断双Q学习(Clipped Double Q-Learning)。需要注意的是,这种方法只改进神经网络之间的组织关系,并不改变神经网络自身的结构。即只改进了训练用的算法。
其组织关系如下
双延时确定策略梯度

这种方法使用两个价值网络和一个策略网络:

Q(s,a;ω1)Q(s,a;ω2)μ(s,;θ)\begin{aligned} & Q(s,a;\omega_1) \\ & Q(s,a;\omega_2) \\ & \mu(s,;\theta) \end{aligned}

三个神经网络对应各自的目标网络:

Q(s,a;ω1)Q(s,a;ω2)μ(s,;θ)\begin{aligned} & Q(s,a;\omega_1^-) \\ & Q(s,a;\omega_2^-) \\ & \mu(s,;\theta^-) \end{aligned}

用目标策略网络计算动作:

a^j+1=μ(sj+1;θ)\hat{a}_{j+1}^- = \mu(s_{j+1};\theta^-)

然后用两个目标价值网络计算:

y^j,1=rj+γQ(sj+1,a^j+1;ω1)y^j,2=rj+γQ(sj+1,a^j+1;ω2)\begin{aligned} \hat{y}_{j,1} & = r_j + \gamma Q(s_{j+1},\hat{a}_{j+1}^-;\omega_1^-) \\ \hat{y}_{j,2} & = r_j + \gamma Q(s_{j+1},\hat{a}_{j+1}^-;\omega_2^-) \end{aligned}

取两者较小者为TD目标:

y^j=min{y^j,1,y^j,2}\hat{y}_j = \min\{\hat{y}_{j,1},\hat{y}_{j,2}\}

这种算法相比DDPG,效果更好。

随机策略和确定策略的比较

最后一个话题,随机策略方法和确定策略方法的比较。

随机策略 确定策略
函数 π(as;θ)\pi(a\mid s;\theta) π(s;θ)\pi(s;\theta)
输出 不同动作的概率分布 动作
控制 根据概率分布随机抽样 输出的就是动作
应用 大多数时候用于离散问题 连续控制
文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/10507
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

评论区