从强化学习到深度强化学习(下)

从强化学习到深度强化学习(下)

强化学习的基础知识可参考:

kevinbauer:从强化学习到深度强化学习(上)zhuanlan.zhihu.com图标


从离散空间到连续空间

在之前提到的强化学习任务中,都是有限的MDP框架,即动作空间及状态空间的个数都是有限个。然而,现实生活中的很多问题动作空间与状态空间并非离散的,而是连续的。那么如何用强化学习的理论基础去解决问题呢?主要有两种思路:离散化处理、函数逼近。

离散化处理:

指的是把连续空间用区域o化的方式划分成有限的个数。具体的处理手法有Tile coding及Coarse coding。

函数逼近:

指的是把输入与输出看作是经过某个函数的映射,然后用梯度更新的方法找到这个函数。具体的处理手法有线性函数逼近、核函数逼近、以及非线性函数逼近。


深度强化学习常用的算法思路有以下三种:深度Q学习、策略梯度、行动者与评论者方法。以下逐一总结这三种思路的特点。

深度Q学习

对于传统的强化学习,我们的解题思路是通过计算最优值函数(包括状态值函数 v_* 以及动作值函数 q_* ),从而求得最优策略 \pi_* 。而状态值函数可以看作是状态 s 到某实数的映射:s\rightarrow v_{\pi}\rightarrow v_{\pi}(s) \in \mathbb{R} 。而在连续空间的问题时,状态 s 一般以向量 \mathbb{x}(s) 表示,因此可以看作是状态向量 \mathbb{x}(s) 到某实数的映射: \mathbb{x}(s)\rightarrow v_{\pi}\rightarrow v_{\pi}(s, w) \in \mathbb{R}

于是我们就有了目标函数 [v_{\pi}(s,w) - \hat v_{\pi}(s,w)]^2 ,然后就可以通过梯度下降算法做函数逼近, \Delta w = \alpha(v_{\pi}(s) - \hat v(s,w))\triangledown _w \hat v(s,w)

同时,对于动作值函数,也有 \Delta w = \alpha (q_{\pi}(s, a) - \hat q(s,a,w)) \bigtriangledown _w \hat q(s,a,w)

然而,问题是此时我们并不知道 v_{\pi}q_{\pi} 的函数真值是多少。于是,需要用两种方法去估算函数真值:蒙特卡罗方法与时间差分方法。

蒙特卡罗方法:

我们从前面的经验可知,基于值函数(v_{\pi}q_{\pi})的方法,解题的思路都是 ( v_{\pi} \rightarrow ) \ q_{\pi} \rightarrow \pi' ,而 v_{\pi}q_{\pi} 的更新公式为:

  • V(S_t) \leftarrow V(S_t) + \alpha (G_t - V(S_t)))
  • Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha (G_t - Q(S_t, A_t)))

在更新公式中可以看到每次的更新的效果为:

  • \Delta V = \alpha(G_t - V(S_t))
  • \Delta Q = \alpha(G_t - Q(S_t, A_t))

值函数的迭代过程,本质上就是一个不精确的(误差很大)的值函数,逐渐更新收敛形成精确的值函数的过程。假如把这过程理解为梯度下降,我们便可以把 G_t 看作是值函数 v_{\pi}(S_t)q_{\pi}(S_t) 的真值,于是有:

  • \Delta w = \alpha (G_t - \hat v(S_t, w))\bigtriangledown _w \hat v(S_t, w)
  • \Delta w = \alpha (G_t - \hat q(S_t,A_t, w))\bigtriangledown _w \hat q(S_t, A_t, w)

有了以上转换,便可以通过Tensorflow等运算框架去计算梯度,从而求得值函数的真值。写成伪代码如下图:

资料来源:Udacity深度学习课程

代码说明:

  • 以上伪代码使用的背景是针对阶段性任务,并且使用的 G_t 是全访问(every visit,MC算法专用定义)


时间差分方法:

对于时间差分方法,我们有值函数:

  • V(S_t) \leftarrow V(S_t) + \alpha(R_{t+1} + \gamma V(S_{t+1}) - V(S_t))
  • Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha (R_{t+1} + \gamma Q(S_{t+1},A_{t+1}) - Q(S_t, A_t))

