Skip to content

Commit 62988d7

Browse files
committed
Mini Cheetah Sample Efficiency Experiments
1 parent 159e584 commit 62988d7

29 files changed

+332
-63
lines changed

cfg/config.yaml

+14-17
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ defaults:
33
- model: dpnet
44
- system: linear_system
55
- override hydra/launcher: joblib
6-
# - override hydra/sweeper: optuna
7-
# - override hydra/sweeper/sampler: tpe
86

97
# Running hyperparameters
108
exp_name: test
@@ -76,27 +74,26 @@ hydra:
7674
dir: ./experiments/${hydra.job.name}/
7775
subdir: ${system.summary}_${model.summary}_${hydra.job.override_dirname}
7876

79-
job_logging:
80-
version: 1
81-
colorlog: True
82-
formatters:
83-
simple:
84-
format: '[%(levelname)s][%(name)s] %(message)s'
77+
# job_logging:
78+
# version: 1
79+
# colorlog: True
80+
# root:
81+
# handlers: [console, file_error]
82+
# level: INFO
8583
# handlers:
8684
# console:
8785
# class: logging.StreamHandler
8886
# formatter: simple
8987
# stream: ext://sys.stdout
90-
# file:
91-
# class: logging.handlers.RotatingFileHandler
88+
# file_error:
89+
# class: logging.FileHandler
90+
# level: ERROR
9291
# formatter: simple
93-
# filename: log.log
94-
# maxBytes: 2048
95-
# backupCount: 1
96-
# root:
97-
# handlers: [console]
98-
#
99-
# disable_existing_loggers: false
92+
# filename: err.log
93+
# mode: a
94+
# formatters:
95+
# simple:
96+
# format: '[%(levelname)s][%(name)s] %(message)s'
10097

10198
# sweeper:
10299
## sampler:

cfg/model/dae-aug.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
defaults:
2+
- dae
3+
4+
name: DAE-AUG
5+
augment: True
6+

cfg/system/linear_system.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pred_horizon: 2 # Number (or percentage) of Marko
1717
eval_pred_horizon: 20 # Number (or percentage) of Markov Process state time steps to predict into the future
1818
test_pred_horizon: .5 # Number (or percentage) of Markov Process state time steps to predict into the future
1919
noise_level: 2 # Scale of the Wiener process noise from 0 to 9 (0 is no noise)
20-
n_constraints: 0
20+
n_constraints: 1
2121

2222
# Symmetry parameters
2323
group: C3

cfg/system/mini_cheetah.yaml

+9-9
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,22 @@ name: 'mini_cheetah'
55
subgroup_id: null
66

77
max_epochs: 100
8-
early_stop_epochs: 20
8+
early_stop_epochs: 30
99
standardize: True
1010

11-
obs_state_ratio: 2 # obs_state_dim = obs_state_ratio * state_dim
11+
obs_state_ratio: 5 # obs_state_dim = obs_state_ratio * state_dim
1212

13-
state_obs: ['q_js', 'v_js']
13+
state_obs: ['joint_pos', 'joint_vel', 'base_z', 'base_vel', 'base_ori', 'base_ang_vel']
1414
action_obs: []
1515

1616
# dt = 0.0012 s this is the average delta time between observations.
17-
frames_per_state: 1 # Number of time-frames to use as a Markov Process state time step
18-
pred_horizon: 10 # Number (or percentage) of Markov Process state time steps to predict into the future
19-
eval_pred_horizon: 20 # Number (or percentage) of Markov Process state time steps to predict into the future
20-
test_pred_horizon: 200
17+
frames_per_state: 1 # Number of time-frames to use as a Markov Process state time step
18+
pred_horizon: 30 # Number (or percentage) of Markov Process state time steps to predict into the future
19+
eval_pred_horizon: ${system.pred_horizon} # Number (or percentage) of Markov Process state time steps to predict into the future
20+
test_pred_horizon: 150
2121

22-
dynamic_mode: concrete_galloping
22+
dynamic_mode: forward_minus_0_4
2323

24-
data_path: '${system.name}/recordings/${system.dynamic_mode}'
24+
data_path: '${system.name}/raysim_recordings/flat/${system.dynamic_mode}'
2525

2626
summary: S:${system.dynamic_mode}-OS:${system.obs_state_ratio}-H:${system.pred_horizon}-EH:${system.eval_pred_horizon}

