Skip to content

Commit

Permalink
更新策略梯度
Browse files Browse the repository at this point in the history
  • Loading branch information
johnjim0816 committed Jul 29, 2023
1 parent f26424b commit 3f68610
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 1 deletion.
2 changes: 2 additions & 0 deletions docs/ch10/main.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Actor-Critic 算法



## Q Actor-Critic 算法

## A2C 算法
Expand Down
2 changes: 2 additions & 0 deletions docs/ch9/0-231-0-516-0-253.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# 0.231 0.516 0.253

171 changes: 170 additions & 1 deletion docs/ch9/main.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,173 @@ $$
\end{aligned}
$$

这里简单解释一下上述公式中的步骤,首先第一行就是目标函数的表达形式,到第二行就是全期望展开式,到第三行就是利用了积分的梯度性质,即梯度可以放到积分号的里面也就是被积函数中,第四行到最后就是对数微分技巧了。回过头来看下,我们为什么要用到对数微分技巧呢?这其实是一个常见的数学技巧:当我们看到公式中出现累乘的项时,我们通常都会取对数简化,因为根据对数公式的性质可以将累乘的项转换成累加的项,这样一来问题会更加便于处理。
这里简单解释一下上述公式中的步骤,首先第一行就是目标函数的表达形式,到第二行就是全期望展开式,到第三行就是利用了积分的梯度性质,即梯度可以放到积分号的里面也就是被积函数中,第四行到最后就是对数微分技巧了。回过头来看下,我们为什么要用到对数微分技巧呢?这其实是一个常见的数学技巧:当我们看到公式中出现累乘的项时,我们通常都会取对数简化,因为根据对数公式的性质可以将累乘的项转换成累加的项,这样一来问题会更加便于处理。

## REINFORCE算法

虽然在前面一节中我们推导并简化了策略梯度公式,但细心的读者可能会发现,实际操作起来是很困难的。我们知道轨迹是由状态和动作组合而成的序列,实际上一方面环境的初始状态是随机的,另一方面智能体每次采取的动作是随机的,从而导致每条轨迹的长度都可能不一样,这样组合起来的轨迹几乎是无限多条的,这样一来求解目标函数的梯度就变得非常困难了。那么这个时候我们就需要利用蒙特卡洛的方法来近似求解了,即我们可以不必采样所有的轨迹,而是采样一部分且数量足够多的轨迹,然后利用这些轨迹的平均值来近似求解目标函数的梯度。这种方法就是蒙特卡洛策略梯度算法,也称作 REINFORCE 算法。其实这种方法在前面的章节中也已经使用过了,我们会发现蒙特卡洛方法虽然听起来很高级,但它实际上就是一种“省懒”的方法,即有时候理论上需要去找无限个数据来支撑我们去求解某个数据,但实际上我们可以只找有限个数据来近似求解。在生活中我们也经常用到这个方法,比如我们想要知道当代大学生都有哪些爱好,理论上我们需要去调查所有的大学生,但实际上我们只需要去抽查一部分大学生就可以了,只要抽查的大学生具有足够的代表性,比如覆盖的学历范围足够广泛等等,那么我们就可以认为这部分大学生的爱好代表了所有大学生的爱好了。

回到我们的问题,我们现在需要求解的是目标函数的梯度,REINFORCE 算法的做法是每次采样 $N$ 条轨迹,然后对这 $N$ 条轨迹的梯度求平均,即

$$
\nabla J_{\theta} \approx \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}} G_{t}^{n} \nabla \log \pi_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right)
$$

其中 $N$ 理论上越大越好,但实际上我们可能只采样几个回合的轨迹就能近似求解梯度了。此外,注意这里我们把奖励函数换成了带有折扣因子的回报 $G_{t}^{n}=\sum_{k=t}^{T_{n}} \gamma^{k-t} r_{k}^{n}$,其中 $T_n$ 表示第 $n$ 条轨迹的长度,$\gamma$ 表示折扣因子,$r_{k}^{n}$ 表示第 $n$ 条轨迹在第 $k$ 步的奖励。尽管回报计算起来麻烦,但可以结合前面章节讲到的贝尔曼公式将当前时刻和下一时刻的回报联系起来,从而在一定程度上简化计算,即:

