Alopezcordero's picture
Upload 11 files
5cd3814 verified
Raw
History Blame Contribute Delete
4.46 kB
import numpy as np
import torch
import gymnasium as gym
import register_env # noqa: F401 (registers HumanoidDirection-v0)
from amp_obs import build_amp_obs
class AMPHumanoidEnv(gym.Wrapper):
"""Wraps HumanoidDirection-v0 with an AMP style reward.
FIXES vs original:
- AMP features come from the shared, heading-invariant `build_amp_obs`
(was: raw world-frame qpos[2:]+qvel, letting the discriminator cheat on
global heading).
- Reward mixing is paper-style: total = w_task * r_task + w_style * r_style
with both rewards in [0, 1] and default weights 0.5 / 0.5
(was: r_task in ~[0, 7] plus 0.2 * near-constant style reward).
- `load_disc_state` lets the training callback push fresh discriminator
weights into each env. This is REQUIRED under SubprocVecEnv, where every
worker process holds its own copy of the discriminator; without syncing,
the style reward would be computed with the frozen initial weights
forever.
"""
def __init__(
self,
discriminator,
motion_lib=None,
env_id="HumanoidDirection-v0",
render_mode=None,
task_weight=0.5,
amp_weight=0.5,
device="cpu",
amp_mean=None,
amp_std=None,
reference_state_init_prob=0.5,
):
env = gym.make(env_id, render_mode=render_mode)
super().__init__(env)
self.disc = discriminator
self.disc.eval()
self.motion_lib = motion_lib
self.task_weight = task_weight
self.amp_weight = amp_weight
self.device = device
self.prev_amp_obs = None
self.fake_amp_transitions = []
self.amp_mean = amp_mean
self.amp_std = amp_std
self.reference_state_init_prob = reference_state_init_prob
# ------------------------------------------------------------------ #
def get_amp_obs(self):
data = self.env.unwrapped.data
return build_amp_obs(data.qpos.copy(), data.qvel.copy())
def load_disc_state(self, state_dict):
"""Called via VecEnv.env_method by the AMP callback after each
discriminator update."""
self.disc.load_state_dict(state_dict)
self.disc.eval()
return True
def _current_policy_obs(self):
unwrapped = self.env.unwrapped
humanoid_obs = unwrapped._get_obs()
if hasattr(unwrapped, "_get_obs_with_direction"):
return unwrapped._get_obs_with_direction(humanoid_obs)
return humanoid_obs
def maybe_reference_state_init(self):
if self.motion_lib is None or self.reference_state_init_prob <= 0.0:
return False
rng = self.env.unwrapped.np_random
if rng.random() >= self.reference_state_init_prob:
return False
qpos, qvel = self.motion_lib.sample_reference_state()
self.env.unwrapped.set_state(qpos, qvel)
return True
def reset(self, **kwargs):
obs, info = self.env.reset(**kwargs)
used_reference_state = self.maybe_reference_state_init()
if used_reference_state:
obs = self._current_policy_obs()
self.prev_amp_obs = self.get_amp_obs()
info["reference_state_init"] = used_reference_state
return obs, info
def step(self, action):
obs, task_reward, terminated, truncated, info = self.env.step(action)
current_amp_obs = self.get_amp_obs()
amp_transition = np.concatenate(
[self.prev_amp_obs, current_amp_obs]
).astype(np.float32)
self.fake_amp_transitions.append(amp_transition)
x_np = amp_transition
if self.amp_mean is not None and self.amp_std is not None:
x_np = (x_np - self.amp_mean) / self.amp_std
with torch.no_grad():
x = torch.tensor(
x_np, dtype=torch.float32, device=self.device
).unsqueeze(0)
amp_reward = self.disc.amp_reward(x).item()
total_reward = (
self.task_weight * task_reward + self.amp_weight * amp_reward
)
info["task_reward"] = task_reward
info["amp_reward"] = amp_reward
info["total_reward"] = total_reward
self.prev_amp_obs = current_amp_obs
return obs, total_reward, terminated, truncated, info
def pop_fake_transitions(self):
transitions = self.fake_amp_transitions
self.fake_amp_transitions = []
return transitions