点击左上方蓝字关注我们
2019年Dota 2比赛中,OpenAI Five完胜世界冠队伍OG。
Bill Gates在Twitter中提到:“AI机器人在Dota 2中战胜人类,是AI发展的一个里程碑事件”。
无论AlphaGo击败围棋世界冠军还是OpenAI Five完胜世界冠队伍OG,都让大众感受到了AI的魅力,促进了AI蓬勃发展。
那么OpenAI Five应用了什么技术,实现在Dota中完胜世界冠军的呢?那就是Proximal Policy Optimization(PPO)算法,本文重点介绍基于飞桨PARL,完成PPO算法实践的过程。
飞桨PARL开源项目地址:
https://github.com/PaddlePaddle/PARL
PPO算法将游戏中所有的状态作为数据输入,通过数据分析计算,由PPO优化的智能体Agent做出相应的动作,智能体Agent的思考过程如下图所示。
在介绍PPO算法之前,我们先了解深度强化学习中基于策略梯度的Policy Gradient算法(以下简称PG)。
PG算法介绍
PG算法的基本组成如下图所示:
  • Actor:由智能体Agent产生的执行动作;
  • Env:智能体Agent的执行环境;
  • Reward Function:奖励计算方式,执行动作的评价指标。
智能体Agent在Env中不断学习,根据环境的状态State(也可以为观察到的observation,下文统一用State)来执行动作Action,过程中会根据反馈的Reward来选择效果更好的动作,实现逻辑如下图所示:
  • 采用神经网络拟合策略函数,通过计算策略梯度来优化策略网络;
  • 通过环境产生的状态State矩阵或者向量Vector作为神经网络的输入,通过神经网络得到每个执行动作的概率,选择概率最大的执行动作。
计算原理如下图所示:
  • 在环境Env中得到状态State,Agent通过State得出使得Reward最大的执行动作Actor;
  • Actor在环境中又得到新的State(next state) ;
  • 重复以上动作,直到Reward Fuction不成立或者达到这次循环终止条件。此时可以计算出一个轨迹的发生概率(一个episode的发生概率)。
在优化神经网络时,除了需要向网络输入数据外,还要给网络一个期望的输出(label)。
优化的目标是策略的期望回报,即所有轨迹的回报与对应轨迹发生概率的加权和。当N足够大时,通过采样N个轨迹求平均的方式近似表达,简单理解就是所得Reward的平均值。每次都让网络来拟合之前Reward的平均值,通常优化策略越好,Reward会越高,不过也不是绝对的
计算策略梯度公式如下图所示:
首先用函数求导的特点进行转化;然后用N次采样的平均值来近似期望;最后将展开,将与无关的项去掉,即得到最终的结果。
关于PG的经验之谈
PG是基于策略梯度求解RL的方法,按照概率分布随机选择动作,计算某个状态下所有策略的分布概率,类似于经典分类问题中,给每个类别预测一个概率,好的PG会给优良的策略分配较高的概率。
但它同时也存在一个缺点:优化是在一个完整的episode结束后,sample每一步的均值。只要最后结果很好,所有的动作全部当作好的动作来学习,因此在训练时很可能出现不稳定的情况,需要大量的数据集,保证足够多的sample次数。
Tip 1:增加基线,使得Reward有正有负。
Tip 2:分配适当的权重,选择在这个动作发生后的Reward总和作为动作的权重。
Tip 3:增加折扣因子。
PPO算法介绍
先了解强化学习常用的两种训练方式:On-policy和Off-policy。
  • On-policy:训练的agent一边互动一边学习(互动的agent就是训练的agent);
  • Off-policy:训练的agent一边看一边学习(互动的agent不是训练的agent)。
