基于飞桨复现强化学习进阶算法SAC,让月球着陆器顺利着陆

飞桨开发者说】秦浩然,沈阳人,毕业于东北大学。强化学习技术爱好者。传统软件开发领域的前浪,AI领域的后浪。php

提及强化学习的入门,不知道你们是否也是从Sarsa、Q-learning开始,到DQN,再到Policy Gradient,最后到DDPG,一步步走进了强化学习的世界。在学习了这些基础算法以后,今天咱们就一块儿来了解一下进阶算法SAC,而且看一看如何利用飞桨的PARL强化学习框架方便地把SAC应用到GYM Box2D的月球着陆器环境(LunarLanderContinuous-v2任务)当中去。让咱们的月球着陆器能够适应各类状况,顺利着陆。python

本文主要包括如下三部份内容:git

  • SAC算法论文简介github

  • SAC算法样例代码简介算法

  • 介绍如何用SAC算法玩转月球着陆器网络

下载安装命令

## CPU版本安装命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安装命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

SAC算法论文

 

SAC是Soft Actor-Critic的缩写,由伯克利人工智能研究实验室(BAIR)的Tuomas Haarnoja等人,提出于2018年。原文连接:框架

https://arxiv.org/abs/1801.01290函数

不知道读完了论文的同窗有没有同感,就是SAC能够大体当作是DDPG的加强版。那么论文为何想要去加强DDPG呢?性能

问题一:为何须要加强DDPG?学习

论文认为,有两大因素使深度强化学习的实际应用变得困难:

  • 很是高的采样复杂度(very high sample complexity)

  • 脆弱的收敛性质(brittle convergence properties)

为了克服这两个困难,论文提出了SAC,一个基于最大熵强化学习框架的off-policy actor-critic深度强化学习算法。(soft actor-critic, an offpolicy actor-critic deep RL algorithm based on the maximum entropy reinforcement learning framework)

问题二:如何加强DDPG

在讨论这个问题以前,咱们先来讲说什么是最大熵?在DDPG算法中,咱们是以使reward最大化为目标的。而在SAC中,在使reward最大化的同时,也要最大化策略分布的熵。这就是最大熵。

有的同窗可能要问了,到底什么是熵呢?此处咱们不说热力学熵,也不说信息熵。这里的熵,我的理解,是用来衡量分布的随机程度的。分布越随机,熵越大。那么Actor把最大化熵也做为优化目标之一,也就意味着,要使策略分布的随机性也最大化。策略分布得更随机了,算法的稳定性和探索性也就随之加强了。

问题三:加强了什么?

那么,加入熵最大化到底加强了什么呢。我的理解,就是使得决策的分布不要像DDPG同样,趋于集中到一个最优解。而是但愿同时存在更多的,一样优的策略。这样能够大幅加强鲁棒性,以适应各类各样的环境。

解决了这三个问题,咱们已经对SAC有了必定的理解。接下来,咱们来看一看论文的实验数据,来感觉一下SAC的强大。论文给出SAC以及另外几个主流深度强化学习算法,在六个强化学习任务Benchmark中的训练曲线,图中黄色表明SAC。

从图中的训练曲线来看,SAC在难度各异的几个任务中都表现出了良好的稳定性(黄色阴影部分较窄,且集中于实线附近)。在Hopper-v1,HalfCheetah-v1,Ant-v1,Humanoid(rllab)中,SAC最终的return明显高于其余算法,尤为是在最为复杂的控制空间多达21维的Humanoid (rllab) 中,一骑绝尘,表现出了明显的优点。

SAC算法样例

SAC算法这么好,实现起来会不会很麻烦呢?一块儿看一看百度飞桨开源深度学习平台PaddlePaddle飞桨深度强化学习框架PARL中的SAC样例。

样例代码连接,GitHub中基于PARL的SAC样例:

https://github.com/PaddlePaddle/PARL/tree/develop/examples/SAC

