RL 基础 | 如何复现 PPO,以及一些踩坑经历
最近在复现 PPO 跑 MiniGrid,记录一下…
这里跑的环境是 Empty-5x5 和 8x8,都是简单环境,主要验证 PPO 实现是否正确。
01 Proximal policy Optimization(PPO)
(参考:知乎 | Proximal Policy Optimization (PPO) 算法理解:从策略梯度开始 )
首先,策略梯度方法 的梯度形式是
\frac1n \sum_{i=0}^{n-1} R(\tau_i)
\sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t)
\tag1
\]
然而,传统策略梯度方法容易一步走的太多,以至于越过了中间比较好的点(在参考知乎博客里称为 overshooting)。一个直观的想法是限制策略每次不要更新太多,比如去约束 新策略 旧策略之间的 KL 散度(公式是 plog(p/q)):
\pi_\theta(a|s)\log\frac{\pi_\theta(a|s)}{\pi_{\theta+\Delta \theta}(a|s)} \le \epsilon
\tag2
\]
我们把这个约束进行拉格朗日松弛,将它变成一个惩罚项:
\lambda [D_{KL}(\pi_\theta | \pi_{\theta+\Delta \theta})-\epsilon]
\tag3
\]
然后再使用一些数学近似技巧,可以得到自然策略梯度(NPG)算法。
NPG 算法貌似还有种种问题,比如 KL 散度的约束太紧,导致每次更新后的策略性能没有提升。我们希望每次策略更新后都带来性能提升,因此计算 新策略 旧策略之间 预期回报的差异。这里采用计算 advantage 的方式:
\gamma^tA^{\pi_{\theta}}(s_t,a_t)
\tag{4}
\]
其中优势函数(advantage)的定义是:
\tag{5}
\]
在公式 (4) 中,我们计算的 advantage 是在 新策略 的期望下的。但是,在新策略下蒙特卡洛采样(rollout)来算 advantage 期望太麻烦了,因此我们在原策略下 rollout,并进行 importance sampling,假装计算的是新策略下的 advantage。这个 advantage 被称为替代优势(surrogate advantage):
J\left(\pi_{\theta+\Delta\theta}\right)-J\left(\pi_{\theta}\right)\approx E_{s\sim\rho_{\pi\theta}}\frac{\pi_{\theta+\Delta\theta}(a\mid s)}{\pi_{\theta}(a\mid s)} A^{\pi_{\theta}}(s, a)
\tag6
\]
所产生的近似误差,貌似可以用两种策略之间最坏情况的 KL 散度表示:
\tag7
\]
其中 C 是一个常数。这貌似就是 TRPO 的单调改进定理,即,如果我们改进下限 RHS,我们也会将目标 LHS 改进至少相同的量。
基于 TRPO 算法,我们可以得到 PPO 算法。PPO Penalty 跟 TRPO 比较相近:
\Big[\mathcal{L}_{\theta+\Delta\theta}(\theta+\Delta\theta)-\beta\cdot \mathcal{D}_{KL}(\pi_{\theta}\parallel\pi_{\theta+\Delta\theta})\Big]
\tag 8
\]
其中,KL 散度惩罚的 β 是启发式确定的:PPO 会设置一个目标散度 \(\delta\),如果最终更新的散度超过目标散度的 1.5 倍,则下一次迭代我们将加倍 β 来加重惩罚。相反,如果更新太小,我们将 β 减半,从而扩大信任域。
接下来是 PPO Clip,这貌似是目前最常用的 PPO。PPO Penalty 用 β 来惩罚策略变化,而 PPO Clip 与此不同,直接限制策略可以改变的范围。我们重新定义 surrogate advantage:
\mathcal{L}_{\pi_{\theta}}^{CLIP}(\pi_{\theta_{k}}) = \mathbb E_{\tau\sim\pi_{\theta}}\bigg[\sum_{t=0}^{T}
\min\Big( & \rho_{t}(\pi_{\theta}, \pi_{\theta_{k}})A_{t}^{\pi_{\theta_{k}}},
\\
& \text{clip} (\rho_{t}(\pi_{\theta},\pi_{\theta_{k}}), 1-\epsilon, 1+\epsilon) A_{t}^{\pi_{\theta_{k}}}
\Big)\bigg]
\end{aligned}
\tag 9
\]
其中, \(\rho_{t}\) 为重要性采样的 ratio:
\tag{10}
\]
公式 (9) 中,min 括号里的第一项是 ratio 和 advantage 相乘,代表新策略下的 advantage;min 括号里的第二项是对 ration 进行的 clip 与 advantage 的相乘。这个 min 貌似可以限制策略变化不要太大。
02 如何复现 PPO(参考 stable baselines3 和 clean RL)
- stable baselines3 的 PPO:https://github.com/DLR-RM/stable-baselines3/blob/master/stable_baselines3/ppo/ppo.py
- clean RL 的 PPO:https://github.com/vwxyzjn/cleanrl/blob/master/cleanrl/ppo.py
代码主要结构如下,以 stable baselines3 为例:(仅保留主要结构,相当于伪代码,不保证正确性)
import torch
import torch.nn.functional as F
import numpy as np
# 1. collect rollout
self.policy.eval()
rollout_buffer.reset()
while not done:
actions, values, log_probs = self.policy(self._last_obs)
new_obs, rewards, dones, infos = env.step(clipped_actions)
rollout_buffer.add(
self._last_obs, actions, rewards,
self._last_episode_starts, values, log_probs,
)
self._last_obs = new_obs
self._last_episode_starts = dones
with torch.no_grad():
# Compute value for the last timestep
values = self.policy.predict_values(obs_as_tensor(new_obs, self.device))
rollout_buffer.compute_returns_and_advantage(last_values=values, dones=dones)
# 2. policy optimization
for rollout_data in self.rollout_buffer.get(self.batch_size):
actions = rollout_data.actions
values, log_prob, entropy = self.policy.evaluate_actions(rollout_data.observations, actions)
advantages = rollout_data.advantages
# Normalize advantage
if self.normalize_advantage and len(advantages) > 1:
advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)
# ratio between old and new policy, should be one at the first iteration
ratio = torch.exp(log_prob - rollout_data.old_log_prob)
# clipped surrogate loss
policy_loss_1 = advantages * ratio
policy_loss_2 = advantages * torch.clamp(ratio, 1 - clip_range, 1 + clip_range)
policy_loss = -torch.min(policy_loss_1, policy_loss_2).mean()
# Value loss using the TD(gae_lambda) target
value_loss = F.mse_loss(rollout_data.returns, values_pred)
# Entropy loss favor exploration
entropy_loss = -torch.mean(entropy)
loss = policy_loss + self.ent_coef * entropy_loss + self.vf_coef * value_loss
# Optimization step
self.policy.optimizer.zero_grad()
loss.backward()
# Clip grad norm
torch.nn.utils.clip_grad_norm_(self.policy.parameters(), self.max_grad_norm)
self.policy.optimizer.step()
大致流程:收集当前策略的 rollout → 计算 advantage → 策略优化。
计算 advantage 是由 rollout_buffer.compute_returns_and_advantage 函数实现的:
rb = rollout_buffer
last_gae_lam = 0
for step in reversed(range(buffer_size)):
if step == buffer_size - 1:
next_non_terminal = 1.0 - dones.astype(np.float32)
next_values = last_values
else:
next_non_terminal = 1.0 - rb.episode_starts[step + 1]
next_values = rb.values[step + 1]
delta = rb.rewards[step] + gamma * next_values * next_non_terminal - rb.values[step] # (1)
last_gae_lam = delta + gamma * gae_lambda * next_non_terminal * last_gae_lam # (2)
rb.advantages[step] = last_gae_lam
rb.returns = rb.advantages + rb.values
其中,
- (1) 行通过类似于 TD error 的形式(A = r + γV(s') - V(s)),计算当前 t 时刻的 advantage;
- (2) 行则是把 t+1 时刻的 advantage 乘 gamma 和 gae_lambda 传递过来。
03 记录一些踩坑经历
- PPO 在收集 rollout 的时候,要在分布里采样,而非采用 argmax 动作,否则没有 exploration。(PPO 在分布里采样 action,这样来保证探索,而非使用 epsilon greedy 等机制;听说 epsilon greedy 机制是 value-based 方法用的)
- 如果 policy 网络里有(比如说)batch norm,rollout 时应该把 policy 开 eval 模式,这样就不会出错。
- (但是,不要加 batch norm,加 batch norm 性能就不好了。听说 RL 不能加 batch norm)
- minigrid 简单环境,RNN 加不加貌似都可以(?)
- 在算 entropy loss 的时候,要用真 entropy,从 Categorical 分布里得到的 entropy;不要用 -logprob 近似的,不然会导致策略分布 熵变得很小 炸掉。
RL 基础 | 如何复现 PPO,以及一些踩坑经历的更多相关文章
- TiDB 深度实践之旅--真实“踩坑”经历
美团点评 TiDB 深度实践之旅(9000 字长文 / 真实“踩坑”经历) 4 PingCAP · 154 天前 · 3956 次点击 这是一个创建于 154 天前的主题,其中的信息可能已经有所发 ...
- nginx搭建网站踩坑经历
为了更好的阅读体验,请访问我的个人博客 前言 早上刷抖音刷到一个只需要三步的nginx搭建教程(视频地址),觉得有些离谱,跟着复现了一遍,果然很多地方不严谨并且省略了大量步骤,对于很多不了解linux ...
- 『审慎』.Net4.6 Task 异步函数 比 同步函数 慢5倍 踩坑经历
异步Task简单介绍 本标题有点 哗众取宠,各位都别介意(不排除个人技术能力问题) —— 接下来:我将会用一个小Demo 把 本文思想阐述清楚. .Net 4.0 就有了 Task 函数 —— 异步编 ...
- Net4.6 Task 异步函数 比 同步函数 慢5倍 踩坑经历
Net4.6 Task 异步函数 比 同步函数 慢5倍 踩坑经历 https://www.cnblogs.com/shuxiaolong/p/DotNet_Task_BUG.html 异步Task简单 ...
- myeclipse使用db-brower连接到sqlserver2012踩坑经历
myeclipse使用db-brower连接到sqlserver踩坑经历 首先得建立个角色 右键->创建登录名 权限开大点 连接设置 Driver template选择我选这个,格式按照我的写 ...
- sqlserver安装和踩坑经历
sqlserver安装和踩坑经历 下载 下载 安装 大致是按照这个来的 安装教程 出错 windows系统安装软件弹出"Windows installer service could not ...
- Dubbo 服务 IP 注册错误踩坑经历
个人博客地址 studyidea.cn,点击查看更多原创文章 踩坑 公司最近新建一个机房,需要将现有系统同步部署到新机房,部署完成之后,两地机房同时对提供服务.系统架构如下图: 这个系统当前对外采用 ...
- 使用BeanUtils.copyProperties踩坑经历
1. 原始转换 提起对象转换,每个程序员都不陌生,比如项目中经常涉及到的DO.DTO.VO之间的转换,举个例子,假设现在有个OrderDTO,定义如下所示: public class OrderDTO ...
- 【踩坑经历】一次Asp.NET小网站部署踩坑和解决经历
2013年给1个大学的小客户部署过一个小型的Asp.NET网站,非常小,用的sqlite数据库,今年人家说要换台服务器,要重新部署一下,好吧,虽然早就过了服务时间,但无奈谁叫人家是客户了,二话不说,上 ...
- RocketMQ同一个消费者唯一Topic多个tag踩坑经历
最近做的项目的一个版本需求中,需要用到MQ,对数据记录进行异步落库,这样可以减轻数据库的压力,同时可以抗住大量的数据落库.这里需要说明一下本人用到的MQ是公司自己在阿里的RokectMQ的基础上进行封 ...
随机推荐
- Oracle数据库安装与还原
安装Oracle 11g数据库 安装数据库参考这位大佬的文章:(135条消息) Oracle 11g版本下载及安装超详细教程图解_oracle11g下载_田夜的博客-CSDN博客 非常详细 利用dmp ...
- springjdbc处理nvarchar
当我们使用spring-jdbc来做持久化时(注意不是spring-data-jbc),有时候一些特殊字符存入数据库时会用到nvarchar.nvarchar2这种类型(比如存放化学式,如CO₂等), ...
- 阿里云【七天深入MySQL实战营】
阿里云[七天深入MySQL实战营] 最近报名了阿里云[七天深入MySQL实战营].不过一直没时间看[最主要还是自己懒],看了下课程及答疑信息,感觉应该还可以,分享出来和大家一起学习学习.现在课程已经 ...
- 【渗透测试】ATT&CK靶场一,phpmyadmin,域渗透,内网横向移动攻略
前言 VulnStack,作为红日安全团队匠心打造的知识平台,其独特优势在于全面模拟了国内企业的实际业务场景,涵盖了CMS.漏洞管理及域管理等核心要素.这一设计理念源于红日安全团队对ATT&C ...
- SpringBoot系列:使用原生JDBC实现对数据库的增删改查
application.yml spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306 ...
- MyBatis——案例——查询-多条件查询-动态条件查询(关键字 if where)
动态条件查询 SQL语句会随着用户的输入或外部条件的变化而变化,我们称为 动态SQL MyBatis 对动态SQL有很强大的支撑: if choose(when,otherwise) ...
- PMP——如何区分项目启动会和开踢会?
在PMP考试中非常强调两个重要会议,一个叫做启动会(Initiating Meeting),另一个叫做开踢会议(Kick-off Meeting),俗称两会. 项目启动会的作用是通过发布项目章程来授权 ...
- [namespace hdk] diff.h
Example cth.txt 12345 54321 114514 hdk.txt 12345 54321 114514 #include"diff.h" using names ...
- Dockerfile定制镜像(FROM?RUN ?WORKDIR ?ADD & COPY指令)(七)
一.Dockerfile 镜像的定制实际上就是定制镜像的每一层所添加的配置.文件等信息,实际上当我们在一个容器中添加或者修改了一些文件后,我们可以通过docker commit命令来生成一个新的镜像, ...
- USB协议详解第3讲(USB描述符-设备描述符)
我们第一个学习要点就是USB描述符,所谓描述符其实就是C语言里面的结构体或者数组,数组包含的信息说明当前的设备具有哪些特征.USB描述符有设备描述符.配置描述符.接口描述符.端点描述符.字符串描述符, ...