$$
\begin{aligned}
G_{t} &=\sum_{k=t+1}^{T} \gamma^{k-t-1} r_{k} \\
&=r_{t+1}+\gamma G_{t+1}
\end{aligned}
$$



## 策略梯度推导进阶

到这里我们已经讲完了 REINFORCE 算法的理论部分,在后面小节中我们会展开实战部分,在此之前我们先看看一个进阶版的策略梯度公式推导。在前面的小节中我们展开的策略梯度推导过程本质上是沿用 REINFORCE 算法或者蒙特卡洛的思路推导的,这种推导思路的优点是简单易懂,但在推导过程中我们也发现了其中的缺点,即最后推导出来的公式实际上不可求,因为理论上有无限条轨迹,所以只能用近似的方法。其次,我们假定了目标是使得每回合的累积价值最大,因此用对应的总奖励 $R(\tau)$ 或回报 $G(\tau)$ 来求解或者说评估价值,但实际使用过程中我们会发现这种价值的评估方法并不是很稳定,因为每回合的累积奖励或回报会受到很多因素的影响,比如回合的长度、奖励的稀疏性等等,后面章节中我们还会发现用一种叫做优势 (Advantage)的量去评估价值会更加有效。因此,我们需要一个更泛化更通用的策略梯度公式,这也是为什么笔者称之为进阶版的原因。

### 平稳分布

在展开进阶版策略梯度推导之前,我们需要先铺垫一些概念,首先是马尔可夫链的平稳分布(Stationary Distribution)。平稳分布,顾名思义就是指在无外界干扰的情况下,系统长期运行之后其状态分布会趋于一个固定的分布,不再随时间变化。已经跑过一些强化学习实战的读者们也会发现,每次成功跑一个算法,奖励曲线都会收敛到一个相对稳定的值,只要环境本身不变,哪怕换一种算法,奖励曲线也会收敛到一个相对稳定的值,除非我们改动了环境的一些参数比如调奖励等,这就是平稳分布的概念。平稳分布本质上也是熵增原理的一种体现,比如初中化学中我们知道,当我们把一块金属钠放到水中,会发生化学反应变成氢氧化钠,在反应的过程中是比较剧烈的,但在一段时间之后它总能稳定生成氢氧化钠,即在我们预测范围之内,而不会某几次实验之后突然生成氯化钠之类的物质。回到马尔科夫链本身,在处于平稳分布下,我们会发现一些规律,一个是任意两个状态之间都是互相连通的,即任意两个状态之间都可以通过一定的步骤到达,这个性质称为**连通性**(Connectedness)。例如在学校里通常会有几种状态:上课、放学、吃饭、睡觉等,这些状态我们都是可以随意切换的,比如上课之后可以放学,放学之后可以吃饭,吃饭之后可以睡觉,睡觉之后可以上课等,这就是连通性的体现。有读者可能会说,我睡觉的时候能一直睡,就是不想切换到其他状态,也就是说到了睡觉这个状态,切换到其他状态的概率就变成 0 了,这个时候连通性就不成立了,我们把这个状态也叫做吸收状态。其实这个问题是因为我们的状态定义不够细致,我们可以把睡觉这个状态细分成睡觉、睡觉中、睡醒等状态,这样一来就可以保证任意两个状态之间都是互相连通的了。可能读者又会想到马尔可夫过程的终止状态不也算是吸收状态吗?其实不是的,这里的终止状态其实在时序差分方法中有提到过,只是为了方便计算,我们把终止状态的价值函数定义为 0,但实际上终止状态也是可以切换到其他状态的,典型的例子就是在游戏中,当我们的智能体死亡之后,游戏并不会立即结束,而是会重新开始,这就是一个终止状态,但它并不是吸收状态,因为它又回到了初始状态。
另一个是任意状态在平稳分布下的概率都是一样的,即任意状态的概率都是相等的,这个性质称为**细致平稳**(Detailed Balance)。这两个性质在马尔科夫链中是等价的,即如果一个马尔科夫链满足连通性,那么它一定满足细致平稳性,反之亦然。这里我们不再展开证明,有兴趣的读者可以自行查阅相关资料。

