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 0000000..b0a61e8
Binary files /dev/null and b/docs/figs/ch8/NoisyDQN_CartPole-v1_training_curve.png differ
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 0000000..27f596b
Binary files /dev/null and b/docs/figs/ch8/PERDQN_CartPole-v1_training_curve.png differ
diff --git a/docs/figs/ch8/per_dqn_pseu.png b/docs/figs/ch8/per_dqn_pseu.png
index 216daf7..056cf91 100644
Binary files a/docs/figs/ch8/per_dqn_pseu.png and b/docs/figs/ch8/per_dqn_pseu.png differ
diff --git a/notebooks/NoisyDQN.ipynb b/notebooks/NoisyDQN.ipynb
new file mode 100644
index 0000000..69c32f5
--- /dev/null
+++ b/notebooks/NoisyDQN.ipynb
@@ -0,0 +1,582 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 1. 定义算法\n",
+ "\n",
+ "NoisyDQN 是在 DQN 的基础上进行改进,主要就是通过在训练网络的时候加上一些噪声参数,可以用较小的额外计算成本,在强化学习算法上获得更优的结果。\n",
+ "配置和 DQN 基本一致,只是在模型定义的时候,在模型中加入了一些噪声参数。"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 1.1、 定义模型\n",
+ "这里使用了一个三层的MLP,不同的是其中加入了一些噪声参数,就是每个权值weight和偏置bias中都有额外的参数mu和sigma,这里仅供参考。"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 56,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import math\n",
+ "import torch\n",
+ "import torch.nn as nn\n",
+ "import torch.nn.functional as F\n",
+ "class NoisyLinear(nn.Module):\n",
+ " def __init__(self, input_dim, output_dim, std_init=0.4):\n",
+ " super(NoisyLinear, self).__init__()\n",
+ " \n",
+ " self.input_dim = input_dim\n",
+ " self.output_dim = output_dim\n",
+ " self.std_init = std_init\n",
+ " \n",
+ " self.weight_mu = nn.Parameter(torch.FloatTensor(output_dim, input_dim))\n",
+ " self.weight_sigma = nn.Parameter(torch.FloatTensor(output_dim, input_dim))\n",
+ " self.register_buffer('weight_epsilon', torch.FloatTensor(output_dim, input_dim))\n",
+ " \n",
+ " self.bias_mu = nn.Parameter(torch.FloatTensor(output_dim))\n",
+ " self.bias_sigma = nn.Parameter(torch.FloatTensor(output_dim))\n",
+ " self.register_buffer('bias_epsilon', torch.FloatTensor(output_dim))\n",
+ " \n",
+ " self.reset_parameters()\n",
+ " self.reset_noise()\n",
+ " \n",
+ " def forward(self, x):\n",
+ " if self.training: \n",
+ " weight = self.weight_mu + self.weight_sigma.mul(torch.tensor(self.weight_epsilon))\n",
+ " bias = self.bias_mu + self.bias_sigma.mul(torch.tensor(self.bias_epsilon))\n",
+ " else:\n",
+ " weight = self.weight_mu\n",
+ " bias = self.bias_mu\n",
+ " \n",
+ " return F.linear(x, weight, bias)\n",
+ " \n",
+ " def reset_parameters(self):\n",
+ " mu_range = 1 / math.sqrt(self.weight_mu.size(1))\n",
+ " \n",
+ " self.weight_mu.data.uniform_(-mu_range, mu_range)\n",
+ " self.weight_sigma.data.fill_(self.std_init / math.sqrt(self.weight_sigma.size(1)))\n",
+ " \n",
+ " self.bias_mu.data.uniform_(-mu_range, mu_range)\n",
+ " self.bias_sigma.data.fill_(self.std_init / math.sqrt(self.bias_sigma.size(0)))\n",
+ " \n",
+ " def reset_noise(self):\n",
+ " epsilon_in = self._scale_noise(self.input_dim)\n",
+ " epsilon_out = self._scale_noise(self.output_dim)\n",
+ " \n",
+ " self.weight_epsilon.copy_(epsilon_out.ger(epsilon_in))\n",
+ " self.bias_epsilon.copy_(self._scale_noise(self.output_dim))\n",
+ " \n",
+ " def _scale_noise(self, size):\n",
+ " x = torch.randn(size)\n",
+ " x = x.sign().mul(x.abs().sqrt())\n",
+ " return x\n",
+ "\n",
+ "class NoisyMLP(nn.Module):\n",
+ " def __init__(self, input_dim,output_dim,hidden_dim=128):\n",
+ " super(NoisyMLP, self).__init__()\n",
+ " self.fc1 = nn.Linear(input_dim, hidden_dim)\n",
+ " self.noisy_fc2 = NoisyLinear(hidden_dim, hidden_dim)\n",
+ " self.noisy_fc3 = NoisyLinear(hidden_dim, output_dim)\n",
+ " \n",
+ " def forward(self, x):\n",
+ " x = F.relu(self.fc1(x))\n",
+ " x = F.relu(self.noisy_fc2(x))\n",
+ " x = self.noisy_fc3(x)\n",
+ " return x\n",
+ "\n",
+ " def reset_noise(self):\n",
+ " self.noisy_fc2.reset_noise()\n",
+ " self.noisy_fc3.reset_noise()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 1.2、定义经验回放\n",
+ "\n",
+ "这里的经验回放和DQN中保持一致,也是具有一定容量,只有存储到一定的transition网络才会更新。经验回放的时候一般包涵两个功能或方法,一个是push,即将一个transition样本按顺序放到经验回放中,如果满了就把最开始放进去的样本挤掉;另外一个是sample,就是随机采样出一个或者若干个(具体多少就是batch_size了)样本供DQN网络更新。"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 57,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from collections import deque\n",
+ "import random\n",
+ "class ReplayBuffer(object):\n",
+ " def __init__(self, capacity: int) -> None:\n",
+ " self.capacity = capacity\n",
+ " self.buffer = deque(maxlen=self.capacity)\n",
+ " def push(self,transitions):\n",
+ " ''' 存储transition到经验回放中\n",
+ " '''\n",
+ " self.buffer.append(transitions)\n",
+ " def sample(self, batch_size: int, sequential: bool = False):\n",
+ " if batch_size > len(self.buffer): # 如果批量大小大于经验回放的容量,则取经验回放的容量\n",
+ " batch_size = len(self.buffer)\n",
+ " if sequential: # 顺序采样\n",
+ " rand = random.randint(0, len(self.buffer) - batch_size)\n",
+ " batch = [self.buffer[i] for i in range(rand, rand + batch_size)]\n",
+ " return zip(*batch)\n",
+ " else: # 随机采样\n",
+ " batch = random.sample(self.buffer, batch_size)\n",
+ " return zip(*batch)\n",
+ " def clear(self):\n",
+ " ''' 清空经验回放\n",
+ " '''\n",
+ " self.buffer.clear()\n",
+ " def __len__(self):\n",
+ " ''' 返回当前存储的量\n",
+ " '''\n",
+ " return len(self.buffer)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 1.3、模型算法定义\n",
+ "\n",
+ "这里根据前面的噪声MLP搭建智能体agent,其中的动作采样和模型更新和DQN基本一致,这里不再赘述。"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 58,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import torch\n",
+ "import torch.optim as optim\n",
+ "import math\n",
+ "import numpy as np\n",
+ "\n",
+ "class NoisyDQN:\n",
+ " def __init__(self, model, memory, cfg) -> None:\n",
+ " self.n_actions = cfg.n_actions \n",
+ " self.device = torch.device(cfg.device) \n",
+ " self.gamma = cfg.gamma \n",
+ " ## e-greedy策略相关参数\n",
+ " self.sample_count = 0 # 用于epsilon的衰减计数\n",
+ " self.epsilon = cfg.epsilon_start\n",
+ " self.epsilon_start = cfg.epsilon_start\n",
+ " self.epsilon_end = cfg.epsilon_end\n",
+ " self.epsilon_decay = cfg.epsilon_decay\n",
+ " self.batch_size = cfg.batch_size\n",
+ " self.target_update = cfg.target_update\n",
+ "\n",
+ " self.device = torch.device(cfg.device) \n",
+ "\n",
+ " self.policy_net = model.to(self.device)\n",
+ " self.target_net = model.to(self.device)\n",
+ " ## 复制参数到目标网络\n",
+ " for target_param, param in zip(self.target_net.parameters(),self.policy_net.parameters()): \n",
+ " target_param.data.copy_(param.data)\n",
+ " self.optimizer = optim.Adam(self.policy_net.parameters(), lr=cfg.lr)\n",
+ " self.memory = memory ## 经验回放\n",
+ " self.update_flag = False\n",
+ " \n",
+ " def sample_action(self, state):\n",
+ " ''' sample action with e-greedy policy \n",
+ " '''\n",
+ " self.sample_count += 1\n",
+ " # epsilon 指数衰减\n",
+ " self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * \\\n",
+ " math.exp(-1. * self.sample_count / self.epsilon_decay) \n",
+ " if random.random() > self.epsilon:\n",
+ " with torch.no_grad():\n",
+ " state = torch.tensor(state, device=self.device, dtype=torch.float32).unsqueeze(dim=0)\n",
+ " q_values = self.policy_net(state)\n",
+ " action = q_values.max(1)[1].item() # 根据Q值选择动作\n",
+ " else:\n",
+ " action = random.randrange(self.n_actions)\n",
+ " return action\n",
+ "\n",
+ " @torch.no_grad()\n",
+ " def predict_action(self, state):\n",
+ " state = torch.tensor(state, device=self.device, dtype=torch.float32).unsqueeze(dim=0)\n",
+ " q_value = self.policy_net(state)\n",
+ " action = q_value.max(1)[1].item()\n",
+ " return action\n",
+ " def update(self):\n",
+ " if len(self.memory) < self.batch_size: # 不满足一个批量时,不更新策略\n",
+ " return\n",
+ " else:\n",
+ " if not self.update_flag:\n",
+ " print(\"Begin to update!\")\n",
+ " self.update_flag = True\n",
+ " # beta = min(1.0, self.beta_start + self.sample_count * (1.0 - self.beta_start) / self.beta_frames)\n",
+ " state_batch, action_batch, reward_batch, next_state_batch, done_batch = self.memory.sample(\n",
+ " self.batch_size)\n",
+ " # state_batch, action_batch, reward_batch, next_state_batch, done_batch, weights_batch, indices = self.memory.sample(self.batch_size, beta) \n",
+ " state_batch = torch.tensor(np.array(state_batch), device=self.device, dtype=torch.float) \n",
+ " action_batch = torch.tensor(action_batch, device=self.device).unsqueeze(1)\n",
+ " reward_batch = torch.tensor(reward_batch, device=self.device, dtype=torch.float).unsqueeze(1)\n",
+ " next_state_batch = torch.tensor(np.array(next_state_batch), device=self.device, dtype=torch.float) # shape(batchsize,n_states)\n",
+ " done_batch = torch.tensor(done_batch, device=self.device, dtype=torch.float).unsqueeze(1)\n",
+ " # weights_batch = torch.tensor(weights_batch, device=self.device, dtype=torch.float)\n",
+ "\n",
+ " q_value_batch = self.policy_net(state_batch).gather(dim=1, index=action_batch) # shape(batchsize,1),requires_grad=True\n",
+ " next_max_q_value_batch = self.target_net(next_state_batch).max(1)[0].detach().unsqueeze(1) \n",
+ " expected_q_value_batch = reward_batch + self.gamma * next_max_q_value_batch* (1-done_batch)\n",
+ "\n",
+ " loss = nn.MSELoss()(q_value_batch, expected_q_value_batch) # shape same to \n",
+ " # 反向传播\n",
+ " self.optimizer.zero_grad() \n",
+ " loss.backward()\n",
+ " # 梯度截断,防止梯度爆炸\n",
+ " for param in self.policy_net.parameters(): \n",
+ " param.grad.data.clamp_(-1, 1)\n",
+ " self.optimizer.step() \n",
+ "\n",
+ " if self.sample_count % self.target_update == 0: # 更新 target_net\n",
+ " self.target_net.load_state_dict(self.policy_net.state_dict()) \n",
+ "\n",
+ " ## 噪声参数重置\n",
+ " self.policy_net.reset_noise()\n",
+ " self.target_net.reset_noise()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 2、 定义训练"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 59,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def train(cfg, env, agent):\n",
+ " ''' 训练\n",
+ " '''\n",
+ " print(\"开始训练!\")\n",
+ " rewards = [] # 记录所有回合的奖励\n",
+ " steps = []\n",
+ " for i_ep in range(cfg.train_eps):\n",
+ " ep_reward = 0 # 记录一回合内的奖励\n",
+ " ep_step = 0\n",
+ " state = env.reset() # 重置环境,返回初始状态\n",
+ " for _ in range(cfg.max_steps):\n",
+ " ep_step += 1\n",
+ " action = agent.sample_action(state) # 选择动作\n",
+ " next_state, reward, done, _ = env.step(action) # 更新环境,返回transition\n",
+ " agent.memory.push((state, action, reward,next_state, done)) # 保存transition\n",
+ " state = next_state # 更新下一个状态\n",
+ " agent.update() # 更新智能体\n",
+ " ep_reward += reward # 累加奖励\n",
+ " if done:\n",
+ " break\n",
+ " if (i_ep + 1) % cfg.target_update == 0: # 智能体目标网络更新\n",
+ " agent.target_net.load_state_dict(agent.policy_net.state_dict())\n",
+ " steps.append(ep_step)\n",
+ " rewards.append(ep_reward)\n",
+ " if (i_ep + 1) % 10 == 0:\n",
+ " print(f\"回合:{i_ep+1}/{cfg.train_eps},奖励:{ep_reward:.2f},Epislon:{agent.epsilon:.3f}\")\n",
+ " print(\"完成训练!\")\n",
+ " env.close()\n",
+ " return {'rewards':rewards}\n",
+ "\n",
+ "def test(cfg, env, agent):\n",
+ " print(\"开始测试!\")\n",
+ " rewards = [] # 记录所有回合的奖励\n",
+ " steps = []\n",
+ " for i_ep in range(cfg.test_eps):\n",
+ " ep_reward = 0 # 记录一回合内的奖励\n",
+ " ep_step = 0\n",
+ " state = env.reset() # 重置环境,返回初始状态\n",
+ " for _ in range(cfg.max_steps):\n",
+ " ep_step+=1\n",
+ " action = agent.predict_action(state) # 选择动作\n",
+ " next_state, reward, done, _ = env.step(action) # 更新环境,返回transition\n",
+ " state = next_state # 更新下一个状态\n",
+ " ep_reward += reward # 累加奖励\n",
+ " if done:\n",
+ " break\n",
+ " steps.append(ep_step)\n",
+ " rewards.append(ep_reward)\n",
+ " print(f\"回合:{i_ep+1}/{cfg.test_eps},奖励:{ep_reward:.2f}\")\n",
+ " print(\"完成测试\")\n",
+ " env.close()\n",
+ " return {'rewards':rewards}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 3. 定义环境"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 60,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import gym\n",
+ "import os\n",
+ "def all_seed(env,seed = 1):\n",
+ " ''' 万能的seed函数\n",
+ " '''\n",
+ " env.seed(seed) # env config\n",
+ " np.random.seed(seed)\n",
+ " random.seed(seed)\n",
+ " torch.manual_seed(seed) # config for CPU\n",
+ " torch.cuda.manual_seed(seed) # config for GPU\n",
+ " os.environ['PYTHONHASHSEED'] = str(seed) # config for python scripts\n",
+ " # config for cudnn\n",
+ " torch.backends.cudnn.deterministic = True\n",
+ " torch.backends.cudnn.benchmark = False\n",
+ " torch.backends.cudnn.enabled = False\n",
+ "def env_agent_config(cfg):\n",
+ " env = gym.make(cfg.env_name) # 创建环境\n",
+ " if cfg.seed !=0:\n",
+ " all_seed(env,seed=cfg.seed)\n",
+ " n_states = env.observation_space.shape[0]\n",
+ " n_actions = env.action_space.n\n",
+ " print(f\"状态空间维度:{n_states},动作空间维度:{n_actions}\")\n",
+ "\n",
+ " cfg.n_actions = env.action_space.n ## set the env action space\n",
+ " model = NoisyMLP(n_states, n_actions, hidden_dim = cfg.hidden_dim) # 创建模型\n",
+ " memory = ReplayBuffer(cfg.buffer_size)\n",
+ " agent = NoisyDQN(model,memory,cfg)\n",
+ " return env,agent"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 4、设置参数"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import argparse\n",
+ "import matplotlib.pyplot as plt\n",
+ "import seaborn as sns\n",
+ "class Config():\n",
+ " def __init__(self) -> None:\n",
+ " self.env_name = \"CartPole-v1\" # 环境名字\n",
+ " self.new_step_api = True # 是否用gym的新api\n",
+ " self.wrapper = None \n",
+ " self.render = False \n",
+ " self.algo_name = \"NoisyDQN\" # 算法名字\n",
+ " self.mode = \"train\" # train or test\n",
+ " self.seed = 0 # 随机种子\n",
+ " self.device = \"cpu\" # device to use\n",
+ " self.train_eps = 100 # 训练的回合数\n",
+ " self.test_eps = 20 # 测试的回合数\n",
+ " self.eval_eps = 10 # 评估的回合数\n",
+ " self.eval_per_episode = 5 # 每个回合的评估次数\n",
+ " self.max_steps = 200 # 每个回合的最大步数\n",
+ " self.load_checkpoint = False\n",
+ " self.load_path = \"tasks\" # 加载模型的路径\n",
+ " self.show_fig = False # 是否展示图片\n",
+ " self.save_fig = True # 是否存储图片\n",
+ "\n",
+ "\n",
+ " # 设置epsilon值\n",
+ " self.epsilon_start = 0.95 # 起始的epsilon值\n",
+ " self.epsilon_end = 0.01 # 终止的epsilon值\n",
+ " self.epsilon_decay = 500 # 衰减率\n",
+ " self.hidden_dim = 256 \n",
+ " self.gamma = 0.95 \n",
+ " self.lr = 0.0001 \n",
+ " self.buffer_size = 100000 # 经验回放的buffer大小\n",
+ " self.batch_size = 64 # batch size\n",
+ " self.target_update = 4 # 目标网络更新频率\n",
+ " self.value_layers = [\n",
+ " {'layer_type': 'linear', 'layer_dim': ['n_states', 256],\n",
+ " 'activation': 'relu'},\n",
+ " {'layer_type': 'linear', 'layer_dim': [256, 256],\n",
+ " 'activation': 'relu'},\n",
+ " {'layer_type': 'linear', 'layer_dim': [256, 'n_actions'],\n",
+ " 'activation': 'none'}]\n",
+ "\n",
+ "def smooth(data, weight=0.9): \n",
+ " '''用于平滑曲线,类似于Tensorboard中的smooth曲线\n",
+ " '''\n",
+ " last = data[0] \n",
+ " smoothed = []\n",
+ " for point in data:\n",
+ " smoothed_val = last * weight + (1 - weight) * point # 计算平滑值\n",
+ " smoothed.append(smoothed_val) \n",
+ " last = smoothed_val \n",
+ " return smoothed\n",
+ "\n",
+ "def plot_rewards(rewards,cfg, tag='train'):\n",
+ " ''' 画图\n",
+ " '''\n",
+ " sns.set()\n",
+ " plt.figure() # 创建一个图形实例,方便同时多画几个图\n",
+ " plt.title(f\"{tag}ing curve on {cfg.device} of {cfg.algo_name} for {cfg.env_name}\")\n",
+ " plt.xlabel('epsiodes')\n",
+ " plt.plot(rewards, label='rewards')\n",
+ " plt.plot(smooth(rewards), label='smoothed')\n",
+ " plt.legend()\n",
+ " plt.show()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 5、开始训练"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 63,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "状态空间维度:4,动作空间维度:2\n",
+ "开始训练!\n",
+ "Begin to update!\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/home/dingli/anaconda3/envs/joyrl/lib/python3.7/site-packages/ipykernel_launcher.py:26: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n",
+ "/home/dingli/anaconda3/envs/joyrl/lib/python3.7/site-packages/ipykernel_launcher.py:27: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "回合:10/100,奖励:11.00,Epislon:0.711\n",
+ "回合:20/100,奖励:18.00,Epislon:0.498\n",
+ "回合:30/100,奖励:20.00,Epislon:0.359\n",
+ "回合:40/100,奖励:20.00,Epislon:0.214\n",
+ "回合:50/100,奖励:94.00,Epislon:0.049\n",
+ "回合:60/100,奖励:200.00,Epislon:0.011\n",
+ "回合:70/100,奖励:200.00,Epislon:0.010\n",
+ "回合:80/100,奖励:200.00,Epislon:0.010\n",
+ "回合:90/100,奖励:200.00,Epislon:0.010\n",
+ "回合:100/100,奖励:200.00,Epislon:0.010\n",
+ "完成训练!\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAi4AAAHPCAYAAAB0ulFlAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAA9hAAAPYQGoP6dpAACyCUlEQVR4nOzdd3xUVfr48c+dmjppJKGElkDoEFCqiAqIUiy7iq4NrKurKOr6W1l33dUt6rru1wJWZC3YGxYEbCAooixIEakSaijpvUy59/fH5E4yySSZTDKTwvN+vXxJZm45czNz88w5z3mOommahhBCCCFEB2Bo6wYIIYQQQvhLAhchhBBCdBgSuAghhBCiw5DARQghhBAdhgQuQgghhOgwJHARQgghRIchgYsQQgghOgwJXIQQQgjRYUjgIoQQQogOQwKXTmjy5MksWLAgoH2vueYarrnmmlZukRD1ffjhh5x//vkMGTKE008/vU3a0JLPSmdTVlbGn/70J8444wwGDBjAP//5z7ZuUof0ww8/MGDAAH744Ye2bkqnZWrrBpyKfvzxR9avX8/cuXOx2Wxt3RwhQm7//v388Y9/5Mwzz+S3v/0tYWFhDW67cOFCFi1aREJCAl999RXh4eFez0+ePJn+/fvz/PPPB7vZzbJgwQKWLVvm+TkiIoL4+HiGDBnCzJkzOffcczEY6n931DSNjz76iHfffZc9e/bgcDjo1asX06dP57rrrqv3+q+55ho2btzIOeecw3PPPef13NGjR5kyZQp/+MMfuOGGGxpt7/PPP8+yZcu49dZb6dmzJ2lpaS149f5xuVx8+OGHfPjhh+zZs4fy8nKSkpIYO3YsV155JcOGDWu1czV23508eTJZWVmen+Pj4+nbty/XXXcd5557bqu1IdjeeOMNvv/+e7Zv387x48f51a9+xSOPPNLWzWp1Eri0gS1btrBo0SJ+9atfBSVwWbVqFYqiBLTvkiVLWrk1QtS3ceNGVFXlT3/6E7179/Zrn7y8PN58802uv/76VmtHSz4r/rBYLPzjH/8AoKqqiqysLNasWcMdd9zBmDFjePbZZ4mKivJs73K5+P3vf8/KlSs5/fTTmTdvHuHh4WzatImFCxeycuVKXn75ZRISEuqda82aNezYsYOhQ4cG1Nbvv/+eESNGMG/evMBebDNVVlYyb948vvnmG0aPHs3NN99MTEwMWVlZrFy5kmXLlvH111/TtWvXVjlfU/fdQYMGcd111wGQnZ3N22+/zbx583jggQe44oorWqUNwfbiiy9SVlbGsGHDyMnJaevmBI0ELu2cqqo4HA6sVqvf+1gsloDP15J927vy8nIiIiLauhkCdxACEB0d7fc+gwYNYsmSJVx55ZWN9tA0R7Df7yaTiYsuusjrsbvuuosXXniB//znP/z5z3/miSee8Dz34osvsnLlSq6//nruvfdez+OXX34506dP57bbbuOPf/wjL7zwgtcxu3fvTllZGYsWLarX6+KvvLw8+vXrF9C+vjidTlRVbfAaP/roo3zzzTf88Y9/5Nprr/V6bt68ebz88sut0g5/P/fJyclev6uLL76YadOm8fLLL3eYwGXp0qV0794dRVEYOXJkWzcnaCTHJcQWLlzIo48+CsCUKVMYMGAAAwYM4OjRowAMGDCAv/3tb3z88cfMnDmTYcOG8c033wDu3pDf/OY3jB07luHDh/PrX/+aVatW1TtH3XH7Dz74gAEDBrB582Yefvhhxo0bR0ZGBrfddhv5+fle+9bNcdHHa1esWMGzzz7LpEmTGDZsGHPnzuXQoUP1zv36668zZcoUhg8fzqWXXsqmTZualTfz0UcfcemllzJixAhGjx7NVVddxbfffut5fsCAASxcuNDv17xx40YeeOABxo8fz1lnncWqVas8j9f11ltvMWDAAPbu3et5bP/+/Z5vx8OGDePXv/41X331lV+vpby8nEceeYSzzjqLoUOHct5557FkyRLqLsiu/86//PJLZs2axdChQ5k5cybr1q3z6zxVVVUsXLiQ8847j2HDhjFx4kTmzZvH4cOHAfdwwYABA1iyZAkvv/wy55xzDsOHD+fqq6/2eq3QcI7TggULmDx5sl/tef3115k5cyZDhw5l4sSJPPjggxQXF3uenzx5sud3OH78+AZ/p3Xddttt5Obm8uabbza5rb/Xvu77xuFwsGjRIqZNm8awYcMYO3YsV1xxBevXrwfg/fffZ8CAAezcubPeOZ977jkGDRrEyZMnm2zfb3/7WyZOnMiqVas4cOAA4O6BWLJkCX369OH3v/99vX0mT57MxRdfzNq1a9m+fbvXc5GRkcydO5c1a9bw888/N3n+2vTP+NGjR/n666/r3ZPy8vK47777mDBhAsOGDePCCy/0GgKD+u+xqVOnMmzYMPbv3+/znCdOnODtt9/mjDPOqBe0ABiNRm644QZPb0tWVhYPPPAA5513HsOHD2fs2LHccccdnjbqGvrcN3Xf9SUxMZHU1FSvIaSdO3dy4403MmrUKEaOHMncuXPZunVrk9cYYNu2bdxwww2cdtppjBgxgquvvprNmzc3uo/D4WDMmDH88Y9/rPdcaWkpw4YN41//+pfnsR49egS1B7G9kB6XEDv33HM5ePAgy5cv549//CNxcXGAe0xV9/3337Ny5Uquuuoq4uLi6NGjBwCvvvoqkydP5oILLsDhcPDpp58yf/58nn/+ec4+++wmz/2Pf/wDm83GvHnzyMrK4pVXXuFvf/ub1ze+hixevBhFUbj++uspLS3lxRdf5J577uHdd9/1bPPGG2/wt7/9jdNPP51rr72WrKwsbrvtNmw2m1/dvYsWLWLhwoWMHDmSO+64A7PZzLZt2/j++++ZOHFik/v78uCDDxIfH89tt91GeXk5Z599NhEREaxcuZIxY8Z4bbtixQr69+9Peno6APv27eOKK64gOTmZm266ybPfbbfdxsKFCxsd+9Y0jd/97nf88MMPXHrppQwaNIhvvvmGRx99lJMnT3Lfffd5bb9582Y+//xzrrzySiIjI1m6dCl33HEHa9as8bxHfHG5XNx8881s2LCBmTNnMmfOHMrKyli/fj179+6lV69enm0//PBDysrKuPLKK6mqqmLp0qXMnTuXTz75hC5dugRyeevR81EmTJjAFVdcwYEDB3jzzTf56aefePPNNzGbzdx33318+OGHfPHFFzzwwANEREQwYMCAJo992mmnMW7cOF588UWuuOKKBntdmnvta1u0aBHPP/88s2fPZvjw4ZSWlrJjxw5+/vlnzjjjDM477zz+9re/8cknnzB48GCvfT/55BPGjBlDcnKyX9fqwgsv5Ntvv+W7776jb9++bN68maKiIubMmYPJ5PvWfPHFF/PBBx+wevVqhg8f7vXc3LlzeeWVV1i4cGGzel3S0tJ49NFHefjhh+natatnuCQ+Pp7KykquueYaDh8+zFVXXUVKSgqrVq1iwYIFFBcXM3fuXK9jffDBB1RVVXHZZZdhsViIiYnxec5169bhdDq58MIL/WrjTz/9xJYtW5g5cyZdu3YlKyuLN998kzlz5vDpp5/Wy/up+7mfNGlSk/fduhwOBydOnCA2NhZw3w+uuuoqIiMjufHGGzGZTLz99ttcc801vPbaa4wYMaLBY23YsIGbbrqJoUOHMm/ePBRF4YMPPmDu3Lm88cYb9X6XOrPZzNSpU/niiy948MEHvXqvvvzyS+x2OzNmzPDrGnYqmgi5F198UUtPT9eOHDlS77n09HRt4MCB2r59++o9V1FR4fWz3W7XZs2apc2ZM8fr8XPOOUe79957PT+///77Wnp6unbttddqqqp6Hn/ooYe0QYMGacXFxZ7Hrr76au3qq6/2/Pz9999r6enp2vTp07WqqirP46+88oqWnp6u7dmzR9M0TauqqtLGjBmjXXLJJZrD4fBs98EHH2jp6elex/Tl4MGD2sCBA7XbbrtNc7lcXs/VbnN6err21FNP1du/odd8xRVXaE6n02vbu+++Wxs/frzX49nZ2drAgQO1RYsWeR6bO3euNmvWLK/Xraqqdvnll2vTpk1r9PV88cUXWnp6uvbMM894PX777bdrAwYM0A4dOuT1moYMGeL12K5du7T09HRt6dKljZ7nvffe09LT07WXXnqp3nP6dTty5IiWnp6uDR8+XDtx4oTn+W3btmnp6enaQw895Hms7u9fd++992rnnHNOo23Jy8vThgwZol1//fVev8PXXntNS09P19577z3PY0899ZSWnp6u5eXlNXrMuttu3Lix3us955xztN/+9reen5tz7eu+by688EKvY/ly9913axMnTvR6jT///LOWnp6uvf/++57H7r33Xi0jI6PB4+zcudPr+r/88staenq69sUXXzS4T2FhoZaenq7NmzfP89jVV1+tzZw5U9M0TVu4cKGWnp6u7dixQ9O0mt/9iy++2Ohr0rT617F2mz766CPPY3a7Xbv88su1jIwMraSkxOs8o0aN8ut3+tBDD2np6enazp07m9xW0+rf+zRN07Zs2aKlp6dry5Yt8zzW2Oe+sfvuOeeco11//fVaXl6elpeXp+3atUu76667tPT0dO3vf/+7pmmaduutt2pDhgzRDh8+7Nnv5MmT2siRI7WrrrrK85h+z/z+++81TXN/DqdNm6Zdf/31XveyiooKbfLkydp1113X6Gv/5ptvtPT0dG316tVej990003alClTGtwvIyPD673dmchQUTs0evRon2PNtb9hFhUVUVJSwmmnneaz29qXyy67zKsb8fTTT8fl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SZNwuVy8/vrrXo+//PLLKIrCpEmTmtXehkybNo2CgoJ654H6vTVffvmlV17O9u3b2bZtm1dbevbsSWZmptfr2717Nz/++GOTbZkwYQJms5mlS5d6nfu9996jpKSEs846q1mvrTFjxozx9LpUVVV5PdeSa19QUOD1c2RkJL169fIamgX3H5kBAwbw3nvv8fnnnzNz5ky/hut0L7zwAt9++y0zZsygT58+gPu+cOONN3LgwAEef/zxevt8/fXXLFu2jHPOOafJLwJ6ddinn37a7zb5MmnSJHJycrzyK5xOJ0uXLiUiIoLRo0cHdNxu3boxe/Zsvv32W5YuXVrveVVV+e9//8uJEycA3z2SS5cubdbnPpD7rs5oNHLGGWfw1VdfeU2hzs3NZfny5Zx22mkNDhMOHTqUXr168d///tfn0Jj+WYuJiWHChAle/+k9RwaDgfPPP581a9bw8ccf43Q6T9lhIpAelzYxZMgQAB5//HFmzJiB2WzmnHPOabRI0llnncVLL73EjTfeyKxZs8jLy+ONN96gV69enu7jtmSxWLj99tv5+9//zty5c5k+fTpZWVl88MEHfg2p9O7dm1tuuYVnnnmGK6+8kmnTpmGxWPjpp59ISkry1LWYPXs2f/3rX7n99tuZMGECu3fv5ttvv210yrAvZrOZc889l08//ZSKigqf489//etfufLKK7ngggu47LLL6NmzJ7m5uWzdupUTJ07w8ccfN3j8yZMnM3bsWB5//HFPj9P69ev56quvmDt3bqsNM1188cV8+OGHPPzww2zfvp3TTjuNiooKNmzYwBVXXOE15NCrVy+uuOIKrrjiCux2O6+++iqxsbHceOONnm0uvfRSXn75ZW644QYuvfRS8vLyeOutt+jXr1+T+Qjx8fHcfPPNLFq0iBtvvJHJkydz4MAB3njjDU/9j9Y0b968el3r0LJrP3PmTMaMGcOQIUOIjY3lp59+4rPPPvOZ/HzxxRd7amg09NqcTicfffQR4O55ysrKYvXq1ezZs4exY8fyt7/9zWv7G2+8kV27drF48WK2bt3KtGnTCAsLY/PmzXz88cf069fPrxLueq5LS5N0L7/8ct5++20WLFjAzz//TI8ePfjss8/48ccfue+++5qV01PXggULOHLkCP/4xz/4/PPPOeecc7DZbBw/fpxVq1aRmZnJzJkzATj77LP56KOPiIqKol+/fmzdupXvvvvOM1XZH4Hcd2u78847+e6777jyyiu58sorMRqNvP3229jtdv7f//t/De5nMBj4xz/+wU033cSsWbP49a9/TXJyMidPnuSHH34gKirKr+nr06dPZ+nSpTz11FOkp6f7zAdcvXo1u3fvBtxJzHv27OGZZ54B3J+LzjK0JIFLGxg+fDjz58/nrbfe4ptvvkFVVb766qtGP0Djx4/nn//8J4sXL+ahhx4iJSWFe+65h6ysrHYRuABcffXVaJrGSy+9xL/+9S8GDhzIs88+yz/+8Q+/xpznz59PSkoKr732Go8//jjh4eEMGDDAq5rlZZddxtGjR3nvvff45ptvOO2003jppZd8Jmk2ZcaMGbz77rsoiuI1TKTr168f77//PosWLWLZsmUUFhYSHx/P4MGDue222xo9tsFg4Nlnn+Wpp55ixYoVfPDBB/To0YM//OEPrVqy3mg0snjxYp599lmWL1/O559/TmxsLKNGjar3rfziiy/GYDDwyiuvkJeXx/Dhw7n//vtJSkrybJOWlsa//vUvnnrqKR5++GH69evHo48+yvLly30W7avr9ttvJz4+ntdee42HH36YmJgYLrvsMu6+++4mh7Kaa+zYsYwZM6Zeu1py7a+55hpWr17N+vXrsdvtdO/enTvvvNPnOj8XXHABjz32GD179mywDofdbucPf/gD4P7GHx8fz9ChQ7ntttt8rlVkNBp5/PHHOeuss3jnnXd44oknPAHjhAkTeP755/2u9qvnugTSw6ALCwtj6dKlPPbYYyxbtozS0lL69u3Lww8/zK9//euAjwvu67F48WI++OADPvzwQ5555hkqKys9axU99thjnuT3P/3pTxgMBj755BOqqqoYNWqU54ucvwK579bWv39/Xn/9df7zn//w/PPPo2kaw4cP59///nejNVzA/V59++23eeaZZ3jttdcoLy8nMTGR4cOHc/nll/t1/lGjRtGtWzeOHz/eYG/L559/7lUccOfOnZ5ZV127du00gYuitZfsP9EpqarK+PHjOffccz1rtojQas5Ce8J/+fn5nHnmmdx6661NBrIt4XA4uOWWW/j+++891auFOJVJjotoNVVVVfXyKj788EMKCwvrVakVoqNbtmwZLper3lpErc1sNrNw4UIGDhzI/Pnzm13SX4jORoaKRKvZunUrDz/8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lBSrsuzsbPLz8/H39wfkc+R6aWi7xsfHYzQa2b59u1YnPT2dlJQU4uPjrykG+2t69i1u7NixfPbZZzzxxBNMmzaNnJwc3njjDcaOHVvjy1Y03Lx589i0aROzZ8+mqKjIatOiiIgIHB0defLJJ3nmmWcICAigZ8+eJCUlceDAAZkr0Eh6vZ6ePXvWei0yMpLIyEgAae8mcPfddxMVFcXMmTNJTEzEycmJ9957D0dHRx588EEAHnvsMcaPH89LL73EwIEDSU5O5ttvv+Wtt95q5uhty9ixY3n11VdZsGABCQkJFBQUaHO5Ll+mK+/rxistLWXz5s2AJRksKirSkpQePXrg7e3doHat3tF4zpw5zJo1CycnJ9566y0MBgP33nvvNcWoqFf24wgrqampzJ8/n3379uHm5sbw4cNJTEyUbP0aJCQkkJ2dXeu1jRs3ar88V61axdKlSzl16hRBQUE8/fTT9OvX70aGektKTk5m/PjxrF69mqioKK1c2vva5eXl8dprr7Fp0yYqKiqIiYnhueeesxpG2rhxI//85z9JT0+nbdu2TJ06lT/96U/NGLXtUVWVFStWsHz5cjIzM3FzcyM6OprExMQau73K+7pxsrKyuOuuu2q99umnn2o/hBrSriaTiddee43vv/+eyspK+vTpwwsvvHDNP/wlcRFCCCGEzZA5LkIIIYSwGZK4CCGEEMJmSOIihBBCCJshiYsQQgghbIYkLkIIIYSwGZK4CCGEEMJmSOIihBBCCJshiYsQQgghbIYkLkKIm05CQgKzZ89usvslJydjMBhITk5usnsKIZqHJC5CCCGEsBlyyKIQ4qazfv16FEVp7jCEEDchSVyEEDcdOcRUCFEXGSoSQlxVTk4Ozz33HL1796ZTp04MHjyY1atXa9er55AkJSXx5ptvEhcXR3R0NNOnT+f06dNW9zpx4gRPPvkkcXFxREVFER8fT2JiIiaTSatT2xyXzMxMZs6cSY8ePejSpQujR4/mxx9/rBHrmTNnePzxx4mOjiY2NpZXX32V8vLyWl/X/v37efTRR+nWrRtdunThoYceYs+ePVZ1ioqKeOWVV0hISKBTp07ExsYyceJEDh061NhmFEI0AelxEULU6/z584wePRpFURg3bhze3t5s2bKF559/nqKiIh555BGt7uLFi1EUhSlTppCbm8snn3zCI488wpo1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",
+ "text/plain": [
+ "