当前位置:   article > 正文

PPO(Proximal Policy Optimization)算法原理及实现,详解近端策略优化_ppo算法

ppo算法

 近端策略优化(PPO),它的性能与最先进的方法相当或更好,同时更容易实现和调整。PPO因其易用性和良好的性能成为OpenAI默认的强化学习算法。(2017年,openAI官网发布)

Proximal Policy Optimization (openai.com)

官方代码:

openAI给出的ppo官方代码OpenAI Baselines: high-quality implementations of reinforcement learning algorithms - openai/baselinesicon-default.png?t=N7T8https://github.com/openai/baselines官方论文参考:

openAI论文icon-default.png?t=N7T8https://arxiv.org/abs/1707.06347

具体步骤是怎样的?
PPO算法的具体步骤是基于对策略梯度方法的改进,它主要包括以下几个关键的步骤:

1、收集数据:通过在环境中执行当前策略(policy)来收集一组交互数据。这些数据包括状态(state)、动作(action)、奖励(reward)以及可能的下一个状态。

2、计算优势估计:为了评价一个动作相对于平均水平的好坏,需要计算优势函数(advantage function)。这通常是通过某种形式的时间差分(TD)估计或者广义优势估计(GAE)来完成的。

3、优化目标函数:PPO算法使用一个特殊设计的目标函数,r_t({\theta})=\frac{\pi_\theta (a_t|s_t)}{\pi_{\theta_{}old} (a_t|s_t)}这个函数涉及到概率比率
 表示旧策略。目标函数的形式通常为:
