深度强化学习实践(原书第2版)
上QQ阅读APP看书,第一时间看更新

8.3 Double DQN

关于如何改善基础DQN的下一个富有成果的想法来自DeepMind研究人员的论文,该论文名为“Deep Reinforcement Learning with Double Q-Learning”[3]。在论文中,作者证明了基础DQN倾向于过高估计Q值,这可能对训练效果有害,有时可能会得到一个次优策略。造成这种情况的根本原因是Bellman方程中的max运算,但是严格的证明太复杂,此处做省略处理。为解决此问题,作者建议对Bellman更新进行一些修改。

在基础DQN中,目标Q值为:

170-02

Q'(st+1, a)是使用目标网络计算得到的Q值,所以我们每n步用训练网络对其更新一次。论文的作者建议使用训练网络来选择动作,但是使用目标网络的Q值。所以新的目标Q值为:

170-03

作者证明了这个小改动可以完美地修复Q值高估的问题,他们称这个新的架构为Double DQN。

8.3.1 实现

核心实现很简单,只需要稍微改动一下损失函数即可。我们来进一步比较一下基础DQN和Double DQN产生的动作。为此,我们需要保存一个随机的状态集合,并定期计算评估集合中每个状态的最优动作。

完整的示例代码在Chapter08/03_dqn_double.py中。我们先看一下损失函数:

171-01

额外的double参数决定在执行哪个动作的时候,打开或关闭Double DQN的计算方式。

171-02

前面的片段和之前是一样的。

171-03

这里和基础DQN的损失函数有点不同。如果Double DQN激活,则计算下一个状态要执行的最优动作时会使用训练网络,但是计算这个动作的对应价值时使用目标网络。当然,这部分可以用更快的方式实现,即将next_states_vstates_v合起来,只调用训练网络一次,但是这也会使代码变得不够直观。

函数的剩余部分是一样的:将完成的片段隐藏并计算网络预测出来的Q值和估算的Q值之间的均方误差(MSE)损失。最后再考虑一个函数,它计算所保存的状态价值:

171-04

这没什么复杂的:只是将保存的状态数组分成长度相等的批,并将每一批传给网络以获取动作的价值,并根据这些价值,选择价值最大的动作(针对每一个状态),并计算这些价值的平均值。因为状态数组在整个训练过程中是固定的,并且这个数组足够大(在代码中保存了1000个状态),我们可以比较这两个DQN变体的平均价值的动态。

03_dqn_double.py文件的其余内容基本一样,两个差异点是使用修改过后的损失函数,并保留了随机采样的1000个状态以进行定期评估。

8.3.2 结果

为了训练Double DQN,传入--double命令行参数来使扩展代码生效:

172-01

为了比较它和基础DQN的Q值,不传--double参数再训练一边。训练需要花费一点时间,取决于计算能力。使用GTX 1080 Ti,100万帧需要花费约2小时。此外,我注意到double扩展版本比基础版本更难收敛。使用基础DQN,大概10次中有一次会收敛失败,但是使用double扩展的版本,大概3次中就有一次收敛失败。很有可能需要调整超参数,但是我们只比较在不触及超参数的情况下double扩展带来的性能收益。

无论如何,可以从图8.6中看到,Double DQN显现出了更好的奖励动态(片段的平均奖励更早增长),但是在最终解决游戏的时候和基础DQN的奖励是一样的。

172-02

图8.6 Double DQN和基础DQN的奖励动态

除了标准指标,示例还输出了保存的状态集的平均价值,如图8.7所示。基础DQN的确高估了价值,所以它的价值在一定水平之后会下降。相比之下,Double DQN的增长更为一致。在本例中,Double DQN对训练时间只有一点影响,但这不意味着Double DQN是没用的,因为Pong是一个简单的环境。在更复杂的游戏中,Double DQN可以得到更好的结果。

173-01

图8.7 用网络预测所保存的状态的价值