From 1e62262a2873b4534c0e20475228ddb003bfb0e1 Mon Sep 17 00:00:00 2001
From: johnjim0816 <39483938+johnjim0816@users.noreply.github.com>
Date: Sun, 20 Aug 2023 16:52:34 +0800
Subject: [PATCH] update ch8
---
docs/ch8/main.md | 428 +++++++-----
.../NoisyDQN_CartPole-v1_training_curve.png | Bin 0 -> 45724 bytes
.../ch8/PERDQN_CartPole-v1_training_curve.png | Bin 0 -> 48768 bytes
docs/figs/ch8/per_dqn_pseu.png | Bin 199409 -> 199317 bytes
notebooks/NoisyDQN.ipynb | 582 ++++++++++++++++
notebooks/PER_DQN.ipynb | 644 ++++++++++++++++++
pseudocodes/pseudo.tex | 2 +-
pseudocodes/pseudo_without_notes.pdf | Bin 199087 -> 196051 bytes
pseudocodes/pseudo_without_notes.tex | 35 +-
pseudocodes/texput.log | 21 +
10 files changed, 1510 insertions(+), 202 deletions(-)
create mode 100644 docs/figs/ch8/NoisyDQN_CartPole-v1_training_curve.png
create mode 100644 docs/figs/ch8/PERDQN_CartPole-v1_training_curve.png
create mode 100644 notebooks/NoisyDQN.ipynb
create mode 100644 notebooks/PER_DQN.ipynb
create mode 100644 pseudocodes/texput.log
diff --git a/docs/ch8/main.md b/docs/ch8/main.md
index 3e79948..dcd1052 100644
--- a/docs/ch8/main.md
+++ b/docs/ch8/main.md
@@ -85,35 +85,219 @@ $\qquad$ 总的来讲,$\text{Dueling DQN}$ 算法在某些情况下相对于 $
## Noisy DQN 算法
-Noisy DQN 算法③ 也是通过优化网络结构的方法来提升 DQN 算法的性能,但与 Dueling 算法不同的是,它的目的并不是为了提高 $Q$ 值的估计,而是增强网络的探索能力。
+$\qquad$ $\text{Noisy DQN}$ 算法③ 也是通过优化网络结构的方法来提升 DQN 算法的性能,但与 Dueling 算法不同的是,它的目的并不是为了提高 $Q$ 值的估计,而是增强网络的探索能力。
-> ③ 论文链接:https://arxiv.org/abs/1706.10295
+> ③ Fortunato M , Azar M G , Piot B ,et al.Noisy Networks for Exploration[J]. 2017.DOI:10.48550/arXiv.1706.10295.
-从 Q-learning 算法开始,我们就讲到了探索策略以及探索-利用窘境的问题,常见的 $\varepsilon-greedy$ 策略是从智能体与环境的交互过程改善探索能力,以避免陷入局部最优解。而在深度强化学习中,由于引入了深度学习,深度学习本身也会因为网络模型限制或者梯度下降方法陷入局部最优解问题。也就是说,深度强化学习既要考虑与环境交互过程中的探索能力,也要考虑深度模型本身的探索能力,从而尽量避免陷入局部最优解的困境之中,这也是为什么经常有人会说强化学习比深度学习更难“炼丹”的原因之一。
+$\qquad$ 从 $\text{Q-learning}$ 算法开始,我们就讲到了探索-利用平衡的问题,常见的 $\varepsilon-greedy$ 策略是从智能体与环境的交互过程改善探索能力,以避免陷入局部最优解。而在深度强化学习中,由于引入了深度学习,深度学习本身也会因为网络模型限制或者梯度下降方法陷入局部最优解问题。也就是说,深度强化学习既要考虑与环境交互过程中的探索能力,也要考虑深度模型本身的探索能力,从而尽量避免陷入局部最优解的困境之中,这也是为什么经常有人会说强化学习比深度学习更难“炼丹”的原因之一。
-回归正题,Noisy DQN 算法其实是在 DQN 算法基础上在神经网络中引入了噪声层来提高网络性能的,即将随机性应用到神经网络中的参数或者说权重,增加了 $Q$ 网络对于状态和动作空间的探索能力,从而提高收敛速度和稳定性。在实践上也比较简单,就是通过添加随机性参数到神经网络的线性层,对应的 $Q$ 值则可以表示为 $Q_{\theta+\epsilon}$,注意不要把这里的 $\epsilon$ 跟 $\varepsilon-greedy$ 策略中的 $\varepsilon$ 混淆了。虽然都叫做 epsilon,但这里 $\epsilon$ 是由高斯分布生成的总体分类噪声参数。
+$\qquad$ 回归正题,$\text{Noisy DQN}$ 算法其实是在 $\text{DQN}$ 算法基础上在神经网络中引入了噪声层来提高网络性能的,即将随机性应用到神经网络中的参数或者说权重,增加了 $Q$ 网络对于状态和动作空间的探索能力,从而提高收敛速度和稳定性。在实践上也比较简单,就是通过添加随机性参数到神经网络的线性层,对应的 $Q$ 值则可以表示为 $Q_{\theta+\epsilon}$,注意不要把这里的 $\epsilon$ 跟 $\varepsilon-greedy$ 策略中的 $\varepsilon$ 混淆了。虽然都叫做 $\epsilon$ ,但这里 $\epsilon$ 是由高斯分布生成的总体分类噪声参数。
-在实战中,我们首先可以定义引入了噪声层的线性层,如下:
+$\qquad$ 其实在网络模型中增加噪声层是一种比较泛用的做法,而不只是用在 $\text{DQN}$ 算法中,具体做法读者可以参考后面的实战内容。
+
+
+## PER DQN 算法
+
+$\qquad$ 在 $\text{DQN}$ 算法章节中我们讲到经验回放,从另一个角度来说也是为了优化深度网络中梯度下降的方式,或者说网络参数更新的方式。在本节要讲的 $\text{PER DQN}$ 算法④中,进一步优化了经验回放的设计从而提高模型的收敛能力和鲁棒性。PER 可以翻译为优先经验回放($\text{prioritized experience replay}$),跟数据结构中优先队列与普通队列一样,会在采样过程中赋予经验回放中样本的优先级。
+
+> ④ Schaul T , Quan J , Antonoglou I ,et al.Prioritized Experience Replay[J].Computer Science, 2015.DOI:10.48550/arXiv.1511.05952.
+
+$\qquad$ 具体以什么为依据来为经验回放中的样本赋予不同优先级呢?答案是 $\text{TD}$ 误差。$\text{TD}$ 误差最开始我们是在时序差分方法的提到的,广义的定义是值函数(包括状态价值函数和动作价值函数)的估计值与实际值之差,在 $\text{DQN}$ 算法中就是目标网络计算的 $Q$ 值和策略网络(当前网络)计算的 $Q$ 值之差,也就是 $\text{DQN}$ 网络中损失函数的主要构成部分。
+
+$\qquad$ 我们每次从经验回访中取出一个批量的样本,进而计算的 $\text{TD}$ 误差一般是不同的,对于 $\text{DQN}$ 网络反向传播的作用也是不同的。**TD误差越大,损失函数的值也越大,对于反向传播的作用也就越大。** 这样一来如果 $\text{TD}$ 误差较大的样本更容易被采到的话,那么我们的算法也会更加容易收敛。因此我们只需要设计一个经验回放,根据经验回放中的每个样本计算出的TD误差赋予对应的优先级,然后在采样的时候取出优先级较大的样本。
+
+$\qquad$ 原理听上去比较简单,但具体如何实现呢?在实践中,我们通常用 SumTree 这样的二叉树结构来实现。这里建议没有了解过数据结构或者二叉树的读者先花个十几分钟的时间快速了解一下二叉树的基本概念,比如根节点、叶节点、父节点与子节点等等。
+
+$\qquad$ 如图 $\text{8-3}$ 所示,每个父节点的值等于左右两个子节点值之和。在强化学习中,所有的样本只保存在最下面的叶子节点中,并且除了保存样本数据之外,还会保存对应的优先级,即对应叶子节点中的值(例如图中的 $\text{31,13,14}$ 以及 $8$ 等,也对应样本的 $\text{TD}$ 误差)。并且根据叶子节点的值,我们从 $0$ 开始依次划分采样区间。然后在采样中,例如这里根节点值为 $66$ ,那么我们就可以在 $[0,66)$ 这个区间均匀采样,采样到的值落在哪个区间中,就说明对应的样本就是我们要采样的样本。例如我们采样到了 $25$ 这个值,即对应区间 $[0,31)$,那么我们就采样到了第一个叶子节点对应的样本。注意到,第一个样本对应的区间也是最长的,这意味着第一个样本的优先级最高,也就是 $\text{TD}$ 误差最大,反之第四个样本的区间最短,优先级也最低。这样一来,我们就可以通过采样来实现优先经验回放的功能。
+
+
+
+
+图 $\text{8-3}$ $\text{SumTree}$ 结构
+
+$\qquad$ 每个叶节点的值就是对应样本的 $\text{TD}$ 误差(例如途中的)。我们可以通过根节点的值来计算出每个样本的 $\text{TD}$ 误差占所有样本 $\text{TD}$ 误差的比例,这样就可以根据比例来采样样本。在实际的实现中,我们可以将每个叶节点的值设置为一个元组,其中包含样本的 $\text{TD}$ 误差和样本的索引,这样就可以通过索引来找到对应的样本。具体如何用 $\text{Python}$ 类来实现 $\text{SumTree}$ 结构,读者可以参考后面的实战内容。
+
+
+
+$\qquad$ 尽管 $\text{SumTree}$ 结构可以实现优先经验回放的功能。然而直接使用 $\text{TD}$ 误差作为优先级存在一些问题。首先,考虑到算法效率问题,我们在每次更新时不会把经验回放中的所有样本都计算 $\text{TD}$ 误差并更新对应的优先级,而是只更新当前取到的一定批量的样本。这样一来,每次计算的 $\text{TD}$ 误差是对应之前的网络,而不是当前待更新的网络。换句话说,如果某批量样本的 $\text{TD}$ 误差较低,只能说明它们对于之前的网络来说“信息量”不大,但不能说明对当前的网络“信息量”不大,因此单纯根据 $\text{TD}$ 误差进行优先采样有可能会错过对当前网络“信息量”更大的样本。其次,被选中样本的 $\text{TD}$ 误差会在当前更新后下降,然后优先级会排到后面去,下次这些样本就不会被选中,这样来来回回都是那几个样本,很容易出现“旱的旱死,涝的涝死”的情况,导致过拟合。
+
+$\qquad$ 为了解决上面提到的两个问题,我们首先引入**随机优先级采样**( $\text{stochastic prioritization}$ )的技巧。即在每次更新时,我们不再是直接采样 $\text{TD}$ 误差最大的样本,而是定义一个采样概率,如式 $\text{(8.7)}$ 所示。
+
+$$
+\tag{8.7}
+P(i) = \frac{p_i^\alpha}{\sum_k p_k^\alpha}
+$$
+
+$\qquad$ 其中,$p_i$ 是样本 $i$ 的优先级,$\alpha$ 是一个超参数,用于调节优先采样的程序,通常在 $(0,1)$ 的区间内。当 $\alpha=0$ 时,采样概率为均匀分布;当 $\alpha =1$ 时,采样概率为优先级的线性分布。同时,即使对于最低优先级的样本,我们也不希望它们的采样概率为 $0$ ,因此我们可以在优先级上加上一个常数 $\epsilon$,即式 $\text{(8.8)}$ 。
+
+$$
+\tag{8.8}
+p_i = |\delta_i| + \epsilon
+$$
+
+$\qquad$ 其中,$|\delta_i|$ 是样本 $i$ 的 $\text{TD}$ 误差。当然,我们也可以使用其他的优先级计算方式,如式 $\text{(8.9)}$ 所示。
+
+$$
+\tag{8.9}
+p_i = \frac{1}{rank(i)}
+$$
+
+$\qquad$ 其中 $rank(i)$ 是样本 $i$ 的优先级排名,这种方式也能保证每个样本的采样概率都不为 $0$ ,但在实践中,我们更倾向于直接增加一个常数 $\epsilon$ 的方式。
+
+$\qquad$ 除了随机优先级采样之外,我们还引入了另外一个技巧,在讲解该技巧之前,我们需要简单了解一下**重要性采样**,这个概念在后面的 $\text{PPO}$ 算法也会用到,读者需要重点掌握。重要性采样($\text{importance sampling}$ )是一种用于估计某一分布性质的方法,它的基本思想是,我们可以通过与待估计分布不同的另一个分布中采样,然后通过采样样本的权重来估计待估计分布的性质,数学表达式如式 $\text{(8.10)}$ 所示。
+
+$$
+\tag{8.10}
+\begin{aligned}
+\mathbb{E}_{x \sim p(x)}[f(x)] &= \int f(x) p(x) dx \\
+&= \int f(x) \frac{p(x)}{q(x)} q(x) dx \\
+&= \int f(x) \frac{p(x)}{q(x)} \frac{q(x)}{p(x)} p(x) dx \\
+&= \mathbb{E}_{x \sim q(x)}\left[\frac{p(x)}{q(x)} f(x)\right]
+\end{aligned}
+$$
+
+$\qquad$ 其中 $p(x)$ 是待估计分布,$q(x)$ 是采样分布,$f(x)$ 是待估计分布的性质。在前面我们讲到,每次计算的 $\text{TD}$ 误差是对应之前的网络,而不是当前待更新的网络。也就是说,我们已经从之前的网络中采样了一批样本,也就是 $q(x)$ 已知,然后只要找到之前网络分布与当前网络分布之前的权重 $\frac{p(x)}{q(x)}$,就可以利用重要性采样来估计出当前网络的性质。我们可以定义权重为式 $\text{(8.11)}$ 。
+
+$$
+\tag{8.11}
+w_i = \left(\frac{1}{N} \frac{1}{P(i)}\right)^\beta
+$$
+
+其中,$N$ 是经验回放中的样本数量,$P(i)$ 是样本 $i$ 的采样概率。同时,为了避免出现权重过大或过小的情况,我们可以对权重进行归一化处理,如式 $\text{(8.12)}$ 所示。
+
+$$
+\tag{8.12}
+w_i = \frac{\left(N*P(i)\right)^{-\beta}}{\max_j (w_j)}
+$$
+
+$\qquad$ 注意到,我们引入了一个超参数 $\beta$,用于调节重要性采样的程度,这个技术叫做 **热偏置**。当 $\beta = 0$ 时,重要性采样的权重为 1,即不考虑重要性采样;当 $\beta = 1$ 时,重要性采样的权重为 $w_i$,即完全考虑重要性采样。在实践中,我们希望 $\beta$ 从 0 开始,随着训练步数的增加而逐渐增加,以便更好地利用重要性采样,这就是热偏置(Annealing The Bias)的思想。数学表达式如式 $\text{(8.13)}$ 所示。
+
+$$
+\tag{8.13}
+\beta = \min(1, \beta + \beta_{\text{step}})
+$$
+
+$\qquad$ 其中,$\beta_{\text{step}}$ 是每个训练步数对应的 $\beta$ 的增量。在实践中,我们可以将 $\beta_{\text{step}}$ 设置为一个很小的常数,如 $0.0001$。这样一来,我们就可以在训练刚开始时,使用随机优先级采样,以便更快地收敛;在训练后期,使用重要性采样,以便更好地利用经验回放中的样本。
+
+结合上面的优先级采样和重要性采样,我们可以基于 SumTree 实现一个带有优先级的经验回放,代码如下:
+
+
+
+
+
+## C51 算法
+
+分布式 DQN 算法,即 Distributed DQN,有时也会看到它也叫 Categorical DQN 这个名字,但最常见的名字是 C51 算法⑤ 。该算法跟 PER 算法一样,是从不同的角度改进强化学习算法,而不单单指 DQN 算法,而是能适用于任何基于 Q-learning 的强化学习算法。该算法的核心思想是将传统 DQN 算法中的值函数 $Q(s,a)$ 换成了值分布 $Z(x,a)$,即将值函数的输出从一个数值变成了一个分布,这样就能更好地处理值函数估计不准确以及离散动作空间的问题。
+
+> ⑤ 论文链接:https://arxiv.org/abs/1707.06887
+
+在之前讲到的经典强化学习算法中我们优化的其实是值分布的均值,也就是 $Q$ 函数,但实际上由于状态转移的随机性、函数近似等原因,智能体与环境之间也存在着随机性,这也导致了最终累积的回报也会是一个随机变量,使用一个确定的均值会忽略整个分布所提供的信息。。因此,我们可以将值函数 $Q$ 看成是一个随机变量,它的期望值就是 $Q$ 函数,而它的方差就是 $Q$ 函数的不确定性,公式表示如下:
+
+$$
+Q^\pi(x, a):=\mathbb{E} Z^\pi(x, a)=\mathbb{E}\left[\sum_{t=0}^{\infty} \gamma^t R\left(x_t, a_t\right)\right]
+$$
+
+其中状态分布 $x_t \sim P\left(\cdot \mid x_{t-1}, a_{t-1}\right), a_t \sim \pi\left(\cdot \mid x_t\right), x_0=x, a_0=a \text {. }$
+
+## Rainbow DQN 算法
+
+## 实战:Double DQN 算法
+
+$\qquad$ 由于本章都是基于 $\text{DQN}$ 改进的算法,整体训练方式跟 $\text{DQN}$ 是一样的,也就是说伪代码基本都是一致的,因此不再赘述,只讲算法的改进部分。而 $\text{Double DQN}$ 算法跟 $\text{DQN}$ 算法的区别在于目标值的计算方式,如代码清单 $\text{8-1}$ 所示。
+
+
+ 代码清单 $\text{8-1}$ $\text{Double DQN}$目标值的计算
+
+
+```python
+# 计算当前网络的Q值,即Q(s_t+1|a)
+next_q_value_batch = self.policy_net(next_state_batch)
+# 计算目标网络的Q值,即Q'(s_t+1|a)
+next_target_value_batch = self.target_net(next_state_batch)
+# 计算 Q'(s_t+1|a=argmax Q(s_t+1|a))
+next_target_q_value_batch = next_target_value_batch.gather(1, torch.max(next_q_value_batch, 1)[1].unsqueeze(1))
+```
+
+$\qquad$ 最后与 $\text{DQN}$ 算法相同,可以得到 $\text{Double DQN}$ 算法在 $\text{CartPole}$ 环境下的训练结果,如图 $\text{8-5}$ 所示,完整的代码可以参考本书的代码仓库。
+
+
+
+
+图 $\text{8-5}$ $\text{CartPole}$ 环境 $\text{Double DQN}$ 算法训练曲线
+
+$\qquad$ 与 $\text{DQN}$ 算法的训练曲线对比可以看出,在实践上 $\text{Double DQN}$ 算法的效果并不一定比 $\text{DQN}$ 算法好,比如在这个环境下其收敛速度反而更慢了,因此读者需要多多实践才能摸索并体会到这些算法适合的场景。
+
+## 实战:Dueling DQN 算法
+
+$\qquad$ $\text{Dueling DQN}$ 算法主要是改了网络结构,其他地方跟 $\text{DQN}$ 是一模一样的,如代码清单 $\text{8-2}$ 所示。
+
+
+ 代码清单 $\text{8-2}$ $\text{Dueling DQN}$ 网络结构
+
+
+```python
+class DuelingQNetwork(nn.Module):
+ def __init__(self, state_dim, action_dim,hidden_dim=128):
+ super(DuelingQNetwork, self).__init__()
+ # 隐藏层
+ self.hidden_layer = nn.Sequential(
+ nn.Linear(state_dim, hidden_dim),
+ nn.ReLU()
+ )
+ # 优势层
+ self.advantage_layer = nn.Sequential(
+ nn.Linear(hidden_dim, hidden_dim),
+ nn.ReLU(),
+ nn.Linear(hidden_dim, action_dim)
+ )
+ # 价值层
+ self.value_layer = nn.Sequential(
+ nn.Linear(hidden_dim, hidden_dim),
+ nn.ReLU(),
+ nn.Linear(hidden_dim, 1)
+ )
+
+ def forward(self, state):
+ x = self.hidden_layer(state)
+ advantage = self.advantage_layer(x)
+ value = self.value_layer(x)
+ return value + advantage - advantage.mean() # Q(s,a) = V(s) + A(s,a) - mean(A(s,a))
+```
+
+$\qquad$ 最后我们展示一下它在 $\text{CartPole}$ 环境下的训练结果,如图 $\text{8-6}$ 所示,完整的代码同样可以参考本书的代码仓库。
+
+
+
+
+图 $\text{8-5}$ $\text{CartPole}$ 环境 $\text{Dueling DQN}$ 算法训练曲线
+
+$\qquad$ 由于环境比较简单,暂时还看不出来 $\text{Dueling DQN}$ 算法的优势,但是在复杂的环境下,比如 $\text{Atari}$ 游戏中,$\text{Dueling DQN}$ 算法的效果就会比 $\text{DQN}$ 算法好很多,读者可以在 $\text{JoyRL}$ 仓库中找到更复杂环境下的训练结果便于更好地进行对比。
+
+## 实战:Noisy DQN 算法
+
+$\qquad$ $\text{Noisy DQN}$ 算法的核心思想是将 $\text{DQN}$ 算法中的线性层替换成带有噪声的线性层,如代码清单 $\text{8-3}$ 所示。
+
+
+ 代码清单 $\text{8-3}$ 带有噪声的线性层网络
+
```python
class NoisyLinear(nn.Module):
'''在Noisy DQN中用NoisyLinear层替换普通的nn.Linear层
'''
- def __init__(self, in_dim, out_dim, std_init=0.4):
+ def __init__(self, input_dim, output_dim, std_init=0.4):
super(NoisyLinear, self).__init__()
-
- self.in_dim = in_dim
- self.out_dim = out_dim
+ self.input_dim = input_dim
+ self.output_dim = output_dim
self.std_init = std_init
-
- self.weight_mu = nn.Parameter(torch.empty(out_dim, in_dim))
- self.weight_sigma = nn.Parameter(torch.empty(out_dim, in_dim))
+ self.weight_mu = nn.Parameter(torch.empty(output_dim, input_dim))
+ self.weight_sigma = nn.Parameter(torch.empty(output_dim, input_dim))
# 将一个 tensor 注册成 buffer,使得这个 tensor 不被当做模型参数进行优化。
- self.register_buffer('weight_epsilon', torch.empty(out_dim, in_dim))
+ self.register_buffer('weight_epsilon', torch.empty(output_dim, input_dim))
- self.bias_mu = nn.Parameter(torch.empty(out_dim))
- self.bias_sigma = nn.Parameter(torch.empty(out_dim))
- self.register_buffer('bias_epsilon', torch.empty(out_dim))
+ self.bias_mu = nn.Parameter(torch.empty(output_dim))
+ self.bias_sigma = nn.Parameter(torch.empty(output_dim))
+ self.register_buffer('bias_epsilon', torch.empty(output_dim))
self.reset_parameters() # 初始化参数
self.reset_noise() # 重置噪声
@@ -128,17 +312,17 @@ class NoisyLinear(nn.Module):
return F.linear(x, weight, bias)
def reset_parameters(self):
- mu_range = 1 / self.in_dim ** 0.5
+ mu_range = 1 / self.input_dim ** 0.5
self.weight_mu.data.uniform_(-mu_range, mu_range)
- self.weight_sigma.data.fill_(self.std_init / self.in_dim ** 0.5)
+ self.weight_sigma.data.fill_(self.std_init / self.input_dim ** 0.5)
self.bias_mu.data.uniform_(-mu_range, mu_range)
- self.bias_sigma.data.fill_(self.std_init / self.out_dim ** 0.5)
+ self.bias_sigma.data.fill_(self.std_init / self.output_dim ** 0.5)
def reset_noise(self):
- epsilon_in = self._scale_noise(self.in_dim)
- epsilon_out = self._scale_noise(self.out_dim)
+ epsilon_in = self._scale_noise(self.input_dim)
+ epsilon_out = self._scale_noise(self.output_dim)
self.weight_epsilon.copy_(epsilon_out.ger(epsilon_in))
- self.bias_epsilon.copy_(self._scale_noise(self.out_dim))
+ self.bias_epsilon.copy_(self._scale_noise(self.output_dim))
def _scale_noise(self, size):
x = torch.randn(size)
@@ -146,7 +330,11 @@ class NoisyLinear(nn.Module):
return x
```
-根据写好的 NoisyLinear 层,我们可以在 DQN 算法中将普通的线性层替换为 NoisyLinear 层,如下:
+$\qquad$ 根据写好的 $\text{NoisyLinear}$ 层,我们可以在 $\text{DQN}$ 算法中将普通的线性层替换为 $\text{NoisyLinear}$ 层,如代码清单 $\text{8-4}$ 所示。
+
+
+ 代码清单 $\text{8-4}$ 带噪声层的全连接网络
+
```python
class NoisyQNetwork(nn.Module):
@@ -167,7 +355,11 @@ class NoisyQNetwork(nn.Module):
self.noisy_fc3.reset_noise()
```
-注意在训练过程中,我们需要在每个 mini-batch 中重置噪声,更多细节请参考 JoyRL 源码。另外,我们可以直接利用 torchrl 模块中中封装好的 NoisyLinear 层来构建 Noisy Q 网络,跟我们自己定义的功能是一样的,如下:
+$\qquad$ 注意在训练过程中,我们需要在每次更新后重置噪声,这样有助于提高训练的稳定性,更多细节请参考 $\text{JoyRL}$ 源码。另外,我们也可以直接利用 $\text{torchrl}$ 模块中中封装好的 $\text{NoisyLinear}$ 层来构建 $\text{Noisy Q}$ 网络,跟我们自己定义的功能是一样的,如代码清单 $\text{8-5}$ 所示。
+
+
+ 代码清单 $\text{8-5}$ 使用 $\text{torchrl}$ 模块构造的 $\text{Noisy Q}$ 网络
+
```python
import torchrl
@@ -189,26 +381,32 @@ class NoisyQNetwork(nn.Module):
self.noisy_fc3.reset_noise()
```
-## PER DQN 算法
+$\qquad$ 同样我们展示一下它在 $\text{CartPole}$ 环境下的训练结果,如图 $\text{8-6}$ 所示。
-在 DQN 算法章节中我们讲到经验回放,从另一个角度来说也是为了优化深度网络中梯度下降的方式,或者说网络参数更新的方式。在本节要讲的 PER DQN 算法④中,进一步优化了经验回放的设计从而提高模型的收敛能力和鲁棒性。PER 英文全称为 Prioritized Experience Replay,即优先经验回放,跟数据结构中优先队列与普通队列一样,会在采样过程中赋予经验回放中样本的优先级。
+
+
+
+图 $\text{8-6}$ $\text{CartPole}$ 环境 $\text{Noisy DQN}$ 算法训练曲线
+
+## 实战:PER DQN 算法
-> ④ 论文链接:https://arxiv.org/abs/1511.05952
+### 伪代码
-具体以什么为依据来为经验回放中的样本赋予不同优先级呢?答案是TD误差。TD误差最开始我们是在时序差分方法的提到的,广义的定义是值函数(包括状态价值函数和动作价值函数)的估计值与实际值之差,在 DQN 算法中就是目标网络计算的 $Q$ 值和策略网络(当前网络)计算的 $Q$ 值之差,也就是 DQN 网络中损失函数的主要构成部分。我们每次从经验回访中取出一个批量的样本,进而计算的TD误差一般是不同的,对于 DQN 网络反向传播的作用也是不同的。**TD误差越大,损失函数的值也越大,对于反向传播的作用也就越大。** 这样一来如果TD误差较大的样本更容易被采到的话,那么我们的算法也会更加容易收敛。因此我们只需要设计一个经验回放,根据经验回放中的每个样本计算出的TD误差赋予对应的优先级,然后在采样的时候取出优先级较大的样本。
+$\qquad$ $\text{PER DQN}$ 算法的核心看起来简单,就是把普通的经验回放改进成了优先级经验回放,但是实现起来却比较复杂,因为我们需要实现一个 $\text{SumTree}$ 结构,并且在模型更新的时候也需要一些额外的操作,因此我们先从伪代码开始,如图 $\text{8-7}$ 所示。
-原理听上去比较简单,但具体如何实现呢?在实践中,我们通常用 SumTree 这样的二叉树结构来实现。这里建议没有了解过数据结构或者二叉树的读者先花个十几分钟的时间快速了解一下二叉树的基本概念,比如根节点、叶节点、父节点与子节点等等。
-
+
-图 8.4 SumTree 结构
+图 $\text{8-7}$ $\text{PER DQN}$ 伪代码
-如图 8.4 所示,每个父节点的值等于左右两个子节点值之和。在强化学习中,所有的样本只保存在最下面的叶子节点中,并且除了保存样本数据之外,还会保存对应的优先级,即对应叶子节点中的值(例如图中的31、13、14以及8等,也对应样本的 TD 误差)。并且根据叶子节点的值,我们从 $0$ 开始依次划分采样区间。然后在采样中,例如这里根节点值为 66,那么我们就可以在$[0,66)$这个区间均匀采样,采样到的值落在哪个区间中,就说明对应的样本就是我们要采样的样本。例如我们采样到了 $25$ 这个值,即对应区间 $[0,31)$,那么我们就采样到了第一个叶子节点对应的样本。注意到,第一个样本对应的区间也是最长的,这意味着第一个样本的优先级最高,也就是 TD 误差最大,反之第四个样本的区间最短,优先级也最低。这样一来,我们就可以通过采样来实现优先经验回放的功能。
+### SumTree 结构
-每个叶节点的值就是对应样本的 TD 误差(例如途中的)。我们可以通过根节点的值来计算出每个样本的 TD 误差占所有样本 TD 误差的比例,这样就可以根据比例来采样样本。在实际的实现中,我们可以将每个叶节点的值设置为一个元组,其中包含样本的 TD 误差和样本的索引,这样就可以通过索引来找到对应的样本。
+$\qquad$ 如代码清单 $\text{8-6}$ 所示,我们可以先实现 $\text{SumTree}$ 结构。
-基于以上原理,我们可以新建一个 Python 类来实现 SumTree 结构,代码如下:
+
+ 代码清单 $\text{8-6}$ $\text{SumTree}$ 结构
+
```python
class SumTree:
@@ -270,62 +468,15 @@ class SumTree:
'''
return np.max(self.tree[self.capacity-1:self.capacity+self.write_idx-1])
```
-其中,除了需要存放各个节点的值(`tree`)之外,我们需要定义要给`data`来存放叶子节点的样本。此外,`add` 函数用于添加一个样本到叶子节点,并更新其父节点的优先级;`update` 函数用于更新叶子节点的优先级,并更新其父节点的优先级;`get_leaf` 函数用于根据优先级的值采样对应区间的叶子节点样本;`get_data` 函数用于根据索引获取对应的样本;`total` 函数用于返回所有样本的优先级之和,即根节点的值;`max_prior` 函数用于返回所有样本的最大优先级。
+$\qquad$ 其中,除了需要存放各个节点的值($\text{tree}$)之外,我们需要定义要给 $\text{data}$ 来存放叶子节点的样本。此外,$\text{add}$ 函数用于添加一个样本到叶子节点,并更新其父节点的优先级;$\text{update}$ 函数用于更新叶子节点的优先级,并更新其父节点的优先级;$\text{get_leaf}$ 函数用于根据优先级的值采样对应区间的叶子节点样本;$\text{get_data}$ 函数用于根据索引获取对应的样本;$\text{total}$ 函数用于返回所有样本的优先级之和,即根节点的值;$\text{max_prior}$ 函数用于返回所有样本的最大优先级。
-基于以上的 SumTree 结构,我们可以实现优先经验回放的功能。然而,论文原作者认为,直接使用 TD 误差作为优先级存在一些问题。首先,考虑到算法效率问题,我们在每次更新时不会把经验回放中的所有样本都计算 TD 误差并更新对应的优先级,而是只更新当前取到的一定批量的样本。这样一来,每次计算的 TD 误差是对应之前的网络,而不是当前待更新的网络。换句话说,如果某批量样本的 TD 误差较低,只能说明它们对于之前的网络来说“信息量”不大,但不能说明对当前的网络“信息量”不大,因此单纯根据 TD 误差进行优先采样有可能会错过对当前网络“信息量”更大的样本。其次,被选中样本的 TD 误差会在当前更新后下降,然后优先级会排到后面去,下次这些样本就不会被选中,这样来来回回都是那几个样本,很容易出现“旱的旱死,涝的涝死”的情况,导致过拟合。
+### 优先级经验回放
-**随机优先级采样**。为了解决上面提到的两个问题,我们首先引入随机优先级采样(Stochastic Prioritization)的技巧。即在每次更新时,我们不再是直接采样 TD 误差最大的样本,而是定义一个采样概率,如下:
+$\qquad$ 基于 $\text{SumTree}$ 结构,并结合优先级采样和重要性采样的技巧,如代码清单 $\text{8-7}$ 所示。
-$$
-P(i) = \frac{p_i^\alpha}{\sum_k p_k^\alpha}
-$$
-
-其中,$p_i$ 是样本 $i$ 的优先级,$\alpha$ 是一个超参数,用于调节优先采样的程序,通常在 $(0,1)$ 的区间内。当 $\alpha=0$ 时,采样概率为均匀分布;当 $\alpha =1$ 时,采样概率为优先级的线性分布。同时,即使对于最低优先级的样本,我们也不希望它们的采样概率为 $0$ ,因此我们可以在优先级上加上一个常数 $\epsilon$,即:
-
-$$
-p_i = |\delta_i| + \epsilon
-$$
-
-其中,$|\delta_i|$ 是样本 $i$ 的 TD 误差。当然,我们也可以使用其他的优先级计算方式,如:
-
-$$
-p_i = \frac{1}{rank(i)}
-$$
-
-其中,$rank(i)$ 是样本 $i$ 的优先级排名,这种方式也能保证每个样本的采样概率都不为 0,但在实践中,我们更倾向于直接增加一个常数 $\epsilon$ 的方式。
-
-**重要性采样**。除了随机优先级采样之外,我们还引入了另外一个技巧,在讲解该技巧之前,我们需要简单了解一下重要性采样。重要性采样(Importance Sampling)是一种用于估计某一分布性质的方法,它的基本思想是,我们可以通过与待估计分布不同的另一个分布中采样,然后通过采样样本的权重来估计待估计分布的性质,数学表达式如下:
-
-$$
-\begin{aligned}
-\mathbb{E}_{x \sim p(x)}[f(x)] &= \int f(x) p(x) dx \\
-&= \int f(x) \frac{p(x)}{q(x)} q(x) dx \\
-&= \int f(x) \frac{p(x)}{q(x)} \frac{q(x)}{p(x)} p(x) dx \\
-&= \mathbb{E}_{x \sim q(x)}\left[\frac{p(x)}{q(x)} f(x)\right]
-\end{aligned}
-$$
-
-其中,$p(x)$ 是待估计分布,$q(x)$ 是采样分布,$f(x)$ 是待估计分布的性质。在前面我们讲到,每次计算的 TD 误差是对应之前的网络,而不是当前待更新的网络。也就是说,我们已经从之前的网络中采样了一批样本,也就是 $q(x)$ 已知,然后只要找到之前网络分布与当前网络分布之前的权重 $\frac{p(x)}{q(x)}$,就可以利用重要性采样来估计出当前网络的性质。我们可以定义权重为:
-
-$$
-w_i = \left(\frac{1}{N} \frac{1}{P(i)}\right)^\beta
-$$
-
-其中,$N$ 是经验回放中的样本数量,$P(i)$ 是样本 $i$ 的采样概率。同时,为了避免出现权重过大或过小的情况,我们可以对权重进行归一化处理:
-
-$$
-w_i = \frac{\left(N*P(i)\right)^{-\beta}}{\max_j (w_j)}
-$$
-
-**热偏置**。注意到,我们引入了一个超参数 $\beta$,用于调节重要性采样的程度。当 $\beta = 0$ 时,重要性采样的权重为 1,即不考虑重要性采样;当 $\beta = 1$ 时,重要性采样的权重为 $w_i$,即完全考虑重要性采样。在实践中,我们希望 $\beta$ 从 0 开始,随着训练步数的增加而逐渐增加,以便更好地利用重要性采样,这就是热偏置(Annealing The Bias)的思想。数学表达式如下:
-
-$$
-\beta = \min(1, \beta + \beta_{\text{step}})
-$$
-
-其中,$\beta_{\text{step}}$ 是每个训练步数对应的 $\beta$ 的增量。在实践中,我们可以将 $\beta_{\text{step}}$ 设置为一个很小的常数,如 $0.0001$。这样一来,我们就可以在训练刚开始时,使用随机优先级采样,以便更快地收敛;在训练后期,使用重要性采样,以便更好地利用经验回放中的样本。
-
-结合上面的优先级采样和重要性采样,我们可以基于 SumTree 实现一个带有优先级的经验回放,代码如下:
+
+ 代码清单 $\text{8-7}$ 优先级经验回放结构
+
```python
class PrioritizedReplayBuffer:
@@ -380,7 +531,11 @@ class PrioritizedReplayBuffer:
return self.tree.count
```
-我们可以看到,优先级经验回放的核心是 SumTree,它可以在 $O(\log N)$ 的时间复杂度内完成添加、更新和采样操作。在实践中,我们可以将经验回放的容量设置为 $10^6$,并将 $\alpha$ 设置为 $0.6$,$\epsilon$ 设置为 $0.01$,$\beta$ 设置为 $0.4$,$\beta_{\text{step}}$ 设置为 $0.0001$。 当然我们也可以利用 Python 队列的方式实现优先级经验回放,形式上会更加简洁,并且在采样的时候减少了 for 循环的操作,会更加高效,如下:
+$\qquad$ 我们可以看到,优先级经验回放的核心是 SumTree,它可以在 $O(\log N)$ 的时间复杂度内完成添加、更新和采样操作。在实践中,我们可以将经验回放的容量设置为 $10^6$,并将 $\alpha$ 设置为 $0.6$,$\epsilon$ 设置为 $0.01$,$\beta$ 设置为 $0.4$,$\beta_{\text{step}}$ 设置为 $0.0001$。 当然我们也可以利用 Python 队列的方式实现优先级经验回放,形式上会更加简洁,并且在采样的时候减少了 for 循环的操作,会更加高效,如代码清单 $\text{8-8}$ 所示。
+
+
+ 代码清单 $\text{8-7}$ 基于队列实现优先级经验回放
+
```python
class PrioritizedReplayBufferQue:
@@ -415,100 +570,9 @@ class PrioritizedReplayBufferQue:
def __len__(self):
return self.count
```
-最后,我们可以将优先级经验回放和 DQN 结合起来,实现一个带有优先级的 DQN 算法,伪代码如下:
-
-
-
-
-图 8.4 SumTree 结构
-
-## C51 算法
-
-分布式 DQN 算法,即 Distributed DQN,有时也会看到它也叫 Categorical DQN 这个名字,但最常见的名字是 C51 算法⑤ 。该算法跟 PER 算法一样,是从不同的角度改进强化学习算法,而不单单指 DQN 算法,而是能适用于任何基于 Q-learning 的强化学习算法。该算法的核心思想是将传统 DQN 算法中的值函数 $Q(s,a)$ 换成了值分布 $Z(x,a)$,即将值函数的输出从一个数值变成了一个分布,这样就能更好地处理值函数估计不准确以及离散动作空间的问题。
-
-> ⑤ 论文链接:https://arxiv.org/abs/1707.06887
-
-在之前讲到的经典强化学习算法中我们优化的其实是值分布的均值,也就是 $Q$ 函数,但实际上由于状态转移的随机性、函数近似等原因,智能体与环境之间也存在着随机性,这也导致了最终累积的回报也会是一个随机变量,使用一个确定的均值会忽略整个分布所提供的信息。。因此,我们可以将值函数 $Q$ 看成是一个随机变量,它的期望值就是 $Q$ 函数,而它的方差就是 $Q$ 函数的不确定性,公式表示如下:
-
-$$
-Q^\pi(x, a):=\mathbb{E} Z^\pi(x, a)=\mathbb{E}\left[\sum_{t=0}^{\infty} \gamma^t R\left(x_t, a_t\right)\right]
-$$
-
-其中状态分布 $x_t \sim P\left(\cdot \mid x_{t-1}, a_{t-1}\right), a_t \sim \pi\left(\cdot \mid x_t\right), x_0=x, a_0=a \text {. }$
-
-## Rainbow DQN 算法
-
-## 实战:Double DQN 算法
-
-$\qquad$ 由于本章都是基于 $\text{DQN}$ 改进的算法,整体训练方式跟 $\text{DQN}$ 是一样的,也就是说伪代码基本都是一致的,因此不再赘述,只讲算法的改进部分。而 $\text{Double DQN}$ 算法跟 $\text{DQN}$ 算法的区别在于目标值的计算方式,如代码清单 $\text{8-1}$ 所示。
-
-
- 代码清单 $\text{8-1}$ $\text{Double DQN}$目标值的计算
-
-
-```python
-# 计算当前网络的Q值,即Q(s_t+1|a)
-next_q_value_batch = self.policy_net(next_state_batch)
-# 计算目标网络的Q值,即Q'(s_t+1|a)
-next_target_value_batch = self.target_net(next_state_batch)
-# 计算 Q'(s_t+1|a=argmax Q(s_t+1|a))
-next_target_q_value_batch = next_target_value_batch.gather(1, torch.max(next_q_value_batch, 1)[1].unsqueeze(1))
-```
-
-$\qquad$ 最后与 $\text{DQN}$ 算法相同,可以得到 $\text{Double DQN}$ 算法在 $\text{CartPole}$ 环境下的训练结果,如图 $\text{8-5}$ 所示,完整的代码可以参考本书的代码仓库。
+最后,我们可以将优先级经验回放和 DQN 结合起来,实现一个带有优先级的 DQN 算法,并展示它在 $\text{CartPole}$ 环境下的训练结果,如图 $\text{8-8}$ 所示。
-
+
-图 $\text{8-5}$ $\text{CartPole}$ 环境 $\text{Double DQN}$ 算法训练曲线
-
-$\qquad$ 与 $\text{DQN}$ 算法的训练曲线对比可以看出,在实践上 $\text{Double DQN}$ 算法的效果并不一定比 $\text{DQN}$ 算法好,比如在这个环境下其收敛速度反而更慢了,因此读者需要多多实践才能摸索并体会到这些算法适合的场景。
-
-## 实战:Dueling DQN 算法
-
-$\qquad$ $\text{Dueling DQN}$ 算法主要是改了网络结构,其他地方跟 $\text{DQN}$ 是一模一样的,如代码清单 $\text{8-2}$ 所示。
-
-
- 代码清单 $\text{8-2}$ $\text{Dueling DQN}$ 网络结构
-
-
-```python
-class DuelingQNetwork(nn.Module):
- def __init__(self, state_dim, action_dim,hidden_dim=128):
- super(DuelingQNetwork, self).__init__()
- # 隐藏层
- self.hidden_layer = nn.Sequential(
- nn.Linear(state_dim, hidden_dim),
- nn.ReLU()
- )
- # 优势层
- self.advantage_layer = nn.Sequential(
- nn.Linear(hidden_dim, hidden_dim),
- nn.ReLU(),
- nn.Linear(hidden_dim, action_dim)
- )
- # 价值层
- self.value_layer = nn.Sequential(
- nn.Linear(hidden_dim, hidden_dim),
- nn.ReLU(),
- nn.Linear(hidden_dim, 1)
- )
-
- def forward(self, state):
- x = self.hidden_layer(state)
- advantage = self.advantage_layer(x)
- value = self.value_layer(x)
- return value + advantage - advantage.mean() # Q(s,a) = V(s) + A(s,a) - mean(A(s,a))
-```
-
-$\qquad$ 最后我们展示一下它在 $\text{CartPole}$ 环境下的训练结果,如图 $\text{8-6}$ 所示,完整的代码同样可以参考本书的代码仓库。
-
-
-
-
-图 $\text{8-5}$ $\text{CartPole}$ 环境 $\text{Dueling DQN}$ 算法训练曲线
-
-$\qquad$ 由于环境比较简单,暂时还看不出来 $\text{Dueling DQN}$ 算法的优势,但是在复杂的环境下,比如 $\text{Atari}$ 游戏中,$\text{Dueling DQN}$ 算法的效果就会比 $\text{DQN}$ 算法好很多,读者可以在 $\text{JoyRL}$ 仓库中找到更复杂环境下的训练结果便于更好地进行对比。
-
-
-
+图 $\text{8-8}$ $\text{CartPole}$ 环境 $\text{PER DQN}$ 算法训练曲线
\ No newline at end of file
diff --git a/docs/figs/ch8/NoisyDQN_CartPole-v1_training_curve.png b/docs/figs/ch8/NoisyDQN_CartPole-v1_training_curve.png
new file mode 100644
index 0000000000000000000000000000000000000000..b0a61e8cc98e33bdeb1f4ce67632d0df6295a74a
GIT binary patch
literal 45724
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∾P)#_qA^YZc{
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
literal 0
HcmV?d00001
diff --git a/docs/figs/ch8/PERDQN_CartPole-v1_training_curve.png b/docs/figs/ch8/PERDQN_CartPole-v1_training_curve.png
new file mode 100644
index 0000000000000000000000000000000000000000..27f596b488ae24ccb3dadb4afde318a6a1e4ed91
GIT binary patch
literal 48768
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