L(\theta )=E(min(r_t(\theta )\hat{A},clip(r_t(\theta ),1-\epsilon,1+\epsilon )\hat{A})

其中,\hat{A}是优势函数的估计\epsilon是一个小的正数(如0.1或0.2),clip 函数限制了概率比率r_t(\theta )
 (θ)的变化范围,防止更新步骤过大。

4、更新策略:使用梯度上升方法来更新策略参数\theta,即 \theta \leftarrow \theta + \alpha \nabla_\theta L(\theta),其中\alpha是学习率。

5、重复步骤:使用新的策略参数重复以上步骤,直到满足某些停止准则,比如策略性能不再提升或者已经达到了一定的迭代次数。

PPO算法的关键之处在于它通过限制策略更新的幅度,使得学习过程更加稳定。在每次更新时,概率比率r_t(\theta )被限制在[1-\epsilon, 1+\epsilon]范围内,防止由于单个数据点导致的极端策略更新,这有助于避免策略性能的急剧下降。同时,PPO允许在每次迭代中使用相同的数据多次进行策略更新,这提高了数据效率。

​​​​​​​
     

1、Policy Gradient(PG)算法

1.1、算法的数学概念

在PG算法中,Agent又被称为Actor,Actor对于一个特定的任务,都有自己的一个策略π,策略π通常用一个神经网络表示,其参数为θ。从一个特定的状态state出发,一直到任务的结束,被称为一个完整的eposide,在每一步,我们都能获得一个奖励r,一个完整的任务所获得的最终奖励被称为R。这样,一个有T个时刻的eposide,Actor不断与环境交互,形成如下的序列τ:

这样一个序列τ是不确定的,因为Actor在不同state下所采取的action可能是不同的,一个序列τ发生的概率为:

序列τ所获得的奖励为每个阶段所得到的奖励的和,称为R(τ)。因此,在Actor的策略为π的情况下,所能获得的期望奖励为:

而我们的期望是调整Actor的策略π,使得期望奖励最大化,于是有了策略梯度的方法,既然期望函数已经有了,只要使用梯度提升的方法更新我们的网络参数θ(即更新策略π)就好了,所以问题的重点变为了求参数的梯度。梯度的求解过程如下

上面的过程中,首先利用log函数求导的特点进行转化随后用N次采样的平均值来近似期望,最后,将pθ展开(1-Tn),将与θ无关的项去掉,即得到了最终的结果。

所以,一个PG方法的完整过程如下:

首先采集数据,然后基于前面得到的梯度提升的式子更新参数,随后再根据更新后的策略再采集数据,再更新参数,如此循环进行。

注意到图中的大红字only used once,因为在更新参数后,策略已经变了,而先前的数据是基于更新参数前的策略得到的。

1.2、Tip1:增加一个基线(baseline)

通过上面的介绍你可能发现了,PG方法在更新策略时,基本思想就是增加reward大的动作出现的概率,减小reward小的策略出现的概率

假设现在有一种情况,我们的reward在无论何时都是正的,对于没有采样到的动作,它的reward是0。因此,如果一个比较好的动作没有被采样到,而采样到的不好的动作得到了一个比较小的正reward,那么没有被采样到的好动作的出现概率会越来越小,这显然是不合适的,因此我们需要增加一个奖励的基线,让reward有正有负。
一般增加的基线是所获得奖励的平均值:

1.3、Tip2:增加折扣因子


这个很容易理解,就像买股票一样,未来的1块钱的价值要小于当前1块钱的价值,因此未来的1块钱变成现在的价值,需要进行一定的折扣

1.3、Tip3:使用优势函数

我们之前介绍的PG方法,对于同一个eposide中的所有数据,使用的奖励都是一样的,其实与st和at相关的。

这里我们使用的是优势函数,即Qπ(st,at) - Vπ(st)。其中Qπ(st,at)可以使用从当前状态开始到eposide结束的奖励折现和得到Vπ(st)可以通过一个critic来计算得到

2、PPO算法原理简介

接着上面的讲,PG方法一个很大的缺点就是参数更新慢,因为我们每更新一次参数都需要进行重新的采样,这其实是中on-policy的策略,即我们想要训练的agent和与环境进行交互的agent是同一个agent;与之对应的就是off-policy的策略,即想要训练的agent和与环境进行交互的agent不是同一个agent,简单来说,就是拿别人的经验来训练自己。

举个下棋的例子,如果你是通过自己下棋来不断提升自己的棋艺,那么就是on-policy的,如果是通过看别人下棋来提升自己,那么就是off-policy的。

2.1、重要性采样

那么为了提升我们的训练速度,让采样到的数据可以重复使用,我们可以将on-policy的方式转换为off-policy的方式。即我们的训练数据通过另一个Actor(对应的网络参数为θ'得到。这要怎么做呢?通过下面的思路:

通过这种方式,我们的p(x)和q(x)的分布不能差别太大,否则需要进行非常多次的采样,才能得到近似的结果:

如上图所示,很显然,在x服从p(x)分布时,f(x)的期望为负,此时我们从q(x)中来采样少数的x,那么我们采样到的x很有可能都分布在右半部分,此时f(x)大于0,我们很容易得到f(x)的期望为正的结论,这就会出现问题,因此需要进行大量的采样。


 

2.2、梯度更新

那么此时我们想要期望奖励最大化,则变为:

则梯度变为:

最后一项因为我们假设两个分布不能差太远,所以认为他们是相等的,为了求解方便,我们直接划掉。此时似然函数变为:

由梯度变为似然函数,使用的还是下面式子,可以自己手动算一下:

到这里,马上就要得到我们的PPO算法了,再坚持一下!

2.3、TRPO

PPO 有一个前身:信任区域策略优化(trust region policy optimization,TRPO),TRPO 的式子如下式所示。


TRPO 与 PPO 不一样的地方是约束项的位置不一样,PPO 是直接把约束放到要优化的式子里,可以直接用梯度上升的方法最大化这个式子。但TRPO是把 KL 散度当作约束,它希望\theta\theta'的 KL 散度小于一个\delta。如果我们使用的是基于梯度的优化时,有约束是很难处理的,因为它把 KL 散度约束当做一个额外的约束,没有放目标里面。PPO 跟 TRPO 的性能差不多,但 PPO 在实现上比 TRPO 容易的多,所以我们一般就用 PPO,而不用TRPO。

2.4、PPO1:近端策略优化惩罚(PPO-penalty)

我们前面介绍了,我们希望θ和θ'不能差太远,这并不是说参数的值不能差太多,而是说,输入同样的state,网络得到的动作的概率分布不能差太远。得到动作的概率分布的相似程度,我们可以用KL散度来计算,将其加入PPO模型的似然函数中,变为:

在实际中,我们会动态改变对θ和θ'分布差异的惩罚,如果KL散度值太大,我们增加这一部分惩罚,如果小到一定值,我们就减小这一部分的惩罚,基于此,我们得到了PPO算法的过程:

2.5、PPO2:近端策略优化裁剪(PPO-clip)

PPO算法还有另一种实现方式,不将KL散度直接放入似然函数中,而是进行一定程度的裁剪:

上图中,绿色的线代表min中的第一项,即不做任何处理,蓝色的线为第二项,如果两个分布差距太大,则进行一定程度的裁剪。最后对这两项再取min,防止了θ更新太快。

上式看起来很复杂,其实很简单,它想做的事情就是希望p_\theta(a_t|s_t)p_{\theta^{k}}(a_t|s_t),也就是做示范的模型跟实际上学习的模型,在优化以后不要差距太大。

  • 操作符min作用是在第一项和第二项中选择最小的。

  • 第二项前面有个裁剪(clip)函数,裁剪函数是指:在括号里有三项,如果第一项小于第二项,则输出1 − ε;如果第一项大于第三项的话,则输出1 + ε。

  • ε 是一个超参数,要需要我们调整的,一般设置为0.1或0.2 。

附录:改进on-policy策略的思路说明(附录可以跳过)

\theta '收集到的数据去训练\theta。假设我们可以用\theta '收集到的数据去训练\theta,意味着说我们可以把\theta '收集到的数据用很多次,也就是\theta可以执行梯度上升好几次,\theta更新参数好几次,这都只要用同一批数据就可以实现。那\theta '就只要采样一次,也许采样多一点的数据,让\theta去更新很多次, 这样就会比较有效率。

那么问题来了, 我们怎么找到这样的一个演员\theta ',使其收集到的数据可以用于训练\theta,且他们之间的差异可以被忽略不计呢?

首先我们先介绍一个名词,重要性采样(importance sampling)。 假设有一个函数f(x)x需要从分布p中采样。我们应该如何怎么计算f(x)的期望值呢?假设分布p不能做积分,那么我们可以从分布p尽可能多采样更多的x^{i}。这样就会得到更多的f(x),取它的平均值就可以近似f(x)的期望值。

现在另外一个问题也来了,假设我们不能在分布p中采样数据,只能从另外一个分布q,中去采样数据.q可以是任何分布。我们从q中采样x^{i}的话就不能直接套下面的式子。


因为上式是假设x都是从p采样出来的。如果我们想要在q中采样的情况下带入上式,就需要做些变换。期望值
E_{x\sim p}[f(x)]的另一种写法是\int f(x)p(x)dx,对其进行变换,如下式所示,


整理得下式,

这样就可以对分布q中采样的x取期望值。具体来说,计算f(x)\tfrac{p(x)}{q(x)},最后取期望值。

这边是从q做采样,所以我们从q里采样出来的每一笔数据,需要乘上一个重要性权重(importance weight)\frac{p(x)}{q(x)}来修正这两个分布的差异。

q(x)可以是任何分布。重要性采样有一些问题。虽然我们可以把p换成任何的q。但是在实现上,

pq不能差太多。差太多的话,会有一些问题。两个随机变量的平均值一样,并不代表它的方差一样,可以带入方差公式Var[X]=E[X^2]-(E[X])^2推导一下。

现在要做的事情就是把重要性采样用在off_policy的情况,把on-policy训练的算法改成off-olicy训练的算法。 怎么改呢,如下式所示,我们用另外一个策略\pi_{\theta'},它就是另外一个演员,与环境做互动,采样出轨迹\tau,计算

实际在做策略梯度的时候,并不是给整个轨迹\tau都一样的分数,而是每一个状态-动作的对会分开来计算。具体可参考上一篇PG的文章。实际上更新梯度的时候,如下式所示。

现在s_t,a_t\theta'跟环境互动以后所采样到的数据。但是拿来训练,要调整参数的模型是\theta。因为

\theta\theta'是不同的模型,所以需要用重要性采样技术去做修正。即把s_t,a_t\theta采样出来的概率除掉

s_t,a_t\theta'采样出来的概率。公式如下。


上式中的A^\theta (s_t,a_t)有一个上标\theta,代表说是agent\theta跟环境互动的时候所计算出来的结果。但实际上从\theta换到\theta'的时候,应该改成A^{\theta'}(s_t,a_t)

接下来,我们可以拆解p_\theta({s_t,a_t} )p_{\theta'}{(s_t,a_t)},即


于是可得公式


这里需要做一件事情,假设模型是\theta的时候,我们看到s_t的概率,跟模型是\theta'的时候,看到s_t的概率是差不多的,即p_\theta({s_t} )=p_{\theta'}{(s_t)},实际上在更新参数 的时候,我们就是按照下式来更新参数。

我们有个策略的网络,输入状态s_t,它会输出每一个a_t的概率。所以p_\theta({a_t|s_t} )p_{\theta'}({a_t|s_t} )这两项,我们只要知道\theta\theta'的参数就可以算。

所以实际上,我们可以从梯度去反推原来的目标函数,可以用

来反推目标函数(由梯度变为似然函数)。

当使用重要性采样的时候,要去优化的目标函数如下式所示,我们把它记括号里面的\theta代表我们需要去优化的参数。用\theta'去做示范采样数据,采样出s_ta_t以后,要去计算跟s_ta_t的优势,再乘上


 

3、PPO算法Tensorflow实现

除了官网的,还有详解近端策略优化(ppo,干货满满) - 简书 (jianshu.com)

参考的是莫烦老师的代码:Reinforcement-learning-with-tensorflow/contents/12_Proximal_Policy_Optimization/simply_PPO.py at master · MorvanZhou/Reinforcement-learning-with-tensorflow · GitHub

案例:倒立摆问题。钟摆以随机位置开始,目标是将其向上摆动,使其保持直立。 测试环境:Pendulum-v1

动作:往左转还是往右转,用力矩来衡量,即力乘以力臂。范围[-2,2]:(连续空间)

状态:cos(theta), sin(theta) , thetadot。

奖励:越直立拿到的奖励越高,越偏离,奖励越低。奖励的最大值为0。

3.1、代码定义网络结构

  1. class FeedForwardNN(nn.Module):
  2. def __init__(self, in_dim, out_dim):
  3. super(FeedForwardNN, self).__init__()
  4. self.layer1 = nn.Linear(in_dim, 64)
  5. self.layer2 = nn.Linear(64, 64)
  6. self.layer3 = nn.Linear(64, out_dim)
  7. def forward(self, obs):
  8. if isinstance(obs, np.ndarray):
  9. obs = torch.tensor(obs, dtype=torch.float)
  10. activation1 = F.relu(self.layer1(obs))
  11. activation2 = F.relu(self.layer2(activation1))
  12. output = self.layer3(activation2)
  13. return output

3.2、定义PPO类

  1. class PPO:
  2. def __init__(self, policy_class, env, **hyperparameters):
  3. # PPO 初始化用于训练的超参数
  4. self._init_hyperparameters(hyperparameters)
  5. # 提取环境信息
  6. self.env = env
  7. self.obs_dim = env.observation_space.shape[0]
  8. self.act_dim = env.action_space.shape[0]
  9. # 初始化演员和评论家网络
  10. self.actor = policy_class(self.obs_dim, self.act_dim)
  11. self.critic = policy_class(self.obs_dim, 1)
  12. # 为演员和评论家初始化优化器
  13. self.actor_optim = Adam(self.actor.parameters(), lr=self.lr)
  14. self.critic_optim = Adam(self.critic.parameters(), lr=self.lr)
  15. # 初始化协方差矩阵,用于查询actor网络的action
  16. self.cov_var = torch.full(size=(self.act_dim,), fill_value=0.5)
  17. self.cov_mat = torch.diag(self.cov_var)
  18. # 这个记录器将帮助我们打印出每个迭代的摘要
  19. self.logger = {
  20. 'delta_t': time.time_ns(),
  21. 't_so_far': 0, # 到目前为止的时间步数
  22. 'i_so_far': 0, # 到目前为止的迭代次数
  23. 'batch_lens': [], # 批次中的episodic长度
  24. 'batch_rews': [], # 批次中的rews回报
  25. 'actor_losses': [], # 当前迭代中演员网络的损失
  26. }
  27. def learn(self, total_timesteps):
  28. print(f"Learning... Running {self.max_timesteps_per_episode} timesteps per episode, ", end='')
  29. print(f"{self.timesteps_per_batch} timesteps per batch for a total of {total_timesteps} timesteps")
  30. t_so_far = 0 # 到目前为止仿真的时间步数
  31. i_so_far = 0 # 到目前为止,已运行的迭代次数
  32. while t_so_far < total_timesteps:
  33. # 收集批量实验数据
  34. batch_obs, batch_acts, batch_log_probs, batch_rtgs, batch_lens = self.rollout()
  35. # 计算收集这一批数据的时间步数
  36. t_so_far += np.sum(batch_lens)
  37. # 增加迭代次数
  38. i_so_far += 1
  39. # 记录到目前为止的时间步数和到目前为止的迭代次数
  40. self.logger['t_so_far'] = t_so_far
  41. self.logger['i_so_far'] = i_so_far
  42. # 计算第k次迭代的advantage
  43. V, _ = self.evaluate(batch_obs, batch_acts)
  44. A_k = batch_rtgs - V.detach()
  45. # 将优势归一化 在理论上不是必须的,但在实践中,它减少了我们优势的方差,使收敛更加稳定和快速。
  46. # 添加这个是因为在没有这个的情况下,解决一些环境的问题太不稳定了。
  47. A_k = (A_k - A_k.mean()) / (A_k.std() + 1e-10)
  48. # 在其中更新我们的网络。
  49. for _ in range(self.n_updates_per_iteration):
  50. V, curr_log_probs = self.evaluate(batch_obs, batch_acts)
  51. # 重要性采样的权重
  52. ratios = torch.exp(curr_log_probs - batch_log_probs)
  53. surr1 = ratios * A_k
  54. surr2 = torch.clamp(ratios, 1 - self.clip, 1 + self.clip) * A_k
  55. # 计算两个网络的损失。
  56. actor_loss = (-torch.min(surr1, surr2)).mean()
  57. critic_loss = nn.MSELoss()(V, batch_rtgs)
  58. # 计算梯度并对actor网络进行反向传播
  59. # 梯度清零
  60. self.actor_optim.zero_grad()
  61. # 反向传播,产生梯度
  62. actor_loss.backward(retain_graph=True)
  63. # 通过梯度下降进行优化
  64. self.actor_optim.step()
  65. # 计算梯度并对critic网络进行反向传播
  66. self.critic_optim.zero_grad()
  67. critic_loss.backward()
  68. self.critic_optim.step()
  69. self.logger['actor_losses'].append(actor_loss.detach())
  70. self._log_summary()
  71. if i_so_far % self.save_freq == 0:
  72. torch.save(self.actor.state_dict(), './ppo_actor.pth')
  73. torch.save(self.critic.state_dict(), './ppo_critic.pth')
  74. def rollout(self):
  75. """
  76. 这就是我们从实验中收集一批数据的地方。由于这是一个on-policy的算法,我们需要在每次迭代行为者/批评者网络时收集一批新的数据。
  77. """
  78. batch_obs = []
  79. batch_acts = []
  80. batch_log_probs = []
  81. batch_rews = []
  82. batch_rtgs = []
  83. batch_lens = []
  84. # 一回合的数据。追踪每一回合的奖励,在回合结束的时候会被清空,开始新的回合。
  85. ep_rews = []
  86. # 追踪到目前为止这批程序我们已经运行了多少个时间段
  87. t = 0
  88. # 继续实验,直到我们每批运行超过或等于指定的时间步数
  89. while t < self.timesteps_per_batch:
  90. ep_rews = [] 每回合收集的奖励
  91. # 重置环境
  92. obs = self.env.reset()
  93. done = False
  94. # 运行一个回合的最大时间为max_timesteps_per_episode的时间步数
  95. for ep_t in range(self.max_timesteps_per_episode):
  96. if self.render and (self.logger['i_so_far'] % self.render_every_i == 0) and len(batch_lens) == 0:
  97. self.env.render()
  98. # 递增时间步数,到目前为止已经运行了这批程序
  99. t += 1
  100. # 追踪本批中的观察结果
  101. batch_obs.append(obs)
  102. # 计算action,并在env中执行一次step。
  103. # 注意,rew是奖励的简称。
  104. action, log_prob = self.get_action(obs)
  105. obs, rew, done, _ = self.env.step(action)
  106. # 追踪最近的奖励、action和action的对数概率
  107. ep_rews.append(rew)
  108. batch_acts.append(action)
  109. batch_log_probs.append(log_prob)
  110. if done:
  111. break
  112. # 追踪本回合的长度和奖励
  113. batch_lens.append(ep_t + 1)
  114. batch_rews.append(ep_rews)
  115. # 将数据重塑为函数描述中指定形状的张量,然后返回
  116. batch_obs = torch.tensor(batch_obs, dtype=torch.float)
  117. batch_acts = torch.tensor(batch_acts, dtype=torch.float)
  118. batch_log_probs = torch.tensor(batch_log_probs, dtype=torch.float)
  119. batch_rtgs = self.compute_rtgs(batch_rews)
  120. # 在这批中记录回合的回报和回合的长度。
  121. self.logger['batch_rews'] = batch_rews
  122. self.logger['batch_lens'] = batch_lens
  123. return batch_obs, batch_acts, batch_log_probs, batch_rtgs, batch_lens
  124. def compute_rtgs(self, batch_rews):
  125. batch_rtgs = []
  126. # 遍历每一回合,一个回合有一批奖励
  127. for ep_rews in reversed(batch_rews):
  128. # 到目前为止的折扣奖励
  129. discounted_reward = 0
  130. # 遍历这一回合的所有奖励。我们向后退,以便更顺利地计算每一个折现的回报
  131. for rew in reversed(ep_rews):
  132. discounted_reward = rew + discounted_reward * self.gamma
  133. batch_rtgs.insert(0, discounted_reward)
  134. # 将每个回合的折扣奖励的数据转换成张量
  135. batch_rtgs = torch.tensor(batch_rtgs, dtype=torch.float)
  136. return batch_rtgs
  137. def get_action(self, obs):
  138. mean = self.actor(obs)
  139. # 用上述协方差矩阵中的平均行动和标准差创建一个分布。
  140. dist = MultivariateNormal(mean, self.cov_mat)
  141. action = dist.sample()
  142. log_prob = dist.log_prob(action)
  143. return action.detach().numpy(), log_prob.detach()
  144. def evaluate(self, batch_obs, batch_acts):
  145. """
  146. 估算每个观察值,以及最近一批actor网络迭代中的每个action的对数prob。
  147. """
  148. # 为每个batch_obs查询critic网络的V值。V的形状应与batch_rtgs相同。
  149. V = self.critic(batch_obs).squeeze()
  150. # 使用最近的actor网络计算批量action的对数概率。
  151. mean = self.actor(batch_obs)
  152. dist = MultivariateNormal(mean, self.cov_mat)
  153. log_probs = dist.log_prob(batch_acts)
  154. # 返回批次中每个观察值的值向量V和批次中每个动作的对数概率log_probs
  155. return V, log_probs

4、总结

PPO的特点:

PPO(Proximal Policy Optimization,近端策略优化)是一种强化学习算法,由John Schulman等人在2017年提出。PPO属于策略梯度方法,这类方法直接对策略(即模型的行为)进行优化,试图找到使得期望回报最大化的策略。PPO旨在改进和简化以前的策略梯度算法,如TRPO(Trust Region Policy Optimization,信任域策略优化),它通过几个关键的技术创新提高了训练的稳定性和效率。

PPO的主要特点包括:

裁剪的概率比率:PPO使用一个目标函数,其中包含了一个裁剪的概率比率,这个比率是旧策略和新策略产生动作概率的比值。这个比率被限制在一个范围内,防止策略在更新时做出太大的改变。

多次更新:在一个数据批次上可以安全地进行多次更新,这对于样本效率非常重要,尤其是在高维输入和实时学习环境中。

简单实现:与TRPO相比,PPO更容易实现和调整,因为它不需要复杂的数学运算来保证策略更新的安全性。

平衡探索与利用:PPO尝试在学习稳定性和足够的探索之间取得平衡,以避免局部最优并改进策略性能。

PPO已被广泛应用于各种强化学习场景,包括游戏、机器人控制以及自然语言处理中的序列决策问题。它是目前最流行的强化学习算法之一。

 参考文献

1、李宏毅老师的强化学习课程的前两讲,主要介绍了Policy Gradient算法和Proximal Policy Optimization算法,在此整理总结一下。

视频地址:Q-learning (Advanced Tips)_哔哩哔哩_bilibili


2、作者:文哥的学习日记
链接:https://www.jianshu.com/p/9f113adc0c50,来源:简书

3、作者:行者AI
链接:https://www.jianshu.com/p/8803cb2d4e30
来源:简书

4、原文链接:https://blog.csdn.net/u014386899/article/details/136474215【基础知识】什么是 PPO(Proximal Policy Optimization,近端策略优化)_schulman 提出ppo-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/928183
推荐阅读
相关标签
  

闽ICP备14008679号