mujax 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mujax/__init__.py +55 -0
- mujax/actor.py +131 -0
- mujax/algorithm.py +53 -0
- mujax/algorithms/__init__.py +21 -0
- mujax/algorithms/gmz.py +116 -0
- mujax/algorithms/muzero.py +331 -0
- mujax/algorithms/smz.py +175 -0
- mujax/checkpoint.py +192 -0
- mujax/cli.py +72 -0
- mujax/config.py +101 -0
- mujax/experiment.py +368 -0
- mujax/learner.py +94 -0
- mujax/loggers.py +153 -0
- mujax/loop.py +129 -0
- mujax/observers.py +80 -0
- mujax/reanalyze.py +87 -0
- mujax/replay.py +218 -0
- mujax/types.py +60 -0
- mujax-0.1.0.dist-info/METADATA +148 -0
- mujax-0.1.0.dist-info/RECORD +24 -0
- mujax-0.1.0.dist-info/WHEEL +5 -0
- mujax-0.1.0.dist-info/entry_points.txt +2 -0
- mujax-0.1.0.dist-info/licenses/LICENSE +202 -0
- mujax-0.1.0.dist-info/top_level.txt +1 -0
mujax/__init__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Stochastic MuZero and Gumbel MuZero, built on JAX, mctx and Gymnasium.
|
|
2
|
+
|
|
3
|
+
Module map:
|
|
4
|
+
config.py MuZeroConfig
|
|
5
|
+
algorithm.py Algorithm, SearchPolicy: the contract between the infrastructure and an algorithm
|
|
6
|
+
algorithms/ ALGORITHMS registry and the algorithms
|
|
7
|
+
muzero.py shared math of the MuZero family: Support, network blocks, loss skeleton
|
|
8
|
+
smz.py / gmz.py algorithms (config, networks, search, loss)
|
|
9
|
+
replay.py / reanalyze.py flashbax replay and reanalyze iterator
|
|
10
|
+
actor.py / learner.py / loop.py environment interaction, gradient step, env loop
|
|
11
|
+
observers.py / loggers.py / checkpoint.py metrics, terminal/W&B writers, orbax checkpoint
|
|
12
|
+
experiment.py ExperimentConfig, run_experiment, load_actor
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from mujax.actor import Actor
|
|
16
|
+
from mujax.algorithm import Algorithm
|
|
17
|
+
from mujax.algorithms import ALGORITHMS, GMZ, SMZ, GMZConfig, SMZConfig, algorithm_for
|
|
18
|
+
from mujax.checkpoint import CheckpointingConfig
|
|
19
|
+
from mujax.config import MuZeroConfig
|
|
20
|
+
from mujax.experiment import EvaluationConfig, ExperimentConfig, load_actor, run_experiment
|
|
21
|
+
from mujax.learner import Learner
|
|
22
|
+
from mujax.loggers import ConsoleWriter, Logger, WandbWriter, Writer, create_writer
|
|
23
|
+
from mujax.loop import EnvironmentLoop
|
|
24
|
+
from mujax.observers import EnvLoopObserver, InfoKeysObserver
|
|
25
|
+
from mujax.types import EnvironmentSpec, make_environment_spec
|
|
26
|
+
|
|
27
|
+
__version__ = "0.1.0"
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"ALGORITHMS",
|
|
31
|
+
"GMZ",
|
|
32
|
+
"SMZ",
|
|
33
|
+
"Actor",
|
|
34
|
+
"Algorithm",
|
|
35
|
+
"CheckpointingConfig",
|
|
36
|
+
"ConsoleWriter",
|
|
37
|
+
"EnvLoopObserver",
|
|
38
|
+
"EnvironmentLoop",
|
|
39
|
+
"EnvironmentSpec",
|
|
40
|
+
"EvaluationConfig",
|
|
41
|
+
"ExperimentConfig",
|
|
42
|
+
"GMZConfig",
|
|
43
|
+
"InfoKeysObserver",
|
|
44
|
+
"Learner",
|
|
45
|
+
"Logger",
|
|
46
|
+
"MuZeroConfig",
|
|
47
|
+
"SMZConfig",
|
|
48
|
+
"WandbWriter",
|
|
49
|
+
"Writer",
|
|
50
|
+
"algorithm_for",
|
|
51
|
+
"create_writer",
|
|
52
|
+
"load_actor",
|
|
53
|
+
"make_environment_spec",
|
|
54
|
+
"run_experiment",
|
|
55
|
+
]
|
mujax/actor.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import jax
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from mujax.algorithm import SearchPolicy
|
|
10
|
+
from mujax.replay import Adder
|
|
11
|
+
from mujax.types import SearchOutput, TimeStep, Transition
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def invalid_actions_from_info(info: dict | None, num_envs: int, num_actions: int) -> np.ndarray:
|
|
15
|
+
"""If `info["invalid_actions"]` (0/1 mask, 1 = invalid) is absent, all actions are treated as valid."""
|
|
16
|
+
raw = info.get("invalid_actions") if info else None
|
|
17
|
+
if raw is None:
|
|
18
|
+
return np.zeros((num_envs, num_actions), dtype=np.float32)
|
|
19
|
+
return np.asarray(raw, dtype=np.float32).reshape(num_envs, num_actions)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Actor:
|
|
23
|
+
"""The env-facing side: jitted search policy + learner params + writing to replay.
|
|
24
|
+
|
|
25
|
+
Observations are (num_envs, obs_dim), actions are (num_envs,).
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
get_params: () -> (params, learner_steps); usually `Learner.get_params`.
|
|
29
|
+
replay: if given (`Buffer.adder(i)`), every step is written there (training actor).
|
|
30
|
+
value_fn: (params, obs) -> search-free value (N,); for episodes cut by the time limit,
|
|
31
|
+
to bootstrap with V(final_obs). If None, a truncated episode is treated as terminated.
|
|
32
|
+
update_period: how many `select_action` calls between params fetches.
|
|
33
|
+
per_episode_update: if True, params are fetched only at episode start (evaluator: the score
|
|
34
|
+
matches the params that played that episode).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
policy: SearchPolicy,
|
|
40
|
+
random_key: jax.Array,
|
|
41
|
+
get_params: Callable[[], tuple[Any, int]],
|
|
42
|
+
*,
|
|
43
|
+
num_actions: int,
|
|
44
|
+
replay: Adder | None = None,
|
|
45
|
+
value_fn: Callable[[Any, Any], Any] | None = None,
|
|
46
|
+
device: jax.Device | None = None,
|
|
47
|
+
update_period: int = 1,
|
|
48
|
+
per_episode_update: bool = False,
|
|
49
|
+
) -> None:
|
|
50
|
+
self._search = jax.jit(policy)
|
|
51
|
+
self._key = random_key
|
|
52
|
+
self._get_params = get_params
|
|
53
|
+
self._num_actions = num_actions
|
|
54
|
+
self._replay = replay
|
|
55
|
+
self._value = jax.jit(value_fn) if value_fn is not None else None
|
|
56
|
+
self._device = device
|
|
57
|
+
self._update_period = max(1, update_period)
|
|
58
|
+
self._per_episode_update = per_episode_update
|
|
59
|
+
self._num_calls = 0
|
|
60
|
+
self._episode_start = True
|
|
61
|
+
self._last_search: SearchOutput | None = None
|
|
62
|
+
self._invalid_actions: np.ndarray | None = None
|
|
63
|
+
self._pull_params()
|
|
64
|
+
|
|
65
|
+
def _pull_params(self) -> None:
|
|
66
|
+
params, learner_steps = self._get_params()
|
|
67
|
+
self._params = (jax.device_put(params, self._device), learner_steps) # single assignment
|
|
68
|
+
|
|
69
|
+
def get_params(self) -> tuple[Any, int]:
|
|
70
|
+
"""The (params, learner_steps) currently playing."""
|
|
71
|
+
return self._params
|
|
72
|
+
|
|
73
|
+
def observe_first(self, observation: np.ndarray, info: dict) -> None:
|
|
74
|
+
self._invalid_actions = invalid_actions_from_info(info, len(observation), self._num_actions)
|
|
75
|
+
self._episode_start = True
|
|
76
|
+
|
|
77
|
+
def select_action(self, observation: np.ndarray) -> np.ndarray:
|
|
78
|
+
self._num_calls += 1
|
|
79
|
+
if (self._per_episode_update and self._episode_start) or (not self._per_episode_update and self._num_calls % self._update_period == 0):
|
|
80
|
+
self._pull_params()
|
|
81
|
+
self._episode_start = False
|
|
82
|
+
|
|
83
|
+
params, learner_steps = self._params
|
|
84
|
+
self._key, search_key = jax.random.split(self._key)
|
|
85
|
+
search = self._search(params, np.asarray(observation), search_key, self._invalid_actions, learner_steps)
|
|
86
|
+
self._last_search = SearchOutput(
|
|
87
|
+
action=np.asarray(search.action, dtype=np.int32),
|
|
88
|
+
policy_probs=np.asarray(search.policy_probs, dtype=np.float32),
|
|
89
|
+
value=np.asarray(search.value, dtype=np.float32),
|
|
90
|
+
)
|
|
91
|
+
return self._last_search.action
|
|
92
|
+
|
|
93
|
+
def _final_values(self, step: TimeStep, truncated: np.ndarray) -> np.ndarray:
|
|
94
|
+
"""V(final_obs) for truncated envs; under SAME_STEP the final observation is in `info["final_obs"]`."""
|
|
95
|
+
values = np.zeros(len(truncated), dtype=np.float32)
|
|
96
|
+
if not truncated.any():
|
|
97
|
+
return values
|
|
98
|
+
# To call the jit with a single shape, the whole batch is filled; non-truncated rows are discarded.
|
|
99
|
+
obs = np.zeros_like(np.asarray(step.observation), dtype=np.float32)
|
|
100
|
+
for index in np.flatnonzero(truncated):
|
|
101
|
+
obs[index] = step.info["final_obs"][index]
|
|
102
|
+
values[truncated] = np.asarray(self._value(self._params[0], obs))[truncated]
|
|
103
|
+
return values
|
|
104
|
+
|
|
105
|
+
def last_search(self) -> SearchOutput | None:
|
|
106
|
+
"""Search output of the last `select_action`; EnvironmentLoop puts it into the TimeStep."""
|
|
107
|
+
return self._last_search
|
|
108
|
+
|
|
109
|
+
def observe(self, step: TimeStep) -> None:
|
|
110
|
+
terminated = np.asarray(step.terminated, dtype=bool)
|
|
111
|
+
truncated = np.asarray(step.truncated, dtype=bool) & ~terminated
|
|
112
|
+
done = terminated | truncated
|
|
113
|
+
if self._replay is not None:
|
|
114
|
+
if self._value is None: # V(final_obs) cannot be computed: a truncated episode is treated as terminated
|
|
115
|
+
terminated, truncated = done, np.zeros_like(truncated)
|
|
116
|
+
self._replay.add(
|
|
117
|
+
Transition(
|
|
118
|
+
observation=np.asarray(step.observation, dtype=np.float32),
|
|
119
|
+
action=self._last_search.action,
|
|
120
|
+
reward=np.asarray(step.reward, dtype=np.float32),
|
|
121
|
+
discount=np.where(terminated, 0.0, 1.0).astype(np.float32),
|
|
122
|
+
policy_probs=self._last_search.policy_probs,
|
|
123
|
+
value=self._last_search.value,
|
|
124
|
+
invalid_actions=self._invalid_actions,
|
|
125
|
+
truncated=truncated.astype(np.float32),
|
|
126
|
+
bootstrap_value=self._final_values(step, truncated),
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
self._invalid_actions = invalid_actions_from_info(step.info, len(done), self._num_actions)
|
|
130
|
+
if done.any():
|
|
131
|
+
self._episode_start = True
|
mujax/algorithm.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""The contract between the shared infrastructure and an algorithm in `mujax.algorithms`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import jax
|
|
10
|
+
import jax.numpy as jnp
|
|
11
|
+
from flax import nnx
|
|
12
|
+
|
|
13
|
+
from mujax.config import MuZeroConfig
|
|
14
|
+
from mujax.types import EnvironmentSpec, SearchOutput
|
|
15
|
+
|
|
16
|
+
# Batched search policy: (params, obs, key, invalid_actions, learner_steps) -> SearchOutput.
|
|
17
|
+
# All inputs and outputs have shape (num_envs, ...).
|
|
18
|
+
SearchPolicy = Callable[[Any, Any, jax.Array, Any, Any], SearchOutput]
|
|
19
|
+
|
|
20
|
+
# Search-free root value: (params, obs) -> (N,); used for the bootstrap tail in reanalyze.
|
|
21
|
+
ValueFn = Callable[[Any, Any], jnp.ndarray]
|
|
22
|
+
|
|
23
|
+
# Algorithm loss: (graphdef, config, params, batch) -> (loss, metrics).
|
|
24
|
+
LossFn = Callable[[Any, MuZeroConfig, Any, Any], tuple[jnp.ndarray, dict[str, jnp.ndarray]]]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclasses.dataclass(frozen=True)
|
|
28
|
+
class Algorithm:
|
|
29
|
+
"""Algorithm-specific parts; everything else (replay, reanalyze, actor, learner, loop) is shared.
|
|
30
|
+
|
|
31
|
+
The model is a single `nnx.Module`; `init` splits it into a graphdef (static structure) and params
|
|
32
|
+
(`nnx.State`, a pytree). Params travel separately (learner, actor threads, mctx, checkpoints) and the
|
|
33
|
+
algorithm functions rebuild the model with `nnx.merge(graphdef, params)`.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
name: Short name written to checkpoint metadata ("gmz", "smz").
|
|
37
|
+
config_cls: Config class of this algorithm; `algorithm_for` uses it to pick the algorithm from a config.
|
|
38
|
+
make_model: (spec, config, rngs) -> model (nnx.Module).
|
|
39
|
+
make_policy: (graphdef, spec, config, evaluation) -> SearchPolicy.
|
|
40
|
+
loss: See `LossFn`.
|
|
41
|
+
make_value_fn: (graphdef, config) -> ValueFn.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
name: str
|
|
45
|
+
config_cls: type[MuZeroConfig]
|
|
46
|
+
make_model: Callable[[EnvironmentSpec, MuZeroConfig, nnx.Rngs], nnx.Module]
|
|
47
|
+
make_policy: Callable[[nnx.GraphDef, EnvironmentSpec, MuZeroConfig, bool], SearchPolicy]
|
|
48
|
+
loss: LossFn
|
|
49
|
+
make_value_fn: Callable[[nnx.GraphDef, MuZeroConfig], ValueFn]
|
|
50
|
+
|
|
51
|
+
def init(self, spec: EnvironmentSpec, config: MuZeroConfig, key: jax.Array) -> tuple[nnx.GraphDef, nnx.State]:
|
|
52
|
+
"""(graphdef, params) of a freshly initialized model."""
|
|
53
|
+
return nnx.split(self.make_model(spec, config, nnx.Rngs(key)))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Algorithms; each module defines an `Algorithm` (see `mujax.algorithm`). New algorithms are registered in `ALGORITHMS`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from mujax.algorithm import Algorithm
|
|
6
|
+
from mujax.algorithms.gmz import GMZ, GMZConfig
|
|
7
|
+
from mujax.algorithms.smz import SMZ, SMZConfig
|
|
8
|
+
from mujax.config import MuZeroConfig
|
|
9
|
+
|
|
10
|
+
ALGORITHMS: dict[str, Algorithm] = {SMZ.name: SMZ, GMZ.name: GMZ}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def algorithm_for(config: MuZeroConfig) -> Algorithm:
|
|
14
|
+
"""The algorithm whose config class `config` is an instance of."""
|
|
15
|
+
for algorithm in ALGORITHMS.values():
|
|
16
|
+
if isinstance(config, algorithm.config_cls):
|
|
17
|
+
return algorithm
|
|
18
|
+
raise ValueError(f"{type(config).__name__} icin algoritma bulunamadi")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = ["ALGORITHMS", "GMZ", "SMZ", "GMZConfig", "SMZConfig", "algorithm_for"]
|
mujax/algorithms/gmz.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Gumbel MuZero: config, networks, search and loss."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
import functools
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
|
|
9
|
+
import jax.numpy as jnp
|
|
10
|
+
import mctx
|
|
11
|
+
from flax import nnx
|
|
12
|
+
|
|
13
|
+
from mujax.algorithm import Algorithm, SearchPolicy
|
|
14
|
+
from mujax.algorithms import muzero
|
|
15
|
+
from mujax.algorithms.muzero import Embedding, Head, Prediction, Support, SymlogInput, Targets
|
|
16
|
+
from mujax.config import MuZeroConfig
|
|
17
|
+
from mujax.types import EnvironmentSpec, SearchOutput
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclasses.dataclass
|
|
21
|
+
class GMZConfig(MuZeroConfig):
|
|
22
|
+
"""Gumbel MuZero hyperparameters."""
|
|
23
|
+
|
|
24
|
+
num_simulations: int = 16
|
|
25
|
+
max_depth: int | None = None
|
|
26
|
+
max_num_considered_actions: int = 16 # clipped to the number of actions
|
|
27
|
+
# Gumbel noise is gmz's only source of exploration (smz uses temperature + Dirichlet).
|
|
28
|
+
gumbel_scale: float = 1.0
|
|
29
|
+
value_scale: float = 0.1
|
|
30
|
+
maxvisit_init: float = 50.0
|
|
31
|
+
|
|
32
|
+
dynamics_layer_sizes: tuple[int, ...] = (256, 256, 256)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Dynamics(nnx.Module):
|
|
36
|
+
def __init__(self, embedding_dim: int, layer_sizes: Sequence[int], num_actions: int, num_bins: int, *, rngs: nnx.Rngs):
|
|
37
|
+
self.num_actions = num_actions
|
|
38
|
+
self.embedding = Embedding(embedding_dim + num_actions, layer_sizes, embedding_dim, rngs=rngs)
|
|
39
|
+
self.reward = Head(embedding_dim, layer_sizes, num_bins, rngs=rngs)
|
|
40
|
+
|
|
41
|
+
def __call__(self, latent, action) -> tuple[jnp.ndarray, jnp.ndarray]:
|
|
42
|
+
"""(next_latent, reward_logits)."""
|
|
43
|
+
next_latent = self.embedding(muzero.one_hot_concat(latent, action, self.num_actions))
|
|
44
|
+
return next_latent, self.reward(next_latent)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class GMZModel(nnx.Module):
|
|
48
|
+
"""The deterministic model: representation, prediction and dynamics."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, spec: EnvironmentSpec, config: GMZConfig, rngs: nnx.Rngs):
|
|
51
|
+
self.representation = SymlogInput(Embedding(spec.obs_dim, config.representation_layer_sizes, config.embedding_dim, rngs=rngs))
|
|
52
|
+
self.prediction = Prediction(
|
|
53
|
+
config.embedding_dim, config.prediction_layer_sizes, spec.num_actions, config.num_bins, unimix=config.policy_unimix, rngs=rngs
|
|
54
|
+
)
|
|
55
|
+
self.dynamics = Dynamics(config.embedding_dim, config.dynamics_layer_sizes, spec.num_actions, config.num_bins, rngs=rngs)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def recurrent_fn(graphdef: nnx.GraphDef, support: Support, config: GMZConfig):
|
|
59
|
+
"""mctx recurrent_fn of the deterministic model."""
|
|
60
|
+
|
|
61
|
+
def fn(params, key, action, latent):
|
|
62
|
+
model = nnx.merge(graphdef, params)
|
|
63
|
+
next_latent, reward_logits = model.dynamics(latent, action)
|
|
64
|
+
value_logits, policy_logits = model.prediction(next_latent)
|
|
65
|
+
reward = support.logits_to_scalar(reward_logits)
|
|
66
|
+
output = mctx.RecurrentFnOutput(
|
|
67
|
+
reward=reward,
|
|
68
|
+
discount=jnp.full_like(reward, config.discount),
|
|
69
|
+
prior_logits=policy_logits,
|
|
70
|
+
value=support.logits_to_scalar(value_logits),
|
|
71
|
+
)
|
|
72
|
+
return output, next_latent
|
|
73
|
+
|
|
74
|
+
return fn
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def make_policy(graphdef: nnx.GraphDef, spec: EnvironmentSpec, config: GMZConfig, evaluation: bool) -> SearchPolicy:
|
|
78
|
+
"""Batched Gumbel MuZero search. `evaluation=True` turns off the Gumbel noise."""
|
|
79
|
+
support = Support.from_config(config)
|
|
80
|
+
max_num_considered_actions = min(int(config.max_num_considered_actions), spec.num_actions)
|
|
81
|
+
qtransform = functools.partial(mctx.qtransform_completed_by_mix_value, value_scale=config.value_scale, maxvisit_init=config.maxvisit_init)
|
|
82
|
+
|
|
83
|
+
def policy(params, obs, key, invalid_actions, learner_steps) -> SearchOutput:
|
|
84
|
+
del learner_steps
|
|
85
|
+
out = mctx.gumbel_muzero_policy(
|
|
86
|
+
params,
|
|
87
|
+
key,
|
|
88
|
+
muzero.root_output(nnx.merge(graphdef, params), support, obs),
|
|
89
|
+
recurrent_fn(graphdef, support, config),
|
|
90
|
+
num_simulations=config.num_simulations,
|
|
91
|
+
invalid_actions=invalid_actions,
|
|
92
|
+
max_depth=config.max_depth,
|
|
93
|
+
qtransform=qtransform,
|
|
94
|
+
max_num_considered_actions=max_num_considered_actions,
|
|
95
|
+
gumbel_scale=0.0 if evaluation else config.gumbel_scale,
|
|
96
|
+
)
|
|
97
|
+
return SearchOutput(out.action, out.action_weights, out.search_tree.summary().value)
|
|
98
|
+
|
|
99
|
+
return policy
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def loss(graphdef: nnx.GraphDef, config: GMZConfig, params: nnx.State, batch) -> tuple[jnp.ndarray, dict[str, jnp.ndarray]]:
|
|
103
|
+
def unroll_step(model: GMZModel, latent, now: Targets, next_: Targets):
|
|
104
|
+
next_latent, reward_logits = model.dynamics(latent, now.action)
|
|
105
|
+
value_logits, policy_logits = model.prediction(next_latent)
|
|
106
|
+
losses = {
|
|
107
|
+
"reward": muzero.masked_mean(muzero.cross_entropy(reward_logits, now.reward_probs), now.in_episode),
|
|
108
|
+
"value": muzero.masked_mean(muzero.cross_entropy(value_logits, next_.value_probs), next_.in_episode),
|
|
109
|
+
"policy": muzero.masked_mean(muzero.cross_entropy(policy_logits, next_.policy_probs), next_.in_episode),
|
|
110
|
+
}
|
|
111
|
+
return next_latent, losses, {}
|
|
112
|
+
|
|
113
|
+
return muzero.loss(graphdef, config, params, batch, unroll_step=unroll_step, loss_weights={"value": config.value_loss_weight})
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
GMZ = Algorithm("gmz", GMZConfig, GMZModel, make_policy, loss, muzero.make_value_fn)
|