feat: split n flat n pad decentered
This commit is contained in:
parent
5e7fd27b61
commit
9c20c28b2a
5 changed files with 88 additions and 50 deletions
|
|
@ -81,6 +81,7 @@ def main(dict_cfg: DictConfig) -> None:
|
||||||
obs_processor = create_obs_processor(
|
obs_processor = create_obs_processor(
|
||||||
bounds_dict=training.obs_bounds.to_bounds_dict(),
|
bounds_dict=training.obs_bounds.to_bounds_dict(),
|
||||||
padding_masks=padding_masks,
|
padding_masks=padding_masks,
|
||||||
|
segments_per_arm=env_morphology.segments_per_arm,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 6. Build environment
|
# 6. Build environment
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@ from typing import Dict, Tuple, Optional
|
||||||
|
|
||||||
from brittle_star_project.environment.env_config import MorphMode
|
from brittle_star_project.environment.env_config import MorphMode
|
||||||
|
|
||||||
|
from experiment_logger import get_logger
|
||||||
|
|
||||||
|
logger11 = get_logger()
|
||||||
|
|
||||||
_JOINT_SCALED_KEYS = frozenset(
|
_JOINT_SCALED_KEYS = frozenset(
|
||||||
{
|
{
|
||||||
"joint_position",
|
"joint_position",
|
||||||
|
|
@ -20,12 +24,35 @@ _SEGMENT_SCALED_KEYS = frozenset(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_joint_indices(segments_per_arm):
|
||||||
|
indices = []
|
||||||
|
start = 0
|
||||||
|
for segs in segments_per_arm:
|
||||||
|
# 2 joints per segment
|
||||||
|
count = segs * 2
|
||||||
|
idx = jnp.arange(start, start + count)
|
||||||
|
indices.append(idx)
|
||||||
|
start += count
|
||||||
|
return indices
|
||||||
|
|
||||||
|
|
||||||
|
def _build_segment_indices(segments_per_arm):
|
||||||
|
indices = []
|
||||||
|
start = 0
|
||||||
|
for segs in segments_per_arm:
|
||||||
|
idx = jnp.arange(start, start + segs)
|
||||||
|
indices.append(idx)
|
||||||
|
start += segs
|
||||||
|
return indices
|
||||||
|
|
||||||
|
|
||||||
def create_obs_processor(
|
def create_obs_processor(
|
||||||
bounds_dict: Dict[str, Tuple[float, float]],
|
bounds_dict: Dict[str, Tuple[float, float]],
|
||||||
num_arms: int,
|
num_arms: int,
|
||||||
needed_copies: int,
|
needed_copies: int,
|
||||||
padding_masks: Optional[Dict] = None,
|
padding_masks: Optional[Dict] = None,
|
||||||
morph_mode: MorphMode = MorphMode.CENTRALIZED,
|
morph_mode: MorphMode = MorphMode.CENTRALIZED,
|
||||||
|
segments_per_arm=[4, 4, 4, 4, 4],
|
||||||
):
|
):
|
||||||
# made a set to allow O(1) search
|
# made a set to allow O(1) search
|
||||||
ordered_keys = frozenset(
|
ordered_keys = frozenset(
|
||||||
|
|
@ -38,6 +65,8 @@ def create_obs_processor(
|
||||||
"segment_contact",
|
"segment_contact",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
segment_indices = _build_segment_indices(segments_per_arm)
|
||||||
|
joint_indices = _build_joint_indices(segments_per_arm)
|
||||||
|
|
||||||
def _add_derived_features(obs: dict) -> dict:
|
def _add_derived_features(obs: dict) -> dict:
|
||||||
new_obs = dict(obs)
|
new_obs = dict(obs)
|
||||||
|
|
@ -69,96 +98,101 @@ def create_obs_processor(
|
||||||
normalized[key] = arr
|
normalized[key] = arr
|
||||||
return normalized
|
return normalized
|
||||||
|
|
||||||
# ndarray (agents, features)
|
|
||||||
def _pad_features(obs: dict, agent_count: int) -> dict:
|
def _pad_features(obs: dict, agent_count: int) -> dict:
|
||||||
assert padding_masks is not None
|
assert padding_masks is not None
|
||||||
|
|
||||||
padded = {}
|
padded = {}
|
||||||
|
|
||||||
# arr shape = (agent_count, ...) TODO
|
|
||||||
for key, arr in obs.items():
|
for key, arr in obs.items():
|
||||||
if key in _JOINT_SCALED_KEYS:
|
if key in _JOINT_SCALED_KEYS:
|
||||||
padded_arr = jnp.zeros(
|
target_size = padding_masks["target_size_2x"]
|
||||||
(agent_count, padding_masks["target_size_2x"]), dtype=arr.dtype
|
out = jnp.zeros((agent_count, target_size), dtype=arr.dtype)
|
||||||
)
|
# place structured values at front, rest stays 0
|
||||||
padded[key] = padded_arr.at[:, padding_masks["mask_2x"]].set(arr)
|
out = out.at[:, : arr.shape[1]].set(arr)
|
||||||
|
padded[key] = out
|
||||||
|
|
||||||
elif key in _SEGMENT_SCALED_KEYS:
|
elif key in _SEGMENT_SCALED_KEYS:
|
||||||
padded_arr = jnp.zeros(
|
target_size = padding_masks["target_size_1x"]
|
||||||
(agent_count, padding_masks["target_size_1x"]), dtype=arr.dtype
|
out = jnp.zeros((agent_count, target_size), dtype=arr.dtype)
|
||||||
)
|
out = out.at[:, : arr.shape[1]].set(arr)
|
||||||
padded[key] = padded_arr.at[:, padding_masks["mask_1x"]].set(arr)
|
padded[key] = out
|
||||||
else:
|
else:
|
||||||
padded[key] = arr
|
padded[key] = arr
|
||||||
return padded
|
return padded
|
||||||
|
|
||||||
def _split_to_agents(obs: dict, agent_count: int) -> dict:
|
def _split_to_agents(obs: dict, morph_mode, segments_per_arm) -> dict:
|
||||||
"""
|
|
||||||
Each observation type is now updated to (agent_count, feature_shape),
|
|
||||||
thus duplicating the observation for each agent
|
|
||||||
"""
|
|
||||||
|
|
||||||
output = {}
|
output = {}
|
||||||
|
num_arms = len(segments_per_arm)
|
||||||
|
|
||||||
for key, arr in obs.items():
|
for key, arr in obs.items():
|
||||||
if key not in ordered_keys or arr.size == 0:
|
if key not in ordered_keys or arr.size == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
logger11.info(f"[INPUT] {key}: {arr.shape}")
|
||||||
|
|
||||||
if arr.ndim == 0:
|
if arr.ndim == 0:
|
||||||
arr = arr.reshape(1)
|
arr = arr.reshape(1)
|
||||||
|
|
||||||
output[key] = jnp.repeat(arr[None, :], agent_count, axis=0)
|
if morph_mode == MorphMode.CENTRALIZED:
|
||||||
|
out = arr.reshape(1, -1)
|
||||||
|
output[key] = out
|
||||||
|
continue
|
||||||
|
|
||||||
|
if key in _SEGMENT_SCALED_KEYS:
|
||||||
|
per_agent = [jnp.take(arr, idx, axis=0) for idx in segment_indices]
|
||||||
|
out = jnp.stack(per_agent)
|
||||||
|
|
||||||
|
elif key in _JOINT_SCALED_KEYS:
|
||||||
|
per_agent = [jnp.take(arr, idx, axis=0) for idx in joint_indices]
|
||||||
|
out = jnp.stack(per_agent)
|
||||||
|
|
||||||
|
else:
|
||||||
|
out = jnp.repeat(arr[None, :], num_arms, axis=0)
|
||||||
|
|
||||||
|
logger11.info(f"[OUTPUT] {key}: {out.shape}")
|
||||||
|
output[key] = out
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
# TODO: update
|
|
||||||
def _flatten_features(obs: dict) -> jnp.ndarray:
|
def _flatten_features(obs: dict) -> jnp.ndarray:
|
||||||
"""
|
"""
|
||||||
Collapse all observations into a single array
|
Input:
|
||||||
"""
|
key -> (num_arms, feat_per_key)
|
||||||
|
|
||||||
|
Output:
|
||||||
|
(num_arms, total_features)
|
||||||
|
"""
|
||||||
values = []
|
values = []
|
||||||
|
|
||||||
for key in sorted(obs.keys()):
|
for key in ordered_keys:
|
||||||
if key not in ordered_keys:
|
if key not in obs:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# empty arrays handled in split_to_agents
|
arr = jnp.asarray(obs[key]) # (num_arms, feat)
|
||||||
v = obs[key] # (agent_count, feat)
|
|
||||||
|
|
||||||
# -------- CENTRALIZED --------
|
if arr.size == 0:
|
||||||
if morph_mode == MorphMode.CENTRALIZED:
|
|
||||||
values.append(v.reshape(1, -1)) # (1, feat)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# -------- SCALE WITH SEGMENTS --------
|
if arr.ndim == 1:
|
||||||
if key in _JOINT_SCALED_KEYS:
|
arr = arr[:, None]
|
||||||
v = v.reshape(num_arms, -1) # (n_arms, 2)
|
|
||||||
|
|
||||||
elif key in _SEGMENT_SCALED_KEYS:
|
arr = arr.reshape(arr.shape[0], -1)
|
||||||
v = v[:, None] # (segments, 1)
|
|
||||||
|
|
||||||
else:
|
values.append(arr)
|
||||||
# global key, broadcast to all nodes
|
|
||||||
v = jnp.repeat(v[None, :], num_arms, axis=0) # (num_arms, feat)
|
|
||||||
|
|
||||||
# RING + FULLY CONNECTED
|
return jnp.concatenate(values, axis=-1) # (num_arms, total_feat)
|
||||||
v = v.reshape(num_arms, -1)
|
|
||||||
values.append(v) # (n_arms, feat)
|
|
||||||
|
|
||||||
return jnp.concatenate(values, axis=-1) # (agent, feat)
|
|
||||||
|
|
||||||
def _process_single(obs_dict: dict) -> jnp.ndarray:
|
def _process_single(obs_dict: dict) -> jnp.ndarray:
|
||||||
processed = _add_derived_features(obs_dict) # key |--> (feat-count,)
|
processed = _add_derived_features(obs_dict)
|
||||||
processed = _normalize_features(processed) # key |--> (feat-count,)
|
processed = _normalize_features(processed)
|
||||||
# TODO: split to agents
|
# morph_mode = MorphMode.FULLY_CONNECTED
|
||||||
processed = _split_to_agents(processed, needed_copies) # key |--> (agents, feat-count)
|
processed = _split_to_agents(processed, morph_mode, segments_per_arm)
|
||||||
|
# needed_copies = 5
|
||||||
if padding_masks is not None:
|
if padding_masks is not None:
|
||||||
processed = _pad_features(
|
processed = _pad_features(processed, agent_count=needed_copies)
|
||||||
processed, agent_count=needed_copies
|
flat = _flatten_features(processed) # (num_arms, total_feat)
|
||||||
) # key |--> (agents, feat-count')
|
logger11.info(f"[FLATTENED FINAL] shape: {flat.shape}")
|
||||||
for k, v in processed.items():
|
logger11.info(f"[PER AGENT] example row 0 shape: {flat[0].shape}")
|
||||||
print(k, v.shape)
|
|
||||||
|
|
||||||
exit(1)
|
exit(1)
|
||||||
return _flatten_features(processed) # (agents, feat)
|
return _flatten_features(processed) # (agents, feat)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,7 @@ class PPOTrainer:
|
||||||
num_arms=self.num_arms,
|
num_arms=self.num_arms,
|
||||||
morph_mode=self.morph_mode,
|
morph_mode=self.morph_mode,
|
||||||
padding_masks=self.env.padding_masks,
|
padding_masks=self.env.padding_masks,
|
||||||
|
segments_per_arm=self.segments_per_arm,
|
||||||
)
|
)
|
||||||
|
|
||||||
action_low = jnp.asarray(self.env.single_action_space.low, dtype=jnp.float32)
|
action_low = jnp.asarray(self.env.single_action_space.low, dtype=jnp.float32)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ def test_centralized_forward_pass_with_padding():
|
||||||
num_arms=num_arms,
|
num_arms=num_arms,
|
||||||
padding_masks=masks,
|
padding_masks=masks,
|
||||||
morph_mode=MorphMode.CENTRALIZED,
|
morph_mode=MorphMode.CENTRALIZED,
|
||||||
|
segments_per_arm=segments_per_arm,
|
||||||
)
|
)
|
||||||
global_state = obs_processor(amputated_obs)
|
global_state = obs_processor(amputated_obs)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ def test_processor_converts_to_egocentric_direction():
|
||||||
num_arms=num_arms,
|
num_arms=num_arms,
|
||||||
padding_masks=env.padding_masks,
|
padding_masks=env.padding_masks,
|
||||||
morph_mode=MorphMode.CENTRALIZED,
|
morph_mode=MorphMode.CENTRALIZED,
|
||||||
|
segments_per_arm=segments_per_arm,
|
||||||
)
|
)
|
||||||
|
|
||||||
env_state = env.reset(seed=42)
|
env_state = env.reset(seed=42)
|
||||||
|
|
|
||||||
Reference in a new issue