举例来说,古代杰出帝王都需要深入了解民间百姓的真实生活,这两种策略就类似帝王们获取信息的途径。皇帝可以选择微服出巡(On-policy),虽然眼见为实,但毕竟皇帝本人分身乏术,掌握情况不全;也可以选择派不同的官员去了解情况,再向皇帝汇报(Off-policy)。
在网络训练过程中,通过采样更新权重非常耗时,每更新一个参数就需要采样一次,因此我们需要把On-policy训练方式转化成Off-policy。
从中采样出来的数据的期望值,可以替换为用采样出来的数据再乘以重要性权重。
值得注意的是,需要分布与分布差异很小才能采用这样的方式,如图所示。若当分布与分布相差较大时,需要足够多采样次数才能使他们的期望相近。
的期望和的期望最后相差的就是一个的系数(重要性权重),如果≈,那么他们的期望就相同(这里省略推导过程)。
PPO解决了On-policy转Off-policy时分布与分布相差较大的问题。通过KL散度来计算分布与分布差异,并将KL加入PPO模型的似然函数,并采用合理适配β来惩罚KL。KL过大我们就增加β,KL小于一定值我们就减小β,PPO算法过程如下图所示:
说明:在概率论或信息论中,通常使用KL散度( Kullback–Leibler divergence),又称相对熵(relative entropy),描述两个概率分布和差异。
Talk is cheap. Show me the code.
基于飞桨PARL实践PPO算法
下面我们基于飞桨PARL框架,动手实践PPO算法,介绍实现CarPole和四轴飞行器悬浮任务的操作过程。
项目可在百度AI Studio平台上运行:
https://aistudio.baidu.com/aistudio/projectdetail/632270

实践一:CartPole任务

基于飞桨PARL,使用PPO解决连续控制版本的CartPole问题,给小车一个力(连续量),使得车上的摆杆倒立起来。
1. 构建CartPole任务的Model、Algorithm和Agent。
构建PPO Model,需要声明Policy,定义Value的Model结构:
classPolicyModel(parl.Model):def__init__(self, obs_dim, act_dim, init_logvar)
:
self
.obs_dim = obs_dim
self
.act_dim = act_dim
        hid1_size = obs_dim * 
10
        hid3_size = act_dim * 
10
        hid2_size = int(np.sqrt(hid1_size * hid3_size))
self
.lr = 
9
e-
4
 / np.sqrt(hid2_size)
self
.fc1 = layers.fc(size=hid1_size, act=
'tanh'
)
self
.fc2 = layers.fc(size=hid2_size, act=
'tanh'
)
self
.fc3 = layers.fc(size=hid3_size, act=
'tanh'
)
self
.fc4 = layers.fc(size=act_dim, act=
'tanh'
)
self
.logvars = layers.create_parameter(
            shape=[act_dim],
            dtype=
'float32'
            default_initializer=fluid.initializer.ConstantInitializer(
                init_logvar))
defpolicy(self, obs)
:      
#策略
        hid1 = 
self
.fc1(obs)
        hid2 = 
self
.fc2(hid1)
        hid3 = 
self
.fc3(hid2)
        means = 
self
.fc4(hid3)
        logvars = 
self
.logvars()
return
 means, logvars
defsample(self, obs)
:       
#采样
        means, logvars = 
self
.policy(obs)
        sampled_act = means + (
            layers.exp(logvars / 
2.0
) *  
# stddev
            layers.gaussian_random(shape=(
self
.act_dim, ), dtype=
'float32'
))
return
 sampled_act
classValueModel(parl.Model): def__init__(self, obs_dim, act_dim)
:
super
(ValueModel, 
self
).__init_
_
()
        hid1_size = obs_dim * 
10
        hid3_size = 
5
        hid2_size = int(np.sqrt(hid1_size * hid3_size))
self
.lr = 
1
e-
2
 / np.sqrt(hid2_size)
self
.fc1 = layers.fc(size=hid1_size, act=
'tanh'
)
self
.fc2 = layers.fc(size=hid2_size, act=
'tanh'
)
self
.fc3 = layers.fc(size=hid3_size, act=
'tanh'
)
self
.fc4 = layers.fc(size=
1
)
defvalue(self, obs)
:
        hid1 = 
self
.fc1(obs)
        hid2 = 
self
.fc2(hid1)
        hid3 = 
self
.fc3(hid2)
        V = 
self
.fc4(hid3)
        V = layers.squeeze(V, axes=[])
