chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
@@ -0,0 +1,99 @@
"""Example of running a custom heuristic (hand-coded) policy alongside trainable ones.
This example has two RLModules (as action computing policies):
(1) one trained by a PPOLearner
(2) one hand-coded policy that acts at random in the env (doesn't learn).
The environment is MultiAgentCartPole, in which there are n agents both policies
How to run this script
----------------------
`python [script file name].py --num-agents=2`
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
Results to expect
-----------------
In the console output, you can see the PPO policy ("learnable_policy") does much
better than "random":
+-------------------+------------+----------+------+----------------+
| Trial name | status | loc | iter | total time (s) |
| | | | | |
|-------------------+------------+----------+------+----------------+
| PPO_multi_agen... | TERMINATED | 127. ... | 20 | 58.646 |
+-------------------+------------+----------+------+----------------+
+--------+-------------------+-----------------+--------------------+
| ts | combined reward | reward random | reward |
| | | | learnable_policy |
+--------+-------------------+-----------------+--------------------|
| 80000 | 481.26 | 78.41 | 464.41 |
+--------+-------------------+-----------------+--------------------+
"""
from ray.rllib.algorithms.ppo import PPOConfig
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentCartPole
from ray.rllib.examples.rl_modules.classes.random_rlm import RandomRLModule
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.tune.registry import register_env
parser = add_rllib_example_script_args(
default_iters=40, default_reward=500.0, default_timesteps=200000
)
parser.set_defaults(
num_agents=2,
)
if __name__ == "__main__":
args = parser.parse_args()
assert args.num_agents == 2, "Must set --num-agents=2 when running this script!"
# Simple environment with n independent cartpole entities.
register_env(
"multi_agent_cartpole",
lambda _: MultiAgentCartPole({"num_agents": args.num_agents}),
)
base_config = (
PPOConfig()
.environment("multi_agent_cartpole")
.multi_agent(
policies={"learnable_policy", "random"},
# Map to either random behavior or PPO learning behavior based on
# the agent's ID.
policy_mapping_fn=lambda agent_id, *args, **kwargs: [
"learnable_policy",
"random",
][agent_id % 2],
# We need to specify this here, b/c the `forward_train` method of
# `RandomRLModule` (ModuleID="random") throws a not-implemented error.
policies_to_train=["learnable_policy"],
)
.rl_module(
rl_module_spec=MultiRLModuleSpec(
rl_module_specs={
"learnable_policy": RLModuleSpec(),
"random": RLModuleSpec(module_class=RandomRLModule),
}
),
)
)
run_rllib_example_script_experiment(base_config, args)
@@ -0,0 +1,108 @@
"""
Example showing how to create a multi-agent env, in which the different agents
have different observation and action spaces.
These spaces do NOT necessarily have to be specified manually by the user. Instead,
RLlib tries to automatically infer them from the env provided spaces dicts
(agentID -> obs/act space) and the policy mapping fn (mapping agent IDs to policy IDs).
How to run this script
----------------------
`python [script file name].py --num-agents=2`
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
"""
import gymnasium as gym
from ray.rllib.env.multi_agent_env import MultiAgentEnv
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.tune.registry import get_trainable_cls
class BasicMultiAgentMultiSpaces(MultiAgentEnv):
"""A simple multi-agent example environment where agents have different spaces.
agent0: obs=Box(10,), act=Discrete(2)
agent1: obs=Box(20,), act=Discrete(3)
The logic of the env doesn't really matter for this example. The point of this env
is to show how to use multi-agent envs, in which the different agents utilize
different obs- and action spaces.
"""
def __init__(self, config=None):
self.agents = ["agent0", "agent1"]
self.terminateds = set()
self.truncateds = set()
# Provide full (preferred format) observation- and action-spaces as Dicts
# mapping agent IDs to the individual agents' spaces.
self.observation_spaces = {
"agent0": gym.spaces.Box(low=-1.0, high=1.0, shape=(10,)),
"agent1": gym.spaces.Box(low=-1.0, high=1.0, shape=(20,)),
}
self.action_spaces = {
"agent0": gym.spaces.Discrete(2),
"agent1": gym.spaces.Discrete(3),
}
super().__init__()
def reset(self, *, seed=None, options=None):
self.terminateds = set()
self.truncateds = set()
return {i: self.get_observation_space(i).sample() for i in self.agents}, {}
def step(self, action_dict):
obs, rew, terminated, truncated, info = {}, {}, {}, {}, {}
for i, action in action_dict.items():
obs[i] = self.get_observation_space(i).sample()
rew[i] = 0.0
terminated[i] = False
truncated[i] = False
info[i] = {}
terminated["__all__"] = len(self.terminateds) == len(self.agents)
truncated["__all__"] = len(self.truncateds) == len(self.agents)
return obs, rew, terminated, truncated, info
parser = add_rllib_example_script_args(
default_iters=10, default_reward=80.0, default_timesteps=10000
)
if __name__ == "__main__":
args = parser.parse_args()
base_config = (
get_trainable_cls(args.algo)
.get_default_config()
.environment(env=BasicMultiAgentMultiSpaces)
.training(train_batch_size=1024)
.multi_agent(
# Use a simple set of policy IDs. Spaces for the individual policies
# are inferred automatically using reverse lookup via the
# `policy_mapping_fn` and the env provided spaces for the different
# agents. Alternatively, you could use:
# policies: {main0: PolicySpec(...), main1: PolicySpec}
policies={"main0", "main1"},
# Simple mapping fn, mapping agent0 to main0 and agent1 to main1.
policy_mapping_fn=(lambda aid, episode, **kw: f"main{aid[-1]}"),
# Only train main0.
policies_to_train=["main0"],
)
)
run_rllib_example_script_experiment(base_config, args)
@@ -0,0 +1,66 @@
"""Simple example of setting up an agent-to-module mapping function.
How to run this script
----------------------
`python [script file name].py --num-agents=2`
Control the number of agents and policies (RLModules) via --num-agents and
--num-policies.
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
"""
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentCartPole
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.tune.registry import get_trainable_cls, register_env
parser = add_rllib_example_script_args(
default_iters=200,
default_timesteps=100000,
default_reward=600.0,
)
# TODO (sven): This arg is currently ignored (hard-set to 2).
parser.add_argument(
"--num-policies",
type=int,
default=2,
)
if __name__ == "__main__":
args = parser.parse_args()
# Register our environment with tune.
if args.num_agents > 0:
register_env(
"env",
lambda _: MultiAgentCartPole(config={"num_agents": args.num_agents}),
)
base_config = (
get_trainable_cls(args.algo)
.get_default_config()
.environment("env" if args.num_agents > 0 else "CartPole-v1")
.env_runners(
num_envs_per_env_runner=20,
)
)
# Add a simple multi-agent setup.
if args.num_agents > 0:
base_config.multi_agent(
policies={f"p{i}" for i in range(args.num_agents)},
policy_mapping_fn=lambda aid, *a, **kw: f"p{aid}",
)
run_rllib_example_script_experiment(base_config, args)
@@ -0,0 +1,77 @@
"""Simple example of setting up an agent-to-module mapping function.
How to run this script
----------------------
`python [script file name].py --num-agents=2`
Control the number of agents and policies (RLModules) via --num-agents and
--num-policies.
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
"""
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentPendulum
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.tune.registry import get_trainable_cls, register_env
parser = add_rllib_example_script_args(
default_iters=200,
default_timesteps=100000,
default_reward=-400.0,
)
# TODO (sven): This arg is currently ignored (hard-set to 2).
parser.add_argument(
"--num-policies",
type=int,
default=2,
)
if __name__ == "__main__":
args = parser.parse_args()
# Register our environment with tune.
if args.num_agents > 0:
register_env(
"env",
lambda _: MultiAgentPendulum(config={"num_agents": args.num_agents}),
)
base_config = (
get_trainable_cls(args.algo)
.get_default_config()
.environment("env" if args.num_agents > 0 else "Pendulum-v1")
.training(
train_batch_size_per_learner=512,
minibatch_size=64,
lambda_=0.1,
gamma=0.95,
lr=0.0003,
model={"fcnet_activation": "relu"},
vf_clip_param=10.0,
)
.rl_module(
model_config=DefaultModelConfig(fcnet_activation="relu"),
)
)
# Add a simple multi-agent setup.
if args.num_agents > 0:
base_config.multi_agent(
policies={f"p{i}" for i in range(args.num_agents)},
policy_mapping_fn=lambda aid, *a, **kw: f"p{aid}",
)
# Augment
run_rllib_example_script_experiment(base_config, args)
@@ -0,0 +1,104 @@
"""Runs the PettingZoo Waterworld env in RLlib using independent multi-agent learning.
See: https://pettingzoo.farama.org/environments/sisl/waterworld/
for more details on the environment.
How to run this script
----------------------
`python [script file name].py --num-agents=2`
Control the number of agents and policies (RLModules) via --num-agents and
--num-policies.
This works with hundreds of agents and policies, but note that initializing
many policies might take some time.
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
Results to expect
-----------------
The above options can reach a combined reward of 0.0 or more after about 500k env
timesteps. Keep in mind, though, that due to the separate value functions (and
learned policies in general), one agent's gain (in per-agent reward) might cause the
other agent's reward to decrease at the same time. However, over time, both agents
should simply improve.
+---------------------+------------+-----------------+--------+------------------+
| Trial name | status | loc | iter | total time (s) |
|---------------------+------------+-----------------+--------+------------------+
| PPO_env_a82fc_00000 | TERMINATED | 127.0.0.1:28346 | 124 | 363.599 |
+---------------------+------------+-----------------+--------+------------------+
+--------+-------------------+--------------------+--------------------+
| ts | combined reward | reward pursuer_1 | reward pursuer_0 |
+--------+-------------------+--------------------+--------------------|
| 496000 | 2.24542 | -34.6869 | 36.9324 |
+--------+-------------------+--------------------+--------------------+
Note that the two agents (`pursuer_0` and `pursuer_1`) are optimized on the exact same
objective and thus differences in the rewards can be attributed to weight initialization
(and sampling randomness) only.
"""
from pettingzoo.sisl import waterworld_v4
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.env.wrappers.pettingzoo_env import PettingZooEnv
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.tune.registry import get_trainable_cls, register_env
parser = add_rllib_example_script_args(
default_iters=200,
default_timesteps=1000000,
default_reward=0.0,
)
if __name__ == "__main__":
args = parser.parse_args()
assert args.num_agents > 0, "Must set --num-agents > 0 when running this script!"
# Here, we use the "Agent Environment Cycle" (AEC) PettingZoo environment type.
# For a "Parallel" environment example, see the rock paper scissors examples
# in this same repository folder.
register_env("env", lambda _: PettingZooEnv(waterworld_v4.env()))
# Policies are called just like the agents (exact 1:1 mapping).
policies = {f"pursuer_{i}" for i in range(args.num_agents)}
base_config = (
get_trainable_cls(args.algo)
.get_default_config()
.environment("env")
.multi_agent(
policies=policies,
# Exact 1:1 mapping from AgentID to ModuleID.
policy_mapping_fn=(lambda aid, *args, **kwargs: aid),
)
.training(
vf_loss_coeff=0.005,
)
.rl_module(
rl_module_spec=MultiRLModuleSpec(
rl_module_specs={p: RLModuleSpec() for p in policies},
),
model_config=DefaultModelConfig(vf_share_layers=True),
)
)
run_rllib_example_script_experiment(base_config, args)
@@ -0,0 +1,101 @@
"""Runs the PettingZoo Waterworld multi-agent env in RLlib using single policy learning.
Other than the `pettingzoo_independent_learning.py` example (in this same folder),
this example simply trains a single policy (shared by all agents).
See: https://pettingzoo.farama.org/environments/sisl/waterworld/
for more details on the environment.
How to run this script
----------------------
`python [script file name].py --num-agents=2`
Control the number of agents and policies (RLModules) via --num-agents and
--num-policies.
This works with hundreds of agents and policies, but note that initializing
many policies might take some time.
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
Results to expect
-----------------
The above options can reach a combined reward of roughly ~0.0 after about 500k-1M env
timesteps. Keep in mind, though, that in this setup, the agents do not have the
opportunity to benefit from or even out other agents' mistakes (and behavior in general)
as everyone is using the same policy. Hence, this example learns a more generic policy,
which might be less specialized to certain "niche exploitation opportunities" inside
the env:
+---------------------+----------+-----------------+--------+-----------------+
| Trial name | status | loc | iter | total time (s) |
|---------------------+----------+-----------------+--------+-----------------+
| PPO_env_91f49_00000 | RUNNING | 127.0.0.1:63676 | 200 | 605.176 |
+---------------------+----------+-----------------+--------+-----------------+
+--------+-------------------+-------------+
| ts | combined reward | reward p0 |
+--------+-------------------+-------------|
| 800000 | 0.323752 | 0.161876 |
+--------+-------------------+-------------+
"""
from pettingzoo.sisl import waterworld_v4
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.env.wrappers.pettingzoo_env import PettingZooEnv
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.tune.registry import get_trainable_cls, register_env
parser = add_rllib_example_script_args(
default_iters=200,
default_timesteps=1000000,
default_reward=0.0,
)
if __name__ == "__main__":
args = parser.parse_args()
assert args.num_agents > 0, "Must set --num-agents > 0 when running this script!"
# Here, we use the "Agent Environment Cycle" (AEC) PettingZoo environment type.
# For a "Parallel" environment example, see the rock paper scissors examples
# in this same repository folder.
register_env("env", lambda _: PettingZooEnv(waterworld_v4.env()))
base_config = (
get_trainable_cls(args.algo)
.get_default_config()
.environment("env")
.multi_agent(
policies={"p0"},
# All agents map to the exact same policy.
policy_mapping_fn=(lambda aid, *args, **kwargs: "p0"),
)
.training(
model={
"vf_share_layers": True,
},
vf_loss_coeff=0.005,
)
.rl_module(
rl_module_spec=MultiRLModuleSpec(
rl_module_specs={"p0": RLModuleSpec()},
),
)
)
run_rllib_example_script_experiment(base_config, args)
@@ -0,0 +1,152 @@
"""A simple multi-agent env with two agents play rock paper scissors.
This demonstrates running the following policies in competition:
Agent 1: heuristic policy of repeating the same move
OR: heuristic policy of beating the last opponent move
Agent 2: Simple, feedforward PPO policy
OR: PPO Policy with an LSTM network
How to run this script
----------------------
`python [script file name].py --num-agents=2 [--use-lstm]?`
Without `--use-lstm`, Agent 2 should quickly reach a reward of ~7.0, always
beating the `always_same` policy, but only 50% of the time beating the `beat_last`
policy.
With `--use-lstm`, Agent 2 should eventually(!) reach a reward of >9.0 (always
beating both the `always_same` policy and the `beat_last` policy).
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
"""
import random
import gymnasium as gym
from pettingzoo.classic import rps_v2
from ray.rllib.connectors.env_to_module import FlattenObservations
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.env.wrappers.pettingzoo_env import ParallelPettingZooEnv
from ray.rllib.examples.rl_modules.classes import (
AlwaysSameHeuristicRLM,
BeatLastHeuristicRLM,
)
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.utils.metrics import (
ENV_RUNNER_RESULTS,
NUM_ENV_STEPS_SAMPLED_LIFETIME,
)
from ray.tune.registry import get_trainable_cls, register_env
from ray.tune.result import TRAINING_ITERATION
parser = add_rllib_example_script_args(
default_iters=50,
default_timesteps=200000,
default_reward=6.0,
)
parser.set_defaults(
num_agents=2,
)
parser.add_argument(
"--use-lstm",
action="store_true",
help="Whether to use an LSTM wrapped module instead of a simple MLP one. With LSTM "
"the reward diff can reach 7.0, without only 5.0.",
)
register_env(
"pettingzoo_rps",
lambda _: ParallelPettingZooEnv(rps_v2.parallel_env()),
)
if __name__ == "__main__":
args = parser.parse_args()
assert args.num_agents == 2, "Must set --num-agents=2 when running this script!"
base_config = (
get_trainable_cls(args.algo)
.get_default_config()
.environment("pettingzoo_rps")
.env_runners(
env_to_module_connector=lambda env, spaces, device: (
# `agent_ids=...`: Only flatten obs for the learning RLModule.
FlattenObservations(multi_agent=True, agent_ids={"player_0"}),
),
)
.multi_agent(
policies={"always_same", "beat_last", "learned"},
# Let learning Policy always play against either heuristic one:
# `always_same` or `beat_last`.
policy_mapping_fn=lambda aid, episode: (
"learned"
if aid == "player_0"
else random.choice(["always_same", "beat_last"])
),
# Must define this as both heuristic RLMs will throw an error, if their
# `forward_train` is called.
policies_to_train=["learned"],
)
.training(
vf_loss_coeff=0.005,
)
.rl_module(
rl_module_spec=MultiRLModuleSpec(
rl_module_specs={
"always_same": RLModuleSpec(
module_class=AlwaysSameHeuristicRLM,
observation_space=gym.spaces.Discrete(4),
action_space=gym.spaces.Discrete(3),
),
"beat_last": RLModuleSpec(
module_class=BeatLastHeuristicRLM,
observation_space=gym.spaces.Discrete(4),
action_space=gym.spaces.Discrete(3),
),
"learned": RLModuleSpec(
model_config=DefaultModelConfig(
use_lstm=args.use_lstm,
# Use a simpler FCNet when we also have an LSTM.
fcnet_hiddens=[32] if args.use_lstm else [256, 256],
lstm_cell_size=256,
max_seq_len=15,
vf_share_layers=True,
),
),
}
),
)
)
# Make `args.stop_reward` "point" to the reward of the learned policy.
stop = {
TRAINING_ITERATION: args.stop_iters,
f"{ENV_RUNNER_RESULTS}/module_episode_returns_mean/learned": args.stop_reward,
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
}
run_rllib_example_script_experiment(
base_config,
args,
stop=stop,
success_metric={
f"{ENV_RUNNER_RESULTS}/module_episode_returns_mean/learned": (
args.stop_reward
),
},
)
@@ -0,0 +1,91 @@
"""A simple multi-agent env with two agents play rock paper scissors.
This demonstrates running two learning policies in competition, both using the same
RLlib algorithm (PPO by default).
The combined reward as well as individual rewards should roughly remain at 0.0 as no
policy should - in the long run - be able to learn a better strategy than chosing
actions at random. However, it could be possible that - for some time - one or the other
policy can exploit a "stochastic weakness" of the opponent policy. For example a policy
`A` learns that its opponent `B` has learnt to choose "paper" more often, which in
return makes `A` choose "scissors" more often as a countermeasure.
"""
import re
from pettingzoo.classic import rps_v2
from ray.rllib.connectors.env_to_module import FlattenObservations
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.env.wrappers.pettingzoo_env import ParallelPettingZooEnv
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.tune.registry import get_trainable_cls, register_env
parser = add_rllib_example_script_args(
default_iters=50,
default_timesteps=200000,
default_reward=6.0,
)
parser.set_defaults(
num_agents=2,
)
parser.add_argument(
"--use-lstm",
action="store_true",
help="Whether to use an LSTM wrapped module instead of a simple MLP one. With LSTM "
"the reward diff can reach 7.0, without only 5.0.",
)
register_env(
"pettingzoo_rps",
lambda _: ParallelPettingZooEnv(rps_v2.parallel_env()),
)
if __name__ == "__main__":
args = parser.parse_args()
assert args.num_agents == 2, "Must set --num-agents=2 when running this script!"
base_config = (
get_trainable_cls(args.algo)
.get_default_config()
.environment("pettingzoo_rps")
.env_runners(
env_to_module_connector=(
lambda env, spaces, device: FlattenObservations(multi_agent=True)
),
)
.multi_agent(
policies={"p0", "p1"},
# `player_0` uses `p0`, `player_1` uses `p1`.
policy_mapping_fn=lambda aid, episode: re.sub("^player_", "p", aid),
)
.training(
vf_loss_coeff=0.005,
)
.rl_module(
model_config=DefaultModelConfig(
use_lstm=args.use_lstm,
# Use a simpler FCNet when we also have an LSTM.
fcnet_hiddens=[32] if args.use_lstm else [256, 256],
lstm_cell_size=256,
max_seq_len=15,
vf_share_layers=True,
),
rl_module_spec=MultiRLModuleSpec(
rl_module_specs={
"p0": RLModuleSpec(),
"p1": RLModuleSpec(),
}
),
)
)
run_rllib_example_script_experiment(base_config, args)
@@ -0,0 +1,129 @@
"""
Multi-agent RLlib Footsies Simplified Example (PPO)
About:
- This example as a simplified version of "rllib/examples/ppo/multi_agent_footsies_ppo.py",
which has more detailed comments and instructions. Please refer to that example for more information.
- This example is created to test the self-play training progression with footsies.
- Simplified version runs with single learner (cpu), single env runner, and single eval env runner.
"""
import platform
from pathlib import Path
from ray.rllib.examples.algorithms.ppo.multi_agent_footsies_ppo import (
config,
env_creator,
stop,
)
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.tune.registry import register_env
parser = add_rllib_example_script_args(
default_iters=500,
default_timesteps=5_000_000,
)
parser.add_argument(
"--train-start-port",
type=int,
default=45001,
help="First port number for the Footsies training environment server (default: 45001). Each server gets its own port.",
)
parser.add_argument(
"--eval-start-port",
type=int,
default=55001,
help="First port number for the Footsies evaluation environment server (default: 55001) Each server gets its own port.",
)
parser.add_argument(
"--binary-download-dir",
type=Path,
default="/tmp/ray/binaries/footsies",
help="Directory to download Footsies binaries (default: /tmp/ray/binaries/footsies)",
)
parser.add_argument(
"--binary-extract-dir",
type=Path,
default="/tmp/ray/binaries/footsies",
help="Directory to extract Footsies binaries (default: /tmp/ray/binaries/footsies)",
)
parser.add_argument(
"--render",
action="store_true",
default=False,
help="Whether to render the Footsies environment. Default is False.",
)
parser.add_argument(
"--win-rate-threshold",
type=float,
default=0.55,
help="The main policy should have at least 'win-rate-threshold' win rate against the "
"other policy to advance to the next level. Moving to the next level "
"means adding a new policy to the mix.",
)
parser.add_argument(
"--target-mix-size",
type=int,
default=4,
help="Target number of policies (RLModules) in the mix to consider the test passed. "
"The initial mix size is 2: 'main policy' vs. 'other'. "
"`--target-mix-size=4` means that 2 new policies will be added to the mix. "
"Whether to add new policy is decided by checking the '--win-rate-threshold' condition. ",
)
parser.add_argument(
"--rollout-fragment-length",
type=int,
default=256,
help="The length of each rollout fragment to be collected by the EnvRunners when sampling.",
)
parser.add_argument(
"--log-unity-output",
action="store_true",
help="Whether to log Unity output (from the game engine). Default is False.",
default=False,
)
args = parser.parse_args()
register_env(name="FootsiesEnv", env_creator=env_creator)
stop["mix_size"] = args.target_mix_size
# Detect platform and choose appropriate binary
if platform.system() == "Darwin":
if args.render:
binary_to_download = "mac_windowed"
else:
binary_to_download = "mac_headless"
elif platform.system() == "Linux":
if args.render:
binary_to_download = "linux_windowed"
else:
binary_to_download = "linux_server"
else:
raise RuntimeError(f"Unsupported platform: {platform.system()}")
config.environment(
env="FootsiesEnv",
env_config={
"train_start_port": args.train_start_port,
"eval_start_port": args.eval_start_port,
"binary_download_dir": args.binary_download_dir,
"binary_extract_dir": args.binary_extract_dir,
"binary_to_download": binary_to_download,
"log_unity_output": args.log_unity_output,
},
).training(
train_batch_size_per_learner=args.rollout_fragment_length
* (args.num_env_runners or 1),
)
if __name__ == "__main__":
results = run_rllib_example_script_experiment(
base_config=config,
args=args,
stop=stop,
success_metric={"mix_size": args.target_mix_size},
)
@@ -0,0 +1,284 @@
"""Example showing how to implement a league-based training workflow.
Uses the open spiel adapter of RLlib with the "markov_soccer" game and
a simplified multi-agent, league-based setup:
https://deepmind.com/blog/article/AlphaStar-Grandmaster-level-in- \
StarCraft-II-using-multi-agent-reinforcement-learning
Our league consists of three groups of policies:
- main policies: The current main policy plus prior versions of it.
- main exploiters: Trained by playing only against different "main policies".
- league exploiters: Trained by playing against any policy in the league.
We start with 1 policy from each group, setting all 3 of these to an initial
PPO policy and allowing all 3 policies to be trained.
After each train update - via our custom callback - we decide for each
trainable policy, whether to make a copy and freeze it. Frozen policies
will not be altered anymore. However, they remain in the league for
future matches against trainable policies.
Matchmaking happens via a policy_mapping_fn, which needs to be altered
after every change (addition) to the league. The mapping function
randomly maps agents in a way, such that:
- Frozen main exploiters play against the one (currently trainable) main
policy.
- Trainable main exploiters play against any main policy (including already
frozen main policies).
- Frozen league exploiters play against any trainable policy in the league.
- Trainable league exploiters play against any policy in the league.
After training for n iterations, a configurable number of episodes can
be played by the user against the "main" agent on the command line.
"""
import functools
import numpy as np
import torch
import ray
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.env.utils import try_import_open_spiel, try_import_pyspiel
from ray.rllib.env.wrappers.open_spiel import OpenSpielEnv
from ray.rllib.examples._old_api_stack.policy.random_policy import RandomPolicy
from ray.rllib.examples.multi_agent.utils import (
SelfPlayLeagueBasedCallback,
SelfPlayLeagueBasedCallbackOldAPIStack,
ask_user_for_action,
)
from ray.rllib.examples.rl_modules.classes.random_rlm import RandomRLModule
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.policy.policy import PolicySpec
from ray.rllib.utils.metrics import NUM_ENV_STEPS_SAMPLED_LIFETIME
from ray.tune.registry import get_trainable_cls, register_env
from ray.tune.result import TRAINING_ITERATION
open_spiel = try_import_open_spiel(error=True)
pyspiel = try_import_pyspiel(error=True)
# Import after try_import_open_spiel, so we can error out with hints
from open_spiel.python.rl_environment import Environment # noqa: E402
parser = add_rllib_example_script_args(default_timesteps=2000000)
parser.set_defaults(
env="markov_soccer",
num_env_runners=2,
checkpoint_freq=1,
checkpoint_at_end=True,
)
parser.add_argument(
"--win-rate-threshold",
type=float,
default=0.85,
help="Win-rate at which we setup another opponent by freezing the "
"current main policy and playing against a uniform distribution "
"of previously frozen 'main's from here on.",
)
parser.add_argument(
"--min-league-size",
type=float,
default=8,
help="Minimum number of policies/RLModules to consider the test passed. "
"The initial league size is 2: `main` and `random`. "
"`--min-league-size=3` thus means that one new policy/RLModule has been "
"added so far (b/c the `main` one has reached the `--win-rate-threshold "
"against the `random` Policy/RLModule).",
)
parser.add_argument(
"--num-episodes-human-play",
type=int,
default=0,
help="How many episodes to play against the user on the command "
"line after training has finished.",
)
parser.add_argument(
"--from-checkpoint",
type=str,
default=None,
help="Full path to a checkpoint file for restoring a previously saved "
"Algorithm state.",
)
if __name__ == "__main__":
args = parser.parse_args()
register_env(
"open_spiel_env",
lambda _: OpenSpielEnv(pyspiel.load_game(args.env)),
)
def policy_mapping_fn(agent_id, episode, worker=None, **kwargs):
# At first, only have main play against the random main exploiter.
return "main" if episode.episode_id % 2 == agent_id else "main_exploiter_0"
def agent_to_module_mapping_fn(agent_id, episode, **kwargs):
# At first, only have main play against the random main exploiter.
return "main" if hash(episode.id_) % 2 == agent_id else "main_exploiter_0"
def _get_multi_agent():
names = {
# Our main policy, we'd like to optimize.
"main",
# First frozen version of main (after we reach n% win-rate).
"main_0",
# Initial main exploiters (one random, one trainable).
"main_exploiter_0",
"main_exploiter_1",
# Initial league exploiters (one random, one trainable).
"league_exploiter_0",
"league_exploiter_1",
}
if not args.old_api_stack:
policies = names
spec = {
mid: RLModuleSpec(
module_class=(
RandomRLModule
if mid in ["main_exploiter_0", "league_exploiter_0"]
else None
),
model_config=DefaultModelConfig(
fcnet_hiddens=[1024, 1024],
# fcnet_activation="tanh",
),
)
for mid in names
}
else:
policies = {
mid: PolicySpec(
policy_class=(
RandomPolicy
if mid in ["main_exploiter_0", "league_exploiter_0"]
else None
)
)
for mid in names
}
spec = None
return {"policies": policies, "spec": spec}
config = (
get_trainable_cls(args.algo)
.get_default_config()
.environment("open_spiel_env")
# Set up the main piece in this experiment: The league-bases self-play
# callback, which controls adding new policies/Modules to the league and
# properly matching the different policies in the league with each other.
.callbacks(
functools.partial(
SelfPlayLeagueBasedCallback
if not args.old_api_stack
else SelfPlayLeagueBasedCallbackOldAPIStack,
win_rate_threshold=args.win_rate_threshold,
)
)
.env_runners(
num_envs_per_env_runner=1 if not args.old_api_stack else 5,
)
.training(
num_epochs=20,
)
.multi_agent(
# Initial policy map: All PPO. This will be expanded
# to more policy snapshots. This is done in the
# custom callback defined above (`LeagueBasedSelfPlayCallback`).
policies=_get_multi_agent()["policies"],
policy_mapping_fn=(
agent_to_module_mapping_fn
if not args.old_api_stack
else policy_mapping_fn
),
# At first, only train main_0 (until good enough to win against
# random).
policies_to_train=["main"],
)
.rl_module(
rl_module_spec=MultiRLModuleSpec(
rl_module_specs=_get_multi_agent()["spec"]
),
)
)
# Run everything as configured.
# Train the "main" policy to play really well using self-play.
results = None
if not args.from_checkpoint:
stop = {
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
TRAINING_ITERATION: args.stop_iters,
"league_size": args.min_league_size,
}
results = run_rllib_example_script_experiment(
config, args, stop=stop, keep_ray_up=True
)
# Restore trained Algorithm (set to non-explore behavior) and play against
# human on command line.
if args.num_episodes_human_play > 0:
num_episodes = 0
# Switch off exploration for better inference performance.
config.explore = False
algo = config.build()
if args.from_checkpoint:
algo.restore(args.from_checkpoint)
else:
checkpoint = results.get_best_result().checkpoint
if not checkpoint:
raise ValueError("No last checkpoint found in results!")
algo.restore(checkpoint)
if not args.old_api_stack:
rl_module = algo.get_module("main")
# Play from the command line against the trained agent
# in an actual (non-RLlib-wrapped) open-spiel env.
human_player = 1
env = Environment(args.env)
while num_episodes < args.num_episodes_human_play:
print("You play as {}".format("o" if human_player else "x"))
time_step = env.reset()
while not time_step.last():
player_id = time_step.observations["current_player"]
if player_id == human_player:
action = ask_user_for_action(time_step)
else:
obs = np.array(time_step.observations["info_state"][player_id])
if not args.old_api_stack:
action = np.argmax(
rl_module.forward_inference(
{"obs": torch.from_numpy(obs).unsqueeze(0).float()}
)["action_dist_inputs"][0].numpy()
)
else:
action = algo.compute_single_action(obs, policy_id="main")
# In case computer chooses an invalid action, pick a
# random one.
legal = time_step.observations["legal_actions"][player_id]
if action not in legal:
action = np.random.choice(legal)
time_step = env.step([action])
print(f"\n{env.get_state}")
print(f"\n{env.get_state}")
print("End of game!")
if time_step.rewards[human_player] > 0:
print("You win")
elif time_step.rewards[human_player] < 0:
print("You lose")
else:
print("Draw")
# Switch order of players
human_player = 1 - human_player
num_episodes += 1
algo.stop()
ray.shutdown()
@@ -0,0 +1,247 @@
"""Example showing how one can implement a simple self-play training workflow.
Uses the open spiel adapter of RLlib with the "connect_four" game and
a multi-agent setup with a "main" policy and n "main_v[x]" policies
(x=version number), which are all at-some-point-frozen copies of
"main". At the very beginning, "main" plays against RandomPolicy.
Checks for the training progress after each training update via a custom
callback. We simply measure the win rate of "main" vs the opponent
("main_v[x]" or RandomPolicy at the beginning) by looking through the
achieved rewards in the episodes in the train batch. If this win rate
reaches some configurable threshold, we add a new policy to
the policy map (a frozen copy of the current "main" one) and change the
policy_mapping_fn to make new matches of "main" vs any of the previous
versions of "main" (including the just added one).
After training for n iterations, a configurable number of episodes can
be played by the user against the "main" agent on the command line.
"""
import functools
import numpy as np
import torch
from ray.rllib.core.rl_module.default_model_config import DefaultModelConfig
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.env.utils import try_import_open_spiel, try_import_pyspiel
from ray.rllib.env.wrappers.open_spiel import OpenSpielEnv
from ray.rllib.examples._old_api_stack.policy.random_policy import RandomPolicy
from ray.rllib.examples.multi_agent.utils import (
SelfPlayCallback,
SelfPlayCallbackOldAPIStack,
ask_user_for_action,
)
from ray.rllib.examples.rl_modules.classes.random_rlm import RandomRLModule
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.rllib.policy.policy import PolicySpec
from ray.rllib.utils.metrics import NUM_ENV_STEPS_SAMPLED_LIFETIME
from ray.tune.registry import get_trainable_cls, register_env
from ray.tune.result import TRAINING_ITERATION
open_spiel = try_import_open_spiel(error=True)
pyspiel = try_import_pyspiel(error=True)
# Import after try_import_open_spiel, so we can error out with hints.
from open_spiel.python.rl_environment import Environment # noqa: E402
parser = add_rllib_example_script_args(default_timesteps=2000000)
parser.set_defaults(
env="connect_four",
checkpoint_freq=1,
checkpoint_at_end=True,
)
parser.add_argument(
"--win-rate-threshold",
type=float,
default=0.95,
help="Win-rate at which we setup another opponent by freezing the "
"current main policy and playing against a uniform distribution "
"of previously frozen 'main's from here on.",
)
parser.add_argument(
"--min-league-size",
type=float,
default=3,
help="Minimum number of policies/RLModules to consider the test passed. "
"The initial league size is 2: `main` and `random`. "
"`--min-league-size=3` thus means that one new policy/RLModule has been "
"added so far (b/c the `main` one has reached the `--win-rate-threshold "
"against the `random` Policy/RLModule).",
)
parser.add_argument(
"--num-episodes-human-play",
type=int,
default=10,
help="How many episodes to play against the user on the command "
"line after training has finished.",
)
parser.add_argument(
"--from-checkpoint",
type=str,
default=None,
help="Full path to a checkpoint file for restoring a previously saved "
"Algorithm state.",
)
if __name__ == "__main__":
args = parser.parse_args()
register_env("open_spiel_env", lambda _: OpenSpielEnv(pyspiel.load_game(args.env)))
def agent_to_module_mapping_fn(agent_id, episode, **kwargs):
# agent_id = [0|1] -> module depends on episode ID
# This way, we make sure that both modules sometimes play agent0
# (start player) and sometimes agent1 (player to move 2nd).
return "main" if hash(episode.id_) % 2 == agent_id else "random"
def policy_mapping_fn(agent_id, episode, worker, **kwargs):
# e.g. episode ID = 10234
# -> agent `0` -> main (b/c epsID % 2 == 0)
# -> agent `1` -> random (b/c epsID % 2 == 1)
return "main" if episode.episode_id % 2 == agent_id else "random"
config = (
get_trainable_cls(args.algo)
.get_default_config()
.environment("open_spiel_env")
# Set up the main piece in this experiment: The league-bases self-play
# callback, which controls adding new policies/Modules to the league and
# properly matching the different policies in the league with each other.
.callbacks(
functools.partial(
(
SelfPlayCallback
if not args.old_api_stack
else SelfPlayCallbackOldAPIStack
),
win_rate_threshold=args.win_rate_threshold,
)
)
.env_runners(
num_env_runners=(args.num_env_runners or 2),
num_envs_per_env_runner=1 if not args.old_api_stack else 5,
)
.multi_agent(
# Initial policy map: Random and default algo one. 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`).
policies=(
{
# Our main policy, we'd like to optimize.
"main": PolicySpec(),
# An initial random opponent to play against.
"random": PolicySpec(policy_class=RandomPolicy),
}
if args.old_api_stack
else {"main", "random"}
),
# 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=(
agent_to_module_mapping_fn
if not args.old_api_stack
else policy_mapping_fn
),
# Always just train the "main" policy.
policies_to_train=["main"],
)
.rl_module(
model_config=DefaultModelConfig(fcnet_hiddens=[512, 512]),
rl_module_spec=MultiRLModuleSpec(
rl_module_specs={
"main": RLModuleSpec(),
"random": RLModuleSpec(module_class=RandomRLModule),
}
),
)
)
# Only for PPO, change the `num_epochs` setting.
if args.algo == "PPO":
config.training(num_epochs=20)
stop = {
NUM_ENV_STEPS_SAMPLED_LIFETIME: args.stop_timesteps,
TRAINING_ITERATION: args.stop_iters,
"league_size": args.min_league_size,
}
# Train the "main" policy to play really well using self-play.
results = None
if not args.from_checkpoint:
results = run_rllib_example_script_experiment(
config, args, stop=stop, keep_ray_up=True
)
# Restore trained Algorithm (set to non-explore behavior) and play against
# human on command line.
if args.num_episodes_human_play > 0:
num_episodes = 0
config.explore = False
algo = config.build()
if args.from_checkpoint:
algo.restore(args.from_checkpoint)
else:
checkpoint = results.get_best_result().checkpoint
if not checkpoint:
raise ValueError("No last checkpoint found in results!")
algo.restore(checkpoint)
if not args.old_api_stack:
rl_module = algo.get_module("main")
# Play from the command line against the trained agent
# in an actual (non-RLlib-wrapped) open-spiel env.
human_player = 1
env = Environment(args.env)
while num_episodes < args.num_episodes_human_play:
print("You play as {}".format("o" if human_player else "x"))
time_step = env.reset()
while not time_step.last():
player_id = time_step.observations["current_player"]
if player_id == human_player:
action = ask_user_for_action(time_step)
else:
obs = np.array(time_step.observations["info_state"][player_id])
if not args.old_api_stack:
action = np.argmax(
rl_module.forward_inference(
{"obs": torch.from_numpy(obs).unsqueeze(0).float()}
)["action_dist_inputs"][0].numpy()
)
else:
action = algo.compute_single_action(obs, policy_id="main")
# In case computer chooses an invalid action, pick a
# random one.
legal = time_step.observations["legal_actions"][player_id]
if action not in legal:
action = np.random.choice(legal)
time_step = env.step([action])
print(f"\n{env.get_state}")
print(f"\n{env.get_state}")
print("End of game!")
if time_step.rewards[human_player] > 0:
print("You win")
elif time_step.rewards[human_player] < 0:
print("You lose")
else:
print("Draw")
# Switch order of players.
human_player = 1 - human_player
num_episodes += 1
algo.stop()
@@ -0,0 +1,164 @@
"""A runnable example involving the use of a shared encoder module.
How to run this script
----------------------
`python [script file name].py --num-agents=2`
Control the number of agents and policies (RLModules) via --num-agents.
--encoder-emb-dim sets the encoder output dimension, and --no-shared-encoder
runs the experiment with independent encoders.
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
Results to expect
-----------------
Under the shared encoder architecture, the target reward of 700 will typically be reached well before 100,000 iterations. A trial concludes as below:
+---------------------+------------+-----------------+--------+------------------+-------+-------------------+-------------+-------------+
| Trial name | status | loc | iter | total time (s) | ts | combined return | return p1 | return p0 |
|---------------------+------------+-----------------+--------+------------------+-------+-------------------+-------------+-------------|
| VPG_env_ab318_00000 | TERMINATED | 127.0.0.1:37375 | 33 | 44.2689 | 74197 | 611.35 | 191.71 | 419.64 |
+---------------------+------------+-----------------+--------+------------------+-------+-------------------+-------------+-------------+
Without a shared encoder, a lower reward is typically achieved after training for the full 100,000 timesteps:
+---------------------+------------+-----------------+--------+------------------+--------+-------------------+-------------+-------------+
| Trial name | status | loc | iter | total time (s) | ts | combined return | return p0 | return p1 |
|---------------------+------------+-----------------+--------+------------------+--------+-------------------+-------------+-------------|
| VPG_env_2e79e_00000 | TERMINATED | 127.0.0.1:39076 | 37 | 52.127 | 103894 | 526.66 | 85.78 | 440.88 |
+---------------------+------------+-----------------+--------+------------------+--------+-------------------+-------------+-------------+
"""
import gymnasium as gym
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.examples.algorithms.classes.vpg import VPGConfig
from ray.rllib.examples.envs.classes.multi_agent import MultiAgentCartPole
from ray.rllib.examples.learners.classes.vpg_torch_learner_shared_optimizer import (
VPGTorchLearnerSharedOptimizer,
)
from ray.rllib.examples.rl_modules.classes.vpg_using_shared_encoder_rlm import (
SHARED_ENCODER_ID,
SharedEncoder,
VPGMultiRLModuleWithSharedEncoder,
VPGPolicyAfterSharedEncoder,
VPGPolicyNoSharedEncoder,
)
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.tune.registry import register_env
parser = add_rllib_example_script_args(
default_iters=200,
default_timesteps=100000,
default_reward=600.0,
)
parser.set_defaults(
algo="VPG",
num_agents=2,
)
parser.add_argument("--encoder-emb-dim", type=int, default=64)
parser.add_argument("--no-shared-encoder", action="store_true")
if __name__ == "__main__":
args = parser.parse_args()
assert args.algo == "VPG", "The shared encoder example is meant for VPG agents."
assert args.num_agents == 2, "This example makes use of two agents."
single_agent_env = gym.make(
"CartPole-v1"
) # To allow instantiation of shared encoder
EMBEDDING_DIM = args.encoder_emb_dim # encoder output dim
if args.no_shared_encoder:
print("Running experiment without shared encoder")
specs = MultiRLModuleSpec(
rl_module_specs={
# Large policy net.
"p0": RLModuleSpec(
module_class=VPGPolicyNoSharedEncoder,
model_config={
"embedding_dim": EMBEDDING_DIM,
"hidden_dim": 64,
},
),
# Small policy net.
"p1": RLModuleSpec(
module_class=VPGPolicyNoSharedEncoder,
model_config={
"embedding_dim": EMBEDDING_DIM,
"hidden_dim": 64,
},
),
}
)
else:
specs = MultiRLModuleSpec(
multi_rl_module_class=VPGMultiRLModuleWithSharedEncoder,
rl_module_specs={
# Shared encoder.
SHARED_ENCODER_ID: RLModuleSpec(
module_class=SharedEncoder,
model_config={"embedding_dim": EMBEDDING_DIM},
observation_space=single_agent_env.observation_space,
action_space=single_agent_env.action_space,
),
# Large policy net.
"p0": RLModuleSpec(
module_class=VPGPolicyAfterSharedEncoder,
model_config={
"embedding_dim": EMBEDDING_DIM,
"hidden_dim": 64,
},
),
# Small policy net.
"p1": RLModuleSpec(
module_class=VPGPolicyAfterSharedEncoder,
model_config={
"embedding_dim": EMBEDDING_DIM,
"hidden_dim": 64,
},
),
},
)
# Register our environment with tune.
register_env(
"env",
lambda _: MultiAgentCartPole(config={"num_agents": args.num_agents}),
)
base_config = (
VPGConfig()
.environment("env" if args.num_agents > 0 else "CartPole-v1")
.training(
learner_class=VPGTorchLearnerSharedOptimizer
if not args.no_shared_encoder
else None,
train_batch_size=2048,
lr=1e-2,
)
.multi_agent(
policies={"p0", "p1"},
policy_mapping_fn=lambda agent_id, episode, **kw: f"p{agent_id}",
)
.rl_module(
rl_module_spec=specs,
)
)
run_rllib_example_script_experiment(base_config, args)
@@ -0,0 +1,90 @@
"""The two-step game from the QMIX paper:
https://arxiv.org/pdf/1803.11485.pdf
See also: rllib/examples/centralized_critic.py for centralized critic PPO on this game.
How to run this script
----------------------
`python [script file name].py --num-agents=2`
Note that in this script, we use an multi-agent environment in which both
agents that normally play this game have been merged into one agent with ID
"agents" and observation- and action-spaces being 2-tupled (1 item for each
agent). The "agents" agent is mapped to the policy with ID "p0".
For debugging, use the following additional command line options
`--no-tune --num-env-runners=0`
Which should allow you to set breakpoints anywhere in the RLlib code and
have the execution stop there for inspection and debugging.
For logging to your WandB account, use:
`--wandb-key=[your WandB API key] --wandb-project=[some project name]
--wandb-run-name=[optional: WandB run name (within the defined project)]`
Results to expect
-----------------
You should expect a reward of 8.0 (the max to reach in thie game) eventually
being achieved by a simple PPO policy (no tuning, just using RLlib's default settings):
+---------------------------------+------------+-----------------+--------+
| Trial name | status | loc | iter |
|---------------------------------+------------+-----------------+--------+
| PPO_grouped_twostep_4354b_00000 | TERMINATED | 127.0.0.1:42602 | 20 |
+---------------------------------+------------+-----------------+--------+
+------------------+-------+-------------------+-------------+
| total time (s) | ts | combined reward | reward p0 |
+------------------+-------+-------------------+-------------|
| 87.5756 | 80000 | 8 | 8 |
+------------------+-------+-------------------+-------------+
"""
from ray.rllib.connectors.env_to_module import FlattenObservations
from ray.rllib.core.rl_module.multi_rl_module import MultiRLModuleSpec
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.examples.envs.classes.multi_agent.two_step_game import (
TwoStepGameWithGroupedAgents,
)
from ray.rllib.examples.utils import (
add_rllib_example_script_args,
run_rllib_example_script_experiment,
)
from ray.tune.registry import get_trainable_cls, register_env
parser = add_rllib_example_script_args(default_reward=7.0)
if __name__ == "__main__":
args = parser.parse_args()
assert args.num_agents == 2, "Must set --num-agents=2 when running this script!"
register_env(
"grouped_twostep",
lambda config: TwoStepGameWithGroupedAgents(config),
)
base_config = (
get_trainable_cls(args.algo)
.get_default_config()
.environment("grouped_twostep")
.env_runners(
env_to_module_connector=lambda env, spaces, device: FlattenObservations(
multi_agent=True
),
)
.multi_agent(
policies={"p0"},
policy_mapping_fn=lambda aid, *a, **kw: "p0",
)
.rl_module(
rl_module_spec=MultiRLModuleSpec(
rl_module_specs={
"p0": RLModuleSpec(),
},
)
)
)
run_rllib_example_script_experiment(base_config, args)
@@ -0,0 +1,43 @@
import sys
from ray.rllib.examples.multi_agent.utils.self_play_callback import SelfPlayCallback
from ray.rllib.examples.multi_agent.utils.self_play_callback_old_api_stack import (
SelfPlayCallbackOldAPIStack,
)
from ray.rllib.examples.multi_agent.utils.self_play_league_based_callback import (
SelfPlayLeagueBasedCallback,
)
from ray.rllib.examples.multi_agent.utils.self_play_league_based_callback_old_api_stack import ( # noqa
SelfPlayLeagueBasedCallbackOldAPIStack,
)
def ask_user_for_action(time_step):
"""Asks the user for a valid action on the command line and returns it.
Re-queries the user until she picks a valid one.
Args:
time_step: The open spiel Environment time-step object.
"""
pid = time_step.observations["current_player"]
legal_moves = time_step.observations["legal_actions"][pid]
choice = -1
while choice not in legal_moves:
print("Choose an action from {}:".format(legal_moves))
sys.stdout.flush()
choice_str = input()
try:
choice = int(choice_str)
except ValueError:
continue
return choice
__all__ = [
"ask_user_for_action",
"SelfPlayCallback",
"SelfPlayLeagueBasedCallback",
"SelfPlayCallbackOldAPIStack",
"SelfPlayLeagueBasedCallbackOldAPIStack",
]
@@ -0,0 +1,97 @@
from collections import defaultdict
import numpy as np
from ray.rllib.callbacks.callbacks import RLlibCallback
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.utils.metrics import ENV_RUNNER_RESULTS
class SelfPlayCallback(RLlibCallback):
def __init__(self, win_rate_threshold):
super().__init__()
# 0=RandomPolicy, 1=1st main policy snapshot,
# 2=2nd main policy snapshot, etc..
self.current_opponent = 0
self.win_rate_threshold = win_rate_threshold
# Report the matchup counters (who played against whom?).
self._matching_stats = defaultdict(int)
def on_episode_end(
self,
*,
episode,
env_runner,
metrics_logger,
env,
env_index,
rl_module,
**kwargs,
) -> None:
# Compute the win rate for this episode and log it with a window of 100.
main_agent = 0 if episode.module_for(0) == "main" else 1
rewards = episode.get_rewards()
if main_agent in rewards:
main_won = rewards[main_agent][-1] == 1.0
metrics_logger.log_value(
"win_rate",
main_won,
reduce="mean",
window=100,
)
def on_train_result(self, *, algorithm, metrics_logger=None, result, **kwargs):
win_rate = result[ENV_RUNNER_RESULTS]["win_rate"]
print(f"Iter={algorithm.iteration} win-rate={win_rate} -> ", end="")
# If win rate is good -> Snapshot current policy and play against
# it next, keeping the snapshot fixed and only improving the "main"
# policy.
if win_rate > self.win_rate_threshold:
self.current_opponent += 1
new_module_id = f"main_v{self.current_opponent}"
print(f"adding new opponent to the mix ({new_module_id}).")
# Re-define the mapping function, such that "main" is forced
# to play against any of the previously played modules
# (excluding "random").
def agent_to_module_mapping_fn(agent_id, episode, **kwargs):
# agent_id = [0|1] -> policy depends on episode ID
# This way, we make sure that both modules sometimes play
# (start player) and sometimes agent1 (player to move 2nd).
opponent = "main_v{}".format(
np.random.choice(list(range(1, self.current_opponent + 1)))
)
if hash(episode.id_) % 2 == agent_id:
self._matching_stats[("main", opponent)] += 1
return "main"
else:
return opponent
main_module = algorithm.get_module("main")
algorithm.add_module(
module_id=new_module_id,
module_spec=RLModuleSpec.from_module(main_module),
new_agent_to_module_mapping_fn=agent_to_module_mapping_fn,
)
# TODO (sven): Maybe we should move this convenience step back into
# `Algorithm.add_module()`? Would be less explicit, but also easier.
algorithm.set_state(
{
"learner_group": {
"learner": {
"rl_module": {
new_module_id: main_module.get_state(),
}
}
}
}
)
else:
print("not good enough; will keep learning ...")
# +2 = main + random
result["league_size"] = self.current_opponent + 2
print(f"Matchups:\n{self._matching_stats}")
@@ -0,0 +1,78 @@
import numpy as np
from ray._common.deprecation import Deprecated
from ray.rllib.callbacks.callbacks import RLlibCallback
from ray.rllib.utils.metrics import ENV_RUNNER_RESULTS
@Deprecated(help="Use the example for the new RLlib API stack.", error=False)
class SelfPlayCallbackOldAPIStack(RLlibCallback):
def __init__(self, win_rate_threshold):
super().__init__()
# 0=RandomPolicy, 1=1st main policy snapshot,
# 2=2nd main policy snapshot, etc..
self.current_opponent = 0
self.win_rate_threshold = win_rate_threshold
def on_train_result(self, *, algorithm, result, **kwargs):
# Get the win rate for the train batch.
# Note that normally, you should set up a proper evaluation config,
# such that evaluation always happens on the already updated policy,
# instead of on the already used train_batch.
main_rew = result[ENV_RUNNER_RESULTS]["hist_stats"].pop("policy_main_reward")
opponent_rew = list(result[ENV_RUNNER_RESULTS]["hist_stats"].values())[0]
assert len(main_rew) == len(opponent_rew)
won = 0
for r_main, r_opponent in zip(main_rew, opponent_rew):
if r_main > r_opponent:
won += 1
win_rate = won / len(main_rew)
result["win_rate"] = win_rate
print(f"Iter={algorithm.iteration} win-rate={win_rate} -> ", end="")
# If win rate is good -> Snapshot current policy and play against
# it next, keeping the snapshot fixed and only improving the "main"
# policy.
if win_rate > self.win_rate_threshold:
self.current_opponent += 1
new_pol_id = f"main_v{self.current_opponent}"
print(f"adding new opponent to the mix ({new_pol_id}).")
# Re-define the mapping function, such that "main" is forced
# to play against any of the previously played policies
# (excluding "random").
def policy_mapping_fn(agent_id, episode, worker, **kwargs):
# agent_id = [0|1] -> policy depends on episode ID
# This way, we make sure that both policies sometimes play
# (start player) and sometimes agent1 (player to move 2nd).
return (
"main"
if episode.episode_id % 2 == agent_id
else "main_v{}".format(
np.random.choice(list(range(1, self.current_opponent + 1)))
)
)
main_policy = algorithm.get_policy("main")
new_policy = algorithm.add_policy(
policy_id=new_pol_id,
policy_cls=type(main_policy),
policy_mapping_fn=policy_mapping_fn,
config=main_policy.config,
observation_space=main_policy.observation_space,
action_space=main_policy.action_space,
)
# Set the weights of the new policy to the main policy.
# We'll keep training the main policy, whereas `new_pol_id` will
# remain fixed.
main_state = main_policy.get_state()
new_policy.set_state(main_state)
# We need to sync the just copied local weights (from main policy)
# to all the remote workers as well.
algorithm.env_runner_group.sync_weights()
else:
print("not good enough; will keep learning ...")
# +2 = main + random
result["league_size"] = self.current_opponent + 2
@@ -0,0 +1,299 @@
import re
from collections import defaultdict
from pprint import pprint
import numpy as np
from ray.rllib.callbacks.callbacks import RLlibCallback
from ray.rllib.core.rl_module.rl_module import RLModuleSpec
from ray.rllib.utils.metrics import ENV_RUNNER_RESULTS
class SelfPlayLeagueBasedCallback(RLlibCallback):
def __init__(self, win_rate_threshold):
super().__init__()
# All policies in the league.
self.main_policies = {"main", "main_0"}
self.main_exploiters = {"main_exploiter_0", "main_exploiter_1"}
self.league_exploiters = {"league_exploiter_0", "league_exploiter_1"}
# Set of currently trainable policies in the league.
self.trainable_policies = {"main"}
# Set of currently non-trainable (frozen) policies in the league.
self.non_trainable_policies = {
"main_0",
"league_exploiter_0",
"main_exploiter_0",
}
# The win-rate value reaching of which leads to a new module being added
# to the leage (frozen copy of main).
self.win_rate_threshold = win_rate_threshold
# Store the win rates for league overview printouts.
self.win_rates = {}
# Report the matchup counters (who played against whom?).
self._matching_stats = defaultdict(int)
def on_episode_end(
self,
*,
episode,
env_runner,
metrics_logger,
env,
env_index,
rl_module,
**kwargs,
) -> None:
num_learning_policies = (
episode.module_for(0) in env_runner.config.policies_to_train
) + (episode.module_for(1) in env_runner.config.policies_to_train)
# Make sure the mapping function doesn't match two non-trainables together.
# This would be a waste of EnvRunner resources.
# assert num_learning_policies > 0
# Ignore matches between two learning policies and don't count win-rates for
# these.
assert num_learning_policies > 0, (
f"agent=0 -> mod={episode.module_for(0)}; "
f"agent=1 -> mod={episode.module_for(1)}; "
f"EnvRunner.config.policies_to_train={env_runner.config.policies_to_train}"
)
if num_learning_policies == 1:
# Compute the win rate for this episode (only looking at non-trained
# opponents, such as random or frozen policies) and log it with some window.
rewards_dict = episode.get_rewards()
for aid, rewards in rewards_dict.items():
mid = episode.module_for(aid)
won = rewards[-1] == 1.0
metrics_logger.log_value(
f"win_rate_{mid}",
won,
window=100,
)
def on_train_result(self, *, algorithm, metrics_logger=None, result, **kwargs):
local_worker = algorithm.env_runner
# Avoid `self` being pickled into the remote function below.
_trainable_policies = self.trainable_policies
# Get the win rate for the train batch.
# Note that normally, one should set up a proper evaluation config,
# such that evaluation always happens on the already updated policy,
# instead of on the already used train_batch.
league_changed = False
keys = [
k for k in result[ENV_RUNNER_RESULTS].keys() if k.startswith("win_rate_")
]
for key in keys:
module_id = key[9:]
self.win_rates[module_id] = result[ENV_RUNNER_RESULTS][key]
# Policy is frozen; ignore.
if module_id in self.non_trainable_policies:
continue
print(
f"Iter={algorithm.iteration} {module_id}'s "
f"win-rate={self.win_rates[module_id]} -> ",
end="",
)
# If win rate is good -> Snapshot current policy and decide,
# whether to freeze the copy or not.
if self.win_rates[module_id] > self.win_rate_threshold:
is_main = re.match("^main(_\\d+)?$", module_id)
initializing_exploiters = False
# First time, main manages a decent win-rate against random:
# Add league_exploiter_1 and main_exploiter_1 as trainables to the mix.
if is_main and len(self.trainable_policies) == 1:
initializing_exploiters = True
self.trainable_policies.add("league_exploiter_1")
self.trainable_policies.add("main_exploiter_1")
# If main manages to win (above threshold) against the entire league
# -> increase the league by another frozen copy of main,
# main-exploiters or league-exploiters.
else:
keep_training = (
False
if is_main
else np.random.choice([True, False], p=[0.3, 0.7])
)
if module_id in self.main_policies:
new_mod_id = re.sub(
"(main)(_\\d+)?$",
f"\\1_{len(self.main_policies) - 1}",
module_id,
)
self.main_policies.add(new_mod_id)
elif module_id in self.main_exploiters:
new_mod_id = re.sub(
"_\\d+$", f"_{len(self.main_exploiters)}", module_id
)
self.main_exploiters.add(new_mod_id)
else:
new_mod_id = re.sub(
"_\\d+$", f"_{len(self.league_exploiters)}", module_id
)
self.league_exploiters.add(new_mod_id)
if keep_training:
self.trainable_policies.add(new_mod_id)
else:
self.non_trainable_policies.add(new_mod_id)
print(f"adding new opponents to the mix ({new_mod_id}).")
# Initialize state variablers for agent-to-module mapping. Note, we
# need to keep track of the league-exploiter to always match a
# non-trainable policy with a trainable one - otherwise matches are
# a waste of resources.
self.type_count = 0
self.exploiter = None
def agent_to_module_mapping_fn(agent_id, episode, **kwargs):
# Pick whether this is ...
type_ = np.random.choice([1, 2])
# Each second third call reset state variables. Note, there will
# be always two agents playing against each others.
if self.type_count >= 2:
# Reset the counter.
self.type_count = 0
# Set the exploiter to `None`.
self.exploiter = None
# Increment the counter for each agent.
self.type_count += 1
# 1) League exploiter vs any other.
if type_ == 1:
# Note, the exploiter could be either of `type_==1` or `type_==2`.
if not self.exploiter:
self.exploiter = "league_exploiter_" + str(
np.random.choice(
list(range(len(self.league_exploiters)))
)
)
# This league exploiter is frozen: Play against a
# trainable policy.
if self.exploiter not in self.trainable_policies:
opponent = np.random.choice(list(self.trainable_policies))
# League exploiter is trainable: Play against any other
# non-trainable policy.
else:
opponent = np.random.choice(
list(self.non_trainable_policies)
)
# Only record match stats once per match.
if hash(episode.id_) % 2 == agent_id:
self._matching_stats[(self.exploiter, opponent)] += 1
return self.exploiter
else:
return opponent
# 2) Main exploiter vs main.
else:
# Note, the exploiter could be either of `type_==1` or `type_==2`.
if not self.exploiter:
self.exploiter = "main_exploiter_" + str(
np.random.choice(list(range(len(self.main_exploiters))))
)
# Main exploiter is frozen: Play against the main
# policy.
if self.exploiter not in self.trainable_policies:
main = "main"
# Main exploiter is trainable: Play against any
# frozen main.
else:
main = np.random.choice(list(self.main_policies - {"main"}))
# Only record match stats once per match.
if hash(episode.id_) % 2 == agent_id:
self._matching_stats[(self.exploiter, main)] += 1
return self.exploiter
else:
return main
multi_rl_module = local_worker.module
main_module = multi_rl_module["main"]
# Set the weights of the new polic(y/ies).
if initializing_exploiters:
main_state = main_module.get_state()
multi_rl_module["main_0"].set_state(main_state)
multi_rl_module["league_exploiter_1"].set_state(main_state)
multi_rl_module["main_exploiter_1"].set_state(main_state)
# We need to sync the just copied local weights to all the
# remote workers and remote Learner workers as well.
algorithm.env_runner_group.sync_weights(
policies=["main_0", "league_exploiter_1", "main_exploiter_1"]
)
algorithm.learner_group.set_weights(multi_rl_module.get_state())
else:
algorithm.add_module(
module_id=new_mod_id,
module_spec=RLModuleSpec.from_module(main_module),
)
# TODO (sven): Maybe we should move this convenience step back into
# `Algorithm.add_module()`? Would be less explicit, but also
# easier.
algorithm.set_state(
{
"learner_group": {
"learner": {
"rl_module": {
new_mod_id: multi_rl_module[
module_id
].get_state(),
}
}
}
}
)
algorithm.env_runner_group.foreach_env_runner(
lambda env_runner: env_runner.config.multi_agent(
policy_mapping_fn=agent_to_module_mapping_fn,
# This setting doesn't really matter for EnvRunners (no
# training going on there, but we'll update this as well
# here for good measure).
policies_to_train=_trainable_policies,
),
local_env_runner=True,
)
# Set all Learner workers' should_module_be_updated to the new
# value.
algorithm.learner_group.foreach_learner(
func=lambda learner: learner.config.multi_agent(
policies_to_train=_trainable_policies,
),
timeout_seconds=0.0, # fire-and-forget
)
league_changed = True
else:
print("not good enough; will keep learning ...")
# Add current league size to results dict.
result["league_size"] = len(self.non_trainable_policies) + len(
self.trainable_policies
)
if league_changed:
self._print_league()
def _print_league(self):
print("--- League ---")
print("Matchups:")
pprint(self._matching_stats)
print("Trainable policies (win-rates):")
for p in sorted(self.trainable_policies):
wr = self.win_rates[p] if p in self.win_rates else 0.0
print(f"\t{p}: {wr}")
print("Frozen policies:")
for p in sorted(self.non_trainable_policies):
wr = self.win_rates[p] if p in self.win_rates else 0.0
print(f"\t{p}: {wr}")
print()
@@ -0,0 +1,201 @@
import re
import numpy as np
from ray._common.deprecation import Deprecated
from ray.rllib.callbacks.callbacks import RLlibCallback
from ray.rllib.utils.metrics import ENV_RUNNER_RESULTS
@Deprecated(help="Use the example for the new RLlib API stack", error=False)
class SelfPlayLeagueBasedCallbackOldAPIStack(RLlibCallback):
def __init__(self, win_rate_threshold):
super().__init__()
# All policies in the league.
self.main_policies = {"main", "main_0"}
self.main_exploiters = {"main_exploiter_0", "main_exploiter_1"}
self.league_exploiters = {"league_exploiter_0", "league_exploiter_1"}
# Set of currently trainable policies in the league.
self.trainable_policies = {"main"}
# Set of currently non-trainable (frozen) policies in the league.
self.non_trainable_policies = {
"main_0",
"league_exploiter_0",
"main_exploiter_0",
}
# The win-rate value reaching of which leads to a new module being added
# to the leage (frozen copy of main).
self.win_rate_threshold = win_rate_threshold
# Store the win rates for league overview printouts.
self.win_rates = {}
def on_train_result(self, *, algorithm, result, **kwargs):
# Avoid `self` being pickled into the remote function below.
_trainable_policies = self.trainable_policies
# Get the win rate for the train batch.
# Note that normally, you should set up a proper evaluation config,
# such that evaluation always happens on the already updated policy,
# instead of on the already used train_batch.
for policy_id, rew in result[ENV_RUNNER_RESULTS]["hist_stats"].items():
mo = re.match("^policy_(.+)_reward$", policy_id)
if mo is None:
continue
policy_id = mo.group(1)
# Calculate this policy's win rate.
won = 0
for r in rew:
if r > 0.0: # win = 1.0; loss = -1.0
won += 1
win_rate = won / len(rew)
self.win_rates[policy_id] = win_rate
# Policy is frozen; ignore.
if policy_id in self.non_trainable_policies:
continue
print(
f"Iter={algorithm.iteration} {policy_id}'s " f"win-rate={win_rate} -> ",
end="",
)
# If win rate is good -> Snapshot current policy and decide,
# whether to freeze the copy or not.
if win_rate > self.win_rate_threshold:
is_main = re.match("^main(_\\d+)?$", policy_id)
initializing_exploiters = False
# First time, main manages a decent win-rate against random:
# Add league_exploiter_0 and main_exploiter_0 to the mix.
if is_main and len(self.trainable_policies) == 1:
initializing_exploiters = True
self.trainable_policies.add("league_exploiter_0")
self.trainable_policies.add("main_exploiter_0")
else:
keep_training = (
False
if is_main
else np.random.choice([True, False], p=[0.3, 0.7])
)
if policy_id in self.main_policies:
new_pol_id = re.sub(
"_\\d+$", f"_{len(self.main_policies) - 1}", policy_id
)
self.main_policies.add(new_pol_id)
elif policy_id in self.main_exploiters:
new_pol_id = re.sub(
"_\\d+$", f"_{len(self.main_exploiters)}", policy_id
)
self.main_exploiters.add(new_pol_id)
else:
new_pol_id = re.sub(
"_\\d+$", f"_{len(self.league_exploiters)}", policy_id
)
self.league_exploiters.add(new_pol_id)
if keep_training:
self.trainable_policies.add(new_pol_id)
else:
self.non_trainable_policies.add(new_pol_id)
print(f"adding new opponents to the mix ({new_pol_id}).")
# Update our mapping function accordingly.
def policy_mapping_fn(agent_id, episode, worker=None, **kwargs):
# Pick, whether this is ...
type_ = np.random.choice([1, 2])
# 1) League exploiter vs any other.
if type_ == 1:
league_exploiter = "league_exploiter_" + str(
np.random.choice(list(range(len(self.league_exploiters))))
)
# This league exploiter is frozen: Play against a
# trainable policy.
if league_exploiter not in self.trainable_policies:
opponent = np.random.choice(list(self.trainable_policies))
# League exploiter is trainable: Play against any other
# non-trainable policy.
else:
opponent = np.random.choice(
list(self.non_trainable_policies)
)
print(f"{league_exploiter} vs {opponent}")
return (
league_exploiter
if episode.episode_id % 2 == agent_id
else opponent
)
# 2) Main exploiter vs main.
else:
main_exploiter = "main_exploiter_" + str(
np.random.choice(list(range(len(self.main_exploiters))))
)
# Main exploiter is frozen: Play against the main
# policy.
if main_exploiter not in self.trainable_policies:
main = "main"
# Main exploiter is trainable: Play against any
# frozen main.
else:
main = np.random.choice(list(self.main_policies - {"main"}))
# print(f"{main_exploiter} vs {main}")
return (
main_exploiter
if episode.episode_id % 2 == agent_id
else main
)
# Set the weights of the new polic(y/ies).
if initializing_exploiters:
main_state = algorithm.get_policy("main").get_state()
pol_map = algorithm.env_runner.policy_map
pol_map["main_0"].set_state(main_state)
pol_map["league_exploiter_1"].set_state(main_state)
pol_map["main_exploiter_1"].set_state(main_state)
# We need to sync the just copied local weights to all the
# remote workers as well.
algorithm.env_runner_group.sync_weights(
policies=["main_0", "league_exploiter_1", "main_exploiter_1"]
)
def _set(worker):
worker.set_policy_mapping_fn(policy_mapping_fn)
worker.set_is_policy_to_train(_trainable_policies)
algorithm.env_runner_group.foreach_env_runner(_set)
else:
base_pol = algorithm.get_policy(policy_id)
new_policy = algorithm.add_policy(
policy_id=new_pol_id,
policy_cls=type(base_pol),
policy_mapping_fn=policy_mapping_fn,
policies_to_train=self.trainable_policies,
config=base_pol.config,
observation_space=base_pol.observation_space,
action_space=base_pol.action_space,
)
main_state = base_pol.get_state()
new_policy.set_state(main_state)
# We need to sync the just copied local weights to all the
# remote workers as well.
algorithm.env_runner_group.sync_weights(policies=[new_pol_id])
self._print_league()
else:
print("not good enough; will keep learning ...")
def _print_league(self):
print("--- League ---")
print("Trainable policies (win-rates):")
for p in sorted(self.trainable_policies):
wr = self.win_rates[p] if p in self.win_rates else 0.0
print(f"\t{p}: {wr}")
print("Frozen policies:")
for p in sorted(self.non_trainable_policies):
wr = self.win_rates[p] if p in self.win_rates else 0.0
print(f"\t{p}: {wr}")
print()