8.2 N步DQN
我们将实现和评估的第一个改进是一个相当古老的办法。它由Rchard Sutton首次在论文“Learning to Predict by the Methods of Temporal Differences”[2]中引入。为了弄清楚这个方法,再看一下Q-learning中用到的Bellman更新:
等式是递归的,意味着可以根据表达式自己来表示Q(st+1, at+1),结果如下:
ra,t+1意味着在t+1时刻执行动作a后的立即奖励。然而,如果假设t+1时刻选择的动作a是最优的或接近最优的,我们可以省去maxa操作,得到:
这个值可以一次又一次地展开。你可能已经猜到了,通过将1步状态转移替换成更长的n步状态转移序列,就可以将这种展开轻易地应用到DQN更新中。为了理解为什么这种展开会提升训练速度,我们来考虑图8.3展示的示例。图中有一个简单的4个状态(s1, s2, s3, s4)的环境,并且除了终结状态s4,每个状态都只有一个可用的动作。
图8.3 一个简单环境的状态转移图
所以在1步的情况下会发生什么?我们总共有三个可能的更新(不使用max,因为这里只有一个可用动作):
1)Q(s1, a)←r1+γQ(s2, a)。
2)Q(s2, a)←r2+γQ(s3, a)。
3)Q(s3, a)←r3。
想象一下,在训练开始时,顺序地完成前面的更新。前两个更新是没有用的,因为当前Q(s2, a)和Q(s2, a)是不对的,并且只包含初始的随机值。唯一有用的更新是第3个更新,它将奖励r3正确地赋给终结状态前的状态s3。
现在来完成一次又一次的更新。在第2次迭代,正确的值被赋给了Q(s2, a),但是Q(s1, a)的更新还是不对的。只有在第3次迭代时才能给所有的Q赋上正确的值。所以,即使在1步的情况下,它也需要3步才能将正确的值传播给所有的状态。
现在考虑一下2步的情况。这种情况下还是有3个更新:
1)Q(s1, a)←r1+γr2+γ2Q(s3, a)。
2)Q(s2, a)←r2+γr3。
3)Q(s3, a)←r3。
在这种情况下,第一次更新迭代会将正确的值赋给Q(s2, a)和Q(s3, a)。第二次迭代,Q(s1, a)也会被正确地更新。所以多步可以提升值的传播速度,也就是会加速收敛。你可能会想,“如果它这么有用,那就直接提前展开100步Bellman方程好了。它将使收敛速度提高100倍吗?”很不幸的是,答案是不会。
和我们期望所不同的是,DQN将完全无法收敛。要理解为什么,我们再次回到展开过程,尤其是将maxa丢掉的地方。它正确吗?严格来说,不是的。我们省略了中间步骤的max运算,并假设基于经验(或者策略)选择的动作是最优的,但它其实不是最优的,例如,在训练一开始智能体的动作就是随机的。在这种情况下,计算出来的Q(st, at)值可能比状态的最优值要小一些(因为一些步骤会随机选择,而不是沿着由最大Q值计算出来的路径前进的)。展开越多步的Bellman方程,更新可能会越不准确。
庞大的经验回放缓冲区可能会让情况变得更糟,因为它增加了从先前劣等策略(取决于之前不准确的Q近似)获取状态转移的概率。这将导致当前Q近似的错误更新,所以它很轻易就破坏了训练的进度。正如我们在第4章谈及RL方法的分类时所提到的那样,这个问题是RL方法的基本特征。
我们有两个大类方法:离线策略和在线策略方法。第一类离线策略方法不依赖于“数据的更新”。例如,简单DQN是离线策略的,意味着我们可以使用好几百万步之前从环境中采样得到的很旧的数据,用这些数据来训练还是会很有效。这是因为我们使用立即奖励加上带折扣的当前近似最优动作值来更新动作的Q值Q(st, at)。即使at是随机采样的也没有关系,因为于状态st而言,对于特定动作at的更新是正确的。这就是为什么使用离线策略方法,我们可以使用非常大的经验缓冲区来让数据更符合独立同分布(i.i.d.)。
在线策略方法在很大程度上取决于正在更新的当前策略要采样的训练数据。这是因为在线策略方法试图间接(比如之前的N步DQN)或直接(本书的第三部分全部在讨论这些方法)地改善当前的策略。
那么哪种方法更好?这要分情况。离线策略方法允许你基于之前的大量历史数据,甚至是人类的构造数据来训练,但它们通常收敛得比较慢。在线策略方法通常更快,但是需要从环境采样更多新鲜的数据,这可能很费力。想象一下使用在线策略方法的无人驾驶汽车。在此系统学会墙和树是它应该绕开的东西之前,它将撞毁大量的汽车。
你可能有一个问题:如果“n步”会变成一种在线策略方法,那为什么还要谈论N步DQN,这将使我们丰富的回放缓冲区毫无用处?实际上,这通常不是非黑即白的。你仍然可以使用N步DQN,因为它有助于加速DQN的训练,但是需要谨慎地选择步长n。较小的2或3步通常效果很好,因为经验缓冲区的轨迹和1步转移时的区别不大。在这种情况下,收敛速度通常会成比例地提升,但是较大的n值可能会破坏训练过程。所以步长n是需要调优的,收敛速度的提升让这样的调优是值得的。
8.2.1 实现
因为ExperienceSourceFirstLast
类已经支持多步Bellman展开了,所以N步DQN很简单。针对基础DQN做两个改动就可以将其变成n步的版本:
- 在创建
ExperienceSourceFirstLast
时为steps_count
参数传入想要展开的步数。 - 给
calc_loss_dqn
函数传入一个正确的γ。这个改动很容易被忽视,不改的话可能会不利于收敛。由于Bellman方程现在是n步的,经验链最后一个状态的折扣因子不再是γ而是γn。
你可以在Chapter08/02_dqn_n_steps.py
找到完整的示例代码,这里只给出改动的代码:
args.n
的值是传入的命令行参数中的步数,默认使用4步。另一个修改之处是传给calc_loss_dqn
函数的gamma
:
8.2.2 结果
训练模块Chapter08/02_dqn_n_steps.py
可以和之前一样启动,使用一个额外的命令行选项-n
,它用来指定Bellman方程展开的步数。
图8.4是基线和N步DQN的奖励和片段步数图,其中n等于2和3。如你所见,Bellman展开极大地提升了收敛速度。
图8.4 基础(单步)DQN(对应基线)和N步DQN的奖励和片段步数
可以看到,3步DQN的收敛速度是普通DQN的两倍,这是一个很不错的改进。那么,对于更大的n结果会怎样呢?图8.5展示了n等于3、4、5和6时的奖励动态。
图8.5 多步DQN之间的比较
如你所见,从3步到4步确实有一点提升,但是比之前提升得少很多。n=5基本和n=4差不多,n=6则表现得更差,所以,在本例中,n=4看起来是最优的。