为了加深读者对于平稳分布的具体理解,我们举一个经典的计算实例。这个例子是这样的,社会学家在他们的研究中通常会把人按照经济状况分成三类:上层,中层和下层,这三层就代表着三种状态,我们分别用 1,2,3 来表示。并且社会学家还发现决定一个人经济阶层的最重要因素就是其父母的收入阶层,即如果一个人的经济阶层为上层,那么他的孩子会有 0.5 的概率继续处于上层,也会有 0.4 的概率变成中层,更有 0.1 的概率降到下层,当然这些概率数值只是笔者拍脑袋想出来以便于后面的计算的,并没有一定的统计依据。这些概率其实就是我们所说的马尔可夫链中的转移概率,同样对于其他经济阶层的人他们的孩子也会有一定的概率变成上、中、下的任一经济阶层,如下图所示。


<div align=center>
<img width="500" src="../figs/ch9/markov_economy.png"/>
</div>
<div align=center>图 9.1 经济阶层转换示例</div>


这样我们就可以列出转移概率矩阵,如下:

$$
\label{eq:tran_prob_economy}
P=\left[\begin{array}{lll}
0.5 & 0.4 & 0.1 \\
0.2 & 0.6 & 0.2 \\
0.05 & 0.45 & 0.5
\end{array}\right]
$$

我们假设有这么一批数量足够的人,称之为第 1 代人,他们的经济阶层比例为$\pi_0=[0.15,0.62,0.23]$,那么根据上面的转移概率矩阵我们就可以求出第二代的阶层比例。怎么求呢?首先求出第二代上层的比例,我们知道第一代人中有 0.15 的比例是上层,这 0.15 比例的人中子代为上层的概率是 0.5, 而第一代人中 0.62 比例的中层会有 0.2 的概率流入上层,0.23 比例的下层中其子代也会有 0.05 的概率流入上层,那么最后第二代上层的比例就为 $0.15 \times 0.5 + 0.62 \times 0.2 + 0.23 \times 0.05 = 0.2105 \approx 0.210$,依次类推,第二代中层的比例为$0.15 \times 0.4 + 0.62 \times 0.6 + 0.23 \times 0.45 \approx 0.536$,第二代下层的比例为$0.254$,这样我们就能得出第二代的阶层比例为$\pi_1=[0.210,0.536,0.254]$。这里细心的读者会发现不需要这么麻烦的计算过程,只要学过线性代数利用矩阵向量相乘就能得到,即$\pi_1 = \pi_0 P = [0.210,0.536,0.254]$。同理,第二代人的比例也可以求出,即 $\pi_2 = \pi_1 P = \pi_0 P^2$,依次类推,第n代人的比例为$\pi_n = \pi_0 P^n$。既然这本书同时也是教大家如何代码实战的,这里我们 Python 代码来求出前 10 代人的比例,如下。

```python
import numpy as np
pi_0 = np.array([[0.15,0.62,0.23]])
P = np.array([[0.5,0.4,0.1],[0.2,0.6,0.2],[0.05,0.45,0.5]])
for i in range(1,10+1):
pi_0 = pi_0.dot(P)
print(f"{i}代人的比例为:")
print(np.around(pi_0,3))
```

我们可以很快获得计算的结果,如下。

```bash
第1代人的比例为:
[[0.211 0.536 0.254]]
第2代人的比例为:
[[0.225 0.52 0.255]]
第3代人的比例为:
[[0.229 0.517 0.254]]
第4代人的比例为:
[[0.231 0.516 0.253]]
第5代人的比例为:
[[0.231 0.516 0.253]]
第6代人的比例为:
[[0.231 0.516 0.253]]
第7代人的比例为:
[[0.232 0.516 0.253]]
第8代人的比例为:
[[0.232 0.516 0.253]]
第9代人的比例为:
[[0.232 0.516 0.253]]
第10代人的比例为:
[[0.232 0.516 0.253]]
```