与MC方法同理,TD方法的梯度更新公式可以写成:

  • \Delta w = \alpha (R_{t+1} + \gamma \hat v(S_{t+1},w) - \hat v(S_t,w))\bigtriangledown _w \hat v(S_t,w)
  • \Delta w = \alpha (R_{t+1} + \gamma \hat q(S_{t+1},A_{t+1}, w) - \hat q(S_t,A_t, w))\bigtriangledown _w \hat q(S_t, A_t, w)

于是,写成伪代码如下图:

资料来源:Udacity深度学习课程

代码说明:

  • 该代码是针对阶段性任务。若要针对连续任务,只需修改循环条件;
  • Sarsa算法属于同步策略(on-policy),即更新值函数与行动用的是同一套策略;
  • 该算法适合在线学习,能够快速收敛;
  • 但该算法的更新值函数与行动之间的关联过于紧密,导致可能无法得到更优的策略

针对Sarsa的不足,Q-learning是另一种选择,伪代码如下图:

资料来源:Udacity深度学习课程

代码说明:

  • Q-learning与Sarsa算法最大的不同,在于更新值函数时的策略与行动策略可能不同;
  • Q-learning属于异步策略(off-policy),即更新值函数的策略与行动策略不同;
  • 该算法适合离线学习,因为值函数的更新会延迟反应在行动策略的改变上;
  • 该算法不单能学习环境反馈,还能学习人为经验;
  • 更适合批量学习(batch learning)


以上提到的算法,仅仅解决了深度学习中梯度更新的问题。除了梯度更新,如何让智能体实现通用学习?Deep Q Network, 又称DQN。

DQN指的是模型结构是DNN,CNN,RNN等常见的神经网络模型,如下图:

DQN示例。资料来源:参考资料[7]

而梯度更新则是使用上述提及深度Q-learning的更新公式: \Delta w = \alpha (R_{t+1} + \gamma max_a \hat q(S_{t+1},a, w) - \hat q(S_t,A_t, w))\bigtriangledown _w \hat q(S_t, A_t, w) ,并以此进行函数逼近。

DQN的训练过程需要大量的训练数据,即便这样,由于状态输入向量与动作输出向量之间的紧密联系,导致无法保证函数能够收敛成为最优值函数,从而也导致策略不稳定或者效果不佳。为了解决这个问题,需要用到以下两种处理手法来优化训练过程。

经验回放

以动作值函数为例,我们知道梯度更新公式为 \Delta w = \alpha (R_{t+1} + \gamma max_a \hat q(S_{t+1},a, w) - \hat q(S_t,A_t, w))\bigtriangledown _w \hat q(S_t, A_t, w) ,因此对于每次更新计算,都需要用到一组经验元组 <S_t, A_t, R_{t+1}, S_{t+1}, A_{t+1}> 。在有限MDP问题中,我们更新值函数时,每组经验元组会且只会使用一次;而(在无限MDP)我们使用DQN时,我们可以使用一个暂存区,把一定数量的经验元组暂存起来,然后能暂存区积累一定数量的经验元组时,再从暂存区随机抽取经验元组进行训练。这样做有两个好处:一是通过随机抽取经验元组,人为地切断了元组之间的前后关联关系,这样可以使函数避免落入局部最优;二是使一组经验元组可能会被使用多次,使得函数能更好地收敛。

通过经验回放处理,相当于把一个强化学习问题转化成一个监督学习问题。


固定Q目标

在梯度更新公式\Delta w = \alpha (R_{t+1} + \gamma max_a \hat q(S_{t+1},a, w) - \hat q(S_t,A_t, w))\bigtriangledown _w \hat q(S_t, A_t, w) 中, R_{t+1} + \gamma max_a \hat q(S_{t+1},a, w) 实际上是梯度更新的目标值(TD target)。然而,权值 w 是一个变化量,从而导致目标值随之改变。这就好比坐在驴背手持萝卜来教驴直线走路,如下图:

资料来源:Udacity深度学习课程

由于驴在移动的过程中会同时移动萝卜的位置,因此这样是无法教会驴走直线的。正确的做法是,人站在地上固定不动,让驴靠近时,人再往后退使驴可以继续走,如下图:

资料来源:Udacity深度学习课程

同理,梯度更新时,目标值的权值应该用一个固定权值代替,使权值更新若干次后,再用最新的权值替换一次固定权值,如此类推。


