在强化学习 (十三) 策略梯度(Policy Gradient) 中, 我们讲到了基于策略 (Policy Based) 的强化学习方法的基本思路, 并讨论了蒙特卡罗策略梯度 reinforce 算法. 但是由于该算法需要完整的状态序列, 同时单独对策略函数进行迭代更新, 不太容易收敛.
在本篇我们讨论策略 (Policy Based) 和价值 (Value Based) 相结合的方法: Actor-Critic 算法.
本文主要参考了 Sutton 的强化学习书第 13 章和 UCL 强化学习讲义的第 7 讲.
1. Actor-Critic 算法简介
Actor-Critic 从名字上看包括两部分, 演员 (Actor) 和评价者 (Critic). 其中 Actor 使用我们上一节讲到的策略函数, 负责生成动作(Action) 并和环境交互. 而 Critic 使用我们之前讲到了的价值函数, 负责评估 Actor 的表现, 并指导 Actor 下一阶段的动作.
回想我们上一篇的策略梯度, 策略函数就是我们的 Actor, 但是那里是没有 Critic 的, 我们当时使用了蒙特卡罗法来计算每一步的价值部分替代了 Critic 的功能, 但是场景比较受限. 因此现在我们使用类似 DQN 中用的价值函数来替代蒙特卡罗法, 作为一个比较通用的 Critic.
也就是说在 Actor-Critic 算法中, 我们需要做两组近似, 第一组是策略函数的近似:$$\pi_{\theta}(s,a) = P(a|s,\theta)\approx \pi(a|s)$$
第二组是价值函数的近似, 对于状态价值和动作价值函数分别是:$$\hat{v}(s, w) \approx v_{\pi}(s)$$$$\hat{q}(s,a,w) \approx q_{\pi}(s,a)$$
对于我们上一节讲到的蒙特卡罗策略梯度 reinforce 算法, 我们需要进行改造才能变成 Actor-Critic 算法.
首先, 在蒙特卡罗策略梯度 reinforce 算法中, 我们的策略的参数更新公式是:$$\theta = \theta + \alpha \nabla_{\theta}log \pi_{\theta}(s_t,a_t) v_t$$
梯度更新部分中,$\nabla_{\theta}log \pi_{\theta}(s_t,a_t) $ 是我们的分值函数, 不用动, 要变成 Actor 的话改动的是 $v_t$, 这块不能再使用蒙特卡罗法来得到, 而应该从 Critic 得到.
而对于 Critic 来说, 这块是新的, 不过我们完全可以参考之前 DQN 的做法, 即用一个 Q 网络来做为 Critic, 这个 Q 网络的输入可以是状态, 而输出是每个动作的价值或者最优动作的价值.
现在我们汇总来说, 就是 Critic 通过 Q 网络计算状态的最优价值 $v_t$, 而 Actor 利用 $v_t$ 这个最优价值迭代更新策略函数的参数 $\theta$, 进而选择动作, 并得到反馈和新的状态, Critic 使用反馈和新的状态更新 Q 网络参数 $w$, 在后面 Critic 会使用新的网络参数 $w$ 来帮 Actor 计算状态的最优价值 $v_t$.
2. Actor-Critic 算法可选形式
在上一节我们已经对 Actor-Critic 算法的流程做了一个初步的总结, 不过有一个可以注意的点就是, 我们对于 Critic 评估的点选择是和上一篇策略梯度一样的状态价值 $v_t$, 实际上, 我们还可以选择很多其他的指标来做为 Critic 的评估点. 而目前可以使用的 Actor-Critic 评估点主要有:
a) 基于状态价值: 这是我们上一节使用的评估点, 这样 Actor 的策略函数参数更新的法公式是:$$\theta = \theta + \alpha \nabla_{\theta}log \pi_{\theta}(s_t,a_t) V(s,w)$$
b) 基于动作价值: 在 DQN 中, 我们一般使用的都是动作价值函数 Q 来做价值评估, 这样 Actor 的策略函数参数更新的法公式是:$$\theta = \theta + \alpha \nabla_{\theta}log \pi_{\theta}(s_t,a_t) Q(s,a,w)$$
c) 基于 TD 误差: 在强化学习 (五) 用时序差分法 (TD) 求解中, 我们讲到了 TD 误差, 它的表达式是 $\delta(t) = R_{t+1} + \gamma V(S_{t+1}) -V(S_t)$ 或者 $\delta(t) = R_{t+1} + \gamma Q(S_{t+1},A_{t+1} ) -Q(S_t,A_t)$, 这样 Actor 的策略函数参数更新的法公式是:$$\theta = \theta + \alpha \nabla_{\theta}log \pi_{\theta}(s_t,a_t)\delta(t)$$
d) 基于优势函数: 在强化学习(十二) Dueling DQN 中, 我们讲到过优势函数 A 的定义:$A(S,A,w,\beta) = Q(S,A, w, \alpha, \beta) - V(S,w,\alpha) $, 即动作价值函数和状态价值函数的差值. 这样 Actor 的策略函数参数更新的法公式是:$$\theta = \theta + \alpha \nabla_{\theta}log \pi_{\theta}(s_t,a_t)A(S,A,w,\beta)$$
e) 基于 TD($\lambda$)误差: 一般都是基于后向 TD($\lambda$)误差, 在强化学习 (五) 用时序差分法 (TD) 求解中也有讲到, 是 TD 误差和效用迹 E 的乘积. 这样 Actor 的策略函数参数更新的法公式是:$$\theta = \theta + \alpha \nabla_{\theta}log \pi_{\theta}(s_t,a_t)\delta(t)E_(t)$$
对于 Critic 本身的模型参数 $w$, 一般都是使用均方误差损失函数来做做迭代更新, 类似之前 DQN 系列中所讲的迭代方法. 如果我们使用的是最简单的线性 Q 函数, 比如 $Q(s,a ,w) = \phi(s,a)^Tw$, 则 Critic 本身的模型参数 $w$ 的更新公式可以表示为:$$\delta = R_{t+1} + \gamma Q(S_{t+1},A_{t+1} ) -Q(S_t,A_t)$$$$w = w+ \beta\delta\phi(s,a)$$
通过对均方误差损失函数求导可以很容易的得到上式. 当然实际应用中, 我们一般不使用线性 Q 函数, 而使用神经网络表示状态和 Q 值的关系.
3. Actor-Critic 算法流程
这里给一个 Actor-Critic 算法的流程总结, 评估点基于 TD 误差, Critic 使用神经网络来计算 TD 误差并更新网络参数, Actor 也使用神经网络来更新网络参数
算法输入: 迭代轮数 $T$, 状态特征维度 $n$, 动作集 $A$, 步长 $\alpha,\beta$, 衰减因子 $\gamma$, 探索率 $\epsilon$, Critic 网络结构和 Actor 网络结构.
输出: Actor 网络参数 $\theta$, Critic 网络参数 $w$
1. 随机初始化所有的状态和动作对应的价值 $Q$. 随机初始化 Critic 网络的所有参数 $w$. 随机初始化 Actor 网络的所有参数 $\theta$.
2. for i from 1 to T, 进行迭代.
a) 初始化 S 为当前状态序列的第一个状态, 拿到其特征向量 $\phi(S)$
b) 在 Actor 网络中使用 $\phi(S)$ 作为输入, 输出动作 $A$, 基于动作 $A$ 得到新的状态 $S'$, 反馈 $R$.
c) 在 Critic 网络中分别使用 $\phi(S), \phi(S'')$ 作为输入, 得到 Q 值输出 $V(S), V(S')$
d) 计算 TD 误差 $\delta = R +V(S') -V(S) $
e) 使用均方差损失函数 $\sum\limits(R +V(S') -V(S,w))^2$ 作 Critic 网络参数 $w$ 的梯度更新
f) 更新 Actor 网络参数 $\theta$:$$\theta = \theta + \alpha \nabla_{\theta}log \pi_{\theta}(S_t,A)\delta$$
对于 Actor 的分值函数 $\nabla_{\theta}log \pi_{\theta}(S_t,A)$, 可以选择 softmax 或者高斯分值函数.
上述 Actor-Critic 算法已经是一个很好的算法框架, 但是离实际应用还比较远. 主要原因是这里有两个神经网络, 都需要梯度更新, 而且互相依赖. 但是了解这个算法过程后, 其他基于 Actor-Critic 的算法就好理解了.
4. Actor-Critic 算法实例
下面我们用一个具体的例子来演示上面的 Actor-Critic 算法. 仍然使用了 OpenAI Gym 中的 CartPole-v0 游戏来作为我们算法应用. CartPole-v0 游戏的介绍参见这里 https://github.com/openai/gym/wiki/CartPole-v0 . 它比较简单, 基本要求就是控制下面的 cart 移动使连接在上面的 pole 保持垂直不倒. 这个任务只有两个离散动作, 要么向左用力, 要么向右用力. 而 state 状态就是这个 cart 的位置和速度, pole 的角度和角速度, 4 维的特征. 坚持到 200 分的奖励则为过关.
算法流程可以参考上面的第三节, 这里的分值函数我们使用的是 softmax 函数, 和上一片的类似. 完整的代码参见我的 GitHub:
代码主要分为两部分, 第一部分是 Actor, 第二部分是 Critic. 对于 Actor 部分, 大家可以和上一篇策略梯度的代码对比, 改动并不大, 主要区别在于梯度更新部分, 策略梯度使用是蒙特卡罗法计算出的价值 $v(t)$, 则我们的 actor 使用的是 TD 误差.
在策略梯度部分, 对应的位置如下:
self.loss = tf.reduce_mean(self.neg_log_prob * self.tf_vt) # reward guided loss
则我们的 Actor 对应的位置的代码是:
self.exp = tf.reduce_mean(self.neg_log_prob * self.td_error)
此处要注意的是, 由于使用的是 TD 误差, 而不是价值 $v(t)$, 此处需要最大化 self.exp, 而不是最小化它, 这点和策略梯度不同. 对应的 Actor 代码为:
- # 这里需要最大化当前策略的价值, 因此需要最大化 self.exp, 即最小化 - self.exp
- self.train_op = tf.train.AdamOptimizer(LEARNING_RATE).minimize(-self.exp)
除此之外, Actor 部分的代码和策略梯度的代码区别并不大.
对于 Critic 部分, 我们使用了类似于 DQN 的三层神经网络. 不过我们简化了这个网络的输出, 只有一维输出值, 而不是之前 DQN 使用的有多少个可选动作, 就有多少维输出值. 网络结构如下:
- def create_Q_network(self):
- # network weights
- W1q = self.weight_variable([self.state_dim, 20])
- b1q = self.bias_variable([20])
- W2q = self.weight_variable([20, 1])
- b2q = self.bias_variable([1])
- self.state_input = tf.placeholder(tf.float32, [1, self.state_dim], "state")
- # hidden layers
- h_layerq = tf.nn.relu(tf.matmul(self.state_input, W1q) + b1q)
- # Q Value layer
- self.Q_value = tf.matmul(h_layerq, W2q) + b2q
和之前的 DQN 相比, 这里还有一个区别就是我们的 critic 没有使用 DQN 的经验回放, 只是使用了反馈和当前网络在下一个状态的输出来拟合当前状态.
对于算法中 Actor 和 Critic 交互的逻辑, 在 main 函数中:
- for step in range(STEP):
- action = actor.choose_action(state) # e-greedy action for train
- next_state,reward,done,_ = env.step(action)
- td_error = critic.train_Q_network(state, reward, next_state) # gradient = grad[r + gamma * V(s_) - V(s)]
- actor.learn(state, action, td_error) # true_gradient = grad[logPi(s,a) * td_error]
- state = next_state
- if done:
- break
大家对照第三节的算法流程和代码应该可以比较容易理清这个过程. 但是这个程序很难收敛. 因此大家跑了后发现分数总是很低的话是可以理解的. 我们需要优化这个问题.
5. Actor-Critic 算法小结
基本版的 Actor-Critic 算法虽然思路很好, 但是由于难收敛的原因, 还需要做改进.
目前改进的比较好的有两个经典算法, 一个是 DDPG 算法, 使用了双 Actor 神经网络和双 Critic 神经网络的方法来改善收敛性. 这个方法我们在从 DQN 到 Nature DQN 的过程中已经用过一次了. 另一个是 A3C 算法, 使用了多线程的方式, 一个主线程负责更新 Actor 和 Critic 的参数, 多个辅线程负责分别和环境交互, 得到梯度更新值, 汇总更新主线程的参数. 而所有的辅线程会定期从主线程更新网络参数. 这些辅线程起到了类似 DQN 中经验回放的作用, 但是效果更好.
在后面的文章中, 我们会继续讨论 DDPG 和 A3C.
来源: https://www.cnblogs.com/pinard/p/10272023.html