从上面的结果中,我们发现从第5代开始经济阶层的比例开始神奇地固定了下来。换句话说,无论初始状态是什么,经过多次概率转移之后都会存在一个稳定的状态分布。其次我们只需要知道这个稳定的分布并乘以对应的价值,就可以计算所谓的长期收益了。

现在我们可以正式地总结一下马尔可夫链的平稳分布了,对于任意马尔可夫链,如果满足以下两个条件:

* 非周期性:由于马尔可夫链需要收敛,那么就一定不能是周期性的,实际上我们处理的问题基本上都是非周期性的,这点不需要做过多的考虑。
* 状态连通性:即存在概率转移矩阵$P$,能够使得任意状态$s_0$经过有限次转移到达状态$s$,反之亦然。

这样我们就可以得出结论,即该马氏链一定存在一个平稳分布,我们用$d^{\pi}(s)$表示,可得到:

$$
\label{eq:markov_station}
d^\pi(s)=\lim _{t \rightarrow \infty} P\left(s_t=s \mid s_0, \pi_\theta\right)
$$

换句话说,对于一个特定的环境,$d^{\pi}(s)$ 相当于一个环境本身常量,类似于状态转移概率矩阵,只是我们在求解马尔可夫过程的时候无法获得,只能通过其他的方法近似,例如在前面章节讲到的基于价值的算法中,我们就是通过贝尔曼公式来绕过状态转移概率矩阵进而通过迭代的方式求解状态价值函数的。

### 基于平稳分布的策略梯度推导

我们回顾前面小节中计算轨迹概率的公式$P_{\theta}(\tau)$,可以发现如果轨迹$\tau$的初始状态是$s_0$并且终止状态是$s$的话,轨迹概率公式$P_{\theta}(\tau)$跟平稳分布的$d^\pi(s)$是等效的,当然前提是该条轨迹必须“无限长”,即$t \rightarrow \infty$。但是平稳分布与轨迹概率公式相比,它的好处就是只涉及一个定量即初始状态$s_0$和一个变量$s$。对于每个状态$s$,我们用$V^{\pi}(s)$表示策略$\pi$下对应的价值。读者们现在可以往前回顾,为什么笔者说策略梯度算法跟基于价值函数的算法都是在计算累积状态的价值期望了,此时策略梯度算法目标函数就可以表示为:

$$
\label{eq:pg_station_object}
J(\theta)=\sum_{s \in \mathcal{S}} d^\pi(s) V^\pi(s)=\sum_{s \in \mathcal{S}} d^\pi(s) \sum_{a \in \mathcal{A}} \pi_\theta(a \mid s) Q^\pi(s, a)
$$

同样可以利用对数微分技巧求得对应的梯度,如下:

$$
\begin{aligned}
\nabla_\theta J(\theta) & \propto \sum_{s \in \mathcal{S}} d^\pi(s) \sum_{a \in \mathcal{A}} Q^\pi(s, a) \nabla_\theta \pi_\theta(a \mid s) \\
&=\sum_{s \in \mathcal{S}} d^\pi(s) \sum_{a \in \mathcal{A}} \pi_\theta(a \mid s) Q^\pi(s, a) \frac{\nabla_\theta \pi_\theta(a \mid s)}{\pi_\theta(a \mid s)} \\
&=\mathbb{E}_\pi\left[Q^\pi(s, a) \nabla_\theta \log \pi_\theta(a \mid s)\right]
\end{aligned}
$$

到这里我们会发现,REINFORCE 算法只是利用蒙特卡洛的方式将公式中的 $Q^\pi(s, a)$ 替换成了 $G(\tau)$。实际上读者们在学习了结合深度学习的 DQN 算法之后,$Q^\pi(s, a)$ 也是可以用神经网络模型来近似的,只是略有不同的是这里的 $Q^\pi(s, a)$ 相比于 DQN 算法中的 Q 函数多了一个策略函数 $\pi$ 作为输入,因此更多的像是在评判策略的价值而不是状态的价值,而用来近似 $Q^\pi(s, a)$ 的模型我们一般称作 Critic。相应地,另外只包含策略梯度的一部分 $\nabla_\theta \log \pi_\theta(a \mid s)$ 的模型我们一般称作 Actor。这样一来已经不是单纯的策略梯度算法了,而是同时结合了基于价值和策略梯度的算法,我们一般把这类算法称之为 Actor-Critic 算法,具体会在后面的章节中展开。

