class SquashedGaussianPolicy(nnx.Module):
"""A state-dependent Gaussian squashed through a = c tanh(u)."""
def __init__(self, obs_dim, act_dim, hidden=64, rngs=None):
self.trunk = nnx.Sequential(
nnx.Linear(obs_dim, hidden, rngs=rngs), jax.nn.relu,
nnx.Linear(hidden, hidden, rngs=rngs), jax.nn.relu)
self.mu = nnx.Linear(hidden, act_dim, rngs=rngs)
self.log_std = nnx.Linear(hidden, act_dim, rngs=rngs)
def __call__(self, obs):
h = self.trunk(obs)
return self.mu(h), jnp.exp(jnp.clip(self.log_std(h), -5, 2))
def log_prob(self, u, mean, std):
"""log pi at a = c tanh(u), from the pre-squash u the sampler keeps."""
logdet = 2 * (jnp.log(2.0) - u - jax.nn.softplus(-2 * u))
return (jax.scipy.stats.norm.logpdf(u, mean, std)
- logdet - jnp.log(c)).sum(-1)
def sample(self, obs, key):
"""A reparameterized action and its log-probability, differentiable."""
mean, std = self(obs)
u = mean + std * jax.random.normal(key, std.shape)
return c * jnp.tanh(u), self.log_prob(u, mean, std)
def act(self, obs, rng):
if not hasattr(self, '_fwd'): # compile the fixed-shape acting
self._fwd = nnx.cached_partial(nnx.jit(lambda net, o: net(o)),
self) # forward, once
mean, std = self._fwd(jnp.asarray(obs))
u = np.asarray(mean) + np.asarray(std) * rng.standard_normal(
mean.shape, dtype=np.float32)
return c * np.tanh(u)
def act_greedy(self, obs, rng=None):
mean, _ = self(jnp.asarray(obs))
return c * np.tanh(np.asarray(mean))