深度Q学习的具体算法

以上关于深度Q学习已经作了足够的铺垫,以下来看看伪代码:

资料来源:Udacity深度学习课程

代码说明:

  • 数组D用于暂存经验元组,容量为N个;
  • 以随机数初始化动作值函数的权值 w
  • 并以 w 初始化固定权值 w^-
  • 设置M代训练循环;
  • 对于每一代都要重新初始化起始状态向量;
  • 对于每一代的每个时间步,主要做两件事:生成经验元组与训练;
  • 生成的经验元组存于数组D;
  • 从数组D获取随机批量的经验,先计算TD目标值,再进行梯度更新计算;
  • 每隔C步就更新一次固定权值 w^-


深度Q学习的优化思路

\diamond 双DQN(Double DQN)

从DQN的梯度更新公式 \Delta w = \alpha (R_{t+1} + \gamma max_a \hat q(S_{t+1},a, w) - \hat q(S_t,A_t, w))\bigtriangledown _w \hat q(S_t, A_t, w) 可知,其中的 max_a \hat q(S_{t+1},a, w) = \hat q(S_{t+1}, argmax_a \hat q(S_{t+1},a,w), w) 。由于在训练的初始阶段, \hat q 函数的值并不准确,我们并不应该太过盲目地信任这些值,并以此作为行动依据。那么怎么才能使策略更可靠呢?

其中一个被验证的方法是双DQN(论文《Deep Reinforcement Learning with Double Q-learning》)。大概的思路如下图:

资料来源:Udacity深度学习课程

对于公式中的两个权值 w ,使用不同的权值。这样就相当于使用了两个DQN,一个用于生成策略,另一个用于评估策略。如果使用之前提到的固定Q目标的方法与双DQN方法结合的话,固定权值 w^- 就可以作为评估器使用。


\diamond 优先经验回放

前面提到经验回放,我们会在经验暂存区随机取样进行批量训练。然而,有些经验比较“宝贵”出现的概率不高,但“信息量”很大,如果能进行多次训练,能有效提升DQN的性能。那么如何定义哪些经验元组更“宝贵”呢?

答案是TD error,如下图:

资料来源:Udacity深度学习课程

TD误差越大,代表实际与预测的差距越大,因此“信息量”越大,模型能从中学到的规律就越多。因此,在保存经验元组时,可以用优先队列进行保存,以TD误差的绝对值作为优先级。在经验抽样时,不再以均匀分布来抽取,而按照优先级的分值分配概率。

以上的处理经过证明,能减少值函数所需的批次更新数量(论文《Prioritized Experience Replay》)。

在上述处理的基础上,还能进行几处优化:

资料来源:Udacity深度学习课程
  • 如果一个经验元组的TD误差很小甚至为零,那么它在抽样时被抽中的概率就几乎为零,但这并不代表这个经验就不值得学习,也许只是因为模型在之前所经历的经验样本有限,使估值比较接近真实值而已;因此在原来TD误差的基础上加上一个常量e作为优先级得分,从而让这些经验元组也有可能被选中。
  • 另一个问题是,如果优先级得分较高的经验总是会大概率地被不断回放,这可能会导致模型过拟合于这部分经验子集;为了避免这个问题,对经验的优先级得分加上一个指数 a ,这就相当于降低了 p_i 的概率,因为当 a=0 时,相当于以均匀的概率选择经验元组,而当 a = 1 时就以优先级得分作为概率计算。
  • 当我们使用优先级得分抽样时,我们需要微调更新规则,加上权重系数。具体原因可参考论文。


\diamond 对抗网络架构(Dueling Networks)

资料来源:Udacity深度学习课程

对抗网络与原始DQN的主要区别在于全连接层从一个变为两个,具体参考论文《Dueling Network Architectures for Deep Reinforcement Learning》。


关于DQN的实践,可参考以下实战项目:

黄伟亮:DQN实战:MIT强化学习实战—Deep Traffic(上)zhuanlan.zhihu.com图标

深度Q学习是一种基于值函数的方法(value-based method),即根据状态值或动作值函数来获得最优策略。而有另一种方法,直接求最优策略而无需基于值函数(policy-based method),这就是策略梯度。

策略梯度(policy gradient)

既然已经有了基于值函数的方法,为何还需要有直接求最优策略的方法呢?

