safe_rl/pg/run_agent.py (315 lines of code) (raw):
import numpy as np
import tensorflow as tf
import gym
import time
import safe_rl.pg.trust_region as tro
from safe_rl.pg.agents import PPOAgent, TRPOAgent, CPOAgent
from safe_rl.pg.buffer import CPOBuffer
from safe_rl.pg.network import count_vars, \
get_vars, \
mlp_actor_critic,\
placeholders, \
placeholders_from_spaces
from safe_rl.pg.utils import values_as_sorted_list
from safe_rl.utils.logx import EpochLogger
from safe_rl.utils.mpi_tf import MpiAdamOptimizer, sync_all_params
from safe_rl.utils.mpi_tools import mpi_fork, proc_id, num_procs, mpi_sum
# Multi-purpose agent runner for policy optimization algos
# (PPO, TRPO, their primal-dual equivalents, CPO)
def run_polopt_agent(env_fn,
agent=PPOAgent(),
actor_critic=mlp_actor_critic,
ac_kwargs=dict(),
seed=0,
render=False,
# Experience collection:
steps_per_epoch=4000,
epochs=50,
max_ep_len=1000,
# Discount factors:
gamma=0.99,
lam=0.97,
cost_gamma=0.99,
cost_lam=0.97,
# Policy learning:
ent_reg=0.,
# Cost constraints / penalties:
cost_lim=25,
penalty_init=1.,
penalty_lr=5e-2,
# KL divergence:
target_kl=0.01,
# Value learning:
vf_lr=1e-3,
vf_iters=80,
# Logging:
logger=None,
logger_kwargs=dict(),
save_freq=1
):
#=========================================================================#
# Prepare logger, seed, and environment in this process #
#=========================================================================#
logger = EpochLogger(**logger_kwargs) if logger is None else logger
logger.save_config(locals())
seed += 10000 * proc_id()
tf.set_random_seed(seed)
np.random.seed(seed)
env = env_fn()
agent.set_logger(logger)
#=========================================================================#
# Create computation graph for actor and critic (not training routine) #
#=========================================================================#
# Share information about action space with policy architecture
ac_kwargs['action_space'] = env.action_space
# Inputs to computation graph from environment spaces
x_ph, a_ph = placeholders_from_spaces(env.observation_space, env.action_space)
# Inputs to computation graph for batch data
adv_ph, cadv_ph, ret_ph, cret_ph, logp_old_ph = placeholders(*(None for _ in range(5)))
# Inputs to computation graph for special purposes
surr_cost_rescale_ph = tf.placeholder(tf.float32, shape=())
cur_cost_ph = tf.placeholder(tf.float32, shape=())
# Outputs from actor critic
ac_outs = actor_critic(x_ph, a_ph, **ac_kwargs)
pi, logp, logp_pi, pi_info, pi_info_phs, d_kl, ent, v, vc = ac_outs
# Organize placeholders for zipping with data from buffer on updates
buf_phs = [x_ph, a_ph, adv_ph, cadv_ph, ret_ph, cret_ph, logp_old_ph]
buf_phs += values_as_sorted_list(pi_info_phs)
# Organize symbols we have to compute at each step of acting in env
get_action_ops = dict(pi=pi,
v=v,
logp_pi=logp_pi,
pi_info=pi_info)
# If agent is reward penalized, it doesn't use a separate value function
# for costs and we don't need to include it in get_action_ops; otherwise we do.
if not(agent.reward_penalized):
get_action_ops['vc'] = vc
# Count variables
var_counts = tuple(count_vars(scope) for scope in ['pi', 'vf', 'vc'])
logger.log('\nNumber of parameters: \t pi: %d, \t v: %d, \t vc: %d\n'%var_counts)
# Make a sample estimate for entropy to use as sanity check
approx_ent = tf.reduce_mean(-logp)
#=========================================================================#
# Create replay buffer #
#=========================================================================#
# Obs/act shapes
obs_shape = env.observation_space.shape
act_shape = env.action_space.shape
# Experience buffer
local_steps_per_epoch = int(steps_per_epoch / num_procs())
pi_info_shapes = {k: v.shape.as_list()[1:] for k,v in pi_info_phs.items()}
buf = CPOBuffer(local_steps_per_epoch,
obs_shape,
act_shape,
pi_info_shapes,
gamma,
lam,
cost_gamma,
cost_lam)
#=========================================================================#
# Create computation graph for penalty learning, if applicable #
#=========================================================================#
if agent.use_penalty:
with tf.variable_scope('penalty'):
# param_init = np.log(penalty_init)
param_init = np.log(max(np.exp(penalty_init)-1, 1e-8))
penalty_param = tf.get_variable('penalty_param',
initializer=float(param_init),
trainable=agent.learn_penalty,
dtype=tf.float32)
# penalty = tf.exp(penalty_param)
penalty = tf.nn.softplus(penalty_param)
if agent.learn_penalty:
if agent.penalty_param_loss:
penalty_loss = -penalty_param * (cur_cost_ph - cost_lim)
else:
penalty_loss = -penalty * (cur_cost_ph - cost_lim)
train_penalty = MpiAdamOptimizer(learning_rate=penalty_lr).minimize(penalty_loss)
#=========================================================================#
# Create computation graph for policy learning #
#=========================================================================#
# Likelihood ratio
ratio = tf.exp(logp - logp_old_ph)
# Surrogate advantage / clipped surrogate advantage
if agent.clipped_adv:
min_adv = tf.where(adv_ph>0,
(1+agent.clip_ratio)*adv_ph,
(1-agent.clip_ratio)*adv_ph
)
surr_adv = tf.reduce_mean(tf.minimum(ratio * adv_ph, min_adv))
else:
surr_adv = tf.reduce_mean(ratio * adv_ph)
# Surrogate cost
surr_cost = tf.reduce_mean(ratio * cadv_ph)
# Create policy objective function, including entropy regularization
pi_objective = surr_adv + ent_reg * ent
# Possibly include surr_cost in pi_objective
if agent.objective_penalized:
pi_objective -= penalty * surr_cost
pi_objective /= (1 + penalty)
# Loss function for pi is negative of pi_objective
pi_loss = -pi_objective
# Optimizer-specific symbols
if agent.trust_region:
# Symbols needed for CG solver for any trust region method
pi_params = get_vars('pi')
flat_g = tro.flat_grad(pi_loss, pi_params)
v_ph, hvp = tro.hessian_vector_product(d_kl, pi_params)
if agent.damping_coeff > 0:
hvp += agent.damping_coeff * v_ph
# Symbols needed for CG solver for CPO only
flat_b = tro.flat_grad(surr_cost, pi_params)
# Symbols for getting and setting params
get_pi_params = tro.flat_concat(pi_params)
set_pi_params = tro.assign_params_from_flat(v_ph, pi_params)
training_package = dict(flat_g=flat_g,
flat_b=flat_b,
v_ph=v_ph,
hvp=hvp,
get_pi_params=get_pi_params,
set_pi_params=set_pi_params)
elif agent.first_order:
# Optimizer for first-order policy optimization
train_pi = MpiAdamOptimizer(learning_rate=agent.pi_lr).minimize(pi_loss)
# Prepare training package for agent
training_package = dict(train_pi=train_pi)
else:
raise NotImplementedError
# Provide training package to agent
training_package.update(dict(pi_loss=pi_loss,
surr_cost=surr_cost,
d_kl=d_kl,
target_kl=target_kl,
cost_lim=cost_lim))
agent.prepare_update(training_package)
#=========================================================================#
# Create computation graph for value learning #
#=========================================================================#
# Value losses
v_loss = tf.reduce_mean((ret_ph - v)**2)
vc_loss = tf.reduce_mean((cret_ph - vc)**2)
# If agent uses penalty directly in reward function, don't train a separate
# value function for predicting cost returns. (Only use one vf for r - p*c.)
if agent.reward_penalized:
total_value_loss = v_loss
else:
total_value_loss = v_loss + vc_loss
# Optimizer for value learning
train_vf = MpiAdamOptimizer(learning_rate=vf_lr).minimize(total_value_loss)
#=========================================================================#
# Create session, sync across procs, and set up saver #
#=========================================================================#
sess = tf.Session()
sess.run(tf.global_variables_initializer())
# Sync params across processes
sess.run(sync_all_params())
# Setup model saving
logger.setup_tf_saver(sess, inputs={'x': x_ph}, outputs={'pi': pi, 'v': v, 'vc': vc})
#=========================================================================#
# Provide session to agent #
#=========================================================================#
agent.prepare_session(sess)
#=========================================================================#
# Create function for running update (called at end of each epoch) #
#=========================================================================#
def update():
cur_cost = logger.get_stats('EpCost')[0]
c = cur_cost - cost_lim
if c > 0 and agent.cares_about_cost:
logger.log('Warning! Safety constraint is already violated.', 'red')
#=====================================================================#
# Prepare feed dict #
#=====================================================================#
inputs = {k:v for k,v in zip(buf_phs, buf.get())}
inputs[surr_cost_rescale_ph] = logger.get_stats('EpLen')[0]
inputs[cur_cost_ph] = cur_cost
#=====================================================================#
# Make some measurements before updating #
#=====================================================================#
measures = dict(LossPi=pi_loss,
SurrCost=surr_cost,
LossV=v_loss,
Entropy=ent)
if not(agent.reward_penalized):
measures['LossVC'] = vc_loss
if agent.use_penalty:
measures['Penalty'] = penalty
pre_update_measures = sess.run(measures, feed_dict=inputs)
logger.store(**pre_update_measures)
#=====================================================================#
# Update penalty if learning penalty #
#=====================================================================#
if agent.learn_penalty:
sess.run(train_penalty, feed_dict={cur_cost_ph: cur_cost})
#=====================================================================#
# Update policy #
#=====================================================================#
agent.update_pi(inputs)
#=====================================================================#
# Update value function #
#=====================================================================#
for _ in range(vf_iters):
sess.run(train_vf, feed_dict=inputs)
#=====================================================================#
# Make some measurements after updating #
#=====================================================================#
del measures['Entropy']
measures['KL'] = d_kl
post_update_measures = sess.run(measures, feed_dict=inputs)
deltas = dict()
for k in post_update_measures:
if k in pre_update_measures:
deltas['Delta'+k] = post_update_measures[k] - pre_update_measures[k]
logger.store(KL=post_update_measures['KL'], **deltas)
#=========================================================================#
# Run main environment interaction loop #
#=========================================================================#
start_time = time.time()
o, r, d, c, ep_ret, ep_cost, ep_len = env.reset(), 0, False, 0, 0, 0, 0
cur_penalty = 0
cum_cost = 0
for epoch in range(epochs):
if agent.use_penalty:
cur_penalty = sess.run(penalty)
for t in range(local_steps_per_epoch):
# Possibly render
if render and proc_id()==0 and t < 1000:
env.render()
# Get outputs from policy
get_action_outs = sess.run(get_action_ops,
feed_dict={x_ph: o[np.newaxis]})
a = get_action_outs['pi']
v_t = get_action_outs['v']
vc_t = get_action_outs.get('vc', 0) # Agent may not use cost value func
logp_t = get_action_outs['logp_pi']
pi_info_t = get_action_outs['pi_info']
# Step in environment
o2, r, d, info = env.step(a)
# Include penalty on cost
c = info.get('cost', 0)
# Track cumulative cost over training
cum_cost += c
# save and log
if agent.reward_penalized:
r_total = r - cur_penalty * c
r_total = r_total / (1 + cur_penalty)
buf.store(o, a, r_total, v_t, 0, 0, logp_t, pi_info_t)
else:
buf.store(o, a, r, v_t, c, vc_t, logp_t, pi_info_t)
logger.store(VVals=v_t, CostVVals=vc_t)
o = o2
ep_ret += r
ep_cost += c
ep_len += 1
terminal = d or (ep_len == max_ep_len)
if terminal or (t==local_steps_per_epoch-1):
# If trajectory didn't reach terminal state, bootstrap value target(s)
if d and not(ep_len == max_ep_len):
# Note: we do not count env time out as true terminal state
last_val, last_cval = 0, 0
else:
feed_dict={x_ph: o[np.newaxis]}
if agent.reward_penalized:
last_val = sess.run(v, feed_dict=feed_dict)
last_cval = 0
else:
last_val, last_cval = sess.run([v, vc], feed_dict=feed_dict)
buf.finish_path(last_val, last_cval)
# Only save EpRet / EpLen if trajectory finished
if terminal:
logger.store(EpRet=ep_ret, EpLen=ep_len, EpCost=ep_cost)
else:
print('Warning: trajectory cut off by epoch at %d steps.'%ep_len)
# Reset environment
o, r, d, c, ep_ret, ep_len, ep_cost = env.reset(), 0, False, 0, 0, 0, 0
# Save model
if (epoch % save_freq == 0) or (epoch == epochs-1):
logger.save_state({'env': env}, None)
#=====================================================================#
# Run RL update #
#=====================================================================#
update()
#=====================================================================#
# Cumulative cost calculations #
#=====================================================================#
cumulative_cost = mpi_sum(cum_cost)
cost_rate = cumulative_cost / ((epoch+1)*steps_per_epoch)
#=====================================================================#
# Log performance and stats #
#=====================================================================#
logger.log_tabular('Epoch', epoch)
# Performance stats
logger.log_tabular('EpRet', with_min_and_max=True)
logger.log_tabular('EpCost', with_min_and_max=True)
logger.log_tabular('EpLen', average_only=True)
logger.log_tabular('CumulativeCost', cumulative_cost)
logger.log_tabular('CostRate', cost_rate)
# Value function values
logger.log_tabular('VVals', with_min_and_max=True)
logger.log_tabular('CostVVals', with_min_and_max=True)
# Pi loss and change
logger.log_tabular('LossPi', average_only=True)
logger.log_tabular('DeltaLossPi', average_only=True)
# Surr cost and change
logger.log_tabular('SurrCost', average_only=True)
logger.log_tabular('DeltaSurrCost', average_only=True)
# V loss and change
logger.log_tabular('LossV', average_only=True)
logger.log_tabular('DeltaLossV', average_only=True)
# Vc loss and change, if applicable (reward_penalized agents don't use vc)
if not(agent.reward_penalized):
logger.log_tabular('LossVC', average_only=True)
logger.log_tabular('DeltaLossVC', average_only=True)
if agent.use_penalty or agent.save_penalty:
logger.log_tabular('Penalty', average_only=True)
logger.log_tabular('DeltaPenalty', average_only=True)
else:
logger.log_tabular('Penalty', 0)
logger.log_tabular('DeltaPenalty', 0)
# Anything from the agent?
agent.log()
# Policy stats
logger.log_tabular('Entropy', average_only=True)
logger.log_tabular('KL', average_only=True)
# Time and steps elapsed
logger.log_tabular('TotalEnvInteracts', (epoch+1)*steps_per_epoch)
logger.log_tabular('Time', time.time()-start_time)
# Show results!
logger.dump_tabular()
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--agent', type=str, default='ppo')
parser.add_argument('--env', type=str, default='Safexp-PointGoal1-v0')
parser.add_argument('--hid', type=int, default=64)
parser.add_argument('--l', type=int, default=2)
parser.add_argument('--gamma', type=float, default=0.99)
parser.add_argument('--cost_gamma', type=float, default=0.99)
parser.add_argument('--seed', '-s', type=int, default=0)
parser.add_argument('--cpu', type=int, default=4)
parser.add_argument('--steps', type=int, default=4000)
parser.add_argument('--epochs', type=int, default=50)
parser.add_argument('--len', type=int, default=1000)
parser.add_argument('--cost_lim', type=float, default=10)
parser.add_argument('--exp_name', type=str, default='runagent')
parser.add_argument('--kl', type=float, default=0.01)
parser.add_argument('--render', action='store_true')
parser.add_argument('--reward_penalized', action='store_true')
parser.add_argument('--objective_penalized', action='store_true')
parser.add_argument('--learn_penalty', action='store_true')
parser.add_argument('--penalty_param_loss', action='store_true')
parser.add_argument('--entreg', type=float, default=0.)
args = parser.parse_args()
try:
import safety_gym
except:
print('Make sure to install Safety Gym to use constrained RL environments.')
mpi_fork(args.cpu) # run parallel code with mpi
# Prepare logger
from safe_rl.utils.run_utils import setup_logger_kwargs
logger_kwargs = setup_logger_kwargs(args.exp_name, args.seed)
# Prepare agent
agent_kwargs = dict(reward_penalized=args.reward_penalized,
objective_penalized=args.objective_penalized,
learn_penalty=args.learn_penalty,
penalty_param_loss=args.penalty_param_loss)
if args.agent=='ppo':
agent = PPOAgent(**agent_kwargs)
elif args.agent=='trpo':
agent = TRPOAgent(**agent_kwargs)
elif args.agent=='cpo':
agent = CPOAgent(**agent_kwargs)
run_polopt_agent(lambda : gym.make(args.env),
agent=agent,
actor_critic=mlp_actor_critic,
ac_kwargs=dict(hidden_sizes=[args.hid]*args.l),
seed=args.seed,
render=args.render,
# Experience collection:
steps_per_epoch=args.steps,
epochs=args.epochs,
max_ep_len=args.len,
# Discount factors:
gamma=args.gamma,
cost_gamma=args.cost_gamma,
# Policy learning:
ent_reg=args.entreg,
# KL Divergence:
target_kl=args.kl,
cost_lim=args.cost_lim,
# Logging:
logger_kwargs=logger_kwargs,
save_freq=1
)