Beispiel #1
0
def rollout(
    task,
    wrapper,
    difficulty,
    seed,
    num_steps=6,
    random_agent=True,
):

    # Create the environment
    env = dm_construction.get_environment(
        task,
        wrapper_type=wrapper,
    )

    #print(env.observation_spec())
    """ {
    'nodes': Array(shape=(0, 18), dtype=dtype('float32'), name='nodes_spec'), 
    'edges': Array(shape=(0, 1), dtype=dtype('float32'), name='edges_spec'), 
    'globals': Array(shape=(1, 1), dtype=dtype('float32'), name='globals_spec'), 
    'n_node': Array(shape=(1,), dtype=dtype('int32'), name='n_node_spec'), 
    'n_edge': Array(shape=(1,), dtype=dtype('int32'), name='n_edge_spec'),
    'receivers': Array(shape=(0,), dtype=dtype('int32'), name='receivers_spec'), 
    'senders': Array(shape=(0,), dtype=dtype('int32'), name='senders_spec')}
    """

    #print(env.action_spec())
    """ {
    'Index': Array(shape=(), dtype=dtype('int32'), name=None),
    'x_action': BoundedArray(shape=(), dtype=dtype('int32'), name=None,
        minimum=0, maximum=14), 
    'sticky': BoundedArray(shape=(), dtype=dtype('int32'), name=None, minimum=0, maximum=1)}
    """
    if random_agent is True:
        model = RandomAgent(env.action_spec())
    else:
        raise NotImplementedError

    # start interaction with environment
    np.random.seed(seed)
    timestep = env.reset(difficulty=difficulty)

    # record trajectory, actions, rgb images for analysis
    trajectory = [timestep]
    actions = [None]
    rgb_imgs = [env.core_env.last_time_step.observation["RGB"]]

    #    while timestep.step_type != dm_env.StepType.LAST:
    for _ in range(num_steps):
        if timestep.last():
            timestep = env.reset(difficulty=difficulty)
        action = model.act(timestep.observation)
        timestep = env.step(action)
        trajectory.append(timestep)
        actions.append(action)
        rgb_imgs.append(env.core_env.last_time_step.observation["RGB"])

    env.close()
    return trajectory, actions, rgb_imgs
Beispiel #2
0
def rollout(task="covering",
            difficulty=0,
            seed=1234,
            num_steps=6,
            model_constructor=model.ActorCritic,
            env_wrapper="mixed"
            ):
    """
    Rollout with graph observations but continuous actions, with
    observations coming from `discrete_relative` wrapper, and actions
    invoked through `continuous_absolute` wrapper.
    """

    # Create the environment
    if env_wrapper == "mixed":
        unity_env = dm_construction.get_unity_environment(backend="docker")
        task_env = dm_construction.get_task_environment(unity_env, problem_type=task,)
        env = ContinuousAbsoluteGraphWrapper(task_env)
    elif env_wrapper in dm_construction.ALL_WRAPPERS:
        env = dm_construction.get_environment(task, wrapper_type=env_wrapper)
    else:
        raise ValueError(f"Unrecognized wrapper type {env_wrapper}")

    # Create model
    # only continuous_absolute is supported, so model init will throw error
    # for discrete_relative
    model = model_constructor(ob_spec=env.observation_spec(),
                              ac_spec=env.action_spec(),
                              embed_size=64,
                              mlp_hidden_size=64)

    # start interaction with environment
    np.random.seed(seed)
    timestep = env.reset(difficulty=difficulty)

    # record trajectory, actions, rgb images for analysis
    trajectory = [timestep]
    actions = [None]
    rgb_imgs = [env._env.last_time_step.observation["RGB"]]

#    while timestep.step_type != dm_env.StepType.LAST:
    for _ in range(num_steps):
        if timestep.last():
            timestep = env.reset(difficulty=difficulty)

        raw_action = model.act(timestep.observation)
        action = model.action_to_dict(raw_action)

        # Take action via continous_absolute wrapper
        timestep = env.step(action)
        #import pdb; pdb.set_trace()

        # Update record
        trajectory.append(timestep)
        actions.append(action)
        rgb_imgs.append(env._env.last_time_step.observation["RGB"])

    env.close()
    return trajectory, actions, rgb_imgs