\diamond 因为更简单:
最优策略 \pi_* 本质上是一个策略函数。

  • 对于确定性策略而言,策略函数实际上是 s\rightarrow a 的映射;
  • 对于随机性策略而言,策略函数实际上是 s\rightarrow \mathbb P[a|s] 概率分布的映射;

直接求策略函数的好处是可以减少对大量无关数据的储存。


\diamond 因为能生成最优的随机策略:

基于值函数的方法,无法生成真正的随机策略,如下图:

资料来源:Udacity深度学习课程

智能体的目标是要拿到香蕉。辣椒代表惩罚。智能体能感知当前状态下周围的环境,当智能体位于中间时,由于三边无墙,向下移动是最优策略;当智能体位于两边时,当左边有墙则向右移动为最优,而当右边有墙则向左移动为最优。但当智能体位于阴影区域时,两块阴影区域的状态完全一样,如下图:

资料来源:Udacity深度学习课程

如果根据值函数求最优策略,会形成要么全部向左,要么全部向右的策略,这样会导致智能体陷入死循环。即便使用 \epsilon 系数来增加策略的随机性,也需要很长的时间才能让智能体“摸索”到出路,这样的模型效率相当低;如果增大 \epsilon 系数,则会严重影响模型性能。因此,最优的策略是在阴影区域使用随机策略,如下图:

资料来源:Udacity深度学习课程

基于值函数的方法倾向于学习确定性策略或者近似确定性策略,而基于策略的方法能学习期望的随机性策略。


\diamond 因为对于连续的动作空间,基于值函数的方法并不适用:

我们知道在基于值函数的方法中,最优策略是用 argmax 函数求出,这仅仅适用于离散的动作空间;要在连续的动作空间中找最大值并不容易,尤其是当动作空间不是一维而是多维。因此,需要使用直接求策略函数的方法。



那么如何得到策略函数,使得从状态可以直接映射到动作或者动作分布呢?

首先,我们知道我们希望找到一个从某状态 s 映射到动作 a 或者动作分布 \mathbb{P}[a|s] 的函数。从线性代数理论可知,把一个向量映射到另一个向量,可以直接用矩阵相乘可得(详见《程序员的数学——线性代数》一书有通俗易懂的讲解)。因此,我们把这映射函数命名为权重 \theta (注意,矩阵等价于映射函数,同时这个矩阵又可称为权重)。于是我们可以把这个映射关系写成 a\sim \pi(s,a,\theta) = \mathbb{P}[a|s,\theta] ,用中文翻译就是“策略 \pi 就是在权值 \theta 下,状态 s 到动作分布 a 的映射”。只要我们找到最合适的映射权重 \theta 使得我们的期望收益最大化,那么这时的策略 \pi_{\theta} 便是最优策略。这就是我们的目标。把这个目标用函数的形式表达,可以写成公式: J(\theta) = \mathbb E_{\pi}[R(\tau)]

当我第一次看到这公式时是相当困惑:怎么突然冒出个参数 \tau ?对于这问题,如解释不当,还望高人指点:

  • R(\tau) 其实是一个抽象函数。也就是说这个函数可以在不同的情况下套用不同的具体函数;同样,参数 \tau 也是一个抽象参数。
  • 对于阶段性任务而言,具体函数表达为 J_1(\theta) = \mathbb E_{\pi}[G_1] = \mathbb E_{\pi}[V(s_1)] ,即从起始状态开始计算回合的期望状态值;
  • 对于持续性任务而言,由于没有明确的起始状态,则可以考虑以下三种计算方法(阶段性任务也适用):
  1. J_{\bar{v}}(\theta) = \mathbb E_{\pi}[V(s)] ,则以期望状态值均值作为目标函数;
  2. J_{\bar{q}}(\theta) = \mathbb E_{\pi}[Q(s,a)] ,则以期望动作值均值作为目标函数;
  3. J_{\bar{r}}(\theta) = \mathbb E_{\pi}[r] ,则以期望奖励均值作为目标函数;
  • 我们可以根据不同的情况,选用以上四种具体目标函数的任意一种。比如,在我们不清楚如何得到值函数的情况下,用一段时期内(每个阶段)的奖励均值作为计算依据,是很自然的思路——在统计期内,期望奖励越高,当然就代表策略越好啦。


