8.4 噪声网络
下一个将要研究的改进解决了RL的另一个问题:环境探索。我们要借鉴的论文为“Noisy Networks for Exploration”[4],它有一个非常简单的思想,即在训练过程中学习探索特征,而不是单独定制探索的策略。
基础DQN通过选择随机动作来完成探索,随机选取会依据特定的超参数epsilon,它会随着时间的增长慢慢地从1.0(完全随机选择动作)降至一个小比例0.1或0.02。这个流程适用于片段较短的简单环境,在游戏过程中不会有太多不稳定的情况,但是即使是在这样简单的情况下,它也需要调优参数来让训练更高效。
在“Noisy Networks for Exploration”论文中,作者提出了一个非常简单的解决方案,但是效果很好。他们在全连接层中加入噪声,并通过反向传播在训练过程中调整噪声参数。当然了,不要将这个方法和“用网络来决定在哪里进行探索”相混淆,后者是一种更为复杂的方法,也得到了广泛的支持(例如,请参见关于内在动机和基于计数的探索方法[5-6])。我们会在第21章讨论高级探索技术。
论文作者提出了两种添加噪声的方法,根据他们的实验这两种方法都有效,但是它们具有不同的计算开销:
1)独立高斯噪声:对于全连接层中的每个权重,都加入一个从正态分布中获取的随机值。噪声的参数μ和σ被存在层中并使用反向传播训练,如同我们训练标准的线性层中的权重一样。以与线性层相同的方式计算这种“噪声层”的输出。
2)分解高斯噪声:为了最小化采样的随机数数量,作者提出只保留两个随机向量:一个和输入的大小一样,另一个和层的输出大小一样。然后,通过计算向量的外积来创建该层的一个随机矩阵。
8.4.1 实现
在PyTorch中,两种方法都可以以非常简单直接的方式来实现。我们需要做的就是创建一个和nn.Linear
层等价的层,并在每次forward()
被调用时随机采样一些额外的值。两种噪声层的实现在Chapter08/lib/dqn_extra.py
的NoisyLinear
类(独立高斯噪声)和NoisyFactorizedLinear
类(分解高斯噪声)中。
在构造函数中,我们创建了σ的矩阵。(μ值被存在从nn.Linear
继承过来的矩阵中[1]。)为了让σ可训练,需要将张量包装在nn.Parameter
里。
register_buffer
方法在神经网络中创建一个张量,它在反向传播时不会更新,但是会被nn.Module
的机制处理(例如,当cuda()
被调用时它会被复制到GPU)。为了层中的偏差需要创建一个额外的参数和缓冲区。使用的σ的初始值(0.017)取自本节开头引用的“Noisy Networks for Exploration”论文。最后,我们调用reset_parameters()
方法,它重写了父类nn.Linear
的reset_parameters()
方法,用来支持层的初始化。
在reset_parameters()
方法中,参考论文对nn.Linear
的权重和偏差进行了初始化处理。
在forward()
方法中,从权重和偏差缓冲区中随机采样噪声,并以与nn.Linear
相同的方式对输入的数据执行线性转换。
分解高斯噪声的工作方式也类似,并且其结果相差不大,所以为了完整性这里只贴出代码。如果你对这个方法好奇,可以在论文[4]中查找它的详细信息和方程。
从实现的角度看,这就结束了。我们现在要做的就是将nn.Linear
(这是DQN神经网络中的最后两层)换成NoisyLinear
层(如果需要,也可以替换成NoisyFactorized-Linear
),让基础DQN转变成噪声网络版本。当然,你必须要把ε-greedy策略相关的代码都移除掉。
为了检查训练时内部的噪声级别,我们可以显示噪声层的噪声信噪比(Signal-to-Noise Ratio,SNR),表示成RMS(μ)/RMS(σ)的比,RMS是相应权重的均方根。在本例中,SNR展示了噪声层中的稳定部分比注入的噪声大多少倍。
8.4.2 结果
训练之后,TensorBoard的图表展示了更好的训练动态(见图8.8)。模型可以在少于60万帧的情况下达到18的平均分。
图8.8 Noisy netwok和基础DQN的对比
在检查SNR图(见图8.9)之后,你可能注意到两层的噪声级别都下降得很快。第一层从1降到了1/2.5的噪声比率。第二层则更有意思,它的噪声级别从1/3下降到1/15,但是在25万帧之后(基本和原始奖励提升至20分的时间差不多),最后一层的噪声级别开始回升了,迫使智能体更多地探索环境。这很有道理,因为在达到高分之后,智能体基本知道如何发挥出良好的水平,但还是需要“磨炼”它的动作以进一步提高分数。
图8.9 训练过程中噪声级别的变化
[1]即nn.Linear.weight
和nn.Linear.bias
。