(GitHub访问困难的同窗能够本身搜索一下Gitee,也能够在example目录下找到的)

 

PARL框架的结构大体如上图所示,采用了层层嵌套的结构,适用于绝大多数强化学习算法。具体到SAC算法的话,最内层Model封装了Q网络与策略网络的网络结构,经过value()与policy()两个方法输出Q值和动做值。向外一层,Algorithm主要封装了损失函数,经过predict()向外输出动做值,经过learn()向内更新Model的网络权重。最外层的Agent主要负责与环境的交互并把从环境获得的数据喂给Algorithm。

具体实现上,由于使用了PARL框架的结构和封装好的算法,整个实现显得很整洁。共分为三个文件:

  • mujoco_agent.py

  • mujoco_model.py

  • train.py

先看看mujoco_model.py,这里封装了两个Model类,来实现策略网络和Q网络,也就是SAC中的A与C(Actor与Critic)。两个类都继承了PARL的Model。ActorModel中定义了Actor的网络结构(策略网络),实现了policy方法。这个方法根据输入obs也就是当前外部环境状态,经过内部的策略网络,来输出动做值。

class ActorModel(parl.Model):
    def __init__(self, act_dim):
        hid1_size = 400
        hid2_size = 300
        self.fc1 = layers.fc(size=hid1_size, act='relu')
        self.fc2 = layers.fc(size=hid2_size, act='relu')
        self.mean_linear = layers.fc(size=act_dim)
        self.log_std_linear = layers.fc(size=act_dim)
    def policy(self, obs):
        hid1 = self.fc1(obs)
        hid2 = self.fc2(hid1)
        means = self.mean_linear(hid2)
        log_std = self.log_std_linear(hid2)
        log_std = layers.clip(log_std, min=LOG_SIG_MIN, max=LOG_SIG_MAX)
        return means, log_std

CriticModel中定义了Critic的网络结构(Q网络)而且实现了value方法。这个方法根据输入obs和策略网络输出的动做值,经过内部的Q网络,来输出Q值。

class CriticModel(parl.Model):
    def __init__(self):
        hid1_size = 400
        hid2_size = 300
        self.fc1 = layers.fc(size=hid1_size, act='relu')
        self.fc2 = layers.fc(size=hid2_size, act='relu')
        self.fc3 = layers.fc(size=1, act=None)
        self.fc4 = layers.fc(size=hid1_size, act='relu')
        self.fc5 = layers.fc(size=hid2_size, act='relu')
        self.fc6 = layers.fc(size=1, act=None)
    def value(self, obs, act):
        hid1 = self.fc1(obs)
        concat1 = layers.concat([hid1, act], axis=1)
        Q1 = self.fc2(concat1)
        Q1 = self.fc3(Q1)
        Q1 = layers.squeeze(Q1, axes=[1])
        hid2 = self.fc4(obs)
        concat2 = layers.concat([hid2, act], axis=1)
        Q2 = self.fc5(concat2)
        Q2 = self.fc6(Q2)
        Q2 = layers.squeeze(Q2, axes=[1])
        return Q1, Q2

再看看mujoco_agent.py,这里封装了MujocoAgent类,这个类继承了PARL的Agent。主要实现了predict,sample,learn三大功能。predict用来根据从环境取得的数据也就是环境状态来输出一个动做值。sample也一样根据环境状态输出动做值,不一样的是,这个方法还有必定的几率输出一个随机的动做值,用来探索新的动做。learn则实现了根据环境状态、动做值和回报值等数据来优化model内部网络参数的功能。