既然有了目标函数,如何求得最优策略呢?

我们知道当目标函数 J(\theta) 越大,策略就越好。如何求得最优策略的 \theta 呢?有两种思路:

  • 随机搜索:直接估算权重 \theta ,无需知道目标函数 J(\theta)
  • 策略梯度:根据目标函数 J(\theta) 来求梯度更新


随机搜索

随机搜索是指随机生成权重 \theta ,然后评估策略 \pi_{\theta} 所得到的期望奖励;若期望奖励比之前高,则用 \theta 更新权重。如此不断迭代。由于该方法效率极低,因此一般情况下不采用,因此不作详细说明。大概的算法有:

  • 最陡上升爬山法:随机生成 \theta ,然后以 \sigma 为标准差随机生成多个 \theta' ,若最大的 \theta' 大于 \theta ,则更新 \theta 。不断迭代这过程直到迭代停止。
  • 模拟退火法:爬山法类似,但在更新权重的同时,会缩小 \sigma
  • 适应噪点法:与模拟退火法类似,但当找不到更好的 \theta' 时,会扩大 \sigma


策略梯度

策略梯度的基本思路是,利用目标函数 J(\theta) 来求梯度更新。更新逻辑为: \Delta \theta = \alpha \bigtriangledown _\theta J(\theta) ,其中 \alpha 为学习率(更新步长)。

然而,目标函数并不总是可导(比如当目标函数很复杂时)。这时则需要用一个估算值来代替近似梯度,即 \bigtriangledown _\theta J(\theta) = \frac{\partial J(\theta)}{\partial \theta_k} \approx \frac{J(\theta + \epsilon u_k) - J(\theta)}{\epsilon},(for\ k\in [1,n]) ,其中 k 是权重 \theta 的维度数, u_k 是第 k 维的单位向量, \epsilon 是一个非常小的数。该近似法的本质是对每一个维度进行斜率估算,以得到整个目标函数的梯度 \bigtriangledown _\theta J(\theta) ,即 \bigtriangledown _\theta J(\theta) = [\frac{\partial J(\theta)}{\partial \theta_1},\frac{\partial J(\theta)}{\partial \theta_2},...,\frac{\partial J(\theta)}{\partial \theta_n}]

以上用斜率近似求梯度的方法,每次更新都要计算一次,因此计算量相当大。因此,我们可以通过变换目标函数,使其变得更容易求导。

已知 J(\theta) = \mathbb E_{\pi}[R(\tau)] ,因此有 \bigtriangledown_{\theta} J(\theta) =\bigtriangledown_{\theta} \mathbb E_{\pi}[R(\tau)] 。直接求 \bigtriangledown_{\theta} \mathbb E_{\pi}[R(\tau)] 不太好办,因此暂且把奖励函数 R(\tau) 用随机得分函数f(x) 代替,其中 x\sim \mathbb P[x|\theta] (x是基于 \theta 的概率分布)。于是便可以做以下一系列的变换:

资料来源:Udacity深度学习课程

有了以上的变换启发,再把奖励函数替换回来,于是有:

资料来源:Udacity深度学习课程

公式说明:

  • 根据我们前面的定义 \pi(s,a,\theta) = \mathbb{P}[a|s,\theta]
  • 假如某个动作 a 的概率很低,那该动作的对数值的绝对值就很大,若这时的奖励值很高,那么就会使 \Delta \theta 更大;相反,如果 a 的概率本来就很高,那即使奖励值很高, \Delta \theta 的变化也不大。因此,用对数几率乘以奖励函数,可以起到纠正权重 \theta 的作用。

这就是策略梯度的基本更新公式。

有了基本更新公式,我们就可以用蒙特卡罗方法,通过不断地与环境互动来得到经验样本,从而估算更新值:

资料来源:Udacity深度学习课程

代码说明:

  • G_t = R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + ...
  • 每个阶段(回合)更新一次策略函数,并用于下个回合生成经验元组

到此为止,我们掌握了基于值函数(状态值函数与动作值函数)的方法,这些方法对于离散的动作空间是可行的;而对于连续的动作空间、以及要生成随机性策略,则需要应用基于策略函数的方法。然而基于策略函数的方法最大的问题在于无法衡量策略究竟是不是最优。这就需要借助值函数的方法了。结合值函数与策略函数的方法,称为行动者与评论者方法(Actor-Critic method)