## 策略函数的设计

### 离散动作的策略函数

我们先回顾一下在 DQN 算法中是如何设计网络模型来近似 Q 函数的,通常是包含一个输入层、一个隐藏层和一个输出层,其中输入层一般是维度等于状态数的线性层,输出层则是维度等于动作数的线性层,对于更复杂的情况读者可以根据实际需要自行设计,比如中间多增加几层隐藏层或者改成 CNN 网络,只需要保证模型能够接收状态作为输入,并且能够输出等于每个动作的 Q 值即可。对于策略函数来说,我们也可以采用类似的设计,只不过我们输出的不是 Q 值,而是各个动作的概率分布。其实动作概率分布在实现上跟 Q 值的唯一区别就是必须都大于 0 且和为 1,最简单的做法是在 Q 网络模型的最后一层增加处理层,一般称作为动作层 (action layer)。由于原来 Q 网络模型输出的值是有正有负的,怎么把它们转换成动作概率分布呢?读者可能想到一个最简单的方式就是用最大值减去最小值得到一个范围值,然后原来的最小值变成0,其他原来的值则各自减去原来的最小值然后除以范围值,例如对于 $[-0.5,0,0.5]$,用最大值减去最小值得到 1,然后原来的最小值变成 0,其他原来的值则各自减去原来的最小值然后除以 1,最后得到的就是 $[0,0.5,0.5]$,这样一来就满足了概率分布的要求了,这就是最原始的 min-max 归一化思路。但是这种方法会有一些缺点,感兴趣的读者可自行查阅相关资料,这里就不详细展开了。我们通常采取目前比较流行的方式,即用 Softmax 函数来处理,定义如下:

$$
\label{eq:softmax_act}
\pi_\theta(s, a)=\frac{e^{\phi(s, a)^T} \theta}{\sum_b e^{\phi(s, b)^T}}
$$

其中 $\phi(s, a)$ 就是模型前面一层的输出。对应的梯度也可方便求得,如

$$
\nabla_\theta \log \pi_\theta(s \mid a)=\phi(s, a)-\mathbb{E}_{\pi_\theta}[\phi(s, .)]
$$

由于右边一项 $\mathbb{E}_{\pi_\theta}[\phi(s, .)]$ 表示的是动作层所有输出之和,也就是概率分布之和,即等于1,因此我们可以将其去掉,这样一来就可以得到更简单的梯度表达式,如下:

$$
\nabla_\theta \log \pi_\theta(s \mid a)= \phi(s, a)
$$

在实战中 $\phi(s, a)$ 和 Softmax 函数层 一般是合并在一起的,即直接在模型最后一层输出 Softmax 函数的结果,即概率分布 $p_\theta(s, a)$,这样就得到了最终的策略梯度,即:

$$
\nabla_\theta \log \pi_\theta(s \mid a)= \log p_\theta(s, a)
$$

在很多代码和论文中,一般都把它写作 `logits_p`,对应的 $p_\theta(s, a)$ 叫做 `probs`, 这个在后面实战中我们会看到。

### 连续动作的策略函数


对于连续动作空间,通常策略对应的动作可以从高斯分布${\mathbb{N}}\left(\phi(s)^{\mathbb{T}} \theta, \sigma^2\right)$,对应的梯度也可求得:
$$
\nabla_\theta \log \pi_\theta(s, a)==\frac{\left(a-\phi(s)^T \theta\right) \phi(s)}{\sigma^2}
$$

这个公式虽然看起来很复杂,但实现起来其实很简单,只需要在模型最后一层输出两个值,一个是均值,一个是方差,然后再用这两个值来构建一个高斯分布,然后采样即可。
File renamed without changes

0 comments on commit 3f68610

Please sign in to comment.