class MujocoAgent(parl.Agent):
    def predict(self, obs):
        obs = np.expand_dims(obs, axis=0)
        act = self.fluid_executor.run(
            self.pred_program, feed={'obs': obs},
            fetch_list=[self.pred_act])[0]
        return act

    def sample(self, obs):
        obs = np.expand_dims(obs, axis=0)
        act = self.fluid_executor.run(
            self.sample_program,
            feed={'obs': obs},
            fetch_list=[self.sample_act])[0]
        return act

    def learn(self, obs, act, reward, next_obs, terminal):
        feed = {
            'obs': obs,
            'act': act,
            'reward': reward,
            'next_obs': next_obs,
            'terminal': terminal
        }
        [critic_cost, actor_cost] = self.fluid_executor.run(
            self.learn_program,
            feed=feed,
            fetch_list=[self.critic_cost, self.actor_cost])
        self.alg.sync_target()
        return critic_cost[0], actor_cost[0]

最后看看train.py,这里就是训练脚本了。实例化actor和critic后,使用PARL封装好的SAC算法,层层嵌套最后获得一个能够和环境交互的agent实例:

    actor = ActorModel(act_dim)
    critic = CriticModel()
    algorithm = parl.algorithms.SAC(
        actor,
        critic,
        max_action=max_action,
        gamma=GAMMA,
        tau=TAU,
        actor_lr=ACTOR_LR,
        critic_lr=CRITIC_LR)
    agent = MujocoAgent(algorithm, obs_dim, act_dim)

能够直接用PARL内置的ReplayMemory类实现经验回放,而且给从PARL导入的ReplayMemory设置参数。

    from parl.utils import ReplayMemory
    rpm = ReplayMemory(MEMORY_SIZE, obs_dim, act_dim)

以上设置OK以后,直接启动 python train.py 就能够愉快的开始训练了。

篇幅有限这里只说说简要思路,有兴趣的同窗能够直接去看看源码。

 

SAC算法实践

 

最后,咱们经过实践,来看一看SAC算法在GYM Box2D的LunarLanderContinuous-v2任务中的表现。

一样基于PARL框架,代码也十分简洁。与样例相比,主要在训练脚本中改动了两处。

首先引入gym库

import gym

而后,在main中建立月球着陆器环境

env = gym.make('LunarLanderContinuous-v2')

另外,为了可视化训练过程,咱们还加入了reward保存与读取,以及可视化的代码

np.save('train_step_list', train_step_list)
np.save('train_reward_list', train_reward_list)
np.save('evaluate_step_list', evaluate_step_list)
np.save('evaluate_reward_list', evaluate_reward_list)

在Notebook文件中加入下面语句,输出可视化就作好了。 

train_step_list = np.load('train_step_list.npy')
train_reward_list = np.load('train_reward_list.npy')

plt.figure()
plt.title('train reward')
plt.xlabel('step')
plt.ylabel('reward')
plt.plot(train_step_list, train_reward_list)
plt.grid()
plt.show()

在GPU上通过几个小时的训练,咱们就能够获得可视化输出以下所示。

从训练与评估的输出来看,收敛良好,SAC算法的优点在这个实践中得以完美体现。

全文回顾

首先,咱们总结了论文的主要内容,分析了SAC算法提出的目的,原理和做用。算法经过引入最大熵使得决策分布趋于多样化,从而适应于更为复杂的实际应用,并取得更好的应用效果。

其次,介绍了PARL框架给出的SAC算法样例概要。得益于PARL框架的强大,样例代码显得简洁明晰,易读易上手。

最后,实践环节,了解了这个框架在LunarLanderContinuous-v2环境中的应用。在SAC算法的帮助下,咱们的登月舱得以平稳准确地着陆于指定位置。而且在训练过程当中表现出了快速收敛和稳定输出的良好性能。

实践代码连接:

https://aistudio.baidu.com/aistudio/projectdetail/888258

想具体了解如何简洁地实现SAC算法吗?想亲手训练并玩转月球着陆器吗?那就点击上面的连接查看开源代码,而后Fork并运行一下吧。

下载安装命令

## CPU版本安装命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安装命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu
相关文章
相关标签/搜索