chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
# @OldAPIStack
|
||||
atari-dist-dqn:
|
||||
env:
|
||||
grid_search:
|
||||
- ale_py:ALE/Breakout-v5
|
||||
- ale_py:ALE/BeamRider-v5
|
||||
- ale_py:ALE/Qbert-v5
|
||||
- ale_py:ALE/SpaceInvaders-v5
|
||||
run: DQN
|
||||
config:
|
||||
# Make analogous to old v4 + NoFrameskip.
|
||||
env_config:
|
||||
frameskip: 1
|
||||
full_action_space: false
|
||||
repeat_action_probability: 0.0
|
||||
double_q: false
|
||||
dueling: false
|
||||
num_atoms: 51
|
||||
noisy: false
|
||||
replay_buffer_config:
|
||||
type: MultiAgentReplayBuffer
|
||||
capacity: 1000000
|
||||
num_steps_sampled_before_learning_starts: 20000
|
||||
n_step: 1
|
||||
target_network_update_freq: 8000
|
||||
lr: .0000625
|
||||
adam_epsilon: .00015
|
||||
hiddens: [512]
|
||||
rollout_fragment_length: 4
|
||||
train_batch_size: 32
|
||||
exploration_config:
|
||||
epsilon_timesteps: 200000
|
||||
final_epsilon: 0.01
|
||||
num_gpus: 0.2
|
||||
min_sample_timesteps_per_iteration: 10000
|
||||
@@ -0,0 +1,39 @@
|
||||
# @OldAPIStack
|
||||
# Runs on a single g3.4xl node
|
||||
# See https://github.com/ray-project/rl-experiments for results
|
||||
atari-basic-dqn:
|
||||
env:
|
||||
grid_search:
|
||||
- ale_py:ALE/Breakout-v5
|
||||
- ale_py:ALE/BeamRider-v5
|
||||
- ale_py:ALE/Qbert-v5
|
||||
- ale_py:ALE/SpaceInvaders-v5
|
||||
run: DQN
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
# Make analogous to old v4 + NoFrameskip.
|
||||
env_config:
|
||||
frameskip: 1
|
||||
full_action_space: false
|
||||
repeat_action_probability: 0.0
|
||||
double_q: false
|
||||
dueling: false
|
||||
num_atoms: 1
|
||||
noisy: false
|
||||
replay_buffer_config:
|
||||
type: MultiAgentReplayBuffer
|
||||
capacity: 1000000
|
||||
num_steps_sampled_before_learning_starts: 20000
|
||||
n_step: 1
|
||||
target_network_update_freq: 8000
|
||||
lr: .0000625
|
||||
adam_epsilon: .00015
|
||||
hiddens: [512]
|
||||
rollout_fragment_length: 4
|
||||
train_batch_size: 32
|
||||
exploration_config:
|
||||
epsilon_timesteps: 200000
|
||||
final_epsilon: 0.01
|
||||
num_gpus: 0.2
|
||||
min_sample_timesteps_per_iteration: 10000
|
||||
@@ -0,0 +1,39 @@
|
||||
# @OldAPIStack
|
||||
# Runs on a single g3.4xl node
|
||||
# See https://github.com/ray-project/rl-experiments for results
|
||||
dueling-ddqn:
|
||||
env:
|
||||
grid_search:
|
||||
- ale_py:ALE/Breakout-v5
|
||||
- ale_py:ALE/BeamRider-v5
|
||||
- ale_py:ALE/Qbert-v5
|
||||
- ale_py:ALE/SpaceInvaders-v5
|
||||
run: DQN
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
# Make analogous to old v4 + NoFrameskip.
|
||||
env_config:
|
||||
frameskip: 1
|
||||
full_action_space: false
|
||||
repeat_action_probability: 0.0
|
||||
double_q: true
|
||||
dueling: true
|
||||
num_atoms: 1
|
||||
noisy: false
|
||||
replay_buffer_config:
|
||||
type: MultiAgentReplayBuffer
|
||||
capacity: 1000000
|
||||
num_steps_sampled_before_learning_starts: 20000
|
||||
n_step: 1
|
||||
target_network_update_freq: 8000
|
||||
lr: .0000625
|
||||
adam_epsilon: .00015
|
||||
hiddens: [512]
|
||||
rollout_fragment_length: 4
|
||||
train_batch_size: 32
|
||||
exploration_config:
|
||||
epsilon_timesteps: 200000
|
||||
final_epsilon: 0.01
|
||||
num_gpus: 0.2
|
||||
min_sample_timesteps_per_iteration: 10000
|
||||
@@ -0,0 +1,28 @@
|
||||
# @OldAPIStack
|
||||
# Runs on a g3.16xl node with 5 m5.24xl workers
|
||||
# Takes roughly 10 minutes.
|
||||
atari-impala:
|
||||
env:
|
||||
grid_search:
|
||||
- ale_py:ALE/Breakout-v5
|
||||
- ale_py:ALE/BeamRider-v5
|
||||
- ale_py:ALE/Qbert-v5
|
||||
- ale_py:ALE/SpaceInvaders-v5
|
||||
run: IMPALA
|
||||
stop:
|
||||
timesteps_total: 3000000
|
||||
config:
|
||||
# Make analogous to old v4 + NoFrameskip.
|
||||
env_config:
|
||||
frameskip: 1
|
||||
full_action_space: false
|
||||
repeat_action_probability: 0.0
|
||||
rollout_fragment_length: 50
|
||||
train_batch_size: 500
|
||||
num_env_runners: 128
|
||||
num_envs_per_env_runner: 5
|
||||
clip_rewards: True
|
||||
lr_schedule: [
|
||||
[0, 0.0005],
|
||||
[20000000, 0.000000000001],
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# @OldAPIStack
|
||||
# Runs on a p2.8xlarge single head node machine.
|
||||
# Should reach ~400 reward in about 1h and after 15-20M ts.
|
||||
atari-impala:
|
||||
env: ale_py:ALE/Breakout-v5
|
||||
run: IMPALA
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
# Make analogous to old v4 + NoFrameskip.
|
||||
env_config:
|
||||
frameskip: 1
|
||||
full_action_space: false
|
||||
repeat_action_probability: 0.0
|
||||
rollout_fragment_length: 50
|
||||
train_batch_size: 4000
|
||||
num_gpus: 4
|
||||
num_env_runners: 31
|
||||
num_gpus_per_env_runner: 0 # works also for partial GPUs (<1.0) per worker
|
||||
num_envs_per_env_runner: 5
|
||||
clip_rewards: True
|
||||
lr_schedule: [
|
||||
[0, 0.0005],
|
||||
[20000000, 0.000000000001],
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
# @OldAPIStack
|
||||
# Runs on a g3.16xl node with 3 m4.16xl workers
|
||||
# See https://github.com/ray-project/rl-experiments for results
|
||||
atari-impala:
|
||||
env:
|
||||
grid_search:
|
||||
- ale_py:ALE/Breakout-v5
|
||||
- ale_py:ALE/BeamRider-v5
|
||||
- ale_py:ALE/Qbert-v5
|
||||
- ale_py:ALE/SpaceInvaders-v5
|
||||
run: IMPALA
|
||||
config:
|
||||
# Make analogous to old v4 + NoFrameskip.
|
||||
env_config:
|
||||
frameskip: 1
|
||||
full_action_space: false
|
||||
repeat_action_probability: 0.0
|
||||
rollout_fragment_length: 50
|
||||
train_batch_size: 500
|
||||
num_env_runners: 32
|
||||
num_envs_per_env_runner: 5
|
||||
clip_rewards: True
|
||||
lr_schedule: [
|
||||
[0, 0.0005],
|
||||
[20000000, 0.000000000001],
|
||||
]
|
||||
@@ -0,0 +1,55 @@
|
||||
# @OldAPIStack
|
||||
# Run e.g. on a g3.16xlarge (4 GPUs) with `num_gpus=1` (1 for each trial;
|
||||
# MsPacman torch + tf; Pong torch + tf).
|
||||
# Uses the hyperparameters published in [2] (see rllib/algorithms/sac/README.md).
|
||||
atari-sac-tf-and-torch:
|
||||
env:
|
||||
grid_search:
|
||||
- ale_py:ALE/MsPacman-v5
|
||||
- ale_py:ALE/Pong-v5
|
||||
run: SAC
|
||||
stop:
|
||||
timesteps_total: 20000000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework:
|
||||
grid_search: [tf, torch]
|
||||
env_config:
|
||||
frameskip: 1 # no frameskip
|
||||
gamma: 0.99
|
||||
q_model_config:
|
||||
hidden_activation: relu
|
||||
hidden_layer_sizes: [512]
|
||||
policy_model_config:
|
||||
hidden_activation: relu
|
||||
hidden_layer_sizes: [512]
|
||||
# Do hard syncs.
|
||||
# Soft-syncs seem to work less reliably for discrete action spaces.
|
||||
tau: 1.0
|
||||
target_network_update_freq: 8000
|
||||
# auto = 0.98 * -log(1/|A|)
|
||||
target_entropy: auto
|
||||
clip_rewards: 1.0
|
||||
n_step: 1
|
||||
rollout_fragment_length: 1
|
||||
replay_buffer_config:
|
||||
type: MultiAgentPrioritizedReplayBuffer
|
||||
capacity: 1000000
|
||||
# How many steps of the model to sample before learning starts.
|
||||
# If True prioritized replay buffer will be used.
|
||||
prioritized_replay_alpha: 0.6
|
||||
prioritized_replay_beta: 0.4
|
||||
prioritized_replay_eps: 1e-6
|
||||
num_steps_sampled_before_learning_starts: 100000
|
||||
train_batch_size: 64
|
||||
min_sample_timesteps_per_iteration: 4
|
||||
# Paper uses 20k random timesteps, which is not exactly the same, but
|
||||
# seems to work nevertheless. We use 100k here for the longer Atari
|
||||
# runs (DQN style: filling up the buffer a bit before learning).
|
||||
optimization:
|
||||
actor_learning_rate: 0.0003
|
||||
critic_learning_rate: 0.0003
|
||||
entropy_learning_rate: 0.0003
|
||||
num_env_runners: 0
|
||||
num_gpus: 1
|
||||
metrics_num_episodes_for_smoothing: 5
|
||||
@@ -0,0 +1,44 @@
|
||||
# @OldAPIStack
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.appo import APPOConfig
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
|
||||
stop = {
|
||||
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 400,
|
||||
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000,
|
||||
}
|
||||
|
||||
config = (
|
||||
APPOConfig()
|
||||
.api_stack(
|
||||
enable_rl_module_and_learner=False,
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
)
|
||||
.environment("CartPole-v1")
|
||||
# Switch on >1 loss/optimizer API for TFPolicy and EagerTFPolicy.
|
||||
.experimental(_tf_policy_handles_more_than_one_loss=True)
|
||||
.training(
|
||||
# APPO will produce two separate loss terms: policy loss + value function loss.
|
||||
_separate_vf_optimizer=True,
|
||||
# Separate learning rate (and schedule) for the value function branch.
|
||||
_lr_vf=tune.grid_search([0.00075, [[0, 0.00075], [100000, 0.0003]]]),
|
||||
num_epochs=6,
|
||||
# `vf_loss_coeff` will be ignored anyways as we use separate loss terms.
|
||||
vf_loss_coeff=0.01,
|
||||
vtrace=True,
|
||||
model={
|
||||
# Make sure we really have completely separate branches.
|
||||
"vf_share_layers": False,
|
||||
},
|
||||
)
|
||||
.env_runners(
|
||||
num_envs_per_env_runner=5,
|
||||
num_env_runners=1,
|
||||
observation_filter="MeanStdFilter",
|
||||
)
|
||||
.resources(num_gpus=0)
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
# @OldAPIStack
|
||||
# To generate training data, first run:
|
||||
# $ ./train.py --run=PPO --env=CartPole-v1 \
|
||||
# --stop='{"timesteps_total": 50000}' \
|
||||
# --config='{"output": "dataset", "output_config": {"format": "json", "path": "/tmp/out", "max_num_samples_per_file": 1}, "batch_mode": "complete_episodes"}'
|
||||
cartpole-bc:
|
||||
env: CartPole-v1
|
||||
run: BC
|
||||
stop:
|
||||
timesteps_total: 500000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
enable_rl_module_and_learner: false
|
||||
enable_env_runner_and_connector_v2: false
|
||||
# In order to evaluate on an actual environment, use these following
|
||||
# settings:
|
||||
evaluation_num_env_runners: 1
|
||||
evaluation_interval: 1
|
||||
evaluation_config:
|
||||
input: sampler
|
||||
# The historic (offline) data file from the PPO run (at the top).
|
||||
input: dataset
|
||||
input_config:
|
||||
format: json
|
||||
paths: /tmp/out
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
# @OldAPIStack
|
||||
"""
|
||||
Tests, whether APPO can learn in a fault-tolerant fashion.
|
||||
|
||||
Workers will be configured to automatically get recreated upon failures (here: within
|
||||
the environment).
|
||||
The environment we use here is configured to crash with a certain probability on each
|
||||
`step()` and/or `reset()` call. Additionally, the environment is configured to stall
|
||||
with a configured probability on each `step()` call for a certain amount of time.
|
||||
"""
|
||||
from gymnasium.wrappers import TimeLimit
|
||||
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.appo import APPOConfig
|
||||
from ray.rllib.examples.envs.classes.cartpole_crashing import CartPoleCrashing
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
EVALUATION_RESULTS,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
|
||||
tune.register_env(
|
||||
"env",
|
||||
lambda cfg: TimeLimit(CartPoleCrashing(cfg), max_episode_steps=500),
|
||||
)
|
||||
|
||||
config = (
|
||||
APPOConfig()
|
||||
.api_stack(
|
||||
enable_rl_module_and_learner=False,
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
)
|
||||
.environment(
|
||||
"env",
|
||||
env_config={
|
||||
"p_crash": 0.0001, # prob to crash during step()
|
||||
"p_crash_reset": 0.001, # prob to crash during reset()
|
||||
"crash_on_worker_indices": [1, 2],
|
||||
"init_time_s": 2.0,
|
||||
"p_stall": 0.0005, # prob to stall during step()
|
||||
"p_stall_reset": 0.001, # prob to stall during reset()
|
||||
"stall_time_sec": (2, 5), # stall between 2 and 10sec.
|
||||
"stall_on_worker_indices": [2, 3],
|
||||
},
|
||||
)
|
||||
.env_runners(
|
||||
num_env_runners=1,
|
||||
num_envs_per_env_runner=1,
|
||||
)
|
||||
# Switch on resiliency (recreate any failed worker).
|
||||
.fault_tolerance(
|
||||
restart_failed_env_runners=True,
|
||||
)
|
||||
.evaluation(
|
||||
evaluation_num_env_runners=4,
|
||||
evaluation_interval=1,
|
||||
evaluation_duration=25,
|
||||
evaluation_duration_unit="episodes",
|
||||
evaluation_parallel_to_training=True,
|
||||
evaluation_config=APPOConfig.overrides(
|
||||
explore=False,
|
||||
env_config={
|
||||
# Make eval workers solid.
|
||||
# This test is to prove that we can learn with crashing envs,
|
||||
# not evaluate with crashing envs.
|
||||
"p_crash": 0.0,
|
||||
"p_crash_reset": 0.0,
|
||||
"init_time_s": 0.0,
|
||||
"p_stall": 0.01,
|
||||
"stall_time_sec": 300, # stall for 5min.
|
||||
"p_stall_reset": 0.0,
|
||||
"stall_on_worker_indices": [1, 2],
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
stop = {
|
||||
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 500.0,
|
||||
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 2000000,
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
# @OldAPIStack
|
||||
"""
|
||||
Tests, whether APPO can learn in a fault-tolerant fashion.
|
||||
|
||||
Workers will be configured to automatically get recreated upon failures (here: within
|
||||
the environment).
|
||||
The environment we use here is configured to crash with a certain probability on each
|
||||
`step()` and/or `reset()` call.
|
||||
"""
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.appo import APPOConfig
|
||||
from ray.rllib.examples.envs.classes.cartpole_crashing import CartPoleCrashing
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
EVALUATION_RESULTS,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
|
||||
tune.register_env("env", lambda cfg: CartPoleCrashing(cfg))
|
||||
|
||||
|
||||
stop = {
|
||||
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 400.0,
|
||||
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 250000,
|
||||
}
|
||||
|
||||
config = (
|
||||
APPOConfig()
|
||||
.api_stack(
|
||||
enable_rl_module_and_learner=False,
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
)
|
||||
.environment(
|
||||
"env",
|
||||
env_config={
|
||||
# Crash roughly every 500 ts.
|
||||
"p_crash": 0.0005, # prob to crash during step()
|
||||
"p_crash_reset": 0.005, # prob to crash during reset()
|
||||
"crash_on_worker_indices": [1, 2],
|
||||
},
|
||||
)
|
||||
.env_runners(
|
||||
num_env_runners=3,
|
||||
num_envs_per_env_runner=1,
|
||||
)
|
||||
# Switch on resiliency (recreate any failed worker).
|
||||
.fault_tolerance(
|
||||
restart_failed_env_runners=True,
|
||||
)
|
||||
.evaluation(
|
||||
evaluation_num_env_runners=1,
|
||||
evaluation_interval=1,
|
||||
evaluation_duration=25,
|
||||
evaluation_duration_unit="episodes",
|
||||
evaluation_parallel_to_training=True,
|
||||
evaluation_config=APPOConfig.overrides(
|
||||
explore=False,
|
||||
env_config={
|
||||
# Make eval workers solid.
|
||||
# This test is to prove that we can learn with crashing envs,
|
||||
# not evaluate with crashing envs.
|
||||
"p_crash": 0.0,
|
||||
"p_crash_reset": 0.0,
|
||||
"init_time_s": 0.0,
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
# @OldAPIStack
|
||||
cartpole-dqn-fake-gpus:
|
||||
env: CartPole-v1
|
||||
run: DQN
|
||||
stop:
|
||||
env_runners/episode_return_mean: 150
|
||||
training_iteration: 400
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
model:
|
||||
fcnet_hiddens: [64]
|
||||
fcnet_activation: linear
|
||||
n_step: 3
|
||||
# Double batch size (2 GPUs).
|
||||
train_batch_size: 64
|
||||
|
||||
# Fake 2 GPUs.
|
||||
num_gpus: 2
|
||||
_fake_gpus: true
|
||||
@@ -0,0 +1,20 @@
|
||||
# @OldAPIStack
|
||||
cartpole-dqn-w-param-noise:
|
||||
env: CartPole-v1
|
||||
run: DQN
|
||||
stop:
|
||||
env_runners/episode_return_mean: 150
|
||||
timesteps_total: 300000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
exploration_config:
|
||||
type: ParameterNoise
|
||||
random_timesteps: 10000
|
||||
initial_stddev: 1.0
|
||||
batch_mode: complete_episodes
|
||||
lr: 0.0008
|
||||
num_env_runners: 0
|
||||
model:
|
||||
fcnet_hiddens: [32, 32]
|
||||
fcnet_activation: tanh
|
||||
@@ -0,0 +1,17 @@
|
||||
# @OldAPIStack
|
||||
cartpole-dqn:
|
||||
env: CartPole-v1
|
||||
run: DQN
|
||||
stop:
|
||||
env_runners/episode_return_mean: 150
|
||||
timesteps_total: 100000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
model:
|
||||
fcnet_hiddens: [64]
|
||||
fcnet_activation: linear
|
||||
n_step: 3
|
||||
exploration_config:
|
||||
type: SoftQ
|
||||
temperature: 0.5
|
||||
@@ -0,0 +1,14 @@
|
||||
# @OldAPIStack
|
||||
cartpole-dqn:
|
||||
env: CartPole-v1
|
||||
run: DQN
|
||||
stop:
|
||||
env_runners/episode_return_mean: 100
|
||||
timesteps_total: 100000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
model:
|
||||
fcnet_hiddens: [64]
|
||||
fcnet_activation: linear
|
||||
n_step: 3
|
||||
@@ -0,0 +1,44 @@
|
||||
# @OldAPIStack
|
||||
from ray.rllib.algorithms.impala import IMPALAConfig
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
|
||||
stop = {
|
||||
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 150,
|
||||
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000,
|
||||
}
|
||||
|
||||
config = (
|
||||
IMPALAConfig()
|
||||
.api_stack(
|
||||
enable_rl_module_and_learner=False,
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
)
|
||||
.environment("CartPole-v1")
|
||||
# Switch on >1 loss/optimizer API for TFPolicy and EagerTFPolicy.
|
||||
.experimental(_tf_policy_handles_more_than_one_loss=True)
|
||||
.training(
|
||||
# IMPALA will produce two separate loss terms: policy loss + value function
|
||||
# loss.
|
||||
_separate_vf_optimizer=True,
|
||||
# Separate learning rate for the value function branch.
|
||||
_lr_vf=0.00075,
|
||||
num_epochs=6,
|
||||
# `vf_loss_coeff` will be ignored anyways as we use separate loss terms.
|
||||
vf_loss_coeff=0.01,
|
||||
vtrace=True,
|
||||
model={
|
||||
# Make sure we really have completely separate branches.
|
||||
"vf_share_layers": False,
|
||||
},
|
||||
)
|
||||
.env_runners(
|
||||
num_envs_per_env_runner=5,
|
||||
num_env_runners=1,
|
||||
observation_filter="MeanStdFilter",
|
||||
)
|
||||
.resources(num_gpus=0)
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
# @OldAPIStack
|
||||
# To generate training data, first run:
|
||||
# $ ./train.py --run=PPO --env=CartPole-v1 \
|
||||
# --stop='{"timesteps_total": 50000}' \
|
||||
# --config='{"output": "/tmp/out", "batch_mode": "complete_episodes"}'
|
||||
cartpole-marwil:
|
||||
env: CartPole-v1
|
||||
run: MARWIL
|
||||
stop:
|
||||
timesteps_total: 500000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
# In order to evaluate on an actual environment, use these following
|
||||
# settings:
|
||||
evaluation_num_env_runners: 1
|
||||
evaluation_interval: 1
|
||||
evaluation_config:
|
||||
input: sampler
|
||||
beta: 1.0 # Compare to behavior cloning (beta=0.0).
|
||||
# The historic (offline) data file from the PPO run (at the top).
|
||||
input: /tmp/out
|
||||
@@ -0,0 +1,22 @@
|
||||
# @OldAPIStack
|
||||
cartpole-sac:
|
||||
env: CartPole-v1
|
||||
run: SAC
|
||||
stop:
|
||||
env_runners/episode_return_mean: 150.0
|
||||
timesteps_total: 100000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
gamma: 0.95
|
||||
target_network_update_freq: 32
|
||||
tau: 1.0
|
||||
# initial_alpha: 0.5
|
||||
train_batch_size: 32
|
||||
optimization:
|
||||
actor_learning_rate: 0.005
|
||||
critic_learning_rate: 0.005
|
||||
entropy_learning_rate: 0.0001
|
||||
# grad_norm_clipping: 40.0
|
||||
# evaluation_config:
|
||||
# explore: true
|
||||
@@ -0,0 +1,33 @@
|
||||
# @OldAPIStack
|
||||
frozenlake-appo-vtrace:
|
||||
env: FrozenLake-v1
|
||||
run: APPO
|
||||
stop:
|
||||
env_runners/episode_return_mean: 0.99
|
||||
timesteps_total: 1000000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
|
||||
# Sparse reward environment (short horizon).
|
||||
env_config:
|
||||
desc:
|
||||
- SFFFFFFF
|
||||
- FFFFFFFF
|
||||
- FFFFFFFF
|
||||
- FFFFFFFF
|
||||
- FFFFFFFF
|
||||
- FFFFFFFF
|
||||
- FFFFFFFF
|
||||
- FFFFFFFG
|
||||
is_slippery: false
|
||||
horizon: 20
|
||||
rollout_fragment_length: 10
|
||||
batch_mode: complete_episodes
|
||||
vtrace: true
|
||||
|
||||
num_envs_per_env_runner: 5
|
||||
num_env_runners: 4
|
||||
num_gpus: 0
|
||||
num_epochs: 1
|
||||
vf_loss_coeff: 0.01
|
||||
@@ -0,0 +1,50 @@
|
||||
# @OldAPIStack
|
||||
halfcheetah_bc:
|
||||
env:
|
||||
grid_search:
|
||||
#- ray.rllib.examples.envs.classes.d4rl_env.halfcheetah_random
|
||||
#- ray.rllib.examples.envs.classes.d4rl_env.halfcheetah_medium
|
||||
- ray.rllib.examples.envs.classes.d4rl_env.halfcheetah_expert
|
||||
#- ray.rllib.examples.envs.classes.d4rl_env.halfcheetah_medium_replay
|
||||
run: CQL
|
||||
config:
|
||||
# SAC Configs
|
||||
#input: d4rl.halfcheetah-random-v0
|
||||
#input: d4rl.halfcheetah-medium-v0
|
||||
input: d4rl.halfcheetah-expert-v0
|
||||
#input: d4rl.halfcheetah-medium-replay-v0
|
||||
framework: torch
|
||||
q_model_config:
|
||||
fcnet_activation: relu
|
||||
fcnet_hiddens: [256, 256, 256]
|
||||
policy_model_config:
|
||||
fcnet_activation: relu
|
||||
fcnet_hiddens: [256, 256, 256]
|
||||
tau: 0.005
|
||||
target_entropy: auto
|
||||
n_step: 1
|
||||
rollout_fragment_length: 1
|
||||
replay_buffer_config:
|
||||
type: MultiAgentReplayBuffer
|
||||
num_steps_sampled_before_learning_starts: 10
|
||||
train_batch_size: 256
|
||||
target_network_update_freq: 0
|
||||
min_train_timesteps_per_iteration: 1000
|
||||
optimization:
|
||||
actor_learning_rate: 0.0001
|
||||
critic_learning_rate: 0.0003
|
||||
entropy_learning_rate: 0.0001
|
||||
num_env_runners: 0
|
||||
num_gpus: 1
|
||||
clip_actions: false
|
||||
normalize_actions: true
|
||||
evaluation_interval: 1
|
||||
metrics_num_episodes_for_smoothing: 5
|
||||
# CQL Configs
|
||||
min_q_weight: 5.0
|
||||
bc_iters: 200000000
|
||||
temperature: 1.0
|
||||
num_actions: 10
|
||||
lagrangian: False
|
||||
evaluation_config:
|
||||
input: sampler
|
||||
@@ -0,0 +1,51 @@
|
||||
# @OldAPIStack
|
||||
halfcheetah_cql:
|
||||
env:
|
||||
grid_search:
|
||||
#- ray.rllib.examples.envs.classes.d4rl_env.halfcheetah_random
|
||||
#- ray.rllib.examples.envs.classes.d4rl_env.halfcheetah_medium
|
||||
- ray.rllib.examples.envs.classes.d4rl_env.halfcheetah_expert
|
||||
#- ray.rllib.examples.envs.classes.d4rl_env.halfcheetah_medium_replay
|
||||
run: CQL
|
||||
config:
|
||||
# SAC Configs
|
||||
#input: d4rl.halfcheetah-random-v0
|
||||
#input: d4rl.halfcheetah-medium-v0
|
||||
input: d4rl.halfcheetah-expert-v0
|
||||
#input: d4rl.halfcheetah-medium-replay-v0
|
||||
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
q_model_config:
|
||||
fcnet_activation: relu
|
||||
fcnet_hiddens: [256, 256, 256]
|
||||
policy_model_config:
|
||||
fcnet_activation: relu
|
||||
fcnet_hiddens: [256, 256, 256]
|
||||
tau: 0.005
|
||||
target_entropy: auto
|
||||
n_step: 3
|
||||
rollout_fragment_length: 1
|
||||
replay_buffer_config:
|
||||
type: MultiAgentReplayBuffer
|
||||
num_steps_sampled_before_learning_starts: 256
|
||||
train_batch_size: 256
|
||||
target_network_update_freq: 0
|
||||
min_train_timesteps_per_iteration: 1000
|
||||
optimization:
|
||||
actor_learning_rate: 0.0001
|
||||
critic_learning_rate: 0.0003
|
||||
entropy_learning_rate: 0.0001
|
||||
num_env_runners: 0
|
||||
num_gpus: 1
|
||||
metrics_num_episodes_for_smoothing: 5
|
||||
# CQL Configs
|
||||
min_q_weight: 5.0
|
||||
bc_iters: 20000
|
||||
temperature: 1.0
|
||||
num_actions: 10
|
||||
lagrangian: False
|
||||
|
||||
evaluation_interval: 3
|
||||
evaluation_config:
|
||||
input: sampler
|
||||
@@ -0,0 +1,26 @@
|
||||
# @OldAPIStack
|
||||
halfcheetah-ppo:
|
||||
env: HalfCheetah-v2
|
||||
run: PPO
|
||||
stop:
|
||||
env_runners/episode_return_mean: 9800
|
||||
time_total_s: 10800
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
gamma: 0.99
|
||||
lambda: 0.95
|
||||
kl_coeff: 1.0
|
||||
num_epochs: 32
|
||||
lr: .0003
|
||||
vf_loss_coeff: 0.5
|
||||
clip_param: 0.2
|
||||
minibatch_size: 4096
|
||||
train_batch_size: 65536
|
||||
num_env_runners: 16
|
||||
num_gpus: 1
|
||||
grad_clip: 0.5
|
||||
num_envs_per_env_runner:
|
||||
grid_search: [16, 32]
|
||||
batch_mode: truncate_episodes
|
||||
observation_filter: MeanStdFilter
|
||||
@@ -0,0 +1,50 @@
|
||||
# @OldAPIStack
|
||||
hopper_bc:
|
||||
env:
|
||||
grid_search:
|
||||
- ray.rllib.examples.envs.classes.d4rl_env.hopper_random
|
||||
#- ray.rllib.examples.envs.classes..d4rl_env.hopper_medium
|
||||
#- ray.rllib.examples.envs.classes..d4rl_env.hopper_expert
|
||||
#- ray.rllib.examples.envs.classes..d4rl_env.hopper_medium_replay
|
||||
run: CQL
|
||||
config:
|
||||
# SAC Configs
|
||||
input: d4rl.hopper-random-v0
|
||||
#input: d4rl.hopper-medium-v0
|
||||
#input: d4rl.hopper-expert-v0
|
||||
#input: d4rl.hopper-medium-replay-v0
|
||||
framework: torch
|
||||
q_model_config:
|
||||
fcnet_activation: relu
|
||||
fcnet_hiddens: [256, 256, 256]
|
||||
policy_model_config:
|
||||
fcnet_activation: relu
|
||||
fcnet_hiddens: [256, 256, 256]
|
||||
tau: 0.005
|
||||
target_entropy: auto
|
||||
n_step: 1
|
||||
rollout_fragment_length: 1
|
||||
replay_buffer_config:
|
||||
type: MultiAgentReplayBuffer
|
||||
num_steps_sampled_before_learning_starts: 10
|
||||
train_batch_size: 256
|
||||
target_network_update_freq: 0
|
||||
min_train_timesteps_per_iteration: 1000
|
||||
optimization:
|
||||
actor_learning_rate: 0.0001
|
||||
critic_learning_rate: 0.0003
|
||||
entropy_learning_rate: 0.0001
|
||||
num_env_runners: 0
|
||||
num_gpus: 1
|
||||
clip_actions: false
|
||||
normalize_actions: true
|
||||
evaluation_interval: 1
|
||||
metrics_num_episodes_for_smoothing: 5
|
||||
# CQL Configs
|
||||
min_q_weight: 5.0
|
||||
bc_iters: 200000000
|
||||
temperature: 1.0
|
||||
num_actions: 10
|
||||
lagrangian: False
|
||||
evaluation_config:
|
||||
input: sampler
|
||||
@@ -0,0 +1,50 @@
|
||||
# @OldAPIStack
|
||||
hopper_cql:
|
||||
env:
|
||||
grid_search:
|
||||
#- ray.rllib.examples.envs.classes.d4rl_env.hopper_random
|
||||
- ray.rllib.examples.envs.classes.d4rl_env.hopper_medium
|
||||
#- ray.rllib.examples.envs.classes.d4rl_env.hopper_expert
|
||||
#- ray.rllib.examples.envs.classes.d4rl_env.hopper_medium_replay
|
||||
run: CQL
|
||||
config:
|
||||
# SAC Configs
|
||||
#input: d4rl.hopper-random-v0
|
||||
input: d4rl.hopper-medium-v0
|
||||
#input: d4rl.hopper-expert-v0
|
||||
#input: d4rl.hopper-medium-replay-v0
|
||||
framework: torch
|
||||
q_model_config:
|
||||
fcnet_activation: relu
|
||||
fcnet_hiddens: [256, 256, 256]
|
||||
policy_model_config:
|
||||
fcnet_activation: relu
|
||||
fcnet_hiddens: [256, 256, 256]
|
||||
tau: 0.005
|
||||
target_entropy: auto
|
||||
n_step: 1
|
||||
rollout_fragment_length: 1
|
||||
replay_buffer_config:
|
||||
type: MultiAgentReplayBuffer
|
||||
num_steps_sampled_before_learning_starts: 10
|
||||
train_batch_size: 256
|
||||
target_network_update_freq: 0
|
||||
min_train_timesteps_per_iteration: 1000
|
||||
optimization:
|
||||
actor_learning_rate: 0.0001
|
||||
critic_learning_rate: 0.0003
|
||||
entropy_learning_rate: 0.0001
|
||||
num_env_runners: 0
|
||||
num_gpus: 1
|
||||
clip_actions: false
|
||||
normalize_actions: true
|
||||
evaluation_interval: 1
|
||||
metrics_num_episodes_for_smoothing: 5
|
||||
# CQL Configs
|
||||
min_q_weight: 5.0
|
||||
bc_iters: 20000
|
||||
temperature: 1.0
|
||||
num_actions: 10
|
||||
lagrangian: False
|
||||
evaluation_config:
|
||||
input: sampler
|
||||
@@ -0,0 +1,17 @@
|
||||
# @OldAPIStack
|
||||
hopper-ppo:
|
||||
env: Hopper-v1
|
||||
run: PPO
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
gamma: 0.995
|
||||
kl_coeff: 1.0
|
||||
num_epochs: 20
|
||||
lr: .0001
|
||||
minibatch_size: 32768
|
||||
train_batch_size: 160000
|
||||
num_env_runners: 64
|
||||
num_gpus: 4
|
||||
batch_mode: complete_episodes
|
||||
observation_filter: MeanStdFilter
|
||||
@@ -0,0 +1,24 @@
|
||||
# @OldAPIStack
|
||||
humanoid-ppo-gae:
|
||||
env: Humanoid-v1
|
||||
run: PPO
|
||||
stop:
|
||||
env_runners/episode_return_mean: 6000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
gamma: 0.995
|
||||
lambda: 0.95
|
||||
clip_param: 0.2
|
||||
kl_coeff: 1.0
|
||||
num_epochs: 20
|
||||
lr: .0001
|
||||
minibatch_size: 32768
|
||||
horizon: 5000
|
||||
train_batch_size: 320000
|
||||
model:
|
||||
free_log_std: true
|
||||
num_env_runners: 64
|
||||
num_gpus: 4
|
||||
batch_mode: complete_episodes
|
||||
observation_filter: MeanStdFilter
|
||||
@@ -0,0 +1,22 @@
|
||||
# @OldAPIStack
|
||||
humanoid-ppo:
|
||||
env: Humanoid-v1
|
||||
run: PPO
|
||||
stop:
|
||||
env_runners/episode_return_mean: 6000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
gamma: 0.995
|
||||
kl_coeff: 1.0
|
||||
num_epochs: 20
|
||||
lr: .0001
|
||||
minibatch_size: 32768
|
||||
train_batch_size: 320000
|
||||
model:
|
||||
free_log_std: true
|
||||
use_gae: false
|
||||
num_env_runners: 64
|
||||
num_gpus: 4
|
||||
batch_mode: complete_episodes
|
||||
observation_filter: MeanStdFilter
|
||||
@@ -0,0 +1,15 @@
|
||||
# @OldAPIStack
|
||||
memory-leak-test-appo:
|
||||
env:
|
||||
ray.rllib.examples.envs.classes.random_env.RandomLargeObsSpaceEnv
|
||||
run: APPO
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
# Switch off np.random, which is known to have memory leaks.
|
||||
env_config:
|
||||
config:
|
||||
static_samples: true
|
||||
num_env_runners: 4
|
||||
num_envs_per_env_runner: 5
|
||||
rollout_fragment_length: 20
|
||||
@@ -0,0 +1,14 @@
|
||||
# @OldAPIStack
|
||||
memory-leak-test-dqn:
|
||||
env:
|
||||
ray.rllib.examples.envs.classes.random_env.RandomLargeObsSpaceEnv
|
||||
run: DQN
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
# Switch off np.random, which is known to have memory leaks.
|
||||
env_config:
|
||||
config:
|
||||
static_samples: true
|
||||
replay_buffer_config:
|
||||
capacity: 500 # use small buffer to catch memory leaks
|
||||
@@ -0,0 +1,17 @@
|
||||
# @OldAPIStack
|
||||
memory-leak-test-ppo:
|
||||
env:
|
||||
ray.rllib.examples.envs.classes.random_env.RandomLargeObsSpaceEnv
|
||||
run: PPO
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
# Switch off np.random, which is known to have memory leaks.
|
||||
env_config:
|
||||
config:
|
||||
static_samples: true
|
||||
num_env_runners: 4
|
||||
num_envs_per_env_runner: 5
|
||||
train_batch_size: 500
|
||||
minibatch_size: 256
|
||||
num_epochs: 5
|
||||
@@ -0,0 +1,14 @@
|
||||
# @OldAPIStack
|
||||
memory-leak-test-sac:
|
||||
env:
|
||||
ray.rllib.examples.envs.classes.random_env.RandomLargeObsSpaceEnvContActions
|
||||
run: SAC
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
# Switch off np.random, which is known to have memory leaks.
|
||||
env_config:
|
||||
config:
|
||||
static_samples: true
|
||||
replay_buffer_config:
|
||||
capacity: 500 # use small buffer to catch memory leaks
|
||||
@@ -0,0 +1,45 @@
|
||||
# @OldAPIStack
|
||||
# Our implementation of SAC discrete can reach up
|
||||
# to ~750 reward in 40k timesteps. Run e.g. on a g3.4xlarge with `num_gpus=1`.
|
||||
# Uses the hyperparameters published in [2] (see rllib/algorithms/sac/README.md).
|
||||
mspacman-sac-tf:
|
||||
env: ale_py:ALE/MsPacman-v5
|
||||
run: SAC
|
||||
stop:
|
||||
env_runners/episode_return_mean: 800
|
||||
timesteps_total: 100000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
env_config:
|
||||
frameskip: 1 # no frameskip
|
||||
gamma: 0.99
|
||||
q_model_config:
|
||||
fcnet_hiddens: [512]
|
||||
fcnet_activation: relu
|
||||
policy_model_config:
|
||||
fcnet_hiddens: [512]
|
||||
fcnet_activation: relu
|
||||
# Do hard syncs.
|
||||
# Soft-syncs seem to work less reliably for discrete action spaces.
|
||||
tau: 1.0
|
||||
target_network_update_freq: 8000
|
||||
# paper uses: 0.98 * -log(1/|A|)
|
||||
target_entropy: 1.755
|
||||
clip_rewards: 1.0
|
||||
n_step: 1
|
||||
rollout_fragment_length: 1
|
||||
train_batch_size: 64
|
||||
min_sample_timesteps_per_iteration: 4
|
||||
# Paper uses 20k random timesteps, which is not exactly the same, but
|
||||
# seems to work nevertheless.
|
||||
replay_buffer_config:
|
||||
type: MultiAgentPrioritizedReplayBuffer
|
||||
num_steps_sampled_before_learning_starts: 20000
|
||||
optimization:
|
||||
actor_learning_rate: 0.0003
|
||||
critic_learning_rate: 0.0003
|
||||
entropy_learning_rate: 0.0003
|
||||
num_env_runners: 0
|
||||
num_gpus: 0
|
||||
metrics_num_episodes_for_smoothing: 5
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
# @OldAPIStack
|
||||
"""
|
||||
Tests, whether APPO can learn in a fault-tolerant fashion in a
|
||||
multi-agent setting.
|
||||
|
||||
Workers will be configured to automatically get recreated upon failures (here: within
|
||||
the environment).
|
||||
The environment we use here is configured to crash with a certain probability on each
|
||||
`step()` and/or `reset()` call.
|
||||
"""
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.appo import APPOConfig
|
||||
from ray.rllib.examples.envs.classes.cartpole_crashing import MultiAgentCartPoleCrashing
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
EVALUATION_RESULTS,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
|
||||
tune.register_env("ma_env", lambda cfg: MultiAgentCartPoleCrashing(cfg))
|
||||
|
||||
stop = {
|
||||
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 800.0,
|
||||
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 250000,
|
||||
}
|
||||
|
||||
config = (
|
||||
APPOConfig()
|
||||
.api_stack(
|
||||
enable_rl_module_and_learner=False,
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
)
|
||||
.environment(
|
||||
"ma_env",
|
||||
env_config={
|
||||
"num_agents": 2,
|
||||
# Crash roughly every 300 ts. This should be ok to measure 180.0
|
||||
# reward (episodes are 200 ts long).
|
||||
"p_crash": 0.00005, # prob to crash during step()
|
||||
"p_crash_reset": 0.0005, # prob to crash during reset()
|
||||
"init_time_s": 2.0,
|
||||
"p_stall": 0.001, # prob to stall during step()
|
||||
"p_stall_reset": 0.001, # prob to stall during reset()
|
||||
"stall_time_sec": (2, 5), # stall between 2 and 10sec.
|
||||
"stall_on_worker_indices": [2, 3],
|
||||
},
|
||||
)
|
||||
.env_runners(
|
||||
num_env_runners=3,
|
||||
num_envs_per_env_runner=1,
|
||||
)
|
||||
# Switch on resiliency (recreate any failed worker).
|
||||
.fault_tolerance(
|
||||
restart_failed_env_runners=True,
|
||||
)
|
||||
.evaluation(
|
||||
evaluation_num_env_runners=1,
|
||||
evaluation_interval=1,
|
||||
evaluation_duration=25,
|
||||
evaluation_duration_unit="episodes",
|
||||
evaluation_parallel_to_training=True,
|
||||
evaluation_config=APPOConfig.overrides(
|
||||
explore=False,
|
||||
env_config={
|
||||
# Make eval workers solid.
|
||||
# This test is to prove that we can learn with crashing envs,
|
||||
# not evaluate with crashing envs.
|
||||
"p_crash": 0.0,
|
||||
"p_crash_reset": 0.0,
|
||||
"init_time_s": 0.0,
|
||||
"p_stall": 0.0,
|
||||
"p_stall_reset": 0.0,
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
# @OldAPIStack
|
||||
"""
|
||||
Tests, whether APPO can learn in a fault-tolerant fashion in a
|
||||
multi-agent setting.
|
||||
|
||||
Workers will be configured to automatically get recreated upon failures (here: within
|
||||
the environment).
|
||||
The environment we use here is configured to crash with a certain probability on each
|
||||
`step()` and/or `reset()` call.
|
||||
"""
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.appo import APPOConfig
|
||||
from ray.rllib.examples.envs.classes.cartpole_crashing import MultiAgentCartPoleCrashing
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
EVALUATION_RESULTS,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
|
||||
tune.register_env("ma_env", lambda cfg: MultiAgentCartPoleCrashing(cfg))
|
||||
|
||||
stop = {
|
||||
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 800.0,
|
||||
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 250000,
|
||||
}
|
||||
|
||||
config = (
|
||||
APPOConfig()
|
||||
.api_stack(
|
||||
enable_rl_module_and_learner=False,
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
)
|
||||
.environment(
|
||||
"ma_env",
|
||||
env_config={
|
||||
"num_agents": 2,
|
||||
# Crash roughly every 300 ts. This should be ok to measure 180.0
|
||||
# reward (episodes are 200 ts long).
|
||||
"p_crash": 0.0005, # prob to crash during step()
|
||||
"p_crash_reset": 0.005, # prob to crash during reset()
|
||||
},
|
||||
)
|
||||
.env_runners(
|
||||
num_env_runners=4,
|
||||
num_envs_per_env_runner=1,
|
||||
)
|
||||
# Switch on resiliency (recreate any failed worker).
|
||||
.fault_tolerance(
|
||||
restart_failed_env_runners=True,
|
||||
)
|
||||
.evaluation(
|
||||
evaluation_num_env_runners=1,
|
||||
evaluation_interval=1,
|
||||
evaluation_duration=25,
|
||||
evaluation_duration_unit="episodes",
|
||||
evaluation_parallel_to_training=True,
|
||||
evaluation_config=APPOConfig.overrides(
|
||||
explore=False,
|
||||
env_config={
|
||||
# Make eval workers solid.
|
||||
# This test is to prove that we can learn with crashing envs,
|
||||
# not evaluate with crashing envs.
|
||||
"p_crash": 0.0,
|
||||
"p_crash_reset": 0.0,
|
||||
"init_time_s": 0.0,
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,83 @@
|
||||
# @OldAPIStack
|
||||
import numpy as np
|
||||
|
||||
from ray.rllib.algorithms.appo import APPOConfig
|
||||
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentCartPole
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EVALUATION_RESULTS,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
from ray.tune.registry import register_env
|
||||
|
||||
register_env("multi_cartpole", lambda _: MultiAgentCartPole({"num_agents": 2}))
|
||||
|
||||
# Number of policies overall in the PolicyMap.
|
||||
num_policies = 20
|
||||
# Number of those policies that should be trained. These are a subset of `num_policies`.
|
||||
num_trainable = 10
|
||||
|
||||
num_envs_per_env_runner = 5
|
||||
|
||||
# Define the config as an APPOConfig object.
|
||||
config = (
|
||||
APPOConfig()
|
||||
.api_stack(
|
||||
enable_rl_module_and_learner=False,
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
)
|
||||
.environment("multi_cartpole")
|
||||
.env_runners(
|
||||
num_env_runners=4,
|
||||
num_envs_per_env_runner=num_envs_per_env_runner,
|
||||
observation_filter="MeanStdFilter",
|
||||
)
|
||||
.training(
|
||||
model={
|
||||
"fcnet_hiddens": [32],
|
||||
"fcnet_activation": "linear",
|
||||
"vf_share_layers": True,
|
||||
},
|
||||
num_epochs=1,
|
||||
vf_loss_coeff=0.005,
|
||||
vtrace=True,
|
||||
)
|
||||
.multi_agent(
|
||||
# 2 agents per sub-env.
|
||||
# This is to avoid excessive swapping during an episode rollout, since
|
||||
# Policies are only re-picked at the beginning of each episode.
|
||||
policy_map_capacity=2 * num_envs_per_env_runner,
|
||||
policy_states_are_swappable=True,
|
||||
policies={f"pol{i}" for i in range(num_policies)},
|
||||
# Train only the first n policies.
|
||||
policies_to_train=[f"pol{i}" for i in range(num_trainable)],
|
||||
# Pick one trainable and one non-trainable policy per episode.
|
||||
policy_mapping_fn=(
|
||||
lambda aid, eps, worker, **kw: "pol"
|
||||
+ str(
|
||||
np.random.randint(0, num_trainable)
|
||||
if aid == 0
|
||||
else np.random.randint(num_trainable, num_policies)
|
||||
)
|
||||
),
|
||||
)
|
||||
# On the eval track, always let policy 0 play so we get its results in each results
|
||||
# dict.
|
||||
.evaluation(
|
||||
evaluation_config=APPOConfig.overrides(
|
||||
policy_mapping_fn=(
|
||||
lambda aid, eps, worker, **kw: "pol"
|
||||
+ str(0 if aid == 0 else np.random.randint(num_trainable, num_policies))
|
||||
),
|
||||
),
|
||||
evaluation_num_env_runners=2,
|
||||
evaluation_interval=1,
|
||||
evaluation_parallel_to_training=True,
|
||||
)
|
||||
)
|
||||
|
||||
# Define some stopping criteria.
|
||||
stop = {
|
||||
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/policy_reward_mean/pol0": 50.0,
|
||||
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 500000,
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
# @OldAPIStack
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.appo import APPOConfig
|
||||
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentCartPole
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
|
||||
tune.registry.register_env("env", lambda cfg: MultiAgentCartPole(config=cfg))
|
||||
|
||||
|
||||
config = (
|
||||
APPOConfig()
|
||||
.api_stack(
|
||||
enable_rl_module_and_learner=False,
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
)
|
||||
.environment("env", env_config={"num_agents": 4})
|
||||
.env_runners(
|
||||
num_envs_per_env_runner=5,
|
||||
num_env_runners=4,
|
||||
observation_filter="MeanStdFilter",
|
||||
)
|
||||
.resources(num_gpus=1, _fake_gpus=True)
|
||||
.multi_agent(
|
||||
policies=["p0", "p1", "p2", "p3"],
|
||||
policy_mapping_fn=(lambda agent_id, episode, worker, **kwargs: f"p{agent_id}"),
|
||||
)
|
||||
.training(
|
||||
num_epochs=1,
|
||||
vf_loss_coeff=0.005,
|
||||
vtrace=True,
|
||||
model={
|
||||
"fcnet_hiddens": [32],
|
||||
"fcnet_activation": "linear",
|
||||
"vf_share_layers": True,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
stop = {
|
||||
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 600, # 600 / 4 (==num_agents) = 150
|
||||
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000,
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
# @OldAPIStack
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.impala import IMPALAConfig
|
||||
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentCartPole
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
|
||||
tune.registry.register_env("env", lambda cfg: MultiAgentCartPole(config=cfg))
|
||||
|
||||
|
||||
config = (
|
||||
IMPALAConfig()
|
||||
.api_stack(
|
||||
enable_rl_module_and_learner=False,
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
)
|
||||
.environment("env", env_config={"num_agents": 4})
|
||||
.env_runners(
|
||||
num_envs_per_env_runner=5,
|
||||
num_env_runners=4,
|
||||
observation_filter="MeanStdFilter",
|
||||
)
|
||||
.resources(num_gpus=1, _fake_gpus=True)
|
||||
.multi_agent(
|
||||
policies=["p0", "p1", "p2", "p3"],
|
||||
policy_mapping_fn=(lambda agent_id, episode, worker, **kwargs: f"p{agent_id}"),
|
||||
)
|
||||
.training(
|
||||
num_epochs=1,
|
||||
vf_loss_coeff=0.005,
|
||||
vtrace=True,
|
||||
model={
|
||||
"fcnet_hiddens": [32],
|
||||
"fcnet_activation": "linear",
|
||||
"vf_share_layers": True,
|
||||
},
|
||||
replay_proportion=0.0,
|
||||
)
|
||||
)
|
||||
|
||||
stop = {
|
||||
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": 600, # 600 / 4 (==num_agents) = 150
|
||||
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": 200000,
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# @OldAPIStack
|
||||
# Given a SAC-generated offline file generated via:
|
||||
# rllib train -f examples/algorithms/sac/pendulum-sac.yaml --no-ray-ui
|
||||
|
||||
# Pendulum CQL can attain ~ -300 reward in 10k from that file.
|
||||
pendulum-cql:
|
||||
env: Pendulum-v1
|
||||
run: CQL
|
||||
stop:
|
||||
evaluation/env_runners/episode_return_mean: -700
|
||||
timesteps_total: 800000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
|
||||
# Set seed.
|
||||
seed: 0
|
||||
|
||||
# Use one or more offline files or "input: sampler" for online learning.
|
||||
input: 'dataset'
|
||||
input_config:
|
||||
paths: ["offline/tests/data/pendulum/enormous.zip"]
|
||||
format: 'json'
|
||||
# Our input file above comes from an SAC run. Actions in there
|
||||
# are already normalized (produced by SquashedGaussian).
|
||||
actions_in_input_normalized: true
|
||||
clip_actions: true
|
||||
|
||||
twin_q: true
|
||||
train_batch_size: 2000
|
||||
bc_iters: 100
|
||||
num_env_runners: 2
|
||||
min_time_s_per_iteration: 10
|
||||
|
||||
metrics_num_episodes_for_smoothing: 5
|
||||
|
||||
# Evaluate in an actual environment.
|
||||
evaluation_interval: 1
|
||||
evaluation_num_env_runners: 2
|
||||
evaluation_duration: 10
|
||||
evaluation_parallel_to_training: true
|
||||
evaluation_config:
|
||||
input: sampler
|
||||
explore: False
|
||||
@@ -0,0 +1,35 @@
|
||||
# @OldAPIStack
|
||||
# Pendulum SAC can attain -150+ reward in 6-7k
|
||||
# Configurations are the similar to original softlearning/sac codebase
|
||||
pendulum-sac:
|
||||
env: Pendulum-v1
|
||||
run: SAC
|
||||
stop:
|
||||
env_runners/episode_return_mean: -250
|
||||
timesteps_total: 10000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
q_model_config:
|
||||
fcnet_activation: relu
|
||||
fcnet_hiddens: [256, 256]
|
||||
policy_model_config:
|
||||
fcnet_activation: relu
|
||||
fcnet_hiddens: [256, 256]
|
||||
tau: 0.005
|
||||
target_entropy: auto
|
||||
n_step: 1
|
||||
rollout_fragment_length: 1
|
||||
train_batch_size: 256
|
||||
target_network_update_freq: 1
|
||||
min_sample_timesteps_per_iteration: 1000
|
||||
replay_buffer_config:
|
||||
type: MultiAgentPrioritizedReplayBuffer
|
||||
num_steps_sampled_before_learning_starts: 256
|
||||
optimization:
|
||||
actor_learning_rate: 0.0003
|
||||
critic_learning_rate: 0.0003
|
||||
entropy_learning_rate: 0.0003
|
||||
num_env_runners: 0
|
||||
num_gpus: 0
|
||||
metrics_num_episodes_for_smoothing: 5
|
||||
@@ -0,0 +1,31 @@
|
||||
# @OldAPIStack
|
||||
# Can expect improvement to -140 reward in ~300-500k timesteps.
|
||||
pendulum-ppo:
|
||||
env: ray.rllib.examples.envs.classes.transformed_action_space_env.TransformedActionPendulum
|
||||
run: PPO
|
||||
stop:
|
||||
env_runners/episode_return_mean: -500
|
||||
timesteps_total: 400000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
|
||||
# Test, whether PPO is able to learn in "distorted" action spaces.
|
||||
env_config:
|
||||
config:
|
||||
low: 300.0
|
||||
high: 500.0
|
||||
|
||||
normalize_actions: true
|
||||
clip_actions: false
|
||||
vf_clip_param: 10.0
|
||||
num_envs_per_env_runner: 20
|
||||
lambda: 0.1
|
||||
gamma: 0.95
|
||||
lr: 0.0003
|
||||
train_batch_size: 512
|
||||
minibatch_size: 64
|
||||
num_epochs: 6
|
||||
observation_filter: MeanStdFilter
|
||||
model:
|
||||
fcnet_activation: relu
|
||||
@@ -0,0 +1,44 @@
|
||||
# @OldAPIStack
|
||||
# TransformedActionPendulum SAC can attain -150+ reward in 6-7k
|
||||
# Configurations are the similar to original softlearning/sac codebase
|
||||
transformed-actions-pendulum-sac-dummy-torch:
|
||||
env: ray.rllib.examples.envs.classes.transformed_action_space_env.TransformedActionPendulum
|
||||
run: SAC
|
||||
stop:
|
||||
env_runners/episode_return_mean: -200
|
||||
timesteps_total: 10000
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
seed: 42
|
||||
framework: torch
|
||||
|
||||
# Test, whether SAC is able to learn in "distorted" action spaces.
|
||||
env_config:
|
||||
config:
|
||||
low: 300.0
|
||||
high: 500.0
|
||||
|
||||
horizon: 200
|
||||
q_model_config:
|
||||
fcnet_activation: relu
|
||||
fcnet_hiddens: [256, 256]
|
||||
policy_model_config:
|
||||
fcnet_activation: relu
|
||||
fcnet_hiddens: [256, 256]
|
||||
tau: 0.005
|
||||
target_entropy: auto
|
||||
n_step: 1
|
||||
rollout_fragment_length: 1
|
||||
train_batch_size: 256
|
||||
target_network_update_freq: 1
|
||||
min_sample_timesteps_per_iteration: 1000
|
||||
replay_buffer_config:
|
||||
type: MultiAgentPrioritizedReplayBuffer
|
||||
num_steps_sampled_before_learning_starts: 256
|
||||
optimization:
|
||||
actor_learning_rate: 0.0003
|
||||
critic_learning_rate: 0.0003
|
||||
entropy_learning_rate: 0.0003
|
||||
num_env_runners: 0
|
||||
num_gpus: 0
|
||||
metrics_num_episodes_for_smoothing: 5
|
||||
@@ -0,0 +1,35 @@
|
||||
# @OldAPIStack
|
||||
# You can expect ~20 reward within 1.1m timesteps / 2.1 hours on a K80 GPU
|
||||
pong-deterministic-dqn:
|
||||
env: ale_py:ALE/Pong-v5
|
||||
run: DQN
|
||||
stop:
|
||||
env_runners/episode_return_mean: 20
|
||||
time_total_s: 7200
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
# Make analogous to old v4 + NoFrameskip.
|
||||
env_config:
|
||||
frameskip: 1
|
||||
full_action_space: false
|
||||
repeat_action_probability: 0.0
|
||||
num_gpus: 1
|
||||
gamma: 0.99
|
||||
lr: .0001
|
||||
replay_buffer_config:
|
||||
type: MultiAgentPrioritizedReplayBuffer
|
||||
capacity: 50000
|
||||
num_steps_sampled_before_learning_starts: 10000
|
||||
rollout_fragment_length: 4
|
||||
train_batch_size: 32
|
||||
exploration_config:
|
||||
epsilon_timesteps: 200000
|
||||
final_epsilon: .01
|
||||
model:
|
||||
grayscale: True
|
||||
zero_mean: False
|
||||
dim: 42
|
||||
# we should set compress_observations to True because few machines
|
||||
# would be able to contain the replay buffers in memory otherwise
|
||||
compress_observations: True
|
||||
@@ -0,0 +1,25 @@
|
||||
# @OldAPIStack
|
||||
# This can reach 18-19 reward in ~3 minutes on p3.16xl head w/m4.16xl workers
|
||||
# 128 workers -> 3 minutes (best case)
|
||||
# 64 workers -> 4 minutes
|
||||
# 32 workers -> 7 minutes
|
||||
# See also: pong-impala.yaml, pong-impala-vectorized.yaml
|
||||
pong-impala-fast:
|
||||
env: ale_py:ALE/Pong-v5
|
||||
run: IMPALA
|
||||
config:
|
||||
# Make analogous to old v4 + NoFrameskip.
|
||||
env_config:
|
||||
frameskip: 1
|
||||
full_action_space: false
|
||||
repeat_action_probability: 0.0
|
||||
rollout_fragment_length: 50
|
||||
train_batch_size: 1000
|
||||
num_env_runners: 128
|
||||
num_envs_per_env_runner: 5
|
||||
broadcast_interval: 5
|
||||
max_sample_requests_in_flight_per_worker: 1
|
||||
num_multi_gpu_tower_stacks: 4
|
||||
num_gpus: 2
|
||||
model:
|
||||
dim: 42
|
||||
@@ -0,0 +1,17 @@
|
||||
# @OldAPIStack
|
||||
# This can reach 18-19 reward within 10 minutes on a Tesla M60 GPU (e.g., G3 EC2 node)
|
||||
# with 32 workers and 10 envs per worker. This is more efficient than the non-vectorized
|
||||
# configuration which requires 128 workers to achieve the same performance.
|
||||
pong-impala-vectorized:
|
||||
env: ale_py:ALE/Pong-v5
|
||||
run: IMPALA
|
||||
config:
|
||||
# Make analogous to old v4 + NoFrameskip.
|
||||
env_config:
|
||||
frameskip: 1
|
||||
full_action_space: false
|
||||
repeat_action_probability: 0.0
|
||||
rollout_fragment_length: 50
|
||||
train_batch_size: 500
|
||||
num_env_runners: 32
|
||||
num_envs_per_env_runner: 10
|
||||
@@ -0,0 +1,19 @@
|
||||
# @OldAPIStack
|
||||
# This can reach 18-19 reward within 10 minutes on a Tesla M60 GPU (e.g., G3 EC2 node):
|
||||
# 128 workers -> 8 minutes
|
||||
# 32 workers -> 17 minutes
|
||||
# 16 workers -> 40 min+
|
||||
# See also: pong-impala-fast.yaml, pong-impala-vectorized.yaml
|
||||
pong-impala:
|
||||
env: ale_py:ALE/Pong-v5
|
||||
run: IMPALA
|
||||
config:
|
||||
# Make analogous to old v4 + NoFrameskip.
|
||||
env_config:
|
||||
frameskip: 1
|
||||
full_action_space: false
|
||||
repeat_action_probability: 0.0
|
||||
rollout_fragment_length: 50
|
||||
train_batch_size: 500
|
||||
num_env_runners: 128
|
||||
num_envs_per_env_runner: 1
|
||||
@@ -0,0 +1,37 @@
|
||||
# @OldAPIStack
|
||||
pong-deterministic-rainbow:
|
||||
env: ale_py:ALE/Pong-v5
|
||||
run: DQN
|
||||
stop:
|
||||
env_runners/episode_return_mean: 20
|
||||
config:
|
||||
# Make analogous to old v4 + NoFrameskip.
|
||||
env_config:
|
||||
frameskip: 1
|
||||
full_action_space: false
|
||||
repeat_action_probability: 0.0
|
||||
num_atoms: 51
|
||||
noisy: True
|
||||
gamma: 0.99
|
||||
lr: .0001
|
||||
hiddens: [512]
|
||||
rollout_fragment_length: 4
|
||||
train_batch_size: 32
|
||||
exploration_config:
|
||||
epsilon_timesteps: 2
|
||||
final_epsilon: 0.0
|
||||
target_network_update_freq: 500
|
||||
replay_buffer_config:
|
||||
type: MultiAgentPrioritizedReplayBuffer
|
||||
prioritized_replay_alpha: 0.5
|
||||
capacity: 50000
|
||||
num_steps_sampled_before_learning_starts: 10000
|
||||
n_step: 3
|
||||
gpu: True
|
||||
model:
|
||||
grayscale: True
|
||||
zero_mean: False
|
||||
dim: 42
|
||||
# we should set compress_observations to True because few machines
|
||||
# would be able to contain the replay buffers in memory otherwise
|
||||
compress_observations: True
|
||||
@@ -0,0 +1,16 @@
|
||||
# @OldAPIStack
|
||||
walker2d-v1-ppo:
|
||||
env: Walker2d-v1
|
||||
run: PPO
|
||||
config:
|
||||
# Works for both torch and tf.
|
||||
framework: torch
|
||||
kl_coeff: 1.0
|
||||
num_epochs: 20
|
||||
lr: .0001
|
||||
minibatch_size: 32768
|
||||
train_batch_size: 320000
|
||||
num_env_runners: 64
|
||||
num_gpus: 4
|
||||
batch_mode: complete_episodes
|
||||
observation_filter: MeanStdFilter
|
||||
@@ -0,0 +1,78 @@
|
||||
# @OldAPIStack
|
||||
import numpy as np
|
||||
from gymnasium.spaces import Box, Discrete
|
||||
|
||||
from rllib.models.tf.attention_net import TrXLNet
|
||||
|
||||
from ray.rllib.utils.framework import try_import_tf
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
|
||||
|
||||
def bit_shift_generator(seq_length, shift, batch_size):
|
||||
while True:
|
||||
values = np.array([0.0, 1.0], dtype=np.float32)
|
||||
seq = np.random.choice(values, (batch_size, seq_length, 1))
|
||||
targets = np.squeeze(np.roll(seq, shift, axis=1).astype(np.int32))
|
||||
targets[:, :shift] = 0
|
||||
yield seq, targets
|
||||
|
||||
|
||||
def train_loss(targets, outputs):
|
||||
loss = tf.nn.sparse_softmax_cross_entropy_with_logits(
|
||||
labels=targets, logits=outputs
|
||||
)
|
||||
return tf.reduce_mean(loss)
|
||||
|
||||
|
||||
def train_bit_shift(seq_length, num_iterations, print_every_n):
|
||||
|
||||
optimizer = tf.keras.optimizers.Adam(1e-3)
|
||||
|
||||
model = TrXLNet(
|
||||
observation_space=Box(low=0, high=1, shape=(1,), dtype=np.int32),
|
||||
action_space=Discrete(2),
|
||||
num_outputs=2,
|
||||
model_config={"max_seq_len": seq_length},
|
||||
name="trxl",
|
||||
num_transformer_units=1,
|
||||
attention_dim=10,
|
||||
num_heads=5,
|
||||
head_dim=20,
|
||||
position_wise_mlp_dim=20,
|
||||
)
|
||||
|
||||
shift = 10
|
||||
train_batch = 10
|
||||
test_batch = 100
|
||||
data_gen = bit_shift_generator(seq_length, shift=shift, batch_size=train_batch)
|
||||
test_gen = bit_shift_generator(seq_length, shift=shift, batch_size=test_batch)
|
||||
|
||||
@tf.function
|
||||
def update_step(inputs, targets):
|
||||
model_out = model(
|
||||
{"obs": inputs},
|
||||
state=[tf.reshape(inputs, [-1, seq_length, 1])],
|
||||
seq_lens=np.full(shape=(train_batch,), fill_value=seq_length),
|
||||
)
|
||||
optimizer.minimize(
|
||||
lambda: train_loss(targets, model_out), lambda: model.trainable_variables
|
||||
)
|
||||
|
||||
for i, (inputs, targets) in zip(range(num_iterations), data_gen):
|
||||
inputs_in = np.reshape(inputs, [-1, 1])
|
||||
targets_in = np.reshape(targets, [-1])
|
||||
update_step(tf.convert_to_tensor(inputs_in), tf.convert_to_tensor(targets_in))
|
||||
|
||||
if i % print_every_n == 0:
|
||||
test_inputs, test_targets = next(test_gen)
|
||||
print(i, train_loss(test_targets, model(test_inputs)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tf.enable_eager_execution()
|
||||
train_bit_shift(
|
||||
seq_length=20,
|
||||
num_iterations=2000,
|
||||
print_every_n=200,
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
# @OldAPIStack
|
||||
|
||||
"""
|
||||
This example shows two modifications:
|
||||
- How to write a custom Encoder (using MobileNet v2)
|
||||
- How to enhance Catalogs with this custom Encoder
|
||||
|
||||
With the pattern shown in this example, we can enhance Catalogs such that they extend
|
||||
to new observation- or action spaces while retaining their original functionality.
|
||||
"""
|
||||
# __sphinx_doc_begin__
|
||||
import gymnasium as gym
|
||||
import numpy as np
|
||||
|
||||
from ray.rllib.algorithms.ppo.ppo import PPOConfig
|
||||
from ray.rllib.algorithms.ppo.ppo_catalog import PPOCatalog
|
||||
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
|
||||
from ray.rllib.examples._old_api_stack.models.mobilenet_v2_encoder import (
|
||||
MOBILENET_INPUT_SHAPE,
|
||||
MobileNetV2EncoderConfig,
|
||||
)
|
||||
from ray.rllib.examples.envs.classes.random_env import RandomEnv
|
||||
|
||||
|
||||
# Define a PPO Catalog that we can use to inject our MobileNetV2 Encoder into RLlib's
|
||||
# decision tree of what model to choose
|
||||
class MobileNetEnhancedPPOCatalog(PPOCatalog):
|
||||
@classmethod
|
||||
def _get_encoder_config(
|
||||
cls,
|
||||
observation_space: gym.Space,
|
||||
**kwargs,
|
||||
):
|
||||
if (
|
||||
isinstance(observation_space, gym.spaces.Box)
|
||||
and observation_space.shape == MOBILENET_INPUT_SHAPE
|
||||
):
|
||||
# Inject our custom encoder here, only if the observation space fits it
|
||||
return MobileNetV2EncoderConfig()
|
||||
else:
|
||||
return super()._get_encoder_config(observation_space, **kwargs)
|
||||
|
||||
|
||||
# Create a generic config with our enhanced Catalog
|
||||
ppo_config = (
|
||||
PPOConfig()
|
||||
.rl_module(rl_module_spec=RLModuleSpec(catalog_class=MobileNetEnhancedPPOCatalog))
|
||||
.env_runners(num_env_runners=0)
|
||||
# The following training settings make it so that a training iteration is very
|
||||
# quick. This is just for the sake of this example. PPO will not learn properly
|
||||
# with these settings!
|
||||
.training(train_batch_size_per_learner=32, minibatch_size=16, num_epochs=1)
|
||||
)
|
||||
|
||||
# CartPole's observation space is not compatible with our MobileNetV2 Encoder, so
|
||||
# this will use the default behaviour of Catalogs
|
||||
ppo_config.environment("CartPole-v1")
|
||||
results = ppo_config.build().train()
|
||||
print(results)
|
||||
|
||||
# For this training, we use a RandomEnv with observations of shape
|
||||
# MOBILENET_INPUT_SHAPE. This will use our custom Encoder.
|
||||
ppo_config.environment(
|
||||
RandomEnv,
|
||||
env_config={
|
||||
"action_space": gym.spaces.Discrete(2),
|
||||
# Test a simple Image observation space.
|
||||
"observation_space": gym.spaces.Box(
|
||||
0.0,
|
||||
1.0,
|
||||
shape=MOBILENET_INPUT_SHAPE,
|
||||
dtype=np.float32,
|
||||
),
|
||||
},
|
||||
)
|
||||
results = ppo_config.build().train()
|
||||
print(results)
|
||||
# __sphinx_doc_end__
|
||||
@@ -0,0 +1,131 @@
|
||||
# @OldAPIStack
|
||||
|
||||
import numpy as np
|
||||
import onnxruntime
|
||||
|
||||
import ray
|
||||
import ray.rllib.algorithms.ppo as ppo
|
||||
from ray.rllib.examples.utils import add_rllib_example_script_args, check
|
||||
from ray.rllib.utils.framework import try_import_torch
|
||||
from ray.rllib.utils.torch_utils import convert_to_torch_tensor
|
||||
|
||||
torch, _ = try_import_torch()
|
||||
|
||||
parser = add_rllib_example_script_args()
|
||||
parser.set_defaults(
|
||||
num_env_runners=1,
|
||||
# ONNX is not supported by RLModule API yet.
|
||||
old_api_stack=True,
|
||||
)
|
||||
|
||||
|
||||
class ONNXCompatibleWrapper(torch.nn.Module):
|
||||
def __init__(self, original_model):
|
||||
super(ONNXCompatibleWrapper, self).__init__()
|
||||
self.original_model = original_model
|
||||
|
||||
def forward(self, a, b0, b1, c):
|
||||
# Convert the separate tensor inputs back into the list format
|
||||
# expected by the original model's forward method.
|
||||
b = [b0, b1]
|
||||
ret = self.original_model({"obs": a}, b, c)
|
||||
# results, state_out_0, state_out_1
|
||||
return ret[0], ret[1][0], ret[1][1]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
ray.init()
|
||||
|
||||
# Configure our PPO Algorithm.
|
||||
config = (
|
||||
ppo.PPOConfig()
|
||||
.environment("CartPole-v1")
|
||||
.env_runners(num_env_runners=args.num_env_runners)
|
||||
.training(model={"use_lstm": True})
|
||||
)
|
||||
|
||||
B = 3
|
||||
T = 5
|
||||
LSTM_CELL = 256
|
||||
|
||||
# Input data for a python inference forward call.
|
||||
test_data_python = {
|
||||
"obs": np.random.uniform(0, 1.0, size=(B * T, 4)).astype(np.float32),
|
||||
"state_ins": [
|
||||
np.random.uniform(0, 1.0, size=(B, LSTM_CELL)).astype(np.float32),
|
||||
np.random.uniform(0, 1.0, size=(B, LSTM_CELL)).astype(np.float32),
|
||||
],
|
||||
"seq_lens": np.array([T] * B, np.float32),
|
||||
}
|
||||
# Input data for the ONNX session.
|
||||
test_data_onnx = {
|
||||
"obs": test_data_python["obs"],
|
||||
"state_in_0": test_data_python["state_ins"][0],
|
||||
"state_in_1": test_data_python["state_ins"][1],
|
||||
"seq_lens": test_data_python["seq_lens"],
|
||||
}
|
||||
|
||||
# Input data for compiling the ONNX model.
|
||||
test_data_onnx_input = convert_to_torch_tensor(test_data_onnx)
|
||||
|
||||
# Initialize a PPO Algorithm.
|
||||
algo = config.build()
|
||||
|
||||
# You could train the model here
|
||||
# algo.train()
|
||||
|
||||
# Let's run inference on the torch model
|
||||
policy = algo.get_policy()
|
||||
result_pytorch, _ = policy.model(
|
||||
{
|
||||
"obs": torch.tensor(test_data_python["obs"]),
|
||||
},
|
||||
[
|
||||
torch.tensor(test_data_python["state_ins"][0]),
|
||||
torch.tensor(test_data_python["state_ins"][1]),
|
||||
],
|
||||
torch.tensor(test_data_python["seq_lens"]),
|
||||
)
|
||||
|
||||
# Evaluate tensor to fetch numpy array
|
||||
result_pytorch = result_pytorch.detach().numpy()
|
||||
|
||||
# Wrap the actual ModelV2 with the torch wrapper above to make this all work with
|
||||
# LSTMs (extra `state` in- and outputs and `seq_lens` inputs).
|
||||
onnx_compatible = ONNXCompatibleWrapper(policy.model)
|
||||
exported_model_file = "model.onnx"
|
||||
input_names = [
|
||||
"obs",
|
||||
"state_in_0",
|
||||
"state_in_1",
|
||||
"seq_lens",
|
||||
]
|
||||
|
||||
# This line will export the model to ONNX.
|
||||
torch.onnx.export(
|
||||
onnx_compatible,
|
||||
tuple(test_data_onnx_input[n] for n in input_names),
|
||||
exported_model_file,
|
||||
export_params=True,
|
||||
opset_version=11,
|
||||
do_constant_folding=True,
|
||||
input_names=input_names,
|
||||
output_names=[
|
||||
"output",
|
||||
"state_out_0",
|
||||
"state_out_1",
|
||||
],
|
||||
dynamic_axes={k: {0: "batch_size"} for k in input_names},
|
||||
)
|
||||
# Start an inference session for the ONNX model.
|
||||
session = onnxruntime.InferenceSession(exported_model_file, None)
|
||||
result_onnx = session.run(["output"], test_data_onnx)
|
||||
|
||||
# These results should be equal!
|
||||
print("PYTORCH", result_pytorch)
|
||||
print("ONNX", result_onnx[0])
|
||||
|
||||
check(result_pytorch, result_onnx[0])
|
||||
print("Model outputs are equal. PASSED")
|
||||
@@ -0,0 +1,318 @@
|
||||
# @OldAPIStack
|
||||
|
||||
# ***********************************************************************************
|
||||
# IMPORTANT NOTE: This script uses the old API stack and will soon be replaced by
|
||||
# `ray.rllib.examples.multi_agent.pettingzoo_shared_value_function.py`!
|
||||
# ***********************************************************************************
|
||||
|
||||
"""An example of customizing PPO to leverage a centralized critic.
|
||||
|
||||
Here the model and policy are hard-coded to implement a centralized critic
|
||||
for TwoStepGame, but you can adapt this for your own use cases.
|
||||
|
||||
Compared to simply running `rllib/examples/two_step_game.py --run=PPO`,
|
||||
this centralized critic version reaches vf_explained_variance=1.0 more stably
|
||||
since it takes into account the opponent actions as well as the policy's.
|
||||
Note that this is also using two independent policies instead of weight-sharing
|
||||
with one.
|
||||
|
||||
See also: centralized_critic_2.py for a simpler approach that instead
|
||||
modifies the environment.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
from gymnasium.spaces import Discrete
|
||||
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.ppo.ppo import PPO, PPOConfig
|
||||
from ray.rllib.algorithms.ppo.ppo_tf_policy import (
|
||||
PPOTF1Policy,
|
||||
PPOTF2Policy,
|
||||
)
|
||||
from ray.rllib.algorithms.ppo.ppo_torch_policy import PPOTorchPolicy
|
||||
from ray.rllib.evaluation.postprocessing import Postprocessing, compute_advantages
|
||||
from ray.rllib.examples._old_api_stack.models.centralized_critic_models import (
|
||||
CentralizedCriticModel,
|
||||
TorchCentralizedCriticModel,
|
||||
)
|
||||
from ray.rllib.examples.envs.classes.multi_agent.two_step_game import TwoStepGame
|
||||
from ray.rllib.models import ModelCatalog
|
||||
from ray.rllib.policy.sample_batch import SampleBatch
|
||||
from ray.rllib.utils.annotations import override
|
||||
from ray.rllib.utils.framework import try_import_tf, try_import_torch
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
from ray.rllib.utils.numpy import convert_to_numpy
|
||||
from ray.rllib.utils.test_utils import check_learning_achieved
|
||||
from ray.rllib.utils.tf_utils import explained_variance, make_tf_callable
|
||||
from ray.rllib.utils.torch_utils import convert_to_torch_tensor
|
||||
from ray.tune.result import TRAINING_ITERATION
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
torch, nn = try_import_torch()
|
||||
|
||||
OPPONENT_OBS = "opponent_obs"
|
||||
OPPONENT_ACTION = "opponent_action"
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--framework",
|
||||
choices=["tf", "tf2", "torch"],
|
||||
default="torch",
|
||||
help="The DL framework specifier.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--as-test",
|
||||
action="store_true",
|
||||
help="Whether this script should be run as a test: --stop-reward must "
|
||||
"be achieved within --stop-timesteps AND --stop-iters.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-iters", type=int, default=100, help="Number of iterations to train."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-timesteps", type=int, default=100000, help="Number of timesteps to train."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-reward", type=float, default=7.99, help="Reward at which we stop training."
|
||||
)
|
||||
|
||||
|
||||
class CentralizedValueMixin:
|
||||
"""Add method to evaluate the central value function from the model."""
|
||||
|
||||
def __init__(self):
|
||||
if self.config["framework"] != "torch":
|
||||
self.compute_central_vf = make_tf_callable(self.get_session())(
|
||||
self.model.central_value_function
|
||||
)
|
||||
else:
|
||||
self.compute_central_vf = self.model.central_value_function
|
||||
|
||||
|
||||
# Grabs the opponent obs/act and includes it in the experience train_batch,
|
||||
# and computes GAE using the central vf predictions.
|
||||
def centralized_critic_postprocessing(
|
||||
policy, sample_batch, other_agent_batches=None, episode=None
|
||||
):
|
||||
pytorch = policy.config["framework"] == "torch"
|
||||
if (pytorch and hasattr(policy, "compute_central_vf")) or (
|
||||
not pytorch and policy.loss_initialized()
|
||||
):
|
||||
assert other_agent_batches is not None
|
||||
[(_, _, opponent_batch)] = list(other_agent_batches.values())
|
||||
|
||||
# also record the opponent obs and actions in the trajectory
|
||||
sample_batch[OPPONENT_OBS] = opponent_batch[SampleBatch.CUR_OBS]
|
||||
sample_batch[OPPONENT_ACTION] = opponent_batch[SampleBatch.ACTIONS]
|
||||
|
||||
# overwrite default VF prediction with the central VF
|
||||
if args.framework == "torch":
|
||||
sample_batch[SampleBatch.VF_PREDS] = (
|
||||
policy.compute_central_vf(
|
||||
convert_to_torch_tensor(
|
||||
sample_batch[SampleBatch.CUR_OBS], policy.device
|
||||
),
|
||||
convert_to_torch_tensor(sample_batch[OPPONENT_OBS], policy.device),
|
||||
convert_to_torch_tensor(
|
||||
sample_batch[OPPONENT_ACTION], policy.device
|
||||
),
|
||||
)
|
||||
.cpu()
|
||||
.detach()
|
||||
.numpy()
|
||||
)
|
||||
else:
|
||||
sample_batch[SampleBatch.VF_PREDS] = convert_to_numpy(
|
||||
policy.compute_central_vf(
|
||||
sample_batch[SampleBatch.CUR_OBS],
|
||||
sample_batch[OPPONENT_OBS],
|
||||
sample_batch[OPPONENT_ACTION],
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Policy hasn't been initialized yet, use zeros.
|
||||
sample_batch[OPPONENT_OBS] = np.zeros_like(sample_batch[SampleBatch.CUR_OBS])
|
||||
sample_batch[OPPONENT_ACTION] = np.zeros_like(sample_batch[SampleBatch.ACTIONS])
|
||||
sample_batch[SampleBatch.VF_PREDS] = np.zeros_like(
|
||||
sample_batch[SampleBatch.REWARDS], dtype=np.float32
|
||||
)
|
||||
|
||||
completed = sample_batch[SampleBatch.TERMINATEDS][-1]
|
||||
if completed:
|
||||
last_r = 0.0
|
||||
else:
|
||||
last_r = sample_batch[SampleBatch.VF_PREDS][-1]
|
||||
|
||||
train_batch = compute_advantages(
|
||||
sample_batch,
|
||||
last_r,
|
||||
policy.config["gamma"],
|
||||
policy.config["lambda"],
|
||||
use_gae=policy.config["use_gae"],
|
||||
)
|
||||
return train_batch
|
||||
|
||||
|
||||
# Copied from PPO but optimizing the central value function.
|
||||
def loss_with_central_critic(policy, base_policy, model, dist_class, train_batch):
|
||||
# Save original value function.
|
||||
vf_saved = model.value_function
|
||||
|
||||
# Calculate loss with a custom value function.
|
||||
model.value_function = lambda: policy.model.central_value_function(
|
||||
train_batch[SampleBatch.CUR_OBS],
|
||||
train_batch[OPPONENT_OBS],
|
||||
train_batch[OPPONENT_ACTION],
|
||||
)
|
||||
policy._central_value_out = model.value_function()
|
||||
loss = base_policy.loss(model, dist_class, train_batch)
|
||||
|
||||
# Restore original value function.
|
||||
model.value_function = vf_saved
|
||||
|
||||
return loss
|
||||
|
||||
|
||||
def central_vf_stats(policy, train_batch):
|
||||
# Report the explained variance of the central value function.
|
||||
return {
|
||||
"vf_explained_var": explained_variance(
|
||||
train_batch[Postprocessing.VALUE_TARGETS], policy._central_value_out
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def get_ccppo_policy(base):
|
||||
class CCPPOTFPolicy(CentralizedValueMixin, base):
|
||||
def __init__(self, observation_space, action_space, config):
|
||||
base.__init__(self, observation_space, action_space, config)
|
||||
CentralizedValueMixin.__init__(self)
|
||||
|
||||
@override(base)
|
||||
def loss(self, model, dist_class, train_batch):
|
||||
# Use super() to get to the base PPO policy.
|
||||
# This special loss function utilizes a shared
|
||||
# value function defined on self, and the loss function
|
||||
# defined on PPO policies.
|
||||
return loss_with_central_critic(
|
||||
self, super(), model, dist_class, train_batch
|
||||
)
|
||||
|
||||
@override(base)
|
||||
def postprocess_trajectory(
|
||||
self, sample_batch, other_agent_batches=None, episode=None
|
||||
):
|
||||
return centralized_critic_postprocessing(
|
||||
self, sample_batch, other_agent_batches, episode
|
||||
)
|
||||
|
||||
@override(base)
|
||||
def stats_fn(self, train_batch: SampleBatch):
|
||||
stats = super().stats_fn(train_batch)
|
||||
stats.update(central_vf_stats(self, train_batch))
|
||||
return stats
|
||||
|
||||
return CCPPOTFPolicy
|
||||
|
||||
|
||||
CCPPOStaticGraphTFPolicy = get_ccppo_policy(PPOTF1Policy)
|
||||
CCPPOEagerTFPolicy = get_ccppo_policy(PPOTF2Policy)
|
||||
|
||||
|
||||
class CCPPOTorchPolicy(CentralizedValueMixin, PPOTorchPolicy):
|
||||
def __init__(self, observation_space, action_space, config):
|
||||
PPOTorchPolicy.__init__(self, observation_space, action_space, config)
|
||||
CentralizedValueMixin.__init__(self)
|
||||
|
||||
@override(PPOTorchPolicy)
|
||||
def loss(self, model, dist_class, train_batch):
|
||||
return loss_with_central_critic(self, super(), model, dist_class, train_batch)
|
||||
|
||||
@override(PPOTorchPolicy)
|
||||
def postprocess_trajectory(
|
||||
self, sample_batch, other_agent_batches=None, episode=None
|
||||
):
|
||||
return centralized_critic_postprocessing(
|
||||
self, sample_batch, other_agent_batches, episode
|
||||
)
|
||||
|
||||
|
||||
class CentralizedCritic(PPO):
|
||||
@classmethod
|
||||
@override(PPO)
|
||||
def get_default_policy_class(cls, config):
|
||||
if config["framework"] == "torch":
|
||||
return CCPPOTorchPolicy
|
||||
elif config["framework"] == "tf":
|
||||
return CCPPOStaticGraphTFPolicy
|
||||
else:
|
||||
return CCPPOEagerTFPolicy
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
ModelCatalog.register_custom_model(
|
||||
"cc_model",
|
||||
TorchCentralizedCriticModel
|
||||
if args.framework == "torch"
|
||||
else CentralizedCriticModel,
|
||||
)
|
||||
|
||||
config = (
|
||||
PPOConfig()
|
||||
.api_stack(
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
enable_rl_module_and_learner=False,
|
||||
)
|
||||
.environment(TwoStepGame)
|
||||
.framework(args.framework)
|
||||
.env_runners(batch_mode="complete_episodes", num_env_runners=0)
|
||||
.training(model={"custom_model": "cc_model"})
|
||||
.multi_agent(
|
||||
policies={
|
||||
"pol1": (
|
||||
None,
|
||||
Discrete(6),
|
||||
TwoStepGame.action_space,
|
||||
# `framework` would also be ok here.
|
||||
PPOConfig.overrides(framework_str=args.framework),
|
||||
),
|
||||
"pol2": (
|
||||
None,
|
||||
Discrete(6),
|
||||
TwoStepGame.action_space,
|
||||
# `framework` would also be ok here.
|
||||
PPOConfig.overrides(framework_str=args.framework),
|
||||
),
|
||||
},
|
||||
policy_mapping_fn=lambda agent_id, episode, worker, **kwargs: "pol1"
|
||||
if agent_id == 0
|
||||
else "pol2",
|
||||
)
|
||||
# Use GPUs iff `RLLIB_NUM_GPUS` env var set to > 0.
|
||||
.resources(num_gpus=int(os.environ.get("RLLIB_NUM_GPUS", "0")))
|
||||
)
|
||||
|
||||
stop = {
|
||||
TRAINING_ITERATION: args.stop_iters,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
|
||||
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": args.stop_reward,
|
||||
}
|
||||
|
||||
tuner = tune.Tuner(
|
||||
CentralizedCritic,
|
||||
param_space=config.to_dict(),
|
||||
run_config=tune.RunConfig(stop=stop, verbose=1),
|
||||
)
|
||||
results = tuner.fit()
|
||||
|
||||
if args.as_test:
|
||||
check_learning_achieved(results, args.stop_reward)
|
||||
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# @OldAPIStack
|
||||
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
|
||||
import ray
|
||||
import ray._common
|
||||
from ray.rllib.policy.policy import Policy
|
||||
from ray.rllib.utils.framework import try_import_tf
|
||||
from ray.tune.registry import get_trainable_cls
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
|
||||
ray.init()
|
||||
|
||||
|
||||
def train_and_export_policy_and_model(algo_name, num_steps, model_dir, ckpt_dir):
|
||||
cls = get_trainable_cls(algo_name)
|
||||
config = cls.get_default_config()
|
||||
config.api_stack(
|
||||
enable_rl_module_and_learner=False,
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
)
|
||||
# This Example is only for tf.
|
||||
config.framework("tf")
|
||||
# Set exporting native (DL-framework) model files to True.
|
||||
config.export_native_model_files = True
|
||||
config.env = "CartPole-v1"
|
||||
alg = config.build()
|
||||
for _ in range(num_steps):
|
||||
alg.train()
|
||||
|
||||
# Export Policy checkpoint.
|
||||
alg.export_policy_checkpoint(ckpt_dir)
|
||||
# Export tensorflow keras Model for online serving
|
||||
alg.export_policy_model(model_dir)
|
||||
|
||||
|
||||
def restore_saved_model(export_dir):
|
||||
signature_key = (
|
||||
tf1.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY
|
||||
)
|
||||
g = tf1.Graph()
|
||||
with g.as_default():
|
||||
with tf1.Session(graph=g) as sess:
|
||||
meta_graph_def = tf1.saved_model.load(
|
||||
sess, [tf1.saved_model.tag_constants.SERVING], export_dir
|
||||
)
|
||||
print("Model restored!")
|
||||
print("Signature Def Information:")
|
||||
print(meta_graph_def.signature_def[signature_key])
|
||||
print("You can inspect the model using TensorFlow SavedModel CLI.")
|
||||
print("https://www.tensorflow.org/guide/saved_model")
|
||||
|
||||
|
||||
def restore_policy_from_checkpoint(export_dir):
|
||||
# Load the model from the checkpoint.
|
||||
policy = Policy.from_checkpoint(export_dir)
|
||||
# Perform a dummy (CartPole) forward pass.
|
||||
test_obs = np.array([0.1, 0.2, 0.3, 0.4])
|
||||
results = policy.compute_single_action(test_obs)
|
||||
# Check results for correctness.
|
||||
assert len(results) == 3
|
||||
assert results[0].shape == () # pure single action (int)
|
||||
assert results[1] == [] # RNN states
|
||||
assert results[2]["action_dist_inputs"].shape == (2,) # categorical inputs
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
algo = "PPO"
|
||||
model_dir = os.path.join(
|
||||
ray._common.utils.get_default_ray_temp_dir(), "model_export_dir"
|
||||
)
|
||||
ckpt_dir = os.path.join(
|
||||
ray._common.utils.get_default_ray_temp_dir(), "ckpt_export_dir"
|
||||
)
|
||||
num_steps = 1
|
||||
train_and_export_policy_and_model(algo, num_steps, model_dir, ckpt_dir)
|
||||
restore_saved_model(model_dir)
|
||||
restore_policy_from_checkpoint(ckpt_dir)
|
||||
@@ -0,0 +1,158 @@
|
||||
# @OldAPIStack
|
||||
|
||||
"""
|
||||
Adapted (time-dependent) GAE for PPO algorithm that you can activate by setting
|
||||
use_adapted_gae=True in the policy config. Additionally, it's required that
|
||||
"callbacks" include the custom callback class in the Algorithm's config.
|
||||
Furthermore, the env must return in its info dictionary a key-value pair of
|
||||
the form "d_ts": ... where the value is the length (time) of recent agent step.
|
||||
|
||||
This adapted, time-dependent computation of advantages may be useful in cases
|
||||
where agent's actions take various times and thus time steps are not
|
||||
equidistant (https://docdro.id/400TvlR)
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ray.rllib.callbacks.callbacks import RLlibCallback
|
||||
from ray.rllib.evaluation.postprocessing import Postprocessing
|
||||
from ray.rllib.policy.sample_batch import SampleBatch
|
||||
from ray.rllib.utils.annotations import override
|
||||
|
||||
|
||||
class MyCallbacks(RLlibCallback):
|
||||
@override(RLlibCallback)
|
||||
def on_postprocess_trajectory(
|
||||
self,
|
||||
*,
|
||||
worker,
|
||||
episode,
|
||||
agent_id,
|
||||
policy_id,
|
||||
policies,
|
||||
postprocessed_batch,
|
||||
original_batches,
|
||||
**kwargs
|
||||
):
|
||||
super().on_postprocess_trajectory(
|
||||
worker=worker,
|
||||
episode=episode,
|
||||
agent_id=agent_id,
|
||||
policy_id=policy_id,
|
||||
policies=policies,
|
||||
postprocessed_batch=postprocessed_batch,
|
||||
original_batches=original_batches,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
if policies[policy_id].config.get("use_adapted_gae", False):
|
||||
policy = policies[policy_id]
|
||||
assert policy.config[
|
||||
"use_gae"
|
||||
], "Can't use adapted gae without use_gae=True!"
|
||||
|
||||
info_dicts = postprocessed_batch[SampleBatch.INFOS]
|
||||
assert np.all(
|
||||
["d_ts" in info_dict for info_dict in info_dicts]
|
||||
), "Info dicts in sample batch must contain data 'd_ts' \
|
||||
(=ts[i+1]-ts[i] length of time steps)!"
|
||||
|
||||
d_ts = np.array(
|
||||
[np.float(info_dict.get("d_ts")) for info_dict in info_dicts]
|
||||
)
|
||||
assert np.all(
|
||||
[e.is_integer() for e in d_ts]
|
||||
), "Elements of 'd_ts' (length of time steps) must be integer!"
|
||||
|
||||
# Trajectory is actually complete -> last r=0.0.
|
||||
if postprocessed_batch[SampleBatch.TERMINATEDS][-1]:
|
||||
last_r = 0.0
|
||||
# Trajectory has been truncated -> last r=VF estimate of last obs.
|
||||
else:
|
||||
# Input dict is provided to us automatically via the Model's
|
||||
# requirements. It's a single-timestep (last one in trajectory)
|
||||
# input_dict.
|
||||
# Create an input dict according to the Model's requirements.
|
||||
input_dict = postprocessed_batch.get_single_step_input_dict(
|
||||
policy.model.view_requirements, index="last"
|
||||
)
|
||||
last_r = policy._value(**input_dict)
|
||||
|
||||
gamma = policy.config["gamma"]
|
||||
lambda_ = policy.config["lambda"]
|
||||
|
||||
vpred_t = np.concatenate(
|
||||
[postprocessed_batch[SampleBatch.VF_PREDS], np.array([last_r])]
|
||||
)
|
||||
delta_t = (
|
||||
postprocessed_batch[SampleBatch.REWARDS]
|
||||
+ gamma**d_ts * vpred_t[1:]
|
||||
- vpred_t[:-1]
|
||||
)
|
||||
# This formula for the advantage is an adaption of
|
||||
# "Generalized Advantage Estimation"
|
||||
# (https://arxiv.org/abs/1506.02438) which accounts for time steps
|
||||
# of irregular length (see proposal here ).
|
||||
# NOTE: last time step delta is not required
|
||||
postprocessed_batch[
|
||||
Postprocessing.ADVANTAGES
|
||||
] = generalized_discount_cumsum(delta_t, d_ts[:-1], gamma * lambda_)
|
||||
postprocessed_batch[Postprocessing.VALUE_TARGETS] = (
|
||||
postprocessed_batch[Postprocessing.ADVANTAGES]
|
||||
+ postprocessed_batch[SampleBatch.VF_PREDS]
|
||||
).astype(np.float32)
|
||||
|
||||
postprocessed_batch[Postprocessing.ADVANTAGES] = postprocessed_batch[
|
||||
Postprocessing.ADVANTAGES
|
||||
].astype(np.float32)
|
||||
|
||||
|
||||
def generalized_discount_cumsum(
|
||||
x: np.ndarray, deltas: np.ndarray, gamma: float
|
||||
) -> np.ndarray:
|
||||
"""Calculates the 'time-dependent' discounted cumulative sum over a
|
||||
(reward) sequence `x`.
|
||||
|
||||
Recursive equations:
|
||||
|
||||
y[t] - gamma**deltas[t+1]*y[t+1] = x[t]
|
||||
|
||||
reversed(y)[t] - gamma**reversed(deltas)[t-1]*reversed(y)[t-1] =
|
||||
reversed(x)[t]
|
||||
|
||||
Args:
|
||||
x (np.ndarray): A sequence of rewards or one-step TD residuals.
|
||||
deltas (np.ndarray): A sequence of time step deltas (length of time
|
||||
steps).
|
||||
gamma: The discount factor gamma.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The sequence containing the 'time-dependent' discounted
|
||||
cumulative sums for each individual element in `x` till the end of
|
||||
the trajectory.
|
||||
|
||||
.. testcode::
|
||||
:skipif: True
|
||||
|
||||
x = np.array([0.0, 1.0, 2.0, 3.0])
|
||||
deltas = np.array([1.0, 4.0, 15.0])
|
||||
gamma = 0.9
|
||||
generalized_discount_cumsum(x, deltas, gamma)
|
||||
|
||||
.. testoutput::
|
||||
|
||||
array([0.0 + 0.9^1.0*1.0 + 0.9^4.0*2.0 + 0.9^15.0*3.0,
|
||||
1.0 + 0.9^4.0*2.0 + 0.9^15.0*3.0,
|
||||
2.0 + 0.9^15.0*3.0,
|
||||
3.0])
|
||||
"""
|
||||
reversed_x = x[::-1]
|
||||
reversed_deltas = deltas[::-1]
|
||||
reversed_y = np.empty_like(x)
|
||||
reversed_y[0] = reversed_x[0]
|
||||
for i in range(1, x.size):
|
||||
reversed_y[i] = (
|
||||
reversed_x[i] + gamma ** reversed_deltas[i - 1] * reversed_y[i - 1]
|
||||
)
|
||||
|
||||
return reversed_y[::-1]
|
||||
@@ -0,0 +1,51 @@
|
||||
# @OldAPIStack
|
||||
import random
|
||||
|
||||
from ray.rllib.algorithms.appo import APPOConfig
|
||||
from ray.rllib.algorithms.sac import SACConfig
|
||||
|
||||
|
||||
def create_appo_cartpole_checkpoint(output_dir, use_lstm=False):
|
||||
config = (
|
||||
APPOConfig()
|
||||
.api_stack(
|
||||
enable_rl_module_and_learner=False,
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
)
|
||||
.environment("CartPole-v1")
|
||||
.training(model={"use_lstm": use_lstm})
|
||||
)
|
||||
# Build algorithm object.
|
||||
algo = config.build()
|
||||
algo.save(checkpoint_dir=output_dir)
|
||||
|
||||
|
||||
def create_open_spiel_checkpoint(output_dir):
|
||||
def _policy_mapping_fn(*args, **kwargs):
|
||||
random.choice(["main", "opponent"])
|
||||
|
||||
config = (
|
||||
SACConfig()
|
||||
.environment("open_spiel_env")
|
||||
# Intentionally create a TF2 policy to demonstrate that we can restore
|
||||
# and use a TF policy in a Torch training stack.
|
||||
.framework("tf2")
|
||||
.env_runners(
|
||||
num_env_runners=1,
|
||||
num_envs_per_env_runner=5,
|
||||
# We will be restoring a TF2 policy.
|
||||
# So tell the RolloutWorkers to enable TF eager exec as well, even if
|
||||
# framework is set to torch.
|
||||
enable_tf1_exec_eagerly=True,
|
||||
)
|
||||
.training(model={"fcnet_hiddens": [512, 512]})
|
||||
.multi_agent(
|
||||
policies={"main", "opponent"},
|
||||
policy_mapping_fn=_policy_mapping_fn,
|
||||
# Just train the "main" policy.
|
||||
policies_to_train=["main"],
|
||||
)
|
||||
)
|
||||
# Build algorithm object.
|
||||
algo = config.build()
|
||||
algo.save(checkpoint_dir=output_dir)
|
||||
@@ -0,0 +1,78 @@
|
||||
# @OldAPIStack
|
||||
"""This example script loads a connector enabled policy,
|
||||
and uses it in a serving or inference setting.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import gymnasium as gym
|
||||
|
||||
from ray.rllib.examples._old_api_stack.connectors.prepare_checkpoint import (
|
||||
# For demo purpose only. Would normally not need this.
|
||||
create_appo_cartpole_checkpoint,
|
||||
)
|
||||
from ray.rllib.policy.policy import Policy
|
||||
from ray.rllib.utils.policy import local_policy_inference
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--use-lstm", action="store_true", help="Add LSTM to the setup.")
|
||||
|
||||
|
||||
def run(checkpoint_path, policy_id):
|
||||
# __sphinx_doc_begin__
|
||||
# Restore policy.
|
||||
policy = Policy.from_checkpoint(
|
||||
checkpoint=checkpoint_path,
|
||||
policy_ids=[policy_id],
|
||||
)
|
||||
|
||||
# Run CartPole.
|
||||
env = gym.make("CartPole-v1")
|
||||
env_id = "env_1"
|
||||
obs, info = env.reset()
|
||||
# Run for 2 episodes.
|
||||
episodes = step = 0
|
||||
while episodes < 2:
|
||||
# Use local_policy_inference() to run inference, so we do not have to
|
||||
# provide policy states or extra fetch dictionaries.
|
||||
# "env_1" and "agent_1" are dummy env and agent IDs to run connectors with.
|
||||
policy_outputs = local_policy_inference(
|
||||
policy, env_id, "agent_1", obs, explore=False
|
||||
)
|
||||
assert len(policy_outputs) == 1
|
||||
action, _, _ = policy_outputs[0]
|
||||
print(f"episode {episodes} step {step}", obs, action)
|
||||
|
||||
# Step environment forward one more step.
|
||||
obs, _, terminated, truncated, _ = env.step(action)
|
||||
step += 1
|
||||
|
||||
# If the episode is done, reset the env and our connectors and start a new
|
||||
# episode.
|
||||
if terminated or truncated:
|
||||
episodes += 1
|
||||
step = 0
|
||||
obs, info = env.reset()
|
||||
policy.agent_connectors.reset(env_id)
|
||||
|
||||
# __sphinx_doc_end__
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
policy_id = "default_policy"
|
||||
|
||||
# Note, this is just for demo purpose.
|
||||
# Normally, you would use a policy checkpoint from a real training run.
|
||||
create_appo_cartpole_checkpoint(tmpdir, args.use_lstm)
|
||||
policy_checkpoint_path = os.path.join(
|
||||
tmpdir,
|
||||
"policies",
|
||||
policy_id,
|
||||
)
|
||||
|
||||
run(policy_checkpoint_path, policy_id)
|
||||
@@ -0,0 +1,152 @@
|
||||
# @OldAPIStack
|
||||
"""Example showing to restore a connector enabled TF policy
|
||||
checkpoint for a new self-play PyTorch training job.
|
||||
You can train the checkpointed policy with a different algorithm too.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import tempfile
|
||||
from functools import partial
|
||||
|
||||
import ray
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.sac import SACConfig
|
||||
from ray.rllib.callbacks.callbacks import RLlibCallback
|
||||
from ray.rllib.env.utils import try_import_pyspiel
|
||||
from ray.rllib.env.wrappers.open_spiel import OpenSpielEnv
|
||||
from ray.rllib.examples._old_api_stack.connectors.prepare_checkpoint import (
|
||||
create_open_spiel_checkpoint,
|
||||
)
|
||||
from ray.rllib.policy.policy import Policy
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
NUM_EPISODES,
|
||||
)
|
||||
from ray.tune import CLIReporter, register_env
|
||||
from ray.tune.result import TRAINING_ITERATION
|
||||
|
||||
pyspiel = try_import_pyspiel(error=True)
|
||||
register_env(
|
||||
"open_spiel_env", lambda _: OpenSpielEnv(pyspiel.load_game("connect_four"))
|
||||
)
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--train_iteration",
|
||||
type=int,
|
||||
default=10,
|
||||
help="Number of iterations to train.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
MAIN_POLICY_ID = "main"
|
||||
OPPONENT_POLICY_ID = "opponent"
|
||||
|
||||
|
||||
class AddPolicyCallback(RLlibCallback):
|
||||
def __init__(self, checkpoint_dir):
|
||||
self._checkpoint_dir = checkpoint_dir
|
||||
super().__init__()
|
||||
|
||||
def on_algorithm_init(self, *, algorithm, metrics_logger, **kwargs):
|
||||
policy = Policy.from_checkpoint(
|
||||
self._checkpoint_dir, policy_ids=[OPPONENT_POLICY_ID]
|
||||
)
|
||||
|
||||
# Add restored policy to Algorithm.
|
||||
# Note that this policy doesn't have to be trained with the same algorithm
|
||||
# of the training stack. You can even mix up TF policies with a Torch stack.
|
||||
algorithm.add_policy(
|
||||
policy_id=OPPONENT_POLICY_ID,
|
||||
policy=policy,
|
||||
add_to_eval_env_runners=True,
|
||||
)
|
||||
|
||||
|
||||
def policy_mapping_fn(agent_id, episode, worker, **kwargs):
|
||||
# main policy plays against opponent policy.
|
||||
return MAIN_POLICY_ID if episode.episode_id % 2 == agent_id else OPPONENT_POLICY_ID
|
||||
|
||||
|
||||
def main(checkpoint_dir):
|
||||
config = (
|
||||
SACConfig()
|
||||
.environment("open_spiel_env")
|
||||
.framework("torch")
|
||||
.callbacks(partial(AddPolicyCallback, checkpoint_dir))
|
||||
.env_runners(
|
||||
num_env_runners=1,
|
||||
num_envs_per_env_runner=5,
|
||||
# We will be restoring a TF2 policy.
|
||||
# So tell the RolloutWorkers to enable TF eager exec as well, even if
|
||||
# framework is set to torch.
|
||||
enable_tf1_exec_eagerly=True,
|
||||
)
|
||||
.training(model={"fcnet_hiddens": [512, 512]})
|
||||
.multi_agent(
|
||||
# Initial policy map: Random and PPO. This will be expanded
|
||||
# to more policy snapshots taken from "main" against which "main"
|
||||
# will then play (instead of "random"). This is done in the
|
||||
# custom callback defined above (`SelfPlayCallback`).
|
||||
# Note: We will add the "opponent" policy with callback.
|
||||
policies={MAIN_POLICY_ID}, # Our main policy, we'd like to optimize.
|
||||
# Assign agent 0 and 1 randomly to the "main" policy or
|
||||
# to the opponent ("random" at first). Make sure (via episode_id)
|
||||
# that "main" always plays against "random" (and not against
|
||||
# another "main").
|
||||
policy_mapping_fn=policy_mapping_fn,
|
||||
# Always just train the "main" policy.
|
||||
policies_to_train=[MAIN_POLICY_ID],
|
||||
)
|
||||
)
|
||||
|
||||
stop = {TRAINING_ITERATION: args.train_iteration}
|
||||
|
||||
# Train the "main" policy to play really well using self-play.
|
||||
tuner = tune.Tuner(
|
||||
"SAC",
|
||||
param_space=config.to_dict(),
|
||||
run_config=tune.RunConfig(
|
||||
stop=stop,
|
||||
checkpoint_config=tune.CheckpointConfig(
|
||||
checkpoint_at_end=True,
|
||||
checkpoint_frequency=10,
|
||||
),
|
||||
verbose=2,
|
||||
progress_reporter=CLIReporter(
|
||||
metric_columns={
|
||||
TRAINING_ITERATION: "iter",
|
||||
"time_total_s": "time_total_s",
|
||||
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": "ts",
|
||||
f"{ENV_RUNNER_RESULTS}/{NUM_EPISODES}": "train_episodes",
|
||||
(
|
||||
f"{ENV_RUNNER_RESULTS}/module_episode_returns_mean/main"
|
||||
): "reward_main",
|
||||
},
|
||||
sort_by_metric=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
tuner.fit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ray.init()
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
create_open_spiel_checkpoint(tmpdir)
|
||||
|
||||
policy_checkpoint_path = os.path.join(
|
||||
tmpdir,
|
||||
"checkpoint_000000",
|
||||
"policies",
|
||||
OPPONENT_POLICY_ID,
|
||||
)
|
||||
|
||||
main(policy_checkpoint_path)
|
||||
|
||||
ray.shutdown()
|
||||
@@ -0,0 +1,126 @@
|
||||
# @OldAPIStack
|
||||
"""
|
||||
Example of interfacing with an environment that produces 2D observations.
|
||||
|
||||
This example shows how turning 2D observations with shape (A, B) into a 3D
|
||||
observations with shape (C, D, 1) can enable usage of RLlib's default models.
|
||||
RLlib's default Catalog class does not provide default models for 2D observation
|
||||
spaces, but it does so for 3D observations.
|
||||
Therefore, one can either write a custom model or transform the 2D observations into 3D
|
||||
observations. This enables RLlib to use one of the default CNN filters, even though the
|
||||
original observation space of the environment does not fit them.
|
||||
|
||||
This simple example should reach rewards of 50 within 150k timesteps.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
||||
from numpy import float32
|
||||
from pettingzoo.butterfly import pistonball_v6
|
||||
from supersuit import (
|
||||
color_reduction_v0,
|
||||
dtype_v0,
|
||||
normalize_obs_v0,
|
||||
reshape_v0,
|
||||
resize_v1,
|
||||
)
|
||||
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.ppo import PPOConfig
|
||||
from ray.rllib.env import PettingZooEnv
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
from ray.tune.registry import register_env
|
||||
from ray.tune.result import TRAINING_ITERATION
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--framework",
|
||||
choices=["tf2", "torch"],
|
||||
default="torch",
|
||||
help="The DL framework specifier.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--as-test",
|
||||
action="store_true",
|
||||
help="Whether this script should be run as a compilation test.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-iters", type=int, default=150, help="Number of iterations to train."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-timesteps", type=int, default=1000000, help="Number of timesteps to train."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-reward", type=float, default=50, help="Reward at which we stop training."
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
# The space we down-sample and transform the greyscale pistonball images to.
|
||||
# Other spaces supported by RLlib can be chosen here.
|
||||
TRANSFORMED_OBS_SPACE = (42, 42, 1)
|
||||
|
||||
|
||||
def env_creator(config):
|
||||
env = pistonball_v6.env(n_pistons=5)
|
||||
env = dtype_v0(env, dtype=float32)
|
||||
# This gives us greyscale images for the color red
|
||||
env = color_reduction_v0(env, mode="R")
|
||||
env = normalize_obs_v0(env)
|
||||
# This gives us images that are upsampled to the number of pixels in the
|
||||
# default CNN filter
|
||||
env = resize_v1(
|
||||
env, x_size=TRANSFORMED_OBS_SPACE[0], y_size=TRANSFORMED_OBS_SPACE[1]
|
||||
)
|
||||
# This gives us 3D images for which we have default filters
|
||||
env = reshape_v0(env, shape=TRANSFORMED_OBS_SPACE)
|
||||
return env
|
||||
|
||||
|
||||
# Register env
|
||||
register_env("pistonball", lambda config: PettingZooEnv(env_creator(config)))
|
||||
|
||||
config = (
|
||||
PPOConfig()
|
||||
.environment("pistonball", env_config={"local_ratio": 0.5}, clip_rewards=True)
|
||||
.env_runners(
|
||||
num_env_runners=15 if not args.as_test else 2,
|
||||
num_envs_per_env_runner=1,
|
||||
observation_filter="NoFilter",
|
||||
rollout_fragment_length="auto",
|
||||
)
|
||||
.framework("torch")
|
||||
.training(
|
||||
entropy_coeff=0.01,
|
||||
vf_loss_coeff=0.1,
|
||||
clip_param=0.1,
|
||||
vf_clip_param=10.0,
|
||||
num_epochs=10,
|
||||
kl_coeff=0.5,
|
||||
lr=0.0001,
|
||||
grad_clip=100,
|
||||
minibatch_size=500,
|
||||
train_batch_size=5000 if not args.as_test else 1000,
|
||||
model={"vf_share_layers": True},
|
||||
)
|
||||
.resources(num_gpus=1 if not args.as_test else 0)
|
||||
.reporting(min_time_s_per_iteration=30)
|
||||
)
|
||||
|
||||
tune.Tuner(
|
||||
"PPO",
|
||||
param_space=config.to_dict(),
|
||||
run_config=tune.RunConfig(
|
||||
stop={
|
||||
TRAINING_ITERATION: args.stop_iters,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
|
||||
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": args.stop_reward,
|
||||
},
|
||||
verbose=2,
|
||||
),
|
||||
).fit()
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
# @OldAPIStack
|
||||
"""
|
||||
Example showing how you can use your trained policy for inference
|
||||
(computing actions) in an environment.
|
||||
|
||||
Includes options for LSTM-based models (--use-lstm), attention-net models
|
||||
(--use-attention), and plain (non-recurrent) models.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import gymnasium as gym
|
||||
import numpy as np
|
||||
|
||||
import ray
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.algorithm import Algorithm
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
from ray.tune.registry import get_trainable_cls
|
||||
from ray.tune.result import TRAINING_ITERATION
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--run", type=str, default="PPO", help="The RLlib-registered algorithm to use."
|
||||
)
|
||||
parser.add_argument("--num-cpus", type=int, default=0)
|
||||
parser.add_argument(
|
||||
"--framework",
|
||||
choices=["tf", "tf2", "torch"],
|
||||
default="torch",
|
||||
help="The DL framework specifier.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--prev-n-actions",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Feed n most recent actions to the attention net as part of its input.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--prev-n-rewards",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Feed n most recent rewards to the attention net as part of its input.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-iters",
|
||||
type=int,
|
||||
default=200,
|
||||
help="Number of iterations to train before we do inference.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-timesteps",
|
||||
type=int,
|
||||
default=100000,
|
||||
help="Number of timesteps to train before we do inference.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-reward",
|
||||
type=float,
|
||||
default=150.0,
|
||||
help="Reward at which we stop training before we do inference.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--explore-during-inference",
|
||||
action="store_true",
|
||||
help="Whether the trained policy should use exploration during action "
|
||||
"inference.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--num-episodes-during-inference",
|
||||
type=int,
|
||||
default=10,
|
||||
help="Number of episodes to do inference over after training.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--use-onnx-for-inference",
|
||||
action="store_true",
|
||||
help="Whether to convert the loaded module to ONNX format and then perform "
|
||||
"inference through this ONNX model.",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.use_onnx_for_inference:
|
||||
if args.explore_during_inference:
|
||||
raise ValueError(
|
||||
"Can't set `--explore-during-inference` and `--use-onnx-for-inference` together!"
|
||||
)
|
||||
import onnxruntime
|
||||
|
||||
ray.init(num_cpus=args.num_cpus or None)
|
||||
|
||||
config = (
|
||||
get_trainable_cls(args.run)
|
||||
.get_default_config()
|
||||
.api_stack(
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
enable_rl_module_and_learner=False,
|
||||
)
|
||||
.environment("FrozenLake-v1")
|
||||
# Run with tracing enabled for tf2?
|
||||
.framework(args.framework)
|
||||
.training(
|
||||
model={
|
||||
"use_attention": True,
|
||||
"attention_num_transformer_units": 1,
|
||||
"attention_use_n_prev_actions": args.prev_n_actions,
|
||||
"attention_use_n_prev_rewards": args.prev_n_rewards,
|
||||
"attention_dim": 32,
|
||||
"attention_memory_inference": 10,
|
||||
"attention_memory_training": 10,
|
||||
},
|
||||
)
|
||||
# Use GPUs iff `RLLIB_NUM_GPUS` env var set to > 0.
|
||||
.resources(num_gpus=int(os.environ.get("RLLIB_NUM_GPUS", "0")))
|
||||
)
|
||||
|
||||
stop = {
|
||||
TRAINING_ITERATION: args.stop_iters,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
|
||||
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": args.stop_reward,
|
||||
}
|
||||
|
||||
print("Training policy until desired reward/timesteps/iterations. ...")
|
||||
tuner = tune.Tuner(
|
||||
args.run,
|
||||
param_space=config,
|
||||
run_config=tune.RunConfig(
|
||||
stop=stop,
|
||||
verbose=2,
|
||||
checkpoint_config=tune.CheckpointConfig(
|
||||
checkpoint_frequency=1,
|
||||
checkpoint_at_end=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
results = tuner.fit()
|
||||
|
||||
print("Training completed. Restoring new Algorithm for action inference.")
|
||||
# Get the last checkpoint from the above training run.
|
||||
checkpoint = results.get_best_result().checkpoint
|
||||
# Create new Algorithm and restore its state from the last checkpoint.
|
||||
algo = Algorithm.from_checkpoint(checkpoint)
|
||||
# Export ONNX model if relevant
|
||||
if args.use_onnx_for_inference:
|
||||
algo.get_policy().export_model(
|
||||
"frozenlake_attention_model_onnx",
|
||||
# ONNX opset version 12 required to support einsum operator.
|
||||
# Requires ONNX >= 1.7 and ONNX runtime >= 1.3
|
||||
onnx=12,
|
||||
)
|
||||
|
||||
# Create the env to do inference in.
|
||||
env = gym.make("FrozenLake-v1")
|
||||
obs, info = env.reset()
|
||||
|
||||
# In case the model needs previous-reward/action inputs, keep track of
|
||||
# these via these variables here (we'll have to pass them into the
|
||||
# compute_actions methods below).
|
||||
init_prev_a = prev_a = None
|
||||
init_prev_r = prev_r = None
|
||||
|
||||
# Set attention net's initial internal state.
|
||||
num_transformers = config["model"]["attention_num_transformer_units"]
|
||||
memory_inference = config["model"]["attention_memory_inference"]
|
||||
attention_dim = config["model"]["attention_dim"]
|
||||
init_state = state = [
|
||||
np.zeros([memory_inference, attention_dim], np.float32)
|
||||
for _ in range(num_transformers)
|
||||
]
|
||||
# Do we need prev-action/reward as part of the input?
|
||||
if args.prev_n_actions:
|
||||
init_prev_a = prev_a = np.array([0] * args.prev_n_actions)
|
||||
if args.prev_n_rewards:
|
||||
init_prev_r = prev_r = np.array([0.0] * args.prev_n_rewards)
|
||||
|
||||
num_episodes = 0
|
||||
ort_session = None
|
||||
|
||||
while num_episodes < args.num_episodes_during_inference:
|
||||
|
||||
# Compute an action (`a`).
|
||||
if args.use_onnx_for_inference:
|
||||
# Prepare the ONNX runtime session.
|
||||
if ort_session is None:
|
||||
ort_session = onnxruntime.InferenceSession(
|
||||
"frozenlake_attention_model_onnx/model.onnx"
|
||||
)
|
||||
# Prepare the inputs dict.
|
||||
seq_len = np.array([config["model"]["max_seq_len"]], dtype=np.int32)
|
||||
|
||||
# pre-process observation: obs is an integer.
|
||||
# we need to convert it to a one-hot vector (FrozenLake-v1 space).
|
||||
n = env.observation_space.n
|
||||
obs_one_hot = np.zeros(n, dtype=np.float32)
|
||||
obs_one_hot[obs] = 1.0
|
||||
obs = obs_one_hot
|
||||
# Add batch dimension.
|
||||
obs = np.array(obs, dtype=np.float32)[np.newaxis, :]
|
||||
|
||||
state_ins = np.array(state, dtype=np.float32)
|
||||
|
||||
ort_inputs = {
|
||||
"obs": obs,
|
||||
"state_ins": state_ins,
|
||||
"seq_lens": seq_len,
|
||||
}
|
||||
|
||||
if init_prev_a is not None:
|
||||
ort_inputs["prev_actions"] = prev_a.astype(np.int64)[np.newaxis, :]
|
||||
if init_prev_r is not None:
|
||||
ort_inputs["prev_rewards"] = prev_r.astype(np.float32)[np.newaxis, :]
|
||||
# Run the ONNX model.
|
||||
ort_outs = ort_session.run(
|
||||
output_names=["output", "state_outs"],
|
||||
input_feed=ort_inputs,
|
||||
)
|
||||
# Extract action and state-out from the ONNX model outputs.
|
||||
dist_inputs = ort_outs[0][0]
|
||||
# Exploration could be added here based on `dist_inputs`.
|
||||
# This would require using the configured exploration strategy.
|
||||
# Not implemented in this example.
|
||||
a = np.argmax(dist_inputs)
|
||||
|
||||
state_out = [ort_outs[i + 1][0] for i in range(len(state))]
|
||||
else:
|
||||
a, state_out, _ = algo.compute_single_action(
|
||||
observation=obs,
|
||||
state=state,
|
||||
prev_action=prev_a,
|
||||
prev_reward=prev_r,
|
||||
explore=args.explore_during_inference,
|
||||
policy_id="default_policy", # <- default value
|
||||
)
|
||||
# Send the computed action `a` to the env.
|
||||
obs, reward, done, truncated, _ = env.step(a)
|
||||
# Is the episode `done`? -> Reset.
|
||||
if done:
|
||||
obs, info = env.reset()
|
||||
num_episodes += 1
|
||||
state = init_state
|
||||
prev_a = init_prev_a
|
||||
prev_r = init_prev_r
|
||||
# Episode is still ongoing -> Continue.
|
||||
else:
|
||||
# Append the just received state-out (most recent timestep) to the
|
||||
# cascade (memory) of our state-ins and drop the oldest state-in.
|
||||
state = [
|
||||
np.concatenate([state[i], [state_out[i]]], axis=0)[1:]
|
||||
for i in range(num_transformers)
|
||||
]
|
||||
if init_prev_a is not None:
|
||||
prev_a = np.concatenate([prev_a, [a]])[1:]
|
||||
if init_prev_r is not None:
|
||||
prev_r = np.concatenate([prev_r, [reward]])[1:]
|
||||
|
||||
algo.stop()
|
||||
|
||||
ray.shutdown()
|
||||
@@ -0,0 +1,186 @@
|
||||
# @OldAPIStack
|
||||
"""
|
||||
Example showing how you can use your trained policy for inference
|
||||
(computing actions) in an environment.
|
||||
|
||||
Includes options for LSTM-based models (--use-lstm), attention-net models
|
||||
(--use-attention), and plain (non-recurrent) models.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import gymnasium as gym
|
||||
import numpy as np
|
||||
|
||||
import ray
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.algorithm import Algorithm
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
from ray.tune.registry import get_trainable_cls
|
||||
from ray.tune.result import TRAINING_ITERATION
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--run", type=str, default="PPO", help="The RLlib-registered algorithm to use."
|
||||
)
|
||||
parser.add_argument("--num-cpus", type=int, default=0)
|
||||
parser.add_argument(
|
||||
"--framework",
|
||||
choices=["tf", "tf2", "torch"],
|
||||
default="torch",
|
||||
help="The DL framework specifier.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--prev-action",
|
||||
action="store_true",
|
||||
help="Feed most recent action to the LSTM as part of its input.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--prev-reward",
|
||||
action="store_true",
|
||||
help="Feed most recent reward to the LSTM as part of its input.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-iters",
|
||||
type=int,
|
||||
default=2,
|
||||
help="Number of iterations to train before we do inference.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-timesteps",
|
||||
type=int,
|
||||
default=100000,
|
||||
help="Number of timesteps to train before we do inference.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-reward",
|
||||
type=float,
|
||||
default=0.8,
|
||||
help="Reward at which we stop training before we do inference.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--explore-during-inference",
|
||||
action="store_true",
|
||||
help="Whether the trained policy should use exploration during action "
|
||||
"inference.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--num-episodes-during-inference",
|
||||
type=int,
|
||||
default=10,
|
||||
help="Number of episodes to do inference over after training.",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
ray.init(num_cpus=args.num_cpus or None)
|
||||
|
||||
config = (
|
||||
get_trainable_cls(args.run)
|
||||
.get_default_config()
|
||||
.api_stack(
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
enable_rl_module_and_learner=False,
|
||||
)
|
||||
.environment("FrozenLake-v1")
|
||||
# Run with tracing enabled for tf2?
|
||||
.framework(args.framework)
|
||||
.training(
|
||||
model={
|
||||
"use_lstm": True,
|
||||
"lstm_cell_size": 256,
|
||||
"lstm_use_prev_action": args.prev_action,
|
||||
"lstm_use_prev_reward": args.prev_reward,
|
||||
},
|
||||
)
|
||||
# Use GPUs iff `RLLIB_NUM_GPUS` env var set to > 0.
|
||||
.resources(num_gpus=int(os.environ.get("RLLIB_NUM_GPUS", "0")))
|
||||
)
|
||||
|
||||
stop = {
|
||||
TRAINING_ITERATION: args.stop_iters,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
|
||||
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": args.stop_reward,
|
||||
}
|
||||
|
||||
print("Training policy until desired reward/timesteps/iterations. ...")
|
||||
tuner = tune.Tuner(
|
||||
args.run,
|
||||
param_space=config,
|
||||
run_config=tune.RunConfig(
|
||||
stop=stop,
|
||||
verbose=2,
|
||||
checkpoint_config=tune.CheckpointConfig(
|
||||
checkpoint_frequency=1,
|
||||
checkpoint_at_end=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
results = tuner.fit()
|
||||
|
||||
print("Training completed. Restoring new Algorithm for action inference.")
|
||||
# Get the last checkpoint from the above training run.
|
||||
checkpoint = results.get_best_result().checkpoint
|
||||
# Create new Algorithm from the last checkpoint.
|
||||
algo = Algorithm.from_checkpoint(checkpoint)
|
||||
|
||||
# Create the env to do inference in.
|
||||
env = gym.make("FrozenLake-v1")
|
||||
obs, info = env.reset()
|
||||
|
||||
# In case the model needs previous-reward/action inputs, keep track of
|
||||
# these via these variables here (we'll have to pass them into the
|
||||
# compute_actions methods below).
|
||||
init_prev_a = prev_a = None
|
||||
init_prev_r = prev_r = None
|
||||
|
||||
# Set LSTM's initial internal state.
|
||||
lstm_cell_size = config["model"]["lstm_cell_size"]
|
||||
# range(2) b/c h- and c-states of the LSTM.
|
||||
if algo.config.enable_rl_module_and_learner:
|
||||
init_state = state = algo.get_policy().model.get_initial_state()
|
||||
else:
|
||||
init_state = state = [np.zeros([lstm_cell_size], np.float32) for _ in range(2)]
|
||||
# Do we need prev-action/reward as part of the input?
|
||||
if args.prev_action:
|
||||
init_prev_a = prev_a = 0
|
||||
if args.prev_reward:
|
||||
init_prev_r = prev_r = 0.0
|
||||
|
||||
num_episodes = 0
|
||||
|
||||
while num_episodes < args.num_episodes_during_inference:
|
||||
# Compute an action (`a`).
|
||||
a, state_out, _ = algo.compute_single_action(
|
||||
observation=obs,
|
||||
state=state,
|
||||
prev_action=prev_a,
|
||||
prev_reward=prev_r,
|
||||
explore=args.explore_during_inference,
|
||||
policy_id="default_policy", # <- default value
|
||||
)
|
||||
# Send the computed action `a` to the env.
|
||||
obs, reward, done, truncated, info = env.step(a)
|
||||
# Is the episode `done`? -> Reset.
|
||||
if done:
|
||||
obs, info = env.reset()
|
||||
num_episodes += 1
|
||||
state = init_state
|
||||
prev_a = init_prev_a
|
||||
prev_r = init_prev_r
|
||||
# Episode is still ongoing -> Continue.
|
||||
else:
|
||||
state = state_out
|
||||
if init_prev_a is not None:
|
||||
prev_a = a
|
||||
if init_prev_r is not None:
|
||||
prev_r = reward
|
||||
|
||||
algo.stop()
|
||||
|
||||
ray.shutdown()
|
||||
@@ -0,0 +1,126 @@
|
||||
# @OldAPIStack
|
||||
from gymnasium.spaces import Dict
|
||||
|
||||
from ray.rllib.models.tf.fcnet import FullyConnectedNetwork
|
||||
from ray.rllib.models.tf.tf_modelv2 import TFModelV2
|
||||
from ray.rllib.models.torch.fcnet import FullyConnectedNetwork as TorchFC
|
||||
from ray.rllib.models.torch.torch_modelv2 import TorchModelV2
|
||||
from ray.rllib.utils.framework import try_import_tf, try_import_torch
|
||||
from ray.rllib.utils.torch_utils import FLOAT_MIN
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
torch, nn = try_import_torch()
|
||||
|
||||
|
||||
class ActionMaskModel(TFModelV2):
|
||||
"""Model that handles simple discrete action masking.
|
||||
|
||||
This assumes the outputs are logits for a single Categorical action dist.
|
||||
Getting this to work with a more complex output (e.g., if the action space
|
||||
is a tuple of several distributions) is also possible but left as an
|
||||
exercise to the reader.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, obs_space, action_space, num_outputs, model_config, name, **kwargs
|
||||
):
|
||||
|
||||
orig_space = getattr(obs_space, "original_space", obs_space)
|
||||
assert (
|
||||
isinstance(orig_space, Dict)
|
||||
and "action_mask" in orig_space.spaces
|
||||
and "observations" in orig_space.spaces
|
||||
)
|
||||
|
||||
super().__init__(obs_space, action_space, num_outputs, model_config, name)
|
||||
|
||||
self.internal_model = FullyConnectedNetwork(
|
||||
orig_space["observations"],
|
||||
action_space,
|
||||
num_outputs,
|
||||
model_config,
|
||||
name + "_internal",
|
||||
)
|
||||
|
||||
# disable action masking --> will likely lead to invalid actions
|
||||
self.no_masking = model_config["custom_model_config"].get("no_masking", False)
|
||||
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
# Extract the available actions tensor from the observation.
|
||||
action_mask = input_dict["obs"]["action_mask"]
|
||||
|
||||
# Compute the unmasked logits.
|
||||
logits, _ = self.internal_model({"obs": input_dict["obs"]["observations"]})
|
||||
|
||||
# If action masking is disabled, directly return unmasked logits
|
||||
if self.no_masking:
|
||||
return logits, state
|
||||
|
||||
# Convert action_mask into a [0.0 || -inf]-type mask.
|
||||
inf_mask = tf.maximum(tf.math.log(action_mask), tf.float32.min)
|
||||
masked_logits = logits + inf_mask
|
||||
|
||||
# Return masked logits.
|
||||
return masked_logits, state
|
||||
|
||||
def value_function(self):
|
||||
return self.internal_model.value_function()
|
||||
|
||||
|
||||
class TorchActionMaskModel(TorchModelV2, nn.Module):
|
||||
"""PyTorch version of above ActionMaskingModel."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
obs_space,
|
||||
action_space,
|
||||
num_outputs,
|
||||
model_config,
|
||||
name,
|
||||
**kwargs,
|
||||
):
|
||||
orig_space = getattr(obs_space, "original_space", obs_space)
|
||||
assert (
|
||||
isinstance(orig_space, Dict)
|
||||
and "action_mask" in orig_space.spaces
|
||||
and "observations" in orig_space.spaces
|
||||
)
|
||||
|
||||
TorchModelV2.__init__(
|
||||
self, obs_space, action_space, num_outputs, model_config, name, **kwargs
|
||||
)
|
||||
nn.Module.__init__(self)
|
||||
|
||||
self.internal_model = TorchFC(
|
||||
orig_space["observations"],
|
||||
action_space,
|
||||
num_outputs,
|
||||
model_config,
|
||||
name + "_internal",
|
||||
)
|
||||
|
||||
# disable action masking --> will likely lead to invalid actions
|
||||
self.no_masking = False
|
||||
if "no_masking" in model_config["custom_model_config"]:
|
||||
self.no_masking = model_config["custom_model_config"]["no_masking"]
|
||||
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
# Extract the available actions tensor from the observation.
|
||||
action_mask = input_dict["obs"]["action_mask"]
|
||||
|
||||
# Compute the unmasked logits.
|
||||
logits, _ = self.internal_model({"obs": input_dict["obs"]["observations"]})
|
||||
|
||||
# If action masking is disabled, directly return unmasked logits
|
||||
if self.no_masking:
|
||||
return logits, state
|
||||
|
||||
# Convert action_mask into a [0.0 || -inf]-type mask.
|
||||
inf_mask = torch.clamp(torch.log(action_mask), min=FLOAT_MIN)
|
||||
masked_logits = logits + inf_mask
|
||||
|
||||
# Return masked logits.
|
||||
return masked_logits, state
|
||||
|
||||
def value_function(self):
|
||||
return self.internal_model.value_function()
|
||||
@@ -0,0 +1,149 @@
|
||||
# @OldAPIStack
|
||||
from ray.rllib.models.tf.tf_action_dist import ActionDistribution, Categorical
|
||||
from ray.rllib.models.torch.torch_action_dist import (
|
||||
TorchCategorical,
|
||||
TorchDistributionWrapper,
|
||||
)
|
||||
from ray.rllib.utils.framework import try_import_tf, try_import_torch
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
torch, nn = try_import_torch()
|
||||
|
||||
|
||||
class BinaryAutoregressiveDistribution(ActionDistribution):
|
||||
"""Action distribution P(a1, a2) = P(a1) * P(a2 | a1)"""
|
||||
|
||||
def deterministic_sample(self):
|
||||
# First, sample a1.
|
||||
a1_dist = self._a1_distribution()
|
||||
a1 = a1_dist.deterministic_sample()
|
||||
|
||||
# Sample a2 conditioned on a1.
|
||||
a2_dist = self._a2_distribution(a1)
|
||||
a2 = a2_dist.deterministic_sample()
|
||||
self._action_logp = a1_dist.logp(a1) + a2_dist.logp(a2)
|
||||
|
||||
# Return the action tuple.
|
||||
return (a1, a2)
|
||||
|
||||
def sample(self):
|
||||
# First, sample a1.
|
||||
a1_dist = self._a1_distribution()
|
||||
a1 = a1_dist.sample()
|
||||
|
||||
# Sample a2 conditioned on a1.
|
||||
a2_dist = self._a2_distribution(a1)
|
||||
a2 = a2_dist.sample()
|
||||
self._action_logp = a1_dist.logp(a1) + a2_dist.logp(a2)
|
||||
|
||||
# Return the action tuple.
|
||||
return (a1, a2)
|
||||
|
||||
def logp(self, actions):
|
||||
a1, a2 = actions[:, 0], actions[:, 1]
|
||||
a1_vec = tf.expand_dims(tf.cast(a1, tf.float32), 1)
|
||||
a1_logits, a2_logits = self.model.action_model([self.inputs, a1_vec])
|
||||
return Categorical(a1_logits).logp(a1) + Categorical(a2_logits).logp(a2)
|
||||
|
||||
def sampled_action_logp(self):
|
||||
return self._action_logp
|
||||
|
||||
def entropy(self):
|
||||
a1_dist = self._a1_distribution()
|
||||
a2_dist = self._a2_distribution(a1_dist.sample())
|
||||
return a1_dist.entropy() + a2_dist.entropy()
|
||||
|
||||
def kl(self, other):
|
||||
a1_dist = self._a1_distribution()
|
||||
a1_terms = a1_dist.kl(other._a1_distribution())
|
||||
|
||||
a1 = a1_dist.sample()
|
||||
a2_terms = self._a2_distribution(a1).kl(other._a2_distribution(a1))
|
||||
return a1_terms + a2_terms
|
||||
|
||||
def _a1_distribution(self):
|
||||
BATCH = tf.shape(self.inputs)[0]
|
||||
a1_logits, _ = self.model.action_model([self.inputs, tf.zeros((BATCH, 1))])
|
||||
a1_dist = Categorical(a1_logits)
|
||||
return a1_dist
|
||||
|
||||
def _a2_distribution(self, a1):
|
||||
a1_vec = tf.expand_dims(tf.cast(a1, tf.float32), 1)
|
||||
_, a2_logits = self.model.action_model([self.inputs, a1_vec])
|
||||
a2_dist = Categorical(a2_logits)
|
||||
return a2_dist
|
||||
|
||||
@staticmethod
|
||||
def required_model_output_shape(action_space, model_config):
|
||||
return 16 # controls model output feature vector size
|
||||
|
||||
|
||||
class TorchBinaryAutoregressiveDistribution(TorchDistributionWrapper):
|
||||
"""Action distribution P(a1, a2) = P(a1) * P(a2 | a1)"""
|
||||
|
||||
def deterministic_sample(self):
|
||||
# First, sample a1.
|
||||
a1_dist = self._a1_distribution()
|
||||
a1 = a1_dist.deterministic_sample()
|
||||
|
||||
# Sample a2 conditioned on a1.
|
||||
a2_dist = self._a2_distribution(a1)
|
||||
a2 = a2_dist.deterministic_sample()
|
||||
self._action_logp = a1_dist.logp(a1) + a2_dist.logp(a2)
|
||||
|
||||
# Return the action tuple.
|
||||
return (a1, a2)
|
||||
|
||||
def sample(self):
|
||||
# First, sample a1.
|
||||
a1_dist = self._a1_distribution()
|
||||
a1 = a1_dist.sample()
|
||||
|
||||
# Sample a2 conditioned on a1.
|
||||
a2_dist = self._a2_distribution(a1)
|
||||
a2 = a2_dist.sample()
|
||||
self._action_logp = a1_dist.logp(a1) + a2_dist.logp(a2)
|
||||
|
||||
# Return the action tuple.
|
||||
return (a1, a2)
|
||||
|
||||
def logp(self, actions):
|
||||
a1, a2 = actions[:, 0], actions[:, 1]
|
||||
a1_vec = torch.unsqueeze(a1.float(), 1)
|
||||
a1_logits, a2_logits = self.model.action_module(self.inputs, a1_vec)
|
||||
return TorchCategorical(a1_logits).logp(a1) + TorchCategorical(a2_logits).logp(
|
||||
a2
|
||||
)
|
||||
|
||||
def sampled_action_logp(self):
|
||||
return self._action_logp
|
||||
|
||||
def entropy(self):
|
||||
a1_dist = self._a1_distribution()
|
||||
a2_dist = self._a2_distribution(a1_dist.sample())
|
||||
return a1_dist.entropy() + a2_dist.entropy()
|
||||
|
||||
def kl(self, other):
|
||||
a1_dist = self._a1_distribution()
|
||||
a1_terms = a1_dist.kl(other._a1_distribution())
|
||||
|
||||
a1 = a1_dist.sample()
|
||||
a2_terms = self._a2_distribution(a1).kl(other._a2_distribution(a1))
|
||||
return a1_terms + a2_terms
|
||||
|
||||
def _a1_distribution(self):
|
||||
BATCH = self.inputs.shape[0]
|
||||
zeros = torch.zeros((BATCH, 1)).to(self.inputs.device)
|
||||
a1_logits, _ = self.model.action_module(self.inputs, zeros)
|
||||
a1_dist = TorchCategorical(a1_logits)
|
||||
return a1_dist
|
||||
|
||||
def _a2_distribution(self, a1):
|
||||
a1_vec = torch.unsqueeze(a1.float(), 1)
|
||||
_, a2_logits = self.model.action_module(self.inputs, a1_vec)
|
||||
a2_dist = TorchCategorical(a2_logits)
|
||||
return a2_dist
|
||||
|
||||
@staticmethod
|
||||
def required_model_output_shape(action_space, model_config):
|
||||
return 16 # controls model output feature vector size
|
||||
@@ -0,0 +1,161 @@
|
||||
# @OldAPIStack
|
||||
from gymnasium.spaces import Discrete, Tuple
|
||||
|
||||
from ray.rllib.models.tf.misc import normc_initializer
|
||||
from ray.rllib.models.tf.tf_modelv2 import TFModelV2
|
||||
from ray.rllib.models.torch.misc import SlimFC, normc_initializer as normc_init_torch
|
||||
from ray.rllib.models.torch.torch_modelv2 import TorchModelV2
|
||||
from ray.rllib.utils.framework import try_import_tf, try_import_torch
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
torch, nn = try_import_torch()
|
||||
|
||||
|
||||
class AutoregressiveActionModel(TFModelV2):
|
||||
"""Implements the `.action_model` branch required above."""
|
||||
|
||||
def __init__(self, obs_space, action_space, num_outputs, model_config, name):
|
||||
super(AutoregressiveActionModel, self).__init__(
|
||||
obs_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
if action_space != Tuple([Discrete(2), Discrete(2)]):
|
||||
raise ValueError("This model only supports the [2, 2] action space")
|
||||
|
||||
# Inputs
|
||||
obs_input = tf.keras.layers.Input(shape=obs_space.shape, name="obs_input")
|
||||
a1_input = tf.keras.layers.Input(shape=(1,), name="a1_input")
|
||||
ctx_input = tf.keras.layers.Input(shape=(num_outputs,), name="ctx_input")
|
||||
|
||||
# Output of the model (normally 'logits', but for an autoregressive
|
||||
# dist this is more like a context/feature layer encoding the obs)
|
||||
context = tf.keras.layers.Dense(
|
||||
num_outputs,
|
||||
name="hidden",
|
||||
activation=tf.nn.tanh,
|
||||
kernel_initializer=normc_initializer(1.0),
|
||||
)(obs_input)
|
||||
|
||||
# V(s)
|
||||
value_out = tf.keras.layers.Dense(
|
||||
1,
|
||||
name="value_out",
|
||||
activation=None,
|
||||
kernel_initializer=normc_initializer(0.01),
|
||||
)(context)
|
||||
|
||||
# P(a1 | obs)
|
||||
a1_logits = tf.keras.layers.Dense(
|
||||
2,
|
||||
name="a1_logits",
|
||||
activation=None,
|
||||
kernel_initializer=normc_initializer(0.01),
|
||||
)(ctx_input)
|
||||
|
||||
# P(a2 | a1)
|
||||
# --note: typically you'd want to implement P(a2 | a1, obs) as follows:
|
||||
# a2_context = tf.keras.layers.Concatenate(axis=1)(
|
||||
# [ctx_input, a1_input])
|
||||
a2_context = a1_input
|
||||
a2_hidden = tf.keras.layers.Dense(
|
||||
16,
|
||||
name="a2_hidden",
|
||||
activation=tf.nn.tanh,
|
||||
kernel_initializer=normc_initializer(1.0),
|
||||
)(a2_context)
|
||||
a2_logits = tf.keras.layers.Dense(
|
||||
2,
|
||||
name="a2_logits",
|
||||
activation=None,
|
||||
kernel_initializer=normc_initializer(0.01),
|
||||
)(a2_hidden)
|
||||
|
||||
# Base layers
|
||||
self.base_model = tf.keras.Model(obs_input, [context, value_out])
|
||||
self.base_model.summary()
|
||||
|
||||
# Autoregressive action sampler
|
||||
self.action_model = tf.keras.Model(
|
||||
[ctx_input, a1_input], [a1_logits, a2_logits]
|
||||
)
|
||||
self.action_model.summary()
|
||||
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
context, self._value_out = self.base_model(input_dict["obs"])
|
||||
return context, state
|
||||
|
||||
def value_function(self):
|
||||
return tf.reshape(self._value_out, [-1])
|
||||
|
||||
|
||||
class TorchAutoregressiveActionModel(TorchModelV2, nn.Module):
|
||||
"""PyTorch version of the AutoregressiveActionModel above."""
|
||||
|
||||
def __init__(self, obs_space, action_space, num_outputs, model_config, name):
|
||||
TorchModelV2.__init__(
|
||||
self, obs_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
nn.Module.__init__(self)
|
||||
|
||||
if action_space != Tuple([Discrete(2), Discrete(2)]):
|
||||
raise ValueError("This model only supports the [2, 2] action space")
|
||||
|
||||
# Output of the model (normally 'logits', but for an autoregressive
|
||||
# dist this is more like a context/feature layer encoding the obs)
|
||||
self.context_layer = SlimFC(
|
||||
in_size=obs_space.shape[0],
|
||||
out_size=num_outputs,
|
||||
initializer=normc_init_torch(1.0),
|
||||
activation_fn=nn.Tanh,
|
||||
)
|
||||
|
||||
# V(s)
|
||||
self.value_branch = SlimFC(
|
||||
in_size=num_outputs,
|
||||
out_size=1,
|
||||
initializer=normc_init_torch(0.01),
|
||||
activation_fn=None,
|
||||
)
|
||||
|
||||
# P(a1 | obs)
|
||||
self.a1_logits = SlimFC(
|
||||
in_size=num_outputs,
|
||||
out_size=2,
|
||||
activation_fn=None,
|
||||
initializer=normc_init_torch(0.01),
|
||||
)
|
||||
|
||||
class _ActionModel(nn.Module):
|
||||
def __init__(self):
|
||||
nn.Module.__init__(self)
|
||||
self.a2_hidden = SlimFC(
|
||||
in_size=1,
|
||||
out_size=16,
|
||||
activation_fn=nn.Tanh,
|
||||
initializer=normc_init_torch(1.0),
|
||||
)
|
||||
self.a2_logits = SlimFC(
|
||||
in_size=16,
|
||||
out_size=2,
|
||||
activation_fn=None,
|
||||
initializer=normc_init_torch(0.01),
|
||||
)
|
||||
|
||||
def forward(self_, ctx_input, a1_input):
|
||||
a1_logits = self.a1_logits(ctx_input)
|
||||
a2_logits = self_.a2_logits(self_.a2_hidden(a1_input))
|
||||
return a1_logits, a2_logits
|
||||
|
||||
# P(a2 | a1)
|
||||
# --note: typically you'd want to implement P(a2 | a1, obs) as follows:
|
||||
# a2_context = tf.keras.layers.Concatenate(axis=1)(
|
||||
# [ctx_input, a1_input])
|
||||
self.action_module = _ActionModel()
|
||||
|
||||
self._context = None
|
||||
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
self._context = self.context_layer(input_dict["obs"])
|
||||
return self._context, state
|
||||
|
||||
def value_function(self):
|
||||
return torch.reshape(self.value_branch(self._context), [-1])
|
||||
@@ -0,0 +1,182 @@
|
||||
# @OldAPIStack
|
||||
from gymnasium.spaces import Box
|
||||
|
||||
from ray.rllib.models.modelv2 import ModelV2
|
||||
from ray.rllib.models.tf.fcnet import FullyConnectedNetwork
|
||||
from ray.rllib.models.tf.tf_modelv2 import TFModelV2
|
||||
from ray.rllib.models.torch.fcnet import FullyConnectedNetwork as TorchFC
|
||||
from ray.rllib.models.torch.misc import SlimFC
|
||||
from ray.rllib.models.torch.torch_modelv2 import TorchModelV2
|
||||
from ray.rllib.utils.annotations import override
|
||||
from ray.rllib.utils.framework import try_import_tf, try_import_torch
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
torch, nn = try_import_torch()
|
||||
|
||||
|
||||
class CentralizedCriticModel(TFModelV2):
|
||||
"""Multi-agent model that implements a centralized value function."""
|
||||
|
||||
def __init__(self, obs_space, action_space, num_outputs, model_config, name):
|
||||
super(CentralizedCriticModel, self).__init__(
|
||||
obs_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
# Base of the model
|
||||
self.model = FullyConnectedNetwork(
|
||||
obs_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
|
||||
# Central VF maps (obs, opp_obs, opp_act) -> vf_pred
|
||||
obs = tf.keras.layers.Input(shape=(6,), name="obs")
|
||||
opp_obs = tf.keras.layers.Input(shape=(6,), name="opp_obs")
|
||||
opp_act = tf.keras.layers.Input(shape=(2,), name="opp_act")
|
||||
concat_obs = tf.keras.layers.Concatenate(axis=1)([obs, opp_obs, opp_act])
|
||||
central_vf_dense = tf.keras.layers.Dense(
|
||||
16, activation=tf.nn.tanh, name="c_vf_dense"
|
||||
)(concat_obs)
|
||||
central_vf_out = tf.keras.layers.Dense(1, activation=None, name="c_vf_out")(
|
||||
central_vf_dense
|
||||
)
|
||||
self.central_vf = tf.keras.Model(
|
||||
inputs=[obs, opp_obs, opp_act], outputs=central_vf_out
|
||||
)
|
||||
|
||||
@override(ModelV2)
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
return self.model.forward(input_dict, state, seq_lens)
|
||||
|
||||
def central_value_function(self, obs, opponent_obs, opponent_actions):
|
||||
return tf.reshape(
|
||||
self.central_vf(
|
||||
[obs, opponent_obs, tf.one_hot(tf.cast(opponent_actions, tf.int32), 2)]
|
||||
),
|
||||
[-1],
|
||||
)
|
||||
|
||||
@override(ModelV2)
|
||||
def value_function(self):
|
||||
return self.model.value_function() # not used
|
||||
|
||||
|
||||
class YetAnotherCentralizedCriticModel(TFModelV2):
|
||||
"""Multi-agent model that implements a centralized value function.
|
||||
|
||||
It assumes the observation is a dict with 'own_obs' and 'opponent_obs', the
|
||||
former of which can be used for computing actions (i.e., decentralized
|
||||
execution), and the latter for optimization (i.e., centralized learning).
|
||||
|
||||
This model has two parts:
|
||||
- An action model that looks at just 'own_obs' to compute actions
|
||||
- A value model that also looks at the 'opponent_obs' / 'opponent_action'
|
||||
to compute the value (it does this by using the 'obs_flat' tensor).
|
||||
"""
|
||||
|
||||
def __init__(self, obs_space, action_space, num_outputs, model_config, name):
|
||||
super(YetAnotherCentralizedCriticModel, self).__init__(
|
||||
obs_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
|
||||
self.action_model = FullyConnectedNetwork(
|
||||
Box(low=0, high=1, shape=(6,)), # one-hot encoded Discrete(6)
|
||||
action_space,
|
||||
num_outputs,
|
||||
model_config,
|
||||
name + "_action",
|
||||
)
|
||||
|
||||
self.value_model = FullyConnectedNetwork(
|
||||
obs_space, action_space, 1, model_config, name + "_vf"
|
||||
)
|
||||
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
self._value_out, _ = self.value_model(
|
||||
{"obs": input_dict["obs_flat"]}, state, seq_lens
|
||||
)
|
||||
return self.action_model({"obs": input_dict["obs"]["own_obs"]}, state, seq_lens)
|
||||
|
||||
def value_function(self):
|
||||
return tf.reshape(self._value_out, [-1])
|
||||
|
||||
|
||||
class TorchCentralizedCriticModel(TorchModelV2, nn.Module):
|
||||
"""Multi-agent model that implements a centralized VF."""
|
||||
|
||||
def __init__(self, obs_space, action_space, num_outputs, model_config, name):
|
||||
TorchModelV2.__init__(
|
||||
self, obs_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
nn.Module.__init__(self)
|
||||
|
||||
# Base of the model
|
||||
self.model = TorchFC(obs_space, action_space, num_outputs, model_config, name)
|
||||
|
||||
# Central VF maps (obs, opp_obs, opp_act) -> vf_pred
|
||||
input_size = 6 + 6 + 2 # obs + opp_obs + opp_act
|
||||
self.central_vf = nn.Sequential(
|
||||
SlimFC(input_size, 16, activation_fn=nn.Tanh),
|
||||
SlimFC(16, 1),
|
||||
)
|
||||
|
||||
@override(ModelV2)
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
model_out, _ = self.model(input_dict, state, seq_lens)
|
||||
return model_out, []
|
||||
|
||||
def central_value_function(self, obs, opponent_obs, opponent_actions):
|
||||
input_ = torch.cat(
|
||||
[
|
||||
obs,
|
||||
opponent_obs,
|
||||
torch.nn.functional.one_hot(opponent_actions.long(), 2).float(),
|
||||
],
|
||||
1,
|
||||
)
|
||||
return torch.reshape(self.central_vf(input_), [-1])
|
||||
|
||||
@override(ModelV2)
|
||||
def value_function(self):
|
||||
return self.model.value_function() # not used
|
||||
|
||||
|
||||
class YetAnotherTorchCentralizedCriticModel(TorchModelV2, nn.Module):
|
||||
"""Multi-agent model that implements a centralized value function.
|
||||
|
||||
It assumes the observation is a dict with 'own_obs' and 'opponent_obs', the
|
||||
former of which can be used for computing actions (i.e., decentralized
|
||||
execution), and the latter for optimization (i.e., centralized learning).
|
||||
|
||||
This model has two parts:
|
||||
- An action model that looks at just 'own_obs' to compute actions
|
||||
- A value model that also looks at the 'opponent_obs' / 'opponent_action'
|
||||
to compute the value (it does this by using the 'obs_flat' tensor).
|
||||
"""
|
||||
|
||||
def __init__(self, obs_space, action_space, num_outputs, model_config, name):
|
||||
TorchModelV2.__init__(
|
||||
self, obs_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
nn.Module.__init__(self)
|
||||
|
||||
self.action_model = TorchFC(
|
||||
Box(low=0, high=1, shape=(6,)), # one-hot encoded Discrete(6)
|
||||
action_space,
|
||||
num_outputs,
|
||||
model_config,
|
||||
name + "_action",
|
||||
)
|
||||
|
||||
self.value_model = TorchFC(
|
||||
obs_space, action_space, 1, model_config, name + "_vf"
|
||||
)
|
||||
self._model_in = None
|
||||
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
# Store model-input for possible `value_function()` call.
|
||||
self._model_in = [input_dict["obs_flat"], state, seq_lens]
|
||||
return self.action_model({"obs": input_dict["obs"]["own_obs"]}, state, seq_lens)
|
||||
|
||||
def value_function(self):
|
||||
value_out, _ = self.value_model(
|
||||
{"obs": self._model_in[0]}, self._model_in[1], self._model_in[2]
|
||||
)
|
||||
return torch.reshape(value_out, [-1])
|
||||
@@ -0,0 +1,137 @@
|
||||
import numpy as np
|
||||
|
||||
from ray.rllib.models.modelv2 import ModelV2, restore_original_dimensions
|
||||
from ray.rllib.models.tf.fcnet import FullyConnectedNetwork
|
||||
from ray.rllib.models.tf.tf_action_dist import Categorical
|
||||
from ray.rllib.models.tf.tf_modelv2 import TFModelV2
|
||||
from ray.rllib.models.torch.fcnet import FullyConnectedNetwork as TorchFC
|
||||
from ray.rllib.models.torch.torch_action_dist import TorchCategorical
|
||||
from ray.rllib.models.torch.torch_modelv2 import TorchModelV2
|
||||
from ray.rllib.offline import JsonReader
|
||||
from ray.rllib.utils.annotations import override
|
||||
from ray.rllib.utils.framework import try_import_tf, try_import_torch
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
torch, nn = try_import_torch()
|
||||
|
||||
|
||||
class CustomLossModel(TFModelV2):
|
||||
"""Custom model that adds an imitation loss on top of the policy loss."""
|
||||
|
||||
def __init__(self, obs_space, action_space, num_outputs, model_config, name):
|
||||
super().__init__(obs_space, action_space, num_outputs, model_config, name)
|
||||
|
||||
self.fcnet = FullyConnectedNetwork(
|
||||
self.obs_space, self.action_space, num_outputs, model_config, name="fcnet"
|
||||
)
|
||||
|
||||
@override(ModelV2)
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
# Delegate to our FCNet.
|
||||
return self.fcnet(input_dict, state, seq_lens)
|
||||
|
||||
@override(ModelV2)
|
||||
def value_function(self):
|
||||
# Delegate to our FCNet.
|
||||
return self.fcnet.value_function()
|
||||
|
||||
@override(ModelV2)
|
||||
def custom_loss(self, policy_loss, loss_inputs):
|
||||
# Create a new input reader per worker.
|
||||
reader = JsonReader(self.model_config["custom_model_config"]["input_files"])
|
||||
input_ops = reader.tf_input_ops()
|
||||
|
||||
# Define a secondary loss by building a graph copy with weight sharing.
|
||||
obs = restore_original_dimensions(
|
||||
tf.cast(input_ops["obs"], tf.float32), self.obs_space
|
||||
)
|
||||
logits, _ = self.forward({"obs": obs}, [], None)
|
||||
|
||||
# Compute the IL loss.
|
||||
action_dist = Categorical(logits, self.model_config)
|
||||
self.policy_loss = policy_loss
|
||||
self.imitation_loss = tf.reduce_mean(-action_dist.logp(input_ops["actions"]))
|
||||
return policy_loss + 10 * self.imitation_loss
|
||||
|
||||
def metrics(self):
|
||||
return {
|
||||
"policy_loss": self.policy_loss,
|
||||
"imitation_loss": self.imitation_loss,
|
||||
}
|
||||
|
||||
|
||||
class TorchCustomLossModel(TorchModelV2, nn.Module):
|
||||
"""PyTorch version of the CustomLossModel above."""
|
||||
|
||||
def __init__(
|
||||
self, obs_space, action_space, num_outputs, model_config, name, input_files
|
||||
):
|
||||
super().__init__(obs_space, action_space, num_outputs, model_config, name)
|
||||
nn.Module.__init__(self)
|
||||
|
||||
self.input_files = input_files
|
||||
# Create a new input reader per worker.
|
||||
self.reader = JsonReader(self.input_files)
|
||||
self.fcnet = TorchFC(
|
||||
self.obs_space, self.action_space, num_outputs, model_config, name="fcnet"
|
||||
)
|
||||
|
||||
@override(ModelV2)
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
# Delegate to our FCNet.
|
||||
return self.fcnet(input_dict, state, seq_lens)
|
||||
|
||||
@override(ModelV2)
|
||||
def value_function(self):
|
||||
# Delegate to our FCNet.
|
||||
return self.fcnet.value_function()
|
||||
|
||||
@override(ModelV2)
|
||||
def custom_loss(self, policy_loss, loss_inputs):
|
||||
"""Calculates a custom loss on top of the given policy_loss(es).
|
||||
|
||||
Args:
|
||||
policy_loss (List[TensorType]): The list of already calculated
|
||||
policy losses (as many as there are optimizers).
|
||||
loss_inputs: Struct of np.ndarrays holding the
|
||||
entire train batch.
|
||||
|
||||
Returns:
|
||||
List[TensorType]: The altered list of policy losses. In case the
|
||||
custom loss should have its own optimizer, make sure the
|
||||
returned list is one larger than the incoming policy_loss list.
|
||||
In case you simply want to mix in the custom loss into the
|
||||
already calculated policy losses, return a list of altered
|
||||
policy losses (as done in this example below).
|
||||
"""
|
||||
# Get the next batch from our input files.
|
||||
batch = self.reader.next()
|
||||
|
||||
# Define a secondary loss by building a graph copy with weight sharing.
|
||||
obs = restore_original_dimensions(
|
||||
torch.from_numpy(batch["obs"]).float().to(policy_loss[0].device),
|
||||
self.obs_space,
|
||||
tensorlib="torch",
|
||||
)
|
||||
logits, _ = self.forward({"obs": obs}, [], None)
|
||||
|
||||
# Compute the IL loss.
|
||||
action_dist = TorchCategorical(logits, self.model_config)
|
||||
imitation_loss = torch.mean(
|
||||
-action_dist.logp(
|
||||
torch.from_numpy(batch["actions"]).to(policy_loss[0].device)
|
||||
)
|
||||
)
|
||||
self.imitation_loss_metric = imitation_loss.item()
|
||||
self.policy_loss_metric = np.mean([loss.item() for loss in policy_loss])
|
||||
|
||||
# Add the imitation loss to each already calculated policy loss term.
|
||||
# Alternatively (if custom loss has its own optimizer):
|
||||
# return policy_loss + [10 * self.imitation_loss]
|
||||
return [loss_ + 10 * imitation_loss for loss_ in policy_loss]
|
||||
|
||||
def metrics(self):
|
||||
return {
|
||||
"policy_loss": self.policy_loss_metric,
|
||||
"imitation_loss": self.imitation_loss_metric,
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
# @OldAPIStack
|
||||
from ray.rllib.models.modelv2 import ModelV2
|
||||
from ray.rllib.models.tf.tf_modelv2 import TFModelV2
|
||||
from ray.rllib.models.torch.misc import SlimFC
|
||||
from ray.rllib.models.torch.torch_modelv2 import TorchModelV2
|
||||
from ray.rllib.utils.annotations import override
|
||||
from ray.rllib.utils.framework import try_import_tf, try_import_torch
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
torch, nn = try_import_torch()
|
||||
|
||||
|
||||
class FastModel(TFModelV2):
|
||||
"""An example for a non-Keras ModelV2 in tf that learns a single weight.
|
||||
|
||||
Defines all network architecture in `forward` (not `__init__` as it's
|
||||
usually done for Keras-style TFModelV2s).
|
||||
"""
|
||||
|
||||
def __init__(self, obs_space, action_space, num_outputs, model_config, name):
|
||||
super().__init__(obs_space, action_space, num_outputs, model_config, name)
|
||||
# Have we registered our vars yet (see `forward`)?
|
||||
self._registered = False
|
||||
|
||||
@override(ModelV2)
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
with tf1.variable_scope("model", reuse=tf1.AUTO_REUSE):
|
||||
bias = tf1.get_variable(
|
||||
dtype=tf.float32,
|
||||
name="bias",
|
||||
initializer=tf.keras.initializers.Zeros(),
|
||||
shape=(),
|
||||
)
|
||||
output = bias + tf.zeros([tf.shape(input_dict["obs"])[0], self.num_outputs])
|
||||
self._value_out = tf.reduce_mean(output, -1) # fake value
|
||||
|
||||
if not self._registered:
|
||||
self.register_variables(
|
||||
tf1.get_collection(
|
||||
tf1.GraphKeys.TRAINABLE_VARIABLES, scope=".+/model/.+"
|
||||
)
|
||||
)
|
||||
self._registered = True
|
||||
|
||||
return output, []
|
||||
|
||||
@override(ModelV2)
|
||||
def value_function(self):
|
||||
return tf.reshape(self._value_out, [-1])
|
||||
|
||||
|
||||
class TorchFastModel(TorchModelV2, nn.Module):
|
||||
"""Torch version of FastModel (tf)."""
|
||||
|
||||
def __init__(self, obs_space, action_space, num_outputs, model_config, name):
|
||||
TorchModelV2.__init__(
|
||||
self, obs_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
nn.Module.__init__(self)
|
||||
|
||||
self.bias = nn.Parameter(
|
||||
torch.tensor([0.0], dtype=torch.float32, requires_grad=True)
|
||||
)
|
||||
|
||||
# Only needed to give some params to the optimizer (even though,
|
||||
# they are never used anywhere).
|
||||
self.dummy_layer = SlimFC(1, 1)
|
||||
self._output = None
|
||||
|
||||
@override(ModelV2)
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
self._output = self.bias + torch.zeros(
|
||||
size=(input_dict["obs"].shape[0], self.num_outputs)
|
||||
).to(self.bias.device)
|
||||
return self._output, []
|
||||
|
||||
@override(ModelV2)
|
||||
def value_function(self):
|
||||
assert self._output is not None, "must call forward first!"
|
||||
return torch.reshape(torch.mean(self._output, -1), [-1])
|
||||
@@ -0,0 +1,248 @@
|
||||
# @OldAPIStack
|
||||
from collections import OrderedDict
|
||||
from typing import Dict, List, Tuple, Union
|
||||
|
||||
import gymnasium as gym
|
||||
|
||||
from ray.rllib.models.torch.misc import SlimFC
|
||||
from ray.rllib.models.torch.torch_modelv2 import TorchModelV2
|
||||
from ray.rllib.utils.framework import try_import_torch
|
||||
from ray.rllib.utils.typing import ModelConfigDict, TensorType
|
||||
|
||||
try:
|
||||
from dnc import DNC
|
||||
except ModuleNotFoundError:
|
||||
print("dnc module not found. Did you forget to 'pip install dnc'?")
|
||||
raise
|
||||
|
||||
torch, nn = try_import_torch()
|
||||
|
||||
|
||||
class DNCMemory(TorchModelV2, nn.Module):
|
||||
"""Differentiable Neural Computer wrapper around ixaxaar's DNC implementation,
|
||||
see https://github.com/ixaxaar/pytorch-dnc"""
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
"dnc_model": DNC,
|
||||
# Number of controller hidden layers
|
||||
"num_hidden_layers": 1,
|
||||
# Number of weights per controller hidden layer
|
||||
"hidden_size": 64,
|
||||
# Number of LSTM units
|
||||
"num_layers": 1,
|
||||
# Number of read heads, i.e. how many addrs are read at once
|
||||
"read_heads": 4,
|
||||
# Number of memory cells in the controller
|
||||
"nr_cells": 32,
|
||||
# Size of each cell
|
||||
"cell_size": 16,
|
||||
# LSTM activation function
|
||||
"nonlinearity": "tanh",
|
||||
# Observation goes through this torch.nn.Module before
|
||||
# feeding to the DNC
|
||||
"preprocessor": torch.nn.Sequential(torch.nn.Linear(64, 64), torch.nn.Tanh()),
|
||||
# Input size to the preprocessor
|
||||
"preprocessor_input_size": 64,
|
||||
# The output size of the preprocessor
|
||||
# and the input size of the dnc
|
||||
"preprocessor_output_size": 64,
|
||||
}
|
||||
|
||||
MEMORY_KEYS = [
|
||||
"memory",
|
||||
"link_matrix",
|
||||
"precedence",
|
||||
"read_weights",
|
||||
"write_weights",
|
||||
"usage_vector",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
obs_space: gym.spaces.Space,
|
||||
action_space: gym.spaces.Space,
|
||||
num_outputs: int,
|
||||
model_config: ModelConfigDict,
|
||||
name: str,
|
||||
**custom_model_kwargs,
|
||||
):
|
||||
nn.Module.__init__(self)
|
||||
super(DNCMemory, self).__init__(
|
||||
obs_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
self.num_outputs = num_outputs
|
||||
self.obs_dim = gym.spaces.utils.flatdim(obs_space)
|
||||
self.act_dim = gym.spaces.utils.flatdim(action_space)
|
||||
|
||||
self.cfg = dict(self.DEFAULT_CONFIG, **custom_model_kwargs)
|
||||
assert (
|
||||
self.cfg["num_layers"] == 1
|
||||
), "num_layers != 1 has not been implemented yet"
|
||||
self.cur_val = None
|
||||
|
||||
self.preprocessor = torch.nn.Sequential(
|
||||
torch.nn.Linear(self.obs_dim, self.cfg["preprocessor_input_size"]),
|
||||
self.cfg["preprocessor"],
|
||||
)
|
||||
|
||||
self.logit_branch = SlimFC(
|
||||
in_size=self.cfg["hidden_size"],
|
||||
out_size=self.num_outputs,
|
||||
activation_fn=None,
|
||||
initializer=torch.nn.init.xavier_uniform_,
|
||||
)
|
||||
|
||||
self.value_branch = SlimFC(
|
||||
in_size=self.cfg["hidden_size"],
|
||||
out_size=1,
|
||||
activation_fn=None,
|
||||
initializer=torch.nn.init.xavier_uniform_,
|
||||
)
|
||||
|
||||
self.dnc: Union[None, DNC] = None
|
||||
|
||||
def get_initial_state(self) -> List[TensorType]:
|
||||
ctrl_hidden = [
|
||||
torch.zeros(self.cfg["num_hidden_layers"], self.cfg["hidden_size"]),
|
||||
torch.zeros(self.cfg["num_hidden_layers"], self.cfg["hidden_size"]),
|
||||
]
|
||||
m = self.cfg["nr_cells"]
|
||||
r = self.cfg["read_heads"]
|
||||
w = self.cfg["cell_size"]
|
||||
memory = [
|
||||
torch.zeros(m, w), # memory
|
||||
torch.zeros(1, m, m), # link_matrix
|
||||
torch.zeros(1, m), # precedence
|
||||
torch.zeros(r, m), # read_weights
|
||||
torch.zeros(1, m), # write_weights
|
||||
torch.zeros(m), # usage_vector
|
||||
]
|
||||
|
||||
read_vecs = torch.zeros(w * r)
|
||||
|
||||
state = [*ctrl_hidden, read_vecs, *memory]
|
||||
assert len(state) == 9
|
||||
return state
|
||||
|
||||
def value_function(self) -> TensorType:
|
||||
assert self.cur_val is not None, "must call forward() first"
|
||||
return self.cur_val
|
||||
|
||||
def unpack_state(
|
||||
self,
|
||||
state: List[TensorType],
|
||||
) -> Tuple[List[Tuple[TensorType, TensorType]], Dict[str, TensorType], TensorType]:
|
||||
"""Given a list of tensors, reformat for self.dnc input"""
|
||||
assert len(state) == 9, "Failed to verify unpacked state"
|
||||
ctrl_hidden: List[Tuple[TensorType, TensorType]] = [
|
||||
(
|
||||
state[0].permute(1, 0, 2).contiguous(),
|
||||
state[1].permute(1, 0, 2).contiguous(),
|
||||
)
|
||||
]
|
||||
read_vecs: TensorType = state[2]
|
||||
memory: List[TensorType] = state[3:]
|
||||
memory_dict: OrderedDict[str, TensorType] = OrderedDict(
|
||||
zip(self.MEMORY_KEYS, memory)
|
||||
)
|
||||
|
||||
return ctrl_hidden, memory_dict, read_vecs
|
||||
|
||||
def pack_state(
|
||||
self,
|
||||
ctrl_hidden: List[Tuple[TensorType, TensorType]],
|
||||
memory_dict: Dict[str, TensorType],
|
||||
read_vecs: TensorType,
|
||||
) -> List[TensorType]:
|
||||
"""Given the dnc output, pack it into a list of tensors
|
||||
for rllib state. Order is ctrl_hidden, read_vecs, memory_dict"""
|
||||
state = []
|
||||
ctrl_hidden = [
|
||||
ctrl_hidden[0][0].permute(1, 0, 2),
|
||||
ctrl_hidden[0][1].permute(1, 0, 2),
|
||||
]
|
||||
state += ctrl_hidden
|
||||
assert len(state) == 2, "Failed to verify packed state"
|
||||
state.append(read_vecs)
|
||||
assert len(state) == 3, "Failed to verify packed state"
|
||||
state += memory_dict.values()
|
||||
assert len(state) == 9, "Failed to verify packed state"
|
||||
return state
|
||||
|
||||
def validate_unpack(self, dnc_output, unpacked_state):
|
||||
"""Ensure the unpacked state shapes match the DNC output"""
|
||||
s_ctrl_hidden, s_memory_dict, s_read_vecs = unpacked_state
|
||||
ctrl_hidden, memory_dict, read_vecs = dnc_output
|
||||
|
||||
for i in range(len(ctrl_hidden)):
|
||||
for j in range(len(ctrl_hidden[i])):
|
||||
assert s_ctrl_hidden[i][j].shape == ctrl_hidden[i][j].shape, (
|
||||
"Controller state mismatch: got "
|
||||
f"{s_ctrl_hidden[i][j].shape} should be "
|
||||
f"{ctrl_hidden[i][j].shape}"
|
||||
)
|
||||
|
||||
for k in memory_dict:
|
||||
assert s_memory_dict[k].shape == memory_dict[k].shape, (
|
||||
"Memory state mismatch at key "
|
||||
f"{k}: got {s_memory_dict[k].shape} should be "
|
||||
f"{memory_dict[k].shape}"
|
||||
)
|
||||
|
||||
assert s_read_vecs.shape == read_vecs.shape, (
|
||||
"Read state mismatch: got "
|
||||
f"{s_read_vecs.shape} should be "
|
||||
f"{read_vecs.shape}"
|
||||
)
|
||||
|
||||
def build_dnc(self, device_idx: Union[int, None]) -> None:
|
||||
self.dnc = self.cfg["dnc_model"](
|
||||
input_size=self.cfg["preprocessor_output_size"],
|
||||
hidden_size=self.cfg["hidden_size"],
|
||||
num_layers=self.cfg["num_layers"],
|
||||
num_hidden_layers=self.cfg["num_hidden_layers"],
|
||||
read_heads=self.cfg["read_heads"],
|
||||
cell_size=self.cfg["cell_size"],
|
||||
nr_cells=self.cfg["nr_cells"],
|
||||
nonlinearity=self.cfg["nonlinearity"],
|
||||
gpu_id=device_idx,
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
input_dict: Dict[str, TensorType],
|
||||
state: List[TensorType],
|
||||
seq_lens: TensorType,
|
||||
) -> Tuple[TensorType, List[TensorType]]:
|
||||
|
||||
flat = input_dict["obs_flat"]
|
||||
# Batch and Time
|
||||
# Forward expects outputs as [B, T, logits]
|
||||
B = len(seq_lens)
|
||||
T = flat.shape[0] // B
|
||||
|
||||
# Deconstruct batch into batch and time dimensions: [B, T, feats]
|
||||
flat = torch.reshape(flat, [-1, T] + list(flat.shape[1:]))
|
||||
|
||||
# First run
|
||||
if self.dnc is None:
|
||||
gpu_id = flat.device.index if flat.device.index is not None else -1
|
||||
self.build_dnc(gpu_id)
|
||||
hidden = (None, None, None)
|
||||
|
||||
else:
|
||||
hidden = self.unpack_state(state) # type: ignore
|
||||
|
||||
# Run thru preprocessor before DNC
|
||||
z = self.preprocessor(flat.reshape(B * T, self.obs_dim))
|
||||
z = z.reshape(B, T, self.cfg["preprocessor_output_size"])
|
||||
output, hidden = self.dnc(z, hidden)
|
||||
packed_state = self.pack_state(*hidden)
|
||||
|
||||
# Compute action/value from output
|
||||
logits = self.logit_branch(output.view(B * T, -1))
|
||||
values = self.value_branch(output.view(B * T, -1))
|
||||
|
||||
self.cur_val = values.squeeze(1)
|
||||
|
||||
return logits, packed_state
|
||||
@@ -0,0 +1,201 @@
|
||||
# @OldAPIStack
|
||||
from gymnasium.spaces import Box
|
||||
|
||||
from ray.rllib.algorithms.dqn.distributional_q_tf_model import DistributionalQTFModel
|
||||
from ray.rllib.algorithms.dqn.dqn_torch_model import DQNTorchModel
|
||||
from ray.rllib.models.tf.fcnet import FullyConnectedNetwork
|
||||
from ray.rllib.models.torch.fcnet import FullyConnectedNetwork as TorchFC
|
||||
from ray.rllib.utils.framework import try_import_tf, try_import_torch
|
||||
from ray.rllib.utils.torch_utils import FLOAT_MAX, FLOAT_MIN
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
torch, nn = try_import_torch()
|
||||
|
||||
|
||||
class ParametricActionsModel(DistributionalQTFModel):
|
||||
"""Parametric action model that handles the dot product and masking.
|
||||
|
||||
This assumes the outputs are logits for a single Categorical action dist.
|
||||
Getting this to work with a more complex output (e.g., if the action space
|
||||
is a tuple of several distributions) is also possible but left as an
|
||||
exercise to the reader.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
obs_space,
|
||||
action_space,
|
||||
num_outputs,
|
||||
model_config,
|
||||
name,
|
||||
true_obs_shape=(4,),
|
||||
action_embed_size=2,
|
||||
**kw
|
||||
):
|
||||
super(ParametricActionsModel, self).__init__(
|
||||
obs_space, action_space, num_outputs, model_config, name, **kw
|
||||
)
|
||||
self.action_embed_model = FullyConnectedNetwork(
|
||||
Box(-1, 1, shape=true_obs_shape),
|
||||
action_space,
|
||||
action_embed_size,
|
||||
model_config,
|
||||
name + "_action_embed",
|
||||
)
|
||||
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
# Extract the available actions tensor from the observation.
|
||||
avail_actions = input_dict["obs"]["avail_actions"]
|
||||
action_mask = input_dict["obs"]["action_mask"]
|
||||
|
||||
# Compute the predicted action embedding
|
||||
action_embed, _ = self.action_embed_model({"obs": input_dict["obs"]["cart"]})
|
||||
|
||||
# Expand the model output to [BATCH, 1, EMBED_SIZE]. Note that the
|
||||
# avail actions tensor is of shape [BATCH, MAX_ACTIONS, EMBED_SIZE].
|
||||
intent_vector = tf.expand_dims(action_embed, 1)
|
||||
|
||||
# Batch dot product => shape of logits is [BATCH, MAX_ACTIONS].
|
||||
action_logits = tf.reduce_sum(avail_actions * intent_vector, axis=2)
|
||||
|
||||
# Mask out invalid actions (use tf.float32.min for stability)
|
||||
inf_mask = tf.maximum(tf.math.log(action_mask), tf.float32.min)
|
||||
return action_logits + inf_mask, state
|
||||
|
||||
def value_function(self):
|
||||
return self.action_embed_model.value_function()
|
||||
|
||||
|
||||
class TorchParametricActionsModel(DQNTorchModel):
|
||||
"""PyTorch version of above ParametricActionsModel."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
obs_space,
|
||||
action_space,
|
||||
num_outputs,
|
||||
model_config,
|
||||
name,
|
||||
true_obs_shape=(4,),
|
||||
action_embed_size=2,
|
||||
**kw
|
||||
):
|
||||
DQNTorchModel.__init__(
|
||||
self, obs_space, action_space, num_outputs, model_config, name, **kw
|
||||
)
|
||||
|
||||
self.action_embed_model = TorchFC(
|
||||
Box(-1, 1, shape=true_obs_shape),
|
||||
action_space,
|
||||
action_embed_size,
|
||||
model_config,
|
||||
name + "_action_embed",
|
||||
)
|
||||
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
# Extract the available actions tensor from the observation.
|
||||
avail_actions = input_dict["obs"]["avail_actions"]
|
||||
action_mask = input_dict["obs"]["action_mask"]
|
||||
|
||||
# Compute the predicted action embedding
|
||||
action_embed, _ = self.action_embed_model({"obs": input_dict["obs"]["cart"]})
|
||||
|
||||
# Expand the model output to [BATCH, 1, EMBED_SIZE]. Note that the
|
||||
# avail actions tensor is of shape [BATCH, MAX_ACTIONS, EMBED_SIZE].
|
||||
intent_vector = torch.unsqueeze(action_embed, 1)
|
||||
|
||||
# Batch dot product => shape of logits is [BATCH, MAX_ACTIONS].
|
||||
action_logits = torch.sum(avail_actions * intent_vector, dim=2)
|
||||
|
||||
# Mask out invalid actions (use -inf to tag invalid).
|
||||
# These are then recognized by the EpsilonGreedy exploration component
|
||||
# as invalid actions that are not to be chosen.
|
||||
inf_mask = torch.clamp(torch.log(action_mask), FLOAT_MIN, FLOAT_MAX)
|
||||
|
||||
return action_logits + inf_mask, state
|
||||
|
||||
def value_function(self):
|
||||
return self.action_embed_model.value_function()
|
||||
|
||||
|
||||
class ParametricActionsModelThatLearnsEmbeddings(DistributionalQTFModel):
|
||||
"""Same as the above ParametricActionsModel.
|
||||
|
||||
However, this version also learns the action embeddings.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
obs_space,
|
||||
action_space,
|
||||
num_outputs,
|
||||
model_config,
|
||||
name,
|
||||
true_obs_shape=(4,),
|
||||
action_embed_size=2,
|
||||
**kw
|
||||
):
|
||||
super(ParametricActionsModelThatLearnsEmbeddings, self).__init__(
|
||||
obs_space, action_space, num_outputs, model_config, name, **kw
|
||||
)
|
||||
|
||||
action_ids_shifted = tf.constant(
|
||||
list(range(1, num_outputs + 1)), dtype=tf.float32
|
||||
)
|
||||
|
||||
obs_cart = tf.keras.layers.Input(shape=true_obs_shape, name="obs_cart")
|
||||
valid_avail_actions_mask = tf.keras.layers.Input(
|
||||
shape=(num_outputs,), name="valid_avail_actions_mask"
|
||||
)
|
||||
|
||||
self.pred_action_embed_model = FullyConnectedNetwork(
|
||||
Box(-1, 1, shape=true_obs_shape),
|
||||
action_space,
|
||||
action_embed_size,
|
||||
model_config,
|
||||
name + "_pred_action_embed",
|
||||
)
|
||||
|
||||
# Compute the predicted action embedding
|
||||
pred_action_embed, _ = self.pred_action_embed_model({"obs": obs_cart})
|
||||
_value_out = self.pred_action_embed_model.value_function()
|
||||
|
||||
# Expand the model output to [BATCH, 1, EMBED_SIZE]. Note that the
|
||||
# avail actions tensor is of shape [BATCH, MAX_ACTIONS, EMBED_SIZE].
|
||||
intent_vector = tf.expand_dims(pred_action_embed, 1)
|
||||
|
||||
valid_avail_actions = action_ids_shifted * valid_avail_actions_mask
|
||||
# Embedding for valid available actions which will be learned.
|
||||
# Embedding vector for 0 is an invalid embedding (a "dummy embedding").
|
||||
valid_avail_actions_embed = tf.keras.layers.Embedding(
|
||||
input_dim=num_outputs + 1,
|
||||
output_dim=action_embed_size,
|
||||
name="action_embed_matrix",
|
||||
)(valid_avail_actions)
|
||||
|
||||
# Batch dot product => shape of logits is [BATCH, MAX_ACTIONS].
|
||||
action_logits = tf.reduce_sum(valid_avail_actions_embed * intent_vector, axis=2)
|
||||
|
||||
# Mask out invalid actions (use tf.float32.min for stability)
|
||||
inf_mask = tf.maximum(tf.math.log(valid_avail_actions_mask), tf.float32.min)
|
||||
|
||||
action_logits = action_logits + inf_mask
|
||||
|
||||
self.param_actions_model = tf.keras.Model(
|
||||
inputs=[obs_cart, valid_avail_actions_mask],
|
||||
outputs=[action_logits, _value_out],
|
||||
)
|
||||
self.param_actions_model.summary()
|
||||
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
# Extract the available actions mask tensor from the observation.
|
||||
valid_avail_actions_mask = input_dict["obs"]["valid_avail_actions_mask"]
|
||||
|
||||
action_logits, self._value_out = self.param_actions_model(
|
||||
[input_dict["obs"]["cart"], valid_avail_actions_mask]
|
||||
)
|
||||
|
||||
return action_logits, state
|
||||
|
||||
def value_function(self):
|
||||
return self._value_out
|
||||
@@ -0,0 +1,206 @@
|
||||
# @OldAPIStack
|
||||
import numpy as np
|
||||
|
||||
from ray.rllib.models.modelv2 import ModelV2
|
||||
from ray.rllib.models.tf.tf_modelv2 import TFModelV2
|
||||
from ray.rllib.models.torch.misc import SlimFC
|
||||
from ray.rllib.models.torch.torch_modelv2 import TorchModelV2
|
||||
from ray.rllib.utils.annotations import override
|
||||
from ray.rllib.utils.framework import try_import_tf, try_import_torch
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
torch, nn = try_import_torch()
|
||||
|
||||
TF2_GLOBAL_SHARED_LAYER = None
|
||||
|
||||
|
||||
class TF2SharedWeightsModel(TFModelV2):
|
||||
"""Example of weight sharing between two different TFModelV2s.
|
||||
|
||||
NOTE: This will only work for tf2.x. When running with config.framework=tf,
|
||||
use SharedWeightsModel1 and SharedWeightsModel2 below, instead!
|
||||
|
||||
The shared (single) layer is simply defined outside of the two Models,
|
||||
then used by both Models in their forward pass.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, observation_space, action_space, num_outputs, model_config, name
|
||||
):
|
||||
super().__init__(
|
||||
observation_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
|
||||
global TF2_GLOBAL_SHARED_LAYER
|
||||
# The global, shared layer to be used by both models.
|
||||
if TF2_GLOBAL_SHARED_LAYER is None:
|
||||
TF2_GLOBAL_SHARED_LAYER = tf.keras.layers.Dense(
|
||||
units=64, activation=tf.nn.relu, name="fc1"
|
||||
)
|
||||
|
||||
inputs = tf.keras.layers.Input(observation_space.shape)
|
||||
last_layer = TF2_GLOBAL_SHARED_LAYER(inputs)
|
||||
output = tf.keras.layers.Dense(
|
||||
units=num_outputs, activation=None, name="fc_out"
|
||||
)(last_layer)
|
||||
vf = tf.keras.layers.Dense(units=1, activation=None, name="value_out")(
|
||||
last_layer
|
||||
)
|
||||
self.base_model = tf.keras.models.Model(inputs, [output, vf])
|
||||
|
||||
@override(ModelV2)
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
out, self._value_out = self.base_model(input_dict["obs"])
|
||||
return out, []
|
||||
|
||||
@override(ModelV2)
|
||||
def value_function(self):
|
||||
return tf.reshape(self._value_out, [-1])
|
||||
|
||||
|
||||
class SharedWeightsModel1(TFModelV2):
|
||||
"""Example of weight sharing between two different TFModelV2s.
|
||||
|
||||
NOTE: This will only work for tf1 (static graph). When running with
|
||||
config.framework_str=tf2, use TF2SharedWeightsModel, instead!
|
||||
|
||||
Here, we share the variables defined in the 'shared' variable scope
|
||||
by entering it explicitly with tf1.AUTO_REUSE. This creates the
|
||||
variables for the 'fc1' layer in a global scope called 'shared'
|
||||
(outside of the Policy's normal variable scope).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, observation_space, action_space, num_outputs, model_config, name
|
||||
):
|
||||
super().__init__(
|
||||
observation_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
|
||||
inputs = tf.keras.layers.Input(observation_space.shape)
|
||||
with tf1.variable_scope(
|
||||
tf1.VariableScope(tf1.AUTO_REUSE, "shared"),
|
||||
reuse=tf1.AUTO_REUSE,
|
||||
auxiliary_name_scope=False,
|
||||
):
|
||||
last_layer = tf.keras.layers.Dense(
|
||||
units=64, activation=tf.nn.relu, name="fc1"
|
||||
)(inputs)
|
||||
output = tf.keras.layers.Dense(
|
||||
units=num_outputs, activation=None, name="fc_out"
|
||||
)(last_layer)
|
||||
vf = tf.keras.layers.Dense(units=1, activation=None, name="value_out")(
|
||||
last_layer
|
||||
)
|
||||
self.base_model = tf.keras.models.Model(inputs, [output, vf])
|
||||
|
||||
@override(ModelV2)
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
out, self._value_out = self.base_model(input_dict["obs"])
|
||||
return out, []
|
||||
|
||||
@override(ModelV2)
|
||||
def value_function(self):
|
||||
return tf.reshape(self._value_out, [-1])
|
||||
|
||||
|
||||
class SharedWeightsModel2(TFModelV2):
|
||||
"""The "other" TFModelV2 using the same shared space as the one above."""
|
||||
|
||||
def __init__(
|
||||
self, observation_space, action_space, num_outputs, model_config, name
|
||||
):
|
||||
super().__init__(
|
||||
observation_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
|
||||
inputs = tf.keras.layers.Input(observation_space.shape)
|
||||
|
||||
# Weights shared with SharedWeightsModel1.
|
||||
with tf1.variable_scope(
|
||||
tf1.VariableScope(tf1.AUTO_REUSE, "shared"),
|
||||
reuse=tf1.AUTO_REUSE,
|
||||
auxiliary_name_scope=False,
|
||||
):
|
||||
last_layer = tf.keras.layers.Dense(
|
||||
units=64, activation=tf.nn.relu, name="fc1"
|
||||
)(inputs)
|
||||
output = tf.keras.layers.Dense(
|
||||
units=num_outputs, activation=None, name="fc_out"
|
||||
)(last_layer)
|
||||
vf = tf.keras.layers.Dense(units=1, activation=None, name="value_out")(
|
||||
last_layer
|
||||
)
|
||||
self.base_model = tf.keras.models.Model(inputs, [output, vf])
|
||||
|
||||
@override(ModelV2)
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
out, self._value_out = self.base_model(input_dict["obs"])
|
||||
return out, []
|
||||
|
||||
@override(ModelV2)
|
||||
def value_function(self):
|
||||
return tf.reshape(self._value_out, [-1])
|
||||
|
||||
|
||||
TORCH_GLOBAL_SHARED_LAYER = None
|
||||
if torch:
|
||||
# The global, shared layer to be used by both models.
|
||||
TORCH_GLOBAL_SHARED_LAYER = SlimFC(
|
||||
64,
|
||||
64,
|
||||
activation_fn=nn.ReLU,
|
||||
initializer=torch.nn.init.xavier_uniform_,
|
||||
)
|
||||
|
||||
|
||||
class TorchSharedWeightsModel(TorchModelV2, nn.Module):
|
||||
"""Example of weight sharing between two different TorchModelV2s.
|
||||
|
||||
The shared (single) layer is simply defined outside of the two Models,
|
||||
then used by both Models in their forward pass.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, observation_space, action_space, num_outputs, model_config, name
|
||||
):
|
||||
TorchModelV2.__init__(
|
||||
self, observation_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
nn.Module.__init__(self)
|
||||
|
||||
# Non-shared initial layer.
|
||||
self.first_layer = SlimFC(
|
||||
int(np.prod(observation_space.shape)),
|
||||
64,
|
||||
activation_fn=nn.ReLU,
|
||||
initializer=torch.nn.init.xavier_uniform_,
|
||||
)
|
||||
|
||||
# Non-shared final layer.
|
||||
self.last_layer = SlimFC(
|
||||
64,
|
||||
self.num_outputs,
|
||||
activation_fn=None,
|
||||
initializer=torch.nn.init.xavier_uniform_,
|
||||
)
|
||||
self.vf = SlimFC(
|
||||
64,
|
||||
1,
|
||||
activation_fn=None,
|
||||
initializer=torch.nn.init.xavier_uniform_,
|
||||
)
|
||||
self._global_shared_layer = TORCH_GLOBAL_SHARED_LAYER
|
||||
self._output = None
|
||||
|
||||
@override(ModelV2)
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
out = self.first_layer(input_dict["obs"])
|
||||
self._output = self._global_shared_layer(out)
|
||||
model_out = self.last_layer(self._output)
|
||||
return model_out, []
|
||||
|
||||
@override(ModelV2)
|
||||
def value_function(self):
|
||||
assert self._output is not None, "must call forward first!"
|
||||
return torch.reshape(self.vf(self._output), [-1])
|
||||
@@ -0,0 +1,65 @@
|
||||
# @OldAPIStack
|
||||
from ray.rllib.models.tf.fcnet import FullyConnectedNetwork as TFFCNet
|
||||
from ray.rllib.models.tf.tf_modelv2 import TFModelV2
|
||||
from ray.rllib.models.torch.fcnet import FullyConnectedNetwork as TorchFCNet
|
||||
from ray.rllib.models.torch.torch_modelv2 import TorchModelV2
|
||||
from ray.rllib.utils.framework import try_import_tf, try_import_torch
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
torch, nn = try_import_torch()
|
||||
|
||||
|
||||
class CustomTorchRPGModel(TorchModelV2, nn.Module):
|
||||
"""Example of interpreting repeated observations."""
|
||||
|
||||
def __init__(self, obs_space, action_space, num_outputs, model_config, name):
|
||||
super().__init__(obs_space, action_space, num_outputs, model_config, name)
|
||||
nn.Module.__init__(self)
|
||||
self.model = TorchFCNet(
|
||||
obs_space, action_space, num_outputs, model_config, name
|
||||
)
|
||||
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
# The unpacked input tensors, where M=MAX_PLAYERS, N=MAX_ITEMS:
|
||||
# {
|
||||
# 'items', <torch.Tensor shape=(?, M, N, 5)>,
|
||||
# 'location', <torch.Tensor shape=(?, M, 2)>,
|
||||
# 'status', <torch.Tensor shape=(?, M, 10)>,
|
||||
# }
|
||||
print("The unpacked input tensors:", input_dict["obs"])
|
||||
print()
|
||||
print("Unbatched repeat dim", input_dict["obs"].unbatch_repeat_dim())
|
||||
print()
|
||||
print("Fully unbatched", input_dict["obs"].unbatch_all())
|
||||
print()
|
||||
return self.model.forward(input_dict, state, seq_lens)
|
||||
|
||||
def value_function(self):
|
||||
return self.model.value_function()
|
||||
|
||||
|
||||
class CustomTFRPGModel(TFModelV2):
|
||||
"""Example of interpreting repeated observations."""
|
||||
|
||||
def __init__(self, obs_space, action_space, num_outputs, model_config, name):
|
||||
super().__init__(obs_space, action_space, num_outputs, model_config, name)
|
||||
self.model = TFFCNet(obs_space, action_space, num_outputs, model_config, name)
|
||||
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
# The unpacked input tensors, where M=MAX_PLAYERS, N=MAX_ITEMS:
|
||||
# {
|
||||
# 'items', <tf.Tensor shape=(?, M, N, 5)>,
|
||||
# 'location', <tf.Tensor shape=(?, M, 2)>,
|
||||
# 'status', <tf.Tensor shape=(?, M, 10)>,
|
||||
# }
|
||||
print("The unpacked input tensors:", input_dict["obs"])
|
||||
print()
|
||||
print("Unbatched repeat dim", input_dict["obs"].unbatch_repeat_dim())
|
||||
print()
|
||||
if tf.executing_eagerly():
|
||||
print("Fully unbatched", input_dict["obs"].unbatch_all())
|
||||
print()
|
||||
return self.model.forward(input_dict, state, seq_lens)
|
||||
|
||||
def value_function(self):
|
||||
return self.model.value_function()
|
||||
@@ -0,0 +1,134 @@
|
||||
# @OldAPIStack
|
||||
|
||||
"""Example of creating a custom input API
|
||||
|
||||
Custom input apis are useful when your data source is in a custom format or
|
||||
when it is necessary to use an external data loading mechanism.
|
||||
In this example, we train an rl agent on user specified input data.
|
||||
Instead of using the built in JsonReader, we will create our own custom input
|
||||
api, and show how to pass config arguments to it.
|
||||
|
||||
To train CQL on the pendulum environment:
|
||||
$ python custom_input_api.py --input-files=../offline/tests/data/pendulum/enormous.zip
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import ray
|
||||
from ray import tune
|
||||
from ray.rllib.offline import InputReader, IOContext, JsonReader, ShuffledInput
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
EVALUATION_RESULTS,
|
||||
)
|
||||
from ray.tune.registry import get_trainable_cls, register_input
|
||||
from ray.tune.result import TRAINING_ITERATION
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--run", type=str, default="CQL", help="The RLlib-registered algorithm to use."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--framework",
|
||||
choices=["tf", "tf2", "torch"],
|
||||
default="torch",
|
||||
help="The DL framework specifier.",
|
||||
)
|
||||
parser.add_argument("--stop-iters", type=int, default=100)
|
||||
parser.add_argument(
|
||||
"--input-files",
|
||||
type=str,
|
||||
default=os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"../../offline/tests/data/pendulum/small.json",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class CustomJsonReader(JsonReader):
|
||||
"""
|
||||
Example custom InputReader implementation (extended from JsonReader).
|
||||
|
||||
This gets wrapped in ShuffledInput to comply with offline rl algorithms.
|
||||
"""
|
||||
|
||||
def __init__(self, ioctx: IOContext):
|
||||
"""
|
||||
The constructor must take an IOContext to be used in the input config.
|
||||
Args:
|
||||
ioctx: use this to access the `input_config` arguments.
|
||||
"""
|
||||
super().__init__(ioctx.input_config["input_files"], ioctx)
|
||||
|
||||
|
||||
def input_creator(ioctx: IOContext) -> InputReader:
|
||||
"""
|
||||
The input creator method can be used in the input registry or set as the
|
||||
config["input"] parameter.
|
||||
|
||||
Args:
|
||||
ioctx: use this to access the `input_config` arguments.
|
||||
|
||||
Returns:
|
||||
instance of ShuffledInput to work with some offline rl algorithms
|
||||
"""
|
||||
return ShuffledInput(CustomJsonReader(ioctx))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ray.init()
|
||||
args = parser.parse_args()
|
||||
|
||||
# make absolute path because relative path looks in result directory
|
||||
args.input_files = os.path.abspath(args.input_files)
|
||||
|
||||
# we register our custom input creator with this convenient function
|
||||
register_input("custom_input", input_creator)
|
||||
|
||||
# Config modified from rllib/examples/algorithms/cql/pendulum-cql.yaml
|
||||
default_config = get_trainable_cls(args.run).get_default_config()
|
||||
config = (
|
||||
default_config.environment("Pendulum-v1", clip_actions=True)
|
||||
.framework(args.framework)
|
||||
.offline_data(
|
||||
# We can either use the tune registry ...
|
||||
input_="custom_input",
|
||||
# ... full classpath
|
||||
# input_: "ray.rllib.examples.offline_rl.custom_input_api.CustomJsonReader"
|
||||
# ... or a direct function to connect our input api.
|
||||
# input_: input_creator
|
||||
input_config={"input_files": args.input_files}, # <- passed to IOContext
|
||||
actions_in_input_normalized=True,
|
||||
)
|
||||
.training(train_batch_size=2000)
|
||||
.evaluation(
|
||||
evaluation_interval=1,
|
||||
evaluation_num_env_runners=2,
|
||||
evaluation_duration=10,
|
||||
evaluation_parallel_to_training=True,
|
||||
evaluation_config=default_config.overrides(
|
||||
input_="sampler",
|
||||
explore=False,
|
||||
),
|
||||
)
|
||||
.reporting(metrics_num_episodes_for_smoothing=5)
|
||||
)
|
||||
|
||||
if args.run == "CQL":
|
||||
config.training(
|
||||
twin_q=True,
|
||||
num_steps_sampled_before_learning_starts=0,
|
||||
bc_iters=100,
|
||||
)
|
||||
|
||||
stop = {
|
||||
TRAINING_ITERATION: args.stop_iters,
|
||||
f"{EVALUATION_RESULTS}/{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": -600,
|
||||
}
|
||||
|
||||
tuner = tune.Tuner(
|
||||
args.run, param_space=config, run_config=tune.RunConfig(stop=stop, verbose=1)
|
||||
)
|
||||
tuner.fit()
|
||||
@@ -0,0 +1,168 @@
|
||||
# @OldAPIStack
|
||||
|
||||
"""Example on how to use CQL to learn from an offline JSON file.
|
||||
|
||||
Important node: Make sure that your offline data file contains only
|
||||
a single timestep per line to mimic the way SAC pulls samples from
|
||||
the buffer.
|
||||
|
||||
Generate the offline json file by running an SAC algo until it reaches expert
|
||||
level on your command line. For example:
|
||||
$ cd ray
|
||||
$ rllib train -f rllib/examples/algorithms/sac/pendulum-sac.yaml --no-ray-ui
|
||||
|
||||
Also make sure that in the above SAC yaml file (pendulum-sac.yaml),
|
||||
you specify an additional "output" key with any path on your local
|
||||
file system. In that path, the offline json files will be written to.
|
||||
|
||||
Use the generated file(s) as "input" in the CQL config below
|
||||
(`config["input"] = [list of your json files]`), then run this script.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ray.rllib.algorithms import cql as cql
|
||||
from ray.rllib.execution.rollout_ops import (
|
||||
synchronous_parallel_sample,
|
||||
)
|
||||
from ray.rllib.policy.sample_batch import convert_ma_batch_to_sample_batch
|
||||
from ray.rllib.utils.framework import try_import_torch
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
EVALUATION_RESULTS,
|
||||
)
|
||||
|
||||
torch, _ = try_import_torch()
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--as-test",
|
||||
action="store_true",
|
||||
help="Whether this script should be run as a test: --stop-reward must "
|
||||
"be achieved within --stop-timesteps AND --stop-iters.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-iters", type=int, default=5, help="Number of iterations to train."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-reward", type=float, default=50.0, help="Reward at which we stop training."
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
# See rllib/examples/algorithms/cql/pendulum-cql.yaml for comparison.
|
||||
config = (
|
||||
cql.CQLConfig()
|
||||
.api_stack(
|
||||
enable_env_runner_and_connector_v2=False,
|
||||
enable_rl_module_and_learner=False,
|
||||
)
|
||||
.framework(framework="torch")
|
||||
.env_runners(num_env_runners=0)
|
||||
.training(
|
||||
n_step=3,
|
||||
bc_iters=0,
|
||||
clip_actions=False,
|
||||
tau=0.005,
|
||||
target_entropy="auto",
|
||||
q_model_config={
|
||||
"fcnet_hiddens": [256, 256],
|
||||
"fcnet_activation": "relu",
|
||||
},
|
||||
policy_model_config={
|
||||
"fcnet_hiddens": [256, 256],
|
||||
"fcnet_activation": "relu",
|
||||
},
|
||||
optimization_config={
|
||||
"actor_learning_rate": 3e-4,
|
||||
"critic_learning_rate": 3e-4,
|
||||
"entropy_learning_rate": 3e-4,
|
||||
},
|
||||
train_batch_size=256,
|
||||
target_network_update_freq=1,
|
||||
num_steps_sampled_before_learning_starts=256,
|
||||
)
|
||||
.reporting(min_train_timesteps_per_iteration=1000)
|
||||
.debugging(log_level="INFO")
|
||||
.environment("Pendulum-v1", normalize_actions=True)
|
||||
.offline_data(
|
||||
input_config={
|
||||
"paths": ["offline/tests/data/pendulum/enormous.zip"],
|
||||
"format": "json",
|
||||
}
|
||||
)
|
||||
.evaluation(
|
||||
evaluation_num_env_runners=1,
|
||||
evaluation_interval=1,
|
||||
evaluation_duration=10,
|
||||
evaluation_parallel_to_training=False,
|
||||
evaluation_config=cql.CQLConfig.overrides(input_="sampler"),
|
||||
)
|
||||
)
|
||||
# evaluation_parallel_to_training should be False b/c iterations are very long
|
||||
# and this would cause evaluation to lag one iter behind training.
|
||||
|
||||
# Check, whether we can learn from the given file in `num_iterations`
|
||||
# iterations, up to a reward of `min_reward`.
|
||||
num_iterations = 5
|
||||
min_reward = -300
|
||||
|
||||
cql_algorithm = cql.CQL(config=config)
|
||||
learnt = False
|
||||
for i in range(num_iterations):
|
||||
print(f"Iter {i}")
|
||||
eval_results = cql_algorithm.train().get(EVALUATION_RESULTS)
|
||||
if eval_results:
|
||||
print(
|
||||
"... R={}".format(eval_results[ENV_RUNNER_RESULTS][EPISODE_RETURN_MEAN])
|
||||
)
|
||||
# Learn until some reward is reached on an actual live env.
|
||||
if eval_results[ENV_RUNNER_RESULTS][EPISODE_RETURN_MEAN] >= min_reward:
|
||||
# Test passed gracefully.
|
||||
if args.as_test:
|
||||
print("Test passed after {} iterations.".format(i))
|
||||
quit(0)
|
||||
learnt = True
|
||||
break
|
||||
|
||||
# Get policy and model.
|
||||
cql_policy = cql_algorithm.get_policy()
|
||||
cql_model = cql_policy.model
|
||||
|
||||
# If you would like to query CQL's learnt Q-function for arbitrary
|
||||
# (cont.) actions, do the following:
|
||||
obs_batch = torch.from_numpy(np.random.random(size=(5, 3)))
|
||||
action_batch = torch.from_numpy(np.random.random(size=(5, 1)))
|
||||
q_values = cql_model.get_q_values(obs_batch, action_batch)[0]
|
||||
# If you are using the "twin_q", there'll be 2 Q-networks and
|
||||
# we usually consider the min of the 2 outputs, like so:
|
||||
twin_q_values = cql_model.get_twin_q_values(obs_batch, action_batch)[0]
|
||||
final_q_values = torch.min(q_values, twin_q_values)[0]
|
||||
print(f"final_q_values={final_q_values.detach().numpy()}")
|
||||
|
||||
# Example on how to do evaluation on the trained Algorithm.
|
||||
# using the data from our buffer.
|
||||
# Get a sample (MultiAgentBatch).
|
||||
|
||||
batch = synchronous_parallel_sample(worker_set=cql_algorithm.env_runner_group)
|
||||
batch = convert_ma_batch_to_sample_batch(batch)
|
||||
obs = torch.from_numpy(batch["obs"])
|
||||
# Pass the observations through our model to get the
|
||||
# features, which then to pass through the Q-head.
|
||||
model_out, _ = cql_model({"obs": obs})
|
||||
# The estimated Q-values from the (historic) actions in the batch.
|
||||
q_values_old = cql_model.get_q_values(
|
||||
model_out, torch.from_numpy(batch["actions"])
|
||||
)[0]
|
||||
# The estimated Q-values for the new actions computed by our policy.
|
||||
actions_new = cql_policy.compute_actions_from_input_dict({"obs": obs})[0]
|
||||
q_values_new = cql_model.get_q_values(model_out, torch.from_numpy(actions_new))[0]
|
||||
print(f"Q-val batch={q_values_old.detach().numpy()}")
|
||||
print(f"Q-val policy={q_values_new.detach().numpy()}")
|
||||
|
||||
cql_algorithm.stop()
|
||||
@@ -0,0 +1,60 @@
|
||||
# @OldAPIStack
|
||||
|
||||
"""Simple example of writing experiences to a file using JsonWriter."""
|
||||
|
||||
# __sphinx_doc_begin__
|
||||
import os
|
||||
|
||||
import gymnasium as gym
|
||||
import numpy as np
|
||||
|
||||
from ray._common.utils import get_default_ray_temp_dir
|
||||
from ray.rllib.evaluation.sample_batch_builder import SampleBatchBuilder
|
||||
from ray.rllib.models.preprocessors import get_preprocessor
|
||||
from ray.rllib.offline.json_writer import JsonWriter
|
||||
|
||||
if __name__ == "__main__":
|
||||
batch_builder = SampleBatchBuilder() # or MultiAgentSampleBatchBuilder
|
||||
writer = JsonWriter(os.path.join(get_default_ray_temp_dir(), "demo-out"))
|
||||
|
||||
# You normally wouldn't want to manually create sample batches if a
|
||||
# simulator is available, but let's do it anyways for example purposes:
|
||||
env = gym.make("CartPole-v1")
|
||||
|
||||
# RLlib uses preprocessors to implement transforms such as one-hot encoding
|
||||
# and flattening of tuple and dict observations. For CartPole a no-op
|
||||
# preprocessor is used, but this may be relevant for more complex envs.
|
||||
prep = get_preprocessor(env.observation_space)(env.observation_space)
|
||||
print("The preprocessor is", prep)
|
||||
|
||||
for eps_id in range(100):
|
||||
obs, info = env.reset()
|
||||
prev_action = np.zeros_like(env.action_space.sample())
|
||||
prev_reward = 0
|
||||
terminated = truncated = False
|
||||
t = 0
|
||||
while not terminated and not truncated:
|
||||
action = env.action_space.sample()
|
||||
new_obs, rew, terminated, truncated, info = env.step(action)
|
||||
batch_builder.add_values(
|
||||
t=t,
|
||||
eps_id=eps_id,
|
||||
agent_index=0,
|
||||
obs=prep.transform(obs),
|
||||
actions=action,
|
||||
action_prob=1.0, # put the true action probability here
|
||||
action_logp=0.0,
|
||||
rewards=rew,
|
||||
prev_actions=prev_action,
|
||||
prev_rewards=prev_reward,
|
||||
terminateds=terminated,
|
||||
truncateds=truncated,
|
||||
infos=info,
|
||||
new_obs=prep.transform(new_obs),
|
||||
)
|
||||
obs = new_obs
|
||||
prev_action = action
|
||||
prev_reward = rew
|
||||
t += 1
|
||||
writer.write(batch_builder.build_and_reset())
|
||||
# __sphinx_doc_end__
|
||||
@@ -0,0 +1,121 @@
|
||||
# @OldAPIStack
|
||||
"""Example of handling variable length or parametric action spaces.
|
||||
|
||||
This toy example demonstrates the action-embedding based approach for handling large
|
||||
discrete action spaces (potentially infinite in size), similar to this example:
|
||||
|
||||
https://neuro.cs.ut.ee/the-use-of-embeddings-in-openai-five/
|
||||
|
||||
This example works with RLlib's policy gradient style algorithms
|
||||
(e.g., PG, PPO, IMPALA, A2C) and DQN.
|
||||
|
||||
Note that since the model outputs now include "-inf" tf.float32.min
|
||||
values, not all algorithm options are supported. For example,
|
||||
algorithms might crash if they don't properly ignore the -inf action scores.
|
||||
Working configurations are given below.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import ray
|
||||
from ray import tune
|
||||
from ray.rllib.examples._old_api_stack.models.parametric_actions_model import (
|
||||
ParametricActionsModel,
|
||||
TorchParametricActionsModel,
|
||||
)
|
||||
from ray.rllib.examples.envs.classes.parametric_actions_cartpole import (
|
||||
ParametricActionsCartPole,
|
||||
)
|
||||
from ray.rllib.models import ModelCatalog
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
from ray.rllib.utils.test_utils import check_learning_achieved
|
||||
from ray.tune.registry import register_env
|
||||
from ray.tune.result import TRAINING_ITERATION
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--run", type=str, default="PPO", help="The RLlib-registered algorithm to use."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--framework",
|
||||
choices=["tf", "tf2", "torch"],
|
||||
default="torch",
|
||||
help="The DL framework specifier.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--as-test",
|
||||
action="store_true",
|
||||
help="Whether this script should be run as a test: --stop-reward must "
|
||||
"be achieved within --stop-timesteps AND --stop-iters.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-iters", type=int, default=200, help="Number of iterations to train."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-timesteps", type=int, default=100000, help="Number of timesteps to train."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-reward", type=float, default=150.0, help="Reward at which we stop training."
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
ray.init()
|
||||
|
||||
register_env("pa_cartpole", lambda _: ParametricActionsCartPole(10))
|
||||
ModelCatalog.register_custom_model(
|
||||
"pa_model",
|
||||
TorchParametricActionsModel
|
||||
if args.framework == "torch"
|
||||
else ParametricActionsModel,
|
||||
)
|
||||
|
||||
if args.run == "DQN":
|
||||
cfg = {
|
||||
# TODO(ekl) we need to set these to prevent the masked values
|
||||
# from being further processed in DistributionalQModel, which
|
||||
# would mess up the masking. It is possible to support these if we
|
||||
# defined a custom DistributionalQModel that is aware of masking.
|
||||
"hiddens": [],
|
||||
"dueling": False,
|
||||
"enable_rl_module_and_learner": False,
|
||||
"enable_env_runner_and_connector_v2": False,
|
||||
}
|
||||
else:
|
||||
cfg = {}
|
||||
|
||||
config = dict(
|
||||
{
|
||||
"env": "pa_cartpole",
|
||||
"model": {
|
||||
"custom_model": "pa_model",
|
||||
},
|
||||
# Use GPUs iff `RLLIB_NUM_GPUS` env var set to > 0.
|
||||
"num_gpus": int(os.environ.get("RLLIB_NUM_GPUS", "0")),
|
||||
"num_env_runners": 0,
|
||||
"framework": args.framework,
|
||||
},
|
||||
**cfg,
|
||||
)
|
||||
|
||||
stop = {
|
||||
TRAINING_ITERATION: args.stop_iters,
|
||||
f"{NUM_ENV_STEPS_SAMPLED_LIFETIME}": args.stop_timesteps,
|
||||
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": args.stop_reward,
|
||||
}
|
||||
|
||||
results = tune.Tuner(
|
||||
args.run,
|
||||
run_config=tune.RunConfig(stop=stop, verbose=1),
|
||||
param_space=config,
|
||||
).fit()
|
||||
|
||||
if args.as_test:
|
||||
check_learning_achieved(results, args.stop_reward)
|
||||
|
||||
ray.shutdown()
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
# @OldAPIStack
|
||||
"""Example of handling variable length or parametric action spaces.
|
||||
|
||||
This is a toy example of the action-embedding based approach for handling large
|
||||
discrete action spaces (potentially infinite in size), similar to this:
|
||||
|
||||
https://neuro.cs.ut.ee/the-use-of-embeddings-in-openai-five/
|
||||
|
||||
This currently works with RLlib's policy gradient style algorithms
|
||||
(e.g., PG, PPO, IMPALA, A2C) and also DQN.
|
||||
|
||||
Note that since the model outputs now include "-inf" tf.float32.min
|
||||
values, not all algorithm options are supported at the moment. For example,
|
||||
algorithms might crash if they don't properly ignore the -inf action scores.
|
||||
Working configurations are given below.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import ray
|
||||
from ray import tune
|
||||
from ray.rllib.examples._old_api_stack.models.parametric_actions_model import (
|
||||
ParametricActionsModelThatLearnsEmbeddings,
|
||||
)
|
||||
from ray.rllib.examples.envs.classes.parametric_actions_cartpole import (
|
||||
ParametricActionsCartPoleNoEmbeddings,
|
||||
)
|
||||
from ray.rllib.models import ModelCatalog
|
||||
from ray.rllib.utils.metrics import (
|
||||
ENV_RUNNER_RESULTS,
|
||||
EPISODE_RETURN_MEAN,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME,
|
||||
)
|
||||
from ray.rllib.utils.test_utils import check_learning_achieved
|
||||
from ray.tune.registry import register_env
|
||||
from ray.tune.result import TRAINING_ITERATION
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--run", type=str, default="PPO")
|
||||
parser.add_argument(
|
||||
"--framework",
|
||||
choices=["tf", "tf2"],
|
||||
default="tf",
|
||||
help="The DL framework specifier (Torch not supported "
|
||||
"due to the lack of a model).",
|
||||
)
|
||||
parser.add_argument("--as-test", action="store_true")
|
||||
parser.add_argument("--stop-iters", type=int, default=200)
|
||||
parser.add_argument("--stop-reward", type=float, default=150.0)
|
||||
parser.add_argument("--stop-timesteps", type=int, default=100000)
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
ray.init()
|
||||
|
||||
register_env("pa_cartpole", lambda _: ParametricActionsCartPoleNoEmbeddings(10))
|
||||
|
||||
ModelCatalog.register_custom_model(
|
||||
"pa_model", ParametricActionsModelThatLearnsEmbeddings
|
||||
)
|
||||
|
||||
if args.run == "DQN":
|
||||
cfg = {
|
||||
# TODO(ekl) we need to set these to prevent the masked values
|
||||
# from being further processed in DistributionalQModel, which
|
||||
# would mess up the masking. It is possible to support these if we
|
||||
# defined a custom DistributionalQModel that is aware of masking.
|
||||
"hiddens": [],
|
||||
"dueling": False,
|
||||
"enable_rl_module_and_learner": False,
|
||||
"enable_env_runner_and_connector_v2": False,
|
||||
}
|
||||
else:
|
||||
cfg = {}
|
||||
|
||||
config = dict(
|
||||
{
|
||||
"env": "pa_cartpole",
|
||||
"model": {
|
||||
"custom_model": "pa_model",
|
||||
},
|
||||
# Use GPUs iff `RLLIB_NUM_GPUS` env var set to > 0.
|
||||
"num_gpus": int(os.environ.get("RLLIB_NUM_GPUS", "0")),
|
||||
"num_env_runners": 0,
|
||||
"framework": args.framework,
|
||||
"action_mask_key": "valid_avail_actions_mask",
|
||||
},
|
||||
**cfg,
|
||||
)
|
||||
|
||||
stop = {
|
||||
TRAINING_ITERATION: args.stop_iters,
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
|
||||
f"{ENV_RUNNER_RESULTS}/{EPISODE_RETURN_MEAN}": args.stop_reward,
|
||||
}
|
||||
|
||||
results = tune.Tuner(
|
||||
args.run,
|
||||
run_config=tune.RunConfig(stop=stop, verbose=2),
|
||||
param_space=config,
|
||||
).fit()
|
||||
|
||||
if args.as_test:
|
||||
check_learning_achieved(results, args.stop_reward)
|
||||
|
||||
ray.shutdown()
|
||||
@@ -0,0 +1,101 @@
|
||||
# @OldAPIStack
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
|
||||
import gymnasium as gym
|
||||
import numpy as np
|
||||
|
||||
from ray.rllib.models.torch.torch_action_dist import TorchCategorical
|
||||
from ray.rllib.policy.policy import Policy, ViewRequirement
|
||||
from ray.rllib.policy.sample_batch import SampleBatch
|
||||
from ray.rllib.utils.annotations import override
|
||||
from ray.rllib.utils.debug import update_global_seed_if_necessary
|
||||
from ray.rllib.utils.typing import AlgorithmConfigDict, TensorStructType, TensorType
|
||||
|
||||
|
||||
class CliffWalkingWallPolicy(Policy):
|
||||
"""Optimal RLlib policy for the CliffWalkingWallEnv environment, defined in
|
||||
ray/rllib/examples/env/cliff_walking_wall_env.py, with epsilon-greedy exploration.
|
||||
|
||||
The policy takes a random action with probability epsilon, specified
|
||||
by `config["epsilon"]`, and the optimal action with probability 1 - epsilon.
|
||||
"""
|
||||
|
||||
@override(Policy)
|
||||
def __init__(
|
||||
self,
|
||||
observation_space: gym.Space,
|
||||
action_space: gym.Space,
|
||||
config: AlgorithmConfigDict,
|
||||
):
|
||||
update_global_seed_if_necessary(seed=config.get("seed"))
|
||||
super().__init__(observation_space, action_space, config)
|
||||
|
||||
# Known optimal action dist for each of the 48 states and 4 actions
|
||||
self.action_dist = np.zeros((48, 4), dtype=float)
|
||||
# Starting state: go up
|
||||
self.action_dist[36] = (1, 0, 0, 0)
|
||||
# Cliff + Goal: never actually used, set to random
|
||||
self.action_dist[37:] = (0.25, 0.25, 0.25, 0.25)
|
||||
# Row 2; always go right
|
||||
self.action_dist[24:36] = (0, 1, 0, 0)
|
||||
# Row 0 and Row 1; go down or go right
|
||||
self.action_dist[0:24] = (0, 0.5, 0.5, 0)
|
||||
# Col 11; always go down, supercedes previous values
|
||||
self.action_dist[[11, 23, 35]] = (0, 0, 1, 0)
|
||||
assert np.allclose(self.action_dist.sum(-1), 1)
|
||||
|
||||
# Epsilon-Greedy action selection
|
||||
epsilon = config.get("epsilon", 0.0)
|
||||
self.action_dist = self.action_dist * (1 - epsilon) + epsilon / 4
|
||||
assert np.allclose(self.action_dist.sum(-1), 1)
|
||||
|
||||
# Attributes required for RLlib; note that while CliffWalkingWallPolicy
|
||||
# inherits from Policy, it actually implements TorchPolicyV2.
|
||||
self.view_requirements[SampleBatch.ACTION_PROB] = ViewRequirement()
|
||||
self.device = "cpu"
|
||||
self.model = None
|
||||
self.dist_class = TorchCategorical
|
||||
|
||||
@override(Policy)
|
||||
def compute_actions(
|
||||
self,
|
||||
obs_batch: Union[List[TensorStructType], TensorStructType],
|
||||
state_batches: Optional[List[TensorType]] = None,
|
||||
**kwargs,
|
||||
) -> Tuple[TensorType, List[TensorType], Dict[str, TensorType]]:
|
||||
obs = np.array(obs_batch, dtype=int)
|
||||
action_probs = self.action_dist[obs]
|
||||
actions = np.zeros(len(obs), dtype=int)
|
||||
for i in range(len(obs)):
|
||||
actions[i] = np.random.choice(4, p=action_probs[i])
|
||||
return (
|
||||
actions,
|
||||
[],
|
||||
{SampleBatch.ACTION_PROB: action_probs[np.arange(len(obs)), actions]},
|
||||
)
|
||||
|
||||
@override(Policy)
|
||||
def compute_log_likelihoods(
|
||||
self,
|
||||
actions: Union[List[TensorType], TensorType],
|
||||
obs_batch: Union[List[TensorType], TensorType],
|
||||
**kwargs,
|
||||
) -> TensorType:
|
||||
obs = np.array(obs_batch, dtype=int)
|
||||
actions = np.array(actions, dtype=int)
|
||||
# Compute action probs for all possible actions
|
||||
action_probs = self.action_dist[obs]
|
||||
# Take the action_probs corresponding to the specified actions
|
||||
action_probs = action_probs[np.arange(len(obs)), actions]
|
||||
# Ignore RuntimeWarning thrown by np.log(0) if action_probs is 0
|
||||
with np.errstate(divide="ignore"):
|
||||
return np.log(action_probs)
|
||||
|
||||
def action_distribution_fn(
|
||||
self, model, obs_batch: TensorStructType, **kwargs
|
||||
) -> Tuple[TensorType, type, List[TensorType]]:
|
||||
obs = np.array(obs_batch[SampleBatch.OBS], dtype=int)
|
||||
action_probs = self.action_dist[obs]
|
||||
# Ignore RuntimeWarning thrown by np.log(0) if action_probs is 0
|
||||
with np.errstate(divide="ignore"):
|
||||
return np.log(action_probs), TorchCategorical, None
|
||||
@@ -0,0 +1,102 @@
|
||||
# @OldAPIStack
|
||||
import random
|
||||
from typing import (
|
||||
List,
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
|
||||
import numpy as np
|
||||
import tree # pip install dm_tree
|
||||
from gymnasium.spaces import Box
|
||||
|
||||
from ray.rllib.policy.policy import Policy
|
||||
from ray.rllib.policy.sample_batch import SampleBatch
|
||||
from ray.rllib.utils.annotations import override
|
||||
from ray.rllib.utils.typing import ModelWeights, TensorStructType, TensorType
|
||||
|
||||
|
||||
class RandomPolicy(Policy):
|
||||
"""Hand-coded policy that returns random actions."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Whether for compute_actions, the bounds given in action_space
|
||||
# should be ignored (default: False). This is to test action-clipping
|
||||
# and any Env's reaction to bounds breaches.
|
||||
if self.config.get("ignore_action_bounds", False) and isinstance(
|
||||
self.action_space, Box
|
||||
):
|
||||
self.action_space_for_sampling = Box(
|
||||
-float("inf"),
|
||||
float("inf"),
|
||||
shape=self.action_space.shape,
|
||||
dtype=self.action_space.dtype,
|
||||
)
|
||||
else:
|
||||
self.action_space_for_sampling = self.action_space
|
||||
|
||||
@override(Policy)
|
||||
def init_view_requirements(self):
|
||||
super().init_view_requirements()
|
||||
# Disable for_training and action attributes for SampleBatch.INFOS column
|
||||
# since it can not be properly batched.
|
||||
vr = self.view_requirements[SampleBatch.INFOS]
|
||||
vr.used_for_training = False
|
||||
vr.used_for_compute_actions = False
|
||||
|
||||
@override(Policy)
|
||||
def compute_actions(
|
||||
self,
|
||||
obs_batch: Union[List[TensorStructType], TensorStructType],
|
||||
state_batches: Optional[List[TensorType]] = None,
|
||||
prev_action_batch: Union[List[TensorStructType], TensorStructType] = None,
|
||||
prev_reward_batch: Union[List[TensorStructType], TensorStructType] = None,
|
||||
**kwargs,
|
||||
):
|
||||
# Alternatively, a numpy array would work here as well.
|
||||
# e.g.: np.array([random.choice([0, 1])] * len(obs_batch))
|
||||
obs_batch_size = len(tree.flatten(obs_batch)[0])
|
||||
return (
|
||||
[self.action_space_for_sampling.sample() for _ in range(obs_batch_size)],
|
||||
[],
|
||||
{},
|
||||
)
|
||||
|
||||
@override(Policy)
|
||||
def learn_on_batch(self, samples):
|
||||
"""No learning."""
|
||||
return {}
|
||||
|
||||
@override(Policy)
|
||||
def compute_log_likelihoods(
|
||||
self,
|
||||
actions,
|
||||
obs_batch,
|
||||
state_batches=None,
|
||||
prev_action_batch=None,
|
||||
prev_reward_batch=None,
|
||||
**kwargs,
|
||||
):
|
||||
return np.array([random.random()] * len(obs_batch))
|
||||
|
||||
@override(Policy)
|
||||
def get_weights(self) -> ModelWeights:
|
||||
"""No weights to save."""
|
||||
return {}
|
||||
|
||||
@override(Policy)
|
||||
def set_weights(self, weights: ModelWeights) -> None:
|
||||
"""No weights to set."""
|
||||
pass
|
||||
|
||||
@override(Policy)
|
||||
def _get_dummy_batch_from_view_requirements(self, batch_size: int = 1):
|
||||
return SampleBatch(
|
||||
{
|
||||
SampleBatch.OBS: tree.map_structure(
|
||||
lambda s: s[None], self.observation_space.sample()
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
# @OldAPIStack
|
||||
|
||||
# __sphinx_doc_replay_buffer_api_example_script_begin__
|
||||
"""Simple example of how to modify replay buffer behaviour.
|
||||
|
||||
We modify DQN to utilize prioritized replay but supplying it with the
|
||||
PrioritizedMultiAgentReplayBuffer instead of the standard MultiAgentReplayBuffer.
|
||||
This is possible because DQN uses the DQN training iteration function,
|
||||
which includes and a priority update, given that a fitting buffer is provided.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
||||
import ray
|
||||
from ray import tune
|
||||
from ray.rllib.algorithms.dqn import DQNConfig
|
||||
from ray.rllib.utils.framework import try_import_tf
|
||||
from ray.rllib.utils.metrics import NUM_ENV_STEPS_SAMPLED_LIFETIME
|
||||
from ray.rllib.utils.replay_buffers.replay_buffer import StorageUnit
|
||||
from ray.tune.result import TRAINING_ITERATION
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("--num-cpus", type=int, default=0)
|
||||
parser.add_argument(
|
||||
"--framework",
|
||||
choices=["tf", "tf2", "torch"],
|
||||
default="torch",
|
||||
help="The DL framework specifier.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-iters", type=int, default=50, help="Number of iterations to train."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stop-timesteps", type=int, default=100000, help="Number of timesteps to train."
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
ray.init(num_cpus=args.num_cpus or None)
|
||||
|
||||
# This is where we add prioritized experiences replay
|
||||
# The training iteration function that is used by DQN already includes a priority
|
||||
# update step.
|
||||
replay_buffer_config = {
|
||||
"type": "MultiAgentPrioritizedReplayBuffer",
|
||||
# Although not necessary, we can modify the default constructor args of
|
||||
# the replay buffer here
|
||||
"prioritized_replay_alpha": 0.5,
|
||||
"storage_unit": StorageUnit.SEQUENCES,
|
||||
"replay_burn_in": 20,
|
||||
"zero_init_states": True,
|
||||
}
|
||||
|
||||
config = (
|
||||
DQNConfig()
|
||||
.environment("CartPole-v1")
|
||||
.framework(framework=args.framework)
|
||||
.env_runners(num_env_runners=4)
|
||||
.training(
|
||||
model=dict(use_lstm=True, lstm_cell_size=64, max_seq_len=20),
|
||||
replay_buffer_config=replay_buffer_config,
|
||||
)
|
||||
)
|
||||
|
||||
stop_config = {
|
||||
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
|
||||
TRAINING_ITERATION: args.stop_iters,
|
||||
}
|
||||
|
||||
results = tune.Tuner(
|
||||
config.algo_class,
|
||||
param_space=config,
|
||||
run_config=tune.RunConfig(stop=stop_config),
|
||||
).fit()
|
||||
|
||||
ray.shutdown()
|
||||
|
||||
# __sphinx_doc_replay_buffer_api_example_script_end__
|
||||
Reference in New Issue
Block a user