赞
踩
PPO (Proximal Policy Optimization)
PPO 是基于 AC 架构的,也就是说,PPO 也有两个网络,分别是Actor和Critic,解决了连续动作空间的问题。
我离散动作就像一个个的按钮,按一个按钮就智能体就做一个动作。就像在 CartPole 游戏里的智能体,只有 0, 1 两个动作分别代表向左走,向右走。
那什么是连续动作呢。这就相当于这些按钮不但有开关的概念,而日还有力度大小的概念。就像我们开车,不但是前进后退转弯,并目要控制油门踩多深,刹车踩多少,转夸时候转向转多少的问题。
也可以是我们先假定策略分布函数服从一个特殊的分布,比如正太分布,我们的神经网络可以直接输出 mu 和sigma, 就能获得整个策略的概率密度函数了。
现在我们已经有概率密度函数,那么当我们要按概率选出一个动作时,就只需要按照整个密度函数抽样出来就可以了。
例如PG,就是一个在线策略。因为 PG 用于产生数据的策略(行为策略) ,和需要更新的策略(目标策略)是一致。
而DQN则是一个离线策略。我们会让智能体在环境互动一定次数,获得数据。用这些数据优化策略后,继续跑新的数据。但老版本的数据我们仍然是可以用的。也就是说,我们产生数据的策略,和要更新的目标策略不是同一个策略。
假设,我们已知在同一个环境下,有两个动作可以选择。现在两个策略,分别是P和B:
P:[0.5, 0.5] B:[0.1, 0.9]
现在我们按照两个策略,进行采样;也就是分别按照这两个策略,以S 状态下出发,与环境进行 10 次互动。获得如图数据。那么,我们可以用B 策略下获得的数据,更新P吗?
答案是不行,但大家可以从如上图看到,如果用行动策略 B[0.1,0.9]产出的数据,对目标策略P进行更新,动作1会被更新 1次,而动作2会更新9次。
那么,PPO 是怎样做到离线更新策略的呢? 答案是important-sampling, 重要性采样技术。
如果我们想用 策略B抽样出来的数据,来更新策路P 也不是不可以。但我们要把 TD_error 乘以一个重要性权重 (IW: importance weight)
重要性权重:IW=P(a)/B(a),应用在 PPO,就是目标策略出现动作a的概率 除以 行为策略出现a的概率。
回到我们之前的例子,我们可以计算出,每个动作的重要性权重,P:[0.5,0.5] B: [0.1,0.9]
我们以a1 为例,计算重要性权重:
我们把重要性权重乘以TD error,我们发现,a1的TD error 大幅提升,而a2的TD error减少了。现在即使我们用 P 策略:[0.5,0.5]进行更新,a1提升的概率也会比a2大得更多。
PPO 应用了 importance sampling,使得我们用行为策略获取的数据,能够更新目标策略,把 AC 从在线策略,变成离线策略。
那我们为什么可以像上面这么做呢?
回想PG:
其实可以把它理解为是在求一个期望,通过不断的sample 然后求平均去近似期望值
两个正太分布的期望u一样,但是只要是方差不同,就是不一样的正太分布。所以上面即使我们乘上了 importance weight 对q的期望进行了修正,但是方差也是不同的
所以结论就是,由于修正后期望是一样的,但是方差公式不同。假设我们对p和q采样sample 的次数足够多,它们会是一样的,原因是期望;但是当 sample 次数不够多时,由于方差不一样,方差差距越大,那么sample 就有可能得到非常大的差距。
当两个分布差距太大的时候,就会有问题。手是,我们还得限制两个分布差距不能太大!
在PPO1 里面,用了 KL散度(相对熵)来衡量两个分布的差距。作为一个惩罚项来出现,KL 散度是一种衡量两个概率分布的匹配程度的指标,两个分布差异越大,KL散度越大。
特别需要注意的是,这里KL计算的还真不是参数上面的距离,而是参数使得行为 action 表现上面的距离,也就是策略的距离。
PPO2 则简单粗暴许多,直接裁剪了更新的范围,但效果却不错。
""" A simple version of Proximal Policy Optimization (PPO) using single thread. Based on: 1. Emergence of Locomotion Behaviours in Rich Environments (Google Deepmind): [https://arxiv.org/abs/1707.02286] 2. Proximal Policy Optimization Algorithms (OpenAI): [https://arxiv.org/abs/1707.06347] View more on my tutorial website: https://morvanzhou.github.io/tutorials Dependencies: tensorflow r1.2 gym 0.9.2 """ import tensorflow.compat.v1 as tf tf.disable_v2_behavior() import numpy as np import matplotlib.pyplot as plt import gym EP_MAX = 1000 EP_LEN = 200 GAMMA = 0.9 A_LR = 0.0001 C_LR = 0.0002 BATCH = 32 A_UPDATE_STEPS = 10 C_UPDATE_STEPS = 10 S_DIM, A_DIM = 3, 1 METHOD = [ dict(name='kl_pen', kl_target=0.01, lam=0.5), # KL penalty dict(name='clip', epsilon=0.2), # Clipped surrogate objective, find this is better ][1] # choose the method for optimization class PPO(object): def __init__(self): self.sess = tf.Session() self.tfs = tf.placeholder(tf.float32, [None, S_DIM], 'state') # critic with tf.variable_scope('critic'): l1 = tf.layers.dense(self.tfs, 100, tf.nn.relu) self.v = tf.layers.dense(l1, 1) self.tfdc_r = tf.placeholder(tf.float32, [None, 1], 'discounted_r') self.advantage = self.tfdc_r - self.v self.closs = tf.reduce_mean(tf.square(self.advantage)) self.ctrain_op = tf.train.AdamOptimizer(C_LR).minimize(self.closs) # actor pi, pi_params = self._build_anet('pi', trainable=True) oldpi, oldpi_params = self._build_anet('oldpi', trainable=False) with tf.variable_scope('sample_action'): self.sample_op = tf.squeeze(pi.sample(1), axis=0) # choosing action with tf.variable_scope('update_oldpi'): self.update_oldpi_op = [oldp.assign(p) for p, oldp in zip(pi_params, oldpi_params)] self.tfa = tf.placeholder(tf.float32, [None, A_DIM], 'action') self.tfadv = tf.placeholder(tf.float32, [None, 1], 'advantage') with tf.variable_scope('loss'): with tf.variable_scope('surrogate'): # ratio = tf.exp(pi.log_prob(self.tfa) - oldpi.log_prob(self.tfa)) ratio = pi.prob(self.tfa) / (oldpi.prob(self.tfa) + 1e-5) surr = ratio * self.tfadv if METHOD['name'] == 'kl_pen': self.tflam = tf.placeholder(tf.float32, None, 'lambda') kl = tf.distributions.kl_divergence(oldpi, pi) self.kl_mean = tf.reduce_mean(kl) self.aloss = -(tf.reduce_mean(surr - self.tflam * kl)) else: # clipping method, find this is better self.aloss = -tf.reduce_mean(tf.minimum( surr, tf.clip_by_value(ratio, 1.-METHOD['epsilon'], 1.+METHOD['epsilon'])*self.tfadv)) with tf.variable_scope('atrain'): self.atrain_op = tf.train.AdamOptimizer(A_LR).minimize(self.aloss) tf.summary.FileWriter("log/", self.sess.graph) self.sess.run(tf.global_variables_initializer()) def update(self, s, a, r): self.sess.run(self.update_oldpi_op) adv = self.sess.run(self.advantage, {self.tfs: s, self.tfdc_r: r}) # adv = (adv - adv.mean())/(adv.std()+1e-6) # sometimes helpful # update actor if METHOD['name'] == 'kl_pen': for _ in range(A_UPDATE_STEPS): _, kl = self.sess.run( [self.atrain_op, self.kl_mean], {self.tfs: s, self.tfa: a, self.tfadv: adv, self.tflam: METHOD['lam']}) if kl > 4*METHOD['kl_target']: # this in in google's paper break if kl < METHOD['kl_target'] / 1.5: # adaptive lambda, this is in OpenAI's paper METHOD['lam'] /= 2 elif kl > METHOD['kl_target'] * 1.5: METHOD['lam'] *= 2 METHOD['lam'] = np.clip(METHOD['lam'], 1e-4, 10) # sometimes explode, this clipping is my solution else: # clipping method, find this is better (OpenAI's paper) [self.sess.run(self.atrain_op, {self.tfs: s, self.tfa: a, self.tfadv: adv}) for _ in range(A_UPDATE_STEPS)] # update critic [self.sess.run(self.ctrain_op, {self.tfs: s, self.tfdc_r: r}) for _ in range(C_UPDATE_STEPS)] def _build_anet(self, name, trainable): with tf.variable_scope(name): l1 = tf.layers.dense(self.tfs, 100, tf.nn.relu, trainable=trainable) mu = 2 * tf.layers.dense(l1, A_DIM, tf.nn.tanh, trainable=trainable) sigma = tf.layers.dense(l1, A_DIM, tf.nn.softplus, trainable=trainable) norm_dist = tf.distributions.Normal(loc=mu, scale=sigma) params = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope=name) return norm_dist, params def choose_action(self, s): s = s[np.newaxis, :] a = self.sess.run(self.sample_op, {self.tfs: s})[0] return np.clip(a, -2, 2) def get_v(self, s): if s.ndim < 2: s = s[np.newaxis, :] return self.sess.run(self.v, {self.tfs: s})[0, 0] env = gym.make('Pendulum-v1',render_mode='human') ppo = PPO() all_ep_r = [] for ep in range(EP_MAX): s = env.reset()[0] buffer_s, buffer_a, buffer_r = [], [], [] ep_r = 0 for t in range(EP_LEN): # in one episode env.render() a = ppo.choose_action(s) s_, r, done, _, _ = env.step(a) buffer_s.append(s) buffer_a.append(a) buffer_r.append((r+8)/8) # normalize reward, find to be useful s = s_ ep_r += r # update ppo if (t+1) % BATCH == 0 or t == EP_LEN-1: v_s_ = ppo.get_v(s_) discounted_r = [] for r in buffer_r[::-1]: v_s_ = r + GAMMA * v_s_ discounted_r.append(v_s_) discounted_r.reverse() bs, ba, br = np.vstack(buffer_s), np.vstack(buffer_a), np.array(discounted_r)[:, np.newaxis] buffer_s, buffer_a, buffer_r = [], [], [] ppo.update(bs, ba, br) if ep == 0: all_ep_r.append(ep_r) else: all_ep_r.append(all_ep_r[-1]*0.9 + ep_r*0.1) print( 'Ep: %i' % ep, "|Ep_r: %i" % ep_r, ("|Lam: %.4f" % METHOD['lam']) if METHOD['name'] == 'kl_pen' else '', ) plt.plot(np.arange(len(all_ep_r)), all_ep_r) plt.xlabel('Episode');plt.ylabel('Moving averaged episode reward');plt.show()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。