Beispiel #3
0
 def _make_environment(
     self, problem_type, curriculum_sample, wrapper_type, backend_type=None):
   """Make the new version of the construction task."""
   if backend_type is None:
     backend_type = FLAGS.backend
   return dm_construction.get_environment(
       problem_type,
       unity_environment=self._unity_envs[backend_type],
       wrapper_type=wrapper_type,
       curriculum_sample=curriculum_sample)
Beispiel #4
0
def get_environment(problem_type,
                    wrapper_type="discrete_relative",
                    difficulty=0,
                    curriculum_sample=False):
    """Gets the environment.

  This function separately creates the unity environment and then passes it to
  the environment factory. We do this so that we can add an observer to the
  unity environment to get all frames from which we will create a video.

  Args:
    problem_type: the name of the task
    wrapper_type: the name of the wrapper
    difficulty: the difficulty level
    curriculum_sample: whether to sample difficulty from [0, difficulty]

  Returns:
    env_: the environment
  """
    # Separately construct the Unity env, so we can enable the observer camera
    # and set a higher resolution on it.
    unity_env = dm_construction.get_unity_environment(
        observer_width=600,
        observer_height=600,
        include_observer_camera=True,
        max_simulation_substeps=50)

    # Create the main environment by passing in the already-created Unity env.
    env_ = dm_construction.get_environment(problem_type,
                                           unity_env,
                                           wrapper_type=wrapper_type,
                                           curriculum_sample=curriculum_sample,
                                           difficulty=difficulty)

    # Create an observer to grab the frames from the observer camera.
    env_.core_env.enable_frame_observer()
    return env_
