强化学习中的调参经验与编程技巧(on policy 篇)

在强化学习的训练过程中,常常会遇见以下问题
在某一环境中可以work的超参数拿去训练别的环境却训练不出来
训练时熵在增大
训练动作达到边界
本文通过调试几个环境的案例来探究强化学习的调参方法

1 pendulum

摆锤这个环境可以看做连续控制中的入门环境了,环境初始时在一个随机的位置,目标是将其摆动以使其保持直立,它的状态维度为3,动作维度为1

拟使用PPO解决这个问题,ppo的流程如下:

  1. 使用Actor网络与环境交互一定步数,记录下(state, action, reward, v, done)
  2. 根据记录下来的值计算优势值adv(更新actor网络使用)和v_target(更新critic网络使用)
  3. 计算loss更新actor网络和critic网络
  • 首先说第一步,在和环境交互的过程中,我们往往规定了步数,在规定的step内,环境往往没有done,这会给我们这一次迭代计算adv有误差,面对这个问题,往往有两种处理方式:
    • 完成这次交互,也就是超过这一次规定的迭代步数直到done,这样做会使每一次迭代更新时的交互step不同,比较不同算法在相同的step性能如何时略显不公平
    • 不完成这次交互,这样会使最后step采用gae对adv估值存在近似。

