diff --git a/docs/ch5/main.md b/docs/ch5/main.md index b2992a4..232824d 100644 --- a/docs/ch5/main.md +++ b/docs/ch5/main.md @@ -1,82 +1,173 @@ # 免模型控制 -回顾前面讲的控制,即给定一个马尔可夫决策过程,输出最优策略以及对应的最优价值函数。而免模型则是指不需要知道环境的状态转移概率的一类算法,实际上很多经典的强化学习算法都是免模型控制的。本章会重点介绍两种基础的免模型算法,Q-learning 和 Sarsa,也都是基于时序差分的方法。 +回顾前面讲的控制,即给定一个马尔可夫决策过程,输出最优策略以及对应的最优价值函数。而免模型则是指不需要知道环境的状态转移概率的一类算法,实际上很多经典的强化学习算法都是免模型控制的。本章会重点介绍两种基础的免模型算法,$\text{Q-learning}$ 和 $\text{Sarsa}$ ,也都是基于时序差分的方法。 ## Q-learning 算法 -在时序差分方法的章节中我们讲的是状态价值函数的时序差分,其目的是为了预测每个状态的价值。而在预测与控制的内容中我们提到了控制的方法是需要输出最优策略的同时,也会输出对应的状态价值函数,预测的方法也是为了帮助解决控制问题做一个铺垫。不知道读者还记不记得,策略与状态价值函数之间是存在一个联系的,这个联系就是动作价值函数,如下: +在时序差分方法的章节中我们讲的是状态价值函数的时序差分,其目的是为了预测每个状态的价值。而在预测与控制的内容中我们提到了控制的方法是需要输出最优策略的同时,也会输出对应的状态价值函数,预测的方法也是为了帮助解决控制问题做一个铺垫。不知道读者还记不记得,策略与状态价值函数之间是存在一个联系的,这个联系就是动作价值函数,如式 $\text(5.1)$ 所示: $$ +\tag{5.1} V_\pi(s)=\sum_{a \in A} \pi(a \mid s) Q_\pi(s, a) $$ -因此,为了解决控制问题,我们只需要直接预测动作价值函数,然后在决策时选择动作价值即 Q 值最大对应的动作即可。这样一来,策略和动作价值函数同时达到最优,相应的状态价值函数也是最优的,这就是 Q-learning 算法的思路。 +因此,为了解决控制问题,我们只需要直接预测动作价值函数,然后在决策时选择动作价值即 Q 值最大对应的动作即可。这样一来,策略和动作价值函数同时达到最优,相应的状态价值函数也是最优的,这就是 $\text{Q-learning}$ 算法的思路。 -Q-learning 算法更新公式如下: +$\text{Q-learning}$ 算法更新公式如式 $\text(5.2)$ 所示: $$ +\tag{5.2} Q(s_t,a_t) \leftarrow Q(s_t,a_t)+\alpha[r_t+\gamma\max _{a}Q(s_{t+1},a)-Q(s_t,a_t)] $$ -我们再回忆一下时序差分方法中状态价值函数的更新公式,如下: +我们再回忆一下时序差分方法中状态价值函数的更新公式,如式 $\text(4.5)$ : $$ +\tag{5.3} V(s_t) \leftarrow V(s_t) + \alpha[r_{t+1}+\gamma V(s_{t+1})- V(s_{t})] $$ -我们会发现两者的更新方式是一样的,都是基于时序差分的更新方法。不同的是,动作价值函数更新时是直接拿最大的未来动作价值的 $\gamma\max _{a}Q(s_{t+1},a)$ 来估计的,而在状态价值函数更新中相当于是拿对应的平均值来估计的。这就会导致这个估计相当于状态价值函数中的估计更不准确,一般称为 **Q 值的过估计**,当然这个过估计仅仅限于以 Q-learning 为基础的算法,不同的算法为了优化这个问题使用了不同的估计方式,其中就包括本章后面会讲的 Sarsa 算法,暂时先不详细展开。 +我们会发现两者的更新方式是一样的,都是基于时序差分的更新方法。不同的是,动作价值函数更新时是直接拿最大的未来动作价值的 $\gamma\max _{a}Q(s_{t+1},a)$ 来估计的,而在状态价值函数更新中相当于是拿对应的平均值来估计的。这就会导致这个估计相当于状态价值函数中的估计更不准确,一般称为 **Q 值的过估计**,当然这个过估计仅仅限于以 $\text{Q-learning}$ 为基础的算法,不同的算法为了优化这个问题使用了不同的估计方式,其中就包括本章后面会讲的 Sarsa 算法,暂时先不详细展开。 ### Q 表格 -回到 Q-learning 算法本身,其实到这里我们已经把 Q-learning 算法的核心内容讲完了,即上面的更新公式,但是有必要提到几个概念,Q 表格和探索策略,以便帮助读者加深理解。 +回到 $\text{Q-learning}$ 算法本身,其实到这里我们已经把 $\text{Q-learning}$ 算法的核心内容讲完了,即上面的更新公式 $\text(4.5)$。但是有必要提到几个概念,$\text{Q}$ 表格和探索策略,以便帮助读者加深理解。 -关于 Q 表格,其实我们在前面讲蒙特卡洛方法的过程中已经形成了雏形,就是我们所举的价值函数网格分布的例子。我们接着这个例子继续讲,不记得的同学建议再往前回顾一下。如图 5.1 所示,这个例子是以左上角为起点,右下角为终点,机器人只能向右或者向下走,每次移动后机器人会收到一个 $-1$ 的奖励,即奖励函数 $R(s,a)=1$,然后求出机器人从起点走到终点的最短路径。 +关于 $\text{Q}$ 表格,其实我们在前面讲蒙特卡洛方法的过程中已经形成了雏形,就是我们所举的价值函数网格分布的例子。我们接着这个例子继续讲,不记得的同学建议再往前回顾一下。如图 $\text{5.1}$ 所示,这个例子是以左上角为起点,右下角为终点,机器人只能向右或者向下走,每次移动后机器人会收到一个 $-1$ 的奖励,即奖励函数 $R(s,a)=1$,然后求出机器人从起点走到终点的最短路径。
-
图 5.1 迷你网格示例
+
图 $\text{5.1}$ 迷你网格示例
-其实这个答案比较简单,机器人要达到终点只能先向右然后向下,或者先向下然后向右走,有且只有两条路径,并且同时也是最短路径。当时出于简化计算的考虑(状态价值的计算实在是太繁琐了!),只考虑了 $2 \times 2$ 的网格。这次我们可以将问题变得更有挑战性一些,如图 5.2 所示, 我们将网格变成大一点的 $3 \times 3$ 的网格,并且不再限制机器人只能向右或者向下走,它可以向上、向下、向左和向右随意走动,当然每次还是只能走动一格,并且不能出边界,另外奖励函数也不变。 +其实这个答案比较简单,机器人要达到终点只能先向右然后向下,或者先向下然后向右走,有且只有两条路径,并且同时也是最短路径。当时出于简化计算的考虑(状态价值的计算实在是太繁琐了!),只考虑了 $2 \times 2$ 的网格。这次我们可以将问题变得更有挑战性一些,如图 $\text{5.2}$ 所示, 我们将网格变成大一点的 $3 \times 3$ 的网格,并且不再限制机器人只能向右或者向下走,它可以向上、向下、向左和向右随意走动,当然每次还是只能走动一格,并且不能出边界,另外奖励函数也不变。
-
图 5.2 迷你网格进阶
- -我们还是把机器人的位置看作状态,这样一来总共有 $9$ 个状态,$4$ 个动作 $a_1,a_2,a_3,a_4$(分别对应上、下、左、右)。我们知道 Q 函数,也就是状态价值函数的输入就是状态和动作,输出就是一个值,由于这里的状态和动作都是离散的,这样一来我们就可以用一个表格来表示,如下: +
图 $\text{5.2}$ 迷你网格进阶
+ +我们还是把机器人的位置看作状态,这样一来总共有 $\text{9}$ 个状态,$\text{4}$ 个动作 $a_1,a_2,a_3,a_4$(分别对应上、下、左、右)。我们知道 Q 函数,也就是状态价值函数的输入就是状态和动作,输出就是一个值,由于这里的状态和动作都是离散的,这样一来我们就可以用一个表格来表示,如表 $\text{5.1}$ 所示。 + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
$\space$ $s_1$$s_2$$s_3$$s_4$$s_5$$s_6$$s_7$$s_8$$s_9$
$a_1$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$
$a_2$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$
$a_3$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$
$a_4$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$$\text{0}$
+
+
表 $\text{5.1}$ :$\text{Q}$ 表格
+
-| | $s_1$ | $s_2$ | $s_3$ | $s_4$ | $s_5$ | $s_6$ | $s_7$ | $s_8$ | $s_9$ | -| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| $a_1$ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| $a_2$ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| $a_3$ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| $a_4$ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -表格的横和列对应状态和动作,数值表示对应的 $Q$ 值,比如最左上角的值表示 $Q(s_1,a_1)=0$,这就是 **Q 表格**。在实践中,我们可以给所有的 $Q$ 预先设一个值,这就是 $Q$ 值的初始化。这些值是可以随机的,这里为了方便全部初始化为 $0$,但除了终止状态对应的值必须为 0 之外,这点我们在讲时序差分的方法中有提到过,比如这里终止状态 $s_9$ 对应的所有 $Q$ 值,包括$Q(s_9,a_1),Q(s_9,a_2),Q(s_9,a_3),Q(s_9,a_4)$ 等都必须为 $0$,并且也不参与 $Q$ 值的更新。 +表格的横和列对应状态和动作,数值表示对应的 $Q$ 值,比如最左上角的值表示 $Q(s_1,a_1)=0$,这就是 $Q$ 表格。在实践中,我们可以给所有的 $Q$ 预先设一个值,这就是 $Q$ 值的初始化。这些值是可以随机的,这里为了方便全部初始化为 $0$,但除了终止状态对应的值必须为 $\text{0}$ 之外,这点我们在讲时序差分的方法中有提到过,比如这里终止状态 $s_9$ 对应的所有 $Q$ 值,包括 $Q(s_9,a_1),Q(s_9,a_2),Q(s_9,a_3),Q(s_9,a_4)$ 等都必须为 $\text{0}$ ,并且也不参与 $Q$ 值的更新。 -现在我们讲讲 $Q$ 值的更新过程,其实跟前面讲的状态价值更新是类似的。但不一样的是,前面讲状态价值的更新是蒙特卡洛方法,这次是时序差分方法。具体的做法是,我们会让机器人自行在网格中走动,走到一个状态,就把对应的 $Q$ 值 更新一次,这个过程就叫做 **探索** 。这个探索的过程也是时序差分方法结合了蒙特卡洛方法的体现。当然探索的方式有很多种,很难在读者初学的阶段一次性就全部倒腾出来,这也是为什么在前面讲时序差分方法的时候我们只讲了更新公式而没有讲实际是怎么操作的原因,之后会结合具体情况一一道来,下面我们讲讲 Q-learning 算法中智能体是怎么探索的。 +现在我们讲讲 $Q$ 值的更新过程,其实跟前面讲的状态价值更新是类似的。但不一样的是,前面讲状态价值的更新是蒙特卡洛方法,这次是时序差分方法。具体的做法是,我们会让机器人自行在网格中走动,走到一个状态,就把对应的 $Q$ 值 更新一次,这个过程就叫做 **探索** 。这个探索的过程也是时序差分方法结合了蒙特卡洛方法的体现。当然探索的方式有很多种,很难在读者初学的阶段一次性就全部倒腾出来,这也是为什么在前面讲时序差分方法的时候我们只讲了更新公式而没有讲实际是怎么操作的原因,之后会结合具体情况一一道来,下面我们讲讲 $\text{Q-learning}$ 算法中智能体是怎么探索的。 ### 探索策略 按理说来,直接根据 $Q$ 函数(即每次选择 $Q$ 值最大对应的动作)来探索是没有问题的。但是由于在探索的过程中 $Q$ 值也是估计出来的,然后还需要利用先前的估计值来更新 $Q$ 值(也就是自举的过程),换句话说,由于自举依赖于先前的估计值,因此这可能会导致估计出的价值函数存在某种程度上的偏差。 -通俗的理解就是,如果我们一直基于某种思路去工作,工作完之后总结经验(也就是学习的过程)以便提高工作能力和效率,这种方式也许会让我们工作得越来越好,但任何一种思路都不是完美的,都会有一定的瑕疵,也就是说可能会慢慢走偏,此时换一种思路也许就会豁然开朗。实际上人类社会和科学的发展也有着异曲同工之处,举一个古老的例子,很久之前人们认为地球是宇宙的中心(即地心说),并且在公元2世纪将它形成一个体系化的理论,并且以此理论为基础也解决了很多当时难以捉摸的问题。但是随着科学的进一步发展这种理论也开始走到极限,直到后面哥白尼提出日心说的理论,尽管当时哥白尼由于不被坚持地心说的守旧派而为此付出了沉重的代价。守旧派就相当于一直坚持同一个思路去探索学习,这种探索思路总会受限于当时人们的认知,并且迟早会到达极限,除非出现一个偶然的因素让我们切换一种思路探索并且学习到更多的东西。尽管在今天看来,地心说和日心说都不是准确的,但其实诸多类似的历史事件也告诉我们一个道理,我们需要牢牢掌握现有的知识形成自己的理论或者经验体系,同时也要保持好奇心与时俱进,这样才能长久地发展下去,对于强化学习中的智能体也是如此。 +通俗的理解就是,如果我们一直基于某种思路去工作,工作完之后总结经验(也就是学习的过程)以便提高工作能力和效率,这种方式也许会让我们工作得越来越好,但任何一种思路都不是完美的,都会有一定的瑕疵,也就是说可能会慢慢走偏,此时换一种思路也许就会豁然开朗。实际上人类社会和科学的发展也有着异曲同工之处,举一个古老的例子,很久之前人们认为地球是宇宙的中心(即地心说),并且在公元2世纪将它形成一个体系化的理论,并且以此理论为基础也解决了很多当时难以捉摸的问题。 + +但是随着科学的进一步发展这种理论也开始走到极限,直到后面哥白尼提出日心说的理论,尽管当时哥白尼由于不被坚持地心说的守旧派而为此付出了沉重的代价。守旧派就相当于一直坚持同一个思路去探索学习,这种探索思路总会受限于当时人们的认知,并且迟早会到达极限,除非出现一个偶然的因素让我们切换一种思路探索并且学习到更多的东西。尽管在今天看来,地心说和日心说都不是准确的,但其实诸多类似的历史事件也告诉我们一个道理,我们需要牢牢掌握现有的知识形成自己的理论或者经验体系,同时也要保持好奇心与时俱进,这样才能长久地发展下去,对于强化学习中的智能体也是如此。 -回到正题,Q-learning 算法是采用了一个叫做 $\varepsilon-greedy$ 的探索策略,$\varepsilon-greedy$ 是指智能体在探索的过程中,会以 $1-\varepsilon$ 的概率按照 $Q$ 函数来执行动作,然后以剩下 $\varepsilon$ 的概率随机动作。这个 $1-\varepsilon$ 的过程就是前面所说的“守旧派”,即以现有的经验去动作,$\varepsilon$ 就是保持一定的好奇心去探索可能的更优的动作。当然,通常这个 $\varepsilon$ 的值会设置的特别小,比如 $0.1$,毕竟“守旧”并不总是一件坏事,新的东西出现的概率总是特别小的,如果保持过度的好奇心即$\varepsilon$ 的值设得很大,就很有可能导致智能体既学不到新的东西也丢掉了已经学习到的东西,所谓“捡了芝麻丢了西瓜”。而且一般来说也会随着学到的东西增多而更少,就好比科学知识体系几近完备的现代,能够探索到新的东西的概率是特别特别小的。**因此通常在实践中,这个 $\varepsilon$ 的值还会随着时步的增长而衰减,比如从 $0.1$ 衰减到 $0.01$**。 +回到正题,$\text{Q-learning}$ 算法是采用了一个叫做 $\varepsilon-greedy$ 的探索策略,$\varepsilon-greedy$ 是指智能体在探索的过程中,会以 $1-\varepsilon$ 的概率按照 $Q$ 函数来执行动作,然后以剩下 $\varepsilon$ 的概率随机动作。这个 $1-\varepsilon$ 的过程就是前面所说的“守旧派”,即以现有的经验去动作,$\varepsilon$ 就是保持一定的好奇心去探索可能的更优的动作。当然,通常这个 $\varepsilon$ 的值会设置的特别小,比如 $\text{0.1}$ ,毕竟“守旧”并不总是一件坏事,新的东西出现的概率总是特别小的,如果保持过度的好奇心即 $\varepsilon$ 的值设得很大,就很有可能导致智能体既学不到新的东西也丢掉了已经学习到的东西,所谓“捡了芝麻丢了西瓜”。而且一般来说也会随着学到的东西增多而更少,就好比科学知识体系几近完备的现代,能够探索到新的东西的概率是特别特别小的。因此通常在实践中,这个 $\varepsilon$ 的值还会随着时步的增长而衰减,比如从 $\text{0.1}$ 衰减到 $\text{0.01}$ 。 -更确切地说,以 $1-\varepsilon$ 的概率按照 $Q$ 函数来执行动作的这个过程在强化学习中我们一般称作利用(exploitation),而以 $\varepsilon$ 的概率随机动作的过程称之为探索(exploration)。什么时候需要探索的更多,什么时候需要利用的更多,其实是很难下定论的,这就是大多数强化学习情景中需要面临的**探索-利用窘境(exploration-exploitation dilemma)**。我们需要在探索和利用之间做一个权衡,这其实跟深度学习中讲的 **偏差-方差权衡(Bias-Variance Tradeoff)** 是如出一辙的。 +更确切地说,以 $1-\varepsilon$ 的概率按照 $Q$ 函数来执行动作的这个过程在强化学习中我们一般称作利用( $\text{exploitation}$ ),而以 $\varepsilon$ 的概率随机动作的过程称之为探索( $\text{exploration}$ )。什么时候需要探索的更多,什么时候需要利用的更多,其实是很难下定论的,这就是大多数强化学习情景中需要面临的**探索-利用窘境**( $\text{exploration-exploitation dilemma}$ )。我们需要在探索和利用之间做一个权衡,这其实跟深度学习中讲的**偏差-方差权衡**( $\text{Bias-Variance Tradeoff}$ )是如出一辙的。 -讲到这里,我们就可以贴出 Q-learning 算法的伪代码了,如图 5.3 所示。 +讲到这里,我们就可以贴出 $\text{Q-learning}$ 算法的伪代码了,如图 $\text{5.3}$ 所示。
-
图 5.3 Q-learning 算法伪代码
+
图 $\text{5.3}$ $\text{Q-learning}$ 算法伪代码
+ + + +## Sarsa 算法 + +$\text{Sarsa}$ 算法虽然在刚提出的时候被认为是 $\text{Q-learning}$ 算法的改进,但在今天看来是非常类似,但是模式却不同的两类算法,$\text{Q-learning}$ 算法被认为是 $\text{Off-Policy}$ 算法,而 $\text{Sarsa}$ 算法相对地则是 $\text{On-policy}$ 的,具体我们后面会展开说明。我们先来看 $\text{Sarsa}$ 算法,我们讲到 $\text{Sarsa}$ 算法跟 $\text{Q-learning}$ 算法是非常类似的,这是因为两者之间在形式上只有 $Q$ 值更新公式是不同的,如式 $\text(5.4)$ 所示: + +$$ +\tag{5.4} +Q(s_t,a_t) \leftarrow Q(s_t,a_t)+\alpha[r_t+\gamma Q(s_{t+1},a_{t+1})-Q(s_t,a_t)] +$$ + +也就是说,$\text{Sarsa}$ 算法是直接用下一个状态和动作对应的 $Q$ 值来作为估计值的,而 $\text{Q-learning}$ 算法则是用下一个状态对应的最大 $Q$ 值。然后我们就可以贴出伪代码了,如图 $\text{5.4}$ 所示。 + +
+ +
+
图 $\text{5.4}$ $\text{Sarsa}$ 算法伪代码
+ +## 同策略与异策略 + +尽管 $\text{Q-learning}$ 算法和 $\text{Sarsa}$ 算法仅在一行更新公式上有所区别,但这两种算法代表的是截然不同的两类算法。我们注意到,$\text{Sarsa}$ 算法在训练的过程中当前策略来生成数据样本,并在其基础上进行更新。换句话说,策略评估和策略改进过程是基于相同的策略完成的,这就是**同策略算法**。相应地,像 $\text{Q-learning}$ 算法这样从其他策略中获取样本然后利用它们来更新目标策略,我们称作**异策略算法**。也就是说,异策略算法基本上是从经验池或者历史数据中进行学习的。这两类算法有着不同的优缺点,同策略相对来说更加稳定,但是效率较低,如同我们实战中展示的那样。而异策略通常来说更加高效,但是需要让获取样本的策略和更新的策略具备一定的分布匹配条件,以避免偏差。 ## 实战:Q-learning 算法 -本节开始我们的第一个算法实战,由于是第一个实战,所以会讲得偏详细一些,后面的算法实战部分可能会讲得越来越粗,如果读者们有不明白的地方,欢迎随时交流讨论。实战的思路会跟理论学习有所区别,并且因人而异,因此读者们在学习实战的时候做个参考即可,最重要的是有自己的想法。此外,笔者认为**对于实战来说最重要的一点就是写好伪代码**。如果说理论部分是数学语言,实战部分就是编程语言,而伪代码则是从数学语言到编程语言之间的一个过渡,这也是笔者为什么在讲解每个算法的时候尽可能贴出伪代码的原因。在每个算法实战的内容中,笔者基本会按照定义算法,定义训练,定义环境,设置参数以及开始训练等步骤为读者们展开,这是笔者个人的编程习惯。由于这次我们是第一次讲到实战,所以会先讲一下定义训练,因为其中涉及到一个所有强化学习通用的训练模式。 +本节开始我们的第一个算法实战,由于是第一个实战,所以会讲得偏详细一些,后面的算法实战部分可能会讲得越来越粗,如果读者们有不明白的地方,欢迎随时交流讨论。实战的思路会跟理论学习有所区别,并且因人而异,因此读者们在学习实战的时候做个参考即可,最重要的是有自己的想法。 + +此外,笔者认为**对于实战来说最重要的一点就是写好伪代码**。如果说理论部分是数学语言,实战部分就是编程语言,而伪代码则是从数学语言到编程语言之间的一个过渡,这也是笔者为什么在讲解每个算法的时候尽可能贴出伪代码的原因。在每个算法实战的内容中,笔者基本会按照定义算法,定义训练,定义环境,设置参数以及开始训练等步骤为读者们展开,这是笔者个人的编程习惯。由于这次我们是第一次讲到实战,所以会先讲一下定义训练,因为其中涉及到一个所有强化学习通用的训练模式。 ### 定义训练 回顾一下伪代码的第二行到最后一行,我们会发现一个强化学习训练的通用模式,首先我们会迭代很多个($M$)回合,在每回合中,首先重置环境回到初始化的状态,智能体根据状态选择动作,然后环境反馈中下一个状态和对应的奖励,同时智能体会更新策略,直到回合结束。这其实就是马尔可夫决策过程中智能体与环境互动的过程,写成一段通用的代码如下: @@ -102,11 +193,11 @@ for i_ep in range(train_eps): # 遍历每个回合 ### 定义算法 -强化学习中有几个要素,智能体、环境、经验池(经回放),在实践中也需要逐一定义这些要素。我们一般首先定义智能体,或者说算法,在`Python`中一般定义为类即可。再考虑一下智能体在强化学习中主要负责哪些工作。 +强化学习中有几个要素,智能体、环境、经验池(经回放),在实践中也需要逐一定义这些要素。我们一般首先定义智能体,或者说算法,在 $\text{Python}$ 中一般定义为类即可。再考虑一下智能体在强化学习中主要负责哪些工作。 #### 采样动作 -首先在训练中我需要采样动作与环境交互,于是我们可以定义一个类方法,命名为`sample_action`,如下: +首先在训练中我需要采样动作与环境交互,于是我们可以定义一个类方法,命名为 `sample_action` ,如下: ```python class Agent: @@ -127,7 +218,7 @@ class Agent: ``` 在这里我们用了 $\varepsilon-greedy$ 策略,其中 $\varepsilon$ 会随着采样的步数指数衰减,感兴趣的读者也可以直接设置固定的 $\varepsilon=0.1$ 试试。 -在 Q-learning 算法中还有一个重要的元素,即 $Q$ 表,$Q$ 表的作用是输入状态和动作,输出一个即可,这样一来我们可以用一个二维的数组来表示,比如 `Q_table[0][1] = 0.1`可以表示 $Q(s_0,a_1)=0.1$ (注意`Python`中下标是从`0`开始)。而在我们的示例代码中用了一个默认字典来表示,如下: +在 $\text{Q-learning}$ 算法中还有一个重要的元素,即 $Q$ 表,$Q$ 表的作用是输入状态和动作,输出一个即可,这样一来我们可以用一个二维的数组来表示,比如 `Q_table[0][1] = 0.1`可以表示 $Q(s_0,a_1)=0.1$ (注意 $\text{Python}$ 中下标是从 $\text{0}$ 开始)。而在我们的示例代码中用了一个默认字典来表示,如下: ```python self.Q_table = defaultdict(lambda: np.zeros(n_actions)) @@ -152,7 +243,7 @@ class Agent: #### 更新方法 -所有强化学习算法的采样动作和预测动作方式几乎是比较固定的,对于每个智能体来说最核心的还是更新网络的方式,在 Q-learning 算法中的更新方式较为简单,而且不需要经验回放(具体会在 DQN 算法中展开),如下: +所有强化学习算法的采样动作和预测动作方式几乎是比较固定的,对于每个智能体来说最核心的还是更新网络的方式,在 $\text{Q-learning}$ 算法中的更新方式较为简单,而且不需要经验回放(具体会在 $\text{DQN}$ 算法中展开),如下: ```python def update(self, state, action, reward, next_state, terminated): @@ -168,29 +259,29 @@ def update(self, state, action, reward, next_state, terminated): ### 定义环境 -在本节中我们选择了一个叫做 `CliffWalking-v0` 的环境(中文名叫“悬崖寻路”),跟前面动态规划章节中举的机器人最短路径是类似的,只是要更加复杂一些。 +在本节中我们选择了一个叫做 $\text{CliffWalking-v0}$ 的环境(中文名叫 “悬崖寻路” ),跟前面动态规划章节中举的机器人最短路径是类似的,只是要更加复杂一些。 + +如图 $\text{5.5}$ 所示,整个环境中共有 $\text{48}$ 个网格,其中黄色网格(标号为 $\text{36}$ )为起点,绿色网格(标号为 $\text{47}$ )为终点,红色的网格表示悬崖,智能体的目标是以最短的路径从起点到终点,并且避开悬崖。由于这个环境比较简单,我们一眼就能看出来最优的策略应当是从起点向上沿着 $\text{24}$ 号网格直线走到 $\text{35}$ 号网格最后到达终点,后面我们看看强化学习智能体能不能学出来。
-
图 5.4 CliffWalking-v0 环境示意图
- -如图 5.4 所示,整个环境中共有 $48$ 个网格,其中黄色网格(标号为 $36$ )为起点,绿色网格(标号为 $47$ )为终点,红色的网格表示悬崖,智能体的目标是以最短的路径从起点到终点,并且避开悬崖。由于这个环境比较简单,我们一眼就能看出来最优的策略应当是从起点向上沿着 $24$ 号网格直线走到 $35$ 号网格最后到达终点,后面我们看看强化学习智能体能不能学出来。 +
图 $\text{5.5}$ $\text{CliffWalking-v0}$ 环境示意图
此外,我们做强化学习算法的时候更重要的还是要对环境本身的状态、动作和奖励了解,以便指导我们优化算法。在这个环境中,状态比较简单,就是当前智能体所处的网格位置或者说编号,动作就是上右下左(分别是 $0,1,2,3$ ,顺序可能有点奇怪,但官方环境是这么设置的),奖励的话分几种情况,一个是每走一个白色网格(包括起点)是会给一个 $-1$ 的奖励,到达终点的时候得到的奖励为 $0$,走到边沿、悬崖或者终点的时候本回合游戏结束,这些设置在官方源码中都能找到。这里之所以每走一个网格会给一个负的奖励,是因为我们的目标是最短路径,换句话说每走一步都是有代价的,所以需要设置一个负的奖励或者说惩罚,设置正的奖励会容易误导智能体在训练过程中一直走网格,设置 $0$ 的话也是一样的,会让智能体找不到目标。奖励就相当于我们给智能体设置的目标,因此如何合理地设置奖励其实也是一项复杂的工程,具体后面我们会再展开。 > ① CliffWalking-v0 源码链接:https://github.com/openai/gym/blob/master/gym/envs/toy_text/cliffwalking.py -我们选择的环境是 `OpenAI Gym` 开发的,它提供了一套标准化的环境,包括经典的控制理论问题和游戏,代码封装得也比较好,只需要一行代码就能定义好环境,如下: +我们选择的环境是 $\text{OpenAI Gym}$ 开发的,它提供了一套标准化的环境,包括经典的控制理论问题和游戏,代码封装得也比较好,只需要一行代码就能定义好环境,如下: ```python env = gym.make('CliffWalking-v0') ``` -当然大多数情况下需要根据需求建立我们自己的环境,这时我们也可以仿照 `Gym` 的模式来做,具体教程会在后面的章节中单独讲到。 +当然大多数情况下需要根据需求建立我们自己的环境,这时我们也可以仿照 $\text{Gym}$ 的模式来做,具体教程会在后面的章节中单独讲到。 -在 gym 中我们可以通过以下方式获取环境的状态数和动作数: +在 $\text{Gym}$ 中我们可以通过以下方式获取环境的状态数和动作数: ```python n_states = env.observation_space.n # 状态数 @@ -208,7 +299,7 @@ print(f"状态数:{n_states}, 动作数:{n_actions}") ### 设置参数 -智能体、环境和训练的代码都写好之后,就是设置参数了,由于 Q-learning 算法的超参数(需要人工调整的参数)比较少,其中 $gamma$ (折扣因子)比较固定,设置在 $0.9$ 到 $0.999$ 之间,一般设置成 $0.99$ 即可。而学习率 $alpha$ 在本章节中设置的比较大,为 $0.1$,实际更复杂的环境和算法中学习率是小于 $0.01$,因为太大很容易发生过拟和的问题,只是本节的环境和算法都比较简单,为了收敛得更快点所以设置得比较大。此外由于我们探索策略中的 $\varepsilon$ 是会随着采样步数衰减的,在实践过程中既不能让它衰减得太快也不能让它衰减得太慢,因此需要合理设置如下参数: +智能体、环境和训练的代码都写好之后,就是设置参数了,由于 $\text{Q-learning}$ 算法的超参数(需要人工调整的参数)比较少,其中 $\gamma$ (折扣因子)比较固定,设置在 $0.9$ 到 $0.999$ 之间,一般设置成 $0.99$ 即可。而学习率 $alpha$ 在本章节中设置的比较大,为 $0.1$,实际更复杂的环境和算法中学习率是小于 $0.01$,因为太大很容易发生过拟和的问题,只是本节的环境和算法都比较简单,为了收敛得更快点所以设置得比较大。此外由于我们探索策略中的 $\varepsilon$ 是会随着采样步数衰减的,在实践过程中既不能让它衰减得太快也不能让它衰减得太慢,因此需要合理设置如下参数: ```python self.epsilon_start = 0.95 # e-greedy策略中epsilon的初始值 @@ -218,14 +309,14 @@ self.epsilon_decay = 200 # e-greedy策略中epsilon的衰减率 ### 开始训练 -准备工作做好之后,就可以开始训练了,得到的训练曲线如下: +准备工作做好之后,就可以开始训练了,得到的训练曲线如图 $\text{5.6}$ 所示,曲线横坐标表示回合数($\text{episode}$),纵坐标表示每回合获得的总奖励,可以看出曲线其实从大约 $\text{50}$ 个回合的时候就开始收敛了,也就是我们的智能体学到了一个最优策略。
-
图 5.5 CliffWalking-v0 环境 Q-learning 算法训练曲线
+
图 $\text{5.6}$ $\text{CliffWalking-v0}$ 环境 $\text{Q-learning}$ 算法训练曲线
-如图 5.5 所示,曲线横坐标表示回合数(episode),纵坐标表示每回合获得的总奖励,可以看出曲线其实从大约 $50$ 个回合的时候就开始收敛了,也就是我们的智能体学到了一个最优策略。在训练过程中我们打印出每回合的奖励,总共训练了 $300$ 个回合,结果如下: +在训练过程中我们打印出每回合的奖励,总共训练了 $300$ 个回合,结果如下: ```bash ... @@ -240,12 +331,13 @@ self.epsilon_decay = 200 # e-greedy策略中epsilon的衰减率 为了确保我们训练出来的策略是有效的,可以拿训好的策略去测试,测试的过程跟训练的过程差别不大,其一是智能体在测试的时候直接用模型预测的动作输出就行,即在训练中是采样动作(带探索),测试中就是预测动作,其二是训练过程中不需要更新策略,因为已经收敛了。 +如图 $\text{5.7}$ 所示,我们测试了 $10$ 个回合,发现每回合获得的奖励都是 $-13$ 左右,说明我们学到的策略是比较稳定的。 +
-
图 5.6 CliffWalking-v0 环境 Q-learning 算法测试曲线
+
图 $\text{5.7}$ $\text{CliffWalking-v0}$ 环境 $\text{Q-learning}$ 算法测试曲线
-如图 5.6 所示,我们测试了 $10$ 个回合,发现每回合获得的奖励都是 $-13$ 左右,说明我们学到的策略是比较稳定的。 ### 结果分析 @@ -260,7 +352,7 @@ self.epsilon_decay = 200 # e-greedy策略中epsilon的衰减率 ### 消融实验 -为了进一步探究 $\varepsilon$ 是随着采样步数衰减更好些,还是恒定不变更好,我们做了一个消融(Ablation)实验,即将 $\varepsilon$ 设置为恒定的 $0.1$,如下: +为了进一步探究 $\varepsilon$ 是随着采样步数衰减更好些,还是恒定不变更好,我们做了一个消融( $\text{Ablation}$ )实验,即将 $\varepsilon$ 设置为恒定的 $0.1$,如下: ```python # 将初始值和最终值设置为一样,这样 epsilon 就不会衰减 @@ -269,57 +361,39 @@ self.epsilon_end = 0.1 # e-greedy策略中epsilon的最终值 self.epsilon_decay = 200 # e-greedy策略中epsilon的衰减率 ``` -然后重新训练和测试,得到的训练曲线如图 5.6 所示: +然后重新训练和测试,得到的训练曲线如图 $\text{5.8}$ 所示:
-
图 5.6 Q-learning 算法消融实验训练曲线
+
图 $\text{5.8}$ $\text{Q-learning}$ 算法消融实验训练曲线
+ -测试曲线如图 5.7 所示 +测试曲线如图 $\text{5.9}$ 所示:
-
图 5.7 Q-learning 算法消融实验训练曲线
+
图 $\text{5.9}$ $\text{Q-learning}$ 算法消融实验训练曲线
不难发现,虽然最后也能收敛,但是相对来说没有那么稳定,在更复杂的环境中 $\varepsilon$ 随着采样步数衰减的好处会体现得更加明显。 - -## Sarsa 算法 - -Sarsa 算法虽然在刚提出的时候被认为是 Q-learning 算法的改进,但在今天看来是非常类似,但是模式却不同的两类算法,Q-learning 算法被认为是 Off-Policy 算法,而 Sarsa 算法相对地则是 On-policy 的,具体我们后面会展开说明。我们先来看 Sarsa 算法,我们讲到 Sarsa 算法跟 Q-learning 算法是非常类似的,这是因为两者之间在形式上只有 $Q$ 值更新公式是不同的,如下: - -$$ -Q(s_t,a_t) \leftarrow Q(s_t,a_t)+\alpha[r_t+\gamma Q(s_{t+1},a_{t+1})-Q(s_t,a_t)] -$$ - -也就是说,Sarsa 算法是直接用下一个状态和动作对应的 $Q$ 值来作为估计值的,而 Q-learning 算法则是用下一个状态对应的最大 $Q$ 值。然后我们就可以贴出伪代码了, - -
- -
-
图 5.8 Sarsa 算法伪代码
- ## 实战:Sarsa 算法 -由于 Sarsa 算法与 Q-learning 算法在实现上区别很小,感兴趣的读者可以直接阅读 `joyrl-offline` 上的代码。 +由于 $\text{Sarsa}$ 算法与 $\text{Q-learning}$ 算法在实现上区别很小,感兴趣的读者可以直接阅读 $\text{JoyRL}$ 上的代码。 -在相同环境和参数设置下,得到的实验结果如下: +在相同环境和参数设置下,得到的实验结果如图 $\text{5.10}$ 所示:
-
图 5.8 Q-learning 算法消融实验训练曲线
+
图 $\text{5.10}$ $\text{Sarsa}$ 算法训练曲线
-测试曲线如图 5.9 所示: +测试曲线如图 $\text{5.11}$ 所示:
-
图 5.9 Q-learning 算法消融实验训练曲线
+
图 $\text{5.11}$ $\text{Sarsa}$ 算法测试曲线
-我们发现相比于 Q-learning 算法的 $300$ 回合收敛,Sarsa 算法需要额外的 $100$ 个回合收敛,但是收敛之后会更稳定,没有一些波动过大的值,这就是我们接下来要讲的同策略(on-policy)与异策略(off-policy)的内容。 - -## 同策略与异策略 +我们发现相比于 $\text{Q-learning}$ 算法的 $300$ 回合收敛,$\text{Sarsa}$ 算法需要额外的 $100$ 个回合收敛,但是收敛之后会更稳定,没有一些波动过大的值,这就是我们接下来要讲的同策略( $\text{on-policy}$ )与异策略( $\text{off-policy}$ )的内容。 -尽管 Q-learning 算法和 Sarsa 算法仅在一行更新公式上有所区别,但这两种算法代表的是截然不同的两类算法。我们注意到,Sarsa 算法在训练的过程中当前策略来生成数据样本,并在其基础上进行更新。换句话说,策略评估和策略改进过程是基于相同的策略完成的,这就是**同策略算法**。相应地,像 Q-learning 算法这样从其他策略中获取样本然后利用它们来更新目标策略,我们称作**异策略算法**。也就是说,异策略算法基本上是从经验池或者历史数据中进行学习的。这两类算法有着不同的优缺点,同策略相对来说更加稳定,但是效率较低,如同我们实战中展示的那样。而异策略通常来说更加高效,但是需要让获取样本的策略和更新的策略具备一定的分布匹配条件,以避免偏差。 \ No newline at end of file diff --git a/docs/ch7/main.md b/docs/ch7/main.md index 27cbdf6..9a36807 100644 --- a/docs/ch7/main.md +++ b/docs/ch7/main.md @@ -1,10 +1,8 @@ # DQN 算法 -本章开始进入深度强化学习的部分,我们首先从 DQN 算法开始讲起。DQN 算法,英文全称 Deep Q-Network, 顾名思义就是基于深度网络模型的 Q-learning 算法,主要由 DeepMind 公司于2013年和2015年分别提出的两篇论文来实现。除了用深度网络代替 $Q$ 之外,DQN 算法还引入了两个技巧,即经验回放和目标网络,我们将逐一介绍。 +本章开始进入深度强化学习的部分,我们首先从 $\text{DQN}$ 算法开始讲起。$\text{DQN}$ 算法,英文全称 $\text{Deep Q-Network}$ , 顾名思义就是基于深度网络模型的 $\text{Q-learning}$ 算法,主要由 $\text{DeepMind}$ 公司于 $\text{2013}$ 年和 $\text{2015}$ 年分别提出的两篇论文来实现。除了用深度网络代替 $Q$ 之外,$\text{DQN}$ 算法还引入了两个技巧,即经验回放和目标网络,我们将逐一介绍。 - - -DQN 算法相对于 Q-learning 算法来说更新方法本质上是一样的,而 DQN 算法最重要的贡献之一就是本章节开头讲的,用神经网络替换表格的形式来近似动作价值函数$Q(\boldsymbol{s},\boldsymbol{a})$。 +$\text{DQN}$ 算法相对于 $\text{Q-learning}$ 算法来说更新方法本质上是一样的,而 $\text{DQN}$ 算法最重要的贡献之一就是本章节开头讲的,用神经网络替换表格的形式来近似动作价值函数$Q(\boldsymbol{s},\boldsymbol{a})$。 > ① 《Playing Atari with Deep Reinforcement Learning》 @@ -12,23 +10,23 @@ DQN 算法相对于 Q-learning 算法来说更新方法本质上是一样的, ## 深度网络 -在 Q-learning 算法中,我们使用 $Q$ 表的形式来实现动作价值函数 $Q(\boldsymbol{s},\boldsymbol{a})$。但是用 $Q$ 表只适用于状态和动作空间都是离散的,并且不利于处理高维的情况,因为在高维状态空间下每次更新维护 $Q$ 表的计算成本会高得多。因此,在 DQN 算法中,使用深度神经网络的形式来更新 $Q$ 值,这样做的好处就是能够处理高维的状态空间,并且也能处理连续的状态空间。 +在 $\text{Q-learning}$ 算法中,我们使用 $Q$ 表的形式来实现动作价值函数 $Q(\boldsymbol{s},\boldsymbol{a})$。但是用 $Q$ 表只适用于状态和动作空间都是离散的,并且不利于处理高维的情况,因为在高维状态空间下每次更新维护 $Q$ 表的计算成本会高得多。因此,在 $\text{DQN}$ 算法中,使用深度神经网络的形式来更新 $Q$ 值,这样做的好处就是能够处理高维的状态空间,并且也能处理连续的状态空间。 + +如图 $\text{7.1}$ 所示,在 $\text{DQN}$ 的网络模型中,我们将当前状态 $s_t$ 作为输入,并输出动作空间中所有动作(假设这里只有两个动作,即1和2)对应的 $Q$ 值,我们记做 $Q(s_t,\boldsymbol{a})$ 。对于其他状态,该网络模型同样可以输出所有动作对应的价值,这样一来神经网络近似的动作价值函数可以表示为 $Q_{\theta}(\boldsymbol{s},\boldsymbol{a})$ 。其中 $\theta$ 就是神经网络模型的参数,可以结合梯度下降的方法求解。
-
图 7.1 DQN 网络结构
- - -如图 7.1 所示,在 DQN 的网络模型中,我们将当前状态 $s_t$ 作为输入,并输出动作空间中所有动作(假设这里只有两个动作,即1和2)对应的 $Q$ 值,我们记做 $Q(s_t,\boldsymbol{a})$ 。对于其他状态,该网络模型同样可以输出所有动作对应的价值,这样一来神经网络近似的动作价值函数可以表示为 $Q_{\theta}(\boldsymbol{s},\boldsymbol{a})$ 。其中 $\theta$ 就是神经网络模型的参数,可以结合梯度下降的方法求解。 +
图 $\text{7.1}$ $\text{DQN}$ 网络结构
-具体该怎么结合梯度下降来更新 $Q$ 函数的参数呢?我们首先回顾一下 Q-learning 算法的更新公式如下: +具体该怎么结合梯度下降来更新 $Q$ 函数的参数呢?我们首先回顾一下 $\text{Q-learning}$ 算法的更新公式如式 $\text(7.1)$ 所示: $$ +\tag{7.1} Q(s_t,a_t) \leftarrow Q(s_t,a_t)+\alpha[r_t+\gamma\max _{a}Q^{\prime}(s_{t+1},a)-Q(s_t,a_t)] $$ -我们注意到公式右边两项 $r_t+\gamma\max _{a}Q^{\prime}(s_{t+1},a)$ 和 $Q(s_t,a_t)$ 分别表示期望的 $Q$ 值和实际的 $Q$ 值,其中预测的 $Q$ 值是用目标网络中下一个状态对应$Q$值的最大值来近似的。换句话说,在更新$Q$值并达到收敛的过程中,期望的 $Q$ 值也应该接近实际的 $Q$ 值,即我们希望最小化 $r_t+\gamma\max _{a}Q(s_{t+1},a)$和$Q(s_t,a_t)$之间的损失,其中 $\alpha$ 是学习率,尽管优化参数的公式跟深度学习中梯度下降法优化参数的公式有一些区别(比如增加了$\gamma$和$r_t$等参数)。从这个角度上来看,强化学习跟深度学习的训练方式其实是一样的,不同的地方在于强化学习用于训练的样本(包括状态、动作和奖励等等)是与环境实时交互得到的,而深度学习则是事先准备好的训练集。当然训练方式类似并不代表强化学习和深度学习之间的区别就很小,本质上来说强化学习和深度学习解决的问题是完全不同的,前者用于解决序列决策问题,后者用于解决静态问题例如回归、分类、识别等等。在 Q-learning 算法中,我们是直接优化 $Q$ 值的,而在 DQN 中使用神经网络来近似 $Q$ 函数,我们则需要优化网络模型对应的参数$\theta$,如下: +我们注意到公式右边两项 $r_t+\gamma\max _{a}Q^{\prime}(s_{t+1},a)$ 和 $Q(s_t,a_t)$ 分别表示期望的 $Q$ 值和实际的 $Q$ 值,其中预测的 $Q$ 值是用目标网络中下一个状态对应$Q$值的最大值来近似的。换句话说,在更新 $Q$ 值并达到收敛的过程中,期望的 $Q$ 值也应该接近实际的 $Q$ 值,即我们希望最小化 $r_t+\gamma\max _{a}Q(s_{t+1},a)$ 和 $Q(s_t,a_t)$ 之间的损失,其中 $\alpha$ 是学习率,尽管优化参数的公式跟深度学习中梯度下降法优化参数的公式有一些区别(比如增加了 $\gamma$ 和 $r_t$ 等参数)。从这个角度上来看,强化学习跟深度学习的训练方式其实是一样的,不同的地方在于强化学习用于训练的样本(包括状态、动作和奖励等等)是与环境实时交互得到的,而深度学习则是事先准备好的训练集。当然训练方式类似并不代表强化学习和深度学习之间的区别就很小,本质上来说强化学习和深度学习解决的问题是完全不同的,前者用于解决序列决策问题,后者用于解决静态问题例如回归、分类、识别等等。在 $\text{Q-learning}$ 算法中,我们是直接优化 $Q$ 值的,而在 DQN 中使用神经网络来近似 $Q$ 函数,我们则需要优化网络模型对应的参数 $\theta$ ,如下: $$ \begin{split} @@ -38,26 +36,34 @@ $$ \end{split} $$ -这里 DQN 算法也是基于 TD 更新的,因此依然需要判断终止状态,在 Q-learning 算法中也有同样的操作。 +这里 $\text{DQN}$ 算法也是基于 $\text{TD}$ 更新的,因此依然需要判断终止状态,在 $\text{Q-learning}$ 算法中也有同样的操作。 ## 经验回放 -强化学习是与环境实时交互得到样本然后进行训练的,在 Q-learning 算法中我们是每次交互一个样本,通常包含当前状态($state$)、当前动作($action$)、下一个状态($next\_state$)、是否为终止状态($done$),这样一个样本我们一般称之为一个状态转移(transition)。但是每次只交互一个样本并即时更新的方式在 DQN 算法中会产生一些问题。首先,对于神经网络来说,每次只喂入一个样本然后反向传播并更新参数是不稳定的。其次,连续交互产生的样本之间关联性过强,会导致深度网络更新的过程,容易陷入局部最优解。这跟深度学习中为什么采用随机梯度下降而不是单纯的顺序梯度下降的道理是类似的,只是在强化学习中问题会更为明显因为强化学习前后两个样本的关联性往往比监督学习的样本更紧密。 +强化学习是与环境实时交互得到样本然后进行训练的,在 $\text{Q-learning}$ 算法中我们是每次交互一个样本,通常包含当前状态( `state` )、当前动作( `action` )、下一个状态( `next_state` )、是否为终止状态( `done` ),这样一个样本我们一般称之为一个状态转移( $\text{transition}$ )。但是每次只交互一个样本并即时更新的方式在 $\text{DQN}$ 算法中会产生一些问题。首先,对于神经网络来说,每次只喂入一个样本然后反向传播并更新参数是不稳定的。其次,连续交互产生的样本之间关联性过强,会导致深度网络更新的过程,容易陷入局部最优解。这跟深度学习中为什么采用随机梯度下降而不是单纯的顺序梯度下降的道理是类似的,只是在强化学习中问题会更为明显因为强化学习前后两个样本的关联性往往比监督学习的样本更紧密。 -回顾一下在深度学习基础的章节中我们讲到梯度下降的方式,首先从样本选择方式来看分成单纯的梯度下降和随机梯度下降,随机梯度下降在样本选择过程中使用随机抽样,即每次从总样本中随机选择一些子样本处理,而不是按照固定的顺序逐个遍历总的样本,这样做的好处就是可以避免模型陷入局部最优解。在随机梯度下降的基础上,从每次抽取的样本数来看可以分为批梯度下降方法(batch gradient descent)、(普通的)随机梯度下降(Stochastic Gradient Descent)和小批量梯度下降(mini-batch gradient descent)。普通的随机梯度下降每一次迭代只使用一个样本来更新模型参数,尽管收敛速度快,但由于实现随机性可能会存在收敛到局部最优解的风险。批量梯度下降算法每一次迭代使用所有训练数据来更新模型参数,它的收敛速度虽然较慢,但从凸优化角度(感兴趣的读者也可以学习凸优化这门课)中可以保证收敛到全局最优解。小批量梯度下降算法每次迭代使用一定数量的样本来更新模型参数,介于批量梯度下降和随机梯度下降之间,可以在保证收敛性的同时提高计算效率。再看我们前面说到的 Q-learning 算法更新方式在深度网络下遇到的两个问题,每次只连续地喂入一个样本相当于是普通的顺序梯度下降的方式,这种方式其实是最糟糕的梯度下降方式,因为既不是随机梯度下降,也不是批梯度下降,因此我们希望在 DQN 算法中也能做到小批量梯度下降,这样就能保证收敛性。 +回顾一下在深度学习基础的章节中我们讲到梯度下降的方式,首先从样本选择方式来看分成单纯的梯度下降和随机梯度下降,随机梯度下降在样本选择过程中使用随机抽样,即每次从总样本中随机选择一些子样本处理,而不是按照固定的顺序逐个遍历总的样本,这样做的好处就是可以避免模型陷入局部最优解。在随机梯度下降的基础上,从每次抽取的样本数来看可以分为批梯度下降方法( $\text{Batch Gradient Descent}$ )、(普通的)随机梯度下降( $\text{Stochastic Gradient Descent}$ )和小批量梯度下降( $\text{Mini-Batch Gradient Descent}$ )。 -如何实现类似的小批量梯度下降呢?DeepMind 公司 在论文中提出了一个经验回放的概念(replay buffer),这个经验回放的功能主要包括几个方面。首先是能够缓存一定量的状态转移即样本,此时 DQN 算法并不急着更新并累积一定的初始样本。然后是每次更新的时候随机从经验回放中取出一个小批量的样本并更新策略,注意这里的随机和小批量以便保证我们存储动作价值函数的网络模型是小批量随机梯度下降的。最后与深度学习不同的是,我们要保证经验回放是具有一定的容量限制的。本质上是因为在深度学习中我们拿到的样本都是事先准备好的,即都是很好的样本,但是在强化学习中样本是由智能体生成的,在训练初期智能体生成的样本虽然能够帮助它朝着更好的方向收敛,但是在训练后期这些前期产生的样本相对来说质量就不是很好了,此时把这些样本喂入智能体的深度网络中更新反而影响其稳定。这就好比我们在小学时积累到的经验,会随着我们逐渐长大之后很有可能就变得不是很适用了,所以经验回放的容量不能太小,太小了会导致收集到的样本具有一定的局限性,也不能太大,太大了会失去经验本身的意义。从这一个细小的点上相信读者们也能体会到深度学习和强化学习的区别了,所谓管中窥豹,可见一斑。 +普通的随机梯度下降每一次迭代只使用一个样本来更新模型参数,尽管收敛速度快,但由于实现随机性可能会存在收敛到局部最优解的风险。批量梯度下降算法每一次迭代使用所有训练数据来更新模型参数,它的收敛速度虽然较慢,但从凸优化角度(感兴趣的读者也可以学习凸优化这门课)中可以保证收敛到全局最优解。小批量梯度下降算法每次迭代使用一定数量的样本来更新模型参数,介于批量梯度下降和随机梯度下降之间,可以在保证收敛性的同时提高计算效率。再回头看看我们前面说到的 Q-learning 算法更新方式在深度网络下遇到的两个问题,每次只连续地喂入一个样本相当于是普通的顺序梯度下降的方式,这种方式其实是最糟糕的梯度下降方式,因为既不是随机梯度下降,也不是批梯度下降,因此我们希望在 DQN 算法中也能做到小批量梯度下降,这样就能保证收敛性。 + +如何实现类似的小批量梯度下降呢?$\text{DeepMind}$ 公司 在论文中提出了一个经验回放的概念( $\text{replay buffer}$ ),这个经验回放的功能主要包括几个方面。首先是能够缓存一定量的状态转移即样本,此时 $\text{DQN}$ 算法并不急着更新并累积一定的初始样本。然后是每次更新的时候随机从经验回放中取出一个小批量的样本并更新策略,注意这里的随机和小批量以便保证我们存储动作价值函数的网络模型是小批量随机梯度下降的。 + +最后与深度学习不同的是,我们要保证经验回放是具有一定的容量限制的。本质上是因为在深度学习中我们拿到的样本都是事先准备好的,即都是很好的样本,但是在强化学习中样本是由智能体生成的,在训练初期智能体生成的样本虽然能够帮助它朝着更好的方向收敛,但是在训练后期这些前期产生的样本相对来说质量就不是很好了,此时把这些样本喂入智能体的深度网络中更新反而影响其稳定。这就好比我们在小学时积累到的经验,会随着我们逐渐长大之后很有可能就变得不是很适用了,所以经验回放的容量不能太小,太小了会导致收集到的样本具有一定的局限性,也不能太大,太大了会失去经验本身的意义。从这一个细小的点上相信读者们也能体会到深度学习和强化学习的区别了,所谓管中窥豹,可见一斑。 ## 目标网络 -在 DQN 算法中还有一个重要的技巧,这个技巧就跟深度学习关系不大了,而是更“强化”的一个技巧。即使用了一个每隔若干步才更新的目标网络。与之相对的,会有一个每步更新的网络,即每次从经验回放中采样到样本就更新网络参数,在本书中一般称之为策略网络。策略网络和目标网络结构都是相同的,都用于近似 Q 值,在实践中每隔若干步才把每步更新的策略网络参数复制给目标网络,这样做的好处是保证训练的稳定,避免 Q值 的估计发散。举一个典型的例子,这里的目标网络好比明朝的皇帝,而策略网络相当于皇帝手下的太监,每次皇帝在做一些行政决策时往往不急着下定论,会让太监们去收集一圈情报,然后集思广益再做决策。这样做的好处是显而易见的,比如皇帝要处决一个可能受冤的犯人时,如果一个太监收集到一个情报说这个犯人就是真凶的时候,如果皇帝是一个急性子可能就当初处决了,但如果这时候另外一个太监收集了一个更有力的证据证明刚才那个太监收集到的情报不可靠并能够证明该犯人无罪时,那么此时皇帝就已经犯下了一个无法挽回的过错。换句话说,如果当前有个小批量样本导致模型对 Q 值进行了较差的过估计,如果接下来从经验回放中提取到的样本正好连续几个都这样的,很有可能导致 Q 值的发散(它的青春小鸟一去不回来了)。再打个比方,我们玩 RPG 或者闯关类游戏,有些人为了破纪录经常存档(Save)和回档(Load),简称“SL”大法。只要我出了错,我不满意我就加载之前的存档,假设不允许加载呢,就像 DQN 算法一样训练过程中会退不了,这时候是不是搞两个档,一个档每帧都存一下,另外一个档打了不错的结果再存,也就是若干个间隔再存一下,到最后用间隔若干步数再存的档一般都比每帧都存的档好些呢。当然我们也可以再搞更多个档,也就是DQN增加多个目标网络,但是对于 DQN 算法来说没有多大必要,因为多几个网络效果不见得会好很多。 +在 $\text{DQN}$ 算法中还有一个重要的技巧,这个技巧就跟深度学习关系不大了,而是更“强化”的一个技巧。即使用了一个每隔若干步才更新的目标网络。与之相对的,会有一个每步更新的网络,即每次从经验回放中采样到样本就更新网络参数,在本书中一般称之为策略网络。策略网络和目标网络结构都是相同的,都用于近似 $Q$ 值,在实践中每隔若干步才把每步更新的策略网络参数复制给目标网络,这样做的好处是保证训练的稳定,避免 $Q$ 值 的估计发散。 + +举一个典型的例子,这里的目标网络好比明朝的皇帝,而策略网络相当于皇帝手下的太监,每次皇帝在做一些行政决策时往往不急着下定论,会让太监们去收集一圈情报,然后集思广益再做决策。这样做的好处是显而易见的,比如皇帝要处决一个可能受冤的犯人时,如果一个太监收集到一个情报说这个犯人就是真凶的时候,如果皇帝是一个急性子可能就当初处决了,但如果这时候另外一个太监收集了一个更有力的证据证明刚才那个太监收集到的情报不可靠并能够证明该犯人无罪时,那么此时皇帝就已经犯下了一个无法挽回的过错。换句话说,如果当前有个小批量样本导致模型对 $Q$ 值进行了较差的过估计,如果接下来从经验回放中提取到的样本正好连续几个都这样的,很有可能导致 $Q$ 值的发散(它的青春小鸟一去不回来了)。 + +再打个比方,我们玩 $\text{RPG}$ 或者闯关类游戏,有些人为了破纪录经常存档( $\text{Save}$ )和回档( $\text{Load}$ ),简称 “$\text{SL}$” 大法。只要我出了错,我不满意我就加载之前的存档,假设不允许加载呢,就像 $\text{DQN}$ 算法一样训练过程中会退不了,这时候是不是搞两个档,一个档每帧都存一下,另外一个档打了不错的结果再存,也就是若干个间隔再存一下,到最后用间隔若干步数再存的档一般都比每帧都存的档好些呢。当然我们也可以再搞更多个档,也就是 $\text{DQN}$ 增加多个目标网络,但是对于 $\text{DQN}$ 算法来说没有多大必要,因为多几个网络效果不见得会好很多。 -到这里我们基本讲完了 DQN 算法的内容,可以直接贴出伪代码准备进入实战了,如下: +到这里我们基本讲完了 $\text{DQN}$ 算法的内容,可以直接贴出伪代码准备进入实战了,如图 $\text{7.2}$ 所示:
-
图 7.2 DQN 算法伪代码
+
图 $\text{7.2}$ $\text{DQN}$ 算法伪代码
## 实战:DQN 算法 @@ -65,7 +71,7 @@ $$ ### 定义算法 -由于 DQN 智能体包含的元素比较多,包括神经网络,经验回放等,我们接下来将逐一实现。首先需要定义一个深度网络来表示 $Q$ 函数,目前 JoyRL 算法都是基于 Torch 框架实现的,所以需要读者们具有一定的相关基础。如下,我们定义一个全连接网络即可,输入维度就是状态数,输出的维度就是动作数,中间的隐藏层采用最常用的 ReLU 激活函数。 +由于 $\text{DQN}$ 智能体包含的元素比较多,包括神经网络,经验回放等,我们接下来将逐一实现。首先需要定义一个深度网络来表示 $Q$ 函数,目前 $\text{JoyRL}$ 算法都是基于 $\text{Torch}$ 框架实现的,所以需要读者们具有一定的相关基础。如下,我们定义一个全连接网络即可,输入维度就是状态数,输出的维度就是动作数,中间的隐藏层采用最常用的 $\text{ReLU}$ 激活函数。 ```python class MLP(nn.Module): @@ -116,9 +122,9 @@ class ReplayBuffer: return len(self.buffer) ``` -前面讲到经验回放的主要功能是,存入样本然后随机采样出一个批量的样本,分别对应这里的 push 和 sample 方法,并且需要保证一定的容量(即 capacity )。实现的手段有很多,也可以用 Python 队列的方式实现,这里只是一个参考。 +前面讲到经验回放的主要功能是,存入样本然后随机采样出一个批量的样本,分别对应这里的 `push` 和 `sample` 方法,并且需要保证一定的容量(即 `capacity` )。实现的手段有很多,也可以用 $\text{Python}$ 队列的方式实现,这里只是一个参考。 -然后我们定义智能体,跟 Q-learning 算法中类似,我们定义一个名为 Agent 的 Python 类,包含 sample action,predict action 和 update 等方法。 +然后我们定义智能体,跟 $\text{Q-learning}$ 算法中类似,我们定义一个名为 `Agent` 的 $\text{Python}$ 类,包含 `sample_action`,`predict_action` 和 `update` 等方法。 ```python class Agent: @@ -137,8 +143,7 @@ class Agent: def sample_action(self,state): self.sample_count += 1 # epsilon 随着采样步数衰减 - self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * \ - math.exp(-1. * self.sample_count / self.epsilon_decay) + self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * math.exp(-1. * self.sample_count / self.epsilon_decay) if random.random() > self.epsilon: with torch.no_grad(): # 不使用梯度 state = torch.tensor(np.array(state), device=self.device, dtype=torch.float32).unsqueeze(dim=0) @@ -156,9 +161,9 @@ class Agent: pass ``` -注意,这里所有的代码都是为了方便讲解用的演示代码,完整的代码读者可在 JoyRL 开源工具上参考。在这里我们定义了两个网络,策略网络和目标网络,在 Torch 中可以使用 `.to(device)` 来决定网络是否使用 CPU 还是 GPU 计算。 此外在初始化的时候我们需要让目标网络和策略网络的参数保持一致,可以使用 `load_state_dict` 方法,然后就是优化器和经验回放了。 在 DQN 算法中采样动作和预测动作跟 Q-learning 是一样的,其中 `q_values = self.policy_net(state)` 拿到的 $Q$ 值是给定状态下所有动作的值,根据这些值选择最大值对应的动作即可。 +注意,这里所有的代码都是为了方便讲解用的演示代码,完整的代码读者可在 $\text{JoyRL}$ 开源工具上参考。在这里我们定义了两个网络,策略网络和目标网络,在 $\text{Torch}$ 中可以使用 `.to(device)` 来决定网络是否使用 $\text{CPU}$ 还是 $\text{GPU}$ 计算。 此外在初始化的时候我们需要让目标网络和策略网络的参数保持一致,可以使用 `load_state_dict` 方法,然后就是优化器和经验回放了。 在 $\text{DQN}$ 算法中采样动作和预测动作跟 $\text{Q-learning}$ 是一样的,其中 `q_values = self.policy_net(state)` 拿到的 $Q$ 值是给定状态下所有动作的值,根据这些值选择最大值对应的动作即可。 -DQN 算法更新本质上跟 Q-learning 区别不大,但由于读者可能第一次接触深度学习的实现方式,这里单独拎出来分析 DQN 算法的更新方式,如下: +$\text{DQN}$ 算法更新本质上跟 $\text{Q-learning}$ 区别不大,但由于读者可能第一次接触深度学习的实现方式,这里单独拎出来分析 $\text{DQN}$ 算法的更新方式,如下: ```python def update(self, share_agent=None): @@ -195,20 +200,20 @@ def update(self, share_agent=None): self.target_net.load_state_dict(self.policy_net.state_dict()) ``` -首先由于我们是小批量随机梯度下降,所以当经验回放不满足批大小时选择不更新,这实际上是工程性问题。然后在更新时我们取出样本,并转换成 Torch 的张量,便于我们用 GPU 计算。接着计算 $Q$ 值的估计值和实际值,并得到损失函数。在得到损失函数并更新参数时,我们在代码上会有一个固定的写法,即梯度清零,反向传播和更新优化器的过程,跟在深度学习中的写法是一样的,最后我们需要定期更新一下目标网络,这里会有一个超参数 `target_update`, 需要读者根据经验调试。 +首先由于我们是小批量随机梯度下降,所以当经验回放不满足批大小时选择不更新,这实际上是工程性问题。然后在更新时我们取出样本,并转换成 $\text{Torch}$ 的张量,便于我们用 $\text{GPU}$ 计算。接着计算 $Q$ 值的估计值和实际值,并得到损失函数。在得到损失函数并更新参数时,我们在代码上会有一个固定的写法,即梯度清零,反向传播和更新优化器的过程,跟在深度学习中的写法是一样的,最后我们需要定期更新一下目标网络,这里会有一个超参数 `target_update`, 需要读者根据经验调试。 ### 定义环境 -由于我们在 Q-learning 算法中已经讲过怎么定义训练和测试过程了,所有强化学习算法的训练过程基本上都是通用的,因此我们在这里及之后的章节中不再赘述。但由于我们在 DQN 算法中使用了跟 Q-learning 算法 中不一样的环境,但都是 OpenAI Gym 平台的,所以我们简单介绍一下该环境。环境名称叫做 `Cart Pole` ,中文译为推车杆游戏。如图 7.3 所示,我们的目标是持续左右推动保持倒立的杆一直不倒。 +由于我们在 $\text{Q-learning}$ 算法中已经讲过怎么定义训练和测试过程了,所有强化学习算法的训练过程基本上都是通用的,因此我们在这里及之后的章节中不再赘述。但由于我们在 $\text{DQN}$ 算法中使用了跟 $\text{Q-learning}$ 算法 中不一样的环境,但都是 $\text{OpenAI Gym}$ 平台的,所以我们简单介绍一下该环境。环境名称叫做 $\text{Cart Pole}$ ,中文译为推车杆游戏。如图 $\text{7.3}$ 所示,我们的目标是持续左右推动保持倒立的杆一直不倒。
-
图 7.3 Cart Pole 游戏
+
图 $\text{7.3}$ $\text{Cart Pole}$ 游戏
>① 官网环境介绍:https://gymnasium.farama.org/environments/classic_control/cart_pole/ -环境的状态数是 $4$, 动作数是 $2$。有读者可能会奇怪,这不是比 Q-learning 算法中的 `CliffWalking-v0` 环境(状态数是 $48$, 动作数是 $2$)更简单吗,应该直接用 Q-learning 算法就能解决?实际上是不能的,因为 `Cart Pole` 的状态包括推车的位置(范围是 $-4.8$ 到 $4.8$ )、速度(范围是负无穷大到正无穷大)、杆的角度(范围是 $-24$ 度 到 $24$ 度)和角速度(范围是负无穷大到正无穷大),这几个状态都是连续的值,也就是前面所说的连续状态空间,因此用 Q-learning 算法是很难解出来的。环境的奖励设置是每个时步下能维持杆不到就给一个 $+1$ 的奖励,因此理论上在最优策略下这个环境是没有终止状态的,因为最优策略下可以一直保持杆不倒。回忆前面讲到基于 TD 的算法都必须要求环境有一个终止状态,所以在这里我们可以设置一个环境的最大步数,比如我们认为如果能在两百个时步以内坚持杆不到就近似说明学到了一个不错的策略。 +环境的状态数是 $4$, 动作数是 $2$。有读者可能会奇怪,这不是比 $\text{Q-learning}$ 算法中的 `CliffWalking-v0` 环境(状态数是 $48$, 动作数是 $2$)更简单吗,应该直接用 $\text{Q-learning}$ 算法就能解决?实际上是不能的,因为 `Cart Pole` 的状态包括推车的位置(范围是 $-4.8$ 到 $4.8$ )、速度(范围是负无穷大到正无穷大)、杆的角度(范围是 $-24$ 度 到 $24$ 度)和角速度(范围是负无穷大到正无穷大),这几个状态都是连续的值,也就是前面所说的连续状态空间,因此用 $\text{Q-learning}$ 算法是很难解出来的。环境的奖励设置是每个时步下能维持杆不到就给一个 $+1$ 的奖励,因此理论上在最优策略下这个环境是没有终止状态的,因为最优策略下可以一直保持杆不倒。回忆前面讲到基于 $\text{TD}$ 的算法都必须要求环境有一个终止状态,所以在这里我们可以设置一个环境的最大步数,比如我们认为如果能在两百个时步以内坚持杆不到就近似说明学到了一个不错的策略。 ### 设置参数 @@ -225,18 +230,18 @@ self.batch_size = 64 # 批大小 self.target_update = 4 # 目标网络更新频率 ``` -与 Q-learning 算法相比,除了 $varepsilon$, 折扣因子以及学习率之外多了三个超参数,即经验回放的容量、批大小和目标网络更新频率。注意这里学习率在更复杂的环境中一般会设置得比较小,经验回放的容量是一个比较经验性的参数,根据实际情况适当调大即可,不需要额外花太多时间调。批大小也比较固定,一般都在 $64$,$128$,$256$,$512$ 中间取值,目标网络更新频率会影响智能体学得快慢,但一般不会导致学不出来。总之,DQN 算法相对来说是深度强化学习的一个稳定且基础的算法,只要适当调整学习率都能让智能体学出一定的策略。 +与 $\text{Q-learning}$ 算法相比,除了 $varepsilon$, 折扣因子以及学习率之外多了三个超参数,即经验回放的容量、批大小和目标网络更新频率。注意这里学习率在更复杂的环境中一般会设置得比较小,经验回放的容量是一个比较经验性的参数,根据实际情况适当调大即可,不需要额外花太多时间调。批大小也比较固定,一般都在 $64$,$128$,$256$,$512$ 中间取值,目标网络更新频率会影响智能体学得快慢,但一般不会导致学不出来。总之,DQN 算法相对来说是深度强化学习的一个稳定且基础的算法,只要适当调整学习率都能让智能体学出一定的策略。 -最后展示一下我们的训练曲线和测试曲线,分别如图 7.4 和 7.5 所示。 +最后展示一下我们的训练曲线和测试曲线,分别如图 $\text{7.4}$ 和 $\text{7.5}$ 所示。
-
图 7.4 CliffWalking-v0 环境 DQN 算法训练曲线
+
图 $\text{7.4}$ $\text{CartPole-v1}$ 环境 $\text{DQN}$ 算法训练曲线
-
图 7.5 CliffWalking-v0 环境 DQN 算法测试曲线
+
图 $\text{7.5}$ $\text{CartPole-v1}$ 环境 $\text{DQN}$ 算法测试曲线
其中我们该环境每回合的最大步数是 $200$,对应的最大奖励也为 $200$,从图中可以看出,智能体确实学到了一个最优的策略。 \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 56ddb77..18ee518 100644 --- a/docs/index.html +++ b/docs/index.html @@ -13,7 +13,7 @@