iMihayo commited on
Commit
4f267b5
·
verified ·
1 Parent(s): 6a15f50

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. aloha-devel/requirements.txt +23 -0
  2. aloha-devel/robomimic/__init__.py +159 -0
  3. aloha-devel/robomimic/__pycache__/__init__.cpython-38.pyc +0 -0
  4. aloha-devel/robomimic/algo/__pycache__/act.cpython-38.pyc +0 -0
  5. aloha-devel/robomimic/algo/__pycache__/gl.cpython-38.pyc +0 -0
  6. aloha-devel/robomimic/algo/__pycache__/iql.cpython-38.pyc +0 -0
  7. aloha-devel/robomimic/algo/__pycache__/iris.cpython-38.pyc +0 -0
  8. aloha-devel/robomimic/config/__init__.py +14 -0
  9. aloha-devel/robomimic/config/__pycache__/act_config.cpython-38.pyc +0 -0
  10. aloha-devel/robomimic/config/__pycache__/base_config.cpython-38.pyc +0 -0
  11. aloha-devel/robomimic/config/__pycache__/config.cpython-38.pyc +0 -0
  12. aloha-devel/robomimic/config/__pycache__/cql_config.cpython-38.pyc +0 -0
  13. aloha-devel/robomimic/config/__pycache__/td3_bc_config.cpython-38.pyc +0 -0
  14. aloha-devel/robomimic/config/bcq_config.py +83 -0
  15. aloha-devel/robomimic/config/config.py +322 -0
  16. aloha-devel/robomimic/config/hbc_config.py +96 -0
  17. aloha-devel/robomimic/config/iql_config.py +73 -0
  18. aloha-devel/robomimic/envs/__init__.py +0 -0
  19. aloha-devel/robomimic/envs/env_base.py +212 -0
  20. aloha-devel/robomimic/envs/env_gym.py +247 -0
  21. aloha-devel/robomimic/envs/env_ig_momart.py +395 -0
  22. aloha-devel/robomimic/envs/env_robosuite.py +415 -0
  23. aloha-devel/robomimic/envs/wrappers.py +222 -0
  24. aloha-devel/robomimic/exps/templates/act.json +160 -0
  25. aloha-devel/robomimic/exps/templates/bc.json +216 -0
  26. aloha-devel/robomimic/exps/templates/bc_transformer.json +172 -0
  27. aloha-devel/robomimic/exps/templates/cql.json +182 -0
  28. aloha-devel/robomimic/exps/templates/gl.json +182 -0
  29. aloha-devel/robomimic/exps/templates/hbc.json +293 -0
  30. aloha-devel/robomimic/exps/templates/iql.json +192 -0
  31. aloha-devel/robomimic/exps/templates/iris.json +465 -0
  32. aloha-devel/robomimic/exps/templates/td3_bc.json +167 -0
  33. aloha-devel/robomimic/macros.py +27 -0
  34. aloha-devel/robomimic/models/__pycache__/__init__.cpython-38.pyc +0 -0
  35. aloha-devel/robomimic/models/__pycache__/base_nets.cpython-38.pyc +0 -0
  36. aloha-devel/robomimic/models/__pycache__/distributions.cpython-38.pyc +0 -0
  37. aloha-devel/robomimic/models/__pycache__/policy_nets.cpython-38.pyc +0 -0
  38. aloha-devel/robomimic/models/__pycache__/transformers.cpython-38.pyc +0 -0
  39. aloha-devel/robomimic/models/__pycache__/value_nets.cpython-38.pyc +0 -0
  40. aloha-devel/robomimic/models/base_nets.py +1156 -0
  41. aloha-devel/robomimic/models/obs_nets.py +1121 -0
  42. aloha-devel/robomimic/models/value_nets.py +318 -0
  43. aloha-devel/robomimic/utils/__init__.py +0 -0
  44. aloha-devel/robomimic/utils/__pycache__/__init__.cpython-38.pyc +0 -0
  45. aloha-devel/robomimic/utils/__pycache__/tensor_utils.cpython-38.pyc +0 -0
  46. aloha-devel/robomimic/utils/action_utils.py +35 -0
  47. aloha-devel/robomimic/utils/dataset.py +1158 -0
  48. aloha-devel/robomimic/utils/hyperparam_utils.py +373 -0
  49. aloha-devel/robomimic/utils/lang_utils.py +27 -0
  50. aloha-devel/robomimic/utils/loss_utils.py +208 -0
aloha-devel/requirements.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # python=3.9
2
+ # pip==23.0.1
3
+ # pytorch==2.0.0
4
+ # torchvision==0.15.0
5
+ # pytorch-cuda==11.8
6
+ pyquaternion==0.9.9
7
+ pyyaml==6.0
8
+ pexpect==4.8.0
9
+ mujoco==2.3.7
10
+ dm_control==1.0.14
11
+ matplotlib==3.7.5
12
+ einops==0.7.0
13
+ packaging==23.0
14
+ h5py==3.8.0
15
+ ipython==8.12.3
16
+ opencv-python==4.9.0.80
17
+ rospkg==1.5.0
18
+ empy==3.3.4
19
+ catkin-pkg
20
+ diffusers==0.26.3
21
+ termcolor==2.4.0
22
+ imageio==2.34.0
23
+ # cd act/detr && pip install -v -e .
aloha-devel/robomimic/__init__.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __version__ = "0.3.0"
2
+
3
+
4
+ # stores released dataset links and rollout horizons in global dictionary.
5
+ # Structure is given below for each type of dataset:
6
+
7
+ # robosuite / real
8
+ # {
9
+ # task:
10
+ # dataset_type:
11
+ # hdf5_type:
12
+ # url: link
13
+ # horizon: value
14
+ # ...
15
+ # ...
16
+ # ...
17
+ # }
18
+ DATASET_REGISTRY = {}
19
+
20
+ # momart
21
+ # {
22
+ # task:
23
+ # dataset_type:
24
+ # url: link
25
+ # size: value
26
+ # ...
27
+ # ...
28
+ # }
29
+ MOMART_DATASET_REGISTRY = {}
30
+
31
+
32
+ def register_dataset_link(task, dataset_type, hdf5_type, link, horizon):
33
+ """
34
+ Helper function to register dataset link in global dictionary.
35
+ Also takes a @horizon parameter - this corresponds to the evaluation
36
+ rollout horizon that should be used during training.
37
+
38
+ Args:
39
+ task (str): name of task for this dataset
40
+ dataset_type (str): type of dataset (usually identifies the dataset source)
41
+ hdf5_type (str): type of hdf5 - usually one of "raw", "low_dim", or "image",
42
+ to identify the kind of observations in the dataset
43
+ link (str): download link for the dataset
44
+ horizon (int): evaluation rollout horizon that should be used with this dataset
45
+ """
46
+ if task not in DATASET_REGISTRY:
47
+ DATASET_REGISTRY[task] = {}
48
+ if dataset_type not in DATASET_REGISTRY[task]:
49
+ DATASET_REGISTRY[task][dataset_type] = {}
50
+ DATASET_REGISTRY[task][dataset_type][hdf5_type] = dict(url=link, horizon=horizon)
51
+
52
+
53
+ def register_all_links():
54
+ """
55
+ Record all dataset links in this function.
56
+ """
57
+
58
+ # all proficient human datasets
59
+ ph_tasks = ["lift", "can", "square", "transport", "tool_hang", "lift_real", "can_real", "tool_hang_real"]
60
+ ph_horizons = [400, 400, 400, 700, 700, 1000, 1000, 1000]
61
+ for task, horizon in zip(ph_tasks, ph_horizons):
62
+ register_dataset_link(task=task, dataset_type="ph", hdf5_type="raw", horizon=horizon,
63
+ link="http://downloads.cs.stanford.edu/downloads/rt_benchmark/{}/ph/demo{}.hdf5".format(
64
+ task, "" if "real" in task else "_v141"
65
+ )
66
+ )
67
+ # real world datasets only have demo.hdf5 files which already contain all observation modalities
68
+ # while sim datasets store raw low-dim mujoco states in the demo.hdf5
69
+ if "real" not in task:
70
+ register_dataset_link(task=task, dataset_type="ph", hdf5_type="low_dim", horizon=horizon,
71
+ link="http://downloads.cs.stanford.edu/downloads/rt_benchmark/{}/ph/low_dim_v141.hdf5".format(task))
72
+ register_dataset_link(task=task, dataset_type="ph", hdf5_type="image", horizon=horizon,
73
+ link=None)
74
+
75
+ # all multi human datasets
76
+ mh_tasks = ["lift", "can", "square", "transport"]
77
+ mh_horizons = [500, 500, 500, 1100]
78
+ for task, horizon in zip(mh_tasks, mh_horizons):
79
+ register_dataset_link(task=task, dataset_type="mh", hdf5_type="raw", horizon=horizon,
80
+ link="http://downloads.cs.stanford.edu/downloads/rt_benchmark/{}/mh/demo_v141.hdf5".format(task))
81
+ register_dataset_link(task=task, dataset_type="mh", hdf5_type="low_dim", horizon=horizon,
82
+ link="http://downloads.cs.stanford.edu/downloads/rt_benchmark/{}/mh/low_dim_v141.hdf5".format(task))
83
+ register_dataset_link(task=task, dataset_type="mh", hdf5_type="image", horizon=horizon,
84
+ link=None)
85
+
86
+ # all machine generated datasets
87
+ for task, horizon in zip(["lift", "can"], [400, 400]):
88
+ register_dataset_link(task=task, dataset_type="mg", hdf5_type="raw", horizon=horizon,
89
+ link="http://downloads.cs.stanford.edu/downloads/rt_benchmark/{}/mg/demo_v141.hdf5".format(task))
90
+ register_dataset_link(task=task, dataset_type="mg", hdf5_type="low_dim_sparse", horizon=horizon,
91
+ link="http://downloads.cs.stanford.edu/downloads/rt_benchmark/{}/mg/low_dim_sparse_v141.hdf5".format(task))
92
+ register_dataset_link(task=task, dataset_type="mg", hdf5_type="image_sparse", horizon=horizon,
93
+ link="http://downloads.cs.stanford.edu/downloads/rt_benchmark/{}/mg/image_sparse_v141.hdf5".format(task))
94
+ register_dataset_link(task=task, dataset_type="mg", hdf5_type="low_dim_dense", horizon=horizon,
95
+ link="http://downloads.cs.stanford.edu/downloads/rt_benchmark/{}/mg/low_dim_dense_v141.hdf5".format(task))
96
+ register_dataset_link(task=task, dataset_type="mg", hdf5_type="image_dense", horizon=horizon,
97
+ link="http://downloads.cs.stanford.edu/downloads/rt_benchmark/{}/mg/image_dense_v141.hdf5".format(task))
98
+
99
+ # can-paired dataset
100
+ register_dataset_link(task="can", dataset_type="paired", hdf5_type="raw", horizon=400,
101
+ link="http://downloads.cs.stanford.edu/downloads/rt_benchmark/can/paired/demo_v141.hdf5")
102
+ register_dataset_link(task="can", dataset_type="paired", hdf5_type="low_dim", horizon=400,
103
+ link="http://downloads.cs.stanford.edu/downloads/rt_benchmark/can/paired/low_dim_v141.hdf5")
104
+ register_dataset_link(task="can", dataset_type="paired", hdf5_type="image", horizon=400,
105
+ link=None)
106
+
107
+
108
+ def register_momart_dataset_link(task, dataset_type, link, dataset_size):
109
+ """
110
+ Helper function to register dataset link in global dictionary.
111
+ Also takes a @horizon parameter - this corresponds to the evaluation
112
+ rollout horizon that should be used during training.
113
+
114
+ Args:
115
+ task (str): name of task for this dataset
116
+ dataset_type (str): type of dataset (usually identifies the dataset source)
117
+ link (str): download link for the dataset
118
+ dataset_size (float): size of the dataset, in GB
119
+ """
120
+ if task not in MOMART_DATASET_REGISTRY:
121
+ MOMART_DATASET_REGISTRY[task] = {}
122
+ if dataset_type not in MOMART_DATASET_REGISTRY[task]:
123
+ MOMART_DATASET_REGISTRY[task][dataset_type] = {}
124
+ MOMART_DATASET_REGISTRY[task][dataset_type] = dict(url=link, size=dataset_size)
125
+
126
+
127
+ def register_all_momart_links():
128
+ """
129
+ Record all dataset links in this function.
130
+ """
131
+ # all tasks, mapped to their [exp, sub, gen, sam] sizes
132
+ momart_tasks = {
133
+ "table_setup_from_dishwasher": [14, 14, 3.3, 0.6],
134
+ "table_setup_from_dresser": [16, 17, 3.1, 0.7],
135
+ "table_cleanup_to_dishwasher": [23, 36, 5.3, 1.1],
136
+ "table_cleanup_to_sink": [17, 28, 2.9, 0.8],
137
+ "unload_dishwasher": [21, 27, 5.4, 1.0],
138
+ }
139
+
140
+ momart_dataset_types = [
141
+ "expert",
142
+ "suboptimal",
143
+ "generalize",
144
+ "sample",
145
+ ]
146
+
147
+ # Iterate over all combos and register the link
148
+ for task, dataset_sizes in momart_tasks.items():
149
+ for dataset_type, dataset_size in zip(momart_dataset_types, dataset_sizes):
150
+ register_momart_dataset_link(
151
+ task=task,
152
+ dataset_type=dataset_type,
153
+ link=f"http://downloads.cs.stanford.edu/downloads/rt_mm/{dataset_type}/{task}_{dataset_type}.hdf5",
154
+ dataset_size=dataset_size,
155
+ )
156
+
157
+
158
+ register_all_links()
159
+ register_all_momart_links()
aloha-devel/robomimic/__pycache__/__init__.cpython-38.pyc ADDED
Binary file (4.53 kB). View file
 
aloha-devel/robomimic/algo/__pycache__/act.cpython-38.pyc ADDED
Binary file (8.26 kB). View file
 
aloha-devel/robomimic/algo/__pycache__/gl.cpython-38.pyc ADDED
Binary file (20.4 kB). View file
 
aloha-devel/robomimic/algo/__pycache__/iql.cpython-38.pyc ADDED
Binary file (12 kB). View file
 
aloha-devel/robomimic/algo/__pycache__/iris.cpython-38.pyc ADDED
Binary file (5.56 kB). View file
 
aloha-devel/robomimic/config/__init__.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from robomimic.config.config import Config
2
+ from robomimic.config.base_config import config_factory, get_all_registered_configs
3
+
4
+ # note: these imports are needed to register these classes in the global config registry
5
+ from robomimic.config.bc_config import BCConfig
6
+ from robomimic.config.bcq_config import BCQConfig
7
+ from robomimic.config.cql_config import CQLConfig
8
+ from robomimic.config.iql_config import IQLConfig
9
+ from robomimic.config.gl_config import GLConfig
10
+ from robomimic.config.hbc_config import HBCConfig
11
+ from robomimic.config.iris_config import IRISConfig
12
+ from robomimic.config.td3_bc_config import TD3_BCConfig
13
+ from robomimic.config.diffusion_policy_config import DiffusionPolicyConfig
14
+ from robomimic.config.act_config import ACTConfig
aloha-devel/robomimic/config/__pycache__/act_config.cpython-38.pyc ADDED
Binary file (1.86 kB). View file
 
aloha-devel/robomimic/config/__pycache__/base_config.cpython-38.pyc ADDED
Binary file (8.94 kB). View file
 
aloha-devel/robomimic/config/__pycache__/config.cpython-38.pyc ADDED
Binary file (11.3 kB). View file
 
aloha-devel/robomimic/config/__pycache__/cql_config.cpython-38.pyc ADDED
Binary file (2.25 kB). View file
 
aloha-devel/robomimic/config/__pycache__/td3_bc_config.cpython-38.pyc ADDED
Binary file (2.86 kB). View file
 
aloha-devel/robomimic/config/bcq_config.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Config for BCQ algorithm.
3
+ """
4
+
5
+ from robomimic.config.base_config import BaseConfig
6
+ from robomimic.config.bc_config import BCConfig
7
+
8
+
9
+ class BCQConfig(BaseConfig):
10
+ ALGO_NAME = "bcq"
11
+
12
+ def algo_config(self):
13
+ """
14
+ This function populates the `config.algo` attribute of the config, and is given to the
15
+ `Algo` subclass (see `algo/algo.py`) for each algorithm through the `algo_config`
16
+ argument to the constructor. Any parameter that an algorithm needs to determine its
17
+ training and test-time behavior should be populated here.
18
+ """
19
+
20
+ # optimization parameters
21
+ self.algo.optim_params.critic.learning_rate.initial = 1e-3 # critic learning rate
22
+ self.algo.optim_params.critic.learning_rate.decay_factor = 0.1 # factor to decay LR by (if epoch schedule non-empty)
23
+ self.algo.optim_params.critic.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
24
+ self.algo.optim_params.critic.regularization.L2 = 0.00 # L2 regularization strength
25
+ self.algo.optim_params.critic.start_epoch = -1 # number of epochs before starting critic training (-1 means start right away)
26
+ self.algo.optim_params.critic.end_epoch = -1 # number of epochs before ending critic training (-1 means start right away)
27
+
28
+ self.algo.optim_params.action_sampler.learning_rate.initial = 1e-3 # action sampler learning rate
29
+ self.algo.optim_params.action_sampler.learning_rate.decay_factor = 0.1 # factor to decay LR by (if epoch schedule non-empty)
30
+ self.algo.optim_params.action_sampler.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
31
+ self.algo.optim_params.action_sampler.regularization.L2 = 0.00 # L2 regularization strength
32
+ self.algo.optim_params.action_sampler.start_epoch = -1 # number of epochs before starting action sampler training (-1 means start right away)
33
+ self.algo.optim_params.action_sampler.end_epoch = -1 # number of epochs before ending action sampler training (-1 means start right away)
34
+
35
+ self.algo.optim_params.actor.learning_rate.initial = 1e-3 # actor learning rate
36
+ self.algo.optim_params.actor.learning_rate.decay_factor = 0.1 # factor to decay LR by (if epoch schedule non-empty)
37
+ self.algo.optim_params.actor.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
38
+ self.algo.optim_params.actor.regularization.L2 = 0.00 # L2 regularization strength
39
+ self.algo.optim_params.actor.start_epoch = -1 # number of epochs before starting actor training (-1 means start right away)
40
+ self.algo.optim_params.actor.end_epoch = -1 # number of epochs before ending actor training (-1 means start right away)
41
+
42
+ # target network related parameters
43
+ self.algo.discount = 0.99 # discount factor to use
44
+ self.algo.n_step = 1 # for using n-step returns in TD-updates
45
+ self.algo.target_tau = 0.005 # update rate for target networks
46
+ self.algo.infinite_horizon = False # if True, scale terminal rewards by 1 / (1 - discount) to treat as infinite horizon
47
+
48
+ # ================== Critic Network Config ===================
49
+ self.algo.critic.use_huber = False # Huber Loss instead of L2 for critic
50
+ self.algo.critic.max_gradient_norm = None # L2 gradient clipping for critic (None to use no clipping)
51
+ self.algo.critic.value_bounds = None # optional 2-tuple to ensure lower and upper bound on value estimates
52
+ self.algo.critic.num_action_samples = 10 # number of actions to sample per training batch to get target critic value
53
+ self.algo.critic.num_action_samples_rollout = 100 # number of actions to sample per environment step
54
+
55
+ # critic ensemble parameters (TD3 trick)
56
+ self.algo.critic.ensemble.n = 2 # number of Q networks in the ensemble
57
+ self.algo.critic.ensemble.weight = 0.75 # weighting for mixing min and max for target Q value
58
+
59
+ # distributional critic
60
+ self.algo.critic.distributional.enabled = False # train distributional critic (C51)
61
+ self.algo.critic.distributional.num_atoms = 51 # number of values in categorical distribution
62
+
63
+ self.algo.critic.layer_dims = (300, 400) # size of critic MLP
64
+
65
+ # ================== Action Sampler Config ===================
66
+ self.algo.action_sampler = BCConfig().algo
67
+ # use VAE by default
68
+ self.algo.action_sampler.vae.enabled = True
69
+ # remove unused parts of BCConfig algo config
70
+ del self.algo.action_sampler.optim_params # since action sampler optim params specified at top-level
71
+ del self.algo.action_sampler.loss
72
+ del self.algo.action_sampler.gaussian
73
+ del self.algo.action_sampler.rnn
74
+ del self.algo.action_sampler.transformer
75
+
76
+ # Number of epochs before freezing encoder (-1 for no freezing). Only applies to cVAE-based action samplers.
77
+ with self.algo.action_sampler.unlocked():
78
+ self.algo.action_sampler.freeze_encoder_epoch = -1
79
+
80
+ # ================== Actor Network Config ===================
81
+ self.algo.actor.enabled = False # whether to use the actor perturbation network
82
+ self.algo.actor.perturbation_scale = 0.05 # size of learned action perturbations
83
+ self.algo.actor.layer_dims = (300, 400) # size of actor MLP
aloha-devel/robomimic/config/config.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Basic config class - provides a convenient way to work with nested
3
+ dictionaries (by exposing keys as attributes) and to save / load from jsons.
4
+
5
+ Based on addict: https://github.com/mewwts/addict
6
+ """
7
+
8
+ import json
9
+ import copy
10
+ import contextlib
11
+ from copy import deepcopy
12
+
13
+
14
+ class Config(dict):
15
+
16
+ def __init__(__self, *args, **kwargs):
17
+ object.__setattr__(__self, '__key_locked', False) # disallow adding new keys
18
+ object.__setattr__(__self, '__all_locked', False) # disallow both key and value update
19
+ object.__setattr__(__self, '__do_not_lock_keys', False) # cannot be key-locked
20
+ object.__setattr__(__self, '__parent', kwargs.pop('__parent', None))
21
+ object.__setattr__(__self, '__key', kwargs.pop('__key', None))
22
+ for arg in args:
23
+ if not arg:
24
+ continue
25
+ elif isinstance(arg, dict):
26
+ for key, val in arg.items():
27
+ __self[key] = __self._hook(val)
28
+ elif isinstance(arg, tuple) and (not isinstance(arg[0], tuple)):
29
+ __self[arg[0]] = __self._hook(arg[1])
30
+ else:
31
+ for key, val in iter(arg):
32
+ __self[key] = __self._hook(val)
33
+
34
+ for key, val in kwargs.items():
35
+ __self[key] = __self._hook(val)
36
+
37
+ def lock(self):
38
+ """
39
+ Lock the config. Afterwards, new keys cannot be added to the
40
+ config, and the values of existing keys cannot be modified.
41
+ """
42
+ object.__setattr__(self, '__all_locked', True)
43
+ if self.key_lockable:
44
+ object.__setattr__(self, '__key_locked', True)
45
+
46
+ for k in self:
47
+ if isinstance(self[k], Config):
48
+ self[k].lock()
49
+
50
+ def unlock(self):
51
+ """
52
+ Unlock the config. Afterwards, new keys can be added to the
53
+ config, and the values of existing keys can be modified.
54
+ """
55
+ object.__setattr__(self, '__all_locked', False)
56
+ object.__setattr__(self, '__key_locked', False)
57
+
58
+ for k in self:
59
+ if isinstance(self[k], Config):
60
+ self[k].unlock()
61
+
62
+ def _get_lock_state_recursive(self):
63
+ """
64
+ Internal helper function to get the lock state of all sub-configs recursively.
65
+ """
66
+ lock_state = {"__all_locked": self.is_locked, "__key_locked": self.is_key_locked}
67
+ for k in self:
68
+ if isinstance(self[k], Config):
69
+ assert k not in ["__all_locked", "__key_locked"]
70
+ lock_state[k] = self[k]._get_lock_state_recursive()
71
+ return lock_state
72
+
73
+ def _set_lock_state_recursive(self, lock_state):
74
+ """
75
+ Internal helper function to set the lock state of all sub-configs recursively.
76
+ """
77
+ lock_state = deepcopy(lock_state)
78
+ object.__setattr__(self, '__all_locked', lock_state.pop("__all_locked"))
79
+ object.__setattr__(self, '__key_locked', lock_state.pop("__key_locked"))
80
+ for k in lock_state:
81
+ if isinstance(self[k], Config):
82
+ self[k]._set_lock_state_recursive(lock_state[k])
83
+
84
+ def _get_lock_state(self):
85
+ """
86
+ Retrieves the lock state of this config.
87
+
88
+ Returns:
89
+ lock_state (dict): a dictionary with an "all_locked" key that is True
90
+ if both key and value updates are locked and False otherwise, and
91
+ a "key_locked" key that is True if only key updates are locked (value
92
+ updates still allowed) and False otherwise
93
+ """
94
+ return {
95
+ "all_locked": self.is_locked,
96
+ "key_locked": self.is_key_locked
97
+ }
98
+
99
+ def _set_lock_state(self, lock_state):
100
+ """
101
+ Sets the lock state for this config.
102
+
103
+ Args:
104
+ lock_state (dict): a dictionary with an "all_locked" key that is True
105
+ if both key and value updates should be locked and False otherwise, and
106
+ a "key_locked" key that is True if only key updates should be locked (value
107
+ updates still allowed) and False otherwise
108
+ """
109
+ if lock_state["all_locked"]:
110
+ self.lock()
111
+ if lock_state["key_locked"]:
112
+ self.lock_keys()
113
+
114
+ @contextlib.contextmanager
115
+ def unlocked(self):
116
+ """
117
+ A context scope for modifying a Config object. Within the scope,
118
+ both keys and values can be updated. Upon leaving the scope,
119
+ the initial level of locking is restored.
120
+ """
121
+ lock_state = self._get_lock_state()
122
+ self.unlock()
123
+ yield
124
+ self._set_lock_state(lock_state)
125
+
126
+ @contextlib.contextmanager
127
+ def values_unlocked(self):
128
+ """
129
+ A context scope for modifying a Config object. Within the scope,
130
+ only values can be updated (new keys cannot be created). Upon
131
+ leaving the scope, the initial level of locking is restored.
132
+ """
133
+ lock_state = self._get_lock_state()
134
+ self.unlock()
135
+ self.lock_keys()
136
+ yield
137
+ self._set_lock_state(lock_state)
138
+
139
+ def lock_keys(self):
140
+ """
141
+ Lock this config so that new keys cannot be added.
142
+ """
143
+ if not self.key_lockable:
144
+ return
145
+ object.__setattr__(self, '__key_locked', True)
146
+ for k in self:
147
+ if isinstance(self[k], Config):
148
+ self[k].lock_keys()
149
+
150
+ def unlock_keys(self):
151
+ """
152
+ Unlock this config so that new keys can be added.
153
+ """
154
+ object.__setattr__(self, '__key_locked', False)
155
+ for k in self:
156
+ if isinstance(self[k], Config):
157
+ self[k].unlock_keys()
158
+
159
+ @property
160
+ def is_locked(self):
161
+ """
162
+ Returns True if the config is locked (no key or value updates allowed).
163
+ """
164
+ return object.__getattribute__(self, '__all_locked')
165
+
166
+ @property
167
+ def is_key_locked(self):
168
+ """
169
+ Returns True if the config is key-locked (no key updates allowed).
170
+ """
171
+ return object.__getattribute__(self, '__key_locked')
172
+
173
+ def do_not_lock_keys(self):
174
+ """
175
+ Calling this function on this config indicates that key updates should be
176
+ allowed even when this config is key-locked (but not when it is completely
177
+ locked). This is convenient for attributes that contain kwargs, where there
178
+ might be a variable type and number of arguments contained in the sub-config.
179
+ """
180
+ object.__setattr__(self, '__do_not_lock_keys', True)
181
+
182
+ @property
183
+ def key_lockable(self):
184
+ """
185
+ Returns true if this config is key-lockable (new keys cannot be inserted in a
186
+ key-locked lock level).
187
+ """
188
+ return not object.__getattribute__(self, '__do_not_lock_keys')
189
+
190
+ def __setattr__(self, name, value):
191
+ if self.is_locked:
192
+ raise RuntimeError("This config has been locked - cannot set attribute '{}' to {}".format(name, value))
193
+
194
+ if hasattr(Config, name):
195
+ raise AttributeError("'Dict' object attribute "
196
+ "'{0}' is read-only".format(name))
197
+ elif not hasattr(self, name) and self.is_key_locked:
198
+ raise RuntimeError("This config is key-locked - cannot add key '{}'".format(name))
199
+ else:
200
+ self[name] = value
201
+
202
+ def __setitem__(self, name, value):
203
+ super(Config, self).__setitem__(name, value)
204
+ p = object.__getattribute__(self, '__parent')
205
+ key = object.__getattribute__(self, '__key')
206
+ if p is not None:
207
+ p[key] = self
208
+
209
+ def __add__(self, other):
210
+ if not self.keys():
211
+ return other
212
+ else:
213
+ self_type = type(self).__name__
214
+ other_type = type(other).__name__
215
+ msg = "unsupported operand type(s) for +: '{}' and '{}'"
216
+ raise TypeError(msg.format(self_type, other_type))
217
+
218
+ @classmethod
219
+ def _hook(cls, item):
220
+ if isinstance(item, dict):
221
+ # We return Config instance instead of cls instance to ensure all sub-configs are not a top-level class
222
+ return Config(item)
223
+ elif isinstance(item, (list, tuple)):
224
+ return type(item)(Config._hook(elem) for elem in item)
225
+ return item
226
+
227
+ def __getattr__(self, item):
228
+ return self.__getitem__(item)
229
+
230
+ def __repr__(self):
231
+ json_string = json.dumps(self.to_dict(), indent=4)
232
+ return json_string
233
+
234
+ def __getitem__(self, name):
235
+ if name not in self:
236
+ if object.__getattribute__(self, '__all_locked') or object.__getattribute__(self, '__key_locked'):
237
+ raise RuntimeError("This config has been locked and '{}' is not in this config".format(name))
238
+ return Config(__parent=self, __key=name)
239
+ return super(Config, self).__getitem__(name)
240
+
241
+ def __delattr__(self, name):
242
+ del self[name]
243
+
244
+ def to_dict(self):
245
+ base = {}
246
+ for key, value in self.items():
247
+ if isinstance(value, type(self)):
248
+ base[key] = value.to_dict()
249
+ elif isinstance(value, (list, tuple)):
250
+ base[key] = type(value)(
251
+ item.to_dict() if isinstance(item, type(self)) else
252
+ item for item in value)
253
+ else:
254
+ base[key] = value
255
+ return base
256
+
257
+ def copy(self):
258
+ return copy.copy(self)
259
+
260
+ def deepcopy(self):
261
+ return copy.deepcopy(self)
262
+
263
+ def __deepcopy__(self, memo):
264
+ other = self.__class__()
265
+ memo[id(self)] = other
266
+ for key, value in self.items():
267
+ other[copy.deepcopy(key, memo)] = copy.deepcopy(value, memo)
268
+ return other
269
+
270
+ def update(self, *args, **kwargs):
271
+ """
272
+ Update this config using another config or nested dictionary.
273
+ """
274
+ if self.is_locked:
275
+ raise RuntimeError('Cannot update - this config has been locked')
276
+ other = {}
277
+ if args:
278
+ if len(args) > 1:
279
+ raise TypeError()
280
+ other.update(args[0])
281
+ other.update(kwargs)
282
+ for k, v in other.items():
283
+ if self.is_key_locked and k not in self:
284
+ raise RuntimeError("Cannot update - this config has been key-locked and key '{}' does not exist".format(k))
285
+ if (not isinstance(self[k], dict)) or (not isinstance(v, dict)):
286
+ self[k] = v
287
+ else:
288
+ self[k].update(v)
289
+
290
+ def __getnewargs__(self):
291
+ return tuple(self.items())
292
+
293
+ def __getstate__(self):
294
+ return self
295
+
296
+ def __setstate__(self, state):
297
+ self.update(state)
298
+
299
+ def setdefault(self, key, default=None):
300
+ if key in self:
301
+ return self[key]
302
+ else:
303
+ self[key] = default
304
+ return default
305
+
306
+ def dump(self, filename=None):
307
+ """
308
+ Dumps the config to a json.
309
+
310
+ Args:
311
+ filename (str): if not None, save to json file.
312
+
313
+ Returns:
314
+ json_string (str): json string representation of
315
+ this config
316
+ """
317
+ json_string = json.dumps(self.to_dict(), indent=4)
318
+ if filename is not None:
319
+ f = open(filename, "w")
320
+ f.write(json_string)
321
+ f.close()
322
+ return json_string
aloha-devel/robomimic/config/hbc_config.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Config for HBC algorithm.
3
+ """
4
+
5
+ from robomimic.config.base_config import BaseConfig
6
+ from robomimic.config.gl_config import GLConfig
7
+ from robomimic.config.bc_config import BCConfig
8
+
9
+
10
+ class HBCConfig(BaseConfig):
11
+ ALGO_NAME = "hbc"
12
+
13
+ def train_config(self):
14
+ """
15
+ Update from superclass to change default sequence length to load from dataset.
16
+ """
17
+ super(HBCConfig, self).train_config()
18
+ self.train.seq_length = 10 # length of experience sequence to fetch from the buffer
19
+
20
+ def algo_config(self):
21
+ """
22
+ This function populates the `config.algo` attribute of the config, and is given to the
23
+ `Algo` subclass (see `algo/algo.py`) for each algorithm through the `algo_config`
24
+ argument to the constructor. Any parameter that an algorithm needs to determine its
25
+ training and test-time behavior should be populated here.
26
+ """
27
+
28
+ # One of ["separate", "cascade"]. In "separate" mode (default),
29
+ # the planner and actor are trained independently and then the planner subgoal predictions are
30
+ # used to condition the actor at test-time. In "cascade" mode, the actor is trained directly
31
+ # on planner subgoal predictions. In "actor_only" mode, only the actor is trained, and in
32
+ # "planner_only" mode, only the planner is trained.
33
+ self.algo.mode = "separate"
34
+ self.algo.actor_use_random_subgoals = False # whether to sample subgoal index from [1, subgoal_horizon]
35
+ self.algo.subgoal_update_interval = 10 # how frequently the subgoal should be updated at test-time
36
+
37
+
38
+ # ================== Latent Subgoal Config ==================
39
+ self.algo.latent_subgoal.enabled = False # if True, use VAE latent space as subgoals for actor, instead of reconstructions
40
+
41
+ # prior correction trick for actor and value training: instead of using encoder for
42
+ # transforming subgoals to latent subgoals, generate prior samples and choose
43
+ # the closest one to the encoder output
44
+ self.algo.latent_subgoal.prior_correction.enabled = False
45
+ self.algo.latent_subgoal.prior_correction.num_samples = 100
46
+
47
+ # ================== Planner Config ==================
48
+ self.algo.planner = GLConfig().algo # config for goal learning
49
+ # set subgoal horizon explicitly
50
+ self.algo.planner.subgoal_horizon = 10
51
+ # ensure VAE is used
52
+ self.algo.planner.vae.enabled = True
53
+
54
+ # ================== Actor Config ===================
55
+ self.algo.actor = BCConfig().algo
56
+ # use RNN
57
+ self.algo.actor.rnn.enabled = True
58
+ self.algo.actor.rnn.horizon = 10
59
+ # remove unused parts of BCConfig algo config
60
+ del self.algo.actor.gaussian
61
+ del self.algo.actor.gmm
62
+ del self.algo.actor.vae
63
+
64
+ def observation_config(self):
65
+ """
66
+ Update from superclass so that planner and actor each get their own observation config.
67
+ """
68
+ self.observation.planner = GLConfig().observation
69
+ self.observation.actor = BCConfig().observation
70
+
71
+ @property
72
+ def use_goals(self):
73
+ """
74
+ Update from superclass - planner goal modalities determine goal-conditioning
75
+ """
76
+ return len(
77
+ self.observation.planner.modalities.goal.low_dim +
78
+ self.observation.planner.modalities.goal.rgb) > 0
79
+
80
+ @property
81
+ def all_obs_keys(self):
82
+ """
83
+ Update from superclass to include modalities from planner and actor.
84
+ """
85
+ # pool all modalities
86
+ return sorted(tuple(set([
87
+ obs_key for group in [
88
+ self.observation.planner.modalities.obs.values(),
89
+ self.observation.planner.modalities.goal.values(),
90
+ self.observation.planner.modalities.subgoal.values(),
91
+ self.observation.actor.modalities.obs.values(),
92
+ self.observation.actor.modalities.goal.values(),
93
+ ]
94
+ for modality in group
95
+ for obs_key in modality
96
+ ])))
aloha-devel/robomimic/config/iql_config.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Config for IQL algorithm.
3
+ """
4
+
5
+ from robomimic.config.base_config import BaseConfig
6
+
7
+
8
+ class IQLConfig(BaseConfig):
9
+ ALGO_NAME = "iql"
10
+
11
+ def algo_config(self):
12
+ """
13
+ This function populates the `config.algo` attribute of the config, and is given to the
14
+ `Algo` subclass (see `algo/algo.py`) for each algorithm through the `algo_config`
15
+ argument to the constructor. Any parameter that an algorithm needs to determine its
16
+ training and test-time behavior should be populated here.
17
+ """
18
+ super(IQLConfig, self).algo_config()
19
+
20
+ # optimization parameters
21
+ self.algo.optim_params.critic.learning_rate.initial = 1e-4 # critic learning rate
22
+ self.algo.optim_params.critic.learning_rate.decay_factor = 0.0 # factor to decay LR by (if epoch schedule non-empty)
23
+ self.algo.optim_params.critic.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
24
+ self.algo.optim_params.critic.regularization.L2 = 0.00 # L2 regularization strength
25
+
26
+ self.algo.optim_params.vf.learning_rate.initial = 1e-4 # vf learning rate
27
+ self.algo.optim_params.vf.learning_rate.decay_factor = 0.0 # factor to decay LR by (if epoch schedule non-empty)
28
+ self.algo.optim_params.vf.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
29
+ self.algo.optim_params.vf.regularization.L2 = 0.00 # L2 regularization strength
30
+
31
+ self.algo.optim_params.actor.learning_rate.initial = 1e-4 # actor learning rate
32
+ self.algo.optim_params.actor.learning_rate.decay_factor = 0.0 # factor to decay LR by (if epoch schedule non-empty)
33
+ self.algo.optim_params.actor.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
34
+ self.algo.optim_params.actor.regularization.L2 = 0.00 # L2 regularization strength
35
+
36
+ # target network related parameters
37
+ self.algo.discount = 0.99 # discount factor to use
38
+ self.algo.target_tau = 0.01 # update rate for target networks
39
+
40
+ # ================== Actor Network Config ===================
41
+ # Actor network settings
42
+ self.algo.actor.net.type = "gaussian" # Options are currently ["gaussian", "gmm"]
43
+
44
+ # Actor network settings - shared
45
+ self.algo.actor.net.common.std_activation = "softplus" # Activation to use for std output from policy net
46
+ self.algo.actor.net.common.low_noise_eval = True # Whether to use deterministic action sampling at eval stage
47
+ self.algo.actor.net.common.use_tanh = False # Whether to use tanh at output of actor network
48
+
49
+ # Actor network settings - gaussian
50
+ self.algo.actor.net.gaussian.init_last_fc_weight = 0.001 # If set, will override the initialization of the final fc layer to be uniformly sampled limited by this value
51
+ self.algo.actor.net.gaussian.init_std = 0.3 # Relative scaling factor for std from policy net
52
+ self.algo.actor.net.gaussian.fixed_std = False # Whether to learn std dev or not
53
+
54
+ self.algo.actor.net.gmm.num_modes = 5 # number of GMM modes
55
+ self.algo.actor.net.gmm.min_std = 0.0001 # minimum std output from network
56
+
57
+ self.algo.actor.layer_dims = (300, 400) # actor MLP layer dimensions
58
+
59
+ self.algo.actor.max_gradient_norm = None # L2 gradient clipping for actor
60
+
61
+ # ================== Critic Network Config ===================
62
+ # critic ensemble parameters
63
+ self.algo.critic.ensemble.n = 2 # number of Q networks in the ensemble
64
+ self.algo.critic.layer_dims = (300, 400) # critic MLP layer dimensions
65
+ self.algo.critic.use_huber = False # Huber Loss instead of L2 for critic
66
+ self.algo.critic.max_gradient_norm = None # L2 gradient clipping for actor
67
+
68
+ # ================== Adv Config ==============================
69
+ self.algo.adv.clip_adv_value = None # whether to clip raw advantage estimates
70
+ self.algo.adv.beta = 1.0 # temperature for operator
71
+ self.algo.adv.use_final_clip = True # whether to clip final weight calculations
72
+
73
+ self.algo.vf_quantile = 0.9 # quantile factor in quantile regression
aloha-devel/robomimic/envs/__init__.py ADDED
File without changes
aloha-devel/robomimic/envs/env_base.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This file contains the base class for environment wrappers that are used
3
+ to provide a standardized environment API for training policies and interacting
4
+ with metadata present in datasets.
5
+ """
6
+ import abc
7
+
8
+
9
+ class EnvType:
10
+ """
11
+ Holds environment types - one per environment class.
12
+ These act as identifiers for different environments.
13
+ """
14
+ ROBOSUITE_TYPE = 1
15
+ GYM_TYPE = 2
16
+ IG_MOMART_TYPE = 3
17
+
18
+
19
+ class EnvBase(abc.ABC):
20
+ """A base class method for environments used by this repo."""
21
+ @abc.abstractmethod
22
+ def __init__(
23
+ self,
24
+ env_name,
25
+ render=False,
26
+ render_offscreen=False,
27
+ use_image_obs=False,
28
+ postprocess_visual_obs=True,
29
+ **kwargs,
30
+ ):
31
+ """
32
+ Args:
33
+ env_name (str): name of environment. Only needs to be provided if making a different
34
+ environment from the one in @env_meta.
35
+
36
+ render (bool): if True, environment supports on-screen rendering
37
+
38
+ render_offscreen (bool): if True, environment supports off-screen rendering. This
39
+ is forced to be True if @env_meta["use_images"] is True.
40
+
41
+ use_image_obs (bool): if True, environment is expected to render rgb image observations
42
+ on every env.step call. Set this to False for efficiency reasons, if image
43
+ observations are not required.
44
+
45
+ postprocess_visual_obs (bool): if True, postprocess image observations
46
+ to prepare for learning. This should only be False when extracting observations
47
+ for saving to a dataset (to save space on RGB images for example).
48
+ """
49
+ return
50
+
51
+ @abc.abstractmethod
52
+ def step(self, action):
53
+ """
54
+ Step in the environment with an action.
55
+
56
+ Args:
57
+ action (np.array): action to take
58
+
59
+ Returns:
60
+ observation (dict): new observation dictionary
61
+ reward (float): reward for this step
62
+ done (bool): whether the task is done
63
+ info (dict): extra information
64
+ """
65
+ return
66
+
67
+ @abc.abstractmethod
68
+ def reset(self):
69
+ """
70
+ Reset environment.
71
+
72
+ Returns:
73
+ observation (dict): initial observation dictionary.
74
+ """
75
+ return
76
+
77
+ @abc.abstractmethod
78
+ def reset_to(self, state):
79
+ """
80
+ Reset to a specific simulator state.
81
+
82
+ Args:
83
+ state (dict): current simulator state
84
+
85
+ Returns:
86
+ observation (dict): observation dictionary after setting the simulator state
87
+ """
88
+ return
89
+
90
+ @abc.abstractmethod
91
+ def render(self, mode="human", height=None, width=None, camera_name=None):
92
+ """Render"""
93
+ return
94
+
95
+ @abc.abstractmethod
96
+ def get_observation(self):
97
+ """Get environment observation"""
98
+ return
99
+
100
+ @abc.abstractmethod
101
+ def get_state(self):
102
+ """Get environment simulator state, compatible with @reset_to"""
103
+ return
104
+
105
+ @abc.abstractmethod
106
+ def get_reward(self):
107
+ """
108
+ Get current reward.
109
+ """
110
+ return
111
+
112
+ @abc.abstractmethod
113
+ def get_goal(self):
114
+ """
115
+ Get goal observation. Not all environments support this.
116
+ """
117
+ return
118
+
119
+ @abc.abstractmethod
120
+ def set_goal(self, **kwargs):
121
+ """
122
+ Set goal observation with external specification. Not all environments support this.
123
+ """
124
+ return
125
+
126
+ @abc.abstractmethod
127
+ def is_done(self):
128
+ """
129
+ Check if the task is done (not necessarily successful).
130
+ """
131
+ return
132
+
133
+ @abc.abstractmethod
134
+ def is_success(self):
135
+ """
136
+ Check if the task condition(s) is reached. Should return a dictionary
137
+ { str: bool } with at least a "task" key for the overall task success,
138
+ and additional optional keys corresponding to other task criteria.
139
+ """
140
+ return
141
+
142
+ @property
143
+ @abc.abstractmethod
144
+ def action_dimension(self):
145
+ """
146
+ Returns dimension of actions (int).
147
+ """
148
+ return
149
+
150
+ @property
151
+ @abc.abstractmethod
152
+ def name(self):
153
+ """
154
+ Returns name of environment name (str).
155
+ """
156
+ return
157
+
158
+ @property
159
+ @abc.abstractmethod
160
+ def type(self):
161
+ """
162
+ Returns environment type (int) for this kind of environment.
163
+ This helps identify this env class.
164
+ """
165
+ return
166
+
167
+ @property
168
+ def version(self):
169
+ """
170
+ Returns version of environment (str).
171
+ This is not an abstract method, some subclasses do not implement it
172
+ """
173
+ return None
174
+
175
+ @abc.abstractmethod
176
+ def serialize(self):
177
+ """
178
+ Save all information needed to re-instantiate this environment in a dictionary.
179
+ This is the same as @env_meta - environment metadata stored in hdf5 datasets,
180
+ and used in utils/env_utils.py.
181
+ """
182
+ return
183
+
184
+ @classmethod
185
+ @abc.abstractmethod
186
+ def create_for_data_processing(cls, camera_names, camera_height, camera_width, reward_shaping, **kwargs):
187
+ """
188
+ Create environment for processing datasets, which includes extracting
189
+ observations, labeling dense / sparse rewards, and annotating dones in
190
+ transitions.
191
+
192
+ Args:
193
+ camera_names ([str]): list of camera names that correspond to image observations
194
+ camera_height (int): camera height for all cameras
195
+ camera_width (int): camera width for all cameras
196
+ reward_shaping (bool): if True, use shaped environment rewards, else use sparse task completion rewards
197
+
198
+ Returns:
199
+ env (EnvBase instance)
200
+ """
201
+ return
202
+
203
+ @property
204
+ @abc.abstractmethod
205
+ def rollout_exceptions(self):
206
+ """
207
+ Return tuple of exceptions to except when doing rollouts. This is useful to ensure
208
+ that the entire training run doesn't crash because of a bad policy that causes unstable
209
+ simulation computations.
210
+ """
211
+ return
212
+
aloha-devel/robomimic/envs/env_gym.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This file contains the gym environment wrapper that is used
3
+ to provide a standardized environment API for training policies and interacting
4
+ with metadata present in datasets.
5
+ """
6
+ import json
7
+ import numpy as np
8
+ from copy import deepcopy
9
+
10
+ import gym
11
+ try:
12
+ import d4rl
13
+ except:
14
+ print("WARNING: could not load d4rl environments!")
15
+
16
+ import robomimic.envs.env_base as EB
17
+ import robomimic.utils.obs_utils as ObsUtils
18
+
19
+
20
+ class EnvGym(EB.EnvBase):
21
+ """Wrapper class for gym"""
22
+ def __init__(
23
+ self,
24
+ env_name,
25
+ render=False,
26
+ render_offscreen=False,
27
+ use_image_obs=False,
28
+ postprocess_visual_obs=True,
29
+ **kwargs,
30
+ ):
31
+ """
32
+ Args:
33
+ env_name (str): name of environment. Only needs to be provided if making a different
34
+ environment from the one in @env_meta.
35
+
36
+ render (bool): ignored - gym envs always support on-screen rendering
37
+
38
+ render_offscreen (bool): ignored - gym envs always support off-screen rendering
39
+
40
+ use_image_obs (bool): ignored - gym envs don't typically use images
41
+
42
+ postprocess_visual_obs (bool): ignored - gym envs don't typically use images
43
+ """
44
+ self._init_kwargs = deepcopy(kwargs)
45
+ self._env_name = env_name
46
+ self._current_obs = None
47
+ self._current_reward = None
48
+ self._current_done = None
49
+ self._done = None
50
+ self.env = gym.make(env_name, **kwargs)
51
+
52
+ def step(self, action):
53
+ """
54
+ Step in the environment with an action.
55
+
56
+ Args:
57
+ action (np.array): action to take
58
+
59
+ Returns:
60
+ observation (dict): new observation dictionary
61
+ reward (float): reward for this step
62
+ done (bool): whether the task is done
63
+ info (dict): extra information
64
+ """
65
+ obs, reward, done, info = self.env.step(action)
66
+ self._current_obs = obs
67
+ self._current_reward = reward
68
+ self._current_done = done
69
+ return self.get_observation(obs), reward, self.is_done(), info
70
+
71
+ def reset(self):
72
+ """
73
+ Reset environment.
74
+
75
+ Returns:
76
+ observation (dict): initial observation dictionary.
77
+ """
78
+ self._current_obs = self.env.reset()
79
+ self._current_reward = None
80
+ self._current_done = None
81
+ return self.get_observation(self._current_obs)
82
+
83
+ def reset_to(self, state):
84
+ """
85
+ Reset to a specific simulator state.
86
+
87
+ Args:
88
+ state (dict): current simulator state that contains:
89
+ - states (np.ndarray): initial state of the mujoco environment
90
+
91
+ Returns:
92
+ observation (dict): observation dictionary after setting the simulator state
93
+ """
94
+ if hasattr(self.env.unwrapped.sim, "set_state_from_flattened"):
95
+ self.env.unwrapped.sim.set_state_from_flattened(state["states"])
96
+ self.env.unwrapped.sim.forward()
97
+ return { "flat" : self.env.unwrapped._get_obs() }
98
+ else:
99
+ raise NotImplementedError
100
+
101
+ def render(self, mode="human", height=None, width=None, camera_name=None, **kwargs):
102
+ """
103
+ Render from simulation to either an on-screen window or off-screen to RGB array.
104
+
105
+ Args:
106
+ mode (str): pass "human" for on-screen rendering or "rgb_array" for off-screen rendering
107
+ height (int): height of image to render - only used if mode is "rgb_array"
108
+ width (int): width of image to render - only used if mode is "rgb_array"
109
+ """
110
+ if mode =="human":
111
+ return self.env.render(mode=mode, **kwargs)
112
+ if mode == "rgb_array":
113
+ return self.env.render(mode="rgb_array", height=height, width=width)
114
+ else:
115
+ raise NotImplementedError("mode={} is not implemented".format(mode))
116
+
117
+ def get_observation(self, obs=None):
118
+ """
119
+ Get current environment observation dictionary.
120
+
121
+ Args:
122
+ ob (np.array): current flat observation vector to wrap and provide as a dictionary.
123
+ If not provided, uses self._current_obs.
124
+ """
125
+ if obs is None:
126
+ assert self._current_obs is not None
127
+ obs = self._current_obs
128
+ return { "flat" : np.copy(obs) }
129
+
130
+ def get_state(self):
131
+ """
132
+ Get current environment simulator state as a dictionary. Should be compatible with @reset_to.
133
+ """
134
+ # NOTE: assumes MuJoCo gym task!
135
+ xml = self.env.sim.model.get_xml() # model xml file
136
+ state = np.array(self.env.sim.get_state().flatten()) # simulator state
137
+ return dict(model=xml, states=state)
138
+
139
+ def get_reward(self):
140
+ """
141
+ Get current reward.
142
+ """
143
+ assert self._current_reward is not None
144
+ return self._current_reward
145
+
146
+ def get_goal(self):
147
+ """
148
+ Get goal observation. Not all environments support this.
149
+ """
150
+ raise NotImplementedError
151
+
152
+ def set_goal(self, **kwargs):
153
+ """
154
+ Set goal observation with external specification. Not all environments support this.
155
+ """
156
+ raise NotImplementedError
157
+
158
+ def is_done(self):
159
+ """
160
+ Check if the task is done (not necessarily successful).
161
+ """
162
+ assert self._current_done is not None
163
+ return self._current_done
164
+
165
+ def is_success(self):
166
+ """
167
+ Check if the task condition(s) is reached. Should return a dictionary
168
+ { str: bool } with at least a "task" key for the overall task success,
169
+ and additional optional keys corresponding to other task criteria.
170
+ """
171
+ if hasattr(self.env.unwrapped, "_check_success"):
172
+ return self.env.unwrapped._check_success()
173
+
174
+ # gym envs generally don't check task success - we only compare returns
175
+ return { "task" : False }
176
+
177
+ @property
178
+ def action_dimension(self):
179
+ """
180
+ Returns dimension of actions (int).
181
+ """
182
+ return self.env.action_space.shape[0]
183
+
184
+ @property
185
+ def name(self):
186
+ """
187
+ Returns name of environment name (str).
188
+ """
189
+ return self._env_name
190
+
191
+ @property
192
+ def type(self):
193
+ """
194
+ Returns environment type (int) for this kind of environment.
195
+ This helps identify this env class.
196
+ """
197
+ return EB.EnvType.GYM_TYPE
198
+
199
+ def serialize(self):
200
+ """
201
+ Save all information needed to re-instantiate this environment in a dictionary.
202
+ This is the same as @env_meta - environment metadata stored in hdf5 datasets,
203
+ and used in utils/env_utils.py.
204
+ """
205
+ return dict(env_name=self.name, type=self.type, env_kwargs=deepcopy(self._init_kwargs))
206
+
207
+ @classmethod
208
+ def create_for_data_processing(cls, env_name, camera_names, camera_height, camera_width, reward_shaping, **kwargs):
209
+ """
210
+ Create environment for processing datasets, which includes extracting
211
+ observations, labeling dense / sparse rewards, and annotating dones in
212
+ transitions. For gym environments, input arguments (other than @env_name)
213
+ are ignored, since environments are mostly pre-configured.
214
+
215
+ Args:
216
+ env_name (str): name of gym environment to create
217
+
218
+ Returns:
219
+ env (EnvGym instance)
220
+ """
221
+
222
+ # make sure to initialize obs utils so it knows which modalities are image modalities.
223
+ # For currently supported gym tasks, there are no image observations.
224
+ obs_modality_specs = {
225
+ "obs": {
226
+ "low_dim": ["flat"],
227
+ "rgb": [],
228
+ }
229
+ }
230
+ ObsUtils.initialize_obs_utils_with_obs_specs(obs_modality_specs)
231
+
232
+ return cls(env_name=env_name, **kwargs)
233
+
234
+ @property
235
+ def rollout_exceptions(self):
236
+ """
237
+ Return tuple of exceptions to except when doing rollouts. This is useful to ensure
238
+ that the entire training run doesn't crash because of a bad policy that causes unstable
239
+ simulation computations.
240
+ """
241
+ return ()
242
+
243
+ def __repr__(self):
244
+ """
245
+ Pretty-print env description.
246
+ """
247
+ return self.name + "\n" + json.dumps(self._init_kwargs, sort_keys=True, indent=4)
aloha-devel/robomimic/envs/env_ig_momart.py ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Wrapper environment class to enable using iGibson-based environments used in the MOMART paper
3
+ """
4
+
5
+ from copy import deepcopy
6
+ import numpy as np
7
+ import json
8
+
9
+ import pybullet as p
10
+ import gibson2
11
+ from gibson2.envs.semantic_organize_and_fetch import SemanticOrganizeAndFetch
12
+ from gibson2.utils.custom_utils import ObjectConfig
13
+ import gibson2.external.pybullet_tools.utils as PBU
14
+ import tempfile
15
+ import os
16
+ import yaml
17
+ import cv2
18
+
19
+ import robomimic.utils.obs_utils as ObsUtils
20
+ import robomimic.envs.env_base as EB
21
+
22
+
23
+ # TODO: Once iG 2.0 is more stable, automate available environments, similar to robosuite
24
+ ENV_MAPPING = {
25
+ "SemanticOrganizeAndFetch": SemanticOrganizeAndFetch,
26
+ }
27
+
28
+
29
+ class EnvGibsonMOMART(EB.EnvBase):
30
+ """
31
+ Wrapper class for gibson environments (https://github.com/StanfordVL/iGibson) specifically compatible with
32
+ MoMaRT datasets
33
+ """
34
+ def __init__(
35
+ self,
36
+ env_name,
37
+ ig_config,
38
+ postprocess_visual_obs=True,
39
+ render=False,
40
+ render_offscreen=False,
41
+ use_image_obs=False,
42
+ image_height=None,
43
+ image_width=None,
44
+ physics_timestep=1./240.,
45
+ action_timestep=1./20.,
46
+ **kwargs,
47
+ ):
48
+ """
49
+ Args:
50
+ ig_config (dict): YAML configuration to use for iGibson, as a dict
51
+
52
+ postprocess_visual_obs (bool): if True, postprocess image observations
53
+ to prepare for learning
54
+
55
+ render (bool): if True, environment supports on-screen rendering
56
+
57
+ render_offscreen (bool): if True, environment supports off-screen rendering. This
58
+ is forced to be True if @use_image_obs is True.
59
+
60
+ use_image_obs (bool): if True, environment is expected to render rgb image observations
61
+ on every env.step call. Set this to False for efficiency reasons, if image
62
+ observations are not required.
63
+
64
+ render_mode (str): How to run simulation rendering. Options are {"pbgui", "iggui", or "headless"}
65
+
66
+ image_height (int): If specified, overrides internal iG image height when rendering
67
+
68
+ image_width (int): If specified, overrides internal iG image width when rendering
69
+
70
+ physics_timestep (float): Pybullet physics timestep to use
71
+
72
+ action_timestep (float): Action timestep to use for robot in simulation
73
+
74
+ kwargs (unrolled dict): Any args to substitute in the ig_configuration
75
+ """
76
+ self._env_name = env_name
77
+ self.ig_config = deepcopy(ig_config)
78
+ self.postprocess_visual_obs = postprocess_visual_obs
79
+ self._init_kwargs = kwargs
80
+
81
+ # Determine rendering mode
82
+ self.render_mode = "iggui" if render else "headless"
83
+ self.render_onscreen = render
84
+
85
+ # Make sure rgb is part of obs in ig config
86
+ self.ig_config["output"] = list(set(self.ig_config["output"] + ["rgb"]))
87
+
88
+ # Warn user that iG always uses a renderer
89
+ if (not render) and (not render_offscreen):
90
+ print("WARNING: iGibson always uses a renderer -- using headless by default.")
91
+
92
+ # Update ig config
93
+ for k, v in kwargs.items():
94
+ assert k in self.ig_config, f"Got unknown ig configuration key {k}!"
95
+ self.ig_config[k] = v
96
+
97
+ # Set rendering values
98
+ self.obs_img_height = image_height if image_height is not None else self.ig_config.get("obs_image_height", 120)
99
+ self.obs_img_width = image_width if image_width is not None else self.ig_config.get("obs_image_width", 120)
100
+
101
+ # Get class to create
102
+ envClass = ENV_MAPPING.get(self._env_name, None)
103
+
104
+ # Make sure we have a valid environment class
105
+ assert envClass is not None, "No valid environment for the requested task was found!"
106
+
107
+ # Set device idx for rendering
108
+ # ensure that we select the correct GPU device for rendering by testing for EGL rendering
109
+ # NOTE: this package should be installed from this link (https://github.com/StanfordVL/egl_probe)
110
+ import egl_probe
111
+ device_idx = 0
112
+ valid_gpu_devices = egl_probe.get_available_devices()
113
+ if len(valid_gpu_devices) > 0:
114
+ device_idx = valid_gpu_devices[0]
115
+
116
+ # Create environment
117
+ self.env = envClass(
118
+ config_file=deepcopy(self.ig_config),
119
+ mode=self.render_mode,
120
+ physics_timestep=physics_timestep,
121
+ action_timestep=action_timestep,
122
+ device_idx=device_idx,
123
+ )
124
+
125
+ # If we have a viewer, make sure to remove all bodies belonging to the visual markers
126
+ self.exclude_body_ids = [] # Bodies to exclude when saving state
127
+ if self.env.simulator.viewer is not None:
128
+ self.exclude_body_ids.append(self.env.simulator.viewer.constraint_marker.body_id)
129
+ self.exclude_body_ids.append(self.env.simulator.viewer.constraint_marker2.body_id)
130
+
131
+ def step(self, action):
132
+ """
133
+ Step in the environment with an action
134
+
135
+ Args:
136
+ action: action to take
137
+
138
+ Returns:
139
+ observation: new observation
140
+ reward: step reward
141
+ done: whether the task is done
142
+ info: extra information
143
+ """
144
+ obs, r, done, info = self.env.step(action)
145
+ obs = self.get_observation(obs)
146
+ return obs, r, self.is_done(), info
147
+
148
+ def reset(self):
149
+ """Reset environment"""
150
+ di = self.env.reset()
151
+ return self.get_observation(di)
152
+
153
+ def reset_to(self, state):
154
+ """
155
+ Reset to a specific state
156
+ Args:
157
+ state (dict): contains:
158
+ - states (np.ndarray): initial state of the mujoco environment
159
+ - goal (dict): goal components to reset
160
+ Returns:
161
+ new observation
162
+ """
163
+ if "states" in state:
164
+ self.env.reset_to(state["states"], exclude=self.exclude_body_ids)
165
+
166
+ if "goal" in state:
167
+ self.set_goal(**state["goal"])
168
+
169
+ # Return obs
170
+ return self.get_observation()
171
+
172
+ def render(self, mode="human", camera_name="rgb", height=None, width=None):
173
+ """
174
+ Render
175
+
176
+ Args:
177
+ mode (str): Mode(s) to render. Options are either 'human' (rendering onscreen) or 'rgb' (rendering to
178
+ frames offscreen)
179
+ camera_name (str): Name of the camera to use -- valid options are "rgb" or "rgb_wrist"
180
+ height (int): If specified with width, resizes the rendered image to this height
181
+ width (int): If specified with height, resizes the rendered image to this width
182
+
183
+ Returns:
184
+ array or None: If rendering to frame, returns the rendered frame. Otherwise, returns None
185
+ """
186
+ # Only robotview camera is currently supported
187
+ assert camera_name in {"rgb", "rgb_wrist"}, \
188
+ f"Only rgb, rgb_wrist cameras currently supported, got {camera_name}."
189
+
190
+ if mode == "human":
191
+ assert self.render_onscreen, "Rendering has not been enabled for onscreen!"
192
+ self.env.simulator.sync()
193
+ else:
194
+ assert self.env.simulator.renderer is not None, "No renderer enabled for this env!"
195
+
196
+ frame = self.env.sensors["vision"].get_obs(self.env)[camera_name]
197
+
198
+ # Reshape all frames
199
+ if height is not None and width is not None:
200
+ frame = cv2.resize(frame, dsize=(height, width), interpolation=cv2.INTER_CUBIC)
201
+ return frame
202
+
203
+ def resize_obs_frame(self, frame):
204
+ """
205
+ Resizes frame to be internal height and width values
206
+ """
207
+ return cv2.resize(frame, dsize=(self.obs_img_width, self.obs_img_height), interpolation=cv2.INTER_CUBIC)
208
+
209
+ def get_observation(self, di=None):
210
+ """Get environment observation"""
211
+ if di is None:
212
+ di = self.env.get_state()
213
+ ret = {}
214
+ for k in di:
215
+ # RGB Images
216
+ if "rgb" in k:
217
+ ret[k] = di[k]
218
+ # ret[k] = np.transpose(di[k], (2, 0, 1))
219
+ if self.postprocess_visual_obs:
220
+ ret[k] = ObsUtils.process_obs(obs=self.resize_obs_frame(ret[k]), obs_key=k)
221
+
222
+ # Depth images
223
+ elif "depth" in k:
224
+ # ret[k] = np.transpose(di[k], (2, 0, 1))
225
+ # Values can be corrupted (negative or > 1.0, so we clip values)
226
+ ret[k] = np.clip(di[k], 0.0, 1.0)
227
+ if self.postprocess_visual_obs:
228
+ ret[k] = ObsUtils.process_obs(obs=self.resize_obs_frame(ret[k])[..., None], obs_key=k)
229
+
230
+ # Segmentation Images
231
+ elif "seg" in k:
232
+ ret[k] = di[k][..., None]
233
+ if self.postprocess_visual_obs:
234
+ ret[k] = ObsUtils.process_obs(obs=self.resize_obs_frame(ret[k]), obs_key=k)
235
+
236
+ # Scans
237
+ elif "scan" in k:
238
+ ret[k] = np.transpose(np.array(di[k]), axes=(1, 0))
239
+
240
+ # Compose proprio obs
241
+ proprio_obs = di["proprio"]
242
+
243
+ # Compute intermediate values
244
+ lin_vel = np.linalg.norm(proprio_obs["base_lin_vel"][:2])
245
+ ang_vel = proprio_obs["base_ang_vel"][2]
246
+
247
+ ret["proprio"] = np.concatenate([
248
+ proprio_obs["head_joint_pos"],
249
+ proprio_obs["grasped"],
250
+ proprio_obs["eef_pos"],
251
+ proprio_obs["eef_quat"],
252
+ ])
253
+
254
+ # Proprio info that's only relevant for navigation
255
+ ret["proprio_nav"] = np.concatenate([
256
+ [lin_vel],
257
+ [ang_vel],
258
+ ])
259
+
260
+ # Compose task obs
261
+ ret["object"] = np.concatenate([
262
+ np.array(di["task_obs"]["object-state"]),
263
+ ])
264
+
265
+ # Add ground truth navigational state
266
+ ret["gt_nav"] = np.concatenate([
267
+ proprio_obs["base_pos"][:2],
268
+ [np.sin(proprio_obs["base_rpy"][2])],
269
+ [np.cos(proprio_obs["base_rpy"][2])],
270
+ ])
271
+
272
+ return ret
273
+
274
+ def sync_task(self):
275
+ """
276
+ Method to synchronize iG task, since we're not actually resetting the env but instead setting states directly.
277
+ Should only be called after resetting the initial state of an episode
278
+ """
279
+ self.env.task.update_target_object_init_pos()
280
+ self.env.task.update_location_info()
281
+
282
+ def set_task_conditions(self, task_conditions):
283
+ """
284
+ Method to override task conditions (e.g.: target object), useful in cases such as playing back
285
+ from demonstrations
286
+
287
+ Args:
288
+ task_conditions (dict): Keyword-mapped arguments to pass to task instance to set internally
289
+ """
290
+ self.env.set_task_conditions(task_conditions)
291
+
292
+ def get_state(self):
293
+ """Get iG flattened state"""
294
+ return {"states": PBU.WorldSaver(exclude_body_ids=self.exclude_body_ids).serialize()}
295
+
296
+ def get_reward(self):
297
+ return self.env.task.get_reward(self.env)[0]
298
+ # return float(self.is_success()["task"])
299
+
300
+ def get_goal(self):
301
+ """Get goal specification"""
302
+ # No support yet in iG
303
+ raise NotImplementedError
304
+
305
+ def set_goal(self, **kwargs):
306
+ """Set env target with external specification"""
307
+ # No support yet in iG
308
+ raise NotImplementedError
309
+
310
+ def is_done(self):
311
+ """Check if the agent is done (not necessarily successful)."""
312
+ return False
313
+
314
+ def is_success(self):
315
+ """
316
+ Check if the task condition(s) is reached. Should return a dictionary
317
+ { str: bool } with at least a "task" key for the overall task success,
318
+ and additional optional keys corresponding to other task criteria.
319
+ """
320
+ succ = self.env.check_success()
321
+ if isinstance(succ, dict):
322
+ assert "task" in succ
323
+ return succ
324
+ return { "task" : succ }
325
+
326
+ @classmethod
327
+ def create_for_data_processing(
328
+ cls,
329
+ env_name,
330
+ camera_names,
331
+ camera_height,
332
+ camera_width,
333
+ reward_shaping,
334
+ **kwargs,
335
+ ):
336
+ """
337
+ Create environment for processing datasets, which includes extracting
338
+ observations, labeling dense / sparse rewards, and annotating dones in
339
+ transitions.
340
+
341
+ Args:
342
+ env_name (str): name of environment
343
+ camera_names (list of str): list of camera names that correspond to image observations
344
+ camera_height (int): camera height for all cameras
345
+ camera_width (int): camera width for all cameras
346
+ reward_shaping (bool): if True, use shaped environment rewards, else use sparse task completion rewards
347
+ """
348
+ has_camera = (len(camera_names) > 0)
349
+
350
+ # note that @postprocess_visual_obs is False since this env's images will be written to a dataset
351
+ return cls(
352
+ env_name=env_name,
353
+ render=False,
354
+ render_offscreen=has_camera,
355
+ use_image_obs=has_camera,
356
+ postprocess_visual_obs=False,
357
+ image_height=camera_height,
358
+ image_width=camera_width,
359
+ **kwargs,
360
+ )
361
+
362
+ @property
363
+ def action_dimension(self):
364
+ """Action dimension"""
365
+ return self.env.robots[0].action_dim
366
+
367
+ @property
368
+ def name(self):
369
+ """Environment name"""
370
+ return self._env_name
371
+
372
+ @property
373
+ def type(self):
374
+ """Environment type"""
375
+ return EB.EnvType.IG_MOMART_TYPE
376
+
377
+ def serialize(self):
378
+ """Serialize to dictionary"""
379
+ return dict(env_name=self.name, type=self.type,
380
+ ig_config=self.ig_config,
381
+ env_kwargs=deepcopy(self._init_kwargs))
382
+
383
+ @classmethod
384
+ def deserialize(cls, info, postprocess_visual_obs=True):
385
+ """Create environment with external info"""
386
+ return cls(env_name=info["env_name"], ig_config=info["ig_config"], postprocess_visual_obs=postprocess_visual_obs, **info["env_kwargs"])
387
+
388
+ @property
389
+ def rollout_exceptions(self):
390
+ """Return tuple of exceptions to except when doing rollouts"""
391
+ return (RuntimeError)
392
+
393
+ def __repr__(self):
394
+ return self.name + "\n" + json.dumps(self._init_kwargs, sort_keys=True, indent=4) + \
395
+ "\niGibson Config: \n" + json.dumps(self.ig_config, sort_keys=True, indent=4)
aloha-devel/robomimic/envs/env_robosuite.py ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This file contains the robosuite environment wrapper that is used
3
+ to provide a standardized environment API for training policies and interacting
4
+ with metadata present in datasets.
5
+ """
6
+ import json
7
+ import numpy as np
8
+ from copy import deepcopy
9
+
10
+ import robosuite
11
+
12
+ import robomimic.utils.obs_utils as ObsUtils
13
+ import robomimic.utils.lang_utils as LangUtils
14
+ import robomimic.envs.env_base as EB
15
+
16
+
17
+ class EnvRobosuite(EB.EnvBase):
18
+ """Wrapper class for robosuite environments (https://github.com/ARISE-Initiative/robosuite)"""
19
+ def __init__(
20
+ self,
21
+ env_name,
22
+ render=False,
23
+ render_offscreen=False,
24
+ use_image_obs=False,
25
+ postprocess_visual_obs=True,
26
+ lang=None,
27
+ **kwargs,
28
+ ):
29
+ """
30
+ Args:
31
+ env_name (str): name of environment. Only needs to be provided if making a different
32
+ environment from the one in @env_meta.
33
+
34
+ render (bool): if True, environment supports on-screen rendering
35
+
36
+ render_offscreen (bool): if True, environment supports off-screen rendering. This
37
+ is forced to be True if @env_meta["use_images"] is True.
38
+
39
+ use_image_obs (bool): if True, environment is expected to render rgb image observations
40
+ on every env.step call. Set this to False for efficiency reasons, if image
41
+ observations are not required.
42
+
43
+ postprocess_visual_obs (bool): if True, postprocess image observations
44
+ to prepare for learning. This should only be False when extracting observations
45
+ for saving to a dataset (to save space on RGB images for example).
46
+
47
+ lang: TODO add documentation
48
+ """
49
+ self.postprocess_visual_obs = postprocess_visual_obs
50
+
51
+ # robosuite version check
52
+ self._is_v1 = (robosuite.__version__.split(".")[0] == "1")
53
+ if self._is_v1:
54
+ assert (int(robosuite.__version__.split(".")[1]) >= 2), "only support robosuite v0.3 and v1.2+"
55
+
56
+ kwargs = deepcopy(kwargs)
57
+
58
+ # update kwargs based on passed arguments
59
+ update_kwargs = dict(
60
+ has_renderer=render,
61
+ has_offscreen_renderer=(render_offscreen or use_image_obs),
62
+ ignore_done=True,
63
+ use_object_obs=True,
64
+ use_camera_obs=use_image_obs,
65
+ camera_depths=False,
66
+ )
67
+ kwargs.update(update_kwargs)
68
+
69
+ if self._is_v1:
70
+ if kwargs["has_offscreen_renderer"]:
71
+ # ensure that we select the correct GPU device for rendering by testing for EGL rendering
72
+ # NOTE: this package should be installed from this link (https://github.com/StanfordVL/egl_probe)
73
+ import egl_probe
74
+ valid_gpu_devices = egl_probe.get_available_devices()
75
+ if len(valid_gpu_devices) > 0:
76
+ kwargs["render_gpu_device_id"] = valid_gpu_devices[0]
77
+ else:
78
+ # make sure gripper visualization is turned off (we almost always want this for learning)
79
+ kwargs["gripper_visualization"] = False
80
+ del kwargs["camera_depths"]
81
+ kwargs["camera_depth"] = False # rename kwarg
82
+
83
+ self._env_name = env_name
84
+ self._init_kwargs = deepcopy(kwargs)
85
+ self.env = robosuite.make(self._env_name, **kwargs)
86
+ self.base_env = self.env # for mimicgen
87
+ self.lang = lang
88
+ self._lang_emb = LangUtils.get_lang_emb(self.lang)
89
+
90
+ if self._is_v1:
91
+ # Make sure joint position observations and eef vel observations are active
92
+ for ob_name in self.env.observation_names:
93
+ if ("joint_pos" in ob_name) or ("eef_vel" in ob_name):
94
+ self.env.modify_observable(observable_name=ob_name, attribute="active", modifier=True)
95
+
96
+ def step(self, action):
97
+ """
98
+ Step in the environment with an action.
99
+
100
+ Args:
101
+ action (np.array): action to take
102
+
103
+ Returns:
104
+ observation (dict): new observation dictionary
105
+ reward (float): reward for this step
106
+ done (bool): whether the task is done
107
+ info (dict): extra information
108
+ """
109
+ obs, r, done, info = self.env.step(action)
110
+ obs = self.get_observation(obs)
111
+ info["is_success"] = self.is_success()
112
+ return obs, r, self.is_done(), info
113
+
114
+ def reset(self):
115
+ """
116
+ Reset environment.
117
+
118
+ Returns:
119
+ observation (dict): initial observation dictionary.
120
+ """
121
+ di = self.env.reset()
122
+ return self.get_observation(di)
123
+
124
+ #notifies the environment whether or not the next environemnt testing object should update its category
125
+ def update_env(self, attr, value):
126
+ self.env.attr = value
127
+
128
+
129
+ def reset_to(self, state):
130
+ """
131
+ Reset to a specific simulator state.
132
+
133
+ Args:
134
+ state (dict): current simulator state that contains one or more of:
135
+ - states (np.ndarray): initial state of the mujoco environment
136
+ - model (str): mujoco scene xml
137
+
138
+ Returns:
139
+ observation (dict): observation dictionary after setting the simulator state (only
140
+ if "states" is in @state)
141
+ """
142
+ should_ret = False
143
+ if "model" in state:
144
+ if state.get("ep_meta", None) is not None:
145
+ # set relevant episode information
146
+ ep_meta = json.loads(state["ep_meta"])
147
+ self.env.set_attrs_from_ep_meta(ep_meta)
148
+
149
+ # this reset is necessary.
150
+ # while the call to env.reset_from_xml_string does call reset,
151
+ # that is only a "soft" reset that doesn't actually reload the model.
152
+ self.reset()
153
+ robosuite_version_id = int(robosuite.__version__.split(".")[1])
154
+ if robosuite_version_id <= 3:
155
+ from robosuite.utils.mjcf_utils import postprocess_model_xml
156
+ xml = postprocess_model_xml(state["model"])
157
+ else:
158
+ # v1.4 and above use the class-based edit_model_xml function
159
+ xml = self.env.edit_model_xml(state["model"])
160
+ self.env.reset_from_xml_string(xml)
161
+ self.env.sim.reset()
162
+ if not self._is_v1:
163
+ # hide teleop visualization after restoring from model
164
+ self.env.sim.model.site_rgba[self.env.eef_site_id] = np.array([0., 0., 0., 0.])
165
+ self.env.sim.model.site_rgba[self.env.eef_cylinder_id] = np.array([0., 0., 0., 0.])
166
+ if "states" in state:
167
+ self.env.sim.set_state_from_flattened(state["states"])
168
+ self.env.sim.forward()
169
+ should_ret = True
170
+
171
+ if "goal" in state:
172
+ self.set_goal(**state["goal"])
173
+ if should_ret:
174
+ # only return obs if we've done a forward call - otherwise the observations will be garbage
175
+ return self.get_observation()
176
+ return None
177
+
178
+ def render(self, mode="human", height=None, width=None, camera_name=None):
179
+ """
180
+ Render from simulation to either an on-screen window or off-screen to RGB array.
181
+
182
+ Args:
183
+ mode (str): pass "human" for on-screen rendering or "rgb_array" for off-screen rendering
184
+ height (int): height of image to render - only used if mode is "rgb_array"
185
+ width (int): width of image to render - only used if mode is "rgb_array"
186
+ camera_name (str): camera name to use for rendering
187
+ """
188
+ # if camera_name is None, infer from initial env kwargs
189
+ if camera_name is None:
190
+ camera_name = self._init_kwargs.get("camera_names", ["agentview"])[0]
191
+
192
+ if mode == "human":
193
+ cam_id = self.env.sim.model.camera_name2id(camera_name)
194
+ self.env.viewer.set_camera(cam_id)
195
+ return self.env.render()
196
+ elif mode == "rgb_array":
197
+ return self.env.sim.render(height=height, width=width, camera_name=camera_name)[::-1]
198
+ else:
199
+ raise NotImplementedError("mode={} is not implemented".format(mode))
200
+
201
+ def get_observation(self, di=None):
202
+ """
203
+ Get current environment observation dictionary.
204
+
205
+ Args:
206
+ di (dict): current raw observation dictionary from robosuite to wrap and provide
207
+ as a dictionary. If not provided, will be queried from robosuite.
208
+ """
209
+ if di is None:
210
+ di = self.env._get_observations(force_update=True) if self._is_v1 else self.env._get_observation()
211
+ ret = {}
212
+ for k in di:
213
+ if (k in ObsUtils.OBS_KEYS_TO_MODALITIES) and ObsUtils.key_is_obs_modality(key=k, obs_modality="rgb"):
214
+ ret[k] = di[k][::-1]
215
+ if self.postprocess_visual_obs:
216
+ ret[k] = ObsUtils.process_obs(obs=ret[k], obs_key=k)
217
+
218
+ # "object" key contains object information
219
+ ret["object"] = np.array(di["object-state"])
220
+
221
+ if self._is_v1:
222
+ for robot in self.env.robots:
223
+ # add all robot-arm-specific observations. Note the (k not in ret) check
224
+ # ensures that we don't accidentally add robot wrist images a second time
225
+ pf = robot.robot_model.naming_prefix
226
+ for k in di:
227
+ if k.startswith(pf) and (k not in ret) and \
228
+ (not k.endswith("proprio-state")):
229
+ ret[k] = np.array(di[k])
230
+ else:
231
+ # minimal proprioception for older versions of robosuite
232
+ ret["proprio"] = np.array(di["robot-state"])
233
+ ret["eef_pos"] = np.array(di["eef_pos"])
234
+ ret["eef_quat"] = np.array(di["eef_quat"])
235
+ ret["gripper_qpos"] = np.array(di["gripper_qpos"])
236
+
237
+ if self._lang_emb is not None:
238
+ ret["lang_emb"] = np.array(self._lang_emb)
239
+ return ret
240
+
241
+ def get_state(self):
242
+ """
243
+ Get current environment simulator state as a dictionary. Should be compatible with @reset_to.
244
+ """
245
+ xml = self.env.sim.model.get_xml() # model xml file
246
+ state = np.array(self.env.sim.get_state().flatten()) # simulator state
247
+ info = dict(model=xml, states=state)
248
+ if hasattr(self.env, "get_ep_meta"):
249
+ # get ep_meta if applicable
250
+ info["ep_meta"] = json.dumps(self.env.get_ep_meta(), indent=4)
251
+ return info
252
+
253
+ def get_reward(self):
254
+ """
255
+ Get current reward.
256
+ """
257
+ return self.env.reward()
258
+
259
+ def get_goal(self):
260
+ """
261
+ Get goal observation. Not all environments support this.
262
+ """
263
+ return self.get_observation(self.env._get_goal())
264
+
265
+ def set_goal(self, **kwargs):
266
+ """
267
+ Set goal observation with external specification. Not all environments support this.
268
+ """
269
+ return self.env.set_goal(**kwargs)
270
+
271
+ def is_done(self):
272
+ """
273
+ Check if the task is done (not necessarily successful).
274
+ """
275
+
276
+ # Robosuite envs always rollout to fixed horizon.
277
+ return False
278
+
279
+ def is_success(self):
280
+ """
281
+ Check if the task condition(s) is reached. Should return a dictionary
282
+ { str: bool } with at least a "task" key for the overall task success,
283
+ and additional optional keys corresponding to other task criteria.
284
+ """
285
+ succ = self.env._check_success()
286
+ if isinstance(succ, dict):
287
+ assert "task" in succ
288
+ return succ
289
+ return { "task" : succ }
290
+
291
+ @property
292
+ def action_dimension(self):
293
+ """
294
+ Returns dimension of actions (int).
295
+ """
296
+ return self.env.action_spec[0].shape[0]
297
+
298
+ @property
299
+ def name(self):
300
+ """
301
+ Returns name of environment name (str).
302
+ """
303
+ return self._env_name
304
+
305
+ @property
306
+ def type(self):
307
+ """
308
+ Returns environment type (int) for this kind of environment.
309
+ This helps identify this env class.
310
+ """
311
+ return EB.EnvType.ROBOSUITE_TYPE
312
+
313
+ @property
314
+ def version(self):
315
+ """
316
+ Returns version of robosuite used for this environment, eg. 1.2.0
317
+ """
318
+ return robosuite.__version__
319
+
320
+ def serialize(self):
321
+ """
322
+ Save all information needed to re-instantiate this environment in a dictionary.
323
+ This is the same as @env_meta - environment metadata stored in hdf5 datasets,
324
+ and used in utils/env_utils.py.
325
+ """
326
+ return dict(
327
+ env_name=self.name,
328
+ env_version=self.version,
329
+ type=self.type,
330
+ env_kwargs=deepcopy(self._init_kwargs)
331
+ )
332
+
333
+ @classmethod
334
+ def create_for_data_processing(
335
+ cls,
336
+ env_name,
337
+ camera_names,
338
+ camera_height,
339
+ camera_width,
340
+ reward_shaping,
341
+ **kwargs,
342
+ ):
343
+ """
344
+ Create environment for processing datasets, which includes extracting
345
+ observations, labeling dense / sparse rewards, and annotating dones in
346
+ transitions.
347
+
348
+ Args:
349
+ env_name (str): name of environment
350
+ camera_names (list of str): list of camera names that correspond to image observations
351
+ camera_height (int): camera height for all cameras
352
+ camera_width (int): camera width for all cameras
353
+ reward_shaping (bool): if True, use shaped environment rewards, else use sparse task completion rewards
354
+ """
355
+ is_v1 = (robosuite.__version__.split(".")[0] == "1")
356
+ has_camera = (len(camera_names) > 0)
357
+
358
+ new_kwargs = {
359
+ "reward_shaping": reward_shaping,
360
+ }
361
+
362
+ if has_camera:
363
+ if is_v1:
364
+ new_kwargs["camera_names"] = list(camera_names)
365
+ new_kwargs["camera_heights"] = camera_height
366
+ new_kwargs["camera_widths"] = camera_width
367
+ else:
368
+ assert len(camera_names) == 1
369
+ if has_camera:
370
+ new_kwargs["camera_name"] = camera_names[0]
371
+ new_kwargs["camera_height"] = camera_height
372
+ new_kwargs["camera_width"] = camera_width
373
+
374
+ kwargs.update(new_kwargs)
375
+
376
+ # also initialize obs utils so it knows which modalities are image modalities
377
+ image_modalities = list(camera_names)
378
+ if is_v1:
379
+ image_modalities = ["{}_image".format(cn) for cn in camera_names]
380
+ elif has_camera:
381
+ # v0.3 only had support for one image, and it was named "rgb"
382
+ assert len(image_modalities) == 1
383
+ image_modalities = ["rgb"]
384
+ obs_modality_specs = {
385
+ "obs": {
386
+ "low_dim": [], # technically unused, so we don't have to specify all of them
387
+ "rgb": image_modalities,
388
+ }
389
+ }
390
+ ObsUtils.initialize_obs_utils_with_obs_specs(obs_modality_specs)
391
+
392
+ # note that @postprocess_visual_obs is False since this env's images will be written to a dataset
393
+ return cls(
394
+ env_name=env_name,
395
+ render=False,
396
+ render_offscreen=has_camera,
397
+ use_image_obs=has_camera,
398
+ postprocess_visual_obs=False,
399
+ **kwargs,
400
+ )
401
+
402
+ @property
403
+ def rollout_exceptions(self):
404
+ """
405
+ Return tuple of exceptions to except when doing rollouts. This is useful to ensure
406
+ that the entire training run doesn't crash because of a bad policy that causes unstable
407
+ simulation computations.
408
+ """
409
+ return (Exception)
410
+
411
+ def __repr__(self):
412
+ """
413
+ Pretty-print env description.
414
+ """
415
+ return self.name + "\n" + json.dumps(self._init_kwargs, sort_keys=True, indent=4)
aloha-devel/robomimic/envs/wrappers.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ A collection of useful environment wrappers.
3
+ """
4
+ from copy import deepcopy
5
+ import textwrap
6
+ import numpy as np
7
+ from collections import deque
8
+
9
+ import robomimic.envs.env_base as EB
10
+
11
+
12
+ class EnvWrapper(object):
13
+ """
14
+ Base class for all environment wrappers in robomimic.
15
+ """
16
+ def __init__(self, env):
17
+ """
18
+ Args:
19
+ env (EnvBase instance): The environment to wrap.
20
+ """
21
+ assert isinstance(env, EB.EnvBase) or isinstance(env, EnvWrapper)
22
+ self.env = env
23
+
24
+ @classmethod
25
+ def class_name(cls):
26
+ return cls.__name__
27
+
28
+ def _warn_double_wrap(self):
29
+ """
30
+ Utility function that checks if we're accidentally trying to double wrap an env
31
+ Raises:
32
+ Exception: [Double wrapping env]
33
+ """
34
+ env = self.env
35
+ while True:
36
+ if isinstance(env, EnvWrapper):
37
+ if env.class_name() == self.class_name():
38
+ raise Exception(
39
+ "Attempted to double wrap with Wrapper: {}".format(
40
+ self.__class__.__name__
41
+ )
42
+ )
43
+ env = env.env
44
+ else:
45
+ break
46
+
47
+ @property
48
+ def unwrapped(self):
49
+ """
50
+ Grabs unwrapped environment
51
+
52
+ Returns:
53
+ env (EnvBase instance): Unwrapped environment
54
+ """
55
+ if hasattr(self.env, "unwrapped"):
56
+ return self.env.unwrapped
57
+ else:
58
+ return self.env
59
+
60
+ def _to_string(self):
61
+ """
62
+ Subclasses should override this method to print out info about the
63
+ wrapper (such as arguments passed to it).
64
+ """
65
+ return ''
66
+
67
+ def __repr__(self):
68
+ """Pretty print environment."""
69
+ header = '{}'.format(str(self.__class__.__name__))
70
+ msg = ''
71
+ indent = ' ' * 4
72
+ if self._to_string() != '':
73
+ msg += textwrap.indent("\n" + self._to_string(), indent)
74
+ msg += textwrap.indent("\nenv={}".format(self.env), indent)
75
+ msg = header + '(' + msg + '\n)'
76
+ return msg
77
+
78
+ # this method is a fallback option on any methods the original env might support
79
+ def __getattr__(self, attr):
80
+ # using getattr ensures that both __getattribute__ and __getattr__ (fallback) get called
81
+ # (see https://stackoverflow.com/questions/3278077/difference-between-getattr-vs-getattribute)
82
+ orig_attr = getattr(self.env, attr)
83
+ if callable(orig_attr):
84
+
85
+ def hooked(*args, **kwargs):
86
+ result = orig_attr(*args, **kwargs)
87
+ # prevent wrapped_class from becoming unwrapped
88
+ if id(result) == id(self.env):
89
+ return self
90
+ return result
91
+
92
+ return hooked
93
+ else:
94
+ return orig_attr
95
+
96
+
97
+ class FrameStackWrapper(EnvWrapper):
98
+ """
99
+ Wrapper for frame stacking observations during rollouts. The agent
100
+ receives a sequence of past observations instead of a single observation
101
+ when it calls @env.reset, @env.reset_to, or @env.step in the rollout loop.
102
+ """
103
+ def __init__(self, env, num_frames):
104
+ """
105
+ Args:
106
+ env (EnvBase instance): The environment to wrap.
107
+ num_frames (int): number of past observations (including current observation)
108
+ to stack together. Must be greater than 1 (otherwise this wrapper would
109
+ be a no-op).
110
+ """
111
+ assert num_frames > 1, "error: FrameStackWrapper must have num_frames > 1 but got num_frames of {}".format(num_frames)
112
+
113
+ super(FrameStackWrapper, self).__init__(env=env)
114
+ self.num_frames = num_frames
115
+
116
+ ### TODO: add action padding option + adding action to obs to include action history in obs ###
117
+
118
+ # keep track of last @num_frames observations for each obs key
119
+ self.obs_history = None
120
+
121
+ def _get_initial_obs_history(self, init_obs):
122
+ """
123
+ Helper method to get observation history from the initial observation, by
124
+ repeating it.
125
+
126
+ Returns:
127
+ obs_history (dict): a deque for each observation key, with an extra
128
+ leading dimension of 1 for each key (for easy concatenation later)
129
+ """
130
+ obs_history = {}
131
+ for k in init_obs:
132
+ obs_history[k] = deque(
133
+ [init_obs[k][None] for _ in range(self.num_frames)],
134
+ maxlen=self.num_frames,
135
+ )
136
+ return obs_history
137
+
138
+ def _get_stacked_obs_from_history(self):
139
+ """
140
+ Helper method to convert internal variable @self.obs_history to a
141
+ stacked observation where each key is a numpy array with leading dimension
142
+ @self.num_frames.
143
+ """
144
+ # concatenate all frames per key so we return a numpy array per key
145
+ return { k : np.concatenate(self.obs_history[k], axis=0) for k in self.obs_history }
146
+
147
+ def cache_obs_history(self):
148
+ self.obs_history_cache = deepcopy(self.obs_history)
149
+
150
+ def uncache_obs_history(self):
151
+ self.obs_history = self.obs_history_cache
152
+ self.obs_history_cache = None
153
+
154
+ def reset(self):
155
+ """
156
+ Modify to return frame stacked observation which is @self.num_frames copies of
157
+ the initial observation.
158
+
159
+ Returns:
160
+ obs_stacked (dict): each observation key in original observation now has
161
+ leading shape @self.num_frames and consists of the previous @self.num_frames
162
+ observations
163
+ """
164
+ obs = self.env.reset()
165
+ self.timestep = 0 # always zero regardless of timestep type
166
+ self.update_obs(obs, reset=True)
167
+ self.obs_history = self._get_initial_obs_history(init_obs=obs)
168
+ return self._get_stacked_obs_from_history()
169
+
170
+ def reset_to(self, state):
171
+ """
172
+ Modify to return frame stacked observation which is @self.num_frames copies of
173
+ the initial observation.
174
+
175
+ Returns:
176
+ obs_stacked (dict): each observation key in original observation now has
177
+ leading shape @self.num_frames and consists of the previous @self.num_frames
178
+ observations
179
+ """
180
+ obs = self.env.reset_to(state)
181
+ self.timestep = 0 # always zero regardless of timestep type
182
+ self.update_obs(obs, reset=True)
183
+ self.obs_history = self._get_initial_obs_history(init_obs=obs)
184
+ return self._get_stacked_obs_from_history()
185
+
186
+ def step(self, action):
187
+ """
188
+ Modify to update the internal frame history and return frame stacked observation,
189
+ which will have leading dimension @self.num_frames for each key.
190
+
191
+ Args:
192
+ action (np.array): action to take
193
+
194
+ Returns:
195
+ obs_stacked (dict): each observation key in original observation now has
196
+ leading shape @self.num_frames and consists of the previous @self.num_frames
197
+ observations
198
+ reward (float): reward for this step
199
+ done (bool): whether the task is done
200
+ info (dict): extra information
201
+ """
202
+ obs, r, done, info = self.env.step(action)
203
+ self.update_obs(obs, action=action, reset=False)
204
+ # update frame history
205
+ for k in obs:
206
+ # make sure to have leading dim of 1 for easy concatenation
207
+ self.obs_history[k].append(obs[k][None])
208
+ obs_ret = self._get_stacked_obs_from_history()
209
+ return obs_ret, r, done, info
210
+
211
+ def update_obs(self, obs, action=None, reset=False):
212
+ obs["timesteps"] = np.array([self.timestep])
213
+
214
+ if reset:
215
+ obs["actions"] = np.zeros(self.env.action_dimension)
216
+ else:
217
+ self.timestep += 1
218
+ obs["actions"] = action[: self.env.action_dimension]
219
+
220
+ def _to_string(self):
221
+ """Info to pretty print."""
222
+ return "num_frames={}".format(self.num_frames)
aloha-devel/robomimic/exps/templates/act.json ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "algo_name": "act",
3
+ "experiment": {
4
+ "name": "test",
5
+ "validate": false,
6
+ "logging": {
7
+ "terminal_output_to_txt": true,
8
+ "log_tb": true,
9
+ "log_wandb": false,
10
+ "wandb_proj_name": "debug"
11
+ },
12
+ "mse":{},
13
+ "save": {
14
+ "enabled": true,
15
+ "every_n_seconds": null,
16
+ "every_n_epochs": 40,
17
+ "epochs": [],
18
+ "on_best_validation": false,
19
+ "on_best_rollout_return": false,
20
+ "on_best_rollout_success_rate": true
21
+ },
22
+ "epoch_every_n_steps": 500,
23
+ "validation_epoch_every_n_steps": 10,
24
+ "env": null,
25
+ "additional_envs": null,
26
+ "render": false,
27
+ "render_video": true,
28
+ "keep_all_videos": false,
29
+ "video_skip": 5,
30
+ "rollout": {
31
+ "enabled": true,
32
+ "n": 50,
33
+ "horizon": 400,
34
+ "rate": 40,
35
+ "warmstart": 0,
36
+ "terminate_on_success": true
37
+ }
38
+ },
39
+ "train": {
40
+ "data": null,
41
+ "output_dir":"../act_trained_models",
42
+ "num_data_workers": 4,
43
+ "hdf5_cache_mode": "low_dim",
44
+ "hdf5_use_swmr": true,
45
+ "hdf5_load_next_obs": false,
46
+ "hdf5_normalize_obs": false,
47
+ "hdf5_filter_key": null,
48
+ "seq_length": 10,
49
+ "pad_seq_length": true,
50
+ "frame_stack": 1,
51
+ "pad_frame_stack": true,
52
+ "dataset_keys": [
53
+ "actions"
54
+ ],
55
+ "goal_mode": null,
56
+ "cuda": true,
57
+ "batch_size": 128,
58
+ "num_epochs": 10000,
59
+ "seed": 1
60
+ },
61
+ "algo": {
62
+ "optim_params": {
63
+ "policy": {
64
+ "optimizer_type": "adamw",
65
+ "learning_rate": {
66
+ "initial": 0.00005,
67
+ "decay_factor": 1,
68
+ "epoch_schedule": [
69
+ 100
70
+ ],
71
+ "scheduler_type": "linear"
72
+ },
73
+ "regularization": {
74
+ "L2": 0.0001
75
+ }
76
+ }
77
+ },
78
+ "loss": {
79
+ "l2_weight": 0.0,
80
+ "l1_weight": 1.0,
81
+ "cos_weight": 0.0
82
+ },
83
+ "act": {
84
+ "hidden_dim": 512,
85
+ "dim_feedforward": 3200,
86
+ "backbone": "resnet18",
87
+ "enc_layers": 4,
88
+ "dec_layers": 7,
89
+ "nheads": 8,
90
+ "latent_dim": 32,
91
+ "kl_weight": 20
92
+ }
93
+ },
94
+ "observation": {
95
+ "modalities": {
96
+ "obs": {
97
+ "low_dim": [
98
+ "robot0_eef_pos",
99
+ "robot0_eef_quat",
100
+ "robot0_gripper_qpos",
101
+ "object"
102
+ ],
103
+ "rgb": [],
104
+ "depth": [],
105
+ "scan": []
106
+ },
107
+ "goal": {
108
+ "low_dim": [],
109
+ "rgb": [],
110
+ "depth": [],
111
+ "scan": []
112
+ }
113
+ },
114
+ "encoder": {
115
+ "low_dim": {
116
+ "core_class": null,
117
+ "core_kwargs": {},
118
+ "obs_randomizer_class": null,
119
+ "obs_randomizer_kwargs": {}
120
+ },
121
+ "rgb": {
122
+ "core_class": "VisualCore",
123
+ "core_kwargs": {
124
+ "feature_dimension": 64,
125
+ "backbone_class": "ResNet18Conv",
126
+ "backbone_kwargs": {
127
+ "pretrained": false,
128
+ "input_coord_conv": false
129
+ },
130
+ "pool_class": "SpatialSoftmax",
131
+ "pool_kwargs": {
132
+ "num_kp": 32,
133
+ "learnable_temperature": false,
134
+ "temperature": 1.0,
135
+ "noise_std": 0.0
136
+ }
137
+ },
138
+ "obs_randomizer_class": "CropRandomizer",
139
+ "obs_randomizer_kwargs": {
140
+ "crop_height": 76,
141
+ "crop_width": 76,
142
+ "num_crops": 1,
143
+ "pos_enc": false
144
+ }
145
+ },
146
+ "depth": {
147
+ "core_class": "VisualCore",
148
+ "core_kwargs": {},
149
+ "obs_randomizer_class": null,
150
+ "obs_randomizer_kwargs": {}
151
+ },
152
+ "scan": {
153
+ "core_class": "ScanCore",
154
+ "core_kwargs": {},
155
+ "obs_randomizer_class": null,
156
+ "obs_randomizer_kwargs": {}
157
+ }
158
+ }
159
+ }
160
+ }
aloha-devel/robomimic/exps/templates/bc.json ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "algo_name": "bc",
3
+ "experiment": {
4
+ "name": "test",
5
+ "validate": false,
6
+ "logging": {
7
+ "terminal_output_to_txt": true,
8
+ "log_tb": true,
9
+ "log_wandb": false,
10
+ "wandb_proj_name": "debug"
11
+ },
12
+ "mse":{},
13
+ "save": {
14
+ "enabled": true,
15
+ "every_n_seconds": null,
16
+ "every_n_epochs": 50,
17
+ "epochs": [],
18
+ "on_best_validation": false,
19
+ "on_best_rollout_return": false,
20
+ "on_best_rollout_success_rate": true
21
+ },
22
+ "epoch_every_n_steps": 100,
23
+ "validation_epoch_every_n_steps": 10,
24
+ "env": null,
25
+ "additional_envs": null,
26
+ "render": false,
27
+ "render_video": true,
28
+ "keep_all_videos": false,
29
+ "video_skip": 5,
30
+ "rollout": {
31
+ "enabled": true,
32
+ "n": 50,
33
+ "horizon": 400,
34
+ "rate": 50,
35
+ "warmstart": 0,
36
+ "terminate_on_success": true
37
+ }
38
+ },
39
+ "train": {
40
+ "data": null,
41
+ "output_dir": "../bc_trained_models",
42
+ "num_data_workers": 0,
43
+ "hdf5_cache_mode": "all",
44
+ "hdf5_use_swmr": true,
45
+ "hdf5_load_next_obs": false,
46
+ "hdf5_normalize_obs": false,
47
+ "hdf5_filter_key": null,
48
+ "hdf5_validation_filter_key": null,
49
+ "seq_length": 1,
50
+ "pad_seq_length": true,
51
+ "frame_stack": 1,
52
+ "pad_frame_stack": true,
53
+ "dataset_keys": [
54
+ "actions",
55
+ "rewards",
56
+ "dones"
57
+ ],
58
+ "goal_mode": null,
59
+ "cuda": true,
60
+ "batch_size": 100,
61
+ "num_epochs": 2000,
62
+ "seed": 1
63
+ },
64
+ "algo": {
65
+ "optim_params": {
66
+ "policy": {
67
+ "optimizer_type": "adam",
68
+ "learning_rate": {
69
+ "initial": 0.0001,
70
+ "decay_factor": 0.1,
71
+ "epoch_schedule": [],
72
+ "scheduler_type": "multistep"
73
+ },
74
+ "regularization": {
75
+ "L2": 0.0
76
+ }
77
+ }
78
+ },
79
+ "loss": {
80
+ "l2_weight": 1.0,
81
+ "l1_weight": 0.0,
82
+ "cos_weight": 0.0
83
+ },
84
+ "actor_layer_dims": [
85
+ 1024,
86
+ 1024
87
+ ],
88
+ "gaussian": {
89
+ "enabled": false,
90
+ "fixed_std": false,
91
+ "init_std": 0.1,
92
+ "min_std": 0.01,
93
+ "std_activation": "softplus",
94
+ "low_noise_eval": true
95
+ },
96
+ "gmm": {
97
+ "enabled": false,
98
+ "num_modes": 5,
99
+ "min_std": 0.0001,
100
+ "std_activation": "softplus",
101
+ "low_noise_eval": true
102
+ },
103
+ "vae": {
104
+ "enabled": false,
105
+ "latent_dim": 14,
106
+ "latent_clip": null,
107
+ "kl_weight": 1.0,
108
+ "decoder": {
109
+ "is_conditioned": true,
110
+ "reconstruction_sum_across_elements": false
111
+ },
112
+ "prior": {
113
+ "learn": false,
114
+ "is_conditioned": false,
115
+ "use_gmm": false,
116
+ "gmm_num_modes": 10,
117
+ "gmm_learn_weights": false,
118
+ "use_categorical": false,
119
+ "categorical_dim": 10,
120
+ "categorical_gumbel_softmax_hard": false,
121
+ "categorical_init_temp": 1.0,
122
+ "categorical_temp_anneal_step": 0.001,
123
+ "categorical_min_temp": 0.3
124
+ },
125
+ "encoder_layer_dims": [
126
+ 300,
127
+ 400
128
+ ],
129
+ "decoder_layer_dims": [
130
+ 300,
131
+ 400
132
+ ],
133
+ "prior_layer_dims": [
134
+ 300,
135
+ 400
136
+ ]
137
+ },
138
+ "rnn": {
139
+ "enabled": false,
140
+ "horizon": 10,
141
+ "hidden_dim": 400,
142
+ "rnn_type": "LSTM",
143
+ "num_layers": 2,
144
+ "open_loop": false,
145
+ "kwargs": {
146
+ "bidirectional": false
147
+ }
148
+ },
149
+ "transformer": {
150
+ "enabled": false,
151
+ "context_length": 10,
152
+ "embed_dim": 512,
153
+ "num_layers": 6,
154
+ "num_heads": 8,
155
+ "emb_dropout": 0.1,
156
+ "attn_dropout": 0.1,
157
+ "block_output_dropout": 0.1,
158
+ "sinusoidal_embedding": false,
159
+ "activation": "gelu",
160
+ "supervise_all_steps": false,
161
+ "nn_parameter_for_timesteps": true
162
+ }
163
+ },
164
+ "observation": {
165
+ "modalities": {
166
+ "obs": {
167
+ "low_dim": [
168
+ "robot0_eef_pos",
169
+ "robot0_eef_quat",
170
+ "robot0_gripper_qpos",
171
+ "object"
172
+ ],
173
+ "rgb": [],
174
+ "depth": [],
175
+ "scan": []
176
+ },
177
+ "goal": {
178
+ "low_dim": [],
179
+ "rgb": [],
180
+ "depth": [],
181
+ "scan": []
182
+ }
183
+ },
184
+ "encoder": {
185
+ "low_dim": {
186
+ "core_class": null,
187
+ "core_kwargs": {},
188
+ "obs_randomizer_class": null,
189
+ "obs_randomizer_kwargs": {}
190
+ },
191
+ "rgb": {
192
+ "core_class": "VisualCore",
193
+ "core_kwargs": {},
194
+ "obs_randomizer_class": null,
195
+ "obs_randomizer_kwargs": {}
196
+ },
197
+ "depth": {
198
+ "core_class": "VisualCore",
199
+ "core_kwargs": {},
200
+ "obs_randomizer_class": null,
201
+ "obs_randomizer_kwargs": {}
202
+ },
203
+ "scan": {
204
+ "core_class": "ScanCore",
205
+ "core_kwargs": {},
206
+ "obs_randomizer_class": null,
207
+ "obs_randomizer_kwargs": {}
208
+ }
209
+ }
210
+ },
211
+ "meta": {
212
+ "hp_base_config_file": null,
213
+ "hp_keys": [],
214
+ "hp_values": []
215
+ }
216
+ }
aloha-devel/robomimic/exps/templates/bc_transformer.json ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "algo_name": "bc",
3
+ "experiment": {
4
+ "name": "test",
5
+ "validate": true,
6
+ "logging": {
7
+ "terminal_output_to_txt": true,
8
+ "log_tb": true,
9
+ "log_wandb": false,
10
+ "wandb_proj_name": "debug"
11
+ },
12
+ "mse":{},
13
+ "save": {
14
+ "enabled": true,
15
+ "every_n_seconds": null,
16
+ "every_n_epochs": 50,
17
+ "epochs": [],
18
+ "on_best_validation": false,
19
+ "on_best_rollout_return": false,
20
+ "on_best_rollout_success_rate": true
21
+ },
22
+ "epoch_every_n_steps": 100,
23
+ "validation_epoch_every_n_steps": 10,
24
+ "env": null,
25
+ "additional_envs": null,
26
+ "render": false,
27
+ "render_video": true,
28
+ "keep_all_videos": false,
29
+ "video_skip": 5,
30
+ "rollout": {
31
+ "enabled": true,
32
+ "n": 50,
33
+ "horizon": 400,
34
+ "rate": 50,
35
+ "warmstart": 0,
36
+ "terminate_on_success": true
37
+ }
38
+ },
39
+ "train": {
40
+ "data": null,
41
+ "output_dir": "../bc_transformer_trained_models",
42
+ "num_data_workers": 0,
43
+ "hdf5_cache_mode": "low_dim",
44
+ "hdf5_use_swmr": true,
45
+ "hdf5_load_next_obs": false,
46
+ "hdf5_normalize_obs": false,
47
+ "hdf5_filter_key": null,
48
+ "seq_length": 1,
49
+ "pad_seq_length": true,
50
+ "frame_stack": 10,
51
+ "pad_frame_stack": true,
52
+ "dataset_keys": [
53
+ "actions"
54
+ ],
55
+ "goal_mode": null,
56
+ "cuda": true,
57
+ "batch_size": 100,
58
+ "num_epochs": 2000,
59
+ "seed": 1
60
+ },
61
+ "algo": {
62
+ "optim_params": {
63
+ "policy": {
64
+ "optimizer_type": "adamw",
65
+ "learning_rate": {
66
+ "initial": 0.0001,
67
+ "decay_factor": 0.1,
68
+ "epoch_schedule": [100],
69
+ "scheduler_type": "linear"
70
+ },
71
+ "regularization": {
72
+ "L2": 0.01
73
+ }
74
+ }
75
+ },
76
+ "loss": {
77
+ "l2_weight": 1.0,
78
+ "l1_weight": 0.0,
79
+ "cos_weight": 0.0
80
+ },
81
+ "actor_layer_dims": [],
82
+ "gaussian": {
83
+ "enabled": false
84
+ },
85
+ "gmm": {
86
+ "enabled": true,
87
+ "num_modes": 5,
88
+ "min_std": 0.0001,
89
+ "std_activation": "softplus",
90
+ "low_noise_eval": true
91
+ },
92
+ "vae": {
93
+ "enabled": false
94
+ },
95
+ "rnn": {
96
+ "enabled": false
97
+ },
98
+ "transformer": {
99
+ "enabled": true,
100
+ "supervise_all_steps": false,
101
+ "num_layers": 6,
102
+ "embed_dim": 512,
103
+ "num_heads": 8
104
+ }
105
+ },
106
+ "observation": {
107
+ "modalities": {
108
+ "obs": {
109
+ "low_dim": [
110
+ "robot0_eef_pos",
111
+ "robot0_eef_quat",
112
+ "robot0_gripper_qpos",
113
+ "object"
114
+ ],
115
+ "rgb": [],
116
+ "depth": [],
117
+ "scan": []
118
+ },
119
+ "goal": {
120
+ "low_dim": [],
121
+ "rgb": [],
122
+ "depth": [],
123
+ "scan": []
124
+ }
125
+ },
126
+ "encoder": {
127
+ "low_dim": {
128
+ "core_class": null,
129
+ "core_kwargs": {},
130
+ "obs_randomizer_class": null,
131
+ "obs_randomizer_kwargs": {}
132
+ },
133
+ "rgb": {
134
+ "core_class": "VisualCore",
135
+ "core_kwargs": {
136
+ "feature_dimension": 64,
137
+ "backbone_class": "ResNet18Conv",
138
+ "backbone_kwargs": {
139
+ "pretrained": false,
140
+ "input_coord_conv": false
141
+ },
142
+ "pool_class": "SpatialSoftmax",
143
+ "pool_kwargs": {
144
+ "num_kp": 32,
145
+ "learnable_temperature": false,
146
+ "temperature": 1.0,
147
+ "noise_std": 0.0
148
+ }
149
+ },
150
+ "obs_randomizer_class": "CropRandomizer",
151
+ "obs_randomizer_kwargs": {
152
+ "crop_height": 76,
153
+ "crop_width": 76,
154
+ "num_crops": 1,
155
+ "pos_enc": false
156
+ }
157
+ },
158
+ "depth": {
159
+ "core_class": "VisualCore",
160
+ "core_kwargs": {},
161
+ "obs_randomizer_class": null,
162
+ "obs_randomizer_kwargs": {}
163
+ },
164
+ "scan": {
165
+ "core_class": "ScanCore",
166
+ "core_kwargs": {},
167
+ "obs_randomizer_class": null,
168
+ "obs_randomizer_kwargs": {}
169
+ }
170
+ }
171
+ }
172
+ }
aloha-devel/robomimic/exps/templates/cql.json ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "algo_name": "cql",
3
+ "experiment": {
4
+ "name": "test",
5
+ "validate": false,
6
+ "logging": {
7
+ "terminal_output_to_txt": true,
8
+ "log_tb": true,
9
+ "log_wandb": false,
10
+ "wandb_proj_name": "debug"
11
+ },
12
+ "save": {
13
+ "enabled": true,
14
+ "every_n_seconds": null,
15
+ "every_n_epochs": 50,
16
+ "epochs": [],
17
+ "on_best_validation": false,
18
+ "on_best_rollout_return": false,
19
+ "on_best_rollout_success_rate": true
20
+ },
21
+ "epoch_every_n_steps": 100,
22
+ "validation_epoch_every_n_steps": 10,
23
+ "env": null,
24
+ "additional_envs": null,
25
+ "render": false,
26
+ "render_video": true,
27
+ "keep_all_videos": false,
28
+ "video_skip": 5,
29
+ "rollout": {
30
+ "enabled": true,
31
+ "n": 50,
32
+ "horizon": 400,
33
+ "rate": 50,
34
+ "warmstart": 0,
35
+ "terminate_on_success": true
36
+ }
37
+ },
38
+ "train": {
39
+ "data": null,
40
+ "output_dir": "../cql_trained_models",
41
+ "num_data_workers": 0,
42
+ "hdf5_cache_mode": "all",
43
+ "hdf5_use_swmr": true,
44
+ "hdf5_load_next_obs": true,
45
+ "hdf5_normalize_obs": false,
46
+ "hdf5_filter_key": null,
47
+ "hdf5_validation_filter_key": null,
48
+ "seq_length": 1,
49
+ "pad_seq_length": true,
50
+ "frame_stack": 1,
51
+ "pad_frame_stack": true,
52
+ "dataset_keys": [
53
+ "actions",
54
+ "rewards",
55
+ "dones"
56
+ ],
57
+ "goal_mode": null,
58
+ "cuda": true,
59
+ "batch_size": 1024,
60
+ "num_epochs": 2000,
61
+ "seed": 1
62
+ },
63
+ "algo": {
64
+ "optim_params": {
65
+ "critic": {
66
+ "learning_rate": {
67
+ "initial": 0.001,
68
+ "decay_factor": 0.0,
69
+ "epoch_schedule": []
70
+ },
71
+ "regularization": {
72
+ "L2": 0.0
73
+ }
74
+ },
75
+ "actor": {
76
+ "learning_rate": {
77
+ "initial": 0.0003,
78
+ "decay_factor": 0.0,
79
+ "epoch_schedule": []
80
+ },
81
+ "regularization": {
82
+ "L2": 0.0
83
+ }
84
+ }
85
+ },
86
+ "discount": 0.99,
87
+ "n_step": 1,
88
+ "target_tau": 0.005,
89
+ "actor": {
90
+ "bc_start_steps": 0,
91
+ "target_entropy": "default",
92
+ "max_gradient_norm": null,
93
+ "net": {
94
+ "type": "gaussian",
95
+ "common": {
96
+ "std_activation": "exp",
97
+ "use_tanh": true,
98
+ "low_noise_eval": true
99
+ },
100
+ "gaussian": {
101
+ "init_last_fc_weight": 0.001,
102
+ "init_std": 0.3,
103
+ "fixed_std": false
104
+ }
105
+ },
106
+ "layer_dims": [
107
+ 300,
108
+ 400
109
+ ]
110
+ },
111
+ "critic": {
112
+ "use_huber": false,
113
+ "max_gradient_norm": null,
114
+ "value_bounds": null,
115
+ "num_action_samples": 1,
116
+ "cql_weight": 1.0,
117
+ "deterministic_backup": true,
118
+ "min_q_weight": 1.0,
119
+ "target_q_gap": 5.0,
120
+ "num_random_actions": 10,
121
+ "ensemble": {
122
+ "n": 2
123
+ },
124
+ "layer_dims": [
125
+ 300,
126
+ 400
127
+ ]
128
+ }
129
+ },
130
+ "observation": {
131
+ "modalities": {
132
+ "obs": {
133
+ "low_dim": [
134
+ "robot0_eef_pos",
135
+ "robot0_eef_quat",
136
+ "robot0_gripper_qpos",
137
+ "object"
138
+ ],
139
+ "rgb": [],
140
+ "depth": [],
141
+ "scan": []
142
+ },
143
+ "goal": {
144
+ "low_dim": [],
145
+ "rgb": [],
146
+ "depth": [],
147
+ "scan": []
148
+ }
149
+ },
150
+ "encoder": {
151
+ "low_dim": {
152
+ "core_class": null,
153
+ "core_kwargs": {},
154
+ "obs_randomizer_class": null,
155
+ "obs_randomizer_kwargs": {}
156
+ },
157
+ "rgb": {
158
+ "core_class": "VisualCore",
159
+ "core_kwargs": {},
160
+ "obs_randomizer_class": null,
161
+ "obs_randomizer_kwargs": {}
162
+ },
163
+ "depth": {
164
+ "core_class": "VisualCore",
165
+ "core_kwargs": {},
166
+ "obs_randomizer_class": null,
167
+ "obs_randomizer_kwargs": {}
168
+ },
169
+ "scan": {
170
+ "core_class": "ScanCore",
171
+ "core_kwargs": {},
172
+ "obs_randomizer_class": null,
173
+ "obs_randomizer_kwargs": {}
174
+ }
175
+ }
176
+ },
177
+ "meta": {
178
+ "hp_base_config_file": null,
179
+ "hp_keys": [],
180
+ "hp_values": []
181
+ }
182
+ }
aloha-devel/robomimic/exps/templates/gl.json ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "algo_name": "gl",
3
+ "experiment": {
4
+ "name": "test",
5
+ "validate": false,
6
+ "logging": {
7
+ "terminal_output_to_txt": true,
8
+ "log_tb": true,
9
+ "log_wandb": false,
10
+ "wandb_proj_name": "debug"
11
+ },
12
+ "save": {
13
+ "enabled": true,
14
+ "every_n_seconds": null,
15
+ "every_n_epochs": 50,
16
+ "epochs": [],
17
+ "on_best_validation": false,
18
+ "on_best_rollout_return": false,
19
+ "on_best_rollout_success_rate": true
20
+ },
21
+ "epoch_every_n_steps": 100,
22
+ "validation_epoch_every_n_steps": 10,
23
+ "env": null,
24
+ "additional_envs": null,
25
+ "render": false,
26
+ "render_video": true,
27
+ "keep_all_videos": false,
28
+ "video_skip": 5,
29
+ "rollout": {
30
+ "enabled": true,
31
+ "n": 50,
32
+ "horizon": 400,
33
+ "rate": 50,
34
+ "warmstart": 0,
35
+ "terminate_on_success": true
36
+ }
37
+ },
38
+ "train": {
39
+ "data": null,
40
+ "output_dir": "../gl_trained_models",
41
+ "num_data_workers": 0,
42
+ "hdf5_cache_mode": "all",
43
+ "hdf5_use_swmr": true,
44
+ "hdf5_load_next_obs": true,
45
+ "hdf5_normalize_obs": false,
46
+ "hdf5_filter_key": null,
47
+ "hdf5_validation_filter_key": null,
48
+ "seq_length": 1,
49
+ "pad_seq_length": true,
50
+ "frame_stack": 1,
51
+ "pad_frame_stack": true,
52
+ "dataset_keys": [
53
+ "actions",
54
+ "rewards",
55
+ "dones"
56
+ ],
57
+ "goal_mode": null,
58
+ "cuda": true,
59
+ "batch_size": 100,
60
+ "num_epochs": 2000,
61
+ "seed": 1
62
+ },
63
+ "algo": {
64
+ "optim_params": {
65
+ "goal_network": {
66
+ "learning_rate": {
67
+ "initial": 0.0001,
68
+ "decay_factor": 0.1,
69
+ "epoch_schedule": []
70
+ },
71
+ "regularization": {
72
+ "L2": 0.0
73
+ }
74
+ }
75
+ },
76
+ "subgoal_horizon": 10,
77
+ "ae": {
78
+ "planner_layer_dims": [
79
+ 300,
80
+ 400
81
+ ]
82
+ },
83
+ "vae": {
84
+ "enabled": true,
85
+ "latent_dim": 16,
86
+ "latent_clip": null,
87
+ "kl_weight": 1.0,
88
+ "decoder": {
89
+ "is_conditioned": true,
90
+ "reconstruction_sum_across_elements": false
91
+ },
92
+ "prior": {
93
+ "learn": false,
94
+ "is_conditioned": false,
95
+ "use_gmm": false,
96
+ "gmm_num_modes": 10,
97
+ "gmm_learn_weights": false,
98
+ "use_categorical": false,
99
+ "categorical_dim": 10,
100
+ "categorical_gumbel_softmax_hard": false,
101
+ "categorical_init_temp": 1.0,
102
+ "categorical_temp_anneal_step": 0.001,
103
+ "categorical_min_temp": 0.3
104
+ },
105
+ "encoder_layer_dims": [
106
+ 300,
107
+ 400
108
+ ],
109
+ "decoder_layer_dims": [
110
+ 300,
111
+ 400
112
+ ],
113
+ "prior_layer_dims": [
114
+ 300,
115
+ 400
116
+ ]
117
+ }
118
+ },
119
+ "observation": {
120
+ "modalities": {
121
+ "obs": {
122
+ "low_dim": [
123
+ "robot0_eef_pos",
124
+ "robot0_eef_quat",
125
+ "robot0_gripper_qpos",
126
+ "object"
127
+ ],
128
+ "rgb": [],
129
+ "depth": [],
130
+ "scan": []
131
+ },
132
+ "goal": {
133
+ "low_dim": [],
134
+ "rgb": [],
135
+ "depth": [],
136
+ "scan": []
137
+ },
138
+ "subgoal": {
139
+ "low_dim": [
140
+ "robot0_eef_pos",
141
+ "robot0_eef_quat",
142
+ "robot0_gripper_qpos",
143
+ "object"
144
+ ],
145
+ "rgb": [],
146
+ "depth": [],
147
+ "scan": []
148
+ }
149
+ },
150
+ "encoder": {
151
+ "low_dim": {
152
+ "core_class": null,
153
+ "core_kwargs": {},
154
+ "obs_randomizer_class": null,
155
+ "obs_randomizer_kwargs": {}
156
+ },
157
+ "rgb": {
158
+ "core_class": "VisualCore",
159
+ "core_kwargs": {},
160
+ "obs_randomizer_class": null,
161
+ "obs_randomizer_kwargs": {}
162
+ },
163
+ "depth": {
164
+ "core_class": "VisualCore",
165
+ "core_kwargs": {},
166
+ "obs_randomizer_class": null,
167
+ "obs_randomizer_kwargs": {}
168
+ },
169
+ "scan": {
170
+ "core_class": "ScanCore",
171
+ "core_kwargs": {},
172
+ "obs_randomizer_class": null,
173
+ "obs_randomizer_kwargs": {}
174
+ }
175
+ }
176
+ },
177
+ "meta": {
178
+ "hp_base_config_file": null,
179
+ "hp_keys": [],
180
+ "hp_values": []
181
+ }
182
+ }
aloha-devel/robomimic/exps/templates/hbc.json ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "algo_name": "hbc",
3
+ "experiment": {
4
+ "name": "test",
5
+ "validate": false,
6
+ "logging": {
7
+ "terminal_output_to_txt": true,
8
+ "log_tb": true,
9
+ "log_wandb": false,
10
+ "wandb_proj_name": "debug"
11
+ },
12
+ "save": {
13
+ "enabled": true,
14
+ "every_n_seconds": null,
15
+ "every_n_epochs": 50,
16
+ "epochs": [],
17
+ "on_best_validation": false,
18
+ "on_best_rollout_return": false,
19
+ "on_best_rollout_success_rate": true
20
+ },
21
+ "epoch_every_n_steps": 100,
22
+ "validation_epoch_every_n_steps": 10,
23
+ "env": null,
24
+ "additional_envs": null,
25
+ "render": false,
26
+ "render_video": true,
27
+ "keep_all_videos": false,
28
+ "video_skip": 5,
29
+ "rollout": {
30
+ "enabled": true,
31
+ "n": 50,
32
+ "horizon": 400,
33
+ "rate": 50,
34
+ "warmstart": 0,
35
+ "terminate_on_success": true
36
+ }
37
+ },
38
+ "train": {
39
+ "data": null,
40
+ "output_dir": "../hbc_trained_models",
41
+ "num_data_workers": 0,
42
+ "hdf5_cache_mode": "all",
43
+ "hdf5_use_swmr": true,
44
+ "hdf5_load_next_obs": true,
45
+ "hdf5_normalize_obs": false,
46
+ "hdf5_filter_key": null,
47
+ "hdf5_validation_filter_key": null,
48
+ "seq_length": 10,
49
+ "pad_seq_length": true,
50
+ "frame_stack": 1,
51
+ "pad_frame_stack": true,
52
+ "dataset_keys": [
53
+ "actions",
54
+ "rewards",
55
+ "dones"
56
+ ],
57
+ "goal_mode": null,
58
+ "cuda": true,
59
+ "batch_size": 100,
60
+ "num_epochs": 2000,
61
+ "seed": 1
62
+ },
63
+ "algo": {
64
+ "mode": "separate",
65
+ "actor_use_random_subgoals": false,
66
+ "subgoal_update_interval": 10,
67
+ "latent_subgoal": {
68
+ "enabled": false,
69
+ "prior_correction": {
70
+ "enabled": false,
71
+ "num_samples": 100
72
+ }
73
+ },
74
+ "planner": {
75
+ "optim_params": {
76
+ "goal_network": {
77
+ "learning_rate": {
78
+ "initial": 0.0001,
79
+ "decay_factor": 0.1,
80
+ "epoch_schedule": []
81
+ },
82
+ "regularization": {
83
+ "L2": 0.0
84
+ }
85
+ }
86
+ },
87
+ "subgoal_horizon": 10,
88
+ "ae": {
89
+ "planner_layer_dims": [
90
+ 300,
91
+ 400
92
+ ]
93
+ },
94
+ "vae": {
95
+ "enabled": true,
96
+ "latent_dim": 16,
97
+ "latent_clip": null,
98
+ "kl_weight": 1.0,
99
+ "decoder": {
100
+ "is_conditioned": true,
101
+ "reconstruction_sum_across_elements": false
102
+ },
103
+ "prior": {
104
+ "learn": false,
105
+ "is_conditioned": false,
106
+ "use_gmm": false,
107
+ "gmm_num_modes": 10,
108
+ "gmm_learn_weights": false,
109
+ "use_categorical": false,
110
+ "categorical_dim": 10,
111
+ "categorical_gumbel_softmax_hard": false,
112
+ "categorical_init_temp": 1.0,
113
+ "categorical_temp_anneal_step": 0.001,
114
+ "categorical_min_temp": 0.3
115
+ },
116
+ "encoder_layer_dims": [
117
+ 300,
118
+ 400
119
+ ],
120
+ "decoder_layer_dims": [
121
+ 300,
122
+ 400
123
+ ],
124
+ "prior_layer_dims": [
125
+ 300,
126
+ 400
127
+ ]
128
+ }
129
+ },
130
+ "actor": {
131
+ "optim_params": {
132
+ "policy": {
133
+ "optimizer_type": "adam",
134
+ "learning_rate": {
135
+ "initial": 0.0001,
136
+ "decay_factor": 0.1,
137
+ "epoch_schedule": [],
138
+ "scheduler_type": "multistep"
139
+ },
140
+ "regularization": {
141
+ "L2": 0.0
142
+ }
143
+ }
144
+ },
145
+ "loss": {
146
+ "l2_weight": 1.0,
147
+ "l1_weight": 0.0,
148
+ "cos_weight": 0.0
149
+ },
150
+ "actor_layer_dims": [
151
+ 1024,
152
+ 1024
153
+ ],
154
+ "rnn": {
155
+ "enabled": true,
156
+ "horizon": 10,
157
+ "hidden_dim": 400,
158
+ "rnn_type": "LSTM",
159
+ "num_layers": 2,
160
+ "open_loop": false,
161
+ "kwargs": {
162
+ "bidirectional": false
163
+ }
164
+ },
165
+ "transformer": {
166
+ "enabled": false,
167
+ "context_length": 10,
168
+ "embed_dim": 512,
169
+ "num_layers": 6,
170
+ "num_heads": 8,
171
+ "emb_dropout": 0.1,
172
+ "attn_dropout": 0.1,
173
+ "block_output_dropout": 0.1,
174
+ "sinusoidal_embedding": false,
175
+ "activation": "gelu",
176
+ "supervise_all_steps": false,
177
+ "nn_parameter_for_timesteps": true
178
+ }
179
+ }
180
+ },
181
+ "observation": {
182
+ "planner": {
183
+ "modalities": {
184
+ "obs": {
185
+ "low_dim": [
186
+ "robot0_eef_pos",
187
+ "robot0_eef_quat",
188
+ "robot0_gripper_qpos",
189
+ "object"
190
+ ],
191
+ "rgb": [],
192
+ "depth": [],
193
+ "scan": []
194
+ },
195
+ "goal": {
196
+ "low_dim": [],
197
+ "rgb": [],
198
+ "depth": [],
199
+ "scan": []
200
+ },
201
+ "subgoal": {
202
+ "low_dim": [
203
+ "robot0_eef_pos",
204
+ "robot0_eef_quat",
205
+ "robot0_gripper_qpos",
206
+ "object"
207
+ ],
208
+ "rgb": [],
209
+ "depth": [],
210
+ "scan": []
211
+ }
212
+ },
213
+ "encoder": {
214
+ "low_dim": {
215
+ "core_class": null,
216
+ "core_kwargs": {},
217
+ "obs_randomizer_class": null,
218
+ "obs_randomizer_kwargs": {}
219
+ },
220
+ "rgb": {
221
+ "core_class": "VisualCore",
222
+ "core_kwargs": {},
223
+ "obs_randomizer_class": null,
224
+ "obs_randomizer_kwargs": {}
225
+ },
226
+ "depth": {
227
+ "core_class": "VisualCore",
228
+ "core_kwargs": {},
229
+ "obs_randomizer_class": null,
230
+ "obs_randomizer_kwargs": {}
231
+ },
232
+ "scan": {
233
+ "core_class": "ScanCore",
234
+ "core_kwargs": {},
235
+ "obs_randomizer_class": null,
236
+ "obs_randomizer_kwargs": {}
237
+ }
238
+ }
239
+ },
240
+ "actor": {
241
+ "modalities": {
242
+ "obs": {
243
+ "low_dim": [
244
+ "robot0_eef_pos",
245
+ "robot0_eef_quat",
246
+ "robot0_gripper_qpos",
247
+ "object"
248
+ ],
249
+ "rgb": [],
250
+ "depth": [],
251
+ "scan": []
252
+ },
253
+ "goal": {
254
+ "low_dim": [],
255
+ "rgb": [],
256
+ "depth": [],
257
+ "scan": []
258
+ }
259
+ },
260
+ "encoder": {
261
+ "low_dim": {
262
+ "core_class": null,
263
+ "core_kwargs": {},
264
+ "obs_randomizer_class": null,
265
+ "obs_randomizer_kwargs": {}
266
+ },
267
+ "rgb": {
268
+ "core_class": "VisualCore",
269
+ "core_kwargs": {},
270
+ "obs_randomizer_class": null,
271
+ "obs_randomizer_kwargs": {}
272
+ },
273
+ "depth": {
274
+ "core_class": "VisualCore",
275
+ "core_kwargs": {},
276
+ "obs_randomizer_class": null,
277
+ "obs_randomizer_kwargs": {}
278
+ },
279
+ "scan": {
280
+ "core_class": "ScanCore",
281
+ "core_kwargs": {},
282
+ "obs_randomizer_class": null,
283
+ "obs_randomizer_kwargs": {}
284
+ }
285
+ }
286
+ }
287
+ },
288
+ "meta": {
289
+ "hp_base_config_file": null,
290
+ "hp_keys": [],
291
+ "hp_values": []
292
+ }
293
+ }
aloha-devel/robomimic/exps/templates/iql.json ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "algo_name": "iql",
3
+ "experiment": {
4
+ "name": "test",
5
+ "validate": false,
6
+ "logging": {
7
+ "terminal_output_to_txt": true,
8
+ "log_tb": true,
9
+ "log_wandb": false,
10
+ "wandb_proj_name": "debug"
11
+ },
12
+ "save": {
13
+ "enabled": true,
14
+ "every_n_seconds": null,
15
+ "every_n_epochs": 50,
16
+ "epochs": [],
17
+ "on_best_validation": false,
18
+ "on_best_rollout_return": false,
19
+ "on_best_rollout_success_rate": true
20
+ },
21
+ "epoch_every_n_steps": 100,
22
+ "validation_epoch_every_n_steps": 10,
23
+ "env": null,
24
+ "additional_envs": null,
25
+ "render": false,
26
+ "render_video": true,
27
+ "keep_all_videos": false,
28
+ "video_skip": 5,
29
+ "rollout": {
30
+ "enabled": true,
31
+ "n": 50,
32
+ "horizon": 400,
33
+ "rate": 50,
34
+ "warmstart": 0,
35
+ "terminate_on_success": true
36
+ }
37
+ },
38
+ "train": {
39
+ "data": null,
40
+ "output_dir": "../iql_trained_models",
41
+ "num_data_workers": 0,
42
+ "hdf5_cache_mode": "all",
43
+ "hdf5_use_swmr": true,
44
+ "hdf5_load_next_obs": true,
45
+ "hdf5_normalize_obs": false,
46
+ "hdf5_filter_key": null,
47
+ "hdf5_validation_filter_key": null,
48
+ "seq_length": 1,
49
+ "pad_seq_length": true,
50
+ "frame_stack": 1,
51
+ "pad_frame_stack": true,
52
+ "dataset_keys": [
53
+ "actions",
54
+ "rewards",
55
+ "dones"
56
+ ],
57
+ "goal_mode": null,
58
+ "cuda": true,
59
+ "batch_size": 100,
60
+ "num_epochs": 2000,
61
+ "seed": 1
62
+ },
63
+ "algo": {
64
+ "optim_params": {
65
+ "critic": {
66
+ "learning_rate": {
67
+ "initial": 0.0001,
68
+ "decay_factor": 0.0,
69
+ "epoch_schedule": []
70
+ },
71
+ "regularization": {
72
+ "L2": 0.0
73
+ }
74
+ },
75
+ "vf": {
76
+ "learning_rate": {
77
+ "initial": 0.0001,
78
+ "decay_factor": 0.0,
79
+ "epoch_schedule": []
80
+ },
81
+ "regularization": {
82
+ "L2": 0.0
83
+ }
84
+ },
85
+ "actor": {
86
+ "learning_rate": {
87
+ "initial": 0.0001,
88
+ "decay_factor": 0.0,
89
+ "epoch_schedule": []
90
+ },
91
+ "regularization": {
92
+ "L2": 0.0
93
+ }
94
+ }
95
+ },
96
+ "discount": 0.99,
97
+ "target_tau": 0.01,
98
+ "actor": {
99
+ "net": {
100
+ "type": "gaussian",
101
+ "common": {
102
+ "std_activation": "softplus",
103
+ "low_noise_eval": true,
104
+ "use_tanh": false
105
+ },
106
+ "gaussian": {
107
+ "init_last_fc_weight": 0.001,
108
+ "init_std": 0.3,
109
+ "fixed_std": false
110
+ },
111
+ "gmm": {
112
+ "num_modes": 5,
113
+ "min_std": 0.0001
114
+ }
115
+ },
116
+ "layer_dims": [
117
+ 300,
118
+ 400
119
+ ],
120
+ "max_gradient_norm": null
121
+ },
122
+ "critic": {
123
+ "ensemble": {
124
+ "n": 2
125
+ },
126
+ "layer_dims": [
127
+ 300,
128
+ 400
129
+ ],
130
+ "use_huber": false,
131
+ "max_gradient_norm": null
132
+ },
133
+ "adv": {
134
+ "clip_adv_value": null,
135
+ "beta": 1.0,
136
+ "use_final_clip": true
137
+ },
138
+ "vf_quantile": 0.9
139
+ },
140
+ "observation": {
141
+ "modalities": {
142
+ "obs": {
143
+ "low_dim": [
144
+ "robot0_eef_pos",
145
+ "robot0_eef_quat",
146
+ "robot0_gripper_qpos",
147
+ "object"
148
+ ],
149
+ "rgb": [],
150
+ "depth": [],
151
+ "scan": []
152
+ },
153
+ "goal": {
154
+ "low_dim": [],
155
+ "rgb": [],
156
+ "depth": [],
157
+ "scan": []
158
+ }
159
+ },
160
+ "encoder": {
161
+ "low_dim": {
162
+ "core_class": null,
163
+ "core_kwargs": {},
164
+ "obs_randomizer_class": null,
165
+ "obs_randomizer_kwargs": {}
166
+ },
167
+ "rgb": {
168
+ "core_class": "VisualCore",
169
+ "core_kwargs": {},
170
+ "obs_randomizer_class": null,
171
+ "obs_randomizer_kwargs": {}
172
+ },
173
+ "depth": {
174
+ "core_class": "VisualCore",
175
+ "core_kwargs": {},
176
+ "obs_randomizer_class": null,
177
+ "obs_randomizer_kwargs": {}
178
+ },
179
+ "scan": {
180
+ "core_class": "ScanCore",
181
+ "core_kwargs": {},
182
+ "obs_randomizer_class": null,
183
+ "obs_randomizer_kwargs": {}
184
+ }
185
+ }
186
+ },
187
+ "meta": {
188
+ "hp_base_config_file": null,
189
+ "hp_keys": [],
190
+ "hp_values": []
191
+ }
192
+ }
aloha-devel/robomimic/exps/templates/iris.json ADDED
@@ -0,0 +1,465 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "algo_name": "iris",
3
+ "experiment": {
4
+ "name": "test",
5
+ "validate": false,
6
+ "logging": {
7
+ "terminal_output_to_txt": true,
8
+ "log_tb": true,
9
+ "log_wandb": false,
10
+ "wandb_proj_name": "debug"
11
+ },
12
+ "save": {
13
+ "enabled": true,
14
+ "every_n_seconds": null,
15
+ "every_n_epochs": 50,
16
+ "epochs": [],
17
+ "on_best_validation": false,
18
+ "on_best_rollout_return": false,
19
+ "on_best_rollout_success_rate": true
20
+ },
21
+ "epoch_every_n_steps": 100,
22
+ "validation_epoch_every_n_steps": 10,
23
+ "env": null,
24
+ "additional_envs": null,
25
+ "render": false,
26
+ "render_video": true,
27
+ "keep_all_videos": false,
28
+ "video_skip": 5,
29
+ "rollout": {
30
+ "enabled": true,
31
+ "n": 50,
32
+ "horizon": 400,
33
+ "rate": 50,
34
+ "warmstart": 0,
35
+ "terminate_on_success": true
36
+ }
37
+ },
38
+ "train": {
39
+ "data": null,
40
+ "output_dir": "../iris_trained_models",
41
+ "num_data_workers": 0,
42
+ "hdf5_cache_mode": "all",
43
+ "hdf5_use_swmr": true,
44
+ "hdf5_load_next_obs": true,
45
+ "hdf5_normalize_obs": false,
46
+ "hdf5_filter_key": null,
47
+ "hdf5_validation_filter_key": null,
48
+ "seq_length": 10,
49
+ "pad_seq_length": true,
50
+ "frame_stack": 1,
51
+ "pad_frame_stack": true,
52
+ "dataset_keys": [
53
+ "actions",
54
+ "rewards",
55
+ "dones"
56
+ ],
57
+ "goal_mode": null,
58
+ "cuda": true,
59
+ "batch_size": 100,
60
+ "num_epochs": 2000,
61
+ "seed": 1
62
+ },
63
+ "algo": {
64
+ "mode": "separate",
65
+ "actor_use_random_subgoals": false,
66
+ "subgoal_update_interval": 10,
67
+ "latent_subgoal": {
68
+ "enabled": false,
69
+ "prior_correction": {
70
+ "enabled": false,
71
+ "num_samples": 100
72
+ }
73
+ },
74
+ "value_planner": {
75
+ "planner": {
76
+ "optim_params": {
77
+ "goal_network": {
78
+ "learning_rate": {
79
+ "initial": 0.0001,
80
+ "decay_factor": 0.1,
81
+ "epoch_schedule": []
82
+ },
83
+ "regularization": {
84
+ "L2": 0.0
85
+ }
86
+ }
87
+ },
88
+ "subgoal_horizon": 10,
89
+ "ae": {
90
+ "planner_layer_dims": [
91
+ 300,
92
+ 400
93
+ ]
94
+ },
95
+ "vae": {
96
+ "enabled": true,
97
+ "latent_dim": 16,
98
+ "latent_clip": null,
99
+ "kl_weight": 1.0,
100
+ "decoder": {
101
+ "is_conditioned": true,
102
+ "reconstruction_sum_across_elements": false
103
+ },
104
+ "prior": {
105
+ "learn": false,
106
+ "is_conditioned": false,
107
+ "use_gmm": false,
108
+ "gmm_num_modes": 10,
109
+ "gmm_learn_weights": false,
110
+ "use_categorical": false,
111
+ "categorical_dim": 10,
112
+ "categorical_gumbel_softmax_hard": false,
113
+ "categorical_init_temp": 1.0,
114
+ "categorical_temp_anneal_step": 0.001,
115
+ "categorical_min_temp": 0.3
116
+ },
117
+ "encoder_layer_dims": [
118
+ 300,
119
+ 400
120
+ ],
121
+ "decoder_layer_dims": [
122
+ 300,
123
+ 400
124
+ ],
125
+ "prior_layer_dims": [
126
+ 300,
127
+ 400
128
+ ]
129
+ }
130
+ },
131
+ "value": {
132
+ "optim_params": {
133
+ "critic": {
134
+ "learning_rate": {
135
+ "initial": 0.001,
136
+ "decay_factor": 0.1,
137
+ "epoch_schedule": []
138
+ },
139
+ "regularization": {
140
+ "L2": 0.0
141
+ },
142
+ "start_epoch": -1,
143
+ "end_epoch": -1
144
+ },
145
+ "action_sampler": {
146
+ "learning_rate": {
147
+ "initial": 0.001,
148
+ "decay_factor": 0.1,
149
+ "epoch_schedule": []
150
+ },
151
+ "regularization": {
152
+ "L2": 0.0
153
+ },
154
+ "start_epoch": -1,
155
+ "end_epoch": -1
156
+ },
157
+ "actor": {
158
+ "learning_rate": {
159
+ "initial": 0.001,
160
+ "decay_factor": 0.1,
161
+ "epoch_schedule": []
162
+ },
163
+ "regularization": {
164
+ "L2": 0.0
165
+ },
166
+ "start_epoch": -1,
167
+ "end_epoch": -1
168
+ }
169
+ },
170
+ "discount": 0.99,
171
+ "n_step": 1,
172
+ "target_tau": 0.005,
173
+ "infinite_horizon": false,
174
+ "critic": {
175
+ "use_huber": false,
176
+ "max_gradient_norm": null,
177
+ "value_bounds": null,
178
+ "num_action_samples": 10,
179
+ "num_action_samples_rollout": 100,
180
+ "ensemble": {
181
+ "n": 2,
182
+ "weight": 0.75
183
+ },
184
+ "distributional": {
185
+ "enabled": false,
186
+ "num_atoms": 51
187
+ },
188
+ "layer_dims": [
189
+ 300,
190
+ 400
191
+ ]
192
+ },
193
+ "action_sampler": {
194
+ "actor_layer_dims": [
195
+ 1024,
196
+ 1024
197
+ ],
198
+ "gmm": {
199
+ "enabled": false,
200
+ "num_modes": 5,
201
+ "min_std": 0.0001,
202
+ "std_activation": "softplus",
203
+ "low_noise_eval": true
204
+ },
205
+ "vae": {
206
+ "enabled": true,
207
+ "latent_dim": 14,
208
+ "latent_clip": null,
209
+ "kl_weight": 1.0,
210
+ "decoder": {
211
+ "is_conditioned": true,
212
+ "reconstruction_sum_across_elements": false
213
+ },
214
+ "prior": {
215
+ "learn": false,
216
+ "is_conditioned": false,
217
+ "use_gmm": false,
218
+ "gmm_num_modes": 10,
219
+ "gmm_learn_weights": false,
220
+ "use_categorical": false,
221
+ "categorical_dim": 10,
222
+ "categorical_gumbel_softmax_hard": false,
223
+ "categorical_init_temp": 1.0,
224
+ "categorical_temp_anneal_step": 0.001,
225
+ "categorical_min_temp": 0.3
226
+ },
227
+ "encoder_layer_dims": [
228
+ 300,
229
+ 400
230
+ ],
231
+ "decoder_layer_dims": [
232
+ 300,
233
+ 400
234
+ ],
235
+ "prior_layer_dims": [
236
+ 300,
237
+ 400
238
+ ]
239
+ },
240
+ "freeze_encoder_epoch": -1
241
+ },
242
+ "actor": {
243
+ "enabled": false,
244
+ "perturbation_scale": 0.05,
245
+ "layer_dims": [
246
+ 300,
247
+ 400
248
+ ]
249
+ }
250
+ },
251
+ "num_samples": 100
252
+ },
253
+ "actor": {
254
+ "optim_params": {
255
+ "policy": {
256
+ "optimizer_type": "adam",
257
+ "learning_rate": {
258
+ "initial": 0.0001,
259
+ "decay_factor": 0.1,
260
+ "epoch_schedule": [],
261
+ "scheduler_type": "multistep"
262
+ },
263
+ "regularization": {
264
+ "L2": 0.0
265
+ }
266
+ }
267
+ },
268
+ "loss": {
269
+ "l2_weight": 1.0,
270
+ "l1_weight": 0.0,
271
+ "cos_weight": 0.0
272
+ },
273
+ "actor_layer_dims": [
274
+ 1024,
275
+ 1024
276
+ ],
277
+ "rnn": {
278
+ "enabled": true,
279
+ "horizon": 10,
280
+ "hidden_dim": 400,
281
+ "rnn_type": "LSTM",
282
+ "num_layers": 2,
283
+ "open_loop": false,
284
+ "kwargs": {
285
+ "bidirectional": false
286
+ }
287
+ },
288
+ "transformer": {
289
+ "enabled": false,
290
+ "context_length": 10,
291
+ "embed_dim": 512,
292
+ "num_layers": 6,
293
+ "num_heads": 8,
294
+ "emb_dropout": 0.1,
295
+ "attn_dropout": 0.1,
296
+ "block_output_dropout": 0.1,
297
+ "sinusoidal_embedding": false,
298
+ "activation": "gelu",
299
+ "supervise_all_steps": false,
300
+ "nn_parameter_for_timesteps": true
301
+ }
302
+ }
303
+ },
304
+ "observation": {
305
+ "value_planner": {
306
+ "planner": {
307
+ "modalities": {
308
+ "obs": {
309
+ "low_dim": [
310
+ "robot0_eef_pos",
311
+ "robot0_eef_quat",
312
+ "robot0_gripper_qpos",
313
+ "object"
314
+ ],
315
+ "rgb": [],
316
+ "depth": [],
317
+ "scan": []
318
+ },
319
+ "goal": {
320
+ "low_dim": [],
321
+ "rgb": [],
322
+ "depth": [],
323
+ "scan": []
324
+ },
325
+ "subgoal": {
326
+ "low_dim": [
327
+ "robot0_eef_pos",
328
+ "robot0_eef_quat",
329
+ "robot0_gripper_qpos",
330
+ "object"
331
+ ],
332
+ "rgb": [],
333
+ "depth": [],
334
+ "scan": []
335
+ }
336
+ },
337
+ "encoder": {
338
+ "low_dim": {
339
+ "core_class": null,
340
+ "core_kwargs": {},
341
+ "obs_randomizer_class": null,
342
+ "obs_randomizer_kwargs": {}
343
+ },
344
+ "rgb": {
345
+ "core_class": "VisualCore",
346
+ "core_kwargs": {},
347
+ "obs_randomizer_class": null,
348
+ "obs_randomizer_kwargs": {}
349
+ },
350
+ "depth": {
351
+ "core_class": "VisualCore",
352
+ "core_kwargs": {},
353
+ "obs_randomizer_class": null,
354
+ "obs_randomizer_kwargs": {}
355
+ },
356
+ "scan": {
357
+ "core_class": "ScanCore",
358
+ "core_kwargs": {},
359
+ "obs_randomizer_class": null,
360
+ "obs_randomizer_kwargs": {}
361
+ }
362
+ }
363
+ },
364
+ "value": {
365
+ "modalities": {
366
+ "obs": {
367
+ "low_dim": [
368
+ "robot0_eef_pos",
369
+ "robot0_eef_quat",
370
+ "robot0_gripper_qpos",
371
+ "object"
372
+ ],
373
+ "rgb": [],
374
+ "depth": [],
375
+ "scan": []
376
+ },
377
+ "goal": {
378
+ "low_dim": [],
379
+ "rgb": [],
380
+ "depth": [],
381
+ "scan": []
382
+ }
383
+ },
384
+ "encoder": {
385
+ "low_dim": {
386
+ "core_class": null,
387
+ "core_kwargs": {},
388
+ "obs_randomizer_class": null,
389
+ "obs_randomizer_kwargs": {}
390
+ },
391
+ "rgb": {
392
+ "core_class": "VisualCore",
393
+ "core_kwargs": {},
394
+ "obs_randomizer_class": null,
395
+ "obs_randomizer_kwargs": {}
396
+ },
397
+ "depth": {
398
+ "core_class": "VisualCore",
399
+ "core_kwargs": {},
400
+ "obs_randomizer_class": null,
401
+ "obs_randomizer_kwargs": {}
402
+ },
403
+ "scan": {
404
+ "core_class": "ScanCore",
405
+ "core_kwargs": {},
406
+ "obs_randomizer_class": null,
407
+ "obs_randomizer_kwargs": {}
408
+ }
409
+ }
410
+ }
411
+ },
412
+ "actor": {
413
+ "modalities": {
414
+ "obs": {
415
+ "low_dim": [
416
+ "robot0_eef_pos",
417
+ "robot0_eef_quat",
418
+ "robot0_gripper_qpos",
419
+ "object"
420
+ ],
421
+ "rgb": [],
422
+ "depth": [],
423
+ "scan": []
424
+ },
425
+ "goal": {
426
+ "low_dim": [],
427
+ "rgb": [],
428
+ "depth": [],
429
+ "scan": []
430
+ }
431
+ },
432
+ "encoder": {
433
+ "low_dim": {
434
+ "core_class": null,
435
+ "core_kwargs": {},
436
+ "obs_randomizer_class": null,
437
+ "obs_randomizer_kwargs": {}
438
+ },
439
+ "rgb": {
440
+ "core_class": "VisualCore",
441
+ "core_kwargs": {},
442
+ "obs_randomizer_class": null,
443
+ "obs_randomizer_kwargs": {}
444
+ },
445
+ "depth": {
446
+ "core_class": "VisualCore",
447
+ "core_kwargs": {},
448
+ "obs_randomizer_class": null,
449
+ "obs_randomizer_kwargs": {}
450
+ },
451
+ "scan": {
452
+ "core_class": "ScanCore",
453
+ "core_kwargs": {},
454
+ "obs_randomizer_class": null,
455
+ "obs_randomizer_kwargs": {}
456
+ }
457
+ }
458
+ }
459
+ },
460
+ "meta": {
461
+ "hp_base_config_file": null,
462
+ "hp_keys": [],
463
+ "hp_values": []
464
+ }
465
+ }
aloha-devel/robomimic/exps/templates/td3_bc.json ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "algo_name": "td3_bc",
3
+ "experiment": {
4
+ "name": "test",
5
+ "validate": false,
6
+ "logging": {
7
+ "terminal_output_to_txt": true,
8
+ "log_tb": true,
9
+ "log_wandb": false,
10
+ "wandb_proj_name": "debug"
11
+ },
12
+ "save": {
13
+ "enabled": true,
14
+ "every_n_seconds": null,
15
+ "every_n_epochs": 20,
16
+ "epochs": [],
17
+ "on_best_validation": false,
18
+ "on_best_rollout_return": true,
19
+ "on_best_rollout_success_rate": false
20
+ },
21
+ "epoch_every_n_steps": 5000,
22
+ "validation_epoch_every_n_steps": 10,
23
+ "env": null,
24
+ "additional_envs": null,
25
+ "render": false,
26
+ "render_video": false,
27
+ "keep_all_videos": false,
28
+ "video_skip": 5,
29
+ "rollout": {
30
+ "enabled": true,
31
+ "n": 50,
32
+ "horizon": 1000,
33
+ "rate": 1,
34
+ "warmstart": 0,
35
+ "terminate_on_success": true
36
+ }
37
+ },
38
+ "train": {
39
+ "data": null,
40
+ "output_dir": "../td3_bc_trained_models",
41
+ "num_data_workers": 0,
42
+ "hdf5_cache_mode": "all",
43
+ "hdf5_use_swmr": true,
44
+ "hdf5_load_next_obs": true,
45
+ "hdf5_normalize_obs": true,
46
+ "hdf5_filter_key": null,
47
+ "hdf5_validation_filter_key": null,
48
+ "seq_length": 1,
49
+ "pad_seq_length": true,
50
+ "frame_stack": 1,
51
+ "pad_frame_stack": true,
52
+ "dataset_keys": [
53
+ "actions",
54
+ "rewards",
55
+ "dones"
56
+ ],
57
+ "goal_mode": null,
58
+ "cuda": true,
59
+ "batch_size": 256,
60
+ "num_epochs": 200,
61
+ "seed": 1
62
+ },
63
+ "algo": {
64
+ "optim_params": {
65
+ "critic": {
66
+ "learning_rate": {
67
+ "initial": 0.0003,
68
+ "decay_factor": 0.1,
69
+ "epoch_schedule": []
70
+ },
71
+ "regularization": {
72
+ "L2": 0.0
73
+ },
74
+ "start_epoch": -1,
75
+ "end_epoch": -1
76
+ },
77
+ "actor": {
78
+ "learning_rate": {
79
+ "initial": 0.0003,
80
+ "decay_factor": 0.1,
81
+ "epoch_schedule": []
82
+ },
83
+ "regularization": {
84
+ "L2": 0.0
85
+ },
86
+ "start_epoch": -1,
87
+ "end_epoch": -1
88
+ }
89
+ },
90
+ "alpha": 2.5,
91
+ "discount": 0.99,
92
+ "n_step": 1,
93
+ "target_tau": 0.005,
94
+ "infinite_horizon": false,
95
+ "critic": {
96
+ "use_huber": false,
97
+ "max_gradient_norm": null,
98
+ "value_bounds": null,
99
+ "ensemble": {
100
+ "n": 2,
101
+ "weight": 1.0
102
+ },
103
+ "layer_dims": [
104
+ 256,
105
+ 256
106
+ ]
107
+ },
108
+ "actor": {
109
+ "update_freq": 2,
110
+ "noise_std": 0.2,
111
+ "noise_clip": 0.5,
112
+ "layer_dims": [
113
+ 256,
114
+ 256
115
+ ]
116
+ }
117
+ },
118
+ "observation": {
119
+ "modalities": {
120
+ "obs": {
121
+ "low_dim": [
122
+ "flat"
123
+ ],
124
+ "rgb": [],
125
+ "depth": [],
126
+ "scan": []
127
+ },
128
+ "goal": {
129
+ "low_dim": [],
130
+ "rgb": [],
131
+ "depth": [],
132
+ "scan": []
133
+ }
134
+ },
135
+ "encoder": {
136
+ "low_dim": {
137
+ "core_class": null,
138
+ "core_kwargs": {},
139
+ "obs_randomizer_class": null,
140
+ "obs_randomizer_kwargs": {}
141
+ },
142
+ "rgb": {
143
+ "core_class": "VisualCore",
144
+ "core_kwargs": {},
145
+ "obs_randomizer_class": null,
146
+ "obs_randomizer_kwargs": {}
147
+ },
148
+ "depth": {
149
+ "core_class": "VisualCore",
150
+ "core_kwargs": {},
151
+ "obs_randomizer_class": null,
152
+ "obs_randomizer_kwargs": {}
153
+ },
154
+ "scan": {
155
+ "core_class": "ScanCore",
156
+ "core_kwargs": {},
157
+ "obs_randomizer_class": null,
158
+ "obs_randomizer_kwargs": {}
159
+ }
160
+ }
161
+ },
162
+ "meta": {
163
+ "hp_base_config_file": null,
164
+ "hp_keys": [],
165
+ "hp_values": []
166
+ }
167
+ }
aloha-devel/robomimic/macros.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Set of global variables shared across robomimic
3
+ """
4
+ # Sets debugging mode. Should be set at top-level script so that internal
5
+ # debugging functionalities are made active
6
+ DEBUG = False
7
+
8
+ # Whether to visualize the before & after of an observation randomizer
9
+ VISUALIZE_RANDOMIZER = False
10
+
11
+ # wandb entity (eg. username or team name)
12
+ WANDB_ENTITY = None
13
+
14
+ # wandb api key (obtain from https://wandb.ai/authorize)
15
+ # alternatively, set up wandb from terminal with `wandb login`
16
+ WANDB_API_KEY = None
17
+
18
+ try:
19
+ from robomimic.macros_private import *
20
+ except ImportError:
21
+ from robomimic.utils.log_utils import log_warning
22
+ import robomimic
23
+ log_warning(
24
+ "No private macro file found!"\
25
+ "\nIt is recommended to use a private macro file"\
26
+ "\nTo setup, run: python {}/scripts/setup_macros.py".format(robomimic.__path__[0])
27
+ )
aloha-devel/robomimic/models/__pycache__/__init__.cpython-38.pyc ADDED
Binary file (223 Bytes). View file
 
aloha-devel/robomimic/models/__pycache__/base_nets.cpython-38.pyc ADDED
Binary file (33.4 kB). View file
 
aloha-devel/robomimic/models/__pycache__/distributions.cpython-38.pyc ADDED
Binary file (5.54 kB). View file
 
aloha-devel/robomimic/models/__pycache__/policy_nets.cpython-38.pyc ADDED
Binary file (47.4 kB). View file
 
aloha-devel/robomimic/models/__pycache__/transformers.cpython-38.pyc ADDED
Binary file (12.5 kB). View file
 
aloha-devel/robomimic/models/__pycache__/value_nets.cpython-38.pyc ADDED
Binary file (11.4 kB). View file
 
aloha-devel/robomimic/models/base_nets.py ADDED
@@ -0,0 +1,1156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Contains torch Modules that correspond to basic network building blocks, like
3
+ MLP, RNN, and CNN backbones.
4
+ """
5
+
6
+ import math
7
+ import abc
8
+ import numpy as np
9
+ import textwrap
10
+ from collections import OrderedDict
11
+
12
+ import torch
13
+ import torch.nn as nn
14
+ import torch.nn.functional as F
15
+ from torchvision import models as vision_models
16
+ from torchvision import transforms
17
+
18
+ import robomimic.utils.tensor_utils as TensorUtils
19
+
20
+ CONV_ACTIVATIONS = {
21
+ "relu": nn.ReLU,
22
+ "None": None,
23
+ None: None,
24
+ }
25
+
26
+
27
+ def rnn_args_from_config(rnn_config):
28
+ """
29
+ Takes a Config object corresponding to RNN settings
30
+ (for example `config.algo.rnn` in BCConfig) and extracts
31
+ rnn kwargs for instantiating rnn networks.
32
+ """
33
+ return dict(
34
+ rnn_hidden_dim=rnn_config.hidden_dim,
35
+ rnn_num_layers=rnn_config.num_layers,
36
+ rnn_type=rnn_config.rnn_type,
37
+ rnn_kwargs=dict(rnn_config.kwargs),
38
+ )
39
+
40
+
41
+ def transformer_args_from_config(transformer_config):
42
+ """
43
+ Takes a Config object corresponding to Transformer settings
44
+ (for example `config.algo.transformer` in BCConfig) and extracts
45
+ transformer kwargs for instantiating transformer networks.
46
+ """
47
+ transformer_args = dict(
48
+ transformer_context_length=transformer_config.context_length,
49
+ transformer_embed_dim=transformer_config.embed_dim,
50
+ transformer_num_heads=transformer_config.num_heads,
51
+ transformer_emb_dropout=transformer_config.emb_dropout,
52
+ transformer_attn_dropout=transformer_config.attn_dropout,
53
+ transformer_block_output_dropout=transformer_config.block_output_dropout,
54
+ transformer_sinusoidal_embedding=transformer_config.sinusoidal_embedding,
55
+ transformer_activation=transformer_config.activation,
56
+ transformer_nn_parameter_for_timesteps=transformer_config.nn_parameter_for_timesteps,
57
+ )
58
+
59
+ if "num_layers" in transformer_config:
60
+ transformer_args["transformer_num_layers"] = transformer_config.num_layers
61
+
62
+ return transformer_args
63
+
64
+
65
+ class Module(torch.nn.Module):
66
+ """
67
+ Base class for networks. The only difference from torch.nn.Module is that it
68
+ requires implementing @output_shape.
69
+ """
70
+ @abc.abstractmethod
71
+ def output_shape(self, input_shape=None):
72
+ """
73
+ Function to compute output shape from inputs to this module.
74
+
75
+ Args:
76
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
77
+ Some modules may not need this argument, if their output does not depend
78
+ on the size of the input, or if they assume fixed size input.
79
+
80
+ Returns:
81
+ out_shape ([int]): list of integers corresponding to output shape
82
+ """
83
+ raise NotImplementedError
84
+
85
+
86
+ class Sequential(torch.nn.Sequential, Module):
87
+ """
88
+ Compose multiple Modules together (defined above).
89
+ """
90
+ def __init__(self, *args):
91
+ for arg in args:
92
+ assert isinstance(arg, Module)
93
+ torch.nn.Sequential.__init__(self, *args)
94
+ self.fixed = False
95
+
96
+ def output_shape(self, input_shape=None):
97
+ """
98
+ Function to compute output shape from inputs to this module.
99
+
100
+ Args:
101
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
102
+ Some modules may not need this argument, if their output does not depend
103
+ on the size of the input, or if they assume fixed size input.
104
+
105
+ Returns:
106
+ out_shape ([int]): list of integers corresponding to output shape
107
+ """
108
+ out_shape = input_shape
109
+ for module in self:
110
+ out_shape = module.output_shape(out_shape)
111
+ return out_shape
112
+
113
+ def freeze(self):
114
+ self.fixed = True
115
+
116
+ def train(self, mode):
117
+ if self.fixed:
118
+ super().train(False)
119
+ else:
120
+ super().train(mode)
121
+
122
+
123
+ class Parameter(Module):
124
+ """
125
+ A class that is a thin wrapper around a torch.nn.Parameter to make for easy saving
126
+ and optimization.
127
+ """
128
+ def __init__(self, init_tensor):
129
+ """
130
+ Args:
131
+ init_tensor (torch.Tensor): initial tensor
132
+ """
133
+ super(Parameter, self).__init__()
134
+ self.param = torch.nn.Parameter(init_tensor)
135
+
136
+ def output_shape(self, input_shape=None):
137
+ """
138
+ Function to compute output shape from inputs to this module.
139
+
140
+ Args:
141
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
142
+ Some modules may not need this argument, if their output does not depend
143
+ on the size of the input, or if they assume fixed size input.
144
+
145
+ Returns:
146
+ out_shape ([int]): list of integers corresponding to output shape
147
+ """
148
+ return list(self.param.shape)
149
+
150
+ def forward(self, inputs=None):
151
+ """
152
+ Forward call just returns the parameter tensor.
153
+ """
154
+ return self.param
155
+
156
+
157
+ class Unsqueeze(Module):
158
+ """
159
+ Trivial class that unsqueezes the input. Useful for including in a nn.Sequential network
160
+ """
161
+ def __init__(self, dim):
162
+ super(Unsqueeze, self).__init__()
163
+ self.dim = dim
164
+
165
+ def output_shape(self, input_shape=None):
166
+ assert input_shape is not None
167
+ return input_shape + [1] if self.dim == -1 else input_shape[:self.dim + 1] + [1] + input_shape[self.dim + 1:]
168
+
169
+ def forward(self, x):
170
+ return x.unsqueeze(dim=self.dim)
171
+
172
+
173
+ class Squeeze(Module):
174
+ """
175
+ Trivial class that squeezes the input. Useful for including in a nn.Sequential network
176
+ """
177
+
178
+ def __init__(self, dim):
179
+ super(Squeeze, self).__init__()
180
+ self.dim = dim
181
+
182
+ def output_shape(self, input_shape=None):
183
+ assert input_shape is not None
184
+ return input_shape[:self.dim] + input_shape[self.dim+1:] if input_shape[self.dim] == 1 else input_shape
185
+
186
+ def forward(self, x):
187
+ return x.squeeze(dim=self.dim)
188
+
189
+
190
+ class MLP(Module):
191
+ """
192
+ Base class for simple Multi-Layer Perceptrons.
193
+ """
194
+ def __init__(
195
+ self,
196
+ input_dim,
197
+ output_dim,
198
+ layer_dims=(),
199
+ layer_func=nn.Linear,
200
+ layer_func_kwargs=None,
201
+ activation=nn.ReLU,
202
+ dropouts=None,
203
+ normalization=False,
204
+ output_activation=None,
205
+ ):
206
+ """
207
+ Args:
208
+ input_dim (int): dimension of inputs
209
+
210
+ output_dim (int): dimension of outputs
211
+
212
+ layer_dims ([int]): sequence of integers for the hidden layers sizes
213
+
214
+ layer_func: mapping per layer - defaults to Linear
215
+
216
+ layer_func_kwargs (dict): kwargs for @layer_func
217
+
218
+ activation: non-linearity per layer - defaults to ReLU
219
+
220
+ dropouts ([float]): if not None, adds dropout layers with the corresponding probabilities
221
+ after every layer. Must be same size as @layer_dims.
222
+
223
+ normalization (bool): if True, apply layer normalization after each layer
224
+
225
+ output_activation: if provided, applies the provided non-linearity to the output layer
226
+ """
227
+ super(MLP, self).__init__()
228
+ layers = []
229
+ dim = input_dim
230
+ if layer_func_kwargs is None:
231
+ layer_func_kwargs = dict()
232
+ if dropouts is not None:
233
+ assert(len(dropouts) == len(layer_dims))
234
+ for i, l in enumerate(layer_dims):
235
+ layers.append(layer_func(dim, l, **layer_func_kwargs))
236
+ if normalization:
237
+ layers.append(nn.LayerNorm(l))
238
+ layers.append(activation())
239
+ if dropouts is not None and dropouts[i] > 0.:
240
+ layers.append(nn.Dropout(dropouts[i]))
241
+ dim = l
242
+ layers.append(layer_func(dim, output_dim))
243
+ if output_activation is not None:
244
+ layers.append(output_activation())
245
+ self._layer_func = layer_func
246
+ self.nets = layers
247
+ self._model = nn.Sequential(*layers)
248
+
249
+ self._layer_dims = layer_dims
250
+ self._input_dim = input_dim
251
+ self._output_dim = output_dim
252
+ self._dropouts = dropouts
253
+ self._act = activation
254
+ self._output_act = output_activation
255
+
256
+ def output_shape(self, input_shape=None):
257
+ """
258
+ Function to compute output shape from inputs to this module.
259
+
260
+ Args:
261
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
262
+ Some modules may not need this argument, if their output does not depend
263
+ on the size of the input, or if they assume fixed size input.
264
+
265
+ Returns:
266
+ out_shape ([int]): list of integers corresponding to output shape
267
+ """
268
+ return [self._output_dim]
269
+
270
+ def forward(self, inputs):
271
+ """
272
+ Forward pass.
273
+ """
274
+ return self._model(inputs)
275
+
276
+ def __repr__(self):
277
+ """Pretty print network."""
278
+ header = str(self.__class__.__name__)
279
+ act = None if self._act is None else self._act.__name__
280
+ output_act = None if self._output_act is None else self._output_act.__name__
281
+
282
+ indent = ' ' * 4
283
+ msg = "input_dim={}\noutput_dim={}\nlayer_dims={}\nlayer_func={}\ndropout={}\nact={}\noutput_act={}".format(
284
+ self._input_dim, self._output_dim, self._layer_dims,
285
+ self._layer_func.__name__, self._dropouts, act, output_act
286
+ )
287
+ msg = textwrap.indent(msg, indent)
288
+ msg = header + '(\n' + msg + '\n)'
289
+ return msg
290
+
291
+
292
+ class RNN_Base(Module):
293
+ """
294
+ A wrapper class for a multi-step RNN and a per-step network.
295
+ """
296
+ def __init__(
297
+ self,
298
+ input_dim,
299
+ rnn_hidden_dim,
300
+ rnn_num_layers,
301
+ rnn_type="LSTM", # [LSTM, GRU]
302
+ rnn_kwargs=None,
303
+ per_step_net=None,
304
+ ):
305
+ """
306
+ Args:
307
+ input_dim (int): dimension of inputs
308
+
309
+ rnn_hidden_dim (int): RNN hidden dimension
310
+
311
+ rnn_num_layers (int): number of RNN layers
312
+
313
+ rnn_type (str): [LSTM, GRU]
314
+
315
+ rnn_kwargs (dict): kwargs for the torch.nn.LSTM / GRU
316
+
317
+ per_step_net: a network that runs per time step on top of the RNN output
318
+ """
319
+ super(RNN_Base, self).__init__()
320
+ self.per_step_net = per_step_net
321
+ if per_step_net is not None:
322
+ assert isinstance(per_step_net, Module), "RNN_Base: per_step_net is not instance of Module"
323
+
324
+ assert rnn_type in ["LSTM", "GRU"]
325
+ rnn_cls = nn.LSTM if rnn_type == "LSTM" else nn.GRU
326
+ rnn_kwargs = rnn_kwargs if rnn_kwargs is not None else {}
327
+ rnn_is_bidirectional = rnn_kwargs.get("bidirectional", False)
328
+
329
+ self.nets = rnn_cls(
330
+ input_size=input_dim,
331
+ hidden_size=rnn_hidden_dim,
332
+ num_layers=rnn_num_layers,
333
+ batch_first=True,
334
+ **rnn_kwargs,
335
+ )
336
+
337
+ self._hidden_dim = rnn_hidden_dim
338
+ self._num_layers = rnn_num_layers
339
+ self._rnn_type = rnn_type
340
+ self._num_directions = int(rnn_is_bidirectional) + 1 # 2 if bidirectional, 1 otherwise
341
+
342
+ @property
343
+ def rnn_type(self):
344
+ return self._rnn_type
345
+
346
+ def get_rnn_init_state(self, batch_size, device):
347
+ """
348
+ Get a default RNN state (zeros)
349
+ Args:
350
+ batch_size (int): batch size dimension
351
+
352
+ device: device the hidden state should be sent to.
353
+
354
+ Returns:
355
+ hidden_state (torch.Tensor or tuple): returns hidden state tensor or tuple of hidden state tensors
356
+ depending on the RNN type
357
+ """
358
+ h_0 = torch.zeros(self._num_layers * self._num_directions, batch_size, self._hidden_dim).to(device)
359
+ if self._rnn_type == "LSTM":
360
+ c_0 = torch.zeros(self._num_layers * self._num_directions, batch_size, self._hidden_dim).to(device)
361
+ return h_0, c_0
362
+ else:
363
+ return h_0
364
+
365
+ def output_shape(self, input_shape):
366
+ """
367
+ Function to compute output shape from inputs to this module.
368
+
369
+ Args:
370
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
371
+ Some modules may not need this argument, if their output does not depend
372
+ on the size of the input, or if they assume fixed size input.
373
+
374
+ Returns:
375
+ out_shape ([int]): list of integers corresponding to output shape
376
+ """
377
+
378
+ # infer time dimension from input shape and add to per_step_net output shape
379
+ if self.per_step_net is not None:
380
+ out = self.per_step_net.output_shape(input_shape[1:])
381
+ if isinstance(out, dict):
382
+ out = {k: [input_shape[0]] + out[k] for k in out}
383
+ else:
384
+ out = [input_shape[0]] + out
385
+ else:
386
+ out = [input_shape[0], self._num_layers * self._hidden_dim]
387
+ return out
388
+
389
+ def forward(self, inputs, rnn_init_state=None, return_state=False):
390
+ """
391
+ Forward a sequence of inputs through the RNN and the per-step network.
392
+
393
+ Args:
394
+ inputs (torch.Tensor): tensor input of shape [B, T, D], where D is the RNN input size
395
+
396
+ rnn_init_state: rnn hidden state, initialize to zero state if set to None
397
+
398
+ return_state (bool): whether to return hidden state
399
+
400
+ Returns:
401
+ outputs: outputs of the per_step_net
402
+
403
+ rnn_state: return rnn state at the end if return_state is set to True
404
+ """
405
+ assert inputs.ndimension() == 3 # [B, T, D]
406
+ batch_size, seq_length, inp_dim = inputs.shape
407
+ if rnn_init_state is None:
408
+ rnn_init_state = self.get_rnn_init_state(batch_size, device=inputs.device)
409
+
410
+ outputs, rnn_state = self.nets(inputs, rnn_init_state)
411
+ if self.per_step_net is not None:
412
+ outputs = TensorUtils.time_distributed(outputs, self.per_step_net)
413
+
414
+ if return_state:
415
+ return outputs, rnn_state
416
+ else:
417
+ return outputs
418
+
419
+ def forward_step(self, inputs, rnn_state):
420
+ """
421
+ Forward a single step input through the RNN and per-step network, and return the new hidden state.
422
+ Args:
423
+ inputs (torch.Tensor): tensor input of shape [B, D], where D is the RNN input size
424
+
425
+ rnn_state: rnn hidden state, initialize to zero state if set to None
426
+
427
+ Returns:
428
+ outputs: outputs of the per_step_net
429
+
430
+ rnn_state: return the new rnn state
431
+ """
432
+ assert inputs.ndimension() == 2
433
+ inputs = TensorUtils.to_sequence(inputs)
434
+ outputs, rnn_state = self.forward(
435
+ inputs,
436
+ rnn_init_state=rnn_state,
437
+ return_state=True,
438
+ )
439
+ return outputs[:, 0], rnn_state
440
+
441
+
442
+ """
443
+ ================================================
444
+ Visual Backbone Networks
445
+ ================================================
446
+ """
447
+ class ConvBase(Module):
448
+ """
449
+ Base class for ConvNets.
450
+ """
451
+ def __init__(self):
452
+ super(ConvBase, self).__init__()
453
+
454
+ # dirty hack - re-implement to pass the buck onto subclasses from ABC parent
455
+ def output_shape(self, input_shape):
456
+ """
457
+ Function to compute output shape from inputs to this module.
458
+
459
+ Args:
460
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
461
+ Some modules may not need this argument, if their output does not depend
462
+ on the size of the input, or if they assume fixed size input.
463
+
464
+ Returns:
465
+ out_shape ([int]): list of integers corresponding to output shape
466
+ """
467
+ raise NotImplementedError
468
+
469
+ def forward(self, inputs):
470
+ x = self.nets(inputs)
471
+ if list(self.output_shape(list(inputs.shape)[1:])) != list(x.shape)[1:]:
472
+ raise ValueError('Size mismatch: expect size %s, but got size %s' % (
473
+ str(self.output_shape(list(inputs.shape)[1:])), str(list(x.shape)[1:]))
474
+ )
475
+ return x
476
+
477
+
478
+ class ResNet18Conv(ConvBase):
479
+ """
480
+ A ResNet18 block that can be used to process input images.
481
+ """
482
+ def __init__(
483
+ self,
484
+ input_channel=3,
485
+ pretrained=False,
486
+ input_coord_conv=False,
487
+ ):
488
+ """
489
+ Args:
490
+ input_channel (int): number of input channels for input images to the network.
491
+ If not equal to 3, modifies first conv layer in ResNet to handle the number
492
+ of input channels.
493
+ pretrained (bool): if True, load pretrained weights for all ResNet layers.
494
+ input_coord_conv (bool): if True, use a coordinate convolution for the first layer
495
+ (a convolution where input channels are modified to encode spatial pixel location)
496
+ """
497
+ super(ResNet18Conv, self).__init__()
498
+ net = vision_models.resnet18(pretrained=pretrained)
499
+
500
+ if input_coord_conv:
501
+ net.conv1 = CoordConv2d(input_channel, 64, kernel_size=7, stride=2, padding=3, bias=False)
502
+ elif input_channel != 3:
503
+ net.conv1 = nn.Conv2d(input_channel, 64, kernel_size=7, stride=2, padding=3, bias=False)
504
+
505
+ # cut the last fc layer
506
+ self._input_coord_conv = input_coord_conv
507
+ self._input_channel = input_channel
508
+ self.nets = torch.nn.Sequential(*(list(net.children())[:-2]))
509
+
510
+ def output_shape(self, input_shape):
511
+ """
512
+ Function to compute output shape from inputs to this module.
513
+
514
+ Args:
515
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
516
+ Some modules may not need this argument, if their output does not depend
517
+ on the size of the input, or if they assume fixed size input.
518
+
519
+ Returns:
520
+ out_shape ([int]): list of integers corresponding to output shape
521
+ """
522
+ assert(len(input_shape) == 3)
523
+ out_h = int(math.ceil(input_shape[1] / 32.))
524
+ out_w = int(math.ceil(input_shape[2] / 32.))
525
+ return [512, out_h, out_w]
526
+
527
+ def __repr__(self):
528
+ """Pretty print network."""
529
+ header = '{}'.format(str(self.__class__.__name__))
530
+ return header + '(input_channel={}, input_coord_conv={})'.format(self._input_channel, self._input_coord_conv)
531
+
532
+
533
+ class ResNet50Conv(ConvBase):
534
+ """
535
+ A ResNet50 block that can be used to process input images.
536
+ """
537
+ def __init__(
538
+ self,
539
+ input_channel=3,
540
+ pretrained=False,
541
+ input_coord_conv=False,
542
+ ):
543
+ """
544
+ Args:
545
+ input_channel (int): number of input channels for input images to the network.
546
+ If not equal to 3, modifies first conv layer in ResNet to handle the number
547
+ of input channels.
548
+ pretrained (bool): if True, load pretrained weights for all ResNet layers.
549
+ input_coord_conv (bool): if True, use a coordinate convolution for the first layer
550
+ (a convolution where input channels are modified to encode spatial pixel location)
551
+ """
552
+ super(ResNet50Conv, self).__init__()
553
+ net = vision_models.resnet50(pretrained=pretrained)
554
+
555
+ if input_coord_conv:
556
+ # copied from ResNet18Conv. TODO: check if sizes need to be changed
557
+ net.conv1 = CoordConv2d(input_channel, 64, kernel_size=7, stride=2, padding=3, bias=False)
558
+ elif input_channel != 3:
559
+ # copied from ResNet18Conv. TODO: check if sizes need to be changed
560
+ net.conv1 = nn.Conv2d(input_channel, 64, kernel_size=7, stride=2, padding=3, bias=False)
561
+
562
+ # cut the last fc layer
563
+ self._input_coord_conv = input_coord_conv
564
+ self._input_channel = input_channel
565
+ self.nets = torch.nn.Sequential(*(list(net.children())[:-2]))
566
+
567
+ def output_shape(self, input_shape):
568
+ """
569
+ Function to compute output shape from inputs to this module.
570
+
571
+ Args:
572
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
573
+ Some modules may not need this argument, if their output does not depend
574
+ on the size of the input, or if they assume fixed size input.
575
+
576
+ Returns:
577
+ out_shape ([int]): list of integers corresponding to output shape
578
+ """
579
+ assert(len(input_shape) == 3)
580
+ out_h = int(math.ceil(input_shape[1] / 32.))
581
+ out_w = int(math.ceil(input_shape[2] / 32.))
582
+ return [2048, out_h, out_w]
583
+
584
+ def __repr__(self):
585
+ """Pretty print network."""
586
+ header = '{}'.format(str(self.__class__.__name__))
587
+ return header + '(input_channel={}, input_coord_conv={})'.format(self._input_channel, self._input_coord_conv)
588
+
589
+
590
+ class R3MConv(ConvBase):
591
+ """
592
+ Base class for ConvNets pretrained with R3M (https://arxiv.org/abs/2203.12601)
593
+ """
594
+ def __init__(
595
+ self,
596
+ input_channel=3,
597
+ r3m_model_class='resnet18',
598
+ freeze=True,
599
+ ):
600
+ """
601
+ Using R3M pretrained observation encoder network proposed by https://arxiv.org/abs/2203.12601
602
+ Args:
603
+ input_channel (int): number of input channels for input images to the network.
604
+ If not equal to 3, modifies first conv layer in ResNet to handle the number
605
+ of input channels.
606
+ r3m_model_class (str): select one of the r3m pretrained model "resnet18", "resnet34" or "resnet50"
607
+ freeze (bool): if True, use a frozen R3M pretrained model.
608
+ """
609
+ super(R3MConv, self).__init__()
610
+
611
+ try:
612
+ from r3m import load_r3m
613
+ except ImportError:
614
+ print("WARNING: could not load r3m library! Please follow https://github.com/facebookresearch/r3m to install R3M")
615
+
616
+ net = load_r3m(r3m_model_class)
617
+
618
+ assert input_channel == 3 # R3M only support input image with channel size 3
619
+ assert r3m_model_class in ["resnet18", "resnet34", "resnet50"] # make sure the selected r3m model do exist
620
+
621
+ # cut the last fc layer
622
+ self._input_channel = input_channel
623
+ self._r3m_model_class = r3m_model_class
624
+ self._freeze = freeze
625
+ self._input_coord_conv = False
626
+ self._pretrained = True
627
+
628
+ preprocess = nn.Sequential(
629
+ transforms.Resize(256),
630
+ transforms.CenterCrop(224),
631
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
632
+ )
633
+ self.nets = Sequential(*([preprocess] + list(net.module.convnet.children())))
634
+ if freeze:
635
+ self.nets.freeze()
636
+
637
+ self.weight_sum = np.sum([param.cpu().data.numpy().sum() for param in self.nets.parameters()])
638
+ if freeze:
639
+ for param in self.nets.parameters():
640
+ param.requires_grad = False
641
+
642
+ self.nets.eval()
643
+
644
+ def output_shape(self, input_shape):
645
+ """
646
+ Function to compute output shape from inputs to this module.
647
+ Args:
648
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
649
+ Some modules may not need this argument, if their output does not depend
650
+ on the size of the input, or if they assume fixed size input.
651
+ Returns:
652
+ out_shape ([int]): list of integers corresponding to output shape
653
+ """
654
+ assert(len(input_shape) == 3)
655
+
656
+ if self._r3m_model_class == 'resnet50':
657
+ out_dim = 2048
658
+ else:
659
+ out_dim = 512
660
+
661
+ return [out_dim, 1, 1]
662
+
663
+ def __repr__(self):
664
+ """Pretty print network."""
665
+ header = '{}'.format(str(self.__class__.__name__))
666
+ return header + '(input_channel={}, input_coord_conv={}, pretrained={}, freeze={})'.format(self._input_channel, self._input_coord_conv, self._pretrained, self._freeze)
667
+
668
+
669
+ class MVPConv(ConvBase):
670
+ """
671
+ Base class for ConvNets pretrained with MVP (https://arxiv.org/abs/2203.06173)
672
+ """
673
+ def __init__(
674
+ self,
675
+ input_channel=3,
676
+ mvp_model_class='vitb-mae-egosoup',
677
+ freeze=True,
678
+ ):
679
+ """
680
+ Using MVP pretrained observation encoder network proposed by https://arxiv.org/abs/2203.06173
681
+ Args:
682
+ input_channel (int): number of input channels for input images to the network.
683
+ If not equal to 3, modifies first conv layer in ResNet to handle the number
684
+ of input channels.
685
+ mvp_model_class (str): select one of the mvp pretrained model "vits-mae-hoi", "vits-mae-in", "vits-sup-in", "vitb-mae-egosoup" or "vitl-256-mae-egosoup"
686
+ freeze (bool): if True, use a frozen MVP pretrained model.
687
+ """
688
+ super(MVPConv, self).__init__()
689
+
690
+ try:
691
+ import mvp
692
+ except ImportError:
693
+ print("WARNING: could not load mvp library! Please follow https://github.com/ir413/mvp to install MVP.")
694
+
695
+ self.nets = mvp.load(mvp_model_class)
696
+ if freeze:
697
+ self.nets.freeze()
698
+
699
+ assert input_channel == 3 # MVP only support input image with channel size 3
700
+ assert mvp_model_class in ["vits-mae-hoi", "vits-mae-in", "vits-sup-in", "vitb-mae-egosoup", "vitl-256-mae-egosoup"] # make sure the selected r3m model do exist
701
+
702
+ self._input_channel = input_channel
703
+ self._freeze = freeze
704
+ self._mvp_model_class = mvp_model_class
705
+ self._input_coord_conv = False
706
+ self._pretrained = True
707
+
708
+ if '256' in mvp_model_class:
709
+ input_img_size = 256
710
+ else:
711
+ input_img_size = 224
712
+ self.preprocess = nn.Sequential(
713
+ transforms.Resize(input_img_size)
714
+ )
715
+
716
+ def forward(self, inputs):
717
+ x = self.preprocess(inputs)
718
+ x = self.nets(x)
719
+ if list(self.output_shape(list(inputs.shape)[1:])) != list(x.shape)[1:]:
720
+ raise ValueError('Size mismatch: expect size %s, but got size %s' % (
721
+ str(self.output_shape(list(inputs.shape)[1:])), str(list(x.shape)[1:]))
722
+ )
723
+ return x
724
+
725
+ def output_shape(self, input_shape):
726
+ """
727
+ Function to compute output shape from inputs to this module.
728
+ Args:
729
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
730
+ Some modules may not need this argument, if their output does not depend
731
+ on the size of the input, or if they assume fixed size input.
732
+ Returns:
733
+ out_shape ([int]): list of integers corresponding to output shape
734
+ """
735
+ assert(len(input_shape) == 3)
736
+ if 'vitb' in self._mvp_model_class:
737
+ output_shape = [768]
738
+ elif 'vitl' in self._mvp_model_class:
739
+ output_shape = [1024]
740
+ else:
741
+ output_shape = [384]
742
+ return output_shape
743
+
744
+ def __repr__(self):
745
+ """Pretty print network."""
746
+ header = '{}'.format(str(self.__class__.__name__))
747
+ return header + '(input_channel={}, input_coord_conv={}, pretrained={}, freeze={})'.format(self._input_channel, self._input_coord_conv, self._pretrained, self._freeze)
748
+
749
+
750
+ class CoordConv2d(nn.Conv2d, Module):
751
+ """
752
+ 2D Coordinate Convolution
753
+
754
+ Source: An Intriguing Failing of Convolutional Neural Networks and the CoordConv Solution
755
+ https://arxiv.org/abs/1807.03247
756
+ (e.g. adds 2 channels per input feature map corresponding to (x, y) location on map)
757
+ """
758
+ def __init__(
759
+ self,
760
+ in_channels,
761
+ out_channels,
762
+ kernel_size,
763
+ stride=1,
764
+ padding=0,
765
+ dilation=1,
766
+ groups=1,
767
+ bias=True,
768
+ padding_mode='zeros',
769
+ coord_encoding='position',
770
+ ):
771
+ """
772
+ Args:
773
+ in_channels: number of channels of the input tensor [C, H, W]
774
+ out_channels: number of output channels of the layer
775
+ kernel_size: convolution kernel size
776
+ stride: conv stride
777
+ padding: conv padding
778
+ dilation: conv dilation
779
+ groups: conv groups
780
+ bias: conv bias
781
+ padding_mode: conv padding mode
782
+ coord_encoding: type of coordinate encoding. currently only 'position' is implemented
783
+ """
784
+
785
+ assert(coord_encoding in ['position'])
786
+ self.coord_encoding = coord_encoding
787
+ if coord_encoding == 'position':
788
+ in_channels += 2 # two extra channel for positional encoding
789
+ self._position_enc = None # position encoding
790
+ else:
791
+ raise Exception("CoordConv2d: coord encoding {} not implemented".format(self.coord_encoding))
792
+ nn.Conv2d.__init__(
793
+ self,
794
+ in_channels=in_channels,
795
+ out_channels=out_channels,
796
+ kernel_size=kernel_size,
797
+ stride=stride,
798
+ padding=padding,
799
+ dilation=dilation,
800
+ groups=groups,
801
+ bias=bias,
802
+ padding_mode=padding_mode
803
+ )
804
+
805
+ def output_shape(self, input_shape):
806
+ """
807
+ Function to compute output shape from inputs to this module.
808
+
809
+ Args:
810
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
811
+ Some modules may not need this argument, if their output does not depend
812
+ on the size of the input, or if they assume fixed size input.
813
+
814
+ Returns:
815
+ out_shape ([int]): list of integers corresponding to output shape
816
+ """
817
+
818
+ # adds 2 to channel dimension
819
+ return [input_shape[0] + 2] + input_shape[1:]
820
+
821
+ def forward(self, input):
822
+ b, c, h, w = input.shape
823
+ if self.coord_encoding == 'position':
824
+ if self._position_enc is None:
825
+ pos_y, pos_x = torch.meshgrid(torch.arange(h), torch.arange(w))
826
+ pos_y = pos_y.float().to(input.device) / float(h)
827
+ pos_x = pos_x.float().to(input.device) / float(w)
828
+ self._position_enc = torch.stack((pos_y, pos_x)).unsqueeze(0)
829
+ pos_enc = self._position_enc.expand(b, -1, -1, -1)
830
+ input = torch.cat((input, pos_enc), dim=1)
831
+ return super(CoordConv2d, self).forward(input)
832
+
833
+
834
+ class ShallowConv(ConvBase):
835
+ """
836
+ A shallow convolutional encoder from https://rll.berkeley.edu/dsae/dsae.pdf
837
+ """
838
+ def __init__(self, input_channel=3, output_channel=32):
839
+ super(ShallowConv, self).__init__()
840
+ self._input_channel = input_channel
841
+ self._output_channel = output_channel
842
+ self.nets = nn.Sequential(
843
+ torch.nn.Conv2d(input_channel, 64, kernel_size=7, stride=2, padding=3),
844
+ torch.nn.ReLU(),
845
+ torch.nn.Conv2d(64, 32, kernel_size=1, stride=1, padding=0),
846
+ torch.nn.ReLU(),
847
+ torch.nn.Conv2d(32, 32, kernel_size=3, stride=1, padding=1),
848
+ torch.nn.ReLU(),
849
+ torch.nn.Conv2d(32, 32, kernel_size=3, stride=1, padding=1),
850
+ )
851
+
852
+ def output_shape(self, input_shape):
853
+ """
854
+ Function to compute output shape from inputs to this module.
855
+
856
+ Args:
857
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
858
+ Some modules may not need this argument, if their output does not depend
859
+ on the size of the input, or if they assume fixed size input.
860
+
861
+ Returns:
862
+ out_shape ([int]): list of integers corresponding to output shape
863
+ """
864
+ assert(len(input_shape) == 3)
865
+ assert(input_shape[0] == self._input_channel)
866
+ out_h = int(math.floor(input_shape[1] / 2.))
867
+ out_w = int(math.floor(input_shape[2] / 2.))
868
+ return [self._output_channel, out_h, out_w]
869
+
870
+
871
+ class Conv1dBase(Module):
872
+ """
873
+ Base class for stacked Conv1d layers.
874
+
875
+ Args:
876
+ input_channel (int): Number of channels for inputs to this network
877
+ activation (None or str): Per-layer activation to use. Defaults to "relu". Valid options are
878
+ currently {relu, None} for no activation
879
+ out_channels (list of int): Output channel size for each sequential Conv1d layer
880
+ kernel_size (list of int): Kernel sizes for each sequential Conv1d layer
881
+ stride (list of int): Stride sizes for each sequential Conv1d layer
882
+ conv_kwargs (dict): additional nn.Conv1D args to use, in list form, where the ith element corresponds to the
883
+ argument to be passed to the ith Conv1D layer.
884
+ See https://pytorch.org/docs/stable/generated/torch.nn.Conv1d.html for specific possible arguments.
885
+ """
886
+ def __init__(
887
+ self,
888
+ input_channel=1,
889
+ activation="relu",
890
+ out_channels=(32, 64, 64),
891
+ kernel_size=(8, 4, 2),
892
+ stride=(4, 2, 1),
893
+ **conv_kwargs,
894
+ ):
895
+ super(Conv1dBase, self).__init__()
896
+
897
+ # Get activation requested
898
+ activation = CONV_ACTIVATIONS[activation]
899
+
900
+ # Generate network
901
+ self.n_layers = len(out_channels)
902
+ layers = OrderedDict()
903
+ for i in range(self.n_layers):
904
+ layer_kwargs = {k: v[i] for k, v in conv_kwargs.items()}
905
+ layers[f'conv{i}'] = nn.Conv1d(
906
+ in_channels=input_channel,
907
+ **layer_kwargs,
908
+ )
909
+ if activation is not None:
910
+ layers[f'act{i}'] = activation()
911
+ input_channel = layer_kwargs["out_channels"]
912
+
913
+ # Store network
914
+ self.nets = nn.Sequential(layers)
915
+
916
+ def output_shape(self, input_shape):
917
+ """
918
+ Function to compute output shape from inputs to this module.
919
+
920
+ Args:
921
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
922
+ Some modules may not need this argument, if their output does not depend
923
+ on the size of the input, or if they assume fixed size input.
924
+
925
+ Returns:
926
+ out_shape ([int]): list of integers corresponding to output shape
927
+ """
928
+ channels, length = input_shape
929
+ for i in range(self.n_layers):
930
+ net = getattr(self.nets, f"conv{i}")
931
+ channels = net.out_channels
932
+ length = int((length + 2 * net.padding[0] - net.dilation[0] * (net.kernel_size[0] - 1) - 1) / net.stride[0]) + 1
933
+ return [channels, length]
934
+
935
+ def forward(self, inputs):
936
+ x = self.nets(inputs)
937
+ if list(self.output_shape(list(inputs.shape)[1:])) != list(x.shape)[1:]:
938
+ raise ValueError('Size mismatch: expect size %s, but got size %s' % (
939
+ str(self.output_shape(list(inputs.shape)[1:])), str(list(x.shape)[1:]))
940
+ )
941
+ return x
942
+
943
+
944
+ """
945
+ ================================================
946
+ Pooling Networks
947
+ ================================================
948
+ """
949
+ class SpatialSoftmax(ConvBase):
950
+ """
951
+ Spatial Softmax Layer.
952
+
953
+ Based on Deep Spatial Autoencoders for Visuomotor Learning by Finn et al.
954
+ https://rll.berkeley.edu/dsae/dsae.pdf
955
+ """
956
+ def __init__(
957
+ self,
958
+ input_shape,
959
+ num_kp=32,
960
+ temperature=1.,
961
+ learnable_temperature=False,
962
+ output_variance=False,
963
+ noise_std=0.0,
964
+ ):
965
+ """
966
+ Args:
967
+ input_shape (list): shape of the input feature (C, H, W)
968
+ num_kp (int): number of keypoints (None for not using spatialsoftmax)
969
+ temperature (float): temperature term for the softmax.
970
+ learnable_temperature (bool): whether to learn the temperature
971
+ output_variance (bool): treat attention as a distribution, and compute second-order statistics to return
972
+ noise_std (float): add random spatial noise to the predicted keypoints
973
+ """
974
+ super(SpatialSoftmax, self).__init__()
975
+ assert len(input_shape) == 3
976
+ self._in_c, self._in_h, self._in_w = input_shape # (C, H, W)
977
+
978
+ if num_kp is not None:
979
+ self.nets = torch.nn.Conv2d(self._in_c, num_kp, kernel_size=1)
980
+ self._num_kp = num_kp
981
+ else:
982
+ self.nets = None
983
+ self._num_kp = self._in_c
984
+ self.learnable_temperature = learnable_temperature
985
+ self.output_variance = output_variance
986
+ self.noise_std = noise_std
987
+
988
+ if self.learnable_temperature:
989
+ # temperature will be learned
990
+ temperature = torch.nn.Parameter(torch.ones(1) * temperature, requires_grad=True)
991
+ self.register_parameter('temperature', temperature)
992
+ else:
993
+ # temperature held constant after initialization
994
+ temperature = torch.nn.Parameter(torch.ones(1) * temperature, requires_grad=False)
995
+ self.register_buffer('temperature', temperature)
996
+
997
+ pos_x, pos_y = np.meshgrid(
998
+ np.linspace(-1., 1., self._in_w),
999
+ np.linspace(-1., 1., self._in_h)
1000
+ )
1001
+ pos_x = torch.from_numpy(pos_x.reshape(1, self._in_h * self._in_w)).float()
1002
+ pos_y = torch.from_numpy(pos_y.reshape(1, self._in_h * self._in_w)).float()
1003
+ self.register_buffer('pos_x', pos_x)
1004
+ self.register_buffer('pos_y', pos_y)
1005
+
1006
+ self.kps = None
1007
+
1008
+ def __repr__(self):
1009
+ """Pretty print network."""
1010
+ header = format(str(self.__class__.__name__))
1011
+ return header + '(num_kp={}, temperature={}, noise={})'.format(
1012
+ self._num_kp, self.temperature.item(), self.noise_std)
1013
+
1014
+ def output_shape(self, input_shape):
1015
+ """
1016
+ Function to compute output shape from inputs to this module.
1017
+
1018
+ Args:
1019
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
1020
+ Some modules may not need this argument, if their output does not depend
1021
+ on the size of the input, or if they assume fixed size input.
1022
+
1023
+ Returns:
1024
+ out_shape ([int]): list of integers corresponding to output shape
1025
+ """
1026
+ assert(len(input_shape) == 3)
1027
+ assert(input_shape[0] == self._in_c)
1028
+ return [self._num_kp, 2]
1029
+
1030
+ def forward(self, feature):
1031
+ """
1032
+ Forward pass through spatial softmax layer. For each keypoint, a 2D spatial
1033
+ probability distribution is created using a softmax, where the support is the
1034
+ pixel locations. This distribution is used to compute the expected value of
1035
+ the pixel location, which becomes a keypoint of dimension 2. K such keypoints
1036
+ are created.
1037
+
1038
+ Returns:
1039
+ out (torch.Tensor or tuple): mean keypoints of shape [B, K, 2], and possibly
1040
+ keypoint variance of shape [B, K, 2, 2] corresponding to the covariance
1041
+ under the 2D spatial softmax distribution
1042
+ """
1043
+ assert(feature.shape[1] == self._in_c)
1044
+ assert(feature.shape[2] == self._in_h)
1045
+ assert(feature.shape[3] == self._in_w)
1046
+ if self.nets is not None:
1047
+ feature = self.nets(feature)
1048
+
1049
+ # [B, K, H, W] -> [B * K, H * W] where K is number of keypoints
1050
+ feature = feature.reshape(-1, self._in_h * self._in_w)
1051
+ # 2d softmax normalization
1052
+ attention = F.softmax(feature / self.temperature, dim=-1)
1053
+ # [1, H * W] x [B * K, H * W] -> [B * K, 1] for spatial coordinate mean in x and y dimensions
1054
+ expected_x = torch.sum(self.pos_x * attention, dim=1, keepdim=True)
1055
+ expected_y = torch.sum(self.pos_y * attention, dim=1, keepdim=True)
1056
+ # stack to [B * K, 2]
1057
+ expected_xy = torch.cat([expected_x, expected_y], 1)
1058
+ # reshape to [B, K, 2]
1059
+ feature_keypoints = expected_xy.view(-1, self._num_kp, 2)
1060
+
1061
+ if self.training:
1062
+ noise = torch.randn_like(feature_keypoints) * self.noise_std
1063
+ feature_keypoints += noise
1064
+
1065
+ if self.output_variance:
1066
+ # treat attention as a distribution, and compute second-order statistics to return
1067
+ expected_xx = torch.sum(self.pos_x * self.pos_x * attention, dim=1, keepdim=True)
1068
+ expected_yy = torch.sum(self.pos_y * self.pos_y * attention, dim=1, keepdim=True)
1069
+ expected_xy = torch.sum(self.pos_x * self.pos_y * attention, dim=1, keepdim=True)
1070
+ var_x = expected_xx - expected_x * expected_x
1071
+ var_y = expected_yy - expected_y * expected_y
1072
+ var_xy = expected_xy - expected_x * expected_y
1073
+ # stack to [B * K, 4] and then reshape to [B, K, 2, 2] where last 2 dims are covariance matrix
1074
+ feature_covar = torch.cat([var_x, var_xy, var_xy, var_y], 1).reshape(-1, self._num_kp, 2, 2)
1075
+ feature_keypoints = (feature_keypoints, feature_covar)
1076
+
1077
+ if isinstance(feature_keypoints, tuple):
1078
+ self.kps = (feature_keypoints[0].detach(), feature_keypoints[1].detach())
1079
+ else:
1080
+ self.kps = feature_keypoints.detach()
1081
+ return feature_keypoints
1082
+
1083
+
1084
+ class SpatialMeanPool(Module):
1085
+ """
1086
+ Module that averages inputs across all spatial dimensions (dimension 2 and after),
1087
+ leaving only the batch and channel dimensions.
1088
+ """
1089
+ def __init__(self, input_shape):
1090
+ super(SpatialMeanPool, self).__init__()
1091
+ assert len(input_shape) == 3 # [C, H, W]
1092
+ self.in_shape = input_shape
1093
+
1094
+ def output_shape(self, input_shape=None):
1095
+ """
1096
+ Function to compute output shape from inputs to this module.
1097
+
1098
+ Args:
1099
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
1100
+ Some modules may not need this argument, if their output does not depend
1101
+ on the size of the input, or if they assume fixed size input.
1102
+
1103
+ Returns:
1104
+ out_shape ([int]): list of integers corresponding to output shape
1105
+ """
1106
+ return list(self.in_shape[:1]) # [C, H, W] -> [C]
1107
+
1108
+ def forward(self, inputs):
1109
+ """Forward pass - average across all dimensions except batch and channel."""
1110
+ return TensorUtils.flatten(inputs, begin_axis=2).mean(dim=2)
1111
+
1112
+
1113
+ class FeatureAggregator(Module):
1114
+ """
1115
+ Helpful class for aggregating features across a dimension. This is useful in
1116
+ practice when training models that break an input image up into several patches
1117
+ since features can be extraced per-patch using the same encoder and then
1118
+ aggregated using this module.
1119
+ """
1120
+ def __init__(self, dim=1, agg_type="avg"):
1121
+ super(FeatureAggregator, self).__init__()
1122
+ self.dim = dim
1123
+ self.agg_type = agg_type
1124
+
1125
+ def set_weight(self, w):
1126
+ assert self.agg_type == "w_avg"
1127
+ self.agg_weight = w
1128
+
1129
+ def clear_weight(self):
1130
+ assert self.agg_type == "w_avg"
1131
+ self.agg_weight = None
1132
+
1133
+ def output_shape(self, input_shape):
1134
+ """
1135
+ Function to compute output shape from inputs to this module.
1136
+
1137
+ Args:
1138
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
1139
+ Some modules may not need this argument, if their output does not depend
1140
+ on the size of the input, or if they assume fixed size input.
1141
+
1142
+ Returns:
1143
+ out_shape ([int]): list of integers corresponding to output shape
1144
+ """
1145
+ # aggregates on @self.dim, so it is removed from the output shape
1146
+ return list(input_shape[:self.dim]) + list(input_shape[self.dim+1:])
1147
+
1148
+ def forward(self, x):
1149
+ """Forward pooling pass."""
1150
+ if self.agg_type == "avg":
1151
+ # mean-pooling
1152
+ return torch.mean(x, dim=1)
1153
+ if self.agg_type == "w_avg":
1154
+ # weighted mean-pooling
1155
+ return torch.sum(x * self.agg_weight, dim=1)
1156
+ raise Exception("unexpected agg type: {}".forward(self.agg_type))
aloha-devel/robomimic/models/obs_nets.py ADDED
@@ -0,0 +1,1121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Contains torch Modules that help deal with inputs consisting of multiple
3
+ modalities. This is extremely common when networks must deal with one or
4
+ more observation dictionaries, where each input dictionary can have
5
+ observation keys of a certain modality and shape.
6
+
7
+ As an example, an observation could consist of a flat "robot0_eef_pos" observation key,
8
+ and a 3-channel RGB "agentview_image" observation key.
9
+ """
10
+ import sys
11
+ import numpy as np
12
+ import textwrap
13
+ from copy import deepcopy
14
+ from collections import OrderedDict
15
+
16
+ import torch
17
+ import torch.nn as nn
18
+ import torch.nn.functional as F
19
+ import torch.distributions as D
20
+
21
+ from robomimic.utils.python_utils import extract_class_init_kwargs_from_dict
22
+ import robomimic.utils.tensor_utils as TensorUtils
23
+ import robomimic.utils.obs_utils as ObsUtils
24
+ from robomimic.models.base_nets import Module, Sequential, MLP, RNN_Base, ResNet18Conv, SpatialSoftmax, \
25
+ FeatureAggregator
26
+ from robomimic.models.obs_core import VisualCore, Randomizer
27
+ from robomimic.models.transformers import PositionalEncoding, GPT_Backbone
28
+
29
+
30
+ def obs_encoder_factory(
31
+ obs_shapes,
32
+ feature_activation=nn.ReLU,
33
+ encoder_kwargs=None,
34
+ ):
35
+ """
36
+ Utility function to create an @ObservationEncoder from kwargs specified in config.
37
+
38
+ Args:
39
+ obs_shapes (OrderedDict): a dictionary that maps observation key to
40
+ expected shapes for observations.
41
+
42
+ feature_activation: non-linearity to apply after each obs net - defaults to ReLU. Pass
43
+ None to apply no activation.
44
+
45
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should be
46
+ nested dictionary containing relevant per-modality information for encoder networks.
47
+ Should be of form:
48
+
49
+ obs_modality1: dict
50
+ feature_dimension: int
51
+ core_class: str
52
+ core_kwargs: dict
53
+ ...
54
+ ...
55
+ obs_randomizer_class: str
56
+ obs_randomizer_kwargs: dict
57
+ ...
58
+ ...
59
+ obs_modality2: dict
60
+ ...
61
+ """
62
+ enc = ObservationEncoder(feature_activation=feature_activation)
63
+ for k, obs_shape in obs_shapes.items():
64
+ obs_modality = ObsUtils.OBS_KEYS_TO_MODALITIES[k]
65
+ enc_kwargs = deepcopy(ObsUtils.DEFAULT_ENCODER_KWARGS[obs_modality]) if encoder_kwargs is None else \
66
+ deepcopy(encoder_kwargs[obs_modality])
67
+
68
+ # Sanity check for kwargs in case they don't exist / are None
69
+ if enc_kwargs.get("core_kwargs", None) is None:
70
+ enc_kwargs["core_kwargs"] = {}
71
+ # Add in input shape info
72
+ enc_kwargs["core_kwargs"]["input_shape"] = obs_shape
73
+ # If group class is specified, then make sure corresponding kwargs only contain relevant kwargs
74
+ if enc_kwargs["core_class"] is not None:
75
+ enc_kwargs["core_kwargs"] = extract_class_init_kwargs_from_dict(
76
+ cls=ObsUtils.OBS_ENCODER_CORES[enc_kwargs["core_class"]],
77
+ dic=enc_kwargs["core_kwargs"],
78
+ copy=False,
79
+ )
80
+
81
+ # Add in input shape info
82
+ randomizers = []
83
+ obs_randomizer_class_list = enc_kwargs["obs_randomizer_class"]
84
+ obs_randomizer_kwargs_list = enc_kwargs["obs_randomizer_kwargs"]
85
+
86
+ if not isinstance(obs_randomizer_class_list, list):
87
+ obs_randomizer_class_list = [obs_randomizer_class_list]
88
+
89
+ if not isinstance(obs_randomizer_kwargs_list, list):
90
+ obs_randomizer_kwargs_list = [obs_randomizer_kwargs_list]
91
+
92
+ for rand_class, rand_kwargs in zip(obs_randomizer_class_list, obs_randomizer_kwargs_list):
93
+ rand = None
94
+ if rand_class is not None:
95
+ rand_kwargs["input_shape"] = obs_shape
96
+ rand_kwargs = extract_class_init_kwargs_from_dict(
97
+ cls=ObsUtils.OBS_RANDOMIZERS[rand_class],
98
+ dic=rand_kwargs,
99
+ copy=False,
100
+ )
101
+ rand = ObsUtils.OBS_RANDOMIZERS[rand_class](**rand_kwargs)
102
+ randomizers.append(rand)
103
+
104
+ enc.register_obs_key(
105
+ name=k,
106
+ shape=obs_shape,
107
+ net_class=enc_kwargs["core_class"],
108
+ net_kwargs=enc_kwargs["core_kwargs"],
109
+ randomizers=randomizers,
110
+ )
111
+
112
+ enc.make()
113
+ return enc
114
+
115
+
116
+ class ObservationEncoder(Module):
117
+ """
118
+ Module that processes inputs by observation key and then concatenates the processed
119
+ observation keys together. Each key is processed with an encoder head network.
120
+ Call @register_obs_key to register observation keys with the encoder and then
121
+ finally call @make to create the encoder networks.
122
+ """
123
+ def __init__(self, feature_activation=nn.ReLU):
124
+ """
125
+ Args:
126
+ feature_activation: non-linearity to apply after each obs net - defaults to ReLU. Pass
127
+ None to apply no activation.
128
+ """
129
+ super(ObservationEncoder, self).__init__()
130
+ self.obs_shapes = OrderedDict()
131
+ self.obs_nets_classes = OrderedDict()
132
+ self.obs_nets_kwargs = OrderedDict()
133
+ self.obs_share_mods = OrderedDict()
134
+ self.obs_nets = nn.ModuleDict()
135
+ self.obs_randomizers = nn.ModuleDict()
136
+ self.feature_activation = feature_activation
137
+ self._locked = False
138
+
139
+ def register_obs_key(
140
+ self,
141
+ name,
142
+ shape,
143
+ net_class=None,
144
+ net_kwargs=None,
145
+ net=None,
146
+ randomizers=None,
147
+ share_net_from=None,
148
+ ):
149
+ """
150
+ Register an observation key that this encoder should be responsible for.
151
+
152
+ Args:
153
+ name (str): modality name
154
+ shape (int tuple): shape of modality
155
+ net_class (str): name of class in base_nets.py that should be used
156
+ to process this observation key before concatenation. Pass None to flatten
157
+ and concatenate the observation key directly.
158
+ net_kwargs (dict): arguments to pass to @net_class
159
+ net (Module instance): if provided, use this Module to process the observation key
160
+ instead of creating a different net
161
+ randomizer (Randomizer instance): if provided, use this Module to augment observation keys
162
+ coming in to the encoder, and possibly augment the processed output as well
163
+ share_net_from (str): if provided, use the same instance of @net_class
164
+ as another observation key. This observation key must already exist in this encoder.
165
+ Warning: Note that this does not share the observation key randomizer
166
+ """
167
+ assert not self._locked, "ObservationEncoder: @register_obs_key called after @make"
168
+ assert name not in self.obs_shapes, "ObservationEncoder: modality {} already exists".format(name)
169
+
170
+ if net is not None:
171
+ assert isinstance(net, Module), "ObservationEncoder: @net must be instance of Module class"
172
+ assert (net_class is None) and (net_kwargs is None) and (share_net_from is None), \
173
+ "ObservationEncoder: @net provided - ignore other net creation options"
174
+
175
+ if share_net_from is not None:
176
+ # share processing with another modality
177
+ assert (net_class is None) and (net_kwargs is None)
178
+ assert share_net_from in self.obs_shapes
179
+
180
+ net_kwargs = deepcopy(net_kwargs) if net_kwargs is not None else {}
181
+ for rand in randomizers:
182
+ if rand is not None:
183
+ assert isinstance(rand, Randomizer)
184
+ if net_kwargs is not None:
185
+ # update input shape to visual core
186
+ net_kwargs["input_shape"] = rand.output_shape_in(shape)
187
+
188
+ self.obs_shapes[name] = shape
189
+ self.obs_nets_classes[name] = net_class
190
+ self.obs_nets_kwargs[name] = net_kwargs
191
+ self.obs_nets[name] = net
192
+ self.obs_randomizers[name] = nn.ModuleList(randomizers)
193
+ self.obs_share_mods[name] = share_net_from
194
+
195
+ def make(self):
196
+ """
197
+ Creates the encoder networks and locks the encoder so that more modalities cannot be added.
198
+ """
199
+ assert not self._locked, "ObservationEncoder: @make called more than once"
200
+ self._create_layers()
201
+ self._locked = True
202
+
203
+ def _create_layers(self):
204
+ """
205
+ Creates all networks and layers required by this encoder using the registered modalities.
206
+ """
207
+ assert not self._locked, "ObservationEncoder: layers have already been created"
208
+
209
+ for k in self.obs_shapes:
210
+ if self.obs_nets_classes[k] is not None:
211
+ # create net to process this modality
212
+ self.obs_nets[k] = ObsUtils.OBS_ENCODER_CORES[self.obs_nets_classes[k]](**self.obs_nets_kwargs[k])
213
+ elif self.obs_share_mods[k] is not None:
214
+ # make sure net is shared with another modality
215
+ self.obs_nets[k] = self.obs_nets[self.obs_share_mods[k]]
216
+
217
+ self.activation = None
218
+ if self.feature_activation is not None:
219
+ self.activation = self.feature_activation()
220
+
221
+ def forward(self, obs_dict):
222
+ """
223
+ Processes modalities according to the ordering in @self.obs_shapes. For each
224
+ modality, it is processed with a randomizer (if present), an encoder
225
+ network (if present), and again with the randomizer (if present), flattened,
226
+ and then concatenated with the other processed modalities.
227
+
228
+ Args:
229
+ obs_dict (OrderedDict): dictionary that maps modalities to torch.Tensor
230
+ batches that agree with @self.obs_shapes. All modalities in
231
+ @self.obs_shapes must be present, but additional modalities
232
+ can also be present.
233
+
234
+ Returns:
235
+ feats (torch.Tensor): flat features of shape [B, D]
236
+ """
237
+ assert self._locked, "ObservationEncoder: @make has not been called yet"
238
+
239
+ # ensure all modalities that the encoder handles are present
240
+ assert set(self.obs_shapes.keys()).issubset(obs_dict), "ObservationEncoder: {} does not contain all modalities {}".format(
241
+ list(obs_dict.keys()), list(self.obs_shapes.keys())
242
+ )
243
+
244
+ # process modalities by order given by @self.obs_shapes
245
+ feats = []
246
+ for k in self.obs_shapes:
247
+ x = obs_dict[k]
248
+ # maybe process encoder input with randomizer
249
+ for rand in self.obs_randomizers[k]:
250
+ if rand is not None:
251
+ x = rand.forward_in(x)
252
+ # maybe process with obs net
253
+ if self.obs_nets[k] is not None:
254
+ x = self.obs_nets[k](x)
255
+ if self.activation is not None:
256
+ x = self.activation(x)
257
+ # maybe process encoder output with randomizer
258
+ for rand in self.obs_randomizers[k]:
259
+ if rand is not None:
260
+ x = rand.forward_out(x)
261
+ # flatten to [B, D]
262
+ x = TensorUtils.flatten(x, begin_axis=1)
263
+ feats.append(x)
264
+
265
+ # concatenate all features together
266
+ return torch.cat(feats, dim=-1)
267
+
268
+ def output_shape(self, input_shape=None):
269
+ """
270
+ Compute the output shape of the encoder.
271
+ """
272
+ feat_dim = 0
273
+ for k in self.obs_shapes:
274
+ feat_shape = self.obs_shapes[k]
275
+ for rand in self.obs_randomizers[k]:
276
+ if rand is not None:
277
+ feat_shape = rand.output_shape_in(feat_shape)
278
+ if self.obs_nets[k] is not None:
279
+ feat_shape = self.obs_nets[k].output_shape(feat_shape)
280
+ for rand in self.obs_randomizers[k]:
281
+ if rand is not None:
282
+ feat_shape = rand.output_shape_out(feat_shape)
283
+ feat_dim += int(np.prod(feat_shape))
284
+ return [feat_dim]
285
+
286
+ def __repr__(self):
287
+ """
288
+ Pretty print the encoder.
289
+ """
290
+ header = '{}'.format(str(self.__class__.__name__))
291
+ msg = ''
292
+ for k in self.obs_shapes:
293
+ msg += textwrap.indent('\nKey(\n', ' ' * 4)
294
+ indent = ' ' * 8
295
+ msg += textwrap.indent("name={}\nshape={}\n".format(k, self.obs_shapes[k]), indent)
296
+ msg += textwrap.indent("modality={}\n".format(ObsUtils.OBS_KEYS_TO_MODALITIES[k]), indent)
297
+ msg += textwrap.indent("randomizer={}\n".format(self.obs_randomizers[k]), indent)
298
+ msg += textwrap.indent("net={}\n".format(self.obs_nets[k]), indent)
299
+ msg += textwrap.indent("sharing_from={}\n".format(self.obs_share_mods[k]), indent)
300
+ msg += textwrap.indent(")", ' ' * 4)
301
+ msg += textwrap.indent("\noutput_shape={}".format(self.output_shape()), ' ' * 4)
302
+ msg = header + '(' + msg + '\n)'
303
+ return msg
304
+
305
+
306
+ class ObservationDecoder(Module):
307
+ """
308
+ Module that can generate observation outputs by modality. Inputs are assumed
309
+ to be flat (usually outputs from some hidden layer). Each observation output
310
+ is generated with a linear layer from these flat inputs. Subclass this
311
+ module in order to implement more complex schemes for generating each
312
+ modality.
313
+ """
314
+ def __init__(
315
+ self,
316
+ decode_shapes,
317
+ input_feat_dim,
318
+ ):
319
+ """
320
+ Args:
321
+ decode_shapes (OrderedDict): a dictionary that maps observation key to
322
+ expected shape. This is used to generate output modalities from the
323
+ input features.
324
+
325
+ input_feat_dim (int): flat input dimension size
326
+ """
327
+ super(ObservationDecoder, self).__init__()
328
+
329
+ # important: sort observation keys to ensure consistent ordering of modalities
330
+ assert isinstance(decode_shapes, OrderedDict)
331
+ self.obs_shapes = OrderedDict()
332
+ for k in decode_shapes:
333
+ self.obs_shapes[k] = decode_shapes[k]
334
+
335
+ self.input_feat_dim = input_feat_dim
336
+ self._create_layers()
337
+
338
+ def _create_layers(self):
339
+ """
340
+ Create a linear layer to predict each modality.
341
+ """
342
+ self.nets = nn.ModuleDict()
343
+ for k in self.obs_shapes:
344
+ layer_out_dim = int(np.prod(self.obs_shapes[k]))
345
+ self.nets[k] = nn.Linear(self.input_feat_dim, layer_out_dim)
346
+
347
+ def output_shape(self, input_shape=None):
348
+ """
349
+ Returns output shape for this module, which is a dictionary instead
350
+ of a list since outputs are dictionaries.
351
+ """
352
+ return { k : list(self.obs_shapes[k]) for k in self.obs_shapes }
353
+
354
+ def forward(self, feats):
355
+ """
356
+ Predict each modality from input features, and reshape to each modality's shape.
357
+ """
358
+ output = {}
359
+ for k in self.obs_shapes:
360
+ out = self.nets[k](feats)
361
+ output[k] = out.reshape(-1, *self.obs_shapes[k])
362
+ return output
363
+
364
+ def __repr__(self):
365
+ """Pretty print network."""
366
+ header = '{}'.format(str(self.__class__.__name__))
367
+ msg = ''
368
+ for k in self.obs_shapes:
369
+ msg += textwrap.indent('\nKey(\n', ' ' * 4)
370
+ indent = ' ' * 8
371
+ msg += textwrap.indent("name={}\nshape={}\n".format(k, self.obs_shapes[k]), indent)
372
+ msg += textwrap.indent("modality={}\n".format(ObsUtils.OBS_KEYS_TO_MODALITIES[k]), indent)
373
+ msg += textwrap.indent("net=({})\n".format(self.nets[k]), indent)
374
+ msg += textwrap.indent(")", ' ' * 4)
375
+ msg = header + '(' + msg + '\n)'
376
+ return msg
377
+
378
+
379
+ class ObservationGroupEncoder(Module):
380
+ """
381
+ This class allows networks to encode multiple observation dictionaries into a single
382
+ flat, concatenated vector representation. It does this by assigning each observation
383
+ dictionary (observation group) an @ObservationEncoder object.
384
+
385
+ The class takes a dictionary of dictionaries, @observation_group_shapes.
386
+ Each key corresponds to a observation group (e.g. 'obs', 'subgoal', 'goal')
387
+ and each OrderedDict should be a map between modalities and
388
+ expected input shapes (e.g. { 'image' : (3, 120, 160) }).
389
+ """
390
+ def __init__(
391
+ self,
392
+ observation_group_shapes,
393
+ feature_activation=nn.ReLU,
394
+ encoder_kwargs=None,
395
+ ):
396
+ """
397
+ Args:
398
+ observation_group_shapes (OrderedDict): a dictionary of dictionaries.
399
+ Each key in this dictionary should specify an observation group, and
400
+ the value should be an OrderedDict that maps modalities to
401
+ expected shapes.
402
+
403
+ feature_activation: non-linearity to apply after each obs net - defaults to ReLU. Pass
404
+ None to apply no activation.
405
+
406
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
407
+ be nested dictionary containing relevant per-modality information for encoder networks.
408
+ Should be of form:
409
+
410
+ obs_modality1: dict
411
+ feature_dimension: int
412
+ core_class: str
413
+ core_kwargs: dict
414
+ ...
415
+ ...
416
+ obs_randomizer_class: str
417
+ obs_randomizer_kwargs: dict
418
+ ...
419
+ ...
420
+ obs_modality2: dict
421
+ ...
422
+ """
423
+ super(ObservationGroupEncoder, self).__init__()
424
+
425
+ # type checking
426
+ assert isinstance(observation_group_shapes, OrderedDict)
427
+ assert np.all([isinstance(observation_group_shapes[k], OrderedDict) for k in observation_group_shapes])
428
+
429
+ self.observation_group_shapes = observation_group_shapes
430
+
431
+ # create an observation encoder per observation group
432
+ self.nets = nn.ModuleDict()
433
+ for obs_group in self.observation_group_shapes:
434
+ self.nets[obs_group] = obs_encoder_factory(
435
+ obs_shapes=self.observation_group_shapes[obs_group],
436
+ feature_activation=feature_activation,
437
+ encoder_kwargs=encoder_kwargs,
438
+ )
439
+
440
+ def forward(self, **inputs):
441
+ """
442
+ Process each set of inputs in its own observation group.
443
+
444
+ Args:
445
+ inputs (dict): dictionary that maps observation groups to observation
446
+ dictionaries of torch.Tensor batches that agree with
447
+ @self.observation_group_shapes. All observation groups in
448
+ @self.observation_group_shapes must be present, but additional
449
+ observation groups can also be present. Note that these are specified
450
+ as kwargs for ease of use with networks that name each observation
451
+ stream in their forward calls.
452
+
453
+ Returns:
454
+ outputs (torch.Tensor): flat outputs of shape [B, D]
455
+ """
456
+
457
+ # ensure all observation groups we need are present
458
+ assert set(self.observation_group_shapes.keys()).issubset(inputs), "{} does not contain all observation groups {}".format(
459
+ list(inputs.keys()), list(self.observation_group_shapes.keys())
460
+ )
461
+
462
+ outputs = []
463
+ # Deterministic order since self.observation_group_shapes is OrderedDict
464
+ for obs_group in self.observation_group_shapes:
465
+ # pass through encoder
466
+ outputs.append(
467
+ self.nets[obs_group].forward(inputs[obs_group])
468
+ )
469
+
470
+ return torch.cat(outputs, dim=-1)
471
+
472
+ def output_shape(self):
473
+ """
474
+ Compute the output shape of this encoder.
475
+ """
476
+ feat_dim = 0
477
+ for obs_group in self.observation_group_shapes:
478
+ # get feature dimension of these keys
479
+ feat_dim += self.nets[obs_group].output_shape()[0]
480
+ return [feat_dim]
481
+
482
+ def __repr__(self):
483
+ """Pretty print network."""
484
+ header = '{}'.format(str(self.__class__.__name__))
485
+ msg = ''
486
+ for k in self.observation_group_shapes:
487
+ msg += '\n'
488
+ indent = ' ' * 4
489
+ msg += textwrap.indent("group={}\n{}".format(k, self.nets[k]), indent)
490
+ msg = header + '(' + msg + '\n)'
491
+ return msg
492
+
493
+
494
+ class MIMO_MLP(Module):
495
+ """
496
+ Extension to MLP to accept multiple observation dictionaries as input and
497
+ to output dictionaries of tensors. Inputs are specified as a dictionary of
498
+ observation dictionaries, with each key corresponding to an observation group.
499
+
500
+ This module utilizes @ObservationGroupEncoder to process the multiple input dictionaries and
501
+ @ObservationDecoder to generate tensor dictionaries. The default behavior
502
+ for encoding the inputs is to process visual inputs with a learned CNN and concatenating
503
+ the flat encodings with the other flat inputs. The default behavior for generating
504
+ outputs is to use a linear layer branch to produce each modality separately
505
+ (including visual outputs).
506
+ """
507
+ def __init__(
508
+ self,
509
+ input_obs_group_shapes,
510
+ output_shapes,
511
+ layer_dims,
512
+ layer_func=nn.Linear,
513
+ activation=nn.ReLU,
514
+ encoder_kwargs=None,
515
+ ):
516
+ """
517
+ Args:
518
+ input_obs_group_shapes (OrderedDict): a dictionary of dictionaries.
519
+ Each key in this dictionary should specify an observation group, and
520
+ the value should be an OrderedDict that maps modalities to
521
+ expected shapes.
522
+
523
+ output_shapes (OrderedDict): a dictionary that maps modality to
524
+ expected shapes for outputs.
525
+
526
+ layer_dims ([int]): sequence of integers for the MLP hidden layer sizes
527
+
528
+ layer_func: mapping per MLP layer - defaults to Linear
529
+
530
+ activation: non-linearity per MLP layer - defaults to ReLU
531
+
532
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
533
+ be nested dictionary containing relevant per-modality information for encoder networks.
534
+ Should be of form:
535
+
536
+ obs_modality1: dict
537
+ feature_dimension: int
538
+ core_class: str
539
+ core_kwargs: dict
540
+ ...
541
+ ...
542
+ obs_randomizer_class: str
543
+ obs_randomizer_kwargs: dict
544
+ ...
545
+ ...
546
+ obs_modality2: dict
547
+ ...
548
+ """
549
+ super(MIMO_MLP, self).__init__()
550
+
551
+ assert isinstance(input_obs_group_shapes, OrderedDict)
552
+ assert np.all([isinstance(input_obs_group_shapes[k], OrderedDict) for k in input_obs_group_shapes])
553
+ assert isinstance(output_shapes, OrderedDict)
554
+
555
+ self.input_obs_group_shapes = input_obs_group_shapes
556
+ self.output_shapes = output_shapes
557
+
558
+ self.nets = nn.ModuleDict()
559
+
560
+ # Encoder for all observation groups.
561
+ self.nets["encoder"] = ObservationGroupEncoder(
562
+ observation_group_shapes=input_obs_group_shapes,
563
+ encoder_kwargs=encoder_kwargs,
564
+ )
565
+
566
+ # flat encoder output dimension
567
+ mlp_input_dim = self.nets["encoder"].output_shape()[0]
568
+
569
+ # intermediate MLP layers
570
+ self.nets["mlp"] = MLP(
571
+ input_dim=mlp_input_dim,
572
+ output_dim=layer_dims[-1],
573
+ layer_dims=layer_dims[:-1],
574
+ layer_func=layer_func,
575
+ activation=activation,
576
+ output_activation=activation, # make sure non-linearity is applied before decoder
577
+ )
578
+
579
+ # decoder for output modalities
580
+ self.nets["decoder"] = ObservationDecoder(
581
+ decode_shapes=self.output_shapes,
582
+ input_feat_dim=layer_dims[-1],
583
+ )
584
+
585
+ def output_shape(self, input_shape=None):
586
+ """
587
+ Returns output shape for this module, which is a dictionary instead
588
+ of a list since outputs are dictionaries.
589
+ """
590
+ return { k : list(self.output_shapes[k]) for k in self.output_shapes }
591
+
592
+ def forward(self, **inputs):
593
+ """
594
+ Process each set of inputs in its own observation group.
595
+
596
+ Args:
597
+ inputs (dict): a dictionary of dictionaries with one dictionary per
598
+ observation group. Each observation group's dictionary should map
599
+ modality to torch.Tensor batches. Should be consistent with
600
+ @self.input_obs_group_shapes.
601
+
602
+ Returns:
603
+ outputs (dict): dictionary of output torch.Tensors, that corresponds
604
+ to @self.output_shapes
605
+ """
606
+ enc_outputs = self.nets["encoder"](**inputs)
607
+ mlp_out = self.nets["mlp"](enc_outputs)
608
+ return self.nets["decoder"](mlp_out)
609
+
610
+ def _to_string(self):
611
+ """
612
+ Subclasses should override this method to print out info about network / policy.
613
+ """
614
+ return ''
615
+
616
+ def __repr__(self):
617
+ """Pretty print network."""
618
+ header = '{}'.format(str(self.__class__.__name__))
619
+ msg = ''
620
+ indent = ' ' * 4
621
+ if self._to_string() != '':
622
+ msg += textwrap.indent("\n" + self._to_string() + "\n", indent)
623
+ msg += textwrap.indent("\nencoder={}".format(self.nets["encoder"]), indent)
624
+ msg += textwrap.indent("\n\nmlp={}".format(self.nets["mlp"]), indent)
625
+ msg += textwrap.indent("\n\ndecoder={}".format(self.nets["decoder"]), indent)
626
+ msg = header + '(' + msg + '\n)'
627
+ return msg
628
+
629
+
630
+ class RNN_MIMO_MLP(Module):
631
+ """
632
+ A wrapper class for a multi-step RNN and a per-step MLP and a decoder.
633
+
634
+ Structure: [encoder -> rnn -> mlp -> decoder]
635
+
636
+ All temporal inputs are processed by a shared @ObservationGroupEncoder,
637
+ followed by an RNN, and then a per-step multi-output MLP.
638
+ """
639
+ def __init__(
640
+ self,
641
+ input_obs_group_shapes,
642
+ output_shapes,
643
+ mlp_layer_dims,
644
+ rnn_hidden_dim,
645
+ rnn_num_layers,
646
+ rnn_type="LSTM", # [LSTM, GRU]
647
+ rnn_kwargs=None,
648
+ mlp_activation=nn.ReLU,
649
+ mlp_layer_func=nn.Linear,
650
+ per_step=True,
651
+ encoder_kwargs=None,
652
+ ):
653
+ """
654
+ Args:
655
+ input_obs_group_shapes (OrderedDict): a dictionary of dictionaries.
656
+ Each key in this dictionary should specify an observation group, and
657
+ the value should be an OrderedDict that maps modalities to
658
+ expected shapes.
659
+
660
+ output_shapes (OrderedDict): a dictionary that maps modality to
661
+ expected shapes for outputs.
662
+
663
+ rnn_hidden_dim (int): RNN hidden dimension
664
+
665
+ rnn_num_layers (int): number of RNN layers
666
+
667
+ rnn_type (str): [LSTM, GRU]
668
+
669
+ rnn_kwargs (dict): kwargs for the rnn model
670
+
671
+ per_step (bool): if True, apply the MLP and observation decoder into @output_shapes
672
+ at every step of the RNN. Otherwise, apply them to the final hidden state of the
673
+ RNN.
674
+
675
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
676
+ be nested dictionary containing relevant per-modality information for encoder networks.
677
+ Should be of form:
678
+
679
+ obs_modality1: dict
680
+ feature_dimension: int
681
+ core_class: str
682
+ core_kwargs: dict
683
+ ...
684
+ ...
685
+ obs_randomizer_class: str
686
+ obs_randomizer_kwargs: dict
687
+ ...
688
+ ...
689
+ obs_modality2: dict
690
+ ...
691
+ """
692
+ super(RNN_MIMO_MLP, self).__init__()
693
+ assert isinstance(input_obs_group_shapes, OrderedDict)
694
+ assert np.all([isinstance(input_obs_group_shapes[k], OrderedDict) for k in input_obs_group_shapes])
695
+ assert isinstance(output_shapes, OrderedDict)
696
+ self.input_obs_group_shapes = input_obs_group_shapes
697
+ self.output_shapes = output_shapes
698
+ self.per_step = per_step
699
+
700
+ self.nets = nn.ModuleDict()
701
+
702
+ # Encoder for all observation groups.
703
+ self.nets["encoder"] = ObservationGroupEncoder(
704
+ observation_group_shapes=input_obs_group_shapes,
705
+ encoder_kwargs=encoder_kwargs,
706
+ )
707
+
708
+ # flat encoder output dimension
709
+ rnn_input_dim = self.nets["encoder"].output_shape()[0]
710
+
711
+ # bidirectional RNNs mean that the output of RNN will be twice the hidden dimension
712
+ rnn_is_bidirectional = rnn_kwargs.get("bidirectional", False)
713
+ num_directions = int(rnn_is_bidirectional) + 1 # 2 if bidirectional, 1 otherwise
714
+ rnn_output_dim = num_directions * rnn_hidden_dim
715
+
716
+ per_step_net = None
717
+ self._has_mlp = (len(mlp_layer_dims) > 0)
718
+ if self._has_mlp:
719
+ self.nets["mlp"] = MLP(
720
+ input_dim=rnn_output_dim,
721
+ output_dim=mlp_layer_dims[-1],
722
+ layer_dims=mlp_layer_dims[:-1],
723
+ output_activation=mlp_activation,
724
+ layer_func=mlp_layer_func
725
+ )
726
+ self.nets["decoder"] = ObservationDecoder(
727
+ decode_shapes=self.output_shapes,
728
+ input_feat_dim=mlp_layer_dims[-1],
729
+ )
730
+ if self.per_step:
731
+ per_step_net = Sequential(self.nets["mlp"], self.nets["decoder"])
732
+ else:
733
+ self.nets["decoder"] = ObservationDecoder(
734
+ decode_shapes=self.output_shapes,
735
+ input_feat_dim=rnn_output_dim,
736
+ )
737
+ if self.per_step:
738
+ per_step_net = self.nets["decoder"]
739
+
740
+ # core network
741
+ self.nets["rnn"] = RNN_Base(
742
+ input_dim=rnn_input_dim,
743
+ rnn_hidden_dim=rnn_hidden_dim,
744
+ rnn_num_layers=rnn_num_layers,
745
+ rnn_type=rnn_type,
746
+ per_step_net=per_step_net,
747
+ rnn_kwargs=rnn_kwargs
748
+ )
749
+
750
+ def get_rnn_init_state(self, batch_size, device):
751
+ """
752
+ Get a default RNN state (zeros)
753
+
754
+ Args:
755
+ batch_size (int): batch size dimension
756
+
757
+ device: device the hidden state should be sent to.
758
+
759
+ Returns:
760
+ hidden_state (torch.Tensor or tuple): returns hidden state tensor or tuple of hidden state tensors
761
+ depending on the RNN type
762
+ """
763
+ return self.nets["rnn"].get_rnn_init_state(batch_size, device=device)
764
+
765
+ def output_shape(self, input_shape):
766
+ """
767
+ Returns output shape for this module, which is a dictionary instead
768
+ of a list since outputs are dictionaries.
769
+
770
+ Args:
771
+ input_shape (dict): dictionary of dictionaries, where each top-level key
772
+ corresponds to an observation group, and the low-level dictionaries
773
+ specify the shape for each modality in an observation dictionary
774
+ """
775
+
776
+ # infers temporal dimension from input shape
777
+ obs_group = list(self.input_obs_group_shapes.keys())[0]
778
+ mod = list(self.input_obs_group_shapes[obs_group].keys())[0]
779
+ T = input_shape[obs_group][mod][0]
780
+ TensorUtils.assert_size_at_dim(input_shape, size=T, dim=0,
781
+ msg="RNN_MIMO_MLP: input_shape inconsistent in temporal dimension")
782
+ # returns a dictionary instead of list since outputs are dictionaries
783
+ return { k : [T] + list(self.output_shapes[k]) for k in self.output_shapes }
784
+
785
+ def forward(self, rnn_init_state=None, return_state=False, **inputs):
786
+ """
787
+ Args:
788
+ inputs (dict): a dictionary of dictionaries with one dictionary per
789
+ observation group. Each observation group's dictionary should map
790
+ modality to torch.Tensor batches. Should be consistent with
791
+ @self.input_obs_group_shapes. First two leading dimensions should
792
+ be batch and time [B, T, ...] for each tensor.
793
+
794
+ rnn_init_state: rnn hidden state, initialize to zero state if set to None
795
+
796
+ return_state (bool): whether to return hidden state
797
+
798
+ Returns:
799
+ outputs (dict): dictionary of output torch.Tensors, that corresponds
800
+ to @self.output_shapes. Leading dimensions will be batch and time [B, T, ...]
801
+ for each tensor.
802
+
803
+ rnn_state (torch.Tensor or tuple): return the new rnn state (if @return_state)
804
+ """
805
+ for obs_group in self.input_obs_group_shapes:
806
+ for k in self.input_obs_group_shapes[obs_group]:
807
+ # first two dimensions should be [B, T] for inputs
808
+ assert inputs[obs_group][k].ndim - 2 == len(self.input_obs_group_shapes[obs_group][k])
809
+
810
+ # use encoder to extract flat rnn inputs
811
+ rnn_inputs = TensorUtils.time_distributed(inputs, self.nets["encoder"], inputs_as_kwargs=True)
812
+ assert rnn_inputs.ndim == 3 # [B, T, D]
813
+ if self.per_step:
814
+ return self.nets["rnn"].forward(inputs=rnn_inputs, rnn_init_state=rnn_init_state, return_state=return_state)
815
+
816
+ # apply MLP + decoder to last RNN output
817
+ outputs = self.nets["rnn"].forward(inputs=rnn_inputs, rnn_init_state=rnn_init_state, return_state=return_state)
818
+ if return_state:
819
+ outputs, rnn_state = outputs
820
+
821
+ assert outputs.ndim == 3 # [B, T, D]
822
+ if self._has_mlp:
823
+ outputs = self.nets["decoder"](self.nets["mlp"](outputs[:, -1]))
824
+ else:
825
+ outputs = self.nets["decoder"](outputs[:, -1])
826
+
827
+ if return_state:
828
+ return outputs, rnn_state
829
+ return outputs
830
+
831
+ def forward_step(self, rnn_state, **inputs):
832
+ """
833
+ Unroll network over a single timestep.
834
+
835
+ Args:
836
+ inputs (dict): expects same modalities as @self.input_shapes, with
837
+ additional batch dimension (but NOT time), since this is a
838
+ single time step.
839
+
840
+ rnn_state (torch.Tensor): rnn hidden state
841
+
842
+ Returns:
843
+ outputs (dict): dictionary of output torch.Tensors, that corresponds
844
+ to @self.output_shapes. Does not contain time dimension.
845
+
846
+ rnn_state: return the new rnn state
847
+ """
848
+ # ensure that the only extra dimension is batch dim, not temporal dim
849
+ assert np.all([inputs[k].ndim - 1 == len(self.input_shapes[k]) for k in self.input_shapes])
850
+
851
+ inputs = TensorUtils.to_sequence(inputs)
852
+ outputs, rnn_state = self.forward(
853
+ inputs,
854
+ rnn_init_state=rnn_state,
855
+ return_state=True,
856
+ )
857
+ if self.per_step:
858
+ # if outputs are not per-step, the time dimension is already reduced
859
+ outputs = outputs[:, 0]
860
+ return outputs, rnn_state
861
+
862
+ def _to_string(self):
863
+ """
864
+ Subclasses should override this method to print out info about network / policy.
865
+ """
866
+ return ''
867
+
868
+ def __repr__(self):
869
+ """Pretty print network."""
870
+ header = '{}'.format(str(self.__class__.__name__))
871
+ msg = ''
872
+ indent = ' ' * 4
873
+ msg += textwrap.indent("\n" + self._to_string(), indent)
874
+ msg += textwrap.indent("\n\nencoder={}".format(self.nets["encoder"]), indent)
875
+ msg += textwrap.indent("\n\nrnn={}".format(self.nets["rnn"]), indent)
876
+ msg = header + '(' + msg + '\n)'
877
+ return msg
878
+
879
+
880
+ class MIMO_Transformer(Module):
881
+ """
882
+ Extension to Transformer (based on GPT architecture) to accept multiple observation
883
+ dictionaries as input and to output dictionaries of tensors. Inputs are specified as
884
+ a dictionary of observation dictionaries, with each key corresponding to an observation group.
885
+ This module utilizes @ObservationGroupEncoder to process the multiple input dictionaries and
886
+ @ObservationDecoder to generate tensor dictionaries. The default behavior
887
+ for encoding the inputs is to process visual inputs with a learned CNN and concatenating
888
+ the flat encodings with the other flat inputs. The default behavior for generating
889
+ outputs is to use a linear layer branch to produce each modality separately
890
+ (including visual outputs).
891
+ """
892
+ def __init__(
893
+ self,
894
+ input_obs_group_shapes,
895
+ output_shapes,
896
+ transformer_embed_dim,
897
+ transformer_num_layers,
898
+ transformer_num_heads,
899
+ transformer_context_length,
900
+ transformer_emb_dropout=0.1,
901
+ transformer_attn_dropout=0.1,
902
+ transformer_block_output_dropout=0.1,
903
+ transformer_sinusoidal_embedding=False,
904
+ transformer_activation="gelu",
905
+ transformer_nn_parameter_for_timesteps=False,
906
+ encoder_kwargs=None,
907
+ ):
908
+ """
909
+ Args:
910
+ input_obs_group_shapes (OrderedDict): a dictionary of dictionaries.
911
+ Each key in this dictionary should specify an observation group, and
912
+ the value should be an OrderedDict that maps modalities to
913
+ expected shapes.
914
+ output_shapes (OrderedDict): a dictionary that maps modality to
915
+ expected shapes for outputs.
916
+ transformer_embed_dim (int): dimension for embeddings used by transformer
917
+ transformer_num_layers (int): number of transformer blocks to stack
918
+ transformer_num_heads (int): number of attention heads for each
919
+ transformer block - must divide @transformer_embed_dim evenly. Self-attention is
920
+ computed over this many partitions of the embedding dimension separately.
921
+ transformer_context_length (int): expected length of input sequences
922
+ transformer_activation: non-linearity for input and output layers used in transformer
923
+ transformer_emb_dropout (float): dropout probability for embedding inputs in transformer
924
+ transformer_attn_dropout (float): dropout probability for attention outputs for each transformer block
925
+ transformer_block_output_dropout (float): dropout probability for final outputs for each transformer block
926
+ encoder_kwargs (dict): observation encoder config
927
+ """
928
+ super(MIMO_Transformer, self).__init__()
929
+
930
+ assert isinstance(input_obs_group_shapes, OrderedDict)
931
+ assert np.all([isinstance(input_obs_group_shapes[k], OrderedDict) for k in input_obs_group_shapes])
932
+ assert isinstance(output_shapes, OrderedDict)
933
+
934
+ self.input_obs_group_shapes = input_obs_group_shapes
935
+ self.output_shapes = output_shapes
936
+
937
+ self.nets = nn.ModuleDict()
938
+ self.params = nn.ParameterDict()
939
+
940
+ # Encoder for all observation groups.
941
+ self.nets["encoder"] = ObservationGroupEncoder(
942
+ observation_group_shapes=input_obs_group_shapes,
943
+ encoder_kwargs=encoder_kwargs,
944
+ feature_activation=None,
945
+ )
946
+
947
+ # flat encoder output dimension
948
+ transformer_input_dim = self.nets["encoder"].output_shape()[0]
949
+
950
+ self.nets["embed_encoder"] = nn.Linear(
951
+ transformer_input_dim, transformer_embed_dim
952
+ )
953
+
954
+ max_timestep = transformer_context_length
955
+
956
+ if transformer_sinusoidal_embedding:
957
+ self.nets["embed_timestep"] = PositionalEncoding(transformer_embed_dim)
958
+ elif transformer_nn_parameter_for_timesteps:
959
+ assert (
960
+ not transformer_sinusoidal_embedding
961
+ ), "nn.Parameter only works with learned embeddings"
962
+ self.params["embed_timestep"] = nn.Parameter(
963
+ torch.zeros(1, max_timestep, transformer_embed_dim)
964
+ )
965
+ else:
966
+ self.nets["embed_timestep"] = nn.Embedding(max_timestep, transformer_embed_dim)
967
+
968
+ # layer norm for embeddings
969
+ self.nets["embed_ln"] = nn.LayerNorm(transformer_embed_dim)
970
+
971
+ # dropout for input embeddings
972
+ self.nets["embed_drop"] = nn.Dropout(transformer_emb_dropout)
973
+
974
+ # GPT transformer
975
+ self.nets["transformer"] = GPT_Backbone(
976
+ embed_dim=transformer_embed_dim,
977
+ num_layers=transformer_num_layers,
978
+ num_heads=transformer_num_heads,
979
+ context_length=transformer_context_length,
980
+ attn_dropout=transformer_attn_dropout,
981
+ block_output_dropout=transformer_block_output_dropout,
982
+ activation=transformer_activation,
983
+ )
984
+
985
+ # decoder for output modalities
986
+ self.nets["decoder"] = ObservationDecoder(
987
+ decode_shapes=self.output_shapes,
988
+ input_feat_dim=transformer_embed_dim,
989
+ )
990
+
991
+ self.transformer_context_length = transformer_context_length
992
+ self.transformer_embed_dim = transformer_embed_dim
993
+ self.transformer_sinusoidal_embedding = transformer_sinusoidal_embedding
994
+ self.transformer_nn_parameter_for_timesteps = transformer_nn_parameter_for_timesteps
995
+
996
+ def output_shape(self, input_shape=None):
997
+ """
998
+ Returns output shape for this module, which is a dictionary instead
999
+ of a list since outputs are dictionaries.
1000
+ """
1001
+ return { k : list(self.output_shapes[k]) for k in self.output_shapes }
1002
+
1003
+ def embed_timesteps(self, embeddings):
1004
+ """
1005
+ Computes timestep-based embeddings (aka positional embeddings) to add to embeddings.
1006
+ Args:
1007
+ embeddings (torch.Tensor): embeddings prior to positional embeddings are computed
1008
+ Returns:
1009
+ time_embeddings (torch.Tensor): positional embeddings to add to embeddings
1010
+ """
1011
+ timesteps = (
1012
+ torch.arange(
1013
+ 0,
1014
+ embeddings.shape[1],
1015
+ dtype=embeddings.dtype,
1016
+ device=embeddings.device,
1017
+ )
1018
+ .unsqueeze(0)
1019
+ .repeat(embeddings.shape[0], 1)
1020
+ )
1021
+ assert (timesteps >= 0.0).all(), "timesteps must be positive!"
1022
+ if self.transformer_sinusoidal_embedding:
1023
+ assert torch.is_floating_point(timesteps), timesteps.dtype
1024
+ else:
1025
+ timesteps = timesteps.long()
1026
+
1027
+ if self.transformer_nn_parameter_for_timesteps:
1028
+ time_embeddings = self.params["embed_timestep"]
1029
+ else:
1030
+ time_embeddings = self.nets["embed_timestep"](
1031
+ timesteps
1032
+ ) # these are NOT fed into transformer, only added to the inputs.
1033
+ # compute how many modalities were combined into embeddings, replicate time embeddings that many times
1034
+ num_replicates = embeddings.shape[-1] // self.transformer_embed_dim
1035
+ time_embeddings = torch.cat([time_embeddings for _ in range(num_replicates)], -1)
1036
+ assert (
1037
+ embeddings.shape == time_embeddings.shape
1038
+ ), f"{embeddings.shape}, {time_embeddings.shape}"
1039
+ return time_embeddings
1040
+
1041
+ def input_embedding(
1042
+ self,
1043
+ inputs,
1044
+ ):
1045
+ """
1046
+ Process encoded observations into embeddings to pass to transformer,
1047
+ Adds timestep-based embeddings (aka positional embeddings) to inputs.
1048
+ Args:
1049
+ inputs (torch.Tensor): outputs from observation encoder
1050
+ Returns:
1051
+ embeddings (torch.Tensor): input embeddings to pass to transformer backbone.
1052
+ """
1053
+ embeddings = self.nets["embed_encoder"](inputs)
1054
+ time_embeddings = self.embed_timesteps(embeddings)
1055
+ embeddings = embeddings + time_embeddings
1056
+ embeddings = self.nets["embed_ln"](embeddings)
1057
+ embeddings = self.nets["embed_drop"](embeddings)
1058
+
1059
+ return embeddings
1060
+
1061
+
1062
+ def forward(self, **inputs):
1063
+ """
1064
+ Process each set of inputs in its own observation group.
1065
+ Args:
1066
+ inputs (dict): a dictionary of dictionaries with one dictionary per
1067
+ observation group. Each observation group's dictionary should map
1068
+ modality to torch.Tensor batches. Should be consistent with
1069
+ @self.input_obs_group_shapes. First two leading dimensions should
1070
+ be batch and time [B, T, ...] for each tensor.
1071
+ Returns:
1072
+ outputs (dict): dictionary of output torch.Tensors, that corresponds
1073
+ to @self.output_shapes. Leading dimensions will be batch and time [B, T, ...]
1074
+ for each tensor.
1075
+ """
1076
+ for obs_group in self.input_obs_group_shapes:
1077
+ for k in self.input_obs_group_shapes[obs_group]:
1078
+ # first two dimensions should be [B, T] for inputs
1079
+ if inputs[obs_group][k] is None:
1080
+ continue
1081
+ assert inputs[obs_group][k].ndim - 2 == len(self.input_obs_group_shapes[obs_group][k])
1082
+
1083
+ inputs = inputs.copy()
1084
+
1085
+ transformer_encoder_outputs = None
1086
+ transformer_inputs = TensorUtils.time_distributed(
1087
+ inputs, self.nets["encoder"], inputs_as_kwargs=True
1088
+ )
1089
+ assert transformer_inputs.ndim == 3 # [B, T, D]
1090
+
1091
+ if transformer_encoder_outputs is None:
1092
+ transformer_embeddings = self.input_embedding(transformer_inputs)
1093
+ # pass encoded sequences through transformer
1094
+ transformer_encoder_outputs = self.nets["transformer"].forward(transformer_embeddings)
1095
+
1096
+ transformer_outputs = transformer_encoder_outputs
1097
+ # apply decoder to each timestep of sequence to get a dictionary of outputs
1098
+ transformer_outputs = TensorUtils.time_distributed(
1099
+ transformer_outputs, self.nets["decoder"]
1100
+ )
1101
+ transformer_outputs["transformer_encoder_outputs"] = transformer_encoder_outputs
1102
+ return transformer_outputs
1103
+
1104
+ def _to_string(self):
1105
+ """
1106
+ Subclasses should override this method to print out info about network / policy.
1107
+ """
1108
+ return ''
1109
+
1110
+ def __repr__(self):
1111
+ """Pretty print network."""
1112
+ header = '{}'.format(str(self.__class__.__name__))
1113
+ msg = ''
1114
+ indent = ' ' * 4
1115
+ if self._to_string() != '':
1116
+ msg += textwrap.indent("\n" + self._to_string() + "\n", indent)
1117
+ msg += textwrap.indent("\nencoder={}".format(self.nets["encoder"]), indent)
1118
+ msg += textwrap.indent("\n\ntransformer={}".format(self.nets["transformer"]), indent)
1119
+ msg += textwrap.indent("\n\ndecoder={}".format(self.nets["decoder"]), indent)
1120
+ msg = header + '(' + msg + '\n)'
1121
+ return msg
aloha-devel/robomimic/models/value_nets.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Contains torch Modules for value networks. These networks take an
3
+ observation dictionary as input (and possibly additional conditioning,
4
+ such as subgoal or goal dictionaries) and produce value or
5
+ action-value estimates or distributions.
6
+ """
7
+ import numpy as np
8
+ from collections import OrderedDict
9
+
10
+ import torch
11
+ import torch.nn as nn
12
+ import torch.nn.functional as F
13
+ import torch.distributions as D
14
+
15
+ import robomimic.utils.tensor_utils as TensorUtils
16
+ from robomimic.models.obs_nets import MIMO_MLP
17
+ from robomimic.models.distributions import DiscreteValueDistribution
18
+
19
+
20
+ class ValueNetwork(MIMO_MLP):
21
+ """
22
+ A basic value network that predicts values from observations.
23
+ Can optionally be goal conditioned on future observations.
24
+ """
25
+ def __init__(
26
+ self,
27
+ obs_shapes,
28
+ mlp_layer_dims,
29
+ value_bounds=None,
30
+ goal_shapes=None,
31
+ encoder_kwargs=None,
32
+ ):
33
+ """
34
+ Args:
35
+ obs_shapes (OrderedDict): a dictionary that maps observation keys to
36
+ expected shapes for observations.
37
+
38
+ mlp_layer_dims ([int]): sequence of integers for the MLP hidden layers sizes.
39
+
40
+ value_bounds (tuple): a 2-tuple corresponding to the lowest and highest possible return
41
+ that the network should be possible of generating. The network will rescale outputs
42
+ using a tanh layer to lie within these bounds. If None, no tanh re-scaling is done.
43
+
44
+ goal_shapes (OrderedDict): a dictionary that maps observation keys to
45
+ expected shapes for goal observations.
46
+
47
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
48
+ be nested dictionary containing relevant per-observation key information for encoder networks.
49
+ Should be of form:
50
+
51
+ obs_modality1: dict
52
+ feature_dimension: int
53
+ core_class: str
54
+ core_kwargs: dict
55
+ ...
56
+ ...
57
+ obs_randomizer_class: str
58
+ obs_randomizer_kwargs: dict
59
+ ...
60
+ ...
61
+ obs_modality2: dict
62
+ ...
63
+ """
64
+ self.value_bounds = value_bounds
65
+ if self.value_bounds is not None:
66
+ # convert [lb, ub] to a scale and offset for the tanh output, which is in [-1, 1]
67
+ self._value_scale = (float(self.value_bounds[1]) - float(self.value_bounds[0])) / 2.
68
+ self._value_offset = (float(self.value_bounds[1]) + float(self.value_bounds[0])) / 2.
69
+
70
+ assert isinstance(obs_shapes, OrderedDict)
71
+ self.obs_shapes = obs_shapes
72
+
73
+ # set up different observation groups for @MIMO_MLP
74
+ observation_group_shapes = OrderedDict()
75
+ observation_group_shapes["obs"] = OrderedDict(self.obs_shapes)
76
+
77
+ self._is_goal_conditioned = False
78
+ if goal_shapes is not None and len(goal_shapes) > 0:
79
+ assert isinstance(goal_shapes, OrderedDict)
80
+ self._is_goal_conditioned = True
81
+ self.goal_shapes = OrderedDict(goal_shapes)
82
+ observation_group_shapes["goal"] = OrderedDict(self.goal_shapes)
83
+ else:
84
+ self.goal_shapes = OrderedDict()
85
+
86
+ output_shapes = self._get_output_shapes()
87
+ super(ValueNetwork, self).__init__(
88
+ input_obs_group_shapes=observation_group_shapes,
89
+ output_shapes=output_shapes,
90
+ layer_dims=mlp_layer_dims,
91
+ encoder_kwargs=encoder_kwargs,
92
+ )
93
+
94
+ def _get_output_shapes(self):
95
+ """
96
+ Allow subclasses to re-define outputs from @MIMO_MLP, since we won't
97
+ always directly predict values, but may instead predict the parameters
98
+ of a value distribution.
99
+ """
100
+ return OrderedDict(value=(1,))
101
+
102
+ def output_shape(self, input_shape=None):
103
+ """
104
+ Function to compute output shape from inputs to this module.
105
+
106
+ Args:
107
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
108
+ Some modules may not need this argument, if their output does not depend
109
+ on the size of the input, or if they assume fixed size input.
110
+
111
+ Returns:
112
+ out_shape ([int]): list of integers corresponding to output shape
113
+ """
114
+ return [1]
115
+
116
+ def forward(self, obs_dict, goal_dict=None):
117
+ """
118
+ Forward through value network, and then optionally use tanh scaling.
119
+ """
120
+ values = super(ValueNetwork, self).forward(obs=obs_dict, goal=goal_dict)["value"]
121
+ if self.value_bounds is not None:
122
+ values = self._value_offset + self._value_scale * torch.tanh(values)
123
+ return values
124
+
125
+ def _to_string(self):
126
+ return "value_bounds={}".format(self.value_bounds)
127
+
128
+
129
+ class ActionValueNetwork(ValueNetwork):
130
+ """
131
+ A basic Q (action-value) network that predicts values from observations
132
+ and actions. Can optionally be goal conditioned on future observations.
133
+ """
134
+ def __init__(
135
+ self,
136
+ obs_shapes,
137
+ ac_dim,
138
+ mlp_layer_dims,
139
+ value_bounds=None,
140
+ goal_shapes=None,
141
+ encoder_kwargs=None,
142
+ ):
143
+ """
144
+ Args:
145
+ obs_shapes (OrderedDict): a dictionary that maps observation keys to
146
+ expected shapes for observations.
147
+
148
+ ac_dim (int): dimension of action space.
149
+
150
+ mlp_layer_dims ([int]): sequence of integers for the MLP hidden layers sizes.
151
+
152
+ value_bounds (tuple): a 2-tuple corresponding to the lowest and highest possible return
153
+ that the network should be possible of generating. The network will rescale outputs
154
+ using a tanh layer to lie within these bounds. If None, no tanh re-scaling is done.
155
+
156
+ goal_shapes (OrderedDict): a dictionary that maps observation keys to
157
+ expected shapes for goal observations.
158
+
159
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
160
+ be nested dictionary containing relevant per-observation key information for encoder networks.
161
+ Should be of form:
162
+
163
+ obs_modality1: dict
164
+ feature_dimension: int
165
+ core_class: str
166
+ core_kwargs: dict
167
+ ...
168
+ ...
169
+ obs_randomizer_class: str
170
+ obs_randomizer_kwargs: dict
171
+ ...
172
+ ...
173
+ obs_modality2: dict
174
+ ...
175
+ """
176
+
177
+ # add in action as a modality
178
+ new_obs_shapes = OrderedDict(obs_shapes)
179
+ new_obs_shapes["action"] = (ac_dim,)
180
+ self.ac_dim = ac_dim
181
+
182
+ # pass to super class to instantiate network
183
+ super(ActionValueNetwork, self).__init__(
184
+ obs_shapes=new_obs_shapes,
185
+ mlp_layer_dims=mlp_layer_dims,
186
+ value_bounds=value_bounds,
187
+ goal_shapes=goal_shapes,
188
+ encoder_kwargs=encoder_kwargs,
189
+ )
190
+
191
+ def forward(self, obs_dict, acts, goal_dict=None):
192
+ """
193
+ Modify forward from super class to include actions in inputs.
194
+ """
195
+ inputs = dict(obs_dict)
196
+ inputs["action"] = acts
197
+ return super(ActionValueNetwork, self).forward(inputs, goal_dict)
198
+
199
+ def _to_string(self):
200
+ return "action_dim={}\nvalue_bounds={}".format(self.ac_dim, self.value_bounds)
201
+
202
+
203
+ class DistributionalActionValueNetwork(ActionValueNetwork):
204
+ """
205
+ Distributional Q (action-value) network that outputs a categorical distribution over
206
+ a discrete grid of value atoms. See https://arxiv.org/pdf/1707.06887.pdf for
207
+ more details.
208
+ """
209
+ def __init__(
210
+ self,
211
+ obs_shapes,
212
+ ac_dim,
213
+ mlp_layer_dims,
214
+ value_bounds,
215
+ num_atoms,
216
+ goal_shapes=None,
217
+ encoder_kwargs=None,
218
+ ):
219
+ """
220
+ Args:
221
+ obs_shapes (OrderedDict): a dictionary that maps modality to
222
+ expected shapes for observations.
223
+
224
+ ac_dim (int): dimension of action space.
225
+
226
+ mlp_layer_dims ([int]): sequence of integers for the MLP hidden layers sizes.
227
+
228
+ value_bounds (tuple): a 2-tuple corresponding to the lowest and highest possible return
229
+ that the network should be possible of generating. This defines the support
230
+ of the value distribution.
231
+
232
+ num_atoms (int): number of value atoms to use for the categorical distribution - which
233
+ is the representation of the value distribution.
234
+
235
+ goal_shapes (OrderedDict): a dictionary that maps modality to
236
+ expected shapes for goal observations.
237
+
238
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
239
+ be nested dictionary containing relevant per-modality information for encoder networks.
240
+ Should be of form:
241
+
242
+ obs_modality1: dict
243
+ feature_dimension: int
244
+ core_class: str
245
+ core_kwargs: dict
246
+ ...
247
+ ...
248
+ obs_randomizer_class: str
249
+ obs_randomizer_kwargs: dict
250
+ ...
251
+ ...
252
+ obs_modality2: dict
253
+ ...
254
+ """
255
+
256
+ # parameters specific to DistributionalActionValueNetwork
257
+ self.num_atoms = num_atoms
258
+ self._atoms = np.linspace(value_bounds[0], value_bounds[1], num_atoms)
259
+
260
+ # pass to super class to instantiate network
261
+ super(DistributionalActionValueNetwork, self).__init__(
262
+ obs_shapes=obs_shapes,
263
+ ac_dim=ac_dim,
264
+ mlp_layer_dims=mlp_layer_dims,
265
+ value_bounds=value_bounds,
266
+ goal_shapes=goal_shapes,
267
+ encoder_kwargs=encoder_kwargs,
268
+ )
269
+
270
+ def _get_output_shapes(self):
271
+ """
272
+ Network outputs log probabilities for categorical distribution over discrete value grid.
273
+ """
274
+ return OrderedDict(log_probs=(self.num_atoms,))
275
+
276
+ def forward_train(self, obs_dict, acts, goal_dict=None):
277
+ """
278
+ Return full critic categorical distribution.
279
+
280
+ Args:
281
+ obs_dict (dict): batch of observations
282
+ acts (torch.Tensor): batch of actions
283
+ goal_dict (dict): if not None, batch of goal observations
284
+
285
+ Returns:
286
+ value_distribution (DiscreteValueDistribution instance)
287
+ """
288
+
289
+ # add in actions
290
+ inputs = dict(obs_dict)
291
+ inputs["action"] = acts
292
+
293
+ # network returns unnormalized log probabilities (logits) for each of the value atoms
294
+ logits = MIMO_MLP.forward(self, obs=inputs, goal=goal_dict)["log_probs"]
295
+
296
+ # turn these logits into a categorical distribution over the value atoms.
297
+ # (unsqueeze to make sure atoms are compatible with batch operations)
298
+ value_atoms = torch.Tensor(self._atoms).unsqueeze(0).to(logits.device)
299
+ return DiscreteValueDistribution(values=value_atoms, logits=logits)
300
+
301
+ def forward(self, obs_dict, acts, goal_dict=None):
302
+ """
303
+ Return mean of critic categorical distribution. Useful for obtaining
304
+ point estimates of critic values.
305
+
306
+ Args:
307
+ obs_dict (dict): batch of observations
308
+ acts (torch.Tensor): batch of actions
309
+ goal_dict (dict): if not None, batch of goal observations
310
+
311
+ Returns:
312
+ mean_value (torch.Tensor): expectation of value distribution
313
+ """
314
+ vd = self.forward_train(obs_dict=obs_dict, acts=acts, goal_dict=goal_dict)
315
+ return vd.mean()
316
+
317
+ def _to_string(self):
318
+ return "action_dim={}\nvalue_bounds={}\nnum_atoms={}".format(self.ac_dim, self.value_bounds, self.num_atoms)
aloha-devel/robomimic/utils/__init__.py ADDED
File without changes
aloha-devel/robomimic/utils/__pycache__/__init__.cpython-38.pyc ADDED
Binary file (157 Bytes). View file
 
aloha-devel/robomimic/utils/__pycache__/tensor_utils.cpython-38.pyc ADDED
Binary file (33.1 kB). View file
 
aloha-devel/robomimic/utils/action_utils.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from typing import Union, Sequence, Dict, Optional, Tuple
3
+
4
+ from copy import deepcopy
5
+ from collections import OrderedDict
6
+ import functools
7
+
8
+ import numpy as np
9
+
10
+
11
+ def action_dict_to_vector(
12
+ action_dict: Dict[str, np.ndarray],
13
+ action_keys: Optional[Sequence[str]]=None) -> np.ndarray:
14
+ if action_keys is None:
15
+ action_keys = list(action_dict.keys())
16
+ actions = [action_dict[k] for k in action_keys]
17
+
18
+ action_vec = np.concatenate(actions, axis=-1)
19
+ return action_vec
20
+
21
+
22
+ def vector_to_action_dict(
23
+ action: np.ndarray,
24
+ action_shapes: Dict[str, Tuple[int]],
25
+ action_keys: Sequence[str]) -> Dict[str, np.ndarray]:
26
+ action_dict = dict()
27
+ start_idx = 0
28
+ for key in action_keys:
29
+ this_act_shape = action_shapes[key]
30
+ this_act_dim = np.prod(this_act_shape)
31
+ end_idx = start_idx + this_act_dim
32
+ action_dict[key] = action[...,start_idx:end_idx].reshape(
33
+ action.shape[:-1]+this_act_shape)
34
+ start_idx = end_idx
35
+ return action_dict
aloha-devel/robomimic/utils/dataset.py ADDED
@@ -0,0 +1,1158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This file contains Dataset classes that are used by torch dataloaders
3
+ to fetch batches from hdf5 files.
4
+ """
5
+ import os
6
+ import h5py
7
+ import numpy as np
8
+ import random
9
+ from copy import deepcopy
10
+ from contextlib import contextmanager
11
+ from collections import OrderedDict
12
+
13
+ import torch.utils.data
14
+
15
+ import robomimic.utils.tensor_utils as TensorUtils
16
+ import robomimic.utils.obs_utils as ObsUtils
17
+ import robomimic.utils.action_utils as AcUtils
18
+ import robomimic.utils.log_utils as LogUtils
19
+ import robomimic.utils.lang_utils as LangUtils
20
+
21
+
22
+ class SequenceDataset(torch.utils.data.Dataset):
23
+ def __init__(
24
+ self,
25
+ hdf5_path,
26
+ obs_keys,
27
+ action_keys,
28
+ dataset_keys,
29
+ action_config,
30
+ frame_stack=1,
31
+ seq_length=1,
32
+ pad_frame_stack=True,
33
+ pad_seq_length=True,
34
+ get_pad_mask=False,
35
+ goal_mode=None,
36
+ hdf5_cache_mode=None,
37
+ hdf5_use_swmr=True,
38
+ hdf5_normalize_obs=False,
39
+ filter_by_attribute=None,
40
+ load_next_obs=True,
41
+ shuffled_obs_key_groups=None,
42
+ lang=None,
43
+ ):
44
+ """
45
+ Dataset class for fetching sequences of experience.
46
+ Length of the fetched sequence is equal to (@frame_stack - 1 + @seq_length)
47
+
48
+ Args:
49
+ hdf5_path (str): path to hdf5
50
+
51
+ obs_keys (tuple, list): keys to observation items (image, object, etc) to be fetched from the dataset
52
+
53
+ action_config (dict): TODO
54
+
55
+ dataset_keys (tuple, list): keys to dataset items (actions, rewards, etc) to be fetched from the dataset
56
+
57
+ frame_stack (int): numbers of stacked frames to fetch. Defaults to 1 (single frame).
58
+
59
+ seq_length (int): length of sequences to sample. Defaults to 1 (single frame).
60
+
61
+ pad_frame_stack (int): whether to pad sequence for frame stacking at the beginning of a demo. This
62
+ ensures that partial frame stacks are observed, such as (s_0, s_0, s_0, s_1). Otherwise, the
63
+ first frame stacked observation would be (s_0, s_1, s_2, s_3).
64
+
65
+ pad_seq_length (int): whether to pad sequence for sequence fetching at the end of a demo. This
66
+ ensures that partial sequences at the end of a demonstration are observed, such as
67
+ (s_{T-1}, s_{T}, s_{T}, s_{T}). Otherwise, the last sequence provided would be
68
+ (s_{T-3}, s_{T-2}, s_{T-1}, s_{T}).
69
+
70
+ get_pad_mask (bool): if True, also provide padding masks as part of the batch. This can be
71
+ useful for masking loss functions on padded parts of the data.
72
+
73
+ goal_mode (str): either "last" or None. Defaults to None, which is to not fetch goals
74
+
75
+ hdf5_cache_mode (str): one of ["all", "low_dim", or None]. Set to "all" to cache entire hdf5
76
+ in memory - this is by far the fastest for data loading. Set to "low_dim" to cache all
77
+ non-image data. Set to None to use no caching - in this case, every batch sample is
78
+ retrieved via file i/o. You should almost never set this to None, even for large
79
+ image datasets.
80
+
81
+ hdf5_use_swmr (bool): whether to use swmr feature when opening the hdf5 file. This ensures
82
+ that multiple Dataset instances can all access the same hdf5 file without problems.
83
+
84
+ hdf5_normalize_obs (bool): if True, normalize observations by computing the mean observation
85
+ and std of each observation (in each dimension and modality), and normalizing to unit
86
+ mean and variance in each dimension.
87
+
88
+ filter_by_attribute (str): if provided, use the provided filter key to look up a subset of
89
+ demonstrations to load
90
+
91
+ load_next_obs (bool): whether to load next_obs from the dataset
92
+
93
+ shuffled_obs_key_groups (list): TODO
94
+
95
+ lang: TODO documentation
96
+ """
97
+ super(SequenceDataset, self).__init__()
98
+
99
+ self.hdf5_path = os.path.expanduser(hdf5_path)
100
+ self.hdf5_use_swmr = hdf5_use_swmr
101
+ self.hdf5_normalize_obs = hdf5_normalize_obs
102
+ self._hdf5_file = None
103
+
104
+ assert hdf5_cache_mode in ["all", "low_dim", None]
105
+ self.hdf5_cache_mode = hdf5_cache_mode
106
+
107
+ self.load_next_obs = load_next_obs
108
+ self.filter_by_attribute = filter_by_attribute
109
+
110
+ # get all keys that needs to be fetched
111
+ self.obs_keys = tuple(obs_keys)
112
+ self.action_keys = tuple(action_keys)
113
+ self.dataset_keys = tuple(dataset_keys)
114
+ # add action keys to dataset keys
115
+ if self.action_keys is not None:
116
+ self.dataset_keys = tuple(set(self.dataset_keys).union(set(self.action_keys)))
117
+
118
+ self.action_config = action_config
119
+
120
+ # set up lang and language embedding
121
+ self.lang = lang
122
+ self._lang_emb = LangUtils.get_lang_emb(self.lang)
123
+
124
+ self.n_frame_stack = frame_stack
125
+ assert self.n_frame_stack >= 1
126
+
127
+ self.seq_length = seq_length
128
+ assert self.seq_length >= 1
129
+
130
+ self.goal_mode = goal_mode
131
+ if self.goal_mode is not None:
132
+ assert self.goal_mode in ["last"]
133
+ if not self.load_next_obs:
134
+ assert self.goal_mode != "last" # we use last next_obs as goal
135
+
136
+ self.pad_seq_length = pad_seq_length
137
+ self.pad_frame_stack = pad_frame_stack
138
+ self.get_pad_mask = get_pad_mask
139
+
140
+ self.load_demo_info(filter_by_attribute=self.filter_by_attribute)
141
+
142
+ # maybe prepare for observation normalization
143
+ self.obs_normalization_stats = None
144
+ if self.hdf5_normalize_obs:
145
+ self.obs_normalization_stats = self.normalize_obs()
146
+
147
+ # prepare for action normalization
148
+ self.action_normalization_stats = None
149
+
150
+ # maybe store dataset in memory for fast access
151
+ if self.hdf5_cache_mode in ["all", "low_dim"]:
152
+ obs_keys_in_memory = self.obs_keys
153
+ if self.hdf5_cache_mode == "low_dim":
154
+ # only store low-dim observations
155
+ obs_keys_in_memory = []
156
+ for k in self.obs_keys:
157
+ if ObsUtils.key_is_obs_modality(k, "low_dim"):
158
+ obs_keys_in_memory.append(k)
159
+ self.obs_keys_in_memory = obs_keys_in_memory
160
+
161
+ self.hdf5_cache = self.load_dataset_in_memory(
162
+ demo_list=self.demos,
163
+ hdf5_file=self.hdf5_file,
164
+ obs_keys=self.obs_keys_in_memory,
165
+ dataset_keys=self.dataset_keys,
166
+ load_next_obs=self.load_next_obs
167
+ )
168
+
169
+ if self.hdf5_cache_mode == "all":
170
+ # cache getitem calls for even more speedup. We don't do this for
171
+ # "low-dim" since image observations require calls to getitem anyways.
172
+ print("SequenceDataset: caching get_item calls...")
173
+ self.getitem_cache = [self.get_item(i) for i in LogUtils.custom_tqdm(range(len(self)))]
174
+
175
+ # don't need the previous cache anymore
176
+ del self.hdf5_cache
177
+ self.hdf5_cache = None
178
+ else:
179
+ self.hdf5_cache = None
180
+
181
+ if shuffled_obs_key_groups is None:
182
+ self.shuffled_obs_key_groups = list()
183
+ else:
184
+ self.shuffled_obs_key_groups = shuffled_obs_key_groups
185
+
186
+ self.close_and_delete_hdf5_handle()
187
+
188
+ def load_demo_info(self, filter_by_attribute=None, demos=None):
189
+ """
190
+ Args:
191
+ filter_by_attribute (str): if provided, use the provided filter key
192
+ to select a subset of demonstration trajectories to load
193
+
194
+ demos (list): list of demonstration keys to load from the hdf5 file. If
195
+ omitted, all demos in the file (or under the @filter_by_attribute
196
+ filter key) are used.
197
+ """
198
+ # filter demo trajectory by mask
199
+ if demos is not None:
200
+ self.demos = demos
201
+ elif filter_by_attribute is not None:
202
+ self.demos = [elem.decode("utf-8") for elem in np.array(self.hdf5_file["mask/{}".format(filter_by_attribute)][:])]
203
+ else:
204
+ self.demos = list(self.hdf5_file["data"].keys())
205
+
206
+ # sort demo keys
207
+ inds = np.argsort([int(elem[5:]) for elem in self.demos])
208
+ self.demos = [self.demos[i] for i in inds]
209
+
210
+ self.n_demos = len(self.demos)
211
+
212
+ # keep internal index maps to know which transitions belong to which demos
213
+ self._index_to_demo_id = dict() # maps every index to a demo id
214
+ self._demo_id_to_start_indices = dict() # gives start index per demo id
215
+ self._demo_id_to_demo_length = dict()
216
+
217
+ # determine index mapping
218
+ self.total_num_sequences = 0
219
+ for ep in self.demos:
220
+ demo_length = self.hdf5_file["data/{}".format(ep)].attrs["num_samples"]
221
+ self._demo_id_to_start_indices[ep] = self.total_num_sequences
222
+ self._demo_id_to_demo_length[ep] = demo_length
223
+
224
+ num_sequences = demo_length
225
+ # determine actual number of sequences taking into account whether to pad for frame_stack and seq_length
226
+ if not self.pad_frame_stack:
227
+ num_sequences -= (self.n_frame_stack - 1)
228
+ if not self.pad_seq_length:
229
+ num_sequences -= (self.seq_length - 1)
230
+
231
+ if self.pad_seq_length:
232
+ assert demo_length >= 1 # sequence needs to have at least one sample
233
+ num_sequences = max(num_sequences, 1)
234
+ else:
235
+ assert num_sequences >= 1 # assume demo_length >= (self.n_frame_stack - 1 + self.seq_length)
236
+
237
+ for _ in range(num_sequences):
238
+ self._index_to_demo_id[self.total_num_sequences] = ep
239
+ self.total_num_sequences += 1
240
+
241
+ @property
242
+ def hdf5_file(self):
243
+ """
244
+ This property allows for a lazy hdf5 file open.
245
+ """
246
+ if self._hdf5_file is None:
247
+ self._hdf5_file = h5py.File(self.hdf5_path, 'r', swmr=self.hdf5_use_swmr, libver='latest')
248
+ return self._hdf5_file
249
+
250
+ def close_and_delete_hdf5_handle(self):
251
+ """
252
+ Maybe close the file handle.
253
+ """
254
+ if self._hdf5_file is not None:
255
+ self._hdf5_file.close()
256
+ self._hdf5_file = None
257
+
258
+ @contextmanager
259
+ def hdf5_file_opened(self):
260
+ """
261
+ Convenient context manager to open the file on entering the scope
262
+ and then close it on leaving.
263
+ """
264
+ should_close = self._hdf5_file is None
265
+ yield self.hdf5_file
266
+ if should_close:
267
+ self.close_and_delete_hdf5_handle()
268
+
269
+ def __del__(self):
270
+ self.close_and_delete_hdf5_handle()
271
+
272
+ def __repr__(self):
273
+ """
274
+ Pretty print the class and important attributes on a call to `print`.
275
+ """
276
+ msg = str(self.__class__.__name__)
277
+ msg += " (\n\tpath={}\n\tobs_keys={}\n\tseq_length={}\n\tfilter_key={}\n\tframe_stack={}\n"
278
+ msg += "\tpad_seq_length={}\n\tpad_frame_stack={}\n\tgoal_mode={}\n"
279
+ msg += "\tcache_mode={}\n"
280
+ msg += "\tnum_demos={}\n\tnum_sequences={}\n)"
281
+ filter_key_str = self.filter_by_attribute if self.filter_by_attribute is not None else "none"
282
+ goal_mode_str = self.goal_mode if self.goal_mode is not None else "none"
283
+ cache_mode_str = self.hdf5_cache_mode if self.hdf5_cache_mode is not None else "none"
284
+ msg = msg.format(self.hdf5_path, self.obs_keys, self.seq_length, filter_key_str, self.n_frame_stack,
285
+ self.pad_seq_length, self.pad_frame_stack, goal_mode_str, cache_mode_str,
286
+ self.n_demos, self.total_num_sequences)
287
+ return msg
288
+
289
+ def __len__(self):
290
+ """
291
+ Ensure that the torch dataloader will do a complete pass through all sequences in
292
+ the dataset before starting a new iteration.
293
+ """
294
+ return self.total_num_sequences
295
+
296
+ def load_dataset_in_memory(self, demo_list, hdf5_file, obs_keys, dataset_keys, load_next_obs):
297
+ """
298
+ Loads the hdf5 dataset into memory, preserving the structure of the file. Note that this
299
+ differs from `self.getitem_cache`, which, if active, actually caches the outputs of the
300
+ `getitem` operation.
301
+
302
+ Args:
303
+ demo_list (list): list of demo keys, e.g., 'demo_0'
304
+ hdf5_file (h5py.File): file handle to the hdf5 dataset.
305
+ obs_keys (list, tuple): observation keys to fetch, e.g., 'images'
306
+ dataset_keys (list, tuple): dataset keys to fetch, e.g., 'actions'
307
+ load_next_obs (bool): whether to load next_obs from the dataset
308
+
309
+ Returns:
310
+ all_data (dict): dictionary of loaded data.
311
+ """
312
+ all_data = dict()
313
+ print("SequenceDataset: loading dataset into memory...")
314
+ for ep in LogUtils.custom_tqdm(demo_list):
315
+ all_data[ep] = {}
316
+ all_data[ep]["attrs"] = {}
317
+ all_data[ep]["attrs"]["num_samples"] = hdf5_file["data/{}".format(ep)].attrs["num_samples"]
318
+ # get obs
319
+ all_data[ep]["obs"] = {k: hdf5_file["data/{}/obs/{}".format(ep, k)][()] for k in obs_keys}
320
+ if load_next_obs:
321
+ all_data[ep]["next_obs"] = {k: hdf5_file["data/{}/next_obs/{}".format(ep, k)][()] for k in obs_keys}
322
+ # get other dataset keys
323
+ for k in dataset_keys:
324
+ if k in hdf5_file["data/{}".format(ep)]:
325
+ all_data[ep][k] = hdf5_file["data/{}/{}".format(ep, k)][()].astype('float32')
326
+ else:
327
+ all_data[ep][k] = np.zeros((all_data[ep]["attrs"]["num_samples"], 1), dtype=np.float32)
328
+
329
+ if "model_file" in hdf5_file["data/{}".format(ep)].attrs:
330
+ all_data[ep]["attrs"]["model_file"] = hdf5_file["data/{}".format(ep)].attrs["model_file"]
331
+
332
+ return all_data
333
+
334
+ def normalize_obs(self):
335
+ """
336
+ Computes a dataset-wide mean and standard deviation for the observations
337
+ (per dimension and per obs key) and returns it.
338
+ """
339
+
340
+ # Run through all trajectories. For each one, compute minimal observation statistics, and then aggregate
341
+ # with the previous statistics.
342
+ ep = self.demos[0]
343
+ obs_traj = {k: self.hdf5_file["data/{}/obs/{}".format(ep, k)][()].astype('float32') for k in self.obs_keys}
344
+ obs_traj = ObsUtils.process_obs_dict(obs_traj)
345
+ merged_stats = _compute_traj_stats(obs_traj)
346
+ print("SequenceDataset: normalizing observations...")
347
+ for ep in LogUtils.custom_tqdm(self.demos[1:]):
348
+ obs_traj = {k: self.hdf5_file["data/{}/obs/{}".format(ep, k)][()].astype('float32') for k in self.obs_keys}
349
+ obs_traj = ObsUtils.process_obs_dict(obs_traj)
350
+ traj_stats = _compute_traj_stats(obs_traj)
351
+ merged_stats = _aggregate_traj_stats(merged_stats, traj_stats)
352
+
353
+ obs_normalization_stats = { k : {} for k in merged_stats }
354
+ for k in merged_stats:
355
+ # note we add a small tolerance of 1e-3 for std
356
+ obs_normalization_stats[k]["mean"] = merged_stats[k]["mean"]
357
+ obs_normalization_stats[k]["std"] = np.sqrt(merged_stats[k]["sqdiff"] / merged_stats[k]["n"]) + 1e-3
358
+ return obs_normalization_stats
359
+
360
+ def get_obs_normalization_stats(self):
361
+ """
362
+ Returns dictionary of mean and std for each observation key if using
363
+ observation normalization, otherwise None.
364
+
365
+ Returns:
366
+ obs_normalization_stats (dict): a dictionary for observation
367
+ normalization. This maps observation keys to dicts
368
+ with a "mean" and "std" of shape (1, ...) where ... is the default
369
+ shape for the observation.
370
+ """
371
+ assert self.hdf5_normalize_obs, "not using observation normalization!"
372
+ return deepcopy(self.obs_normalization_stats)
373
+
374
+ def get_action_traj(self, ep):
375
+ action_traj = dict()
376
+ for key in self.action_keys:
377
+ action_traj[key] = self.hdf5_file["data/{}/{}".format(ep, key)][()].astype('float32')
378
+ return action_traj
379
+
380
+ def get_action_stats(self):
381
+ ep = self.demos[0]
382
+ action_traj = self.get_action_traj(ep)
383
+ action_stats = _compute_traj_stats(action_traj)
384
+ print("SequenceDataset: normalizing actions...")
385
+ for ep in LogUtils.custom_tqdm(self.demos[1:]):
386
+ action_traj = self.get_action_traj(ep)
387
+ traj_stats = _compute_traj_stats(action_traj)
388
+ action_stats = _aggregate_traj_stats(action_stats, traj_stats)
389
+ return action_stats
390
+
391
+ def set_action_normalization_stats(self, action_normalization_stats):
392
+ self.action_normalization_stats = action_normalization_stats
393
+
394
+ def get_action_normalization_stats(self):
395
+ """
396
+ Computes a dataset-wide min, max, mean and standard deviation for the actions
397
+ (per dimension) and returns it.
398
+ """
399
+
400
+ # Run through all trajectories. For each one, compute minimal observation statistics, and then aggregate
401
+ # with the previous statistics.
402
+ if self.action_normalization_stats is None:
403
+ action_stats = self.get_action_stats()
404
+ self.action_normalization_stats = action_stats_to_normalization_stats(
405
+ action_stats, self.action_config)
406
+ return self.action_normalization_stats
407
+
408
+ def get_dataset_for_ep(self, ep, key):
409
+ """
410
+ Helper utility to get a dataset for a specific demonstration.
411
+ Takes into account whether the dataset has been loaded into memory.
412
+ """
413
+
414
+ # check if this key should be in memory
415
+ key_should_be_in_memory = (self.hdf5_cache_mode in ["all", "low_dim"])
416
+ if key_should_be_in_memory:
417
+ # if key is an observation, it may not be in memory
418
+ if '/' in key:
419
+ key1, key2 = key.split('/')
420
+ assert(key1 in ['obs', 'next_obs', 'action_dict'])
421
+ if key2 not in self.obs_keys_in_memory:
422
+ key_should_be_in_memory = False
423
+
424
+ if key_should_be_in_memory:
425
+ # read cache
426
+ if '/' in key:
427
+ key1, key2 = key.split('/')
428
+ assert(key1 in ['obs', 'next_obs', 'action_dict'])
429
+ ret = self.hdf5_cache[ep][key1][key2]
430
+ else:
431
+ ret = self.hdf5_cache[ep][key]
432
+ else:
433
+ # read from file
434
+ hd5key = "data/{}/{}".format(ep, key)
435
+ ret = self.hdf5_file[hd5key]
436
+ return ret
437
+
438
+ def __getitem__(self, index):
439
+ """
440
+ Fetch dataset sequence @index (inferred through internal index map), using the getitem_cache if available.
441
+ """
442
+ if self.hdf5_cache_mode == "all":
443
+ output = self.getitem_cache[index]
444
+ else:
445
+ output = self.get_item(index)
446
+
447
+ for (g1, g2) in self.shuffled_obs_key_groups:
448
+ assert len(g1) == len(g2)
449
+ if random.random() > 0.5:
450
+ # shuffle the keys accordingly
451
+ for (o1, o2) in zip(g1, g2):
452
+ for otype in ["obs", "next_obs", "goal_obs"]:
453
+ if output.get(otype, None) is None:
454
+ continue
455
+ if o1 not in output[otype] or o2 not in output[otype]:
456
+ continue
457
+ # swap values
458
+ output[otype][o1], output[otype][o2] = output[otype][o2], output[otype][o1]
459
+
460
+ return output
461
+
462
+ def get_item(self, index):
463
+ """
464
+ Main implementation of getitem when not using cache.
465
+ """
466
+
467
+ demo_id = self._index_to_demo_id[index]
468
+ demo_start_index = self._demo_id_to_start_indices[demo_id]
469
+ demo_length = self._demo_id_to_demo_length[demo_id]
470
+
471
+ # start at offset index if not padding for frame stacking
472
+ demo_index_offset = 0 if self.pad_frame_stack else (self.n_frame_stack - 1)
473
+ index_in_demo = index - demo_start_index + demo_index_offset
474
+
475
+ # end at offset index if not padding for seq length
476
+ demo_length_offset = 0 if self.pad_seq_length else (self.seq_length - 1)
477
+ end_index_in_demo = demo_length - demo_length_offset
478
+
479
+ meta = self.get_dataset_sequence_from_demo(
480
+ demo_id,
481
+ index_in_demo=index_in_demo,
482
+ keys=self.dataset_keys,
483
+ num_frames_to_stack=self.n_frame_stack - 1, # note: need to decrement self.n_frame_stack by one
484
+ seq_length=self.seq_length
485
+ )
486
+
487
+ # determine goal index
488
+ goal_index = None
489
+ if self.goal_mode == "last":
490
+ goal_index = end_index_in_demo - 1
491
+
492
+ meta["obs"] = self.get_obs_sequence_from_demo(
493
+ demo_id,
494
+ index_in_demo=index_in_demo,
495
+ keys=self.obs_keys,
496
+ num_frames_to_stack=self.n_frame_stack - 1,
497
+ seq_length=self.seq_length,
498
+ prefix="obs"
499
+ )
500
+
501
+ if self.load_next_obs:
502
+ meta["next_obs"] = self.get_obs_sequence_from_demo(
503
+ demo_id,
504
+ index_in_demo=index_in_demo,
505
+ keys=self.obs_keys,
506
+ num_frames_to_stack=self.n_frame_stack - 1,
507
+ seq_length=self.seq_length,
508
+ prefix="next_obs"
509
+ )
510
+
511
+ if goal_index is not None:
512
+ goal = self.get_obs_sequence_from_demo(
513
+ demo_id,
514
+ index_in_demo=goal_index,
515
+ keys=self.obs_keys,
516
+ num_frames_to_stack=0,
517
+ seq_length=1,
518
+ prefix="next_obs",
519
+ )
520
+ meta["goal_obs"] = {k: goal[k][0] for k in goal} # remove sequence dimension for goal
521
+
522
+ # get action components
523
+ ac_dict = OrderedDict()
524
+ for k in self.action_keys:
525
+ ac = meta[k]
526
+ # expand action shape if needed
527
+ if len(ac.shape) == 1:
528
+ ac = ac.reshape(-1, 1)
529
+ ac_dict[k] = ac
530
+
531
+ # normalize actions
532
+ action_normalization_stats = self.get_action_normalization_stats()
533
+ ac_dict = ObsUtils.normalize_dict(ac_dict, normalization_stats=action_normalization_stats)
534
+
535
+ # concatenate all action components
536
+ meta["actions"] = AcUtils.action_dict_to_vector(ac_dict)
537
+
538
+ # also return the sampled index
539
+ meta["index"] = index
540
+
541
+ # language embedding
542
+ T = meta["actions"].shape[0]
543
+ meta["obs"]["lang_emb"] = np.tile(self._lang_emb, (T, 1))
544
+
545
+ return meta
546
+
547
+ def get_sequence_from_demo(self, demo_id, index_in_demo, keys, num_frames_to_stack=0, seq_length=1):
548
+ """
549
+ Extract a (sub)sequence of data items from a demo given the @keys of the items.
550
+
551
+ Args:
552
+ demo_id (str): id of the demo, e.g., demo_0
553
+ index_in_demo (int): beginning index of the sequence wrt the demo
554
+ keys (tuple): list of keys to extract
555
+ num_frames_to_stack (int): numbers of frame to stack. Seq gets prepended with repeated items if out of range
556
+ seq_length (int): sequence length to extract. Seq gets post-pended with repeated items if out of range
557
+
558
+ Returns:
559
+ a dictionary of extracted items.
560
+ """
561
+ assert num_frames_to_stack >= 0
562
+ assert seq_length >= 1
563
+
564
+ demo_length = self._demo_id_to_demo_length[demo_id]
565
+ assert index_in_demo < demo_length
566
+
567
+ # determine begin and end of sequence
568
+ seq_begin_index = max(0, index_in_demo - num_frames_to_stack)
569
+ seq_end_index = min(demo_length, index_in_demo + seq_length)
570
+
571
+ # determine sequence padding
572
+ seq_begin_pad = max(0, num_frames_to_stack - index_in_demo) # pad for frame stacking
573
+ seq_end_pad = max(0, index_in_demo + seq_length - demo_length) # pad for sequence length
574
+
575
+ # make sure we are not padding if specified.
576
+ if not self.pad_frame_stack:
577
+ assert seq_begin_pad == 0
578
+ if not self.pad_seq_length:
579
+ assert seq_end_pad == 0
580
+
581
+ # fetch observation from the dataset file
582
+ seq = dict()
583
+ for k in keys:
584
+ data = self.get_dataset_for_ep(demo_id, k)
585
+ seq[k] = data[seq_begin_index: seq_end_index]
586
+
587
+ seq = TensorUtils.pad_sequence(seq, padding=(seq_begin_pad, seq_end_pad), pad_same=True)
588
+ pad_mask = np.array([0] * seq_begin_pad + [1] * (seq_end_index - seq_begin_index) + [0] * seq_end_pad)
589
+ pad_mask = pad_mask[:, None].astype(bool)
590
+
591
+ return seq, pad_mask
592
+
593
+ def get_obs_sequence_from_demo(self, demo_id, index_in_demo, keys, num_frames_to_stack=0, seq_length=1, prefix="obs"):
594
+ """
595
+ Extract a (sub)sequence of observation items from a demo given the @keys of the items.
596
+
597
+ Args:
598
+ demo_id (str): id of the demo, e.g., demo_0
599
+ index_in_demo (int): beginning index of the sequence wrt the demo
600
+ keys (tuple): list of keys to extract
601
+ num_frames_to_stack (int): numbers of frame to stack. Seq gets prepended with repeated items if out of range
602
+ seq_length (int): sequence length to extract. Seq gets post-pended with repeated items if out of range
603
+ prefix (str): one of "obs", "next_obs"
604
+
605
+ Returns:
606
+ a dictionary of extracted items.
607
+ """
608
+ obs, pad_mask = self.get_sequence_from_demo(
609
+ demo_id,
610
+ index_in_demo=index_in_demo,
611
+ keys=tuple('{}/{}'.format(prefix, k) for k in keys),
612
+ num_frames_to_stack=num_frames_to_stack,
613
+ seq_length=seq_length,
614
+ )
615
+ obs = {'/'.join(k.split('/')[1:]): obs[k] for k in obs} # strip the prefix
616
+ if self.get_pad_mask:
617
+ obs["pad_mask"] = pad_mask
618
+
619
+ return obs
620
+
621
+ def get_dataset_sequence_from_demo(self, demo_id, index_in_demo, keys, num_frames_to_stack=0, seq_length=1):
622
+ """
623
+ Extract a (sub)sequence of dataset items from a demo given the @keys of the items (e.g., states, actions).
624
+
625
+ Args:
626
+ demo_id (str): id of the demo, e.g., demo_0
627
+ index_in_demo (int): beginning index of the sequence wrt the demo
628
+ keys (tuple): list of keys to extract
629
+ num_frames_to_stack (int): numbers of frame to stack. Seq gets prepended with repeated items if out of range
630
+ seq_length (int): sequence length to extract. Seq gets post-pended with repeated items if out of range
631
+
632
+ Returns:
633
+ a dictionary of extracted items.
634
+ """
635
+ data, pad_mask = self.get_sequence_from_demo(
636
+ demo_id,
637
+ index_in_demo=index_in_demo,
638
+ keys=keys,
639
+ num_frames_to_stack=num_frames_to_stack,
640
+ seq_length=seq_length,
641
+ )
642
+ if self.get_pad_mask:
643
+ data["pad_mask"] = pad_mask
644
+ return data
645
+
646
+ def get_trajectory_at_index(self, index):
647
+ """
648
+ Method provided as a utility to get an entire trajectory, given
649
+ the corresponding @index.
650
+ """
651
+ demo_id = self.demos[index]
652
+ demo_length = self._demo_id_to_demo_length[demo_id]
653
+
654
+ meta = self.get_dataset_sequence_from_demo(
655
+ demo_id,
656
+ index_in_demo=0,
657
+ keys=self.dataset_keys,
658
+ num_frames_to_stack=self.n_frame_stack - 1, # note: need to decrement self.n_frame_stack by one
659
+ seq_length=demo_length
660
+ )
661
+ meta["obs"] = self.get_obs_sequence_from_demo(
662
+ demo_id,
663
+ index_in_demo=0,
664
+ keys=self.obs_keys,
665
+ seq_length=demo_length
666
+ )
667
+ if self.load_next_obs:
668
+ meta["next_obs"] = self.get_obs_sequence_from_demo(
669
+ demo_id,
670
+ index_in_demo=0,
671
+ keys=self.obs_keys,
672
+ seq_length=demo_length,
673
+ prefix="next_obs"
674
+ )
675
+
676
+ meta["ep"] = demo_id
677
+ return meta
678
+
679
+ def get_dataset_sampler(self):
680
+ """
681
+ Return instance of torch.utils.data.Sampler or None. Allows
682
+ for dataset to define custom sampling logic, such as
683
+ re-weighting the probability of samples being drawn.
684
+ See the `train` function in scripts/train.py, and torch
685
+ `DataLoader` documentation, for more info.
686
+ """
687
+ return None
688
+
689
+
690
+ class R2D2Dataset(SequenceDataset):
691
+ def get_action_traj(self, ep):
692
+ action_traj = dict()
693
+ for key in self.action_keys:
694
+ action_traj[key] = self.hdf5_file[key][()].astype('float32')
695
+ if len(action_traj[key].shape) == 1:
696
+ action_traj[key] = np.reshape(action_traj[key], (-1, 1))
697
+
698
+ return action_traj
699
+
700
+ def load_demo_info(self, filter_by_attribute=None, demos=None, n_demos=None):
701
+ """
702
+ Args:
703
+ filter_by_attribute (str): if provided, use the provided filter key
704
+ to select a subset of demonstration trajectories to load
705
+
706
+ demos (list): list of demonstration keys to load from the hdf5 file. If
707
+ omitted, all demos in the file (or under the @filter_by_attribute
708
+ filter key) are used.
709
+ """
710
+
711
+ self.demos = ["demo"]
712
+
713
+ self.n_demos = len(self.demos)
714
+
715
+ # keep internal index maps to know which transitions belong to which demos
716
+ self._index_to_demo_id = dict() # maps every index to a demo id
717
+ self._demo_id_to_start_indices = dict() # gives start index per demo id
718
+ self._demo_id_to_demo_length = dict()
719
+
720
+ # segment time stamps
721
+ self._demo_id_to_segments = dict()
722
+
723
+ ep = self.demos[0]
724
+
725
+ # determine index mapping
726
+ self.total_num_sequences = 0
727
+ demo_length = self.hdf5_file["action/cartesian_velocity"].shape[0]
728
+ self._demo_id_to_start_indices[ep] = self.total_num_sequences
729
+ self._demo_id_to_demo_length[ep] = demo_length
730
+
731
+ # seperate demo into segments for better alignment
732
+ gripper_actions = list(self.hdf5_file["action/gripper_position"])
733
+ gripper_closed = [1 if x > 0 else 0 for x in gripper_actions]
734
+
735
+ try:
736
+ # find when the gripper fist opens/closes
737
+ gripper_close = gripper_closed.index(1)
738
+ gripper_open = gripper_close + gripper_closed[gripper_close:].index(0)
739
+ except ValueError:
740
+ # special case for (invalid) trajectories
741
+ gripper_close, gripper_open = int(demo_length / 3), int(demo_length / 3 * 2)
742
+ print("No gripper action:", gripper_actions)
743
+ self._demo_id_to_segments[ep] = [0, gripper_close, gripper_open, demo_length - 1]
744
+
745
+ num_sequences = demo_length
746
+ # determine actual number of sequences taking into account whether to pad for frame_stack and seq_length
747
+ if not self.pad_frame_stack:
748
+ num_sequences -= (self.n_frame_stack - 1)
749
+ if not self.pad_seq_length:
750
+ num_sequences -= (self.seq_length - 1)
751
+
752
+ if self.pad_seq_length:
753
+ assert demo_length >= 1 # sequence needs to have at least one sample
754
+ num_sequences = max(num_sequences, 1)
755
+ else:
756
+ assert num_sequences >= 1 # assume demo_length >= (self.n_frame_stack - 1 + self.seq_length)
757
+
758
+ for _ in range(num_sequences):
759
+ self._index_to_demo_id[self.total_num_sequences] = ep
760
+ self.total_num_sequences += 1
761
+
762
+ def load_dataset_in_memory(self, demo_list, hdf5_file, obs_keys, dataset_keys, load_next_obs):
763
+ """
764
+ Loads the hdf5 dataset into memory, preserving the structure of the file. Note that this
765
+ differs from `self.getitem_cache`, which, if active, actually caches the outputs of the
766
+ `getitem` operation.
767
+
768
+ Args:
769
+ demo_list (list): list of demo keys, e.g., 'demo_0'
770
+ hdf5_file (h5py.File): file handle to the hdf5 dataset.
771
+ obs_keys (list, tuple): observation keys to fetch, e.g., 'images'
772
+ dataset_keys (list, tuple): dataset keys to fetch, e.g., 'actions'
773
+ load_next_obs (bool): whether to load next_obs from the dataset
774
+
775
+ Returns:
776
+ all_data (dict): dictionary of loaded data.
777
+ """
778
+ all_data = dict()
779
+ print("SequenceDataset: loading dataset into memory...")
780
+
781
+ for ep in LogUtils.custom_tqdm(demo_list):
782
+ all_data[ep] = {}
783
+ all_data[ep]["attrs"] = {}
784
+ all_data[ep]["attrs"]["num_samples"] = hdf5_file["action/cartesian_velocity"].shape[0] # hack to get traj len
785
+ # get obs
786
+ all_data[ep]["obs"] = {k: hdf5_file["observation/{}".format(k)][()].astype('float32') for k in obs_keys}
787
+ if load_next_obs:
788
+ raise NotImplementedError
789
+ # get other dataset keys
790
+ for k in dataset_keys:
791
+ if k in hdf5_file.keys():
792
+ all_data[ep][k] = hdf5_file["{}".format(k)][()].astype('float32')
793
+ else:
794
+ raise NotImplementedError
795
+
796
+ return all_data
797
+
798
+ def get_dataset_for_ep(self, ep, key, try_to_use_cache=True):
799
+ """
800
+ Helper utility to get a dataset for a specific demonstration.
801
+ Takes into account whether the dataset has been loaded into memory.
802
+ """
803
+
804
+ # check if this key should be in memory
805
+ key_should_be_in_memory = try_to_use_cache and (self.hdf5_cache_mode in ["all", "low_dim"])
806
+ if key_should_be_in_memory:
807
+ # if key is an observation, it may not be in memory
808
+ if '/' in key:
809
+ key_splits = key.split('/')
810
+ key1 = key_splits[0]
811
+ key2 = "/".join(key_splits[1:])
812
+ if key1 == "observation" and key2 not in self.obs_keys_in_memory:
813
+ key_should_be_in_memory = False
814
+
815
+ if key_should_be_in_memory:
816
+ # read cache
817
+ if '/' in key:
818
+ key_splits = key.split('/')
819
+ key1 = key_splits[0]
820
+ key2 = "/".join(key_splits[1:])
821
+ if key1 == "observation":
822
+ ret = self.hdf5_cache[ep]["obs"][key2]
823
+ else:
824
+ ret = self.hdf5_cache[ep][key]
825
+ else:
826
+ ret = self.hdf5_cache[ep][key]
827
+ else:
828
+ # read from file
829
+ hd5key = "{}".format(key) #"data/{}/{}".format(ep, key)
830
+ ret = self.hdf5_file[hd5key]
831
+ return ret
832
+
833
+
834
+ def get_sequence_from_demo(self, demo_id, index_in_demo, keys, num_frames_to_stack=0, seq_length=1):
835
+ """
836
+ Extract a (sub)sequence of data items from a demo given the @keys of the items.
837
+
838
+ Args:
839
+ demo_id (str): id of the demo, e.g., demo_0
840
+ index_in_demo (int): beginning index of the sequence wrt the demo
841
+ keys (tuple): list of keys to extract
842
+ num_frames_to_stack (int): numbers of frame to stack. Seq gets prepended with repeated items if out of range
843
+ seq_length (int): sequence length to extract. Seq gets post-pended with repeated items if out of range
844
+
845
+ Returns:
846
+ a dictionary of extracted items.
847
+ """
848
+ assert num_frames_to_stack >= 0
849
+ assert seq_length >= 1
850
+
851
+ demo_length = self._demo_id_to_demo_length[demo_id]
852
+ assert index_in_demo < demo_length
853
+
854
+ # determine begin and end of sequence
855
+ seq_begin_index = max(0, index_in_demo - num_frames_to_stack)
856
+ seq_end_index = min(demo_length, index_in_demo + seq_length)
857
+
858
+ # determine sequence padding
859
+ seq_begin_pad = max(0, num_frames_to_stack - index_in_demo) # pad for frame stacking
860
+ seq_end_pad = max(0, index_in_demo + seq_length - demo_length) # pad for sequence length
861
+
862
+ # make sure we are not padding if specified.
863
+ if not self.pad_frame_stack:
864
+ assert seq_begin_pad == 0
865
+ if not self.pad_seq_length:
866
+ assert seq_end_pad == 0
867
+
868
+ # fetch observation from the dataset file
869
+ seq = dict()
870
+ for k in keys:
871
+ data = self.get_dataset_for_ep(demo_id, k)
872
+ seq[k] = data[seq_begin_index: seq_end_index].astype("float32")
873
+
874
+ seq = TensorUtils.pad_sequence(seq, padding=(seq_begin_pad, seq_end_pad), pad_same=True)
875
+ pad_mask = np.array([0] * seq_begin_pad + [1] * (seq_end_index - seq_begin_index) + [0] * seq_end_pad)
876
+ pad_mask = pad_mask[:, None].astype(np.bool_)
877
+
878
+ return seq, pad_mask
879
+
880
+
881
+ def get_item(self, index):
882
+ """
883
+ Main implementation of getitem when not using cache.
884
+ """
885
+
886
+ demo_id = self._index_to_demo_id[index]
887
+ demo_start_index = self._demo_id_to_start_indices[demo_id]
888
+ demo_length = self._demo_id_to_demo_length[demo_id]
889
+
890
+ # start at offset index if not padding for frame stacking
891
+ demo_index_offset = 0 if self.pad_frame_stack else (self.n_frame_stack - 1)
892
+ index_in_demo = index - demo_start_index + demo_index_offset
893
+
894
+ # end at offset index if not padding for seq length
895
+ demo_length_offset = 0 if self.pad_seq_length else (self.seq_length - 1)
896
+ end_index_in_demo = demo_length - demo_length_offset
897
+
898
+ meta = self.get_dataset_sequence_from_demo(
899
+ demo_id,
900
+ index_in_demo=index_in_demo,
901
+ keys=self.dataset_keys,
902
+ num_frames_to_stack=self.n_frame_stack - 1,
903
+ seq_length=self.seq_length,
904
+ )
905
+
906
+ # determine goal index
907
+ goal_index = None
908
+ if self.goal_mode == "last":
909
+ goal_index = end_index_in_demo - 1
910
+
911
+ meta["obs"] = self.get_obs_sequence_from_demo(
912
+ demo_id,
913
+ index_in_demo=index_in_demo,
914
+ keys=self.obs_keys,
915
+ num_frames_to_stack=self.n_frame_stack - 1,
916
+ seq_length=self.seq_length,
917
+ prefix="observation"
918
+ )
919
+
920
+ if self.load_next_obs:
921
+ meta["next_obs"] = self.get_obs_sequence_from_demo(
922
+ demo_id,
923
+ index_in_demo=index_in_demo,
924
+ keys=self.obs_keys,
925
+ num_frames_to_stack=self.n_frame_stack - 1,
926
+ seq_length=self.seq_length,
927
+ prefix="next_obs"
928
+ )
929
+
930
+ if goal_index is not None:
931
+ goal = self.get_obs_sequence_from_demo(
932
+ demo_id,
933
+ index_in_demo=goal_index,
934
+ keys=self.obs_keys,
935
+ num_frames_to_stack=0,
936
+ seq_length=1,
937
+ prefix="next_obs",
938
+ )
939
+ meta["goal_obs"] = {k: goal[k][0] for k in goal} # remove sequence dimension for goal
940
+
941
+ # get action components
942
+ ac_dict = OrderedDict()
943
+ for k in self.action_keys:
944
+ ac = meta[k]
945
+ # expand action shape if needed
946
+ if len(ac.shape) == 1:
947
+ ac = ac.reshape(-1, 1)
948
+ ac_dict[k] = ac
949
+
950
+ # normalize actions
951
+ action_normalization_stats = self.get_action_normalization_stats()
952
+ ac_dict = ObsUtils.normalize_dict(ac_dict, normalization_stats=action_normalization_stats)
953
+
954
+ # concatenate all action components
955
+ meta["actions"] = AcUtils.action_dict_to_vector(ac_dict)
956
+
957
+ # keys to reshape
958
+ for k in meta["obs"]:
959
+ if len(meta["obs"][k].shape) == 1:
960
+ meta["obs"][k] = np.expand_dims(meta["obs"][k], axis=1)
961
+
962
+ # also return the sampled index
963
+ meta["index"] = index
964
+
965
+ # language embedding
966
+ T = meta["actions"].shape[0]
967
+ meta["obs"]["lang_emb"] = np.tile(self._lang_emb, (T, 1))
968
+
969
+ return meta
970
+
971
+
972
+ class MetaDataset(torch.utils.data.Dataset):
973
+ def __init__(
974
+ self,
975
+ datasets,
976
+ ds_weights,
977
+ normalize_weights_by_ds_size=False,
978
+ ):
979
+ super(MetaDataset, self).__init__()
980
+ self.datasets = datasets
981
+ ds_lens = np.array([len(ds) for ds in self.datasets])
982
+ if normalize_weights_by_ds_size:
983
+ self.ds_weights = np.array(ds_weights) / ds_lens
984
+ else:
985
+ self.ds_weights = ds_weights
986
+ self._ds_ind_bins = np.cumsum([0] + list(ds_lens))
987
+
988
+ # cache mode "all" not supported! The action normalization stats of each
989
+ # dataset will change after the datasets are already initialized
990
+ for ds in self.datasets:
991
+ assert ds.hdf5_cache_mode != "all"
992
+
993
+ # TODO: comment
994
+ action_stats = self.get_action_stats()
995
+ self.action_normalization_stats = action_stats_to_normalization_stats(
996
+ action_stats, self.datasets[0].action_config)
997
+ self.set_action_normalization_stats(self.action_normalization_stats)
998
+
999
+ def __len__(self):
1000
+ return np.sum([len(ds) for ds in self.datasets])
1001
+
1002
+ def __getitem__(self, idx):
1003
+ ds_ind = np.digitize(idx, self._ds_ind_bins) - 1
1004
+ ind_in_ds = idx - self._ds_ind_bins[ds_ind]
1005
+ meta = self.datasets[ds_ind].__getitem__(ind_in_ds)
1006
+ meta["index"] = idx
1007
+ return meta
1008
+
1009
+ def get_ds_label(self, idx):
1010
+ ds_ind = np.digitize(idx, self._ds_ind_bins) - 1
1011
+ ds_label = self.ds_labels[ds_ind]
1012
+ return ds_label
1013
+
1014
+ def get_ds_id(self, idx):
1015
+ ds_ind = np.digitize(idx, self._ds_ind_bins) - 1
1016
+ ds_label = self.ds_labels[ds_ind]
1017
+ return self.ds_labels_to_ids[ds_label]
1018
+
1019
+ def __repr__(self):
1020
+ str_output = '\n'.join([ds.__repr__() for ds in self.datasets])
1021
+ return str_output
1022
+
1023
+ def get_dataset_sampler(self):
1024
+ weights = np.ones(len(self))
1025
+ for i, (start, end) in enumerate(zip(self._ds_ind_bins[:-1], self._ds_ind_bins[1:])):
1026
+ weights[start:end] = self.ds_weights[i]
1027
+
1028
+ sampler = torch.utils.data.WeightedRandomSampler(
1029
+ weights=weights,
1030
+ num_samples=len(self),
1031
+ replacement=True,
1032
+ )
1033
+ return sampler
1034
+
1035
+ def get_action_stats(self):
1036
+ meta_action_stats = self.datasets[0].get_action_stats()
1037
+ for dataset in self.datasets[1:]:
1038
+ ds_action_stats = dataset.get_action_stats()
1039
+ meta_action_stats = _aggregate_traj_stats(meta_action_stats, ds_action_stats)
1040
+
1041
+ return meta_action_stats
1042
+
1043
+ def set_action_normalization_stats(self, action_normalization_stats):
1044
+ self.action_normalization_stats = action_normalization_stats
1045
+ for ds in self.datasets:
1046
+ ds.set_action_normalization_stats(self.action_normalization_stats)
1047
+
1048
+ def get_action_normalization_stats(self):
1049
+ """
1050
+ Computes a dataset-wide min, max, mean and standard deviation for the actions
1051
+ (per dimension) and returns it.
1052
+ """
1053
+
1054
+ # Run through all trajectories. For each one, compute minimal observation statistics, and then aggregate
1055
+ # with the previous statistics.
1056
+ if self.action_normalization_stats is None:
1057
+ action_stats = self.get_action_stats()
1058
+ self.action_normalization_stats = action_stats_to_normalization_stats(
1059
+ action_stats, self.datasets[0].action_config)
1060
+ return self.action_normalization_stats
1061
+
1062
+ def _compute_traj_stats(traj_obs_dict):
1063
+ """
1064
+ Helper function to compute statistics over a single trajectory of observations.
1065
+ """
1066
+ traj_stats = { k : {} for k in traj_obs_dict }
1067
+ for k in traj_obs_dict:
1068
+ traj_stats[k]["n"] = traj_obs_dict[k].shape[0]
1069
+ traj_stats[k]["mean"] = traj_obs_dict[k].mean(axis=0, keepdims=True) # [1, ...]
1070
+ traj_stats[k]["sqdiff"] = ((traj_obs_dict[k] - traj_stats[k]["mean"]) ** 2).sum(axis=0, keepdims=True) # [1, ...]
1071
+ traj_stats[k]["min"] = traj_obs_dict[k].min(axis=0, keepdims=True)
1072
+ traj_stats[k]["max"] = traj_obs_dict[k].max(axis=0, keepdims=True)
1073
+ return traj_stats
1074
+
1075
+ def _aggregate_traj_stats(traj_stats_a, traj_stats_b):
1076
+ """
1077
+ Helper function to aggregate trajectory statistics.
1078
+ See https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Parallel_algorithm
1079
+ for more information.
1080
+ """
1081
+ merged_stats = {}
1082
+ for k in traj_stats_a:
1083
+ n_a, avg_a, M2_a, min_a, max_a = traj_stats_a[k]["n"], traj_stats_a[k]["mean"], traj_stats_a[k]["sqdiff"], traj_stats_a[k]["min"], traj_stats_a[k]["max"]
1084
+ n_b, avg_b, M2_b, min_b, max_b = traj_stats_b[k]["n"], traj_stats_b[k]["mean"], traj_stats_b[k]["sqdiff"], traj_stats_b[k]["min"], traj_stats_b[k]["max"]
1085
+ n = n_a + n_b
1086
+ mean = (n_a * avg_a + n_b * avg_b) / n
1087
+ delta = (avg_b - avg_a)
1088
+ M2 = M2_a + M2_b + (delta ** 2) * (n_a * n_b) / n
1089
+ min_ = np.minimum(min_a, min_b)
1090
+ max_ = np.maximum(max_a, max_b)
1091
+ merged_stats[k] = dict(n=n, mean=mean, sqdiff=M2, min=min_, max=max_)
1092
+ return merged_stats
1093
+
1094
+ def action_stats_to_normalization_stats(action_stats, action_config):
1095
+ action_normalization_stats = OrderedDict()
1096
+ for action_key in action_stats.keys():
1097
+ # get how this action should be normalized from config, default to None
1098
+ norm_method = action_config[action_key].get("normalization", None)
1099
+ if norm_method is None:
1100
+ # no normalization, unit scale, zero offset
1101
+ action_normalization_stats[action_key] = {
1102
+ "scale": np.ones_like(action_stats[action_key]["mean"], dtype=np.float32),
1103
+ "offset": np.zeros_like(action_stats[action_key]["mean"], dtype=np.float32)
1104
+ }
1105
+ elif norm_method == "min_max":
1106
+ # normalize min to -1 and max to 1
1107
+ range_eps = 1e-4
1108
+ input_min = action_stats[action_key]["min"].astype(np.float32)
1109
+ input_max = action_stats[action_key]["max"].astype(np.float32)
1110
+ # instead of -1 and 1 use numbers just below threshold to prevent numerical instability issues
1111
+ output_min = -0.999999
1112
+ output_max = 0.999999
1113
+
1114
+ # ignore input dimentions that is too small to prevent division by zero
1115
+ input_range = input_max - input_min
1116
+ ignore_dim = input_range < range_eps
1117
+ input_range[ignore_dim] = output_max - output_min
1118
+
1119
+ # expected usage of scale and offset
1120
+ # normalized_action = (raw_action - offset) / scale
1121
+ # raw_action = scale * normalized_action + offset
1122
+
1123
+ # eq1: input_max = scale * output_max + offset
1124
+ # eq2: input_min = scale * output_min + offset
1125
+
1126
+ # solution for scale and offset
1127
+ # eq1 - eq2:
1128
+ # input_max - input_min = scale * (output_max - output_min)
1129
+ # (input_max - input_min) / (output_max - output_min) = scale <- eq3
1130
+ # offset = input_min - scale * output_min <- eq4
1131
+ scale = input_range / (output_max - output_min)
1132
+ offset = input_min - scale * output_min
1133
+
1134
+ offset[ignore_dim] = input_min[ignore_dim] - (output_max + output_min) / 2
1135
+
1136
+ action_normalization_stats[action_key] = {
1137
+ "scale": scale,
1138
+ "offset": offset
1139
+ }
1140
+ elif norm_method == "gaussian":
1141
+ # normalize to zero mean unit variance
1142
+ input_mean = action_stats[action_key]["mean"].astype(np.float32)
1143
+ input_std = np.sqrt(action_stats[action_key]["sqdiff"] / action_stats[action_key]["n"]).astype(np.float32)
1144
+
1145
+ # ignore input dimentions that is too small to prevent division by zero
1146
+ std_eps = 1e-6
1147
+ ignore_dim = input_std < std_eps
1148
+ input_std[ignore_dim] = 1.0
1149
+
1150
+ action_normalization_stats[action_key] = {
1151
+ "scale": input_mean,
1152
+ "offset": input_std
1153
+ }
1154
+ else:
1155
+ raise NotImplementedError(
1156
+ 'action_config.actions.normalization: "{}" is not supported'.format(norm_method))
1157
+
1158
+ return action_normalization_stats
aloha-devel/robomimic/utils/hyperparam_utils.py ADDED
@@ -0,0 +1,373 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ A collection of utility functions and classes for generating config jsons for hyperparameter sweeps.
3
+ """
4
+ import argparse
5
+ import os
6
+ import json
7
+ import re
8
+ import itertools
9
+
10
+ from collections import OrderedDict
11
+ from copy import deepcopy
12
+
13
+
14
+ class ConfigGenerator(object):
15
+ """
16
+ Useful class to keep track of hyperparameters to sweep, and to generate
17
+ the json configs for each experiment run.
18
+ """
19
+ def __init__(self, base_config_file, wandb_proj_name="debug", script_file=None, generated_config_dir=None):
20
+ """
21
+ Args:
22
+ base_config_file (str): path to a base json config to use as a starting point
23
+ for the parameter sweep.
24
+
25
+ script_file (str): script filename to write as output
26
+ """
27
+ assert isinstance(base_config_file, str)
28
+ self.base_config_file = base_config_file
29
+ assert generated_config_dir is None or isinstance(generated_config_dir, str)
30
+ if generated_config_dir is not None:
31
+ generated_config_dir = os.path.expanduser(generated_config_dir)
32
+ self.generated_config_dir = generated_config_dir
33
+ assert script_file is None or isinstance(script_file, str)
34
+ if script_file is None:
35
+ self.script_file = os.path.join('~', 'tmp/tmpp.sh')
36
+ else:
37
+ self.script_file = script_file
38
+ self.script_file = os.path.expanduser(self.script_file)
39
+ self.parameters = OrderedDict()
40
+
41
+ assert isinstance(wandb_proj_name, str)
42
+ self.wandb_proj_name = wandb_proj_name
43
+
44
+ def add_param(self, key, name, group, values, value_names=None, hidename=False, prepend=False):
45
+ """
46
+ Add parameter to the hyperparameter sweep.
47
+
48
+ Args:
49
+ key (str): location of parameter in the config, using hierarchical key format
50
+ (ex. train/data = config.train.data)
51
+
52
+ name (str): name, as it will appear in the experiment name
53
+
54
+ group (int): group id - parameters with the same ID have their values swept
55
+ together
56
+
57
+ values (list): list of values to sweep over for this parameter
58
+
59
+ value_names ([str]): if provided, strings to use in experiment name for
60
+ each value, instead of the parameter value. This is helpful for parameters
61
+ that may have long or large values (for example, dataset path).
62
+ """
63
+ if value_names is not None:
64
+ assert len(values) == len(value_names)
65
+ self.parameters[key] = argparse.Namespace(
66
+ key=key,
67
+ name=name,
68
+ group=group,
69
+ values=values,
70
+ value_names=value_names,
71
+ hidename=hidename,
72
+ )
73
+ if prepend:
74
+ self.parameters.move_to_end(key, last=False)
75
+
76
+ def generate(self, override_base_name=False):
77
+ """
78
+ Generates json configs for the hyperparameter sweep using attributes
79
+ @self.parameters, @self.base_config_file, and @self.script_file,
80
+ all of which should have first been set externally by calling
81
+ @add_param, @set_base_config_file, and @set_script_file.
82
+ """
83
+ assert len(self.parameters) > 0, "must add parameters using add_param first!"
84
+ generated_json_paths = self._generate_jsons(override_base_name=override_base_name)
85
+ self._script_from_jsons(generated_json_paths)
86
+
87
+ def _name_for_experiment(self, base_name, parameter_values, parameter_value_names):
88
+ """
89
+ This function generates the name for an experiment, given one specific
90
+ parameter setting.
91
+
92
+ Args:
93
+ base_name (str): base experiment name
94
+ parameter_values (OrderedDict): dictionary that maps parameter name to
95
+ the parameter value for this experiment run
96
+ parameter_value_names (dict): dictionary that maps parameter name to
97
+ the name to use for its value in the experiment name
98
+
99
+ Returns:
100
+ name (str): generated experiment name
101
+ """
102
+ name = base_name
103
+ for k in parameter_values:
104
+ # append parameter name and value to end of base name
105
+ if len(self.parameters[k].name) == 0 or self.parameters[k].hidename:
106
+ # empty string indicates that naming should be skipped
107
+ continue
108
+ if parameter_value_names[k] is not None:
109
+ # take name from passed dictionary
110
+ val_str = parameter_value_names[k]
111
+ else:
112
+ val_str = parameter_values[k]
113
+ if isinstance(parameter_values[k], list) or isinstance(parameter_values[k], tuple):
114
+ # convert list to string to avoid weird spaces and naming problems
115
+ val_str = "_".join([str(x) for x in parameter_values[k]])
116
+ val_str = str(val_str)
117
+ if len(name) > 0:
118
+ name += "_"
119
+ name += '{}'.format(self.parameters[k].name)
120
+ if len(val_str) > 0:
121
+ name += '_{}'.format(val_str)
122
+ return name
123
+
124
+ def _get_parameter_ranges(self):
125
+ """
126
+ Extract parameter ranges from base json file. Also takes all possible
127
+ combinations of the parameter ranges to generate an expanded set of values.
128
+
129
+ Returns:
130
+ parameter_ranges (dict): dictionary that maps the parameter to a list
131
+ of all values it should take for each generated config. The length
132
+ of the list will be the total number of configs that will be
133
+ generated from this scan.
134
+
135
+ parameter_names (dict): dictionary that maps the parameter to a list
136
+ of all name strings that should contribute to each invididual
137
+ experiment's name. The length of the list will be the total
138
+ number of configs that will be generated from this scan.
139
+ """
140
+
141
+ # mapping from group id to list of indices to grab from each parameter's list
142
+ # of values in the parameter group
143
+ parameter_group_indices = OrderedDict()
144
+ for k in self.parameters:
145
+ group_id = self.parameters[k].group
146
+ assert isinstance(self.parameters[k].values, list)
147
+ num_param_values = len(self.parameters[k].values)
148
+ if group_id not in parameter_group_indices:
149
+ parameter_group_indices[group_id] = list(range(num_param_values))
150
+ else:
151
+ assert len(parameter_group_indices[group_id]) == num_param_values, \
152
+ "error: inconsistent number of parameter values in group with id {}".format(group_id)
153
+
154
+ keys = list(parameter_group_indices.keys())
155
+ inds = list(parameter_group_indices.values())
156
+ new_parameter_group_indices = OrderedDict(
157
+ { k : [] for k in keys }
158
+ )
159
+ # get all combinations of the different parameter group indices
160
+ # and then use these indices to determine the new parameter ranges
161
+ # per member of each parameter group.
162
+ #
163
+ # e.g. with two parameter groups, one with two values, and another with three values
164
+ # we have [0, 1] x [0, 1, 2] = [0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]
165
+ # so the corresponding parameter group indices are [0, 0, 0, 1, 1, 1] and
166
+ # [0, 1, 2, 0, 1, 2], and all parameters in each parameter group are indexed
167
+ # together using these indices, to get each parameter range.
168
+ for comb in itertools.product(*inds):
169
+ for i in range(len(comb)):
170
+ new_parameter_group_indices[keys[i]].append(comb[i])
171
+ parameter_group_indices = new_parameter_group_indices
172
+
173
+ # use the indices to gather the parameter values to sweep per parameter
174
+ parameter_ranges = OrderedDict()
175
+ parameter_names = OrderedDict()
176
+ for k in self.parameters:
177
+ parameter_values = self.parameters[k].values
178
+ group_id = self.parameters[k].group
179
+ inds = parameter_group_indices[group_id]
180
+ parameter_ranges[k] = [parameter_values[ind] for ind in inds]
181
+
182
+ # add in parameter names if supplied
183
+ parameter_names[k] = None
184
+ if self.parameters[k].value_names is not None:
185
+ par_names = self.parameters[k].value_names
186
+ assert isinstance(par_names, list)
187
+ assert len(par_names) == len(parameter_values)
188
+ parameter_names[k] = [par_names[ind] for ind in inds]
189
+
190
+ # ensure that the number of parameter settings is the same per parameter
191
+ first_key = list(parameter_ranges.keys())[0]
192
+ num_settings = len(parameter_ranges[first_key])
193
+ for k in parameter_ranges:
194
+ assert len(parameter_ranges[k]) == num_settings, "inconsistent number of values"
195
+
196
+ return parameter_ranges, parameter_names
197
+
198
+ def _generate_jsons(self, override_base_name=False):
199
+ """
200
+ Generates json configs for the hyperparameter sweep, using @self.parameters and
201
+ @self.base_config_file.
202
+
203
+ Returns:
204
+ json_paths (list): list of paths to created json files, one per experiment
205
+ """
206
+
207
+ # base directory for saving jsons
208
+ if self.generated_config_dir:
209
+ base_dir = self.generated_config_dir
210
+ if not os.path.exists(base_dir):
211
+ os.makedirs(base_dir)
212
+ else:
213
+ base_dir = os.path.abspath(os.path.dirname(self.base_config_file))
214
+
215
+ # read base json
216
+ base_config = load_json(self.base_config_file, verbose=False)
217
+
218
+ # base exp name from this base config
219
+ if override_base_name:
220
+ base_exp_name = ""
221
+ else:
222
+ base_exp_name = base_config['experiment']['name']
223
+
224
+ # use base json to determine the parameter ranges
225
+ parameter_ranges, parameter_names = self._get_parameter_ranges()
226
+
227
+ # iterate through each parameter setting to create each json
228
+ first_key = list(parameter_ranges.keys())[0]
229
+ num_settings = len(parameter_ranges[first_key])
230
+
231
+ # keep track of path to generated jsons
232
+ json_paths = []
233
+
234
+ for i in range(num_settings):
235
+ # the specific parameter setting for this experiment
236
+ setting = { k : parameter_ranges[k][i] for k in parameter_ranges }
237
+ maybe_parameter_names = OrderedDict()
238
+ for k in parameter_names:
239
+ maybe_parameter_names[k] = None
240
+ if parameter_names[k] is not None:
241
+ maybe_parameter_names[k] = parameter_names[k][i]
242
+
243
+ # experiment name from setting
244
+ exp_name = self._name_for_experiment(
245
+ base_name=base_exp_name,
246
+ parameter_values=setting,
247
+ parameter_value_names=maybe_parameter_names,
248
+ )
249
+
250
+ # copy old json, but override name, and parameter values
251
+ json_dict = deepcopy(base_config)
252
+ json_dict['experiment']['name'] = exp_name
253
+ for k in parameter_ranges:
254
+ set_value_for_key(json_dict, k, v=parameter_ranges[k][i])
255
+
256
+ # populate list of identifying meta for logger;
257
+ # see meta_config method in base_config.py for more info
258
+ json_dict["experiment"]["logging"]["wandb_proj_name"] = self.wandb_proj_name
259
+ if "meta" not in json_dict:
260
+ json_dict["meta"] = dict()
261
+ json_dict["meta"].update(
262
+ hp_base_config_file=self.base_config_file,
263
+ hp_keys=list(),
264
+ hp_values=list(),
265
+ )
266
+ # logging: keep track of hyp param names and values as meta info
267
+ for k in parameter_ranges.keys():
268
+ key_name = self.parameters[k].name
269
+ if key_name is not None and len(key_name) > 0:
270
+ if maybe_parameter_names[k] is not None:
271
+ value_name = maybe_parameter_names[k]
272
+ else:
273
+ value_name = setting[k]
274
+
275
+ json_dict["meta"]["hp_keys"].append(key_name)
276
+ json_dict["meta"]["hp_values"].append(value_name)
277
+
278
+ # save file in same directory as old json
279
+ json_path = os.path.join(base_dir, "{}.json".format(exp_name))
280
+ save_json(json_dict, json_path)
281
+ json_paths.append(json_path)
282
+
283
+ print("Num exps:", len(json_paths))
284
+
285
+ return json_paths
286
+
287
+ def _script_from_jsons(self, json_paths):
288
+ """
289
+ Generates a bash script to run the experiments that correspond to
290
+ the input jsons.
291
+ """
292
+ with open(self.script_file, 'w') as f:
293
+ f.write("#!/bin/bash\n\n")
294
+ for path in json_paths:
295
+ # write python command to file
296
+ import robomimic
297
+ cmd = "python {}/scripts/train.py --config {}\n".format(robomimic.__path__[0], path)
298
+
299
+ print()
300
+ print(cmd)
301
+ f.write(cmd)
302
+
303
+
304
+ def load_json(json_file, verbose=True):
305
+ """
306
+ Simple utility function to load a json file as a dict.
307
+
308
+ Args:
309
+ json_file (str): path to json file to load
310
+ verbose (bool): if True, pretty print the loaded json dictionary
311
+
312
+ Returns:
313
+ config (dict): json dictionary
314
+ """
315
+ with open(json_file, 'r') as f:
316
+ config = json.load(f)
317
+ if verbose:
318
+ print('loading external config: =================')
319
+ print(json.dumps(config, indent=4))
320
+ print('==========================================')
321
+ return config
322
+
323
+
324
+ def save_json(config, json_file):
325
+ """
326
+ Simple utility function to save a dictionary to a json file on disk.
327
+
328
+ Args:
329
+ config (dict): dictionary to save
330
+ json_file (str): path to json file to write
331
+ """
332
+ with open(json_file, 'w') as f:
333
+ # preserve original key ordering
334
+ json.dump(config, f, sort_keys=False, indent=4)
335
+
336
+
337
+ def get_value_for_key(dic, k):
338
+ """
339
+ Get value for nested dictionary with levels denoted by "/" or ".".
340
+ For example, if @k is "a/b", then this function returns
341
+ @dic["a"]["b"].
342
+
343
+ Args:
344
+ dic (dict): a nested dictionary
345
+ k (str): a single string meant to index several levels down into
346
+ the nested dictionary, where levels can be denoted by "/" or
347
+ by ".".
348
+ Returns:
349
+ val: the nested dictionary value for the provided key
350
+ """
351
+ val = dic
352
+ subkeys = re.split('/|\.', k)
353
+ for s in subkeys[:-1]:
354
+ val = val[s]
355
+ return val[subkeys[-1]]
356
+
357
+
358
+ def set_value_for_key(dic, k, v):
359
+ """
360
+ Set value for hierarchical dictionary with levels denoted by "/" or ".".
361
+
362
+ Args:
363
+ dic (dict): a nested dictionary
364
+ k (str): a single string meant to index several levels down into
365
+ the nested dictionary, where levels can be denoted by "/" or
366
+ by ".".
367
+ v: the value to set at the provided key
368
+ """
369
+ val = dic
370
+ subkeys = re.split('/|\.', k) #k.split('/')
371
+ for s in subkeys[:-1]:
372
+ val = val[s]
373
+ val[subkeys[-1]] = v
aloha-devel/robomimic/utils/lang_utils.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from transformers import AutoModel, pipeline, AutoTokenizer, CLIPTextModelWithProjection
3
+
4
+ os.environ["TOKENIZERS_PARALLELISM"] = "true" # needed to suppress warning about potential deadlock
5
+ tokenizer = "openai/clip-vit-large-patch14" #"openai/clip-vit-base-patch32"
6
+ lang_emb_model = CLIPTextModelWithProjection.from_pretrained(
7
+ tokenizer,
8
+ cache_dir=os.path.expanduser("~/tmp/clip")
9
+ ).eval()
10
+ tz = AutoTokenizer.from_pretrained(tokenizer, TOKENIZERS_PARALLELISM=True)
11
+
12
+ def get_lang_emb(lang):
13
+ if lang is None:
14
+ return None
15
+
16
+ tokens = tz(
17
+ text=lang, # the sentence to be encoded
18
+ add_special_tokens=True, # Add [CLS] and [SEP]
19
+ max_length=25, # maximum length of a sentence
20
+ padding="max_length",
21
+ return_attention_mask=True, # Generate the attention mask
22
+ return_tensors="pt", # ask the function to return PyTorch tensors
23
+ )
24
+ lang_emb = lang_emb_model(**tokens)['text_embeds'].detach()[0]
25
+
26
+ return lang_emb
27
+
aloha-devel/robomimic/utils/loss_utils.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This file contains a collection of useful loss functions for use with torch tensors.
3
+ """
4
+
5
+ import math
6
+ import numpy as np
7
+ import torch
8
+ import torch.nn.functional as F
9
+
10
+
11
+ def cosine_loss(preds, labels):
12
+ """
13
+ Cosine loss between two tensors.
14
+
15
+ Args:
16
+ preds (torch.Tensor): torch tensor
17
+ labels (torch.Tensor): torch tensor
18
+
19
+ Returns:
20
+ loss (torch.Tensor): cosine loss
21
+ """
22
+ sim = torch.nn.CosineSimilarity(dim=len(preds.shape) - 1)(preds, labels)
23
+ return -torch.mean(sim - 1.0)
24
+
25
+
26
+ def KLD_0_1_loss(mu, logvar):
27
+ """
28
+ KL divergence loss. Computes D_KL( N(mu, sigma) || N(0, 1) ). Note that
29
+ this function averages across the batch dimension, but sums across dimension.
30
+
31
+ Args:
32
+ mu (torch.Tensor): mean tensor of shape (B, D)
33
+ logvar (torch.Tensor): logvar tensor of shape (B, D)
34
+
35
+ Returns:
36
+ loss (torch.Tensor): KL divergence loss between the input gaussian distribution
37
+ and N(0, 1)
38
+ """
39
+ return -0.5 * (1. + logvar - mu.pow(2) - logvar.exp()).sum(dim=1).mean()
40
+
41
+
42
+ def KLD_gaussian_loss(mu_1, logvar_1, mu_2, logvar_2):
43
+ """
44
+ KL divergence loss between two Gaussian distributions. This function
45
+ computes the average loss across the batch.
46
+
47
+ Args:
48
+ mu_1 (torch.Tensor): first means tensor of shape (B, D)
49
+ logvar_1 (torch.Tensor): first logvars tensor of shape (B, D)
50
+ mu_2 (torch.Tensor): second means tensor of shape (B, D)
51
+ logvar_2 (torch.Tensor): second logvars tensor of shape (B, D)
52
+
53
+ Returns:
54
+ loss (torch.Tensor): KL divergence loss between the two gaussian distributions
55
+ """
56
+ return -0.5 * (1. + \
57
+ logvar_1 - logvar_2 \
58
+ - ((mu_2 - mu_1).pow(2) / logvar_2.exp()) \
59
+ - (logvar_1.exp() / logvar_2.exp()) \
60
+ ).sum(dim=1).mean()
61
+
62
+
63
+ def log_normal(x, m, v):
64
+ """
65
+ Log probability of tensor x under diagonal multivariate normal with
66
+ mean m and variance v. The last dimension of the tensors is treated
67
+ as the dimension of the Gaussian distribution - all other dimensions
68
+ are treated as independent Gaussians. Adapted from CS 236 at Stanford.
69
+
70
+ Args:
71
+ x (torch.Tensor): tensor with shape (B, ..., D)
72
+ m (torch.Tensor): means tensor with shape (B, ..., D) or (1, ..., D)
73
+ v (torch.Tensor): variances tensor with shape (B, ..., D) or (1, ..., D)
74
+
75
+ Returns:
76
+ log_prob (torch.Tensor): log probabilities of shape (B, ...)
77
+ """
78
+ element_wise = -0.5 * (torch.log(v) + (x - m).pow(2) / v + np.log(2 * np.pi))
79
+ log_prob = element_wise.sum(-1)
80
+ return log_prob
81
+
82
+
83
+ def log_normal_mixture(x, m, v, w=None, log_w=None):
84
+ """
85
+ Log probability of tensor x under a uniform mixture of Gaussians.
86
+ Adapted from CS 236 at Stanford.
87
+
88
+ Args:
89
+ x (torch.Tensor): tensor with shape (B, D)
90
+ m (torch.Tensor): means tensor with shape (B, M, D) or (1, M, D), where
91
+ M is number of mixture components
92
+ v (torch.Tensor): variances tensor with shape (B, M, D) or (1, M, D) where
93
+ M is number of mixture components
94
+ w (torch.Tensor): weights tensor - if provided, should be
95
+ shape (B, M) or (1, M)
96
+ log_w (torch.Tensor): log-weights tensor - if provided, should be
97
+ shape (B, M) or (1, M)
98
+
99
+ Returns:
100
+ log_prob (torch.Tensor): log probabilities of shape (B,)
101
+ """
102
+
103
+ # (B , D) -> (B , 1, D)
104
+ x = x.unsqueeze(1)
105
+ # (B, 1, D) -> (B, M, D) -> (B, M)
106
+ log_prob = log_normal(x, m, v)
107
+ if w is not None or log_w is not None:
108
+ # this weights the log probabilities by the mixture weights so we have log(w_i * N(x | m_i, v_i))
109
+ if w is not None:
110
+ assert log_w is None
111
+ log_w = torch.log(w)
112
+ log_prob += log_w
113
+ # then compute log sum_i exp [log(w_i * N(x | m_i, v_i))]
114
+ # (B, M) -> (B,)
115
+ log_prob = log_sum_exp(log_prob , dim=1)
116
+ else:
117
+ # (B, M) -> (B,)
118
+ log_prob = log_mean_exp(log_prob , dim=1) # mean accounts for uniform weights
119
+ return log_prob
120
+
121
+
122
+ def log_mean_exp(x, dim):
123
+ """
124
+ Compute the log(mean(exp(x), dim)) in a numerically stable manner.
125
+ Adapted from CS 236 at Stanford.
126
+
127
+ Args:
128
+ x (torch.Tensor): a tensor
129
+ dim (int): dimension along which mean is computed
130
+
131
+ Returns:
132
+ y (torch.Tensor): log(mean(exp(x), dim))
133
+ """
134
+ return log_sum_exp(x, dim) - np.log(x.size(dim))
135
+
136
+
137
+ def log_sum_exp(x, dim=0):
138
+ """
139
+ Compute the log(sum(exp(x), dim)) in a numerically stable manner.
140
+ Adapted from CS 236 at Stanford.
141
+
142
+ Args:
143
+ x (torch.Tensor): a tensor
144
+ dim (int): dimension along which sum is computed
145
+
146
+ Returns:
147
+ y (torch.Tensor): log(sum(exp(x), dim))
148
+ """
149
+ max_x = torch.max(x, dim)[0]
150
+ new_x = x - max_x.unsqueeze(dim).expand_as(x)
151
+ return max_x + (new_x.exp().sum(dim)).log()
152
+
153
+
154
+ def project_values_onto_atoms(values, probabilities, atoms):
155
+ """
156
+ Project the categorical distribution given by @probabilities on the
157
+ grid of values given by @values onto a grid of values given by @atoms.
158
+ This is useful when computing a bellman backup where the backed up
159
+ values from the original grid will not be in the original support,
160
+ requiring L2 projection.
161
+
162
+ Each value in @values has a corresponding probability in @probabilities -
163
+ this probability mass is shifted to the closest neighboring grid points in
164
+ @atoms in proportion. For example, if the value in question is 0.2, and the
165
+ neighboring atoms are 0 and 1, then 0.8 of the probability weight goes to
166
+ atom 0 and 0.2 of the probability weight will go to 1.
167
+
168
+ Adapted from https://github.com/deepmind/acme/blob/master/acme/tf/losses/distributional.py#L42
169
+
170
+ Args:
171
+ values: value grid to project, of shape (batch_size, n_atoms)
172
+ probabilities: probabilities for categorical distribution on @values, shape (batch_size, n_atoms)
173
+ atoms: value grid to project onto, of shape (n_atoms,) or (1, n_atoms)
174
+
175
+ Returns:
176
+ new probability vectors that correspond to the L2 projection of the categorical distribution
177
+ onto @atoms
178
+ """
179
+
180
+ # make sure @atoms is shape (n_atoms,)
181
+ if len(atoms.shape) > 1:
182
+ atoms = atoms.squeeze(0)
183
+
184
+ # helper tensors from @atoms
185
+ vmin, vmax = atoms[0], atoms[1]
186
+ d_pos = torch.cat([atoms, vmin[None]], dim=0)[1:]
187
+ d_neg = torch.cat([vmax[None], atoms], dim=0)[:-1]
188
+
189
+ # ensure that @values grid is within the support of @atoms
190
+ clipped_values = values.clamp(min=vmin, max=vmax)[:, None, :] # (batch_size, 1, n_atoms)
191
+ clipped_atoms = atoms[None, :, None] # (1, n_atoms, 1)
192
+
193
+ # distance between atom values in support
194
+ d_pos = (d_pos - atoms)[None, :, None] # atoms[i + 1] - atoms[i], shape (1, n_atoms, 1)
195
+ d_neg = (atoms - d_neg)[None, :, None] # atoms[i] - atoms[i - 1], shape (1, n_atoms, 1)
196
+
197
+ # distances between all pairs of grid values
198
+ deltas = clipped_values - clipped_atoms # (batch_size, n_atoms, n_atoms)
199
+
200
+ # computes eqn (7) in distributional RL paper by doing the following - for each
201
+ # output atom in @atoms, consider values that are close enough, and weight their
202
+ # probability mass contribution by the normalized distance in [0, 1] given
203
+ # by (1. - (z_j - z_i) / (delta_z)).
204
+ d_sign = (deltas >= 0.).float()
205
+ delta_hat = (d_sign * deltas / d_pos) - ((1. - d_sign) * deltas / d_neg)
206
+ delta_hat = (1. - delta_hat).clamp(min=0., max=1.)
207
+ probabilities = probabilities[:, None, :]
208
+ return (delta_hat * probabilities).sum(dim=2)