return
 V

构建Agent:
classPPOAgent(parl.Agent):def__init__(self,algorithm,obs_dim,act_dim,kl_targ,loss_type,beta=1.0,epsilon=0.2,policy_learn_times=20,value_learn_times=10,value_batch_size=256)
:
        参数初始化(略)
defbuild_program(self)
:      
#在静态图下构建program,定义图的输入和输出self
.policy_predict_program = fluid.Program()
self
.policy_sample_program = fluid.Program()
self
.policy_learn_program = fluid.Program()
self
.value_predict_program = fluid.Program()
self
.value_learn_program = fluid.Program()
defpolicy_sample(self, obs)
:   
#通过网络推理得到samplereturn
 sampled_act
defpolicy_predict(self, obs)
:
#通过网络推理得到predictreturn
 means
defvalue_predict(self, obs)
:
#通过网络推理得到value_predictreturn
 value
#用ppo算法更新policydefpolicy_learn(self, obs, actions, advantages)
:
self
.alg.sync_old_policy()
        all_loss, all_kl = [], []
for_in
 range(
self
.policy_learn_times):
            loss, kl = 
self
._batch_policy_learn(obs, actions, advantages)
            all_loss.append(loss)
            all_kl.append(kl)
ifself
.loss_type == 
'KLPEN'
:
# Adative KL penalty coefficientif
 kl > 
self
.kl_targ * 
2
:
self
.beta = 
1.5
 * 
self
.beta
            elif kl < 
self
.kl_targ / 
2
:
self
.beta = 
self
.beta / 
1.5return
 np.mean(all_loss), np.mean(all_kl)
#用ppo算法更新valuedefvalue_learn(self, obs, value)
:
        data_size = obs.shape[
0
]
ifself
.value_learn_buffer is 
None:
            obs_train, value_train = obs, value
else:
            obs_train = np.concatenate([obs, 
self
.value_learn_buffer[
0
]])
            value_train = np.concatenate([value, 
self
.value_learn_buffer[
1
]])
self
.value_learn_buffer = (obs, value)
        all_loss = []
for_in
 range(
self
.value_learn_times):
            random_ids = np.arange(obs_train.shape[
0
])
            np.random.shuffle(random_ids)
            shuffle_obs_train = obs_train[random_ids]
            shuffle_value_train = value_train[random_ids]
            start = 
0while
 start < 
data_size:end
 = start + 
self
.value_batch_size
                value_loss = 
self
._batch_value_learn(
                    shuffle_obs_train[
start:end
, 
:
],
                    shuffle_value_train[
start:end
])
                all_loss.append(value_loss)
                start += 
self
.value_batch_size
return
 np.mean(all_loss)
构建PPO Algorithm,此段代码无需运行,便于大家理解PPO整个过程:
classPPO(Algorithm):def__init__(self,model,act_dim=None,policy_lr=None,value_lr=None,epsilon=0.2)
:
        模型初始化略()
def_calc_logprob(self, actions, means, logvars)
:
        exp_item = layers.elementwise_div(
            layers.square(actions - means), layers.exp(logvars), axis=
1
)
        exp_item = -
0
.
5
 * layers.reduce_sum(exp_item, dim=
1
)
        vars_item = -
0
.
5
 * layers.reduce_sum(logvars)
        logprob = exp_item + vars_item
return
 logprob
#计算KLdef_calc_kl(self, means, logvars, old_means, old_logvars)
:
        log_det_cov_old = layers.reduce_sum(old_logvars)
        log_det_cov_new = layers.reduce_sum(logvars)
        tr_old_new = layers.reduce_sum(layers.exp(old_logvars - logvars))
        kl = 
0
.
5
 * (layers.reduce_sum(
            layers.square(means - old_means) / layers.exp(logvars), dim=
1
) + (
                log_det_cov_new - log_det_cov_old) + tr_old_new - 
self
.act_dim)
return
 kl
defpolicy_learn(self, obs, actions, advantages, beta=None)
:
        old_means, old_logvars = 
self
.old_policy_model.policy(obs)
        old_means.stop_gradient = True
        old_logvars.stop_gradient = True
        old_logprob = 