data/DynamicsDataModule.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from torch.utils.data import DataLoader, default_collate
1414
from tqdm import tqdm
1515

16-
from data.DynamicsRecording import DynamicsRecording, get_dynamics_dataset, get_train_test_val_file_paths
16+
from morpho_symm.data.DynamicsRecording import DynamicsRecording, get_dynamics_dataset, get_train_test_val_file_paths
1717
from utils.mysc import traj_from_states
1818
from utils.plotting import plot_system_3D, plot_trajectories, plot_two_panel_trajectories
1919

@@ -220,7 +220,7 @@ def compute_loss_metrics(self, predictions: dict, inputs: dict) -> (torch.Tensor
220220
def train_dataloader(self):
221221
return DataLoader(dataset=self.train_dataset, batch_size=self.batch_size,
222222
num_workers=self.num_workers,
223-
collate_fn=self.collate_fn,
223+
collate_fn=self.collate_fn if not self.augment else self.data_augmentation_collate_fn,
224224
persistent_workers=True if self.num_workers > 0 else False, drop_last=False)
225225

226226
def val_dataloader(self):
@@ -257,6 +257,8 @@ def collate_fn(self, batch_list: list) -> dict:
257257

258258
def data_augmentation_collate_fn(self, batch_list: list) -> dict:
259259
batch = torch.utils.data.default_collate(batch_list)
260+
for k, v in batch.items():
261+
batch[k] = v.to(self.device)
260262
state = batch['state']
261263
next_state = batch['next_state']
262264

@@ -307,7 +309,7 @@ def plot_sample_trajs(self):
307309
assert path_to_data.exists(), f"Invalid Dataset path {path_to_data.absolute()}"
308310

309311
# Find all dynamic systems recordings
310-
path_to_data /= Path('mini_cheetah') / 'recordings' / 'grass'
312+
path_to_data /= Path('mini_cheetah') / 'raysim_recordings' / 'flat' / 'forward_minus_0_4'
311313
# path_to_data = Path('/home/danfoa/Projects/koopman_robotics/data/linear_system/group=C10-dim=10/n_constraints=0/'
312314
# 'f_time_constant=1.5[s]-frames=200-horizon=8.7[s]/noise_level=0')
313315
path_to_dyn_sys_data = set([a.parent for a in list(path_to_data.rglob('*train.pkl'))])
@@ -316,14 +318,14 @@ def plot_sample_trajs(self):
316318

317319
data_module = DynamicsDataModule(data_path=mock_path,
318320
pred_horizon=10,
319-
eval_pred_horizon=200,
320-
test_pred_horizon=300,
321+
eval_pred_horizon=10,
322+
test_pred_horizon=150,
321323
frames_per_step=1,
322324
num_workers=1,
323325
batch_size=1000,
324326
augment=False,
325327
standardize=True,
326-
state_obs=('q_js', 'v_js', 'imu_ang_vel'),
328+
# state_obs=('base_z_error', 'base_vel_error', 'base_ang_vel_error', ),
327329
# action_obs=tuple(),
328330
)
329331

data/DynamicsRecording.py

+57-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
import logging
23
import math
34
import pickle
@@ -35,9 +36,13 @@ class DynamicsRecording:
3536
def save_to_file(self, file_path: Path):
3637
# Store representations and groups without serializing
3738
if len(self.obs_representations) > 0:
38-
self._obs_rep_irreps = {k: rep.irreps for k, rep in self.obs_representations.items()}
39-
self._obs_rep_names = {k: rep.name for k, rep in self.obs_representations.items()}
40-
self._obs_rep_Q = {k: rep.change_of_basis for k, rep in self.obs_representations.items()}
39+
self._obs_rep_irreps = {}
40+
self._obs_rep_names = {}
41+
self._obs_rep_Q = {}
42+
for k, rep in self.obs_representations.items():
43+
self._obs_rep_irreps[k] = rep.irreps if rep is not None else None
44+
self._obs_rep_names[k] = rep.name if rep is not None else None
45+
self._obs_rep_Q[k] = rep.change_of_basis if rep is not None else None
4146
group = self.obs_representations[self.state_obs[0]].group
4247
self._group_keys = group._keys
4348
self._group_name = group.__class__.__name__
@@ -335,7 +340,7 @@ def reduce_dataset_size(recordings: Iterable[DynamicsRecording], train_ratio: fl
335340
return recordings
336341
log.info(f"Reducing dataset size to {train_ratio * 100}%")
337342
# Ensure all training seeds use the same training data partitions
338-
from utils.mysc import TemporaryNumpySeed
343+
from morpho_symm.utils.mysc import TemporaryNumpySeed
339344
with TemporaryNumpySeed(10):
340345
for r in recordings:
341346
# Decide to keep a ratio of the original trajectories
@@ -380,6 +385,54 @@ def reduce_dataset_size(recordings: Iterable[DynamicsRecording], train_ratio: fl
380385
new_recordings = {k: v[idx_to_keep] for k, v in r.recordings.items()}
381386
r.recordings = new_recordings
382387

388+
def split_train_val_test(
389+
dyn_recording: DynamicsRecording, partition_sizes=(0.70, 0.15, 0.15)) -> tuple[DynamicsRecording]:
390+
assert np.isclose(np.sum(partition_sizes), 1.0), f"Invalid partition sizes {partition_sizes}"
391+
partitions_names = ["train", "val", "test"]
392+
393+
log.info(f"Partitioning {dyn_recording.description} into train/val/test of sizes {partition_sizes}[%]")
394+
# Ensure all training seeds use the same training data partitions
395+
from morpho_symm.utils.mysc import TemporaryNumpySeed
396+
with TemporaryNumpySeed(10): # Ensure deterministic behavior
397+
# Decide to keep a ratio of the original trajectories
398+
num_trajs = int(dyn_recording.info['num_traj'])
399+
if num_trajs < 10: # Do not discard entire trajectories, but rather parts of the trajectories
400+
# Take the time horizon from the first observation
401+
sample_obs = dyn_recording.recordings[dyn_recording.state_obs[0]]
402+
if len(sample_obs.shape) == 3: # [traj, time, obs_dim]
403+
time_horizon = sample_obs.shape[1]
404+
elif len(sample_obs.shape) == 2: # [traj, obs_dim]
405+
time_horizon = sample_obs.shape[0]
406+
else:
407+
raise RuntimeError(f"Invalid shape {sample_obs.shape} of {dyn_recording.state_obs[0]}")
408+
409+
num_samples = time_horizon
410+
min_idx = 0
411+
partitions_sample_idx = {partition: None for partition in partitions_names}
412+
for partition_name, ratio in zip(partitions_names, partition_sizes):
413+
max_idx = min_idx + int(num_samples * ratio)
414+
partitions_sample_idx[partition_name] = list(range(min_idx, max_idx))
415+
min_idx = min_idx + int(num_samples * ratio)
416+
417+
# TODO: Avoid deep copying the data itself.
418+
partitions_recordings = {partition: copy.deepcopy(dyn_recording) for partition in partitions_names}
419+
for partition_name, sample_idx in partitions_sample_idx.items():
420+
part_num_samples = len(sample_idx)
421+
partitions_recordings[partition_name].info['trajectory_length'] = part_num_samples
422+
partitions_recordings[partition_name].recordings = dict()
423+
for obs_name in dyn_recording.recordings.keys():
424+
if len(dyn_recording.recordings[obs_name].shape) == 3:
425+
data = dyn_recording.recordings[obs_name][:, sample_idx]
426+
elif len(dyn_recording.recordings[obs_name].shape) == 2:
427+
data = dyn_recording.recordings[obs_name][sample_idx]
428+
else:
429+
raise RuntimeError(f"Invalid shape {dyn_recording.recordings[obs_name].shape} of {obs_name}")
430+
partitions_recordings[partition_name].recordings[obs_name] = data
431+
432+
return partitions_recordings['train'], partitions_recordings['val'], partitions_recordings['test']
433+
else: # Discard entire trajectories
434+
raise NotImplementedError()
435+
383436

384437
def get_dynamics_dataset(train_shards: list[Path],
385438
test_shards: Optional[list[Path]] = None,

data/linear_system/stable_linear_system.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ def evolve_linear_dynamics(A: np.ndarray, init_state: np.ndarray, dt: float, sim
326326
if __name__ == '__main__':
327327
np.set_printoptions(precision=3)
328328
seed_everything(120)
329-
order = 3
329+
order = 5
330330
subgroups_ids = dict(C2=('cone', 1),
331331
Tetrahedral=('fulltetra',),
332332
Octahedral=(True, 'octa',),
@@ -347,7 +347,7 @@ def evolve_linear_dynamics(A: np.ndarray, init_state: np.ndarray, dt: float, sim
347347
# Parameters of the state space.
348348
for n_constraints in [1]: # [0, 1]:
349349
# Define the state representation.
350-
for multiplicity in [2]:
350+
for multiplicity in [20]:
351351
# Generate stable equivariant linear dynamics withing a range of fast and slow dynamics
352352
max_time_constant = 5 # [s] Maximum time constant of the system.
353353
min_period = max_time_constant / 3 # [s] Minimum period of oscillation of the fastest transient mode.

data/mini_cheetah/harmonic_analysis.py

+34-10
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def decom_signal_into_isotypic_components2(signal: np.ndarray, rep: Representati
9999
train_data = train_data[0] # Get the first file path
100100
dyn_recording = DynamicsRecording.load_from_file(train_data)
101101

102-
robot, G = load_symmetric_system(robot_name='mini_cheetah')
102+
robot, G = load_symmetric_system(robot_name='mini_cheetah-c2')
103103
q0 = robot._q0
104104

105105
rep_Qjs = G.representations['Q_js']
@@ -125,18 +125,32 @@ def decom_signal_into_isotypic_components2(signal: np.ndarray, rep: Representati
125125
tau_js_t_iso_signal, tau_js_t_iso_comps = decom_signal_into_isotypic_components(tau_js_t, rep_TqQjs)
126126

127127
# Compute approximate work done by the torques on the system
128-
work_t = v_js_t * tau_js_t
128+
work_t = np.sum(v_js_t * tau_js_t, axis=-1)
129129
work_iso_decomp_t = {} # Decomposition of the work into work done by isotypic components forces.
130130
for irrep_id, v_js_t_iso in v_js_t_iso_signal.items():
131131
tau_js_t_iso = tau_js_t_iso_signal[irrep_id]
132132
work_iso_dims = v_js_t_iso * tau_js_t_iso
133-
work_iso_t = v_js_t_iso * tau_js_t_iso
133+
work_iso_t = np.sum(v_js_t_iso * tau_js_t_iso, axis=-1)
134134
work_iso_decomp_t[irrep_id] = work_iso_t
135135
# Check that computing the work in the original basis is equivalent to computing the work in the isotypic basis
136136
# v_js_t_iso_comp = v_js_t_iso_comps[irrep_id]
137137
# tau_js_t_iso_comp = tau_js_t_iso_comps[irrep_id]
138138
# assert np.allclose(work_iso_t, np.sum(v_js_t_iso_comp * tau_js_t_iso_comp, axis=-1))
139139

140+
work_iso_trajs = np.concatenate([work_iso[None, :, None] for work_iso in work_iso_decomp_t.values()], axis=0)
141+
142+
fig = plot_trajectories(trajs=work_iso_trajs,
143+
# secondary_trajs=work[None, :, None],
144+
main_legend_label="Work_Iso",
145+
colorscale='G10',
146+
plot_error=False)
147+
148+
# fig = plot_trajectories(trajs=work_t[None, :, None],
149+
# main_legend_label="Work",
150+
# colorscale='Set2',
151+
# plot_error=False,
152+
# fig=fig)
153+
140154
# Compute the Kinetic Energy of the system and the kinetic energy of each isotypic component.
141155
from pinocchio import pinocchio_pywrap as pin
142156
kin_energy_iso_t = {}
@@ -161,20 +175,30 @@ def decom_signal_into_isotypic_components2(signal: np.ndarray, rep: Representati
161175
import plotly.express as px
162176
df = px.data.gapminder()
163177

164-
work_iso_trajs = np.concatenate([work_iso[None, :, None] for work_iso in work_iso_decomp_t.values()], axis=0)
165178
kin_energy_iso_trajs = np.concatenate([kin_energy_iso[None, :, None] for kin_energy_iso in kin_energy_iso_t.values()], axis=0)
166-
fig = plot_trajectories(trajs=kin_energy_iso_trajs,
167-
# secondary_trajs=work[None, :, None],
168-
main_legend_label="KinEnergy_Iso",
169-
colorscale='G10',
170-
plot_error=False)
179+
# fig = plot_trajectories(trajs=kin_energy_iso_trajs,
180+
# # secondary_trajs=work[None, :, None],
181+
# main_legend_label="Work_Iso",
182+
# colorscale='G10',
183+
# plot_error=False)
171184

172-
# fig = plot_trajectories(trajs=work_t[None, :600, None],
185+
# fig = plot_trajectories(trajs=work_t,
173186
# main_legend_label="Work",
174187
# colorscale='Set2',
175188
# plot_error=False,
176189
# fig=fig)
177190

191+
# fig = plot_trajectories(trajs=work_iso_trajs,
192+
# # secondary_trajs=work[None, :, None],
193+
# main_legend_label="Work_Iso",
194+
# colorscale='G10',
195+
# plot_error=False)
196+
#
197+
# fig = plot_trajectories(trajs=work_t,
198+
# main_legend_label="Work",
199+
# colorscale='Set2',
200+
# plot_error=False,
201+
# fig=fig)
178202

179203
fig.show()
180204
# Plot the data

data/mini_cheetah/umich_contact_dataset.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def mat_to_dynamics_recordings(data_path: Path,
9191
dynamics_parameters=dict(dt=dt, group=dict(group_name=G.name, group_order=G.order())),
9292
recordings=dict(q_js=q_js_unit_circle_t[None, idx].astype(np.float32),
9393
v_js=v_js_t[None, idx].astype(np.float32),
94-
torques=torques[None, idx].astype(np.float32),
94+
joint_torques=torques[None, idx].astype(np.float32),
9595
# imu_acc=imu_acc[None, idx].astype(np.float32),
9696
imu_ori=imu_ori[None, idx].astype(np.float32),
9797
imu_ang_vel=imu_ang_vel[None, idx].astype(np.float32),
@@ -102,7 +102,7 @@ def mat_to_dynamics_recordings(data_path: Path,
102102
action_obs=('torques',),
103103
obs_representations=dict(q_js=rep_Q_js,
104104
v_js=rep_TqQ_js,
105-
torques=rep_TqQ_js,
105+
joint_torques=rep_TqQ_js,
106106
# imu_acc=rep_imu_acc,
107107
imu_ori=rep_imu_ori,
108108
imu_ang_vel=rep_imu_ang_vel,

launch/dae_obs_state_dim.sh

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/bash
2-
#PBS -l select=1:ncpus=16:mpiprocs=16:ngpus=2
3-
#PBS -l walltime=12:00:00
2+
#PBS -l select=1:ncpus=20:mpiprocs=20:ngpus=2
3+
#PBS -l walltime=24:00:00
44
#PBS -N dae_obs_state_dim
55
66
#PBS -m bea
@@ -10,6 +10,8 @@
1010
cd /work/dordonez/Projects/koopman_robotics
1111
conda activate robotics
1212

13-
python train_observables.py --multirun exp_name=C10-ObsStateDim hydra.launcher.n_jobs=-1 model=dae system.n_constraints=1 system.group=C10 system.state_dim=30 system.obs_state_ratio=1,4 system.train_ratio=1.0 seed=0,1,2,3 device=0 &
14-
python train_observables.py --multirun exp_name=C10-ObsStateDim hydra.launcher.n_jobs=-1 model=dae system.n_constraints=1 system.group=C10 system.state_dim=30 system.obs_state_ratio=2,3 system.train_ratio=1.0 seed=0,1,2,3 device=0 &
13+
#python train_observables.py --multirun seed=0,1,2,3 device=0 exp_name=C10-ObsStateDim2 hydra.launcher.n_jobs=-1 model=dae system.n_constraints=1 system.group=C10 system.state_dim=30 system.obs_state_ratio=1,4 system.train_ratio=1.0 &
14+
#python train_observables.py --multirun seed=0,1,2,3 device=1 exp_name=C10-ObsStateDim2 hydra.launcher.n_jobs=4 model=dae system.n_constraints=1 system.group=C10 system.state_dim=30 system.obs_state_ratio=2,3 system.train_ratio=1.0 &
15+
python train_observables.py --multirun seed=0,1,2,3 device=0 exp_name=C10-ObsStateDim2 hydra.launcher.n_jobs=-1 model=dae system.n_constraints=1 system.group=C5 system.state_dim=30 system.obs_state_ratio=1,4 system.train_ratio=1.0 &
16+
python train_observables.py --multirun seed=0,1,2,3 device=1 exp_name=C10-ObsStateDim2 hydra.launcher.n_jobs=4 model=dae system.n_constraints=1 system.group=C5 system.state_dim=30 system.obs_state_ratio=2,3 system.train_ratio=1.0 &
1517
wait

0 commit comments

Comments
 (0)