如何理解强化学习中的近端策略优化算法?

What is the way to understand Proximal Policy Optimization Algorithm in RL?

我了解强化学习的基础知识,但需要了解哪些术语才能阅读 arxiv PPO paper

学习和使用的路线图是什么PPO

PPO 是一种简单的算法,属于策略优化算法class(与 DQN 等基于值的方法相对)。如果你 "know" RL 基础知识(我的意思是如果你至少仔细阅读了 Sutton's book for example), then a first logical step is to get familiar with policy gradient algorithms. You can read this paper or chapter 13 of Sutton's book new edition. Additionally, you may also read this paper 关于 TRPO 的第一章,这是 PPO 的第一作者之前的工作(这篇论文有很多符号错误;请注意). 希望有帮助。--Mehdi

PPO(包括 TRPO)尝试保守地更新策略,而不会对每次策略更新之间的性能产生不利影响。

为此,您需要一种方法来衡量政策在每次更新后发生了多少变化。这种测量是通过查看更新策略和旧策略之间的 KL 差异来完成的。

这变成了一个有约束的优化问题,我们想要在最大性能的方向上改变策略,遵循我的新策略和旧策略之间的 KL 差异不超过某个预定义(或自适应)阈值的约束。

使用 TRPO,我们在更新期间计算 KL 约束并找到该问题的学习率(通过 Fisher 矩阵和共轭梯度)。这实现起来有点麻烦。

使用 PPO,我们通过将 KL 散度从约束变为惩罚项来简化问题,类似于例如 L1、L2 权重惩罚(以防止权重增长过大)。 PPO 通过将策略比率(更新策略与旧策略的比率)硬限制在 1.0 左右的小范围内,消除了一起计算 KL 散度的需要,进行了额外的修改,其中 1.0 表示新策略与旧策略相同。

为了更好地理解 PPO,查看这篇论文的主要贡献会有所帮助,它们是:(1) the Clipped Surrogate Objective 和 (2) 使用“随机梯度上升的多个时期来执行每个策略更新”。


来自原文PPO paper

We have introduced [PPO], a family of policy optimization methods that use multiple epochs of stochastic gradient ascent to perform each policy update. These methods have the stability and reliability of trust-region [TRPO] methods but are much simpler to implement, requiring only a few lines of code change to a vanilla policy gradient implementation, applicable in more general settings (for example, when using a joint architecture for the policy and value function), and have better overall performance.


1。剪短的代理人 Objective

Clipped Surrogate Objective 是策略梯度 objective 的 drop-in 替代品,旨在通过限制您在每一步对策略所做的更改来提高训练稳定性。

对于 vanilla 策略梯度(例如,REINFORCE)--- 你应该熟悉它,或者 familiarize yourself with 在你阅读这篇文章之前 --- 用于优化神经网络的 objective 看起来喜欢:

例如,这是您将在 Sutton book, and other resources, where the A-hat could be the discounted return (as in REINFORCE) or the advantage function (as in GAE) 中看到的标准公式。通过对网络参数的这种损失采取梯度上升步骤,您将激励导致更高回报的行动。

香草策略梯度方法使用您的操作的对数概率 (log π(a | s)) 来跟踪操作的影响,但您可以想象使用另一个函数来执行此操作。 this paper 中引入的另一个此类函数使用 当前策略下的动作概率 (π(a|s)),除以你的动作概率之前的策略 (π_old(a|s))。如果您熟悉的话,这看起来有点类似于重要性抽样:

当您的当前政策比您的政策可能采取行动时,此 r(θ) 将大于 1 政策;当您当前的保单采取行动的可能性低于您的旧保单时,它将介于 0 和 1 之间。

现在要用这个 r(θ) 构建一个 objective 函数,我们可以简单地将它换成 log π(a|s) 项。这是在 TRPO 中所做的:

但是如果你的行动对于你当前的政策来说更有可能(比如 100 倍),这里会发生什么? r(θ) 往往会非常大并导致采取可能会破坏您的政策的大梯度步骤。为了解决这个问题和其他问题,TRPO 添加了一些额外的功能(例如,KL 散度约束)以限制策略可以更改的数量并帮助保证它是单调改进的。

如果我们可以将这些稳定属性构建到 objective 函数中,而不是添加所有这些额外的花里胡哨的东西会怎么样?您可能会猜到,这就是 PPO 所做的。它获得了与 TRPO 相同的性能优势,并通过优化这个简单(但看起来有点滑稽)的 Clipped Surrogate 避免了并发症 Objective:

最小化中的第一项(蓝色)与我们在 TRPO objective 中看到的相同 (r(θ)A) 项。第二项(红色)是 (r(θ)) 被夹在 (1 - e, 1 + e) 之间的版本。 (在论文中,他们指出 e 的最佳值约为 0.2,因此 r 可以在 ~(0.8, 1.2) 之间变化)。然后,最后,对这两项进行最小化(绿色)。

花点时间仔细查看方程式,确保您知道所有符号的含义,以及数学上发生的事情。查看代码也可能有所帮助;这是 OpenAI baselines and anyrl-py 实现中的相关部分。

太棒了。

接下来,让我们看看L裁剪功能会产生什么效果。这是论文中的一张图表,绘制了 Advantage 为正和负时剪辑 objective 的值:

在图表的左半部分,其中 (A > 0),这是行动对结果产生估计积极影响的地方。在图表的右半部分,其中 (A < 0),这是行动对结果产生估计负面影响的地方。

注意左半边的 r-value 如果它太高就会被剪掉。如果在当前政策下采取行动的可能性比旧政策下的可能性大得多,就会发生这种情况。当发生这种情况时,我们不想变得贪婪而步得太远(因为这只是我们策略的局部近似和样本,所以如果我们步得太远将不准确),因此我们将 objective 以防止它增长。 (这将在阻止梯度的反向传递中产生效果---导致梯度为0的扁平线)。

在图表的右侧,该动作估计对结果有 负面 影响,我们看到剪辑在接近 0 时激活,其中动作在当前政策不太可能。这个裁剪区域同样会阻止我们更新太多以降低行动的可能性,因为我们已经迈出了一大步来降低它的可能性。

所以我们看到这两个裁剪区域防止我们变得过于贪婪和尝试o 一次更新太多并离开该样本提供良好估计的区域。

但为什么我们要让 r(θ) 在图的最右侧无限增长?乍一看这似乎很奇怪,但是在这种情况下,什么会导致 r(θ) 变得非常大? r(θ) 在该区域的增长将由梯度步骤引起,该梯度步骤使我们的动作 更有可能,结果使我们的政策更糟。如果真是这样,我们希望能够撤消该梯度步骤。恰好 L 剪辑功能允许这样做。这里的函数是负的,所以梯度会告诉我们朝另一个方向走,并根据我们搞砸的程度来降低这个动作的可能性。 (请注意,在图的最左侧有一个类似的区域,该区域的动作很好,但我们不小心降低了它的可能性。)

这些“撤消”区域解释了为什么我们必须在 objective 函数中包含奇怪的最小化项。它们对应于未剪切的 r(θ)A,其值低于剪切版本并由最小化返回。这是因为它们朝着错误的方向迈出了一步(例如,行动很好,但我们不小心降低了它的可能性)。如果我们没有在 objective 函数中包含最小值,这些区域将是平坦的(梯度 = 0)并且我们将无法修复错误。

这里有一张总结图:

这就是它的要点。 Clipped Surrogate Objective 只是一个 drop-in 替代品,您可以在 vanilla 策略梯度中使用。裁剪限制了您在每一步可以进行的有效更改以提高稳定性,而最小化允许我们在我们搞砸的情况下修复我们的错误。我没有讨论的一件事是 PPO objective 形成论文中讨论的“下限”的含义。有关更多信息,我建议 this part 作者的讲座。

2。策略更新的多个时期

不同于香草策略梯度方法,并且由于 Clipped Surrogate Objective 函数,PPO 允许您 运行 梯度上升的多个阶段样本而不会导致破坏性的大策略更新。这使您可以从数据中提取更多信息并降低样本效率。

PPO 运行s 策略使用 N 个并行参与者,每个参与者收集数据,然后它对该数据的 mini-batches 进行采样以训练 K 个 epochs 使用 Clipped Surrogate Objective 函数。请参阅下面的完整算法(近似参数值是:K = 3-15,M = 64-4096,T (horizon) = 128-2048):

平行参与者部分由 A3C paper 推广并已成为收集数据的相当标准的方式。

新的部分是它们能够在轨迹样本上进行 运行 K 个梯度上升时期。正如他们在论文中所说的那样,运行 对数据进行多次传递的香草策略梯度优化会很好,这样您就可以从每个样本中了解更多信息。然而,对于普通方法来说,这在实践中通常会失败,因为它们对本地样本采取了太大的步骤,这会破坏策略。另一方面,PPO 具有 built-in 机制来防止更新过多。

对于每次迭代,在使用 π_old(第 3 行)对环境进行采样之后,当我们开始 运行优化(第 6 行)时,我们的策略 π 将完全等于 π_old。所以一开始,none 我们的更新将被剪掉,我们保证可以从这些例子中学到一些东西。然而,当我们使用多个时期更新 π 时,objective 将开始达到裁剪限制,这些样本的梯度将变为 0,训练将逐渐停止……直到我们进入下一次迭代并收集新样本。

.....

现在就这些。如果您有兴趣获得更好的理解,我建议您深入研究 original paper, trying to implement it yourself, or diving into the baselines implementation 并使用代码。

[编辑:2019/01/27]:为了更好的背景知识以及 PPO 与其他 RL 算法的关系,我还强烈建议查看 OpenAI 的 Spinning Up resources and implementations

我认为实现离散动作 space(例如 Cartpole-v1)比连续动作 spaces 更容易。但是对于连续动作 spaces,这是我在 Pytorch 中发现的最多 straight-forward 的实现,因为你可以清楚地看到它们是如何获得 mustd 的,而我无法使用更知名的实现,例如 Openai Baselines 和 Spinning up 或 Stable Baselines。

RL-Adventure PPO

以上 link 中的这些行:

class ActorCritic(nn.Module):
    def __init__(self, num_inputs, num_outputs, hidden_size, std=0.0):
        super(ActorCritic, self).__init__()

        self.critic = nn.Sequential(
            nn.Linear(num_inputs, hidden_size),
            nn.ReLU(),
            nn.Linear(hidden_size, 1)
        )

        self.actor = nn.Sequential(
            nn.Linear(num_inputs, hidden_size),
            nn.ReLU(),
            nn.Linear(hidden_size, num_outputs),
        )
        self.log_std = nn.Parameter(torch.ones(1, num_outputs) * std)

        self.apply(init_weights)

    def forward(self, x):
        value = self.critic(x)
        mu    = self.actor(x)
        std   = self.log_std.exp().expand_as(mu)
        dist  = Normal(mu, std)
        return dist, value

和剪辑:

def ppo_update(ppo_epochs, mini_batch_size, states, actions, log_probs, returns, advantages, clip_param=0.2):
    for _ in range(ppo_epochs):
        for state, action, old_log_probs, return_, advantage in ppo_iter(mini_batch_size, states, actions, log_probs, returns, advantages):
            dist, value = model(state)
            entropy = dist.entropy().mean()
            new_log_probs = dist.log_prob(action)

            ratio = (new_log_probs - old_log_probs).exp()
            surr1 = ratio * advantage
            surr2 = torch.clamp(ratio, 1.0 - clip_param, 1.0 + clip_param) * advantage

我在 Youtube 上找到了 link 上面关于这个视频的评论:

arxiv insights PPO