self
._calc_logprob(actions, old_means, old_logvars)
        means, logvars = 
self
.model.policy(obs)
        logprob = 
self
._calc_logprob(actions, means, logvars)
        kl = 
self
._calc_kl(means, logvars, old_means, old_logvars)
        kl = layers.reduce_mean(kl)
2. 模型训练
def 
main
():

    env 
= ContinuousCartPoleEnv()
    obs_dim = env.observation_space.shape[
0
]
    act_dim = env.action_space.shape[
0
]
    obs_dim += 
1# add 1 to obs dim for time step feature  应该是为了方便引入衰减因子
    scaler = Scaler(obs_dim)
    model = PPOModel(obs_dim, act_dim)
    alg = parl.algorithms.PPO(
        model,
        act_dim=act_dim,
        policy_lr=model.policy_lr,
        value_lr=model.value_lr)
    agent = PPOAgent(
        alg, obs_dim, act_dim, kl_targ, loss_type=loss_type)
# 运行几个episode来初始化 scaler
    collect_trajectories(env, agent, scaler, episodes=
5
)
    test_flag = 
0
    total_steps = 
0while
 total_steps < train_total_steps:
        trajectories = collect_trajectories(
            env, agent, scaler, episodes=episodes_per_batch)
        total_steps += sum([t[
'obs'
].shape[
0
for
 t 
in
 trajectories])
        total_train_rewards = sum([np.sum(t[
'rewards'
]) 
for
 t 
in
 trajectories])
#产生训练数据
        train_obs, train_actions, train_advantages, train_discount_sum_rewards = build_train_data(
            trajectories, agent)
#计算policy_loss, kl
        policy_loss, kl = agent.policy_learn(train_obs, train_actions,
                                             train_advantages)
        value_loss = agent.value_learn(train_obs, train_discount_sum_rewards)
if
 total_steps 
// test_every_steps >= test_flag:while
 total_steps 
// test_every_steps >= test_flag:
                test_flag += 
1
            eval_reward = run_evaluate_episode(env, agent, scaler)
[
07-23 12:00:44 MainThread @<ipython-input-7-710321de7941>:188
] Steps 
1001984
, Evaluate reward: 
23285.0
3. 实践效果
在CartPole环境下, Evaluate reward一直在上涨,达到了23285.0(训练过多可能会出现不稳定的情况)。
完整项目地址:
https://aistudio.baidu.com/aistudio/projectdetail/632270
实践二:四轴飞行器悬浮任务。
基于飞桨PARL,使用PPO实现四轴飞行器悬浮任务。您只需要获取如下文件,并在终端安装parl和rlschool后,运行python train.py,即可查看实践效果。
  • mujoco_model.py
  • mujoco_agent.py
  • scaler.py
  • train.py
.py文件获取路径:
https://aistudio.baidu.com/aistudio/projectdetail/632270
实践效果
在rlschool环境下 四轴飞行器悬浮任务 Evaluate reward一直在上涨,达到了7107。
完整项目地址:
https://aistudio.baidu.com/aistudio/projectdetail/632270
文章小结
  1. 在文章开篇和大家一起学习了PG(Policy Gradient)算法是如何解决连续动作空间上求解RL, PG在更新策略时的优缺点及相应的避“坑儿”技巧,如:增加基线、合理适配action的权重、增加代价因子。
  2. 接下来介绍了强化学习常用的两种模型训练方式:On-policy与Off-policy,并引出PPO算法。
  3. 最后基于飞桨PARL框架,动手实践了PPO算法,完成了CarPole和四轴飞行器悬浮两个任务。
如果您想详细了解更多飞桨的相关内容,请参阅以下文档。
官网地址:
https://www.paddlepaddle.org.cn
飞桨PARL开源项目地址:
https://github.com/PaddlePaddle/PARL
飞桨开源框架项目地址:
GitHub:
https://github.com/PaddlePaddle/Paddle
Gitee: 
https://gitee.com/paddlepaddle/Paddle
END
继续阅读
阅读原文