feat(docs): Docstrings for ppotrainer
This commit is contained in:
parent
1c64dc20ae
commit
b5d93fad29
1 changed files with 65 additions and 0 deletions
|
|
@ -27,12 +27,19 @@ from brittle_star_project.ppo import PPO
|
||||||
|
|
||||||
@jax.jit
|
@jax.jit
|
||||||
def _linear_schedule(count, minibatch_count, update_epochs, num_iterations, learning_rate):
|
def _linear_schedule(count, minibatch_count, update_epochs, num_iterations, learning_rate):
|
||||||
|
"""
|
||||||
|
Linear scheduler, as first approach for our models.
|
||||||
|
First setup assumes that we want to adapt less in later stages, linearly scaled.
|
||||||
|
"""
|
||||||
frac = 1.0 - (count // (minibatch_count * update_epochs)) / num_iterations
|
frac = 1.0 - (count // (minibatch_count * update_epochs)) / num_iterations
|
||||||
return learning_rate * frac
|
return learning_rate * frac
|
||||||
|
|
||||||
|
|
||||||
@jax.jit
|
@jax.jit
|
||||||
def _convert_obs_dict_to_array(obs_dict: dict) -> jnp.ndarray:
|
def _convert_obs_dict_to_array(obs_dict: dict) -> jnp.ndarray:
|
||||||
|
"""
|
||||||
|
Turn observation into a vectorized jax map, needed for efficient jax jit
|
||||||
|
"""
|
||||||
return jax.vmap(lambda o: jnp.concatenate([v.flatten() for v in o.values() if v.size > 0]))(
|
return jax.vmap(lambda o: jnp.concatenate([v.flatten() for v in o.values() if v.size > 0]))(
|
||||||
obs_dict
|
obs_dict
|
||||||
)
|
)
|
||||||
|
|
@ -48,6 +55,10 @@ def _get_action_and_value_noise(
|
||||||
next_obs: jnp.ndarray,
|
next_obs: jnp.ndarray,
|
||||||
key: jax.random.PRNGKey,
|
key: jax.random.PRNGKey,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Sample an action and value from our MLP pipeline.
|
||||||
|
This is used during rollout to test our model in the training phase.
|
||||||
|
"""
|
||||||
hidden = sensor.apply(agent_state.params["sensor_params"], next_obs)
|
hidden = sensor.apply(agent_state.params["sensor_params"], next_obs)
|
||||||
hidden_critic = feature_extractor.apply(
|
hidden_critic = feature_extractor.apply(
|
||||||
agent_state.params["feature_extractor_params"], next_obs
|
agent_state.params["feature_extractor_params"], next_obs
|
||||||
|
|
@ -74,6 +85,10 @@ def _step_once(
|
||||||
actor: Actor,
|
actor: Actor,
|
||||||
critic: OneDenseLayerMLP,
|
critic: OneDenseLayerMLP,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Takes one step in the environment and updates our storage state.
|
||||||
|
Makes our code more modular.
|
||||||
|
"""
|
||||||
agent_state, episode_stats, obs, done, key, env_state = carry
|
agent_state, episode_stats, obs, done, key, env_state = carry
|
||||||
action, logprob, value, key = _get_action_and_value_noise(
|
action, logprob, value, key = _get_action_and_value_noise(
|
||||||
sensor, feature_extractor, actor, critic, agent_state, obs, key
|
sensor, feature_extractor, actor, critic, agent_state, obs, key
|
||||||
|
|
@ -98,6 +113,10 @@ def _step_once(
|
||||||
|
|
||||||
# removed jit: used in _rollout_jit, so will be compiled with _rollout_jit
|
# removed jit: used in _rollout_jit, so will be compiled with _rollout_jit
|
||||||
def _step_env_wrapped(episode_stats, env_state, action, env_step_fn):
|
def _step_env_wrapped(episode_stats, env_state, action, env_step_fn):
|
||||||
|
"""
|
||||||
|
Wrapped step function for jax jitting.
|
||||||
|
Takes a jax jitted step and updates state.
|
||||||
|
"""
|
||||||
next_env_state = env_step_fn(env_state, action)
|
next_env_state = env_step_fn(env_state, action)
|
||||||
|
|
||||||
# Extract per-environment signals from the state object
|
# Extract per-environment signals from the state object
|
||||||
|
|
@ -141,6 +160,11 @@ def _rollout_jit(
|
||||||
actor: Actor,
|
actor: Actor,
|
||||||
critic: OneDenseLayerMLP,
|
critic: OneDenseLayerMLP,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Rollout steps.
|
||||||
|
Takes steps in environment and updates states.
|
||||||
|
Jax.lax for efficiently applying it on all envs.
|
||||||
|
"""
|
||||||
(agent_state, episode_stats, next_obs, next_done, key, env_state), storage = jax.lax.scan(
|
(agent_state, episode_stats, next_obs, next_done, key, env_state), storage = jax.lax.scan(
|
||||||
partial(
|
partial(
|
||||||
_step_once,
|
_step_once,
|
||||||
|
|
@ -159,6 +183,9 @@ def _rollout_jit(
|
||||||
|
|
||||||
# removed jit: used in _compute_gae_jit, so will be compiled with _compute_gae_jit
|
# removed jit: used in _compute_gae_jit, so will be compiled with _compute_gae_jit
|
||||||
def _compute_gae_once(carry, inp, gamma, gae_lambda):
|
def _compute_gae_once(carry, inp, gamma, gae_lambda):
|
||||||
|
"""
|
||||||
|
Helper to compute advantages, needed to see how good our model was.
|
||||||
|
"""
|
||||||
advantages = carry
|
advantages = carry
|
||||||
nextdone, nextvalues, curvalues, reward = inp
|
nextdone, nextvalues, curvalues, reward = inp
|
||||||
nextnonterminal = 1.0 - nextdone
|
nextnonterminal = 1.0 - nextdone
|
||||||
|
|
@ -171,6 +198,12 @@ def _compute_gae_once(carry, inp, gamma, gae_lambda):
|
||||||
def _compute_gae_jit(
|
def _compute_gae_jit(
|
||||||
agent_state, storage, next_obs, next_done, gamma, gae_lambda, num_envs, sensor, critic
|
agent_state, storage, next_obs, next_done, gamma, gae_lambda, num_envs, sensor, critic
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Gets value prediction from critic, and calls the
|
||||||
|
advantage helper with jax.lax.scan for
|
||||||
|
efficient application on all envs.
|
||||||
|
Finally returns updated storage.
|
||||||
|
"""
|
||||||
next_value = critic.apply(
|
next_value = critic.apply(
|
||||||
agent_state.params["critic_params"],
|
agent_state.params["critic_params"],
|
||||||
sensor.apply(agent_state.params["sensor_params"], next_obs),
|
sensor.apply(agent_state.params["sensor_params"], next_obs),
|
||||||
|
|
@ -190,6 +223,10 @@ def _compute_gae_jit(
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LossInfo:
|
class LossInfo:
|
||||||
|
"""
|
||||||
|
Helper class to store data for logging
|
||||||
|
"""
|
||||||
|
|
||||||
# todo: better typing
|
# todo: better typing
|
||||||
loss: Any
|
loss: Any
|
||||||
pg_loss: Any
|
pg_loss: Any
|
||||||
|
|
@ -200,7 +237,14 @@ class LossInfo:
|
||||||
|
|
||||||
|
|
||||||
class PPOTrainer:
|
class PPOTrainer:
|
||||||
|
"""
|
||||||
|
Main training loop class.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, args: PPOArgs, env: BrittleStarJaxEnvWrapper, run_dir: str, run_name: str):
|
def __init__(self, args: PPOArgs, env: BrittleStarJaxEnvWrapper, run_dir: str, run_name: str):
|
||||||
|
"""
|
||||||
|
Sets args and prepares partial jitted functions to be used later.
|
||||||
|
"""
|
||||||
self.args = args
|
self.args = args
|
||||||
self.env = env
|
self.env = env
|
||||||
self.run_dir = run_dir
|
self.run_dir = run_dir
|
||||||
|
|
@ -246,12 +290,18 @@ class PPOTrainer:
|
||||||
self._init_random()
|
self._init_random()
|
||||||
|
|
||||||
def _init_random(self):
|
def _init_random(self):
|
||||||
|
"""
|
||||||
|
Set seed, used for randomized environment.
|
||||||
|
"""
|
||||||
self.logger.info(f"[RANDOM]: Setting random seed to {self.args.seed}")
|
self.logger.info(f"[RANDOM]: Setting random seed to {self.args.seed}")
|
||||||
|
|
||||||
random.seed(self.args.seed)
|
random.seed(self.args.seed)
|
||||||
np.random.seed(self.args.seed)
|
np.random.seed(self.args.seed)
|
||||||
|
|
||||||
def _init_agent(self):
|
def _init_agent(self):
|
||||||
|
"""
|
||||||
|
Sets up MLPs.
|
||||||
|
"""
|
||||||
self.logger.info("[AGENT]: Initializing agent...")
|
self.logger.info("[AGENT]: Initializing agent...")
|
||||||
|
|
||||||
sensor = GenericDenseLayersWithActivation()
|
sensor = GenericDenseLayersWithActivation()
|
||||||
|
|
@ -264,6 +314,11 @@ class PPOTrainer:
|
||||||
return sensor, feature_extractor, actor, critic
|
return sensor, feature_extractor, actor, critic
|
||||||
|
|
||||||
def _init_agent_state(self) -> TrainState:
|
def _init_agent_state(self) -> TrainState:
|
||||||
|
"""
|
||||||
|
Prepares the full training state,
|
||||||
|
this contains the MLP configs
|
||||||
|
and their params.
|
||||||
|
"""
|
||||||
self.logger.info("[AGENT STATE]: Initializing agent state...")
|
self.logger.info("[AGENT STATE]: Initializing agent state...")
|
||||||
|
|
||||||
self.key, sensor_key, actor_key, critic_key, feature_extractor_key = jax.random.split(
|
self.key, sensor_key, actor_key, critic_key, feature_extractor_key = jax.random.split(
|
||||||
|
|
@ -309,6 +364,10 @@ class PPOTrainer:
|
||||||
)
|
)
|
||||||
|
|
||||||
def _init_episode_stats(self) -> EpisodeStatistics:
|
def _init_episode_stats(self) -> EpisodeStatistics:
|
||||||
|
"""
|
||||||
|
Epiosde stats tell us how long a rollout ran,
|
||||||
|
we use this for debugging.
|
||||||
|
"""
|
||||||
self.logger.info("[EPISODE STATS]: Initializing episode stats...")
|
self.logger.info("[EPISODE STATS]: Initializing episode stats...")
|
||||||
|
|
||||||
return EpisodeStatistics( # type: ignore[call-arg]
|
return EpisodeStatistics( # type: ignore[call-arg]
|
||||||
|
|
@ -344,6 +403,9 @@ class PPOTrainer:
|
||||||
iteration_time_start,
|
iteration_time_start,
|
||||||
loss_info,
|
loss_info,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Helper to log our metrics to the logger backend.
|
||||||
|
"""
|
||||||
metrics = {
|
metrics = {
|
||||||
"charts/avg_episodic_return": loss_info.avg_episodic_return,
|
"charts/avg_episodic_return": loss_info.avg_episodic_return,
|
||||||
"charts/avg_episodic_length": np.mean(
|
"charts/avg_episodic_length": np.mean(
|
||||||
|
|
@ -365,6 +427,9 @@ class PPOTrainer:
|
||||||
self.logger.log(metrics, step=global_step)
|
self.logger.log(metrics, step=global_step)
|
||||||
|
|
||||||
def _step(self, env_state, next_obs, next_done, iteration: int) -> tuple:
|
def _step(self, env_state, next_obs, next_done, iteration: int) -> tuple:
|
||||||
|
"""
|
||||||
|
Main step helper
|
||||||
|
"""
|
||||||
if iteration == 1:
|
if iteration == 1:
|
||||||
self.logger.log_non_interactive(f"Starting first rollout (JIT): {time.ctime()}")
|
self.logger.log_non_interactive(f"Starting first rollout (JIT): {time.ctime()}")
|
||||||
|
|
||||||
|
|
|
||||||
Reference in a new issue