行动者与评论者方法

前面提到,奖励函数 R(\tau) 是一个抽象函数。在蒙特卡罗方法中,用了 R(\tau) = G_t = R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + ... 。由于蒙特卡罗方法只能用于阶段性任务,因此对于持续性任务,该方法并不适用。一是因为无法计算奖励折扣,二是因为无法定义策略更新时机。

因此,我们需要一个能在线计算的奖励函数。比如动作值函数 Q(s,a) ,即把更新公式写成: \Delta \theta = \alpha \bigtriangledown_\theta (log \pi(S_t,A_t,\theta))Q(S_t,A_t)

那动作值函数 Q(s,a) 要怎么计算呢?可以参考之前的动作值更新思路:

\Delta Q = \alpha(R_{t+1} + \gamma Q(S_{t+1},A_{t+1}) - Q(S_t, A_t))

注意!以上公式是Sarsa0的更新公式,我们都知道这个公式只对离散的状态空间与动作空间适用。对于连续的状态空间与动作空间,动作值函数的更新公式要加上权重w来做映射:

\Delta w = \alpha(R_{t+1} + \gamma \hat q(S_{t+1},A_{t+1}, w) - \hat q(S_t, A_t, w)) \bigtriangledown_w \hat q(S_t, A_t, w)

注意!由于在前面已经用了 \alpha 作为 \Delta \theta 的学习率,因为这里需要把 \Delta w 的学习率换成 \beta

\Delta w = \beta(R_{t+1} + \gamma \hat q(S_{t+1},A_{t+1}, w) - \hat q(S_t, A_t, w)) \bigtriangledown_w \hat q(S_t, A_t, w)


于是,到此为止我们有了两个逼近函数:

  • 策略函数 \pi (s,a,\theta) ,它就好比是一个演员(Actor)专门负责做动作;
  • 值函数 \hat q(s,a,w) ,它就好比是一个评论者(Critic)专门负责给反馈;

这两个函数可以由两个不同的神经网络去逼近,并互相作用。整个作用过程如下图:

资料来源:Udacity深度学习课程

说明:

  1. 根据环境状态 S_t ,通过策略函数 \pi_{\theta} 映射得出相应的动作 A_t
  2. 这时通过 \Delta \theta 的更新公式便可以更新策略函数
  3. A_t 与环境互动,生成 R_{t+1}, S_{t+1}
  4. 这时通过 \Delta w 的更新公式便可以更新值函数
  5. 有了 S_{t+1} 与新的策略函数,便可以重复步骤1进行迭代
  6. 注意:与蒙特卡罗的回合更新不同,行动者与评论者方法是单步更新


仔细研究更新机制,我们可以有所改进:

由于 \Delta \theta = \alpha \bigtriangledown_\theta J(\theta)

又有 \bigtriangledown_{\theta}J(\theta) = \mathbb E_{\pi}[\bigtriangledown_{\theta} (log \pi(s,a,\theta))\hat q(s,a,w)]

我们之所以能计算期望值 \mathbb E_{\pi} 是因为我们用了小的学习率 \alpha 来迭代计算得到。之所以需要保持足够小的学习率,是因为在最终随机抽样时,单个样本可能会变化很大。只要是在计算期望值,就会存在相关误差。如果我们尝试去估算这个期望值,则最好使样本之间的方差尽量小,这会使得整个过程更加稳定。该方程方差的大小主要是受奖励函数 \hat q 所影响。对于每个时间步, \hat q 的值可能会变化很大,因为它基于单个奖励。这样可能会导致策略更新的步长不一。那么,如何才能减少这个方差呢?

假设动作值函数 Q(s,a) 符合正态分布,那么该分布的均值 \mu = \mathbb E_{\pi}[Q(s,a)] 。而对于状态 sQ(s,a) 是在状态 s 下整个动作空间下的分布,由于这个分布的期望值本质上就是状态值,于是有 \mu = \mathbb E_{\pi}[Q(s,a)] = V(s)。此时,如果把奖励函数表达为:

A(s,a) = Q(s,a) - V(s)A 被称为优势函数。它可以理解为“对于状态 s ,当我们采取特定动作 a ,而非随机动作时,我们可以从中得到多少额外收益”。因此,优势函数不仅会稳定学习过程,而且可以更好地区分动作。