在John Schulman's 程序中,对V估值采用这种方式:

    V(s_t+1) = {0 if s_t is terminal         
               {v_s_{t+1} if s_t not terminal and t != T (last step)         
               {v_s if s_t not terminal and t == T

也就是最后一个step如果不是终止状态,则它下一状态的V估值为当前状态的V估值
在有的程序中,也采用V神经网络下一状态的值作为对下一状态的V函数估值

    • 第二步流程中计算v_target会根据是否采用gae有两种计算方式
      • 根据每一step的reward按照gamma return的方式计算v_target
      • 根据每一step的adv和v估值累加作为v_target

  • 第三步中loss计算包含有aloss,vloss和entropy

1.1 初始

我们先使用简单的PPO来训练一下环境,参数选择如下:

  • actor,critic 网络初始化为正交初始化
  • steps=2048;
  • batch=64;
  • lr=3e-4且经过训练迭代数逐渐减小;
lam = lambda f: 1 - f / train_steps
self.opti_scheduler = torch.optim.lr_scheduler.LambdaLR(self.opti, lr_lambda=lam)
  • 采用return方式计算v_target;
  • adv计算采用gae
  • loss 计算添加熵,系数(self.c_en)为0.01
loss = aloss - loss_entropy*self.c_en + v_loss*self.c_vf
  • max_grad_norm=0.5
torch.nn.utils.clip_grad_norm_(self.critic.parameters(), self.max_grad_norm)
torch.nn.utils.clip_grad_norm_(self.actor.parameters(), self.max_grad_norm)

这些都是比较常规的PPO参数设置,进行1000迭代后(2048*1000 step)reward变化如下:

算法并没有很好的学习,reward在100 iter以内还有上升趋势,100iter时突然下降,之后就再也起不来。

我们来看一下学习过程中各个诊断量变化情况

vloss一开始值很大,接着骤降,之后一直处于比较高的水平

entropy的变化幅度过快,最终值小于0。这里简单提一下在连续密度分布中,熵值可能小于0,拿高斯分布举例,如果其sigma过小,均值点处的密度概率可以远大于1,熵值也为负数。综合来看,熵值出现小于0一般为Actor网络更新时sigma参数过小,可能是actor更新过快的原因。

1.2 clip V

为了让critic更新更合适,一般程序中采用clipv的技巧,防止更新前后V差距过大,对其进行惩罚,程序代码如下:

clip_v = oldv + torch.clamp(v - oldv, -self.epsilon, self.epsilon)
v_max = torch.max(((v - v_target) ** 2), ((clip_v - v_target) ** 2))
v_loss = v_max.mean()

同时程序中采用gae方式计算v_target

self.v_target = self.adv + self.v

进行1000迭代后(2048*1000 step)reward变化如下:

reward 最终能呈上升趋势最终达到一个不错的值,但美中不足在于中间出现两次波折

vloss最终也能收敛到较小的值,但和reward类似在相同的地方出现了波折

熵值的下降显得平稳多了

观察kl散度变化,发现类似的地方出现kl散度过大的现象

ppo在一次迭代中使用同一批数据进行策略更新,要求策略变化不能过大,不然重要性采样就不再适用,所以在ppo的策略更新中采用了裁剪的技巧,但事实上即使这个技巧也不能保证限制kl散度大小,论文IMPLEMENTATION MATTERS IN DEEP POLICY GRADIENTS: A CASE STUDY ON PPO AND TRPO也指出裁剪没有起到真正作用。

1.3 kl early stop

为了防止kl散度过大,我们设置一个最大kl值,在每次迭代中当达到这个最大kl就停止这次迭代,继续下次采样数据,这里我们设置kl_max=0.03

继续进行1000迭代后(2048*1000 step)reward变化如下:

似乎是有了一定改善,但中间还有一次波动

看kl散度也比较平均

1.4 normalization

考虑到state,reward我们还没有标准化,起初的vloss也比较大,我们决定这次从这里入手

state和reward都是在交互过程中产生的,我们无法在预先知道其平均值和方差,于是我们采用运行时均值和方差作为近似代替

对每个state减去均值并除以标准差

x = self.pre_filter(x)
if update:
    self.rs.push(x)
if self.demean:
    x = x - self.rs.mean
if self.destd:
    x = x / (self.rs.std + 1e-8)
if self.clip:
    x = np.clip(x, -self.clip, self.clip)

对reward我们只除以标准差

x = self.pre_filter(x)
self.ret = self.ret*self.gamma + x
if update:
    self.rs.push(self.ret)
    x = x/(self.rs.std + 1e-8)
if self.clip:
    x = np.clip(x, -self.clip, self.clip)
return x

继续进行1000迭代后(2048*1000 step)reward变化如下:

终于,reward变得比较平稳了。

用最后的结果总结一下,如果reward长时间不能很好的上升,需要即时诊断其他重要变量变化情况,一般情况下vloss先下降再稍微上升最终平稳,entropy的曲线则是下降最终平稳,不能太快也不能上升,kl散度变化不能过大。

2.mujoco

用我们以上学到的经验去调试mujoco中的halfcheetah,hopper和walker2d

这里主要调节每次early_stop 的max_kl,采样3个随机种子完成实验

在halfcheetah环境中,目标kl0.07稳定性最差,可以看出在其他参数保持不变时,0.07的限制依然导致每次策略更新时幅度过大,整体效果不够稳定

在hopper环境中,依然是kl0.07的限制最不稳定

在walker2d环境中,kl0.07的效果却是最好的,这也说明在不同的任务环境中,超参数的选择也是不同的。

这些结果的表现来看也都达到或超过部分论文上ppo的效果了,如果想试试调节超参数的可以看看

如果你还不太清楚如何用seaborn绘制强化学习训练图,可以参考这篇

3. deepmind control suite

dmc是谷歌开发的强化学习环境套件(基于物理控制),和mujoco有类似的场景,但丰富了其任务设置,同时也提高了难度。

dmc有相应的gym接口库,安装过dmc2gym后即可通过下面方式使用

env = dmc2gym.make(
    domain_name=args.domain_name,
    task_name=args.task_name,
    seed=args.seed,
    visualize_reward=False,
    from_pixels=(args.encoder_type == 'pixel'),
    height=args.image_size,
    width=args.image_size,
    frame_skip=args.action_repeat
)

dmc的状态输入有普通的state也有基于图片的pixel,这里先用普通的state测试

使用cheetah run作为任务环境

先使用mujoco训练时使用的超参数,reward如下:

reward结果极其不稳定,最终也没有达到比较好的结果

entropy在训练过程中由原来的8左右逐渐增大,这在以前的实验中都没有遇见

查看Actor网络动作std的变化情况,由一开始设置的1越变越大,也正是如此导致了entropy的不降反升

在ppo的loss中熵项的存在确实是希望动作随机保持探索,但最终entropy越来越大,也体现出ppo策略网络的不自信,我们考虑将entropy的系数变小。

试试系数为0的效果

reward有比较好的上升效果了

熵也能正常的下降了

比较这两次的实际运行情况

可以看出第一次后期翻车了,第二次还是能比较不错的跑下去。

大家也可以试试熵前面系数0~0.01之间其他值,

尝试总结一下,虽然dmc中的cheetah-run和mujoco的halfcheetah有类似的模型和动态转移,一开始的动作熵也在8左右,但dmc用同样的超参数熵就会上升,可能在于两者的reward不同,dmc只有速度reward,mujoco还加上了控制reward.

如果后面有时间的话还会补充上dmc pixel状态和Atari的调参过程,全部程序在

欢迎点赞star和交流。

参考资料
The 32 Implementation Details of Proximal Policy Optimization (PPO) Algorithm costa.sh/blog-the-32-im
IMPLEMENTATION MATTERS IN DEEP POLICY GRADIENTS: A CASE STUDY ON PPO AND TRPO。ICLR2020