![深度强化学习实践(原书第2版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/297/40216297/b_40216297.jpg)
8.3 Double DQN
关于如何改善基础DQN的下一个富有成果的想法来自DeepMind研究人员的论文,该论文名为“Deep Reinforcement Learning with Double Q-Learning”[3]。在论文中,作者证明了基础DQN倾向于过高估计Q值,这可能对训练效果有害,有时可能会得到一个次优策略。造成这种情况的根本原因是Bellman方程中的max运算,但是严格的证明太复杂,此处做省略处理。为解决此问题,作者建议对Bellman更新进行一些修改。
在基础DQN中,目标Q值为:
![170-02](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/170-02.jpg?sign=1739291685-eOgiPWXMkTWOG7nqEtJJr4zeDM0C0V8B-0-fa9c9886315dbbc9099562c2b79e53e9)
Q'(st+1, a)是使用目标网络计算得到的Q值,所以我们每n步用训练网络对其更新一次。论文的作者建议使用训练网络来选择动作,但是使用目标网络的Q值。所以新的目标Q值为:
![170-03](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/170-03.jpg?sign=1739291685-B9hDzus9fFt4YgmiOt0qiaCtAsO7qMT1-0-d31c55d7d77eab2deb4d57f72e2f6097)
作者证明了这个小改动可以完美地修复Q值高估的问题,他们称这个新的架构为Double DQN。
8.3.1 实现
核心实现很简单,只需要稍微改动一下损失函数即可。我们来进一步比较一下基础DQN和Double DQN产生的动作。为此,我们需要保存一个随机的状态集合,并定期计算评估集合中每个状态的最优动作。
完整的示例代码在Chapter08/03_dqn_double.py
中。我们先看一下损失函数:
![171-01](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/171-01.jpg?sign=1739291685-cjRFHub6iiIfGZv7qebfL6UNUsl5QM8K-0-f0025406cf8dc8cf3dd1474785210c52)
额外的double
参数决定在执行哪个动作的时候,打开或关闭Double DQN的计算方式。
![171-02](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/171-02.jpg?sign=1739291685-RGxTX16gKxwo5CqTiaHfweBlCjQqZMPJ-0-fbcd4bdbfaddcb02c61233af181997f1)
前面的片段和之前是一样的。
![171-03](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/171-03.jpg?sign=1739291685-chz9kFfUKPh43YFnGhaaf5z8SPtHp1cs-0-3ed098dd55a07725717e19661e55b972)
这里和基础DQN的损失函数有点不同。如果Double DQN激活,则计算下一个状态要执行的最优动作时会使用训练网络,但是计算这个动作的对应价值时使用目标网络。当然,这部分可以用更快的方式实现,即将next_states_v
和states_v
合起来,只调用训练网络一次,但是这也会使代码变得不够直观。
函数的剩余部分是一样的:将完成的片段隐藏并计算网络预测出来的Q值和估算的Q值之间的均方误差(MSE)损失。最后再考虑一个函数,它计算所保存的状态价值:
![171-04](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/171-04.jpg?sign=1739291685-TZFAkI73EQgjvUGBbcKcB0aEo2wUbKpe-0-a831b2eba89d54946311291de130fd85)
这没什么复杂的:只是将保存的状态数组分成长度相等的批,并将每一批传给网络以获取动作的价值,并根据这些价值,选择价值最大的动作(针对每一个状态),并计算这些价值的平均值。因为状态数组在整个训练过程中是固定的,并且这个数组足够大(在代码中保存了1000个状态),我们可以比较这两个DQN变体的平均价值的动态。
03_dqn_double.py
文件的其余内容基本一样,两个差异点是使用修改过后的损失函数,并保留了随机采样的1000个状态以进行定期评估。
8.3.2 结果
为了训练Double DQN,传入--double
命令行参数来使扩展代码生效:
![172-01](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/172-01.jpg?sign=1739291685-SS12UInT8IDpnyGIA6aDWn27Xi2VK4Kd-0-71a57f3261d9710a9139766a45ec2a8b)
为了比较它和基础DQN的Q值,不传--double
参数再训练一边。训练需要花费一点时间,取决于计算能力。使用GTX 1080 Ti,100万帧需要花费约2小时。此外,我注意到double扩展版本比基础版本更难收敛。使用基础DQN,大概10次中有一次会收敛失败,但是使用double扩展的版本,大概3次中就有一次收敛失败。很有可能需要调整超参数,但是我们只比较在不触及超参数的情况下double扩展带来的性能收益。
无论如何,可以从图8.6中看到,Double DQN显现出了更好的奖励动态(片段的平均奖励更早增长),但是在最终解决游戏的时候和基础DQN的奖励是一样的。
![172-02](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/172-02.jpg?sign=1739291685-uF6IKO71IL0oY8gcymncIEVW4QF4Ehg3-0-a96e56ab8efbafb2b8b59cea3e5bcd57)
图8.6 Double DQN和基础DQN的奖励动态
除了标准指标,示例还输出了保存的状态集的平均价值,如图8.7所示。基础DQN的确高估了价值,所以它的价值在一定水平之后会下降。相比之下,Double DQN的增长更为一致。在本例中,Double DQN对训练时间只有一点影响,但这不意味着Double DQN是没用的,因为Pong是一个简单的环境。在更复杂的游戏中,Double DQN可以得到更好的结果。
![173-01](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/173-01.jpg?sign=1739291685-CgEVaV4nqzmc7vs7TNFJnlCHhcSJe4jQ-0-5e67ad04a4e493e3f321b1aecd051c73)
图8.7 用网络预测所保存的状态的价值