把优势函数代入原来的得分函数,有:

  • \Delta \theta = \alpha \bigtriangledown_{\theta}(log\pi(s,a,\theta))(\hat q(s,a,w) - \hat v(s,w'))

为了进一步简化计算,避免计算两个值函数,可以用TD误差来估算优势函数,有:

  • \Delta \theta = \alpha \bigtriangledown_{\theta}(log\pi(s,a,\theta))(r + \gamma \hat v(s',w) - \hat v(s,w))

注意,这时的值函数权重的更新公式也会变为:

  • \Delta w = \beta(r + \gamma \hat v(S_{t+1},w) - \hat v(S_t,w))\bigtriangledown_w \hat v(S_t,w)

现在我们梳理一下:

Q-learning是在离散状态空间与动作空间下,寻找最优策略 \pi_* 的有效方法。但当状态空间维度过高或者在连续的状态空间下,Q-learning方法就不再适用,这时就需要用到DQN来解决高维状态空间的问题;然而,DQN只能解决高维状态空间,却不能解决高维动作空间,因为DQN是依赖最大化动作值来找到合适的策略。因此,为了解决高维动作空间的问题,就有了Policy Gradient方法。但原始Policy Gradient方法无法及时得知所得的动作值,使得性能相当不理想,于是有了Actor-Critic方法。Actor-Critic方法基本上解决了高维状态与动作空间的问题,并使性能有明显的提升。但原始的Actor-Critic方法对于复杂问题可能会不稳定。所以才有了以下的改进方法。

DDPG(Deep Deterministic Policy Gradient,深度确定性策略梯度)

DDPG是基于Actor-Critic方法框架,并引入DQN的两种高稳定性的训练思路:1, 经验回放;2, 固定Q目标。另外,在论文中提及DDPG在搭建神经网络时还用到了BN层来优化训练。

回顾Actor-Critic方法,我们知道:

  • 对于Actor函数的更新公式为(未考虑优势函数的优化):
    \Delta \theta = \alpha \bigtriangledown_\theta (log \pi(S_t,A_t,\theta))\hat q(S_t,A_t)
  • 对于Critic函数的更新公式为:
    \Delta w = \beta(R_{t+1} + \gamma \hat q(S_{t+1},A_{t+1}, w) - \hat q(S_t, A_t, w)) \bigtriangledown_w \hat q(S_t, A_t, w)

而在DDPG略有不同的是,对于Actor与Critic函数,各自都会有两个神经网络(原理参考DQN的固定Q目标优化方法)。具体参考以下伪代码(注意符号表达与之前略有不同):

资料来源:参考资料[8]

代码说明:

  • 首先随机初始化一个Critic神经网络 Q(s,a|\theta^{Q}) ,其中 \theta^Q 是网络权重;同理,随机初始化一个Actor神经网络 \mu(s|\theta^{\mu}) ,其中 \theta^{\mu} 是网络权重;
  • 再定义一个Critic目标网络 Q' ,以及一个Actor的目标网络 \mu' ,并使 \theta^{Q'} = \theta^Q\theta^{\mu'} = \theta^{\mu}
  • 初始化一个经验缓存 R
  • 对于每一个回合:
    • 初始化一个用于动作探索的随机噪点生成函数 \mathcal N
    • 重置环境并获取一个初始状态向量 s_1
    • 对于回合中的每个时间步:
      • 依据当前的策略函数、Actor网络 \mu(s_t|\theta^{\mu}) 与探索噪点函数 \mathcal N_t 生成当前的动作 a_t
      • 利用 a_t 与环境互动,得到 r_{t+1} 与新的状态 s_{t+1}
      • 把该经验元组 (s_t, a_t, r_{t+1}, s_{t+1}) 保存到经验缓存 R (注意:此处为了与之前的命名一致,没有按以上论文伪代码那样写成 r_t ,而是写成 r_{t+1} 。实质上是同一个奖励)
      • 当经验缓存的数量大于抽样批次数量N时,从经验缓存随机抽样N个经验元组
      • 使用Critic目标网络与Actor目标网络来当“真实”的值函数输出: y_i = r_i + \gamma Q'(s_{i}', \mu'(s_i'|\theta^{\mu'})|\theta^{Q'}))
        注意:此处的 i 代表第 i 个经验元组,因此为了不与其他经验元组混淆,笔者把论文的公式略为修改把 i 个经验元组中的 s_{t+1} 表达为 s_i'
      • 有了“真实值”与预估值,我们才有损失函数 L = \frac{1}{N} \sum_i (y_i - Q(s_i, a_i|\theta^Q))^2
      • 有了损失函数,才能求梯度: \bigtriangledown _{\theta^{\mu}}J = \frac{1}{N} \bigtriangledown _a Q(s, a| \theta^Q)|_{s=s_i, a=\mu(s_i)} \bigtriangledown _{\theta^{\mu}}\mu(s|\theta^{\mu})|_{s_i}
        说明:坦白说,笔者第一眼看到这公式时有点眩晕的感觉,估计能一眼看懂这公式的应该是数学系毕业的。但当笔者看完代码再回头写一遍这公式时,就觉得非常理解了。稍后再作详细分析。
      • 最后,以一个学习率 \tau 来分别更新目标网络,因此有:
        \theta ^{Q'} \leftarrow \tau \theta^Q + (1-\tau)\theta^{Q'}
        \theta^{\mu'} \leftarrow \tau \theta^{\mu} + (1- \tau) \theta^{\mu'}

以下是DDPG的学习更新代码:

def learn(self, experiences):
    """Update policy and value parameters using given batch of experience tuples."""
    # Convert experience tuples to separate arrays for each element (states, actions, rewards, etc.)
    states = np.vstack([e.state for e in experiences if e is not None])
    actions = np.array([e.action for e in experiences if e is not None]).astype(np.float32).reshape(-1, self.action_size)
    rewards = np.array([e.reward for e in experiences if e is not None]).astype(np.float32).reshape(-1, 1)
    dones = np.array([e.done for e in experiences if e is not None]).astype(np.uint8).reshape(-1, 1)
    next_states = np.vstack([e.next_state for e in experiences if e is not None])

    # 通过Actor目标网络,根据next_state生成next_action
    actions_next = self.actor_target.model.predict_on_batch(next_states)
    # 通过Critic目标网络,根据next_state与next_action生成动作值Q_targets_next
    Q_targets_next = self.critic_target.model.predict_on_batch([next_states, actions_next])

    # 通过TD目标公式求Critic网络的“真实值 y_i”(当回合结束dones=1)
    Q_targets = rewards + self.gamma * Q_targets_next * (1 - dones)
    # 以Q_targets为目标值,states与actions为输入训练Critic估值网络
    self.critic_local.model.train_on_batch(x=[states, actions], y=Q_targets)

    # 借助Tensorflow的方法获取本轮Critic估值网络的训练梯度
    action_gradients = np.reshape(self.critic_local.get_action_gradients([states, actions, 0]), (-1, self.action_size))
    # 借助Tensorflow的方法训练Actor估值网络
    self.actor_local.train_fn([states, action_gradients, 1])  # custom training function

    # 以学习率\tau来更新目标网络
    self.soft_update(self.critic_local.model, self.critic_target.model)
    self.soft_update(self.actor_local.model, self.actor_target.model)

以上代码中,最难的部分仍然是Actor估值网络与Critic估值网络的权重更新部分。伪代码的难点在于数学公式的理解,而代码的难点在于Tensorflow(或其他)框架的使用。

这就是DDPG理论部分的全部内容。



以上便是强化学习相关课程的全部内容整理。笔者水平有限,若有勘误,欢迎指正。


延伸阅读:

为你分享73篇论文解决深度强化学习的18个关键问题 / 水缘泡泡


参考资料:

  1. Udacity深度学习进阶课程P5,课程优惠码:6E1CAAC8
  2. Reinforcement Learning: An Introduction》,Richard S. Sutton and Andrew G. Barto
  3. 蒙特卡洛方法到底有什么用 - CSDN博客
  4. Monte Carlo method, en.wikipedia.org
  5. Deep Reinforcement Learning: Pong from Pixels
  6. Deep Learning》, 5.5_Maximum Likelihood Estimation
  7. Human-level control through deep reinforcement learning》, nature.com
  8. [1509.02971] Continuous control with deep reinforcement learning
  9. Deep Deterministic Policy Gradients in TensorFlow
编辑于 2018-05-31

文章被以下专栏收录