Beispiel #5
0
def ppo(task,
        actor_critic=model.ActorCritic,
        ac_kwargs=dict(),
        seed=0,
        steps_per_epoch=4000,
        epochs=50,
        gamma=0.99,
        clip_ratio=0.2,
        lr=3e-4,
        v_loss_coeff=0.5,
        train_iters=80,
        lam=0.97,
        max_ep_len=1000,
        target_kl=0.01,
        logger_kwargs=dict(),
        save_freq=10,
        wrapper_type="continuous_absolute",
        log_wandb=False):
    """
    Proximal Policy Optimization (by clipping), 

    with early stopping based on approximate KL

    Args:
        env_fn : A function which creates a copy of the environment.
            The environment must satisfy the OpenAI Gym API.

        actor_critic: The constructor method for a PyTorch Module with a 
            ``step`` method, an ``act`` method, a ``pi`` module, and a ``v`` 
            module. The ``step`` method should accept a batch of observations 
            and return:

            ===========  ================  ======================================
            Symbol       Shape             Description
            ===========  ================  ======================================
            ``a``        (batch, act_dim)  | Numpy array of actions for each 
                                           | observation.
            ``v``        (batch,)          | Numpy array of value estimates
                                           | for the provided observations.
            ``logp_a``   (batch,)          | Numpy array of log probs for the
                                           | actions in ``a``.
            ===========  ================  ======================================

            The ``act`` method behaves the same as ``step`` but only returns ``a``.

            The ``pi`` module's forward call should accept a batch of 
            observations and optionally a batch of actions, and return:

            ===========  ================  ======================================
            Symbol       Shape             Description
            ===========  ================  ======================================
            ``pi``       N/A               | Torch Distribution object, containing
                                           | a batch of distributions describing
                                           | the policy for the provided observations.
            ``logp_a``   (batch,)          | Optional (only returned if batch of
                                           | actions is given). Tensor containing 
                                           | the log probability, according to 
                                           | the policy, of the provided actions.
                                           | If actions not given, will contain
                                           | ``None``.
            ===========  ================  ======================================

            The ``v`` module's forward call should accept a batch of observations
            and return:

            ===========  ================  ======================================
            Symbol       Shape             Description
            ===========  ================  ======================================
            ``v``        (batch,)          | Tensor containing the value estimates
                                           | for the provided observations. (Critical: 
                                           | make sure to flatten this!)
            ===========  ================  ======================================


        ac_kwargs (dict): Any kwargs appropriate for the ActorCritic object 
            you provided to PPO.

        seed (int): Seed for random number generators.

        steps_per_epoch (int): Number of steps of interaction (state-action pairs) 
            for the agent and the environment in each epoch.

        epochs (int): Number of epochs of interaction (equivalent to
            number of policy updates) to perform.

        gamma (float): Discount factor. (Always between 0 and 1.)

        clip_ratio (float): Hyperparameter for clipping in the policy objective.
            Roughly: how far can the new policy go from the old policy while 
            still profiting (improving the objective function)? The new policy 
            can still go farther than the clip_ratio says, but it doesn't help
            on the objective anymore. (Usually small, 0.1 to 0.3.) Typically
            denoted by :math:`\epsilon`. 

        pi_lr (float): Learning rate for policy optimizer.

        vf_lr (float): Learning rate for value function optimizer.

        train_pi_iters (int): Maximum number of gradient descent steps to take 
            on policy loss per epoch. (Early stopping may cause optimizer
            to take fewer than this.)

        train_v_iters (int): Number of gradient descent steps to take on 
            value function per epoch.

        lam (float): Lambda for GAE-Lambda. (Always between 0 and 1,
            close to 1.)

        max_ep_len (int): Maximum length of trajectory / episode / rollout.

        target_kl (float): Roughly what KL divergence we think is appropriate
            between new and old policies after an update. This will get used 
            for early stopping. (Usually small, 0.01 or 0.05.)

        logger_kwargs (dict): Keyword args for EpochLogger.

        save_freq (int): How often (in terms of gap between epochs) to save
            the current policy and value function.

    """

    # Special function to avoid certain slowdowns from PyTorch + MPI combo.
    setup_pytorch_for_mpi()

    # Set up logger and save configuration
    logger = EpochLogger(**logger_kwargs)
    logger.save_config(locals())

    # Random seed
    seed += 10000 * proc_id()
    torch.manual_seed(seed)
    np.random.seed(seed)

    # Instantiate environment
    env = dm_construction.get_environment(task, wrapper_type=wrapper_type)
    obs_dim = env.observation_spec().shape
    if wrapper_type == "continuous_absolute":
        act_dim = 4  # for continuous absolute action space
    else:
        raise NotImplementedError

    # Create actor-critic module
    ac = actor_critic(env.observation_spec(), env.action_spec(), **ac_kwargs)

    # Sync params across processes
    sync_params(ac)

    # Count variables
    var_counts = count_vars(ac.ac)
    logger.log(f"\nNumber of parameters: \t {var_counts}")

    # Set up experience buffer
    local_steps_per_epoch = int(steps_per_epoch / num_procs())
    buf = PPOBuffer(obs_dim, act_dim, local_steps_per_epoch, gamma, lam)

    def compute_loss(data):
        obs, act, adv, logp_old, ret = data['obs'], data['act'], data[
            'adv'], data['logp'], data['ret']
        pi, v, logp = ac.ac(obs, act)

        # value loss (just MSE)
        loss_v = ((v - ret)**2).mean()

        # policy loss
        ratio = torch.exp(logp - logp_old)
        clip_adv = torch.clamp(ratio, 1 - clip_ratio, 1 + clip_ratio) * adv
        loss_pi = -(torch.min(ratio * adv, clip_adv)).mean()

        # useful extra info re: policy
        approx_kl = (logp_old - logp).mean().item()
        ent = pi.entropy().mean().item()
        clipped = ratio.gt(1 + clip_ratio) | ratio.lt(1 - clip_ratio)
        clipfrac = torch.as_tensor(clipped, dtype=torch.float32).mean().item()
        pi_info = dict(kl=approx_kl, ent=ent, cf=clipfrac)

        return loss_v, loss_pi, pi_info

    # Set up optimizers for policy and value function
    optimizer = Adam(ac.ac.parameters(), lr=lr)

    # Set up model saving
    logger.setup_pytorch_saver(ac)

    def update():
        data = buf.get()

        v_l_old, pi_l_old, pi_info_old = compute_loss(data)
        pi_l_old = pi_l_old.item()
        vl_l_old = v_l_old.item()

        # Train policy with multiple steps of gradient descent
        for i in range(train_iters):
            optimizer.zero_grad()
            loss_v, loss_pi, pi_info = compute_loss(data)
            kl = mpi_avg(pi_info['kl'])
            if kl > 1.5 * target_kl:
                logger.log(
                    f'Early stopping at step {i} due to reaching max kl.')
                break

            loss = loss_pi + loss_v * v_loss_coeff
            loss.backward()
            mpi_avg_grads(ac.ac)  # average grads across MPI processes
            optimizer.step()

        logger.store(StopIter=i)

        # Log changes from update
        kl, ent, cf = pi_info['kl'], pi_info_old['ent'], pi_info['cf']
        logger.store(LossPi=pi_l_old,
                     LossV=v_l_old,
                     KL=kl,
                     Entropy=ent,
                     ClipFrac=cf,
                     DeltaLossPi=(loss_pi.item() - pi_l_old),
                     DeltaLossV=(loss_v.item() - v_l_old))

    # Prepare for interaction with environment
    start_time = time.time()
    timestep, ep_ret, ep_len = env.reset(difficulty=0), 0, 0

    # Main loop: collect experience in env and update/log each epoch
    for epoch in range(epochs):
        encountered_terminal = False
        for t in range(local_steps_per_epoch):
            # assumes obs is an rgb array: rescale to [0, 1]
            o = timestep.observation / 255.0

            a, v, logp = ac.step(o)

            next_timestep = env.step(ac.action_to_dict(a, rescale=True))
            r = timestep.reward
            d = next_timestep.last(
            )  # TODO: check if r, d are assoc w/ correct timestep
            ep_ret += r
            ep_len += 1

            # save and log
            buf.store(o, a, r, v, logp)
            logger.store(VVals=v)

            # TODO debugging
            logger.store(AHor=a[0])
            logger.store(AVer=a[1])
            logger.store(ASel=a[3])

            # Update obs (critical!)
            timestep = next_timestep

            timeout = ep_len == max_ep_len
            terminal = d or timeout
            epoch_ended = t == local_steps_per_epoch - 1

            if terminal or epoch_ended:
                if epoch_ended and not (terminal):
                    print(
                        f'Warning: trajectory cut off by epoch at {ep_len} steps.',
                        flush=True)
                # if trajectory didn't reach terminal state, bootstrap value target
                if timeout or epoch_ended:
                    _, v, _ = ac.step(timestep.observation / 255.0)
                else:
                    v = 0
                buf.finish_path(v)
                if terminal:
                    # only save EpRet / EpLen if trajectory finished.
                    logger.store(EpRet=ep_ret, EpLen=ep_len)
                    encountered_terminal = True
                timestep, ep_ret, ep_len = env.reset(difficulty=0), 0, 0

        # Perform PPO update!
        update()

        # Log info about epoch
        logger.log_tabular('Epoch', epoch)
        if encountered_terminal:
            # Note, if local_steps_per_epoch is too small so no terminal state
            # has been encountered, then ep_ret and ep_len will not
            # be stored before call to log_tabular, resulting in error.
            logger.log_tabular('EpRet', with_min_and_max=True)
            logger.log_tabular('EpLen', average_only=True)
        logger.log_tabular('VVals', with_min_and_max=True)
        logger.log_tabular('TotalEnvInteracts', (epoch + 1) * steps_per_epoch)
        logger.log_tabular('LossPi', average_only=True)
        logger.log_tabular('LossV', average_only=True)
        logger.log_tabular('DeltaLossPi', average_only=True)
        logger.log_tabular('DeltaLossV', average_only=True)
        logger.log_tabular('Entropy', average_only=True)
        logger.log_tabular('KL', average_only=True)
        logger.log_tabular('ClipFrac', average_only=True)
        logger.log_tabular('StopIter', average_only=True)
        logger.log_tabular('Time', time.time() - start_time)

        # TODO debugging
        logger.log_tabular('AHor', with_min_and_max=True)
        logger.log_tabular('AVer', with_min_and_max=True)
        logger.log_tabular('ASel', with_min_and_max=True)

        # Save model
        if (epoch % save_freq == 0 and epoch > 0) or (epoch == epochs - 1):
            logger.save_state({'env': env}, None)

            if proc_id() == 0 and log_wandb:
                # Save the model parameters to wandb every save_freq epoch
                # instead of waiting till the end
                state = {
                    'epoch': epoch,
                    'ac_state_dict': ac.ac.state_dict(),
                    'optimizer': optimizer.state_dict(),
                }
                # output the model in the wandb.run.dir to avoid problems
                # syncing the model in the cloud with wandb's files
                state_fname = os.path.join(wandb.run.dir, "state_dict.pt")
                torch.save(state, state_fname)

        if proc_id() == 0 and log_wandb:
            wandb.log(logger.log_current_row, step=epoch)
        logger.dump_tabular()