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

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/act/detr/README.md +9 -0
  2. aloha-devel/act/detr/setup.py +10 -0
  3. aloha-devel/act/detr/util/__pycache__/__init__.cpython-38.pyc +0 -0
  4. aloha-devel/act/inference.py +766 -0
  5. aloha-devel/act/policy.py +166 -0
  6. aloha-devel/act/test_inference.py +180 -0
  7. aloha-devel/act/train.py +376 -0
  8. aloha-devel/robomimic/__pycache__/macros.cpython-38.pyc +0 -0
  9. aloha-devel/robomimic/algo/__init__.py +13 -0
  10. aloha-devel/robomimic/algo/__pycache__/__init__.cpython-38.pyc +0 -0
  11. aloha-devel/robomimic/algo/__pycache__/bc.cpython-38.pyc +0 -0
  12. aloha-devel/robomimic/algo/act.py +247 -0
  13. aloha-devel/robomimic/algo/algo.py +674 -0
  14. aloha-devel/robomimic/algo/bc.py +899 -0
  15. aloha-devel/robomimic/algo/bcq.py +1022 -0
  16. aloha-devel/robomimic/algo/cql.py +668 -0
  17. aloha-devel/robomimic/algo/diffusion_policy.py +700 -0
  18. aloha-devel/robomimic/algo/gl.py +775 -0
  19. aloha-devel/robomimic/algo/hbc.py +344 -0
  20. aloha-devel/robomimic/algo/iql.py +428 -0
  21. aloha-devel/robomimic/algo/iris.py +183 -0
  22. aloha-devel/robomimic/algo/td3_bc.py +567 -0
  23. aloha-devel/robomimic/config/__pycache__/__init__.cpython-38.pyc +0 -0
  24. aloha-devel/robomimic/config/__pycache__/bc_config.cpython-38.pyc +0 -0
  25. aloha-devel/robomimic/config/__pycache__/bcq_config.cpython-38.pyc +0 -0
  26. aloha-devel/robomimic/config/__pycache__/diffusion_policy_config.cpython-38.pyc +0 -0
  27. aloha-devel/robomimic/config/__pycache__/gl_config.cpython-38.pyc +0 -0
  28. aloha-devel/robomimic/config/__pycache__/hbc_config.cpython-38.pyc +0 -0
  29. aloha-devel/robomimic/config/__pycache__/iql_config.cpython-38.pyc +0 -0
  30. aloha-devel/robomimic/config/__pycache__/iris_config.cpython-38.pyc +0 -0
  31. aloha-devel/robomimic/config/act_config.py +47 -0
  32. aloha-devel/robomimic/config/base_config.py +354 -0
  33. aloha-devel/robomimic/config/bc_config.py +110 -0
  34. aloha-devel/robomimic/config/cql_config.py +82 -0
  35. aloha-devel/robomimic/config/diffusion_policy_config.py +60 -0
  36. aloha-devel/robomimic/config/gl_config.py +89 -0
  37. aloha-devel/robomimic/config/iris_config.py +99 -0
  38. aloha-devel/robomimic/config/td3_bc_config.py +111 -0
  39. aloha-devel/robomimic/exps/templates/bcq.json +235 -0
  40. aloha-devel/robomimic/exps/templates/diffusion_policy.json +175 -0
  41. aloha-devel/robomimic/models/__init__.py +1 -0
  42. aloha-devel/robomimic/models/__pycache__/obs_core.cpython-38.pyc +0 -0
  43. aloha-devel/robomimic/models/__pycache__/obs_nets.cpython-38.pyc +0 -0
  44. aloha-devel/robomimic/models/__pycache__/vae_nets.cpython-38.pyc +0 -0
  45. aloha-devel/robomimic/models/distributions.py +123 -0
  46. aloha-devel/robomimic/models/obs_core.py +829 -0
  47. aloha-devel/robomimic/models/policy_nets.py +1570 -0
  48. aloha-devel/robomimic/models/transformers.py +426 -0
  49. aloha-devel/robomimic/models/vae_nets.py +1386 -0
  50. aloha-devel/robomimic/scripts/config_gen/act_gen.py +131 -0
aloha-devel/act/detr/README.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ This part of the codebase is modified from DETR https://github.com/facebookresearch/detr under APACHE 2.0.
2
+
3
+ @article{Carion2020EndtoEndOD,
4
+ title={End-to-End Object Detection with Transformers},
5
+ author={Nicolas Carion and Francisco Massa and Gabriel Synnaeve and Nicolas Usunier and Alexander Kirillov and Sergey Zagoruyko},
6
+ journal={ArXiv},
7
+ year={2020},
8
+ volume={abs/2005.12872}
9
+ }
aloha-devel/act/detr/setup.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from distutils.core import setup
2
+ from setuptools import find_packages
3
+
4
+ setup(
5
+ name='detr',
6
+ version='0.0.0',
7
+ packages=find_packages(),
8
+ license='MIT License',
9
+ long_description=open('README.md').read(),
10
+ )
aloha-devel/act/detr/util/__pycache__/__init__.cpython-38.pyc ADDED
Binary file (153 Bytes). View file
 
aloha-devel/act/inference.py ADDED
@@ -0,0 +1,766 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/home/lin/software/miniconda3/envs/aloha/bin/python
2
+ # -- coding: UTF-8
3
+ """
4
+ #!/usr/bin/python3
5
+ """
6
+
7
+ import torch
8
+ import numpy as np
9
+ import os
10
+ import pickle
11
+ import argparse
12
+ from einops import rearrange
13
+
14
+ from utils import compute_dict_mean, set_seed, detach_dict # helper functions
15
+ from policy import ACTPolicy, CNNMLPPolicy, DiffusionPolicy
16
+ import collections
17
+ from collections import deque
18
+
19
+ import rospy
20
+ from std_msgs.msg import Header
21
+ from geometry_msgs.msg import Twist
22
+ from sensor_msgs.msg import JointState, Image
23
+ from nav_msgs.msg import Odometry
24
+ from cv_bridge import CvBridge
25
+ import time
26
+ import threading
27
+ import math
28
+ import threading
29
+
30
+
31
+ import sys
32
+ sys.path.append("./")
33
+
34
+ task_config = {'camera_names': ['cam_high', 'cam_left_wrist', 'cam_right_wrist']}
35
+
36
+ inference_thread = None
37
+ inference_lock = threading.Lock()
38
+ inference_actions = None
39
+ inference_timestep = None
40
+
41
+
42
+ def actions_interpolation(args, pre_action, actions, stats):
43
+ steps = np.concatenate((np.array(args.arm_steps_length), np.array(args.arm_steps_length)), axis=0)
44
+ pre_process = lambda s_qpos: (s_qpos - stats['qpos_mean']) / stats['qpos_std']
45
+ post_process = lambda a: a * stats['qpos_std'] + stats['qpos_mean']
46
+ result = [pre_action]
47
+ post_action = post_process(actions[0])
48
+ # print("pre_action:", pre_action[7:])
49
+ # print("actions_interpolation1:", post_action[:, 7:])
50
+ max_diff_index = 0
51
+ max_diff = -1
52
+ for i in range(post_action.shape[0]):
53
+ diff = 0
54
+ for j in range(pre_action.shape[0]):
55
+ if j == 6 or j == 13:
56
+ continue
57
+ diff += math.fabs(pre_action[j] - post_action[i][j])
58
+ if diff > max_diff:
59
+ max_diff = diff
60
+ max_diff_index = i
61
+
62
+ for i in range(max_diff_index, post_action.shape[0]):
63
+ step = max([math.floor(math.fabs(result[-1][j] - post_action[i][j])/steps[j]) for j in range(pre_action.shape[0])])
64
+ inter = np.linspace(result[-1], post_action[i], step+2)
65
+ result.extend(inter[1:])
66
+ while len(result) < args.chunk_size+1:
67
+ result.append(result[-1])
68
+ result = np.array(result)[1:args.chunk_size+1]
69
+ # print("actions_interpolation2:", result.shape, result[:, 7:])
70
+ result = pre_process(result)
71
+ result = result[np.newaxis, :]
72
+ return result
73
+
74
+
75
+ def get_model_config(args):
76
+ # 设置随机种子,你可以确保在相同的初始条件下,每次运行代码时生成的随机数序列是相同的。
77
+ set_seed(1)
78
+
79
+ # 如果是ACT策略
80
+ # fixed parameters
81
+ if args.policy_class == 'ACT':
82
+ policy_config = {'lr': args.lr,
83
+ 'lr_backbone': args.lr_backbone,
84
+ 'backbone': args.backbone,
85
+ 'masks': args.masks,
86
+ 'weight_decay': args.weight_decay,
87
+ 'dilation': args.dilation,
88
+ 'position_embedding': args.position_embedding,
89
+ 'loss_function': args.loss_function,
90
+ 'chunk_size': args.chunk_size, # 查询
91
+ 'camera_names': task_config['camera_names'],
92
+ 'use_depth_image': args.use_depth_image,
93
+ 'use_robot_base': args.use_robot_base,
94
+ 'kl_weight': args.kl_weight, # kl散度权重
95
+ 'hidden_dim': args.hidden_dim, # 隐藏层维度
96
+ 'dim_feedforward': args.dim_feedforward,
97
+ 'enc_layers': args.enc_layers,
98
+ 'dec_layers': args.dec_layers,
99
+ 'nheads': args.nheads,
100
+ 'dropout': args.dropout,
101
+ 'pre_norm': args.pre_norm
102
+ }
103
+ elif args.policy_class == 'CNNMLP':
104
+ policy_config = {'lr': args.lr,
105
+ 'lr_backbone': args.lr_backbone,
106
+ 'backbone': args.backbone,
107
+ 'masks': args.masks,
108
+ 'weight_decay': args.weight_decay,
109
+ 'dilation': args.dilation,
110
+ 'position_embedding': args.position_embedding,
111
+ 'loss_function': args.loss_function,
112
+ 'chunk_size': 1, # 查询
113
+ 'camera_names': task_config['camera_names'],
114
+ 'use_depth_image': args.use_depth_image,
115
+ 'use_robot_base': args.use_robot_base
116
+ }
117
+
118
+ elif args.policy_class == 'Diffusion':
119
+ policy_config = {'lr': args.lr,
120
+ 'lr_backbone': args.lr_backbone,
121
+ 'backbone': args.backbone,
122
+ 'masks': args.masks,
123
+ 'weight_decay': args.weight_decay,
124
+ 'dilation': args.dilation,
125
+ 'position_embedding': args.position_embedding,
126
+ 'loss_function': args.loss_function,
127
+ 'chunk_size': args.chunk_size, # 查询
128
+ 'camera_names': task_config['camera_names'],
129
+ 'use_depth_image': args.use_depth_image,
130
+ 'use_robot_base': args.use_robot_base,
131
+ 'observation_horizon': args.observation_horizon,
132
+ 'action_horizon': args.action_horizon,
133
+ 'num_inference_timesteps': args.num_inference_timesteps,
134
+ 'ema_power': args.ema_power
135
+ }
136
+ else:
137
+ raise NotImplementedError
138
+
139
+ config = {
140
+ 'ckpt_dir': args.ckpt_dir,
141
+ 'ckpt_name': args.ckpt_name,
142
+ 'ckpt_stats_name': args.ckpt_stats_name,
143
+ 'episode_len': args.max_publish_step,
144
+ 'state_dim': args.state_dim,
145
+ 'policy_class': args.policy_class,
146
+ 'policy_config': policy_config,
147
+ 'temporal_agg': args.temporal_agg,
148
+ 'camera_names': task_config['camera_names'],
149
+ }
150
+ return config
151
+
152
+
153
+ def make_policy(policy_class, policy_config):
154
+ if policy_class == 'ACT':
155
+ policy = ACTPolicy(policy_config)
156
+ elif policy_class == 'CNNMLP':
157
+ policy = CNNMLPPolicy(policy_config)
158
+ elif policy_class == 'Diffusion':
159
+ policy = DiffusionPolicy(policy_config)
160
+ else:
161
+ raise NotImplementedError
162
+ return policy
163
+
164
+
165
+ def get_image(observation, camera_names):
166
+ curr_images = []
167
+ for cam_name in camera_names:
168
+ curr_image = rearrange(observation['images'][cam_name], 'h w c -> c h w')
169
+
170
+ curr_images.append(curr_image)
171
+ curr_image = np.stack(curr_images, axis=0)
172
+ curr_image = torch.from_numpy(curr_image / 255.0).float().cuda().unsqueeze(0)
173
+ return curr_image
174
+
175
+
176
+ def get_depth_image(observation, camera_names):
177
+ curr_images = []
178
+ for cam_name in camera_names:
179
+ curr_images.append(observation['images_depth'][cam_name])
180
+ curr_image = np.stack(curr_images, axis=0)
181
+ curr_image = torch.from_numpy(curr_image / 255.0).float().cuda().unsqueeze(0)
182
+ return curr_image
183
+
184
+
185
+ def inference_process(args, config, ros_operator, policy, stats, t, pre_action):
186
+ global inference_lock
187
+ global inference_actions
188
+ global inference_timestep
189
+ print_flag = True
190
+ pre_pos_process = lambda s_qpos: (s_qpos - stats['qpos_mean']) / stats['qpos_std']
191
+ pre_action_process = lambda next_action: (next_action - stats["action_mean"]) / stats["action_std"]
192
+ rate = rospy.Rate(args.publish_rate)
193
+ while True and not rospy.is_shutdown():
194
+ result = ros_operator.get_frame()
195
+ if not result:
196
+ if print_flag:
197
+ print("syn fail")
198
+ print_flag = False
199
+ rate.sleep()
200
+ continue
201
+ print_flag = True
202
+ (img_front, img_left, img_right, img_front_depth, img_left_depth, img_right_depth,
203
+ puppet_arm_left, puppet_arm_right, robot_base) = result
204
+ obs = collections.OrderedDict()
205
+ image_dict = dict()
206
+
207
+ image_dict[config['camera_names'][0]] = img_front
208
+ image_dict[config['camera_names'][1]] = img_left
209
+ image_dict[config['camera_names'][2]] = img_right
210
+
211
+
212
+ obs['images'] = image_dict
213
+
214
+ if args.use_depth_image:
215
+ image_depth_dict = dict()
216
+ image_depth_dict[config['camera_names'][0]] = img_front_depth
217
+ image_depth_dict[config['camera_names'][1]] = img_left_depth
218
+ image_depth_dict[config['camera_names'][2]] = img_right_depth
219
+ obs['images_depth'] = image_depth_dict
220
+
221
+ obs['qpos'] = np.concatenate(
222
+ (np.array(puppet_arm_left.position), np.array(puppet_arm_right.position)), axis=0)
223
+ obs['qvel'] = np.concatenate(
224
+ (np.array(puppet_arm_left.velocity), np.array(puppet_arm_right.velocity)), axis=0)
225
+ obs['effort'] = np.concatenate(
226
+ (np.array(puppet_arm_left.effort), np.array(puppet_arm_right.effort)), axis=0)
227
+ if args.use_robot_base:
228
+ obs['base_vel'] = [robot_base.twist.twist.linear.x, robot_base.twist.twist.angular.z]
229
+ obs['qpos'] = np.concatenate((obs['qpos'], obs['base_vel']), axis=0)
230
+ else:
231
+ obs['base_vel'] = [0.0, 0.0]
232
+ # qpos_numpy = np.array(obs['qpos'])
233
+
234
+ # 归一化处理qpos 并转到cuda
235
+ qpos = pre_pos_process(obs['qpos'])
236
+ qpos = torch.from_numpy(qpos).float().cuda().unsqueeze(0)
237
+ # 当前图像curr_image获取图像
238
+ curr_image = get_image(obs, config['camera_names'])
239
+ curr_depth_image = None
240
+ if args.use_depth_image:
241
+ curr_depth_image = get_depth_image(obs, config['camera_names'])
242
+ start_time = time.time()
243
+ all_actions = policy(curr_image, curr_depth_image, qpos)
244
+ end_time = time.time()
245
+ print("model cost time: ", end_time -start_time)
246
+ inference_lock.acquire()
247
+ inference_actions = all_actions.cpu().detach().numpy()
248
+ if pre_action is None:
249
+ pre_action = obs['qpos']
250
+ # print("obs['qpos']:", obs['qpos'][7:])
251
+ if args.use_actions_interpolation:
252
+ inference_actions = actions_interpolation(args, pre_action, inference_actions, stats)
253
+ inference_timestep = t
254
+ inference_lock.release()
255
+ break
256
+
257
+
258
+ def model_inference(args, config, ros_operator, save_episode=True):
259
+ global inference_lock
260
+ global inference_actions
261
+ global inference_timestep
262
+ global inference_thread
263
+ set_seed(1000)
264
+
265
+ # 1 创建模型数据 继承nn.Module
266
+ policy = make_policy(config['policy_class'], config['policy_config'])
267
+ # print("model structure\n", policy.model)
268
+
269
+ # 2 加载模型权重
270
+ ckpt_path = os.path.join(config['ckpt_dir'], config['ckpt_name'])
271
+ state_dict = torch.load(ckpt_path)
272
+ new_state_dict = {}
273
+ for key, value in state_dict.items():
274
+ if key in ["model.is_pad_head.weight", "model.is_pad_head.bias"]:
275
+ continue
276
+ if key in ["model.input_proj_next_action.weight", "model.input_proj_next_action.bias"]:
277
+ continue
278
+ new_state_dict[key] = value
279
+ loading_status = policy.deserialize(new_state_dict)
280
+ if not loading_status:
281
+ print("ckpt path not exist")
282
+ return False
283
+
284
+ # 3 模型设置为cuda模式和验证模式
285
+ policy.cuda()
286
+ policy.eval()
287
+
288
+ # 4 加载统计值
289
+ stats_path = os.path.join(config['ckpt_dir'], config['ckpt_stats_name'])
290
+ # 统计的数据 # 加载action_mean, action_std, qpos_mean, qpos_std 14维
291
+ with open(stats_path, 'rb') as f:
292
+ stats = pickle.load(f)
293
+
294
+ # 数据预处理和后处理函数定义
295
+ pre_process = lambda s_qpos: (s_qpos - stats['qpos_mean']) / stats['qpos_std']
296
+ post_process = lambda a: a * stats['qpos_std'] + stats['qpos_mean']
297
+
298
+ max_publish_step = config['episode_len']
299
+ chunk_size = config['policy_config']['chunk_size']
300
+
301
+ # 发布基础的姿态
302
+ left0 = [-0.00133514404296875, 0.00209808349609375, 0.01583099365234375, -0.032616615295410156, -0.00286102294921875, 0.00095367431640625, 3.557830810546875]
303
+ right0 = [-0.00133514404296875, 0.00438690185546875, 0.034523963928222656, -0.053597450256347656, -0.00476837158203125, -0.00209808349609375, 3.557830810546875]
304
+ left1 = [-0.00133514404296875, 0.00209808349609375, 0.01583099365234375, -0.032616615295410156, -0.00286102294921875, 0.00095367431640625, -0.3393220901489258]
305
+ right1 = [-0.00133514404296875, 0.00247955322265625, 0.01583099365234375, -0.032616615295410156, -0.00286102294921875, 0.00095367431640625, -0.3397035598754883]
306
+
307
+ ros_operator.puppet_arm_publish_continuous(left0, right0)
308
+ input("Enter any key to continue :")
309
+ ros_operator.puppet_arm_publish_continuous(left1, right1)
310
+ action = None
311
+ # 推理
312
+ with torch.inference_mode():
313
+ while True and not rospy.is_shutdown():
314
+ # 每个回合的步数
315
+ t = 0
316
+ max_t = 0
317
+ rate = rospy.Rate(args.publish_rate)
318
+ if config['temporal_agg']:
319
+ all_time_actions = np.zeros([max_publish_step, max_publish_step + chunk_size, config['state_dim']])
320
+ while t < max_publish_step and not rospy.is_shutdown():
321
+ # start_time = time.time()
322
+ # query policy
323
+ if config['policy_class'] == "ACT":
324
+ if t >= max_t:
325
+ pre_action = action
326
+ inference_thread = threading.Thread(target=inference_process,
327
+ args=(args, config, ros_operator,
328
+ policy, stats, t, pre_action))
329
+ inference_thread.start()
330
+ inference_thread.join()
331
+ inference_lock.acquire()
332
+ if inference_actions is not None:
333
+ inference_thread = None
334
+ all_actions = inference_actions
335
+ inference_actions = None
336
+ max_t = t + args.pos_lookahead_step
337
+ if config['temporal_agg']:
338
+ all_time_actions[[t], t:t + chunk_size] = all_actions
339
+ inference_lock.release()
340
+ if config['temporal_agg']:
341
+ actions_for_curr_step = all_time_actions[:, t]
342
+ actions_populated = np.all(actions_for_curr_step != 0, axis=1)
343
+ actions_for_curr_step = actions_for_curr_step[actions_populated]
344
+ k = 0.01
345
+ exp_weights = np.exp(-k * np.arange(len(actions_for_curr_step)))
346
+ exp_weights = exp_weights / exp_weights.sum()
347
+ exp_weights = exp_weights[:, np.newaxis]
348
+ raw_action = (actions_for_curr_step * exp_weights).sum(axis=0, keepdims=True)
349
+ else:
350
+ if args.pos_lookahead_step != 0:
351
+ raw_action = all_actions[:, t % args.pos_lookahead_step]
352
+ else:
353
+ raw_action = all_actions[:, t % chunk_size]
354
+ else:
355
+ raise NotImplementedError
356
+ action = post_process(raw_action[0])
357
+ left_action = action[:7] # 取7维度
358
+ right_action = action[7:14]
359
+ ros_operator.puppet_arm_publish(left_action, right_action) # puppet_arm_publish_continuous_thread
360
+ if args.use_robot_base:
361
+ vel_action = action[14:16]
362
+ ros_operator.robot_base_publish(vel_action)
363
+ t += 1
364
+ # end_time = time.time()
365
+ # print("publish: ", t)
366
+ # print("time:", end_time - start_time)
367
+ # print("left_action:", left_action)
368
+ # print("right_action:", right_action)
369
+ rate.sleep()
370
+
371
+
372
+ class RosOperator:
373
+ def __init__(self, args):
374
+ self.robot_base_deque = None
375
+ self.puppet_arm_right_deque = None
376
+ self.puppet_arm_left_deque = None
377
+ self.img_front_deque = None
378
+ self.img_right_deque = None
379
+ self.img_left_deque = None
380
+ self.img_front_depth_deque = None
381
+ self.img_right_depth_deque = None
382
+ self.img_left_depth_deque = None
383
+ self.bridge = None
384
+ self.puppet_arm_left_publisher = None
385
+ self.puppet_arm_right_publisher = None
386
+ self.robot_base_publisher = None
387
+ self.puppet_arm_publish_thread = None
388
+ self.puppet_arm_publish_lock = None
389
+ self.args = args
390
+ self.ctrl_state = False
391
+ self.ctrl_state_lock = threading.Lock()
392
+ self.init()
393
+ self.init_ros()
394
+
395
+ def init(self):
396
+ self.bridge = CvBridge()
397
+ self.img_left_deque = deque()
398
+ self.img_right_deque = deque()
399
+ self.img_front_deque = deque()
400
+ self.img_left_depth_deque = deque()
401
+ self.img_right_depth_deque = deque()
402
+ self.img_front_depth_deque = deque()
403
+ self.puppet_arm_left_deque = deque()
404
+ self.puppet_arm_right_deque = deque()
405
+ self.robot_base_deque = deque()
406
+ self.puppet_arm_publish_lock = threading.Lock()
407
+ self.puppet_arm_publish_lock.acquire()
408
+
409
+ def puppet_arm_publish(self, left, right):
410
+ joint_state_msg = JointState()
411
+ joint_state_msg.header = Header()
412
+ joint_state_msg.header.stamp = rospy.Time.now() # 设置时间戳
413
+ joint_state_msg.name = ['joint0', 'joint1', 'joint2', 'joint3', 'joint4', 'joint5', 'joint6'] # 设置关节名称
414
+ joint_state_msg.position = left
415
+ self.puppet_arm_left_publisher.publish(joint_state_msg)
416
+ joint_state_msg.position = right
417
+ self.puppet_arm_right_publisher.publish(joint_state_msg)
418
+
419
+ def robot_base_publish(self, vel):
420
+ vel_msg = Twist()
421
+ vel_msg.linear.x = vel[0]
422
+ vel_msg.linear.y = 0
423
+ vel_msg.linear.z = 0
424
+ vel_msg.angular.x = 0
425
+ vel_msg.angular.y = 0
426
+ vel_msg.angular.z = vel[1]
427
+ self.robot_base_publisher.publish(vel_msg)
428
+
429
+ def puppet_arm_publish_continuous(self, left, right):
430
+ rate = rospy.Rate(self.args.publish_rate)
431
+ left_arm = None
432
+ right_arm = None
433
+ while True and not rospy.is_shutdown():
434
+ if len(self.puppet_arm_left_deque) != 0:
435
+ left_arm = list(self.puppet_arm_left_deque[-1].position)
436
+ if len(self.puppet_arm_right_deque) != 0:
437
+ right_arm = list(self.puppet_arm_right_deque[-1].position)
438
+ if left_arm is None or right_arm is None:
439
+ rate.sleep()
440
+ continue
441
+ else:
442
+ break
443
+ left_symbol = [1 if left[i] - left_arm[i] > 0 else -1 for i in range(len(left))]
444
+ right_symbol = [1 if right[i] - right_arm[i] > 0 else -1 for i in range(len(right))]
445
+ flag = True
446
+ step = 0
447
+ while flag and not rospy.is_shutdown():
448
+ if self.puppet_arm_publish_lock.acquire(False):
449
+ return
450
+ left_diff = [abs(left[i] - left_arm[i]) for i in range(len(left))]
451
+ right_diff = [abs(right[i] - right_arm[i]) for i in range(len(right))]
452
+ flag = False
453
+ for i in range(len(left)):
454
+ if left_diff[i] < self.args.arm_steps_length[i]:
455
+ left_arm[i] = left[i]
456
+ else:
457
+ left_arm[i] += left_symbol[i] * self.args.arm_steps_length[i]
458
+ flag = True
459
+ for i in range(len(right)):
460
+ if right_diff[i] < self.args.arm_steps_length[i]:
461
+ right_arm[i] = right[i]
462
+ else:
463
+ right_arm[i] += right_symbol[i] * self.args.arm_steps_length[i]
464
+ flag = True
465
+ joint_state_msg = JointState()
466
+ joint_state_msg.header = Header()
467
+ joint_state_msg.header.stamp = rospy.Time.now() # 设置时间戳
468
+ joint_state_msg.name = ['joint0', 'joint1', 'joint2', 'joint3', 'joint4', 'joint5', 'joint6'] # 设置关节名称
469
+ joint_state_msg.position = left_arm
470
+ self.puppet_arm_left_publisher.publish(joint_state_msg)
471
+ joint_state_msg.position = right_arm
472
+ self.puppet_arm_right_publisher.publish(joint_state_msg)
473
+ step += 1
474
+ print("puppet_arm_publish_continuous:", step)
475
+ rate.sleep()
476
+
477
+ def puppet_arm_publish_linear(self, left, right):
478
+ num_step = 100
479
+ rate = rospy.Rate(200)
480
+
481
+ left_arm = None
482
+ right_arm = None
483
+
484
+ while True and not rospy.is_shutdown():
485
+ if len(self.puppet_arm_left_deque) != 0:
486
+ left_arm = list(self.puppet_arm_left_deque[-1].position)
487
+ if len(self.puppet_arm_right_deque) != 0:
488
+ right_arm = list(self.puppet_arm_right_deque[-1].position)
489
+ if left_arm is None or right_arm is None:
490
+ rate.sleep()
491
+ continue
492
+ else:
493
+ break
494
+
495
+ traj_left_list = np.linspace(left_arm, left, num_step)
496
+ traj_right_list = np.linspace(right_arm, right, num_step)
497
+
498
+ for i in range(len(traj_left_list)):
499
+ traj_left = traj_left_list[i]
500
+ traj_right = traj_right_list[i]
501
+ traj_left[-1] = left[-1]
502
+ traj_right[-1] = right[-1]
503
+ joint_state_msg = JointState()
504
+ joint_state_msg.header = Header()
505
+ joint_state_msg.header.stamp = rospy.Time.now() # 设置时间戳
506
+ joint_state_msg.name = ['joint0', 'joint1', 'joint2', 'joint3', 'joint4', 'joint5', 'joint6'] # 设置关节名称
507
+ joint_state_msg.position = traj_left
508
+ self.puppet_arm_left_publisher.publish(joint_state_msg)
509
+ joint_state_msg.position = traj_right
510
+ self.puppet_arm_right_publisher.publish(joint_state_msg)
511
+ rate.sleep()
512
+
513
+ def puppet_arm_publish_continuous_thread(self, left, right):
514
+ if self.puppet_arm_publish_thread is not None:
515
+ self.puppet_arm_publish_lock.release()
516
+ self.puppet_arm_publish_thread.join()
517
+ self.puppet_arm_publish_lock.acquire(False)
518
+ self.puppet_arm_publish_thread = None
519
+ self.puppet_arm_publish_thread = threading.Thread(target=self.puppet_arm_publish_continuous, args=(left, right))
520
+ self.puppet_arm_publish_thread.start()
521
+
522
+ def get_frame(self):
523
+ if len(self.img_left_deque) == 0 or len(self.img_right_deque) == 0 or len(self.img_front_deque) == 0 or \
524
+ (self.args.use_depth_image and (len(self.img_left_depth_deque) == 0 or len(self.img_right_depth_deque) == 0 or len(self.img_front_depth_deque) == 0)):
525
+ return False
526
+ if self.args.use_depth_image:
527
+ frame_time = min([self.img_left_deque[-1].header.stamp.to_sec(), self.img_right_deque[-1].header.stamp.to_sec(), self.img_front_deque[-1].header.stamp.to_sec(),
528
+ self.img_left_depth_deque[-1].header.stamp.to_sec(), self.img_right_depth_deque[-1].header.stamp.to_sec(), self.img_front_depth_deque[-1].header.stamp.to_sec()])
529
+ else:
530
+ frame_time = min([self.img_left_deque[-1].header.stamp.to_sec(), self.img_right_deque[-1].header.stamp.to_sec(), self.img_front_deque[-1].header.stamp.to_sec()])
531
+
532
+ if len(self.img_left_deque) == 0 or self.img_left_deque[-1].header.stamp.to_sec() < frame_time:
533
+ return False
534
+ if len(self.img_right_deque) == 0 or self.img_right_deque[-1].header.stamp.to_sec() < frame_time:
535
+ return False
536
+ if len(self.img_front_deque) == 0 or self.img_front_deque[-1].header.stamp.to_sec() < frame_time:
537
+ return False
538
+ if len(self.puppet_arm_left_deque) == 0 or self.puppet_arm_left_deque[-1].header.stamp.to_sec() < frame_time:
539
+ return False
540
+ if len(self.puppet_arm_right_deque) == 0 or self.puppet_arm_right_deque[-1].header.stamp.to_sec() < frame_time:
541
+ return False
542
+ if self.args.use_depth_image and (len(self.img_left_depth_deque) == 0 or self.img_left_depth_deque[-1].header.stamp.to_sec() < frame_time):
543
+ return False
544
+ if self.args.use_depth_image and (len(self.img_right_depth_deque) == 0 or self.img_right_depth_deque[-1].header.stamp.to_sec() < frame_time):
545
+ return False
546
+ if self.args.use_depth_image and (len(self.img_front_depth_deque) == 0 or self.img_front_depth_deque[-1].header.stamp.to_sec() < frame_time):
547
+ return False
548
+ if self.args.use_robot_base and (len(self.robot_base_deque) == 0 or self.robot_base_deque[-1].header.stamp.to_sec() < frame_time):
549
+ return False
550
+
551
+ while self.img_left_deque[0].header.stamp.to_sec() < frame_time:
552
+ self.img_left_deque.popleft()
553
+ img_left = self.bridge.imgmsg_to_cv2(self.img_left_deque.popleft(), 'passthrough')
554
+
555
+ while self.img_right_deque[0].header.stamp.to_sec() < frame_time:
556
+ self.img_right_deque.popleft()
557
+ img_right = self.bridge.imgmsg_to_cv2(self.img_right_deque.popleft(), 'passthrough')
558
+
559
+ while self.img_front_deque[0].header.stamp.to_sec() < frame_time:
560
+ self.img_front_deque.popleft()
561
+ img_front = self.bridge.imgmsg_to_cv2(self.img_front_deque.popleft(), 'passthrough')
562
+
563
+ while self.puppet_arm_left_deque[0].header.stamp.to_sec() < frame_time:
564
+ self.puppet_arm_left_deque.popleft()
565
+ puppet_arm_left = self.puppet_arm_left_deque.popleft()
566
+
567
+ while self.puppet_arm_right_deque[0].header.stamp.to_sec() < frame_time:
568
+ self.puppet_arm_right_deque.popleft()
569
+ puppet_arm_right = self.puppet_arm_right_deque.popleft()
570
+
571
+ img_left_depth = None
572
+ if self.args.use_depth_image:
573
+ while self.img_left_depth_deque[0].header.stamp.to_sec() < frame_time:
574
+ self.img_left_depth_deque.popleft()
575
+ img_left_depth = self.bridge.imgmsg_to_cv2(self.img_left_depth_deque.popleft(), 'passthrough')
576
+
577
+ img_right_depth = None
578
+ if self.args.use_depth_image:
579
+ while self.img_right_depth_deque[0].header.stamp.to_sec() < frame_time:
580
+ self.img_right_depth_deque.popleft()
581
+ img_right_depth = self.bridge.imgmsg_to_cv2(self.img_right_depth_deque.popleft(), 'passthrough')
582
+
583
+ img_front_depth = None
584
+ if self.args.use_depth_image:
585
+ while self.img_front_depth_deque[0].header.stamp.to_sec() < frame_time:
586
+ self.img_front_depth_deque.popleft()
587
+ img_front_depth = self.bridge.imgmsg_to_cv2(self.img_front_depth_deque.popleft(), 'passthrough')
588
+
589
+ robot_base = None
590
+ if self.args.use_robot_base:
591
+ while self.robot_base_deque[0].header.stamp.to_sec() < frame_time:
592
+ self.robot_base_deque.popleft()
593
+ robot_base = self.robot_base_deque.popleft()
594
+
595
+ return (img_front, img_left, img_right, img_front_depth, img_left_depth, img_right_depth,
596
+ puppet_arm_left, puppet_arm_right, robot_base)
597
+
598
+ def img_left_callback(self, msg):
599
+ if len(self.img_left_deque) >= 2000:
600
+ self.img_left_deque.popleft()
601
+ self.img_left_deque.append(msg)
602
+
603
+ def img_right_callback(self, msg):
604
+ if len(self.img_right_deque) >= 2000:
605
+ self.img_right_deque.popleft()
606
+ self.img_right_deque.append(msg)
607
+
608
+ def img_front_callback(self, msg):
609
+ if len(self.img_front_deque) >= 2000:
610
+ self.img_front_deque.popleft()
611
+ self.img_front_deque.append(msg)
612
+
613
+ def img_left_depth_callback(self, msg):
614
+ if len(self.img_left_depth_deque) >= 2000:
615
+ self.img_left_depth_deque.popleft()
616
+ self.img_left_depth_deque.append(msg)
617
+
618
+ def img_right_depth_callback(self, msg):
619
+ if len(self.img_right_depth_deque) >= 2000:
620
+ self.img_right_depth_deque.popleft()
621
+ self.img_right_depth_deque.append(msg)
622
+
623
+ def img_front_depth_callback(self, msg):
624
+ if len(self.img_front_depth_deque) >= 2000:
625
+ self.img_front_depth_deque.popleft()
626
+ self.img_front_depth_deque.append(msg)
627
+
628
+ def puppet_arm_left_callback(self, msg):
629
+ if len(self.puppet_arm_left_deque) >= 2000:
630
+ self.puppet_arm_left_deque.popleft()
631
+ self.puppet_arm_left_deque.append(msg)
632
+
633
+ def puppet_arm_right_callback(self, msg):
634
+ if len(self.puppet_arm_right_deque) >= 2000:
635
+ self.puppet_arm_right_deque.popleft()
636
+ self.puppet_arm_right_deque.append(msg)
637
+
638
+ def robot_base_callback(self, msg):
639
+ if len(self.robot_base_deque) >= 2000:
640
+ self.robot_base_deque.popleft()
641
+ self.robot_base_deque.append(msg)
642
+
643
+ def ctrl_callback(self, msg):
644
+ self.ctrl_state_lock.acquire()
645
+ self.ctrl_state = msg.data
646
+ self.ctrl_state_lock.release()
647
+
648
+ def get_ctrl_state(self):
649
+ self.ctrl_state_lock.acquire()
650
+ state = self.ctrl_state
651
+ self.ctrl_state_lock.release()
652
+ return state
653
+
654
+ def init_ros(self):
655
+ rospy.init_node('joint_state_publisher', anonymous=True)
656
+ rospy.Subscriber(self.args.img_left_topic, Image, self.img_left_callback, queue_size=1000, tcp_nodelay=True)
657
+ rospy.Subscriber(self.args.img_right_topic, Image, self.img_right_callback, queue_size=1000, tcp_nodelay=True)
658
+ rospy.Subscriber(self.args.img_front_topic, Image, self.img_front_callback, queue_size=1000, tcp_nodelay=True)
659
+ if self.args.use_depth_image:
660
+ rospy.Subscriber(self.args.img_left_depth_topic, Image, self.img_left_depth_callback, queue_size=1000, tcp_nodelay=True)
661
+ rospy.Subscriber(self.args.img_right_depth_topic, Image, self.img_right_depth_callback, queue_size=1000, tcp_nodelay=True)
662
+ rospy.Subscriber(self.args.img_front_depth_topic, Image, self.img_front_depth_callback, queue_size=1000, tcp_nodelay=True)
663
+ rospy.Subscriber(self.args.puppet_arm_left_topic, JointState, self.puppet_arm_left_callback, queue_size=1000, tcp_nodelay=True)
664
+ rospy.Subscriber(self.args.puppet_arm_right_topic, JointState, self.puppet_arm_right_callback, queue_size=1000, tcp_nodelay=True)
665
+ rospy.Subscriber(self.args.robot_base_topic, Odometry, self.robot_base_callback, queue_size=1000, tcp_nodelay=True)
666
+ self.puppet_arm_left_publisher = rospy.Publisher(self.args.puppet_arm_left_cmd_topic, JointState, queue_size=10)
667
+ self.puppet_arm_right_publisher = rospy.Publisher(self.args.puppet_arm_right_cmd_topic, JointState, queue_size=10)
668
+ self.robot_base_publisher = rospy.Publisher(self.args.robot_base_cmd_topic, Twist, queue_size=10)
669
+
670
+
671
+ def get_arguments():
672
+ parser = argparse.ArgumentParser()
673
+ parser.add_argument('--ckpt_dir', action='store', type=str, help='ckpt_dir', required=True)
674
+ parser.add_argument('--task_name', action='store', type=str, help='task_name', default='aloha_mobile_dummy', required=False)
675
+ parser.add_argument('--max_publish_step', action='store', type=int, help='max_publish_step', default=10000, required=False)
676
+ parser.add_argument('--ckpt_name', action='store', type=str, help='ckpt_name', default='policy_best.ckpt', required=False)
677
+ parser.add_argument('--ckpt_stats_name', action='store', type=str, help='ckpt_stats_name', default='dataset_stats.pkl', required=False)
678
+ parser.add_argument('--policy_class', action='store', type=str, help='policy_class, capitalize', default='ACT', required=False)
679
+ parser.add_argument('--batch_size', action='store', type=int, help='batch_size', default=8, required=False)
680
+ parser.add_argument('--seed', action='store', type=int, help='seed', default=0, required=False)
681
+ parser.add_argument('--num_epochs', action='store', type=int, help='num_epochs', default=2000, required=False)
682
+ parser.add_argument('--lr', action='store', type=float, help='lr', default=1e-5, required=False)
683
+ parser.add_argument('--weight_decay', type=float, help='weight_decay', default=1e-4, required=False)
684
+ parser.add_argument('--dilation', action='store_true',
685
+ help="If true, we replace stride with dilation in the last convolutional block (DC5)", required=False)
686
+ parser.add_argument('--position_embedding', default='sine', type=str, choices=('sine', 'learned'),
687
+ help="Type of positional embedding to use on top of the image features", required=False)
688
+ parser.add_argument('--masks', action='store_true',
689
+ help="Train segmentation head if the flag is provided")
690
+ parser.add_argument('--kl_weight', action='store', type=int, help='KL Weight', default=10, required=False)
691
+ parser.add_argument('--hidden_dim', action='store', type=int, help='hidden_dim', default=512, required=False)
692
+ parser.add_argument('--dim_feedforward', action='store', type=int, help='dim_feedforward', default=3200, required=False)
693
+ parser.add_argument('--temporal_agg', action='store', type=bool, help='temporal_agg', default=True, required=False)
694
+
695
+ parser.add_argument('--state_dim', action='store', type=int, help='state_dim', default=14, required=False)
696
+ parser.add_argument('--lr_backbone', action='store', type=float, help='lr_backbone', default=1e-5, required=False)
697
+ parser.add_argument('--backbone', action='store', type=str, help='backbone', default='resnet18', required=False)
698
+ parser.add_argument('--loss_function', action='store', type=str, help='loss_function l1 l2 l1+l2', default='l1', required=False)
699
+ parser.add_argument('--enc_layers', action='store', type=int, help='enc_layers', default=4, required=False)
700
+ parser.add_argument('--dec_layers', action='store', type=int, help='dec_layers', default=7, required=False)
701
+ parser.add_argument('--nheads', action='store', type=int, help='nheads', default=8, required=False)
702
+ parser.add_argument('--dropout', default=0.1, type=float, help="Dropout applied in the transformer", required=False)
703
+ parser.add_argument('--pre_norm', action='store_true', required=False)
704
+
705
+ parser.add_argument('--img_front_topic', action='store', type=str, help='img_front_topic',
706
+ default='/camera_f/color/image_raw', required=False)
707
+ parser.add_argument('--img_left_topic', action='store', type=str, help='img_left_topic',
708
+ default='/camera_l/color/image_raw', required=False)
709
+ parser.add_argument('--img_right_topic', action='store', type=str, help='img_right_topic',
710
+ default='/camera_r/color/image_raw', required=False)
711
+
712
+ parser.add_argument('--img_front_depth_topic', action='store', type=str, help='img_front_depth_topic',
713
+ default='/camera_f/depth/image_raw', required=False)
714
+ parser.add_argument('--img_left_depth_topic', action='store', type=str, help='img_left_depth_topic',
715
+ default='/camera_l/depth/image_raw', required=False)
716
+ parser.add_argument('--img_right_depth_topic', action='store', type=str, help='img_right_depth_topic',
717
+ default='/camera_r/depth/image_raw', required=False)
718
+
719
+ parser.add_argument('--puppet_arm_left_cmd_topic', action='store', type=str, help='puppet_arm_left_cmd_topic',
720
+ default='/master/joint_left', required=False)
721
+ parser.add_argument('--puppet_arm_right_cmd_topic', action='store', type=str, help='puppet_arm_right_cmd_topic',
722
+ default='/master/joint_right', required=False)
723
+ parser.add_argument('--puppet_arm_left_topic', action='store', type=str, help='puppet_arm_left_topic',
724
+ default='/puppet/joint_left', required=False)
725
+ parser.add_argument('--puppet_arm_right_topic', action='store', type=str, help='puppet_arm_right_topic',
726
+ default='/puppet/joint_right', required=False)
727
+
728
+ parser.add_argument('--robot_base_topic', action='store', type=str, help='robot_base_topic',
729
+ default='/odom_raw', required=False)
730
+ parser.add_argument('--robot_base_cmd_topic', action='store', type=str, help='robot_base_topic',
731
+ default='/cmd_vel', required=False)
732
+ parser.add_argument('--use_robot_base', action='store', type=bool, help='use_robot_base',
733
+ default=False, required=False)
734
+ parser.add_argument('--publish_rate', action='store', type=int, help='publish_rate',
735
+ default=40, required=False)
736
+ parser.add_argument('--pos_lookahead_step', action='store', type=int, help='pos_lookahead_step',
737
+ default=0, required=False)
738
+ parser.add_argument('--chunk_size', action='store', type=int, help='chunk_size',
739
+ default=32, required=False)
740
+ parser.add_argument('--arm_steps_length', action='store', type=float, help='arm_steps_length',
741
+ default=[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.2], required=False)
742
+
743
+ parser.add_argument('--use_actions_interpolation', action='store', type=bool, help='use_actions_interpolation',
744
+ default=False, required=False)
745
+ parser.add_argument('--use_depth_image', action='store', type=bool, help='use_depth_image',
746
+ default=False, required=False)
747
+
748
+ # for Diffusion
749
+ parser.add_argument('--observation_horizon', action='store', type=int, help='observation_horizon', default=1, required=False)
750
+ parser.add_argument('--action_horizon', action='store', type=int, help='action_horizon', default=8, required=False)
751
+ parser.add_argument('--num_inference_timesteps', action='store', type=int, help='num_inference_timesteps', default=10, required=False)
752
+ parser.add_argument('--ema_power', action='store', type=int, help='ema_power', default=0.75, required=False)
753
+ args = parser.parse_args()
754
+ return args
755
+
756
+
757
+ def main():
758
+ args = get_arguments()
759
+ ros_operator = RosOperator(args)
760
+ config = get_model_config(args)
761
+ model_inference(args, config, ros_operator, save_episode=True)
762
+
763
+
764
+ if __name__ == '__main__':
765
+ main()
766
+ # python act/inference.py --ckpt_dir ~/train0314/
aloha-devel/act/policy.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+ from torch.nn import functional as F
3
+ import torchvision.transforms as transforms
4
+ from detr.main import build_ACT_model_and_optimizer, build_CNNMLP_model_and_optimizer, build_diffusion_model_and_optimizer
5
+
6
+ import IPython
7
+ e = IPython.embed
8
+
9
+
10
+ class DiffusionPolicy(nn.Module):
11
+ def __init__(self, args_override):
12
+ super().__init__()
13
+ model, optimizer = build_diffusion_model_and_optimizer(args_override)
14
+ self.model = model
15
+ self.optimizer = optimizer
16
+
17
+ def configure_optimizers(self):
18
+ return self.optimizer
19
+
20
+ def __call__(self, image, depth_image, robot_state, actions=None, action_is_pad=None):
21
+ B = robot_state.shape[0]
22
+ if actions is not None:
23
+ noise, noise_pred = self.model(image, depth_image, robot_state, actions, action_is_pad)
24
+ # L2 loss
25
+ all_l2 = F.mse_loss(noise_pred, noise, reduction='none')
26
+ loss = (all_l2 * ~action_is_pad.unsqueeze(-1)).mean()
27
+
28
+ loss_dict = {}
29
+ loss_dict['l2_loss'] = loss
30
+ loss_dict['loss'] = loss
31
+ return loss_dict, (noise, noise_pred)
32
+ else: # inference time
33
+ return self.model(image, depth_image, robot_state, actions, action_is_pad)
34
+
35
+ def serialize(self):
36
+ return self.model.serialize()
37
+
38
+ def deserialize(self, model_dict):
39
+ return self.model.deserialize(model_dict)
40
+
41
+
42
+ class ACTPolicy(nn.Module):
43
+ def __init__(self, args_override):
44
+ super().__init__()
45
+ model, optimizer = build_ACT_model_and_optimizer(args_override)
46
+
47
+ self.model = model # CVAE decoder
48
+ self.optimizer = optimizer
49
+ self.kl_weight = args_override['kl_weight']
50
+ self.loss_function = args_override['loss_function']
51
+
52
+ print(f'KL Weight {self.kl_weight}')
53
+
54
+ def __call__(self, image, depth_image, robot_state, actions=None, action_is_pad=None):
55
+
56
+ normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],
57
+ std=[0.229, 0.224, 0.225])
58
+ depth_normalize = transforms.Normalize(mean=[0.5], std=[0.5])
59
+
60
+ image = normalize(image) # 图像归一化
61
+ if depth_image is not None:
62
+ depth_image = depth_normalize(depth_image)
63
+
64
+ # 总共max个步 只取前model.num_queries个
65
+ if actions is not None: # training time
66
+ actions = actions[:, :self.model.num_queries]
67
+ action_is_pad = action_is_pad[:, :self.model.num_queries]
68
+
69
+ a_hat, (mu, logvar) = self.model(image, depth_image, robot_state, actions, action_is_pad)
70
+
71
+ loss_dict = dict()
72
+ if self.loss_function == 'l1':
73
+ all_l1 = F.l1_loss(actions, a_hat, reduction='none')
74
+ elif self.loss_function == 'l2':
75
+ all_l1 = F.mse_loss(actions, a_hat, reduction='none')
76
+ else:
77
+ all_l1 = F.smooth_l1_loss(actions, a_hat, reduction='none')
78
+
79
+ l1 = (all_l1 * ~action_is_pad.unsqueeze(-1)).mean()
80
+
81
+ loss_dict['l1'] = l1
82
+ if self.kl_weight != 0:
83
+ total_kld, dim_wise_kld, mean_kld = kl_divergence(mu, logvar)
84
+ loss_dict['kl'] = total_kld[0]
85
+ loss_dict['loss'] = loss_dict['l1'] + loss_dict['kl'] * self.kl_weight
86
+ else:
87
+ loss_dict['loss'] = loss_dict['l1']
88
+
89
+ return loss_dict, a_hat
90
+ else: # inference time
91
+ a_hat, (_, _) = self.model(image, depth_image, robot_state) # no action, sample from prior
92
+ return a_hat
93
+
94
+ def configure_optimizers(self):
95
+ return self.optimizer
96
+
97
+ def serialize(self):
98
+ return self.state_dict()
99
+
100
+ def deserialize(self, model_dict):
101
+ return self.load_state_dict(model_dict)
102
+
103
+
104
+ class CNNMLPPolicy(nn.Module):
105
+ def __init__(self, args_override):
106
+ super().__init__()
107
+ model, optimizer = build_CNNMLP_model_and_optimizer(args_override)
108
+ self.model = model # decoder
109
+ self.optimizer = optimizer
110
+ self.loss_function = args_override['loss_function']
111
+
112
+ # 而 __call__ 在对象被调用时执行
113
+ def __call__(self, image, depth_image, robot_state, actions=None,
114
+ action_is_pad=None):
115
+ env_state = None # TODO
116
+
117
+ normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],
118
+ std=[0.229, 0.224, 0.225])
119
+ depth_normalize = transforms.Normalize(mean=[0.5], std=[0.5])
120
+ image = normalize(image) # 图像归一化
121
+ if depth_image is not None:
122
+ depth_image = depth_normalize(depth_image)
123
+ if actions is not None: # training time
124
+ actions = actions[:, 0] # 动作
125
+ a_hat = self.model(image, depth_image, robot_state, actions, action_is_pad)
126
+ # 均方误差
127
+ if self.loss_function == 'l1':
128
+ mse = F.l1_loss(actions, a_hat)
129
+ elif self.loss_function == 'l2':
130
+ mse = F.mse_loss(actions, a_hat)
131
+ else:
132
+ mse = F.smooth_l1_loss(actions, a_hat)
133
+
134
+ loss_dict = dict()
135
+ loss_dict['mse'] = mse
136
+ loss_dict['loss'] = loss_dict['mse']
137
+ return loss_dict, a_hat
138
+
139
+ else: # inference time
140
+ a_hat = self.model(image, depth_image, robot_state) # no action, sample from prior
141
+ return a_hat
142
+
143
+ def configure_optimizers(self):
144
+ return self.optimizer
145
+
146
+ def serialize(self):
147
+ return self.state_dict()
148
+
149
+ def deserialize(self, model_dict):
150
+ return self.load_state_dict(model_dict)
151
+
152
+
153
+ def kl_divergence(mu, logvar):
154
+ batch_size = mu.size(0)
155
+ assert batch_size != 0
156
+ if mu.data.ndimension() == 4:
157
+ mu = mu.view(mu.size(0), mu.size(1))
158
+ if logvar.data.ndimension() == 4:
159
+ logvar = logvar.view(logvar.size(0), logvar.size(1))
160
+
161
+ klds = -0.5 * (1 + logvar - mu.pow(2) - logvar.exp())
162
+ total_kld = klds.sum(1).mean(0, True)
163
+ dimension_wise_kld = klds.mean(0)
164
+ mean_kld = klds.mean(1).mean(0, True)
165
+
166
+ return total_kld, dimension_wise_kld, mean_kld
aloha-devel/act/test_inference.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import argparse
3
+ import os
4
+ from policy import ACTPolicy, CNNMLPPolicy, DiffusionPolicy
5
+ from train import make_policy
6
+
7
+
8
+ def test(args):
9
+ # a. Parse arguments is done outside
10
+ # b. Define TASK_CONFIGS and policy_config
11
+ # (Adapted from train.py)
12
+ TASK_CONFIGS = {
13
+ args.task_name: {
14
+ 'camera_names': ['cam_high', 'cam_left_wrist', 'cam_right_wrist'],
15
+ }
16
+ }
17
+ task_config = TASK_CONFIGS[args.task_name]
18
+ camera_names = task_config['camera_names']
19
+
20
+ if args.policy_class == 'ACT':
21
+ policy_config = {'lr': args.lr,
22
+ 'lr_backbone': args.lr_backbone,
23
+ 'backbone': args.backbone,
24
+ 'masks': args.masks,
25
+ 'weight_decay': args.weight_decay,
26
+ 'dilation': args.dilation,
27
+ 'position_embedding': args.position_embedding,
28
+ 'loss_function': args.loss_function,
29
+ 'chunk_size': args.chunk_size,
30
+ 'camera_names': camera_names,
31
+ 'use_depth_image': args.use_depth_image,
32
+ 'use_robot_base': args.use_robot_base,
33
+ 'kl_weight': args.kl_weight,
34
+ 'hidden_dim': args.hidden_dim,
35
+ 'dim_feedforward': args.dim_feedforward,
36
+ 'enc_layers': args.enc_layers,
37
+ 'dec_layers': args.dec_layers,
38
+ 'nheads': args.nheads,
39
+ 'dropout': args.dropout,
40
+ 'pre_norm': args.pre_norm,
41
+ 'pretrain_backbone_path': args.pretrain_backbone_path,
42
+ }
43
+ elif args.policy_class == 'CNNMLP':
44
+ policy_config = {'lr': args.lr,
45
+ 'lr_backbone': args.lr_backbone,
46
+ 'backbone': args.backbone,
47
+ 'masks': args.masks,
48
+ 'weight_decay': args.weight_decay,
49
+ 'dilation': args.dilation,
50
+ 'position_embedding': args.position_embedding,
51
+ 'loss_function': args.loss_function,
52
+ 'chunk_size': 1,
53
+ 'camera_names': camera_names,
54
+ 'use_depth_image': args.use_depth_image,
55
+ 'use_robot_base': args.use_robot_base,
56
+ 'hidden_dim': args.hidden_dim,
57
+ 'pretrain_backbone_path': args.pretrain_backbone_path,
58
+ }
59
+ elif args.policy_class == 'Diffusion':
60
+ policy_config = {'lr': args.lr,
61
+ 'lr_backbone': args.lr_backbone,
62
+ 'backbone': args.backbone,
63
+ 'masks': args.masks,
64
+ 'weight_decay': args.weight_decay,
65
+ 'dilation': args.dilation,
66
+ 'position_embedding': args.position_embedding,
67
+ 'loss_function': args.loss_function,
68
+ 'chunk_size': args.chunk_size,
69
+ 'camera_names': camera_names,
70
+ 'use_depth_image': args.use_depth_image,
71
+ 'use_robot_base': args.use_robot_base,
72
+ 'observation_horizon': args.observation_horizon,
73
+ 'action_horizon': args.action_horizon,
74
+ 'num_inference_timesteps': args.num_inference_timesteps,
75
+ 'ema_power': args.ema_power,
76
+ 'hidden_dim': args.hidden_dim,
77
+ 'pretrain_backbone_path': args.pretrain_backbone_path,
78
+ }
79
+ else:
80
+ raise NotImplementedError
81
+
82
+ # c. Create the policy
83
+ print(f"Loading checkpoint from: {args.ckpt_path}")
84
+ policy = make_policy(args.policy_class, policy_config, args.ckpt_path)
85
+
86
+ # d. Move policy to GPU
87
+ policy.cuda()
88
+
89
+ # e. Set to eval mode
90
+ policy.eval()
91
+ print("Policy loaded and in eval mode.")
92
+
93
+ # f. Create dummy input tensors
94
+ batch_size = 1
95
+ num_cam = len(camera_names)
96
+ image_data = torch.randn(batch_size, num_cam, 3, 480, 640).cuda()
97
+ qpos_data = torch.randn(batch_size, args.state_dim).cuda()
98
+
99
+ depth_image_data = None
100
+ if args.use_depth_image:
101
+ depth_image_data = torch.randn(batch_size, num_cam, 1, 480, 640).cuda()
102
+
103
+ print("Dummy data created and moved to GPU.")
104
+
105
+ # g. Measure GPU memory before inference
106
+ torch.cuda.reset_peak_memory_stats()
107
+ start_mem = torch.cuda.memory_allocated()
108
+ print(f"Initial memory allocated: {start_mem / 1024**2:.2f} MB")
109
+
110
+ # h. Perform inference
111
+ with torch.no_grad():
112
+ print("Running forward pass...")
113
+ action = policy(image_data, depth_image_data, qpos_data)
114
+ print("Forward pass completed.")
115
+
116
+ # i. Measure GPU memory after inference
117
+ end_mem = torch.cuda.memory_allocated()
118
+ peak_mem = torch.cuda.max_memory_allocated()
119
+
120
+ print(f"Final memory allocated: {end_mem / 1024**2:.2f} MB")
121
+ print(f"Peak memory during inference: {peak_mem / 1024**2:.2f} MB")
122
+ print(f"Memory consumed by forward pass: {(peak_mem - start_mem) / 1024**2:.2f} MB")
123
+ if isinstance(action, tuple):
124
+ print(f"Output action shape: {action[0].shape}")
125
+ else:
126
+ print(f"Output action shape: {action.shape}")
127
+
128
+
129
+ def main():
130
+ parser = argparse.ArgumentParser("Test Inference Memory", parents=[get_inference_args_parser()])
131
+ parser.add_argument('--ckpt_path', action='store', type=str, help='path to checkpoint', required=True)
132
+ args = parser.parse_args()
133
+ test(args)
134
+
135
+ def get_inference_args_parser():
136
+ parser = argparse.ArgumentParser(add_help=False)
137
+ # Remove arguments that are not needed for inference testing
138
+ # and set sensible defaults.
139
+ parser.add_argument('--dataset_dir', action='store', type=str, help='dataset_dir', default='./dataset')
140
+ parser.add_argument('--task_name', action='store', type=str, help='task_name', default='aloha_mobile_dummy')
141
+ parser.add_argument('--policy_class', action='store', type=str, help='policy_class, capitalize, CNNMLP, ACT, Diffusion', default='ACT')
142
+
143
+ # Model parameters
144
+ parser.add_argument('--kl_weight', action='store', type=int, help='KL Weight', default=10)
145
+ parser.add_argument('--chunk_size', action='store', type=int, help='chunk_size', default=32)
146
+ parser.add_argument('--hidden_dim', action='store', type=int, help='hidden_dim', default=512)
147
+ parser.add_argument('--dim_feedforward', action='store', type=int, help='dim_feedforward', default=3200)
148
+ parser.add_argument('--state_dim', action='store', type=int, help='state_dim', default=14)
149
+ parser.add_argument('--lr_backbone', action='store', type=float, help='lr_backbone', default=1e-5)
150
+ parser.add_argument('--backbone', action='store', type=str, help='backbone', default='resnet18')
151
+ parser.add_argument('--loss_function', action='store', type=str, help='loss_function l1 l2 l1+l2', default='l1')
152
+ parser.add_argument('--enc_layers', action='store', type=int, help='enc_layers', default=4)
153
+ parser.add_argument('--dec_layers', action='store', type=int, help='dec_layers', default=7)
154
+ parser.add_argument('--nheads', action='store', type=int, help='nheads', default=8)
155
+ parser.add_argument('--dropout', default=0.1, type=float, help="Dropout applied in the transformer")
156
+ parser.add_argument('--pre_norm', action='store_true')
157
+ parser.add_argument('--lr', action='store', type=float, help='lr', default=1e-5)
158
+ parser.add_argument('--weight_decay', type=float, help='weight_decay', default=1e-4)
159
+ parser.add_argument('--dilation', action='store_true',
160
+ help="If true, we replace stride with dilation in the last convolutional block (DC5)")
161
+ parser.add_argument('--position_embedding', default='sine', type=str, choices=('sine', 'learned'),
162
+ help="Type of positional embedding to use on top of the image features")
163
+ parser.add_argument('--masks', action='store_true',
164
+ help="Train segmentation head if the flag is provided")
165
+ parser.add_argument('--pretrain_backbone_path', action='store', type=str, help='pretrain_backbone_path', default='')
166
+
167
+ # for Diffusion
168
+ parser.add_argument('--observation_horizon', action='store', type=int, help='observation_horizon', default=1)
169
+ parser.add_argument('--action_horizon', action='store', type=int, help='action_horizon', default=8)
170
+ parser.add_argument('--num_inference_timesteps', action='store', type=int, help='num_inference_timesteps', default=10)
171
+ parser.add_argument('--ema_power', action='store', type=float, help='ema_power', default=0.75) # Changed type to float
172
+
173
+ parser.add_argument('--use_robot_base', action='store', type=bool, help='use_robot_base', default=False)
174
+ parser.add_argument('--use_depth_image', action='store', type=bool, help='use_depth_image', default=False)
175
+
176
+ return parser
177
+
178
+
179
+ if __name__ == '__main__':
180
+ main()
aloha-devel/act/train.py ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ import os
4
+ import pickle
5
+ import argparse
6
+ import matplotlib.pyplot as plt
7
+ from copy import deepcopy
8
+ from tqdm import tqdm
9
+
10
+ from utils import load_data
11
+ from utils import compute_dict_mean, set_seed, detach_dict
12
+ from policy import ACTPolicy, CNNMLPPolicy, DiffusionPolicy
13
+
14
+ import sys
15
+ sys.path.append("./")
16
+
17
+
18
+ def train(args):
19
+ set_seed(1)
20
+
21
+ DATA_DIR = os.path.expanduser(args.dataset_dir)
22
+
23
+ TASK_CONFIGS = {
24
+ args.task_name: {
25
+ 'dataset_dir': os.path.join(DATA_DIR, args.task_name),
26
+ 'camera_names': ['cam_high', 'cam_left_wrist', 'cam_right_wrist'],
27
+ 'num_episodes': args.num_episodes
28
+ }
29
+ }
30
+
31
+ task_config = TASK_CONFIGS[args.task_name]
32
+
33
+ dataset_dir = task_config['dataset_dir']
34
+ num_episodes = task_config['num_episodes']
35
+ camera_names = task_config['camera_names']
36
+
37
+ # fixed parameters
38
+ if args.policy_class == 'ACT':
39
+ policy_config = {'lr': args.lr,
40
+ 'lr_backbone': args.lr_backbone,
41
+ 'backbone': args.backbone,
42
+ 'masks': args.masks,
43
+ 'weight_decay': args.weight_decay,
44
+ 'dilation': args.dilation,
45
+ 'position_embedding': args.position_embedding,
46
+ 'loss_function': args.loss_function,
47
+ 'chunk_size': args.chunk_size, # chunk_size
48
+ 'camera_names': camera_names,
49
+ 'use_depth_image': args.use_depth_image,
50
+ 'use_robot_base': args.use_robot_base,
51
+ 'kl_weight': args.kl_weight, # kl
52
+ 'hidden_dim': args.hidden_dim, # Hidden dim
53
+ 'dim_feedforward': args.dim_feedforward,
54
+ 'enc_layers': args.enc_layers,
55
+ 'dec_layers': args.dec_layers,
56
+ 'nheads': args.nheads,
57
+ 'dropout': args.dropout,
58
+ 'pre_norm': args.pre_norm,
59
+ 'pretrain_backbone_path': args.pretrain_backbone_path,
60
+ }
61
+ elif args.policy_class == 'CNNMLP':
62
+ policy_config = {'lr': args.lr,
63
+ 'lr_backbone': args.lr_backbone,
64
+ 'backbone': args.backbone,
65
+ 'masks': args.masks,
66
+ 'weight_decay': args.weight_decay,
67
+ 'dilation': args.dilation,
68
+ 'position_embedding': args.position_embedding,
69
+ 'loss_function': args.loss_function,
70
+ 'chunk_size': 1, # 查询
71
+ 'camera_names': camera_names,
72
+ 'use_depth_image': args.use_depth_image,
73
+ 'use_robot_base': args.use_robot_base,
74
+ 'hidden_dim': args.hidden_dim,
75
+ 'pretrain_backbone_path': args.pretrain_backbone_path,
76
+ }
77
+ elif args.policy_class == 'Diffusion':
78
+ policy_config = {'lr': args.lr,
79
+ 'lr_backbone': args.lr_backbone,
80
+ 'backbone': args.backbone,
81
+ 'masks': args.masks,
82
+ 'weight_decay': args.weight_decay,
83
+ 'dilation': args.dilation,
84
+ 'position_embedding': args.position_embedding,
85
+ 'loss_function': args.loss_function,
86
+ 'chunk_size': args.chunk_size, # 查询
87
+ 'camera_names': camera_names,
88
+ 'use_depth_image': args.use_depth_image,
89
+ 'use_robot_base': args.use_robot_base,
90
+ 'observation_horizon': args.observation_horizon,
91
+ 'action_horizon': args.action_horizon,
92
+ 'num_inference_timesteps': args.num_inference_timesteps,
93
+ 'ema_power': args.ema_power,
94
+ 'hidden_dim': args.hidden_dim,
95
+ 'pretrain_backbone_path': args.pretrain_backbone_path,
96
+ }
97
+ else:
98
+ raise NotImplementedError
99
+
100
+ config = {
101
+ 'num_epochs': args.num_epochs,
102
+ 'ckpt_dir': args.ckpt_dir,
103
+ 'policy_class': args.policy_class,
104
+ 'policy_config': policy_config,
105
+ 'seed': args.seed,
106
+ 'pretrain_ckpt_dir': args.pretrain_ckpt,
107
+ 'ckpt_save_interval': args.ckpt_save_interval,
108
+ 'plot_interval': args.plot_interval,
109
+ 'lr_decay_start_epoch': args.lr_decay_start_epoch,
110
+ 'min_lr': args.min_lr,
111
+ }
112
+
113
+ # data Preprocess
114
+ train_dataloader, val_dataloader, stats, _ = load_data(dataset_dir, num_episodes, args.arm_delay_time,
115
+ args.use_depth_image, args.use_robot_base, camera_names,
116
+ args.batch_size, args.batch_size)
117
+
118
+ # save dataset stats
119
+ if not os.path.isdir(args.ckpt_dir):
120
+ os.makedirs(args.ckpt_dir)
121
+ stats_path = os.path.join(args.ckpt_dir, args.ckpt_stats_name)
122
+ with open(stats_path, 'wb') as f:
123
+ pickle.dump(stats, f)
124
+
125
+ best_ckpt_info = train_process(train_dataloader, val_dataloader, config, stats)
126
+ best_epoch, min_val_loss, best_state_dict = best_ckpt_info
127
+
128
+ # save best checkpoint
129
+ ckpt_path = os.path.join(args.ckpt_dir, args.ckpt_name)
130
+ torch.save(best_state_dict, ckpt_path)
131
+ print(f'Best ckpt, val loss {min_val_loss:.6f} @ epoch{best_epoch}')
132
+
133
+
134
+ def make_policy(policy_class, policy_config, pretrain_ckpt_dir):
135
+ if policy_class == 'ACT':
136
+ policy = ACTPolicy(policy_config)
137
+ if len(pretrain_ckpt_dir) != 0:
138
+ state_dict = torch.load(pretrain_ckpt_dir)
139
+
140
+ loading_status = policy.deserialize(state_dict)
141
+ if not loading_status:
142
+ print("ckpt path not exist")
143
+ elif policy_class == 'CNNMLP':
144
+ policy = CNNMLPPolicy(policy_config)
145
+ if len(pretrain_ckpt_dir) != 0:
146
+ loading_status = policy.deserialize(torch.load(pretrain_ckpt_dir))
147
+ if not loading_status:
148
+ print("ckpt path not exist")
149
+ elif policy_class == 'Diffusion':
150
+ policy = DiffusionPolicy(policy_config)
151
+ if len(pretrain_ckpt_dir) != 0:
152
+ loading_status = policy.deserialize(torch.load(pretrain_ckpt_dir))
153
+ if not loading_status:
154
+ print("ckpt path not exist")
155
+ else:
156
+ raise NotImplementedError
157
+ return policy
158
+
159
+
160
+ def make_optimizer(policy_class, policy):
161
+ if policy_class == 'ACT':
162
+ optimizer = policy.configure_optimizers()
163
+ elif policy_class == 'CNNMLP':
164
+ optimizer = policy.configure_optimizers()
165
+ elif policy_class == 'Diffusion':
166
+ optimizer = policy.configure_optimizers()
167
+ else:
168
+ raise NotImplementedError
169
+ return optimizer
170
+
171
+
172
+ def forward_pass(policy_config, data, policy):
173
+ image_data, image_depth_data, qpos_data, action_data, action_is_pad = data
174
+ (image_data, qpos_data, action_data, action_is_pad) = (image_data.cuda(), qpos_data.cuda(),
175
+ action_data.cuda(), action_is_pad.cuda())
176
+ if policy_config['use_depth_image']:
177
+ image_depth_data = image_depth_data.cuda()
178
+ else:
179
+ image_depth_data = None
180
+ return policy(image_data, image_depth_data, qpos_data, action_data, action_is_pad)
181
+
182
+
183
+ def train_process(train_dataloader, val_dataloader, config, stats):
184
+ post_process = lambda a: a * stats['qpos_std'] + stats['qpos_mean']
185
+ num_epochs = config['num_epochs']
186
+ ckpt_dir = config['ckpt_dir']
187
+ seed = config['seed']
188
+ policy_class = config['policy_class']
189
+ policy_config = config['policy_config']
190
+ pretrain_ckpt_dir = config['pretrain_ckpt_dir']
191
+ ckpt_save_interval = config.get('ckpt_save_interval', 100)
192
+ plot_interval = config.get('plot_interval', 100)
193
+ lr_decay_start_epoch = config.get('lr_decay_start_epoch', num_epochs)
194
+ min_lr = config.get('min_lr', 1e-6)
195
+ set_seed(seed)
196
+
197
+ policy = make_policy(policy_class, policy_config, pretrain_ckpt_dir)
198
+ policy.cuda()
199
+ optimizer = make_optimizer(policy_class, policy)
200
+
201
+ train_history = []
202
+ validation_history = []
203
+ min_val_loss = np.inf
204
+ best_ckpt_info = None
205
+
206
+ original_lr = policy_config['lr']
207
+ original_lr_backbone = policy_config['lr_backbone']
208
+ len_train_loader = len(train_dataloader)
209
+ lr_decay_start_step = lr_decay_start_epoch * len_train_loader
210
+ total_steps = num_epochs * len_train_loader
211
+ total_decay_steps = total_steps - lr_decay_start_step
212
+
213
+ for epoch in tqdm(range(num_epochs)):
214
+ print(f'\nEpoch {epoch}')
215
+ # validation
216
+ with torch.inference_mode():
217
+ policy.eval()
218
+ epoch_dicts = []
219
+ for batch_idx, data in enumerate(val_dataloader):
220
+ forward_dict, result = forward_pass(policy_config, data, policy)
221
+ # print("result:", post_process(result.cpu().detach().numpy())[0, :, 7:])
222
+ epoch_dicts.append(forward_dict)
223
+ epoch_summary = compute_dict_mean(epoch_dicts)
224
+ validation_history.append(epoch_summary)
225
+
226
+ epoch_val_loss = epoch_summary['loss']
227
+ if epoch_val_loss < min_val_loss:
228
+ min_val_loss = epoch_val_loss
229
+ best_ckpt_info = (epoch, min_val_loss, deepcopy(policy.serialize()))
230
+ print(f'Val loss: {epoch_val_loss:.5f}')
231
+ summary_string = ''
232
+ for k, v in epoch_summary.items():
233
+ summary_string += f'{k}: {v.item():.3f} '
234
+ print(summary_string)
235
+
236
+ # training
237
+ policy.train()
238
+ optimizer.zero_grad()
239
+ for batch_idx, data in enumerate(train_dataloader):
240
+ current_step = epoch * len_train_loader + batch_idx
241
+ if current_step >= lr_decay_start_step and total_decay_steps > 0:
242
+ decay_progress = (current_step - lr_decay_start_step) / total_decay_steps
243
+ decay_factor = 1.0 - decay_progress
244
+
245
+ new_lr = max(original_lr * decay_factor, min_lr)
246
+ new_lr_backbone = max(original_lr_backbone * decay_factor, min_lr)
247
+
248
+ optimizer.param_groups[0]['lr'] = new_lr
249
+ optimizer.param_groups[1]['lr'] = new_lr_backbone
250
+
251
+ # debug
252
+ # print(optimizer.param_groups[0]['lr'])
253
+ forward_dict, result = forward_pass(policy_config, data, policy)
254
+ # print("result:", post_process(result.cpu().detach().numpy())[0, :, 7:])
255
+ # backward
256
+ loss = forward_dict['loss']
257
+ loss.backward()
258
+ optimizer.step()
259
+ optimizer.zero_grad()
260
+ train_history.append(detach_dict(forward_dict))
261
+ epoch_summary = compute_dict_mean(train_history[(batch_idx+1)*epoch:(batch_idx+1)*(epoch+1)])
262
+ epoch_train_loss = epoch_summary['loss']
263
+ print(f'Train loss: {epoch_train_loss:.5f}')
264
+ summary_string = ''
265
+ for k, v in epoch_summary.items():
266
+ summary_string += f'{k}: {v.item():.3f} '
267
+ print(summary_string)
268
+
269
+ if epoch % ckpt_save_interval == 0:
270
+ ckpt_path = os.path.join(ckpt_dir, f'policy_epoch_{epoch}_seed_{seed}.ckpt')
271
+ torch.save(policy.serialize(), ckpt_path)
272
+
273
+ if epoch > 0 and epoch % plot_interval == 0:
274
+ plot_history(train_history, validation_history, epoch, ckpt_dir, seed)
275
+
276
+ ckpt_path = os.path.join(ckpt_dir, f'policy_last.ckpt')
277
+ torch.save(policy.serialize(), ckpt_path)
278
+
279
+ best_epoch, min_val_loss, best_state_dict = best_ckpt_info
280
+ ckpt_path = os.path.join(ckpt_dir, f'policy_epoch_{best_epoch}_seed_{seed}.ckpt')
281
+ torch.save(best_state_dict, ckpt_path)
282
+ print(f'Training finished:\nSeed {seed}, val loss {min_val_loss:.6f} at epoch {best_epoch}')
283
+
284
+ # save training curves
285
+ plot_history(train_history, validation_history, num_epochs, ckpt_dir, seed)
286
+
287
+ return best_ckpt_info
288
+
289
+
290
+ def plot_history(train_history, validation_history, num_epochs, ckpt_dir, seed):
291
+ # save training curves
292
+ for key in train_history[0]:
293
+ plot_path = os.path.join(ckpt_dir, f'train_val_{key}_seed_{seed}.png')
294
+ plt.figure()
295
+ train_values = [summary[key].item() for summary in train_history]
296
+ val_values = [summary[key].item() for summary in validation_history]
297
+ plt.plot(np.linspace(0, num_epochs-1, len(train_history)), train_values, label='train')
298
+ plt.plot(np.linspace(0, num_epochs-1, len(validation_history)), val_values, label='validation')
299
+ # plt.ylim([-0.1, 1])
300
+ plt.tight_layout()
301
+ plt.legend()
302
+ plt.title(key)
303
+ plt.savefig(plot_path)
304
+ print(f'Saved plots to {ckpt_dir}')
305
+
306
+
307
+ def get_arguments():
308
+ parser = argparse.ArgumentParser()
309
+ parser.add_argument('--dataset_dir', action='store', type=str, help='dataset_dir', default='./dataset', required=True)
310
+ parser.add_argument('--ckpt_dir', action='store', type=str, help='ckpt_dir', required=True)
311
+ parser.add_argument('--num_episodes', action='store', type=int, help='num_episodes', required=True)
312
+
313
+ parser.add_argument('--pretrain_ckpt', action='store', type=str, help='pretrain_ckpt', default='', required=False)
314
+ parser.add_argument('--pretrain_backbone_path', action='store', type=str, help='pretrain_backbone_path', default='', required=False)
315
+ parser.add_argument('--task_name', action='store', type=str, help='task_name', default='aloha_mobile_dummy', required=False)
316
+
317
+ parser.add_argument('--ckpt_name', action='store', type=str, help='ckpt_name', default='policy_best.ckpt', required=False)
318
+ parser.add_argument('--ckpt_stats_name', action='store', type=str, help='ckpt_stats_name', default='dataset_stats.pkl', required=False)
319
+ parser.add_argument('--policy_class', action='store', type=str, help='policy_class, capitalize, CNNMLP, ACT, Diffusion', default='ACT', required=False)
320
+ parser.add_argument('--batch_size', action='store', type=int, help='batch_size', default=32, required=False)
321
+ parser.add_argument('--seed', action='store', type=int, help='seed', default=0, required=False)
322
+ parser.add_argument('--num_epochs', action='store', type=int, help='num_epochs', default=3000, required=False)
323
+ parser.add_argument('--ckpt_save_interval', action='store', type=int, help='ckpt_save_interval', default=100, required=False)
324
+ parser.add_argument('--plot_interval', action='store', type=int, help='plot_interval', default=100, required=False)
325
+
326
+ parser.add_argument('--lr', action='store', type=float, help='lr', default=4e-5, required=False)
327
+ parser.add_argument('--lr_decay_start_epoch', action='store', type=int, help='epoch to start LR decay', default=3000, required=False)
328
+ parser.add_argument('--min_lr', action='store', type=float, help='minimum learning rate for decay', default=1e-6, required=False)
329
+ parser.add_argument('--weight_decay', type=float, help='weight_decay', default=1e-4, required=False)
330
+ parser.add_argument('--dilation', action='store_true',
331
+ help="If true, we replace stride with dilation in the last convolutional block (DC5)", required=False)
332
+ parser.add_argument('--position_embedding', default='sine', type=str, choices=('sine', 'learned'),
333
+ help="Type of positional embedding to use on top of the image features", required=False)
334
+ parser.add_argument('--masks', action='store_true',
335
+ help="Train segmentation head if the flag is provided")
336
+
337
+ parser.add_argument('--state_dim', action='store', type=int, help='state_dim', default=14, required=False)
338
+ parser.add_argument('--lr_backbone', action='store', type=float, help='lr_backbone', default=4e-5, required=False)
339
+ parser.add_argument('--backbone', action='store', type=str, help='backbone', default='resnet18', required=False)
340
+ parser.add_argument('--loss_function', action='store', type=str, help='loss_function l1 l2 l1+l2', default='l1', required=False)
341
+ parser.add_argument('--enc_layers', action='store', type=int, help='enc_layers', default=4, required=False)
342
+ parser.add_argument('--dec_layers', action='store', type=int, help='dec_layers', default=7, required=False)
343
+ parser.add_argument('--nheads', action='store', type=int, help='nheads', default=8, required=False)
344
+ parser.add_argument('--dropout', default=0.1, type=float, help="Dropout applied in the transformer", required=False)
345
+ parser.add_argument('--pre_norm', action='store_true', required=False)
346
+
347
+ # for ACT
348
+ parser.add_argument('--kl_weight', action='store', type=int, help='KL Weight', default=10, required=False)
349
+ parser.add_argument('--chunk_size', action='store', type=int, help='chunk_size', default=32, required=False)
350
+ parser.add_argument('--hidden_dim', action='store', type=int, help='hidden_dim', default=512, required=False)
351
+ parser.add_argument('--dim_feedforward', action='store', type=int, help='dim_feedforward', default=3200, required=False)
352
+ parser.add_argument('--temporal_agg', action='store', type=bool, help='temporal_agg', default=True, required=False)
353
+
354
+ # for Diffusion
355
+ parser.add_argument('--observation_horizon', action='store', type=int, help='observation_horizon', default=1, required=False)
356
+ parser.add_argument('--action_horizon', action='store', type=int, help='action_horizon', default=8, required=False)
357
+ parser.add_argument('--num_inference_timesteps', action='store', type=int, help='num_inference_timesteps', default=10, required=False)
358
+ parser.add_argument('--ema_power', action='store', type=int, help='ema_power', default=0.75, required=False)
359
+
360
+ parser.add_argument('--use_robot_base', action='store', type=bool, help='use_robot_base', default=False, required=False)
361
+
362
+ parser.add_argument('--arm_delay_time', action='store', type=int, help='arm_delay_time', default=0, required=False)
363
+
364
+ parser.add_argument('--use_depth_image', action='store', type=bool, help='use_depth_image', default=False, required=False)
365
+
366
+ args = parser.parse_args()
367
+ return args
368
+
369
+
370
+ def main():
371
+ args = get_arguments()
372
+ train(args)
373
+
374
+ if __name__ == '__main__':
375
+ main()
376
+ # python act/train.py --dataset_dir ~/data --pretrain_ckpt policy_best.ckpt --ckpt_dir ~/train_dir/ --num_episodes 20 --batch_size 10 --num_epochs 2000
aloha-devel/robomimic/__pycache__/macros.cpython-38.pyc ADDED
Binary file (637 Bytes). View file
 
aloha-devel/robomimic/algo/__init__.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from robomimic.algo.algo import register_algo_factory_func, algo_name_to_factory_func, algo_factory, Algo, PolicyAlgo, ValueAlgo, PlannerAlgo, HierarchicalAlgo, RolloutPolicy
2
+
3
+ # note: these imports are needed to register these classes in the global algo registry
4
+ from robomimic.algo.bc import BC, BC_Gaussian, BC_GMM, BC_VAE, BC_RNN, BC_RNN_GMM
5
+ from robomimic.algo.bcq import BCQ, BCQ_GMM, BCQ_Distributional
6
+ from robomimic.algo.cql import CQL
7
+ from robomimic.algo.iql import IQL
8
+ from robomimic.algo.gl import GL, GL_VAE, ValuePlanner
9
+ from robomimic.algo.hbc import HBC
10
+ from robomimic.algo.iris import IRIS
11
+ from robomimic.algo.td3_bc import TD3_BC
12
+ from robomimic.algo.diffusion_policy import DiffusionPolicyUNet
13
+ from robomimic.algo.act import ACT
aloha-devel/robomimic/algo/__pycache__/__init__.cpython-38.pyc ADDED
Binary file (1.18 kB). View file
 
aloha-devel/robomimic/algo/__pycache__/bc.cpython-38.pyc ADDED
Binary file (22.5 kB). View file
 
aloha-devel/robomimic/algo/act.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Implementation of Action Chunking with Transformers (ACT).
3
+ """
4
+ from collections import OrderedDict
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+ import torchvision.transforms as transforms
10
+
11
+ import robomimic.utils.tensor_utils as TensorUtils
12
+
13
+ from robomimic.algo import register_algo_factory_func, PolicyAlgo
14
+ from robomimic.algo.bc import BC_VAE
15
+
16
+
17
+ @register_algo_factory_func("act")
18
+ def algo_config_to_class(algo_config):
19
+ """
20
+ Maps algo config to the BC algo class to instantiate, along with additional algo kwargs.
21
+
22
+ Args:
23
+ algo_config (Config instance): algo config
24
+
25
+ Returns:
26
+ algo_class: subclass of Algo
27
+ algo_kwargs (dict): dictionary of additional kwargs to pass to algorithm
28
+ """
29
+ algo_class, algo_kwargs = ACT, {}
30
+
31
+ return algo_class, algo_kwargs
32
+
33
+
34
+ class ACT(BC_VAE):
35
+ """
36
+ BC training with a VAE policy.
37
+ """
38
+ def _create_networks(self):
39
+ """
40
+ Creates networks and places them into @self.nets.
41
+ """
42
+
43
+ self.normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
44
+
45
+ self.nets = nn.ModuleDict()
46
+ self.chunk_size = self.global_config["train"]["seq_length"]
47
+ self.camera_keys = self.obs_config['modalities']['obs']['rgb'].copy()
48
+ self.proprio_keys = self.obs_config['modalities']['obs']['low_dim'].copy()
49
+ self.obs_keys = self.proprio_keys + self.camera_keys
50
+
51
+ self.proprio_dim = 0
52
+ for k in self.proprio_keys:
53
+ self.proprio_dim += self.obs_key_shapes[k][0]
54
+
55
+ from act.detr.main import build_ACT_model_and_optimizer
56
+ policy_config = {'num_queries': self.chunk_size,
57
+ 'hidden_dim': self.algo_config.act.hidden_dim,
58
+ 'dim_feedforward': self.algo_config.act.dim_feedforward,
59
+ 'backbone': self.algo_config.act.backbone,
60
+ 'enc_layers': self.algo_config.act.enc_layers,
61
+ 'dec_layers': self.algo_config.act.dec_layers,
62
+ 'nheads': self.algo_config.act.nheads,
63
+ 'latent_dim': self.algo_config.act.latent_dim,
64
+ 'a_dim': self.ac_dim,
65
+ 'state_dim': self.proprio_dim,
66
+ 'camera_names': self.camera_keys
67
+ }
68
+ self.kl_weight = self.algo_config.act.kl_weight
69
+ model, optimizer = build_ACT_model_and_optimizer(policy_config)
70
+ self.nets["policy"] = model
71
+ self.nets = self.nets.float().to(self.device)
72
+
73
+ self.temporal_agg = False
74
+ self.query_frequency = self.chunk_size # TODO maybe tune
75
+
76
+ self._step_counter = 0
77
+ self.a_hat_store = None
78
+
79
+
80
+ def process_batch_for_training(self, batch):
81
+ """
82
+ Processes input batch from a data loader to filter out
83
+ relevant information and prepare the batch for training.
84
+ Args:
85
+ batch (dict): dictionary with torch.Tensors sampled
86
+ from a data loader
87
+ Returns:
88
+ input_batch (dict): processed and filtered batch that
89
+ will be used for training
90
+ """
91
+
92
+ input_batch = dict()
93
+ input_batch["obs"] = {k: batch["obs"][k][:, 0, :] for k in batch["obs"] if k != 'pad_mask'}
94
+ input_batch["obs"]['pad_mask'] = batch["obs"]['pad_mask']
95
+ input_batch["goal_obs"] = batch.get("goal_obs", None) # goals may not be present
96
+ input_batch["actions"] = batch["actions"][:, :, :]
97
+ # we move to device first before float conversion because image observation modalities will be uint8 -
98
+ # this minimizes the amount of data transferred to GPU
99
+ return TensorUtils.to_float(TensorUtils.to_device(input_batch, self.device))
100
+
101
+ def train_on_batch(self, batch, epoch, validate=False):
102
+ """
103
+ Update from superclass to set categorical temperature, for categorcal VAEs.
104
+ """
105
+
106
+ return super(BC_VAE, self).train_on_batch(batch, epoch, validate=validate)
107
+
108
+ def _forward_training(self, batch):
109
+ """
110
+ Internal helper function for BC algo class. Compute forward pass
111
+ and return network outputs in @predictions dict.
112
+ Args:
113
+ batch (dict): dictionary with torch.Tensors sampled
114
+ from a data loader and filtered by @process_batch_for_training
115
+ Returns:
116
+ predictions (dict): dictionary containing network outputs
117
+ """
118
+
119
+ proprio = [batch["obs"][k] for k in self.proprio_keys]
120
+ proprio = torch.cat(proprio, axis=1)
121
+ qpos = proprio
122
+
123
+ images = []
124
+ for cam_name in self.camera_keys:
125
+ image = batch['obs'][cam_name]
126
+ image = self.normalize(image)
127
+ image = image.unsqueeze(axis=1)
128
+ images.append(image)
129
+ images = torch.cat(images, axis=1)
130
+
131
+ env_state = torch.zeros([qpos.shape[0], 10]).cuda() # this is not used
132
+
133
+ actions = batch['actions']
134
+ is_pad = batch['obs']['pad_mask'] == 0 # from 1.0 or 0 to False and True
135
+ is_pad = is_pad.squeeze(dim=-1)
136
+
137
+ a_hat, is_pad_hat, (mu, logvar) = self.nets["policy"](qpos, images, env_state, actions, is_pad)
138
+ total_kld, dim_wise_kld, mean_kld = self.kl_divergence(mu, logvar)
139
+ loss_dict = dict()
140
+ all_l1 = F.l1_loss(actions, a_hat, reduction='none')
141
+ l1 = (all_l1 * ~is_pad.unsqueeze(-1)).mean()
142
+ loss_dict['l1'] = l1
143
+ loss_dict['kl'] = total_kld[0]
144
+
145
+
146
+ predictions = OrderedDict(
147
+ actions=actions,
148
+ kl_loss=loss_dict['kl'],
149
+ reconstruction_loss=loss_dict['l1'],
150
+ )
151
+
152
+ return predictions
153
+
154
+ def get_action(self, obs_dict, goal_dict=None):
155
+ """
156
+ Get policy action outputs.
157
+ Args:
158
+ obs_dict (dict): current observation
159
+ goal_dict (dict): (optional) goal
160
+ Returns:
161
+ action (torch.Tensor): action tensor
162
+ """
163
+ assert not self.nets.training
164
+
165
+ proprio = [obs_dict[k] for k in self.proprio_keys]
166
+ proprio = torch.cat(proprio, axis=1)
167
+ qpos = proprio
168
+
169
+ images = []
170
+ for cam_name in self.camera_keys:
171
+ image = obs_dict[cam_name]
172
+ image = self.normalize(image)
173
+ image = image.unsqueeze(axis=1)
174
+ images.append(image)
175
+ images = torch.cat(images, axis=1)
176
+
177
+ env_state = torch.zeros([qpos.shape[0], 10]).cuda() # not used
178
+
179
+ if self._step_counter % self.query_frequency == 0:
180
+ a_hat, is_pad_hat, (mu, logvar) = self.nets["policy"](qpos, images, env_state)
181
+ self.a_hat_store = a_hat
182
+
183
+ action = self.a_hat_store[:, self._step_counter % self.query_frequency, :]
184
+ self._step_counter += 1
185
+ return action
186
+
187
+
188
+ def reset(self):
189
+ """
190
+ Reset algo state to prepare for environment rollouts.
191
+ """
192
+ self._step_counter = 0
193
+
194
+ def _compute_losses(self, predictions, batch):
195
+ """
196
+ Internal helper function for BC algo class. Compute losses based on
197
+ network outputs in @predictions dict, using reference labels in @batch.
198
+ Args:
199
+ predictions (dict): dictionary containing network outputs, from @_forward_training
200
+ batch (dict): dictionary with torch.Tensors sampled
201
+ from a data loader and filtered by @process_batch_for_training
202
+ Returns:
203
+ losses (dict): dictionary of losses computed over the batch
204
+ """
205
+
206
+ # total loss is sum of reconstruction and KL, weighted by beta
207
+ kl_loss = predictions["kl_loss"]
208
+ recons_loss = predictions["reconstruction_loss"]
209
+ action_loss = recons_loss + self.kl_weight * kl_loss
210
+ return OrderedDict(
211
+ recons_loss=recons_loss,
212
+ kl_loss=kl_loss,
213
+ action_loss=action_loss,
214
+ )
215
+
216
+ def log_info(self, info):
217
+ """
218
+ Process info dictionary from @train_on_batch to summarize
219
+ information to pass to tensorboard for logging.
220
+ Args:
221
+ info (dict): dictionary of info
222
+ Returns:
223
+ loss_log (dict): name -> summary statistic
224
+ """
225
+ log = PolicyAlgo.log_info(self, info)
226
+ log["Loss"] = info["losses"]["action_loss"].item()
227
+ log["KL_Loss"] = info["losses"]["kl_loss"].item()
228
+ log["Reconstruction_Loss"] = info["losses"]["recons_loss"].item()
229
+ if "policy_grad_norms" in info:
230
+ log["Policy_Grad_Norms"] = info["policy_grad_norms"]
231
+ return log
232
+
233
+ def kl_divergence(self, mu, logvar):
234
+ batch_size = mu.size(0)
235
+ assert batch_size != 0
236
+ if mu.data.ndimension() == 4:
237
+ mu = mu.view(mu.size(0), mu.size(1))
238
+ if logvar.data.ndimension() == 4:
239
+ logvar = logvar.view(logvar.size(0), logvar.size(1))
240
+
241
+ klds = -0.5 * (1 + logvar - mu.pow(2) - logvar.exp())
242
+ total_kld = klds.sum(1).mean(0, True)
243
+ dimension_wise_kld = klds.mean(0)
244
+ mean_kld = klds.mean(1).mean(0, True)
245
+
246
+ return total_kld, dimension_wise_kld, mean_kld
247
+
aloha-devel/robomimic/algo/algo.py ADDED
@@ -0,0 +1,674 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This file contains base classes that other algorithm classes subclass.
3
+ Each algorithm file also implements a algorithm factory function that
4
+ takes in an algorithm config (`config.algo`) and returns the particular
5
+ Algo subclass that should be instantiated, along with any extra kwargs.
6
+ These factory functions are registered into a global dictionary with the
7
+ @register_algo_factory_func function decorator. This makes it easy for
8
+ @algo_factory to instantiate the correct `Algo` subclass.
9
+ """
10
+ import textwrap
11
+ from copy import deepcopy
12
+ from collections import OrderedDict
13
+
14
+ import torch.nn as nn
15
+ import torch
16
+ import os
17
+ import numpy as np
18
+ import imageio
19
+
20
+ import robomimic.utils.tensor_utils as TensorUtils
21
+ import robomimic.utils.torch_utils as TorchUtils
22
+ import robomimic.utils.obs_utils as ObsUtils
23
+ import robomimic.utils.action_utils as AcUtils
24
+ import robomimic.utils.vis_utils as VisUtils
25
+
26
+ from torch.utils.data import DataLoader
27
+
28
+ # mapping from algo name to factory functions that map algo configs to algo class names
29
+ REGISTERED_ALGO_FACTORY_FUNCS = OrderedDict()
30
+
31
+
32
+ def register_algo_factory_func(algo_name):
33
+ """
34
+ Function decorator to register algo factory functions that map algo configs to algo class names.
35
+ Each algorithm implements such a function, and decorates it with this decorator.
36
+
37
+ Args:
38
+ algo_name (str): the algorithm name to register the algorithm under
39
+ """
40
+ def decorator(factory_func):
41
+ REGISTERED_ALGO_FACTORY_FUNCS[algo_name] = factory_func
42
+ return decorator
43
+
44
+
45
+ def algo_name_to_factory_func(algo_name):
46
+ """
47
+ Uses registry to retrieve algo factory function from algo name.
48
+
49
+ Args:
50
+ algo_name (str): the algorithm name
51
+ """
52
+ return REGISTERED_ALGO_FACTORY_FUNCS[algo_name]
53
+
54
+
55
+ def algo_factory(algo_name, config, obs_key_shapes, ac_dim, device):
56
+ """
57
+ Factory function for creating algorithms based on the algorithm name and config.
58
+
59
+ Args:
60
+ algo_name (str): the algorithm name
61
+
62
+ config (BaseConfig instance): config object
63
+
64
+ obs_key_shapes (OrderedDict): dictionary that maps observation keys to shapes
65
+
66
+ ac_dim (int): dimension of action space
67
+
68
+ device (torch.Device): where the algo should live (i.e. cpu, gpu)
69
+ """
70
+
71
+ # @algo_name is included as an arg to be explicit, but make sure it matches the config
72
+ assert algo_name == config.algo_name
73
+
74
+ # use algo factory func to get algo class and kwargs from algo config
75
+ factory_func = algo_name_to_factory_func(algo_name)
76
+ algo_cls, algo_kwargs = factory_func(config.algo)
77
+
78
+ # create algo instance
79
+ return algo_cls(
80
+ algo_config=config.algo,
81
+ obs_config=config.observation,
82
+ global_config=config,
83
+ obs_key_shapes=obs_key_shapes,
84
+ ac_dim=ac_dim,
85
+ device=device,
86
+ **algo_kwargs
87
+ )
88
+
89
+
90
+ class Algo(object):
91
+ """
92
+ Base algorithm class that all other algorithms subclass. Defines several
93
+ functions that should be overriden by subclasses, in order to provide
94
+ a standard API to be used by training functions such as @run_epoch in
95
+ utils/train_utils.py.
96
+ """
97
+ def __init__(
98
+ self,
99
+ algo_config,
100
+ obs_config,
101
+ global_config,
102
+ obs_key_shapes,
103
+ ac_dim,
104
+ device
105
+ ):
106
+ """
107
+ Args:
108
+ algo_config (Config object): instance of Config corresponding to the algo section
109
+ of the config
110
+
111
+ obs_config (Config object): instance of Config corresponding to the observation
112
+ section of the config
113
+
114
+ global_config (Config object): global training config
115
+
116
+ obs_key_shapes (OrderedDict): dictionary that maps observation keys to shapes
117
+
118
+ ac_dim (int): dimension of action space
119
+
120
+ device (torch.Device): where the algo should live (i.e. cpu, gpu)
121
+ """
122
+ self.optim_params = deepcopy(algo_config.optim_params)
123
+ self.algo_config = algo_config
124
+ self.obs_config = obs_config
125
+ self.global_config = global_config
126
+
127
+ self.ac_dim = ac_dim
128
+ self.device = device
129
+ self.obs_key_shapes = obs_key_shapes
130
+
131
+ self.nets = nn.ModuleDict()
132
+ self._create_shapes(obs_config.modalities, obs_key_shapes)
133
+ self._create_networks()
134
+ self._create_optimizers()
135
+ assert isinstance(self.nets, nn.ModuleDict)
136
+
137
+ def _create_shapes(self, obs_keys, obs_key_shapes):
138
+ """
139
+ Create obs_shapes, goal_shapes, and subgoal_shapes dictionaries, to make it
140
+ easy for this algorithm object to keep track of observation key shapes. Each dictionary
141
+ maps observation key to shape.
142
+
143
+ Args:
144
+ obs_keys (dict): dict of required observation keys for this training run (usually
145
+ specified by the obs config), e.g., {"obs": ["rgb", "proprio"], "goal": ["proprio"]}
146
+ obs_key_shapes (dict): dict of observation key shapes, e.g., {"rgb": [3, 224, 224]}
147
+ """
148
+ # determine shapes
149
+ self.obs_shapes = OrderedDict()
150
+ self.goal_shapes = OrderedDict()
151
+ self.subgoal_shapes = OrderedDict()
152
+
153
+ # We check across all modality groups (obs, goal, subgoal), and see if the inputted observation key exists
154
+ # across all modalitie specified in the config. If so, we store its corresponding shape internally
155
+ for k in obs_key_shapes:
156
+ if "obs" in self.obs_config.modalities and k in [obs_key for modality in self.obs_config.modalities.obs.values() for obs_key in modality]:
157
+ self.obs_shapes[k] = obs_key_shapes[k]
158
+ if "goal" in self.obs_config.modalities and k in [obs_key for modality in self.obs_config.modalities.goal.values() for obs_key in modality]:
159
+ self.goal_shapes[k] = obs_key_shapes[k]
160
+ if "subgoal" in self.obs_config.modalities and k in [obs_key for modality in self.obs_config.modalities.subgoal.values() for obs_key in modality]:
161
+ self.subgoal_shapes[k] = obs_key_shapes[k]
162
+
163
+ def _create_networks(self):
164
+ """
165
+ Creates networks and places them into @self.nets.
166
+ @self.nets should be a ModuleDict.
167
+ """
168
+ raise NotImplementedError
169
+
170
+ def _create_optimizers(self):
171
+ """
172
+ Creates optimizers using @self.optim_params and places them into @self.optimizers.
173
+ """
174
+ self.optimizers = dict()
175
+ self.lr_schedulers = dict()
176
+
177
+ for k in self.optim_params:
178
+ # only make optimizers for networks that have been created - @optim_params may have more
179
+ # settings for unused networks
180
+ if k in self.nets:
181
+ if isinstance(self.nets[k], nn.ModuleList):
182
+ self.optimizers[k] = [
183
+ TorchUtils.optimizer_from_optim_params(net_optim_params=self.optim_params[k], net=self.nets[k][i])
184
+ for i in range(len(self.nets[k]))
185
+ ]
186
+ self.lr_schedulers[k] = [
187
+ TorchUtils.lr_scheduler_from_optim_params(net_optim_params=self.optim_params[k], net=self.nets[k][i], optimizer=self.optimizers[k][i])
188
+ for i in range(len(self.nets[k]))
189
+ ]
190
+ else:
191
+ self.optimizers[k] = TorchUtils.optimizer_from_optim_params(
192
+ net_optim_params=self.optim_params[k], net=self.nets[k])
193
+ self.lr_schedulers[k] = TorchUtils.lr_scheduler_from_optim_params(
194
+ net_optim_params=self.optim_params[k], net=self.nets[k], optimizer=self.optimizers[k])
195
+
196
+ def process_batch_for_training(self, batch):
197
+ """
198
+ Processes input batch from a data loader to filter out
199
+ relevant information and prepare the batch for training.
200
+
201
+ Args:
202
+ batch (dict): dictionary with torch.Tensors sampled
203
+ from a data loader
204
+
205
+ Returns:
206
+ input_batch (dict): processed and filtered batch that
207
+ will be used for training
208
+ """
209
+ return batch
210
+
211
+ def postprocess_batch_for_training(self, batch, obs_normalization_stats):
212
+ """
213
+ Does some operations (like channel swap, uint8 to float conversion, normalization)
214
+ after @process_batch_for_training is called, in order to ensure these operations
215
+ take place on GPU.
216
+
217
+ Args:
218
+ batch (dict): dictionary with torch.Tensors sampled
219
+ from a data loader. Assumed to be on the device where
220
+ training will occur (after @process_batch_for_training
221
+ is called)
222
+
223
+ obs_normalization_stats (dict or None): if provided, this should map observation
224
+ keys to dicts with a "mean" and "std" of shape (1, ...) where ... is the
225
+ default shape for the observation.
226
+
227
+ Returns:
228
+ batch (dict): postproceesed batch
229
+ """
230
+ obs_keys = ["obs", "next_obs", "goal_obs"]
231
+ for k in obs_keys:
232
+ if k in batch and batch[k] is not None:
233
+ batch[k] = ObsUtils.process_obs_dict(batch[k])
234
+ if obs_normalization_stats is not None:
235
+ batch[k] = ObsUtils.normalize_dict(batch[k], obs_normalization_stats=obs_normalization_stats)
236
+ return batch
237
+
238
+ def train_on_batch(self, batch, epoch, validate=False):
239
+ """
240
+ Training on a single batch of data.
241
+
242
+ Args:
243
+ batch (dict): dictionary with torch.Tensors sampled
244
+ from a data loader and filtered by @process_batch_for_training
245
+
246
+ epoch (int): epoch number - required by some Algos that need
247
+ to perform staged training and early stopping
248
+
249
+ validate (bool): if True, don't perform any learning updates.
250
+
251
+ Returns:
252
+ info (dict): dictionary of relevant inputs, outputs, and losses
253
+ that might be relevant for logging
254
+ """
255
+ assert validate or self.nets.training
256
+ return OrderedDict()
257
+
258
+ def log_info(self, info):
259
+ """
260
+ Process info dictionary from @train_on_batch to summarize
261
+ information to pass to tensorboard for logging.
262
+
263
+ Args:
264
+ info (dict): dictionary of info
265
+
266
+ Returns:
267
+ loss log (dict): name -> summary statistic
268
+ """
269
+ log = OrderedDict()
270
+
271
+ # record current optimizer learning rates
272
+ for k in self.optimizers:
273
+ for i, param_group in enumerate(self.optimizers[k].param_groups):
274
+ log["Optimizer/{}{}_lr".format(k, i)] = param_group["lr"]
275
+
276
+ return log
277
+
278
+ def on_epoch_end(self, epoch):
279
+ """
280
+ Called at the end of each epoch.
281
+ """
282
+
283
+ # LR scheduling updates
284
+ for k in self.lr_schedulers:
285
+ if self.lr_schedulers[k] is not None:
286
+ self.lr_schedulers[k].step()
287
+
288
+ def set_eval(self):
289
+ """
290
+ Prepare networks for evaluation.
291
+ """
292
+ self.nets.eval()
293
+
294
+ def set_train(self):
295
+ """
296
+ Prepare networks for training.
297
+ """
298
+ self.nets.train()
299
+
300
+ def serialize(self):
301
+ """
302
+ Get dictionary of current model parameters.
303
+ """
304
+ return self.nets.state_dict()
305
+
306
+ def deserialize(self, model_dict):
307
+ """
308
+ Load model from a checkpoint.
309
+
310
+ Args:
311
+ model_dict (dict): a dictionary saved by self.serialize() that contains
312
+ the same keys as @self.network_classes
313
+ """
314
+ self.nets.load_state_dict(model_dict)
315
+
316
+ def __repr__(self):
317
+ """
318
+ Pretty print algorithm and network description.
319
+ """
320
+ return "{} (\n".format(self.__class__.__name__) + \
321
+ textwrap.indent(self.nets.__repr__(), ' ') + "\n)"
322
+
323
+ def reset(self):
324
+ """
325
+ Reset algo state to prepare for environment rollouts.
326
+ """
327
+ pass
328
+
329
+
330
+ class PolicyAlgo(Algo):
331
+ """
332
+ Base class for all algorithms that can be used as policies.
333
+ """
334
+ def get_action(self, obs_dict, goal_dict=None):
335
+ """
336
+ Get policy action outputs.
337
+
338
+ Args:
339
+ obs_dict (dict): current observation
340
+ goal_dict (dict): (optional) goal
341
+
342
+ Returns:
343
+ action (torch.Tensor): action tensor
344
+ """
345
+ raise NotImplementedError
346
+
347
+ def compute_traj_pred_actual_actions(self, traj, return_images=False):
348
+ """
349
+ traj is an R2D2Dataset object representing one trajectory
350
+ This function is slow (>1s per trajectory) because there is no batching
351
+ and instead loops through all timesteps one by one
352
+ TODO: documentation
353
+ """
354
+ if return_images:
355
+ image_keys = [item for item in traj.__getitem__(0)['obs'].keys() if "image" in item]
356
+ images = {key: [] for key in image_keys}
357
+ else:
358
+ images = None
359
+
360
+ dataloader = DataLoader(
361
+ dataset=traj,
362
+ sampler=None,
363
+ batch_size=1,
364
+ shuffle=False,
365
+ num_workers=1,
366
+ drop_last=True,
367
+ )
368
+
369
+ self.reset()
370
+ actual_actions = []
371
+ predicted_actions = []
372
+
373
+ # loop through each timestep
374
+ for batch in iter(dataloader):
375
+ batch = self.process_batch_for_training(batch)
376
+
377
+ if return_images:
378
+ for image_key in image_keys:
379
+ im = batch["obs"][image_key][0][-1]
380
+ im = TensorUtils.to_numpy(im).astype(np.uint32)
381
+ images[image_key].append(im)
382
+
383
+ batch = self.postprocess_batch_for_training(batch, obs_normalization_stats=None) # ignore obs_normalization for now
384
+
385
+ model_output = self.get_action(batch["obs"])
386
+
387
+ actual_action = TensorUtils.to_numpy(
388
+ batch["actions"][0][0]
389
+ )
390
+ predicted_action = TensorUtils.to_numpy(
391
+ model_output[0]
392
+ )
393
+
394
+ actual_actions.append(actual_action)
395
+ predicted_actions.append(predicted_action)
396
+
397
+ actual_actions = np.array(actual_actions)
398
+ predicted_actions = np.array(predicted_actions)
399
+ return actual_actions, predicted_actions, images
400
+
401
+ def compute_mse_visualize(self, trainset, validset, num_samples, savedir=None):
402
+ """If savedir is not None, then also visualize the model predictions and save them to savedir"""
403
+ visualize = savedir is not None
404
+
405
+ # set model into eval mode
406
+ self.set_eval()
407
+ random_state = np.random.RandomState(0)
408
+ train_indices = random_state.choice(
409
+ len(trainset.datasets),
410
+ min(len(trainset.datasets), num_samples)
411
+ ).astype(int)
412
+ training_sampled_data = [trainset.datasets[idx] for idx in train_indices]
413
+
414
+ if validset is not None:
415
+ valid_indices = random_state.choice(
416
+ len(validset.datasets),
417
+ min(len(validset.datasets), num_samples)
418
+ ).astype(int)
419
+ validation_sampled_data = [validset.datasets[idx] for idx in valid_indices]
420
+
421
+ inference_datasets_mapping = {"Train": training_sampled_data, "Valid": validation_sampled_data}
422
+ else:
423
+ inference_datasets_mapping = {"Train": training_sampled_data}
424
+
425
+ # extract action name for visualization
426
+ action_keys = self.global_config.train.action_keys
427
+ training_sample=training_sampled_data[0][0]
428
+ modified_action_keys = [element.replace("action/", "") for element in action_keys]
429
+ action_names = []
430
+
431
+ for i, action_key in enumerate(action_keys):
432
+ if isinstance(training_sample[action_key][0], np.ndarray):
433
+ action_names.extend([f'{modified_action_keys[i]}_{j+1}' for j in range(len(training_sample[action_key][0]))])
434
+ else:
435
+ action_names.append(modified_action_keys[i])
436
+
437
+ if visualize:
438
+ print("Saving model prediction plots to {}".format(savedir))
439
+
440
+ mse_log = {}
441
+ vis_log = {}
442
+ # loop through training and validation sets
443
+ for inference_key in inference_datasets_mapping:
444
+ actual_actions_all_traj = [] # (NxT, D)
445
+ predicted_actions_all_traj = [] # (NxT, D)
446
+
447
+ # loop through each trajectory
448
+ traj_num = 1
449
+ for d in inference_datasets_mapping[inference_key]:
450
+ actual_actions, predicted_actions, images = self.compute_traj_pred_actual_actions(d, return_images=visualize)
451
+ actual_actions_all_traj.append(actual_actions)
452
+ predicted_actions_all_traj.append(predicted_actions)
453
+ if visualize:
454
+ traj_key = "{}_traj_{}".format(inference_key.lower(), traj_num)
455
+ save_path = os.path.join(savedir, traj_key + ".png")
456
+ VisUtils.make_model_prediction_plot(
457
+ hdf5_path=d.hdf5_path,
458
+ save_path=save_path,
459
+ images=images,
460
+ action_names=action_names,
461
+ actual_actions=actual_actions,
462
+ predicted_actions=predicted_actions,
463
+ )
464
+ vis_log[traj_key] = imageio.imread(save_path)
465
+ traj_num += 1
466
+
467
+ actual_actions_all_traj = np.concatenate(actual_actions_all_traj, axis=0)
468
+ predicted_actions_all_traj = np.concatenate(predicted_actions_all_traj, axis=0)
469
+ accuracy_thresholds = np.logspace(-3,-5, num=3).tolist()
470
+ mse = torch.nn.functional.mse_loss(
471
+ torch.tensor(predicted_actions_all_traj),
472
+ torch.tensor(actual_actions_all_traj),
473
+ reduction='none'
474
+ ) # (NxT, D)
475
+ mse_log[f'{inference_key}/action_mse_error'] = mse.mean().item() # average MSE across all timesteps averaged across all action dimensions (D,)
476
+
477
+ # compute percentage of timesteps that have MSE less than the accuracy thresholds
478
+ for accuracy_threshold in accuracy_thresholds:
479
+ mse_log[f'{inference_key}/action_accuracy@{accuracy_threshold}'] = (torch.less(mse,accuracy_threshold).float().mean().item())
480
+
481
+ return mse_log, vis_log
482
+
483
+
484
+ class ValueAlgo(Algo):
485
+ """
486
+ Base class for all algorithms that can learn a value function.
487
+ """
488
+ def get_state_value(self, obs_dict, goal_dict=None):
489
+ """
490
+ Get state value outputs.
491
+
492
+ Args:
493
+ obs_dict (dict): current observation
494
+ goal_dict (dict): (optional) goal
495
+
496
+ Returns:
497
+ value (torch.Tensor): value tensor
498
+ """
499
+ raise NotImplementedError
500
+
501
+ def get_state_action_value(self, obs_dict, actions, goal_dict=None):
502
+ """
503
+ Get state-action value outputs.
504
+
505
+ Args:
506
+ obs_dict (dict): current observation
507
+ actions (torch.Tensor): action
508
+ goal_dict (dict): (optional) goal
509
+
510
+ Returns:
511
+ value (torch.Tensor): value tensor
512
+ """
513
+ raise NotImplementedError
514
+
515
+
516
+ class PlannerAlgo(Algo):
517
+ """
518
+ Base class for all algorithms that can be used for planning subgoals
519
+ conditioned on current observations and potential goal observations.
520
+ """
521
+ def get_subgoal_predictions(self, obs_dict, goal_dict=None):
522
+ """
523
+ Get predicted subgoal outputs.
524
+
525
+ Args:
526
+ obs_dict (dict): current observation
527
+ goal_dict (dict): (optional) goal
528
+
529
+ Returns:
530
+ subgoal prediction (dict): name -> Tensor [batch_size, ...]
531
+ """
532
+ raise NotImplementedError
533
+
534
+ def sample_subgoals(self, obs_dict, goal_dict, num_samples=1):
535
+ """
536
+ For planners that rely on sampling subgoals.
537
+
538
+ Args:
539
+ obs_dict (dict): current observation
540
+ goal_dict (dict): (optional) goal
541
+
542
+ Returns:
543
+ subgoals (dict): name -> Tensor [batch_size, num_samples, ...]
544
+ """
545
+ raise NotImplementedError
546
+
547
+
548
+ class HierarchicalAlgo(Algo):
549
+ """
550
+ Base class for all hierarchical algorithms that consist of (1) subgoal planning
551
+ and (2) subgoal-conditioned policy learning.
552
+ """
553
+ def get_action(self, obs_dict, goal_dict=None):
554
+ """
555
+ Get policy action outputs.
556
+
557
+ Args:
558
+ obs_dict (dict): current observation
559
+ goal_dict (dict): (optional) goal
560
+
561
+ Returns:
562
+ action (torch.Tensor): action tensor
563
+ """
564
+ raise NotImplementedError
565
+
566
+ def get_subgoal_predictions(self, obs_dict, goal_dict=None):
567
+ """
568
+ Get subgoal predictions from high-level subgoal planner.
569
+
570
+ Args:
571
+ obs_dict (dict): current observation
572
+ goal_dict (dict): (optional) goal
573
+
574
+ Returns:
575
+ subgoal (dict): predicted subgoal
576
+ """
577
+ raise NotImplementedError
578
+
579
+ @property
580
+ def current_subgoal(self):
581
+ """
582
+ Get the current subgoal for conditioning the low-level policy
583
+
584
+ Returns:
585
+ current subgoal (dict): predicted subgoal
586
+ """
587
+ raise NotImplementedError
588
+
589
+
590
+ class RolloutPolicy(object):
591
+ """
592
+ Wraps @Algo object to make it easy to run policies in a rollout loop.
593
+ """
594
+ def __init__(self, policy, obs_normalization_stats=None, action_normalization_stats=None):
595
+ """
596
+ Args:
597
+ policy (Algo instance): @Algo object to wrap to prepare for rollouts
598
+
599
+ obs_normalization_stats (dict): optionally pass a dictionary for observation
600
+ normalization. This should map observation keys to dicts
601
+ with a "mean" and "std" of shape (1, ...) where ... is the default
602
+ shape for the observation.
603
+ """
604
+ self.policy = policy
605
+ self.obs_normalization_stats = obs_normalization_stats
606
+ self.action_normalization_stats = action_normalization_stats
607
+
608
+ def start_episode(self):
609
+ """
610
+ Prepare the policy to start a new rollout.
611
+ """
612
+ self.policy.set_eval()
613
+ self.policy.reset()
614
+
615
+ def _prepare_observation(self, ob, batched=False):
616
+ """
617
+ Prepare raw observation dict from environment for policy.
618
+
619
+ Args:
620
+ ob (dict): single observation dictionary from environment (no batch dimension,
621
+ and np.array values for each key)
622
+
623
+ batched (bool): whether the input is already batched
624
+ """
625
+ if self.obs_normalization_stats is not None:
626
+ ob = ObsUtils.normalize_dict(ob, obs_normalization_stats=self.obs_normalization_stats)
627
+ ob = TensorUtils.to_tensor(ob)
628
+ if not batched:
629
+ ob = TensorUtils.to_batch(ob)
630
+ ob = TensorUtils.to_device(ob, self.policy.device)
631
+ ob = TensorUtils.to_float(ob)
632
+ return ob
633
+
634
+ def __repr__(self):
635
+ """Pretty print network description"""
636
+ return self.policy.__repr__()
637
+
638
+ def __call__(self, ob, goal=None, batched=False):
639
+ """
640
+ Produce action from raw observation dict (and maybe goal dict) from environment.
641
+
642
+ Args:
643
+ ob (dict): single observation dictionary from environment (no batch dimension,
644
+ and np.array values for each key)
645
+ goal (dict): goal observation
646
+ batched (bool): whether the input is already batched
647
+ """
648
+ ob = self._prepare_observation(ob, batched=batched)
649
+ if goal is not None:
650
+ goal = self._prepare_observation(goal, batched=batched)
651
+ ac = self.policy.get_action(obs_dict=ob, goal_dict=goal)
652
+ if not batched:
653
+ ac = ac[0]
654
+ ac = TensorUtils.to_numpy(ac)
655
+ if self.action_normalization_stats is not None:
656
+ action_keys = self.policy.global_config.train.action_keys
657
+ action_shapes = {k: self.action_normalization_stats[k]["offset"].shape[1:] for k in self.action_normalization_stats}
658
+ ac_dict = AcUtils.vector_to_action_dict(ac, action_shapes=action_shapes, action_keys=action_keys)
659
+ ac_dict = ObsUtils.unnormalize_dict(ac_dict, normalization_stats=self.action_normalization_stats)
660
+ action_config = self.policy.global_config.train.action_config
661
+ for key, value in ac_dict.items():
662
+ this_format = action_config[key].get("format", None)
663
+ if this_format == "rot_6d":
664
+ rot_6d = torch.from_numpy(value).unsqueeze(0)
665
+ conversion_format = action_config[key].get("convert_at_runtime", "rot_axis_angle")
666
+ if conversion_format == "rot_axis_angle":
667
+ rot = TorchUtils.rot_6d_to_axis_angle(rot_6d=rot_6d).squeeze().numpy()
668
+ elif conversion_format == "rot_euler":
669
+ rot = TorchUtils.rot_6d_to_euler_angles(rot_6d=rot_6d, convention="XYZ").squeeze().numpy()
670
+ else:
671
+ raise ValueError
672
+ ac_dict[key] = rot
673
+ ac = AcUtils.action_dict_to_vector(ac_dict, action_keys=action_keys)
674
+ return ac
aloha-devel/robomimic/algo/bc.py ADDED
@@ -0,0 +1,899 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Implementation of Behavioral Cloning (BC).
3
+ """
4
+ from collections import OrderedDict
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+ import torch.distributions as D
10
+
11
+ import robomimic.models.base_nets as BaseNets
12
+ import robomimic.models.obs_nets as ObsNets
13
+ import robomimic.models.policy_nets as PolicyNets
14
+ import robomimic.models.vae_nets as VAENets
15
+ import robomimic.utils.loss_utils as LossUtils
16
+ import robomimic.utils.tensor_utils as TensorUtils
17
+ import robomimic.utils.torch_utils as TorchUtils
18
+ import robomimic.utils.obs_utils as ObsUtils
19
+
20
+ from robomimic.algo import register_algo_factory_func, PolicyAlgo
21
+
22
+
23
+ @register_algo_factory_func("bc")
24
+ def algo_config_to_class(algo_config):
25
+ """
26
+ Maps algo config to the BC algo class to instantiate, along with additional algo kwargs.
27
+
28
+ Args:
29
+ algo_config (Config instance): algo config
30
+
31
+ Returns:
32
+ algo_class: subclass of Algo
33
+ algo_kwargs (dict): dictionary of additional kwargs to pass to algorithm
34
+ """
35
+
36
+ # note: we need the check below because some configs import BCConfig and exclude
37
+ # some of these options
38
+ gaussian_enabled = ("gaussian" in algo_config and algo_config.gaussian.enabled)
39
+ gmm_enabled = ("gmm" in algo_config and algo_config.gmm.enabled)
40
+ vae_enabled = ("vae" in algo_config and algo_config.vae.enabled)
41
+
42
+ rnn_enabled = algo_config.rnn.enabled
43
+ transformer_enabled = algo_config.transformer.enabled
44
+
45
+ if gaussian_enabled:
46
+ if rnn_enabled:
47
+ raise NotImplementedError
48
+ elif transformer_enabled:
49
+ raise NotImplementedError
50
+ else:
51
+ algo_class, algo_kwargs = BC_Gaussian, {}
52
+ elif gmm_enabled:
53
+ if rnn_enabled:
54
+ algo_class, algo_kwargs = BC_RNN_GMM, {}
55
+ elif transformer_enabled:
56
+ algo_class, algo_kwargs = BC_Transformer_GMM, {}
57
+ else:
58
+ algo_class, algo_kwargs = BC_GMM, {}
59
+ elif vae_enabled:
60
+ if rnn_enabled:
61
+ raise NotImplementedError
62
+ elif transformer_enabled:
63
+ raise NotImplementedError
64
+ else:
65
+ algo_class, algo_kwargs = BC_VAE, {}
66
+ else:
67
+ if rnn_enabled:
68
+ algo_class, algo_kwargs = BC_RNN, {}
69
+ elif transformer_enabled:
70
+ algo_class, algo_kwargs = BC_Transformer, {}
71
+ else:
72
+ algo_class, algo_kwargs = BC, {}
73
+
74
+ return algo_class, algo_kwargs
75
+
76
+
77
+ class BC(PolicyAlgo):
78
+ """
79
+ Normal BC training.
80
+ """
81
+ def _create_networks(self):
82
+ """
83
+ Creates networks and places them into @self.nets.
84
+ """
85
+ self.nets = nn.ModuleDict()
86
+ self.nets["policy"] = PolicyNets.ActorNetwork(
87
+ obs_shapes=self.obs_shapes,
88
+ goal_shapes=self.goal_shapes,
89
+ ac_dim=self.ac_dim,
90
+ mlp_layer_dims=self.algo_config.actor_layer_dims,
91
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
92
+ )
93
+ self.nets = self.nets.float().to(self.device)
94
+
95
+ def process_batch_for_training(self, batch):
96
+ """
97
+ Processes input batch from a data loader to filter out
98
+ relevant information and prepare the batch for training.
99
+
100
+ Args:
101
+ batch (dict): dictionary with torch.Tensors sampled
102
+ from a data loader
103
+
104
+ Returns:
105
+ input_batch (dict): processed and filtered batch that
106
+ will be used for training
107
+ """
108
+ input_batch = dict()
109
+ input_batch["obs"] = {k: batch["obs"][k][:, 0, :] for k in batch["obs"]}
110
+ input_batch["goal_obs"] = batch.get("goal_obs", None) # goals may not be present
111
+ input_batch["actions"] = batch["actions"][:, 0, :]
112
+ # we move to device first before float conversion because image observation modalities will be uint8 -
113
+ # this minimizes the amount of data transferred to GPU
114
+ return TensorUtils.to_float(TensorUtils.to_device(input_batch, self.device))
115
+
116
+
117
+ def train_on_batch(self, batch, epoch, validate=False):
118
+ """
119
+ Training on a single batch of data.
120
+
121
+ Args:
122
+ batch (dict): dictionary with torch.Tensors sampled
123
+ from a data loader and filtered by @process_batch_for_training
124
+
125
+ epoch (int): epoch number - required by some Algos that need
126
+ to perform staged training and early stopping
127
+
128
+ validate (bool): if True, don't perform any learning updates.
129
+
130
+ Returns:
131
+ info (dict): dictionary of relevant inputs, outputs, and losses
132
+ that might be relevant for logging
133
+ """
134
+ with TorchUtils.maybe_no_grad(no_grad=validate):
135
+ info = super(BC, self).train_on_batch(batch, epoch, validate=validate)
136
+ predictions = self._forward_training(batch)
137
+ losses = self._compute_losses(predictions, batch)
138
+
139
+ info["predictions"] = TensorUtils.detach(predictions)
140
+ info["losses"] = TensorUtils.detach(losses)
141
+
142
+ if not validate:
143
+ step_info = self._train_step(losses)
144
+ info.update(step_info)
145
+
146
+ return info
147
+
148
+ def _forward_training(self, batch):
149
+ """
150
+ Internal helper function for BC algo class. Compute forward pass
151
+ and return network outputs in @predictions dict.
152
+
153
+ Args:
154
+ batch (dict): dictionary with torch.Tensors sampled
155
+ from a data loader and filtered by @process_batch_for_training
156
+
157
+ Returns:
158
+ predictions (dict): dictionary containing network outputs
159
+ """
160
+ predictions = OrderedDict()
161
+ actions = self.nets["policy"](obs_dict=batch["obs"], goal_dict=batch["goal_obs"])
162
+ predictions["actions"] = actions
163
+ return predictions
164
+
165
+ def _compute_losses(self, predictions, batch):
166
+ """
167
+ Internal helper function for BC algo class. Compute losses based on
168
+ network outputs in @predictions dict, using reference labels in @batch.
169
+
170
+ Args:
171
+ predictions (dict): dictionary containing network outputs, from @_forward_training
172
+ batch (dict): dictionary with torch.Tensors sampled
173
+ from a data loader and filtered by @process_batch_for_training
174
+
175
+ Returns:
176
+ losses (dict): dictionary of losses computed over the batch
177
+ """
178
+ losses = OrderedDict()
179
+ a_target = batch["actions"]
180
+ actions = predictions["actions"]
181
+ losses["l2_loss"] = nn.MSELoss()(actions, a_target)
182
+ losses["l1_loss"] = nn.SmoothL1Loss()(actions, a_target)
183
+ # cosine direction loss on eef delta position
184
+ losses["cos_loss"] = LossUtils.cosine_loss(actions[..., :3], a_target[..., :3])
185
+
186
+ action_losses = [
187
+ self.algo_config.loss.l2_weight * losses["l2_loss"],
188
+ self.algo_config.loss.l1_weight * losses["l1_loss"],
189
+ self.algo_config.loss.cos_weight * losses["cos_loss"],
190
+ ]
191
+ action_loss = sum(action_losses)
192
+ losses["action_loss"] = action_loss
193
+ return losses
194
+
195
+ def _train_step(self, losses):
196
+ """
197
+ Internal helper function for BC algo class. Perform backpropagation on the
198
+ loss tensors in @losses to update networks.
199
+
200
+ Args:
201
+ losses (dict): dictionary of losses computed over the batch, from @_compute_losses
202
+ """
203
+
204
+ # gradient step
205
+ info = OrderedDict()
206
+ policy_grad_norms = TorchUtils.backprop_for_loss(
207
+ net=self.nets["policy"],
208
+ optim=self.optimizers["policy"],
209
+ loss=losses["action_loss"],
210
+ max_grad_norm=self.global_config.train.max_grad_norm,
211
+ )
212
+ info["policy_grad_norms"] = policy_grad_norms
213
+ return info
214
+
215
+ def log_info(self, info):
216
+ """
217
+ Process info dictionary from @train_on_batch to summarize
218
+ information to pass to tensorboard for logging.
219
+
220
+ Args:
221
+ info (dict): dictionary of info
222
+
223
+ Returns:
224
+ loss_log (dict): name -> summary statistic
225
+ """
226
+ log = super(BC, self).log_info(info)
227
+ log["Loss"] = info["losses"]["action_loss"].item()
228
+ if "l2_loss" in info["losses"]:
229
+ log["L2_Loss"] = info["losses"]["l2_loss"].item()
230
+ if "l1_loss" in info["losses"]:
231
+ log["L1_Loss"] = info["losses"]["l1_loss"].item()
232
+ if "cos_loss" in info["losses"]:
233
+ log["Cosine_Loss"] = info["losses"]["cos_loss"].item()
234
+ if "policy_grad_norms" in info:
235
+ log["Policy_Grad_Norms"] = info["policy_grad_norms"]
236
+ return log
237
+
238
+ def get_action(self, obs_dict, goal_dict=None):
239
+ """
240
+ Get policy action outputs.
241
+
242
+ Args:
243
+ obs_dict (dict): current observation
244
+ goal_dict (dict): (optional) goal
245
+
246
+ Returns:
247
+ action (torch.Tensor): action tensor
248
+ """
249
+ assert not self.nets.training
250
+ return self.nets["policy"](obs_dict, goal_dict=goal_dict)
251
+
252
+
253
+ class BC_Gaussian(BC):
254
+ """
255
+ BC training with a Gaussian policy.
256
+ """
257
+ def _create_networks(self):
258
+ """
259
+ Creates networks and places them into @self.nets.
260
+ """
261
+ assert self.algo_config.gaussian.enabled
262
+
263
+ self.nets = nn.ModuleDict()
264
+ self.nets["policy"] = PolicyNets.GaussianActorNetwork(
265
+ obs_shapes=self.obs_shapes,
266
+ goal_shapes=self.goal_shapes,
267
+ ac_dim=self.ac_dim,
268
+ mlp_layer_dims=self.algo_config.actor_layer_dims,
269
+ fixed_std=self.algo_config.gaussian.fixed_std,
270
+ init_std=self.algo_config.gaussian.init_std,
271
+ std_limits=(self.algo_config.gaussian.min_std, 7.5),
272
+ std_activation=self.algo_config.gaussian.std_activation,
273
+ low_noise_eval=self.algo_config.gaussian.low_noise_eval,
274
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
275
+ )
276
+
277
+ self.nets = self.nets.float().to(self.device)
278
+
279
+ def _forward_training(self, batch):
280
+ """
281
+ Internal helper function for BC algo class. Compute forward pass
282
+ and return network outputs in @predictions dict.
283
+
284
+ Args:
285
+ batch (dict): dictionary with torch.Tensors sampled
286
+ from a data loader and filtered by @process_batch_for_training
287
+
288
+ Returns:
289
+ predictions (dict): dictionary containing network outputs
290
+ """
291
+ dists = self.nets["policy"].forward_train(
292
+ obs_dict=batch["obs"],
293
+ goal_dict=batch["goal_obs"],
294
+ )
295
+
296
+ # make sure that this is a batch of multivariate action distributions, so that
297
+ # the log probability computation will be correct
298
+ assert len(dists.batch_shape) == 1
299
+ log_probs = dists.log_prob(batch["actions"])
300
+
301
+ predictions = OrderedDict(
302
+ log_probs=log_probs,
303
+ )
304
+ return predictions
305
+
306
+ def _compute_losses(self, predictions, batch):
307
+ """
308
+ Internal helper function for BC algo class. Compute losses based on
309
+ network outputs in @predictions dict, using reference labels in @batch.
310
+
311
+ Args:
312
+ predictions (dict): dictionary containing network outputs, from @_forward_training
313
+ batch (dict): dictionary with torch.Tensors sampled
314
+ from a data loader and filtered by @process_batch_for_training
315
+
316
+ Returns:
317
+ losses (dict): dictionary of losses computed over the batch
318
+ """
319
+
320
+ # loss is just negative log-likelihood of action targets
321
+ action_loss = -predictions["log_probs"].mean()
322
+ return OrderedDict(
323
+ log_probs=-action_loss,
324
+ action_loss=action_loss,
325
+ )
326
+
327
+ def log_info(self, info):
328
+ """
329
+ Process info dictionary from @train_on_batch to summarize
330
+ information to pass to tensorboard for logging.
331
+
332
+ Args:
333
+ info (dict): dictionary of info
334
+
335
+ Returns:
336
+ loss_log (dict): name -> summary statistic
337
+ """
338
+ log = PolicyAlgo.log_info(self, info)
339
+ log["Loss"] = info["losses"]["action_loss"].item()
340
+ log["Log_Likelihood"] = info["losses"]["log_probs"].item()
341
+ if "policy_grad_norms" in info:
342
+ log["Policy_Grad_Norms"] = info["policy_grad_norms"]
343
+ return log
344
+
345
+
346
+ class BC_GMM(BC_Gaussian):
347
+ """
348
+ BC training with a Gaussian Mixture Model policy.
349
+ """
350
+ def _create_networks(self):
351
+ """
352
+ Creates networks and places them into @self.nets.
353
+ """
354
+ assert self.algo_config.gmm.enabled
355
+
356
+ self.nets = nn.ModuleDict()
357
+ self.nets["policy"] = PolicyNets.GMMActorNetwork(
358
+ obs_shapes=self.obs_shapes,
359
+ goal_shapes=self.goal_shapes,
360
+ ac_dim=self.ac_dim,
361
+ mlp_layer_dims=self.algo_config.actor_layer_dims,
362
+ num_modes=self.algo_config.gmm.num_modes,
363
+ min_std=self.algo_config.gmm.min_std,
364
+ std_activation=self.algo_config.gmm.std_activation,
365
+ low_noise_eval=self.algo_config.gmm.low_noise_eval,
366
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
367
+ )
368
+
369
+ self.nets = self.nets.float().to(self.device)
370
+
371
+
372
+ class BC_VAE(BC):
373
+ """
374
+ BC training with a VAE policy.
375
+ """
376
+ def _create_networks(self):
377
+ """
378
+ Creates networks and places them into @self.nets.
379
+ """
380
+ self.nets = nn.ModuleDict()
381
+ self.nets["policy"] = PolicyNets.VAEActor(
382
+ obs_shapes=self.obs_shapes,
383
+ goal_shapes=self.goal_shapes,
384
+ ac_dim=self.ac_dim,
385
+ device=self.device,
386
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
387
+ **VAENets.vae_args_from_config(self.algo_config.vae),
388
+ )
389
+
390
+ self.nets = self.nets.float().to(self.device)
391
+
392
+ def train_on_batch(self, batch, epoch, validate=False):
393
+ """
394
+ Update from superclass to set categorical temperature, for categorical VAEs.
395
+ """
396
+ if self.algo_config.vae.prior.use_categorical:
397
+ temperature = self.algo_config.vae.prior.categorical_init_temp - epoch * self.algo_config.vae.prior.categorical_temp_anneal_step
398
+ temperature = max(temperature, self.algo_config.vae.prior.categorical_min_temp)
399
+ self.nets["policy"].set_gumbel_temperature(temperature)
400
+ return super(BC_VAE, self).train_on_batch(batch, epoch, validate=validate)
401
+
402
+ def _forward_training(self, batch):
403
+ """
404
+ Internal helper function for BC algo class. Compute forward pass
405
+ and return network outputs in @predictions dict.
406
+
407
+ Args:
408
+ batch (dict): dictionary with torch.Tensors sampled
409
+ from a data loader and filtered by @process_batch_for_training
410
+
411
+ Returns:
412
+ predictions (dict): dictionary containing network outputs
413
+ """
414
+ vae_inputs = dict(
415
+ actions=batch["actions"],
416
+ obs_dict=batch["obs"],
417
+ goal_dict=batch["goal_obs"],
418
+ freeze_encoder=batch.get("freeze_encoder", False),
419
+ )
420
+
421
+ vae_outputs = self.nets["policy"].forward_train(**vae_inputs)
422
+ predictions = OrderedDict(
423
+ actions=vae_outputs["decoder_outputs"],
424
+ kl_loss=vae_outputs["kl_loss"],
425
+ reconstruction_loss=vae_outputs["reconstruction_loss"],
426
+ encoder_z=vae_outputs["encoder_z"],
427
+ )
428
+ if not self.algo_config.vae.prior.use_categorical:
429
+ with torch.no_grad():
430
+ encoder_variance = torch.exp(vae_outputs["encoder_params"]["logvar"])
431
+ predictions["encoder_variance"] = encoder_variance
432
+ return predictions
433
+
434
+ def _compute_losses(self, predictions, batch):
435
+ """
436
+ Internal helper function for BC algo class. Compute losses based on
437
+ network outputs in @predictions dict, using reference labels in @batch.
438
+
439
+ Args:
440
+ predictions (dict): dictionary containing network outputs, from @_forward_training
441
+ batch (dict): dictionary with torch.Tensors sampled
442
+ from a data loader and filtered by @process_batch_for_training
443
+
444
+ Returns:
445
+ losses (dict): dictionary of losses computed over the batch
446
+ """
447
+
448
+ # total loss is sum of reconstruction and KL, weighted by beta
449
+ kl_loss = predictions["kl_loss"]
450
+ recons_loss = predictions["reconstruction_loss"]
451
+ action_loss = recons_loss + self.algo_config.vae.kl_weight * kl_loss
452
+ return OrderedDict(
453
+ recons_loss=recons_loss,
454
+ kl_loss=kl_loss,
455
+ action_loss=action_loss,
456
+ )
457
+
458
+ def log_info(self, info):
459
+ """
460
+ Process info dictionary from @train_on_batch to summarize
461
+ information to pass to tensorboard for logging.
462
+
463
+ Args:
464
+ info (dict): dictionary of info
465
+
466
+ Returns:
467
+ loss_log (dict): name -> summary statistic
468
+ """
469
+ log = PolicyAlgo.log_info(self, info)
470
+ log["Loss"] = info["losses"]["action_loss"].item()
471
+ log["KL_Loss"] = info["losses"]["kl_loss"].item()
472
+ log["Reconstruction_Loss"] = info["losses"]["recons_loss"].item()
473
+ if self.algo_config.vae.prior.use_categorical:
474
+ log["Gumbel_Temperature"] = self.nets["policy"].get_gumbel_temperature()
475
+ else:
476
+ log["Encoder_Variance"] = info["predictions"]["encoder_variance"].mean().item()
477
+ if "policy_grad_norms" in info:
478
+ log["Policy_Grad_Norms"] = info["policy_grad_norms"]
479
+ return log
480
+
481
+
482
+ class BC_RNN(BC):
483
+ """
484
+ BC training with an RNN policy.
485
+ """
486
+ def _create_networks(self):
487
+ """
488
+ Creates networks and places them into @self.nets.
489
+ """
490
+ self.nets = nn.ModuleDict()
491
+ self.nets["policy"] = PolicyNets.RNNActorNetwork(
492
+ obs_shapes=self.obs_shapes,
493
+ goal_shapes=self.goal_shapes,
494
+ ac_dim=self.ac_dim,
495
+ mlp_layer_dims=self.algo_config.actor_layer_dims,
496
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
497
+ **BaseNets.rnn_args_from_config(self.algo_config.rnn),
498
+ )
499
+
500
+ self._rnn_hidden_state = None
501
+ self._rnn_horizon = self.algo_config.rnn.horizon
502
+ self._rnn_counter = 0
503
+ self._rnn_is_open_loop = self.algo_config.rnn.get("open_loop", False)
504
+
505
+ self.nets = self.nets.float().to(self.device)
506
+
507
+ def process_batch_for_training(self, batch):
508
+ """
509
+ Processes input batch from a data loader to filter out
510
+ relevant information and prepare the batch for training.
511
+
512
+ Args:
513
+ batch (dict): dictionary with torch.Tensors sampled
514
+ from a data loader
515
+
516
+ Returns:
517
+ input_batch (dict): processed and filtered batch that
518
+ will be used for training
519
+ """
520
+ input_batch = dict()
521
+ input_batch["obs"] = batch["obs"]
522
+ input_batch["goal_obs"] = batch.get("goal_obs", None) # goals may not be present
523
+ input_batch["actions"] = batch["actions"]
524
+
525
+ if self._rnn_is_open_loop:
526
+ # replace the observation sequence with one that only consists of the first observation.
527
+ # This way, all actions are predicted "open-loop" after the first observation, based
528
+ # on the rnn hidden state.
529
+ n_steps = batch["actions"].shape[1]
530
+ obs_seq_start = TensorUtils.index_at_time(batch["obs"], ind=0)
531
+ input_batch["obs"] = TensorUtils.unsqueeze_expand_at(obs_seq_start, size=n_steps, dim=1)
532
+
533
+ # we move to device first before float conversion because image observation modalities will be uint8 -
534
+ # this minimizes the amount of data transferred to GPU
535
+ return TensorUtils.to_float(TensorUtils.to_device(input_batch, self.device))
536
+
537
+ def get_action(self, obs_dict, goal_dict=None):
538
+ """
539
+ Get policy action outputs.
540
+
541
+ Args:
542
+ obs_dict (dict): current observation
543
+ goal_dict (dict): (optional) goal
544
+
545
+ Returns:
546
+ action (torch.Tensor): action tensor
547
+ """
548
+ assert not self.nets.training
549
+
550
+ if self._rnn_hidden_state is None or self._rnn_counter % self._rnn_horizon == 0:
551
+ batch_size = list(obs_dict.values())[0].shape[0]
552
+ self._rnn_hidden_state = self.nets["policy"].get_rnn_init_state(batch_size=batch_size, device=self.device)
553
+
554
+ if self._rnn_is_open_loop:
555
+ # remember the initial observation, and use it instead of the current observation
556
+ # for open-loop action sequence prediction
557
+ self._open_loop_obs = TensorUtils.clone(TensorUtils.detach(obs_dict))
558
+
559
+ obs_to_use = obs_dict
560
+ if self._rnn_is_open_loop:
561
+ # replace current obs with last recorded obs
562
+ obs_to_use = self._open_loop_obs
563
+
564
+ self._rnn_counter += 1
565
+ action, self._rnn_hidden_state = self.nets["policy"].forward_step(
566
+ obs_to_use, goal_dict=goal_dict, rnn_state=self._rnn_hidden_state)
567
+ return action
568
+
569
+ def reset(self):
570
+ """
571
+ Reset algo state to prepare for environment rollouts.
572
+ """
573
+ self._rnn_hidden_state = None
574
+ self._rnn_counter = 0
575
+
576
+
577
+ class BC_RNN_GMM(BC_RNN):
578
+ """
579
+ BC training with an RNN GMM policy.
580
+ """
581
+ def _create_networks(self):
582
+ """
583
+ Creates networks and places them into @self.nets.
584
+ """
585
+ assert self.algo_config.gmm.enabled
586
+ assert self.algo_config.rnn.enabled
587
+
588
+ self.nets = nn.ModuleDict()
589
+ self.nets["policy"] = PolicyNets.RNNGMMActorNetwork(
590
+ obs_shapes=self.obs_shapes,
591
+ goal_shapes=self.goal_shapes,
592
+ ac_dim=self.ac_dim,
593
+ mlp_layer_dims=self.algo_config.actor_layer_dims,
594
+ num_modes=self.algo_config.gmm.num_modes,
595
+ min_std=self.algo_config.gmm.min_std,
596
+ std_activation=self.algo_config.gmm.std_activation,
597
+ low_noise_eval=self.algo_config.gmm.low_noise_eval,
598
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
599
+ **BaseNets.rnn_args_from_config(self.algo_config.rnn),
600
+ )
601
+
602
+ self._rnn_hidden_state = None
603
+ self._rnn_horizon = self.algo_config.rnn.horizon
604
+ self._rnn_counter = 0
605
+ self._rnn_is_open_loop = self.algo_config.rnn.get("open_loop", False)
606
+
607
+ self.nets = self.nets.float().to(self.device)
608
+
609
+ def _forward_training(self, batch):
610
+ """
611
+ Internal helper function for BC algo class. Compute forward pass
612
+ and return network outputs in @predictions dict.
613
+
614
+ Args:
615
+ batch (dict): dictionary with torch.Tensors sampled
616
+ from a data loader and filtered by @process_batch_for_training
617
+
618
+ Returns:
619
+ predictions (dict): dictionary containing network outputs
620
+ """
621
+ dists = self.nets["policy"].forward_train(
622
+ obs_dict=batch["obs"],
623
+ goal_dict=batch["goal_obs"],
624
+ )
625
+
626
+ # make sure that this is a batch of multivariate action distributions, so that
627
+ # the log probability computation will be correct
628
+ assert len(dists.batch_shape) == 2 # [B, T]
629
+ log_probs = dists.log_prob(batch["actions"])
630
+
631
+ predictions = OrderedDict(
632
+ log_probs=log_probs,
633
+ )
634
+ return predictions
635
+
636
+ def _compute_losses(self, predictions, batch):
637
+ """
638
+ Internal helper function for BC algo class. Compute losses based on
639
+ network outputs in @predictions dict, using reference labels in @batch.
640
+
641
+ Args:
642
+ predictions (dict): dictionary containing network outputs, from @_forward_training
643
+ batch (dict): dictionary with torch.Tensors sampled
644
+ from a data loader and filtered by @process_batch_for_training
645
+
646
+ Returns:
647
+ losses (dict): dictionary of losses computed over the batch
648
+ """
649
+
650
+ # loss is just negative log-likelihood of action targets
651
+ action_loss = -predictions["log_probs"].mean()
652
+ return OrderedDict(
653
+ log_probs=-action_loss,
654
+ action_loss=action_loss,
655
+ )
656
+
657
+ def log_info(self, info):
658
+ """
659
+ Process info dictionary from @train_on_batch to summarize
660
+ information to pass to tensorboard for logging.
661
+
662
+ Args:
663
+ info (dict): dictionary of info
664
+
665
+ Returns:
666
+ loss_log (dict): name -> summary statistic
667
+ """
668
+ log = PolicyAlgo.log_info(self, info)
669
+ log["Loss"] = info["losses"]["action_loss"].item()
670
+ log["Log_Likelihood"] = info["losses"]["log_probs"].item()
671
+ if "policy_grad_norms" in info:
672
+ log["Policy_Grad_Norms"] = info["policy_grad_norms"]
673
+ return log
674
+
675
+
676
+ class BC_Transformer(BC):
677
+ """
678
+ BC training with a Transformer policy.
679
+ """
680
+ def _create_networks(self):
681
+ """
682
+ Creates networks and places them into @self.nets.
683
+ """
684
+ assert self.algo_config.transformer.enabled
685
+
686
+ self.nets = nn.ModuleDict()
687
+ self.nets["policy"] = PolicyNets.TransformerActorNetwork(
688
+ obs_shapes=self.obs_shapes,
689
+ goal_shapes=self.goal_shapes,
690
+ ac_dim=self.ac_dim,
691
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
692
+ **BaseNets.transformer_args_from_config(self.algo_config.transformer),
693
+ )
694
+ self._set_params_from_config()
695
+ self.nets = self.nets.float().to(self.device)
696
+
697
+ def _set_params_from_config(self):
698
+ """
699
+ Read specific config variables we need for training / eval.
700
+ Called by @_create_networks method
701
+ """
702
+ self.context_length = self.algo_config.transformer.context_length
703
+ self.supervise_all_steps = self.algo_config.transformer.supervise_all_steps
704
+ self.pred_future_acs = self.algo_config.transformer.pred_future_acs
705
+ if self.pred_future_acs:
706
+ assert self.supervise_all_steps is True
707
+
708
+ def process_batch_for_training(self, batch):
709
+ """
710
+ Processes input batch from a data loader to filter out
711
+ relevant information and prepare the batch for training.
712
+ Args:
713
+ batch (dict): dictionary with torch.Tensors sampled
714
+ from a data loader
715
+ Returns:
716
+ input_batch (dict): processed and filtered batch that
717
+ will be used for training
718
+ """
719
+ input_batch = dict()
720
+ h = self.context_length
721
+ input_batch["obs"] = {k: batch["obs"][k][:, :h, :] for k in batch["obs"]}
722
+ input_batch["goal_obs"] = batch.get("goal_obs", None) # goals may not be present
723
+
724
+ if self.supervise_all_steps:
725
+ # supervision on entire sequence (instead of just current timestep)
726
+ if self.pred_future_acs:
727
+ ac_start = h - 1
728
+ else:
729
+ ac_start = 0
730
+ input_batch["actions"] = batch["actions"][:, ac_start:ac_start+h, :]
731
+ else:
732
+ # just use current timestep
733
+ input_batch["actions"] = batch["actions"][:, h-1, :]
734
+
735
+ if self.pred_future_acs:
736
+ assert input_batch["actions"].shape[1] == h
737
+
738
+ input_batch = TensorUtils.to_device(TensorUtils.to_float(input_batch), self.device)
739
+ return input_batch
740
+
741
+ def _forward_training(self, batch, epoch=None):
742
+ """
743
+ Internal helper function for BC_Transformer algo class. Compute forward pass
744
+ and return network outputs in @predictions dict.
745
+
746
+ Args:
747
+ batch (dict): dictionary with torch.Tensors sampled
748
+ from a data loader and filtered by @process_batch_for_training
749
+
750
+ Returns:
751
+ predictions (dict): dictionary containing network outputs
752
+ """
753
+ # ensure that transformer context length is consistent with temporal dimension of observations
754
+ TensorUtils.assert_size_at_dim(
755
+ batch["obs"],
756
+ size=(self.context_length),
757
+ dim=1,
758
+ msg="Error: expect temporal dimension of obs batch to match transformer context length {}".format(self.context_length),
759
+ )
760
+
761
+ predictions = OrderedDict()
762
+ predictions["actions"] = self.nets["policy"](obs_dict=batch["obs"], actions=None, goal_dict=batch["goal_obs"])
763
+ if not self.supervise_all_steps:
764
+ # only supervise final timestep
765
+ predictions["actions"] = predictions["actions"][:, -1, :]
766
+ return predictions
767
+
768
+ def get_action(self, obs_dict, goal_dict=None):
769
+ """
770
+ Get policy action outputs.
771
+ Args:
772
+ obs_dict (dict): current observation
773
+ goal_dict (dict): (optional) goal
774
+ Returns:
775
+ action (torch.Tensor): action tensor
776
+ """
777
+ assert not self.nets.training
778
+
779
+ output = self.nets["policy"](obs_dict, actions=None, goal_dict=goal_dict)
780
+
781
+ if self.supervise_all_steps:
782
+ if self.algo_config.transformer.pred_future_acs:
783
+ output = output[:, 0, :]
784
+ else:
785
+ output = output[:, -1, :]
786
+ else:
787
+ output = output[:, -1, :]
788
+
789
+ return output
790
+
791
+
792
+
793
+ class BC_Transformer_GMM(BC_Transformer):
794
+ """
795
+ BC training with a Transformer GMM policy.
796
+ """
797
+ def _create_networks(self):
798
+ """
799
+ Creates networks and places them into @self.nets.
800
+ """
801
+ assert self.algo_config.gmm.enabled
802
+ assert self.algo_config.transformer.enabled
803
+
804
+ if self.algo_config.language_conditioned:
805
+ self.obs_shapes["lang_emb"] = [768] # clip is 768-dim embedding
806
+
807
+ self.nets = nn.ModuleDict()
808
+ self.nets["policy"] = PolicyNets.TransformerGMMActorNetwork(
809
+ obs_shapes=self.obs_shapes,
810
+ goal_shapes=self.goal_shapes,
811
+ ac_dim=self.ac_dim,
812
+ num_modes=self.algo_config.gmm.num_modes,
813
+ min_std=self.algo_config.gmm.min_std,
814
+ std_activation=self.algo_config.gmm.std_activation,
815
+ low_noise_eval=self.algo_config.gmm.low_noise_eval,
816
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
817
+ **BaseNets.transformer_args_from_config(self.algo_config.transformer),
818
+ )
819
+ self._set_params_from_config()
820
+ self.nets = self.nets.float().to(self.device)
821
+
822
+ def _forward_training(self, batch, epoch=None):
823
+ """
824
+ Modify from super class to support GMM training.
825
+ """
826
+ # ensure that transformer context length is consistent with temporal dimension of observations
827
+ TensorUtils.assert_size_at_dim(
828
+ batch["obs"],
829
+ size=(self.context_length),
830
+ dim=1,
831
+ msg="Error: expect temporal dimension of obs batch to match transformer context length {}".format(self.context_length),
832
+ )
833
+
834
+ dists = self.nets["policy"].forward_train(
835
+ obs_dict=batch["obs"],
836
+ actions=None,
837
+ goal_dict=batch["goal_obs"],
838
+ low_noise_eval=False,
839
+ )
840
+
841
+ # make sure that this is a batch of multivariate action distributions, so that
842
+ # the log probability computation will be correct
843
+ assert len(dists.batch_shape) == 2 # [B, T]
844
+
845
+ if not self.supervise_all_steps:
846
+ # only use final timestep prediction by making a new distribution with only final timestep.
847
+ # This essentially does `dists = dists[:, -1]`
848
+ component_distribution = D.Normal(
849
+ loc=dists.component_distribution.base_dist.loc[:, -1],
850
+ scale=dists.component_distribution.base_dist.scale[:, -1],
851
+ )
852
+ component_distribution = D.Independent(component_distribution, 1)
853
+ mixture_distribution = D.Categorical(logits=dists.mixture_distribution.logits[:, -1])
854
+ dists = D.MixtureSameFamily(
855
+ mixture_distribution=mixture_distribution,
856
+ component_distribution=component_distribution,
857
+ )
858
+
859
+ log_probs = dists.log_prob(batch["actions"])
860
+
861
+ predictions = OrderedDict(
862
+ log_probs=log_probs,
863
+ )
864
+ return predictions
865
+
866
+ def _compute_losses(self, predictions, batch):
867
+ """
868
+ Internal helper function for BC_Transformer_GMM algo class. Compute losses based on
869
+ network outputs in @predictions dict, using reference labels in @batch.
870
+ Args:
871
+ predictions (dict): dictionary containing network outputs, from @_forward_training
872
+ batch (dict): dictionary with torch.Tensors sampled
873
+ from a data loader and filtered by @process_batch_for_training
874
+ Returns:
875
+ losses (dict): dictionary of losses computed over the batch
876
+ """
877
+
878
+ # loss is just negative log-likelihood of action targets
879
+ action_loss = -predictions["log_probs"].mean()
880
+ return OrderedDict(
881
+ log_probs=-action_loss,
882
+ action_loss=action_loss,
883
+ )
884
+
885
+ def log_info(self, info):
886
+ """
887
+ Process info dictionary from @train_on_batch to summarize
888
+ information to pass to tensorboard for logging.
889
+ Args:
890
+ info (dict): dictionary of info
891
+ Returns:
892
+ loss_log (dict): name -> summary statistic
893
+ """
894
+ log = PolicyAlgo.log_info(self, info)
895
+ log["Loss"] = info["losses"]["action_loss"].item()
896
+ log["Log_Likelihood"] = info["losses"]["log_probs"].item()
897
+ if "policy_grad_norms" in info:
898
+ log["Policy_Grad_Norms"] = info["policy_grad_norms"]
899
+ return log
aloha-devel/robomimic/algo/bcq.py ADDED
@@ -0,0 +1,1022 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Batch-Constrained Q-Learning (BCQ), with support for more general
3
+ generative action models (the original paper uses a cVAE).
4
+ (Paper - https://arxiv.org/abs/1812.02900).
5
+ """
6
+ from collections import OrderedDict
7
+
8
+ import torch
9
+ import torch.nn as nn
10
+ import torch.nn.functional as F
11
+
12
+ import robomimic.models.obs_nets as ObsNets
13
+ import robomimic.models.policy_nets as PolicyNets
14
+ import robomimic.models.value_nets as ValueNets
15
+ import robomimic.models.vae_nets as VAENets
16
+ import robomimic.utils.tensor_utils as TensorUtils
17
+ import robomimic.utils.torch_utils as TorchUtils
18
+ import robomimic.utils.obs_utils as ObsUtils
19
+ import robomimic.utils.loss_utils as LossUtils
20
+
21
+ from robomimic.algo import register_algo_factory_func, PolicyAlgo, ValueAlgo
22
+
23
+
24
+ @register_algo_factory_func("bcq")
25
+ def algo_config_to_class(algo_config):
26
+ """
27
+ Maps algo config to the BCQ algo class to instantiate, along with additional algo kwargs.
28
+
29
+ Args:
30
+ algo_config (Config instance): algo config
31
+
32
+ Returns:
33
+ algo_class: subclass of Algo
34
+ algo_kwargs (dict): dictionary of additional kwargs to pass to algorithm
35
+ """
36
+ if algo_config.critic.distributional.enabled:
37
+ return BCQ_Distributional, {}
38
+ if algo_config.action_sampler.gmm.enabled:
39
+ return BCQ_GMM, {}
40
+ assert algo_config.action_sampler.vae.enabled
41
+ return BCQ, {}
42
+
43
+
44
+ class BCQ(PolicyAlgo, ValueAlgo):
45
+ """
46
+ Default BCQ training, based on https://arxiv.org/abs/1812.02900 and
47
+ https://github.com/sfujim/BCQ
48
+ """
49
+ def __init__(self, **kwargs):
50
+ PolicyAlgo.__init__(self, **kwargs)
51
+
52
+ # save the discount factor - it may be overriden later
53
+ self.set_discount(self.algo_config.discount)
54
+
55
+ def _create_networks(self):
56
+ """
57
+ Creates networks and places them into @self.nets.
58
+ """
59
+ self.nets = nn.ModuleDict()
60
+
61
+ self._create_critics()
62
+ self._create_action_sampler()
63
+ if self.algo_config.actor.enabled:
64
+ self._create_actor()
65
+
66
+ # sync target networks at beginning of training
67
+ with torch.no_grad():
68
+ for critic_ind in range(len(self.nets["critic"])):
69
+ TorchUtils.hard_update(
70
+ source=self.nets["critic"][critic_ind],
71
+ target=self.nets["critic_target"][critic_ind],
72
+ )
73
+
74
+ if self.algo_config.actor.enabled:
75
+ TorchUtils.hard_update(
76
+ source=self.nets["actor"],
77
+ target=self.nets["actor_target"],
78
+ )
79
+
80
+ self.nets = self.nets.float().to(self.device)
81
+
82
+ def _create_critics(self):
83
+ """
84
+ Called in @_create_networks to make critic networks.
85
+ """
86
+ critic_class = ValueNets.ActionValueNetwork
87
+ critic_args = dict(
88
+ obs_shapes=self.obs_shapes,
89
+ ac_dim=self.ac_dim,
90
+ mlp_layer_dims=self.algo_config.critic.layer_dims,
91
+ value_bounds=self.algo_config.critic.value_bounds,
92
+ goal_shapes=self.goal_shapes,
93
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
94
+ )
95
+
96
+ # Q network ensemble and target ensemble
97
+ self.nets["critic"] = nn.ModuleList()
98
+ self.nets["critic_target"] = nn.ModuleList()
99
+ for _ in range(self.algo_config.critic.ensemble.n):
100
+ critic = critic_class(**critic_args)
101
+ self.nets["critic"].append(critic)
102
+
103
+ critic_target = critic_class(**critic_args)
104
+ self.nets["critic_target"].append(critic_target)
105
+
106
+ def _create_action_sampler(self):
107
+ """
108
+ Called in @_create_networks to make action sampler network.
109
+ """
110
+
111
+ # VAE network for approximate sampling from batch dataset
112
+ assert self.algo_config.action_sampler.vae.enabled
113
+ self.nets["action_sampler"] = PolicyNets.VAEActor(
114
+ obs_shapes=self.obs_shapes,
115
+ ac_dim=self.ac_dim,
116
+ device=self.device,
117
+ goal_shapes=self.goal_shapes,
118
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
119
+ **VAENets.vae_args_from_config(self.algo_config.action_sampler.vae),
120
+ )
121
+
122
+ def _create_actor(self):
123
+ """
124
+ Called in @_create_networks to make actor network.
125
+ """
126
+ assert self.algo_config.actor.enabled
127
+ actor_class = PolicyNets.PerturbationActorNetwork
128
+ actor_args = dict(
129
+ obs_shapes=self.obs_shapes,
130
+ goal_shapes=self.goal_shapes,
131
+ ac_dim=self.ac_dim,
132
+ mlp_layer_dims=self.algo_config.actor.layer_dims,
133
+ perturbation_scale=self.algo_config.actor.perturbation_scale,
134
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
135
+ )
136
+
137
+ self.nets["actor"] = actor_class(**actor_args)
138
+ self.nets["actor_target"] = actor_class(**actor_args)
139
+
140
+ def _check_epoch(self, net_name, epoch):
141
+ """
142
+ Helper function to check whether backprop should happen this epoch.
143
+
144
+ Args:
145
+ net_name (str): name of network in @self.nets and @self.optim_params
146
+ epoch (int): epoch number
147
+ """
148
+ epoch_start_check = (self.optim_params[net_name]["start_epoch"] == -1) or (epoch >= self.optim_params[net_name]["start_epoch"])
149
+ epoch_end_check = (self.optim_params[net_name]["end_epoch"] == -1) or (epoch < self.optim_params[net_name]["end_epoch"])
150
+ return (epoch_start_check and epoch_end_check)
151
+
152
+ def set_discount(self, discount):
153
+ """
154
+ Useful function to modify discount factor if necessary (e.g. for n-step returns).
155
+ """
156
+ self.discount = discount
157
+
158
+ def process_batch_for_training(self, batch):
159
+ """
160
+ Processes input batch from a data loader to filter out
161
+ relevant information and prepare the batch for training.
162
+
163
+ Args:
164
+ batch (dict): dictionary with torch.Tensors sampled
165
+ from a data loader
166
+
167
+ Returns:
168
+ input_batch (dict): processed and filtered batch that
169
+ will be used for training
170
+ """
171
+ input_batch = dict()
172
+
173
+ # n-step returns (default is 1)
174
+ n_step = self.algo_config.n_step
175
+ assert batch["actions"].shape[1] >= n_step
176
+
177
+ # remove temporal batches for all
178
+ input_batch["obs"] = {k: batch["obs"][k][:, 0, :] for k in batch["obs"]}
179
+ input_batch["next_obs"] = {k: batch["next_obs"][k][:, n_step - 1, :] for k in batch["next_obs"]}
180
+ input_batch["goal_obs"] = batch.get("goal_obs", None) # goals may not be present
181
+ input_batch["actions"] = batch["actions"][:, 0, :]
182
+
183
+ # note: ensure scalar signals (rewards, done) retain last dimension of 1 to be compatible with model outputs
184
+
185
+ # single timestep reward is discounted sum of intermediate rewards in sequence
186
+ reward_seq = batch["rewards"][:, :n_step]
187
+ discounts = torch.pow(self.algo_config.discount, torch.arange(n_step).float()).unsqueeze(0)
188
+ input_batch["rewards"] = (reward_seq * discounts).sum(dim=1).unsqueeze(1)
189
+
190
+ # discount rate will be gamma^N for computing n-step returns
191
+ new_discount = (self.algo_config.discount ** n_step)
192
+ self.set_discount(new_discount)
193
+
194
+ # consider this n-step seqeunce done if any intermediate dones are present
195
+ done_seq = batch["dones"][:, :n_step]
196
+ input_batch["dones"] = (done_seq.sum(dim=1) > 0).float().unsqueeze(1)
197
+
198
+ if self.algo_config.infinite_horizon:
199
+ # scale terminal rewards by 1 / (1 - gamma) for infinite horizon MDPs
200
+ done_inds = input_batch["dones"].round().long().nonzero(as_tuple=False)[:, 0]
201
+ if done_inds.shape[0] > 0:
202
+ input_batch["rewards"][done_inds] = input_batch["rewards"][done_inds] * (1. / (1. - self.discount))
203
+
204
+ # we move to device first before float conversion because image observation modalities will be uint8 -
205
+ # this minimizes the amount of data transferred to GPU
206
+ return TensorUtils.to_float(TensorUtils.to_device(input_batch, self.device))
207
+
208
+ def _train_action_sampler_on_batch(self, batch, epoch, no_backprop=False):
209
+ """
210
+ A modular helper function that can be overridden in case
211
+ subclasses would like to modify training behavior for the
212
+ action sampler.
213
+
214
+ Args:
215
+ batch (dict): dictionary with torch.Tensors sampled
216
+ from a data loader and filtered by @process_batch_for_training
217
+
218
+ epoch (int): epoch number - required by some Algos that need
219
+ to perform staged training and early stopping
220
+
221
+ no_backprop (bool): if True, don't perform any learning updates.
222
+
223
+ Returns:
224
+ info (dict): dictionary of relevant inputs, outputs, and losses
225
+ that might be relevant for logging
226
+ outputs (dict): dictionary of outputs to use during critic training
227
+ (for computing target values)
228
+ """
229
+ info = OrderedDict()
230
+ if self.algo_config.action_sampler.vae.prior.use_categorical:
231
+ temperature = self.algo_config.action_sampler.vae.prior.categorical_init_temp - epoch * self.algo_config.action_sampler.vae.prior.categorical_temp_anneal_step
232
+ temperature = max(temperature, self.algo_config.action_sampler.vae.prior.categorical_min_temp)
233
+ self.nets["action_sampler"].set_gumbel_temperature(temperature)
234
+
235
+ vae_inputs = dict(
236
+ actions=batch["actions"],
237
+ obs_dict=batch["obs"],
238
+ goal_dict=batch["goal_obs"],
239
+ )
240
+
241
+ # maybe freeze encoder weights
242
+ if (self.algo_config.action_sampler.freeze_encoder_epoch != -1) and (epoch >= self.algo_config.action_sampler.freeze_encoder_epoch):
243
+ vae_inputs["freeze_encoder"] = True
244
+
245
+ # VAE forward
246
+ vae_outputs = self.nets["action_sampler"].forward_train(**vae_inputs)
247
+ recons_loss = vae_outputs["reconstruction_loss"]
248
+ kl_loss = vae_outputs["kl_loss"]
249
+ vae_loss = recons_loss + self.algo_config.action_sampler.vae.kl_weight * kl_loss
250
+ info["action_sampler/loss"] = vae_loss
251
+ info["action_sampler/recons_loss"] = recons_loss
252
+ info["action_sampler/kl_loss"] = kl_loss
253
+ if not self.algo_config.action_sampler.vae.prior.use_categorical:
254
+ with torch.no_grad():
255
+ encoder_variance = torch.exp(vae_outputs["encoder_params"]["logvar"]).mean()
256
+ info["action_sampler/encoder_variance"] = encoder_variance
257
+ outputs = TensorUtils.detach(vae_outputs)
258
+
259
+ # VAE gradient step
260
+ if not no_backprop:
261
+ vae_grad_norms = TorchUtils.backprop_for_loss(
262
+ net=self.nets["action_sampler"],
263
+ optim=self.optimizers["action_sampler"],
264
+ loss=vae_loss,
265
+ )
266
+ info["action_sampler/grad_norms"] = vae_grad_norms
267
+ return info, outputs
268
+
269
+ def _train_critic_on_batch(self, batch, action_sampler_outputs, epoch, no_backprop=False):
270
+ """
271
+ A modular helper function that can be overridden in case
272
+ subclasses would like to modify training behavior for the
273
+ critics.
274
+
275
+ Args:
276
+ batch (dict): dictionary with torch.Tensors sampled
277
+ from a data loader and filtered by @process_batch_for_training
278
+
279
+ action_sampler_outputs (dict): dictionary of outputs from the action sampler. Used
280
+ to form target values for training the critic
281
+
282
+ epoch (int): epoch number - required by some Algos that need
283
+ to perform staged training and early stopping
284
+
285
+ no_backprop (bool): if True, don't perform any learning updates.
286
+
287
+ Returns:
288
+ info (dict): dictionary of relevant inputs, outputs, and losses
289
+ that might be relevant for logging
290
+ critic_outputs (dict): dictionary of critic outputs - useful for
291
+ logging purposes
292
+ """
293
+ info = OrderedDict()
294
+
295
+ # batch variables
296
+ s_batch = batch["obs"]
297
+ a_batch = batch["actions"]
298
+ r_batch = batch["rewards"]
299
+ ns_batch = batch["next_obs"]
300
+ goal_s_batch = batch["goal_obs"]
301
+
302
+ # 1 if not done, 0 otherwise
303
+ done_mask_batch = 1. - batch["dones"]
304
+ info["done_masks"] = done_mask_batch
305
+
306
+ # Bellman backup for Q-targets
307
+ q_targets = self._get_target_values(
308
+ next_states=ns_batch,
309
+ goal_states=goal_s_batch,
310
+ rewards=r_batch,
311
+ dones=done_mask_batch,
312
+ action_sampler_outputs=action_sampler_outputs,
313
+ )
314
+ info["critic/q_targets"] = q_targets
315
+
316
+ # Train all critics using this set of targets for regression
317
+ critic_outputs = []
318
+ for critic_ind, critic in enumerate(self.nets["critic"]):
319
+ critic_loss, critic_output = self._compute_critic_loss(
320
+ critic=critic,
321
+ states=s_batch,
322
+ actions=a_batch,
323
+ goal_states=goal_s_batch,
324
+ q_targets=q_targets,
325
+ )
326
+ info["critic/critic{}_loss".format(critic_ind + 1)] = critic_loss
327
+ critic_outputs.append(critic_output)
328
+
329
+ if not no_backprop:
330
+ critic_grad_norms = TorchUtils.backprop_for_loss(
331
+ net=self.nets["critic"][critic_ind],
332
+ optim=self.optimizers["critic"][critic_ind],
333
+ loss=critic_loss,
334
+ max_grad_norm=self.algo_config.critic.max_gradient_norm,
335
+ )
336
+ info["critic/critic{}_grad_norms".format(critic_ind + 1)] = critic_grad_norms
337
+
338
+ return info, critic_outputs
339
+
340
+ def _train_actor_on_batch(self, batch, action_sampler_outputs, critic_outputs, epoch, no_backprop=False):
341
+ """
342
+ A modular helper function that can be overridden in case
343
+ subclasses would like to modify training behavior for the
344
+ perturbation actor.
345
+
346
+ Args:
347
+ batch (dict): dictionary with torch.Tensors sampled
348
+ from a data loader and filtered by @process_batch_for_training
349
+
350
+ action_sampler_outputs (dict): dictionary of outputs from the action sampler. Currently
351
+ unused, although more sophisticated models may use it.
352
+
353
+ critic_outputs (dict): dictionary of outputs from the critic. Currently
354
+ unused, although more sophisticated models may use it.
355
+
356
+ epoch (int): epoch number - required by some Algos that need
357
+ to perform staged training and early stopping
358
+
359
+ no_backprop (bool): if True, don't perform any learning updates.
360
+
361
+ Returns:
362
+ info (dict): dictionary of relevant inputs, outputs, and losses
363
+ that might be relevant for logging
364
+ """
365
+ assert self.algo_config.actor.enabled
366
+
367
+ info = OrderedDict()
368
+
369
+ # Actor loss (update with DDPG loss)
370
+ s_batch = batch["obs"]
371
+ goal_s_batch = batch["goal_obs"]
372
+
373
+ # sample some actions from action sampler and perturb them, then improve perturbations
374
+ # where improvement is measured by the critic
375
+ sampled_actions = self.nets["action_sampler"](s_batch, goal_s_batch).detach() # don't backprop into samples
376
+ perturbed_actions = self.nets["actor"](s_batch, sampled_actions, goal_s_batch)
377
+ actor_loss = -(self.nets["critic"][0](s_batch, perturbed_actions, goal_s_batch)).mean()
378
+ info["actor/loss"] = actor_loss
379
+
380
+ if not no_backprop:
381
+ actor_grad_norms = TorchUtils.backprop_for_loss(
382
+ net=self.nets["actor"],
383
+ optim=self.optimizers["actor"],
384
+ loss=actor_loss,
385
+ )
386
+ info["actor/grad_norms"] = actor_grad_norms
387
+
388
+ return info
389
+
390
+ def _get_target_values(self, next_states, goal_states, rewards, dones, action_sampler_outputs=None):
391
+ """
392
+ Helper function to get target values for training Q-function with TD-loss.
393
+
394
+ Args:
395
+ next_states (dict): batch of next observations
396
+ goal_states (dict): if not None, batch of goal observations
397
+ rewards (torch.Tensor): batch of rewards - should be shape (B, 1)
398
+ dones (torch.Tensor): batch of done signals - should be shape (B, 1)
399
+ action_sampler_outputs (dict): dictionary of outputs from the action sampler. Currently
400
+ unused, although more sophisticated models may use it.
401
+
402
+ Returns:
403
+ q_targets (torch.Tensor): target Q-values to use for TD loss
404
+ """
405
+
406
+ with torch.no_grad():
407
+ # we need to stack the observations with redundancy @num_action_samples here, then decode
408
+ # to get all sampled actions. for example, if we generate 2 samples per observation and
409
+ # the batch size is 3, then ob_tiled = [ob1; ob1; ob2; ob2; ob3; ob3]
410
+ next_states_tiled = ObsUtils.repeat_and_stack_observation(next_states, n=self.algo_config.critic.num_action_samples)
411
+ goal_states_tiled = None
412
+ if len(self.goal_shapes) > 0:
413
+ goal_states_tiled = ObsUtils.repeat_and_stack_observation(goal_states, n=self.algo_config.critic.num_action_samples)
414
+
415
+ # sample action proposals
416
+ next_sampled_actions = self._sample_actions_for_value_maximization(
417
+ states_tiled=next_states_tiled,
418
+ goal_states_tiled=goal_states_tiled,
419
+ for_target_update=True,
420
+ )
421
+
422
+ q_targets = self._get_target_values_from_sampled_actions(
423
+ next_states_tiled=next_states_tiled,
424
+ next_sampled_actions=next_sampled_actions,
425
+ goal_states_tiled=goal_states_tiled,
426
+ rewards=rewards,
427
+ dones=dones,
428
+ )
429
+
430
+ return q_targets
431
+
432
+ def _sample_actions_for_value_maximization(self, states_tiled, goal_states_tiled, for_target_update):
433
+ """
434
+ Helper function to sample actions for maximization (the "batch-constrained" part of
435
+ batch-constrained q-learning).
436
+
437
+ Args:
438
+ states_tiled (dict): observations to use for sampling actions. Assumes that tiling
439
+ has already occurred - so that if the batch size is B, and N samples are
440
+ desired for each observation in the batch, the leading dimension for each
441
+ observation in the dict is B * N
442
+
443
+ goal_states_tiled (dict): if not None, goal observations
444
+
445
+ for_target_update (bool): if True, actions are being sampled for use in training the
446
+ critic - which means the target actor network should be used
447
+
448
+ Returns:
449
+ sampled_actions (torch.Tensor): actions sampled from the action sampler, and maybe
450
+ perturbed by the actor network
451
+ """
452
+
453
+ with torch.no_grad():
454
+ sampled_actions = self.nets["action_sampler"](states_tiled, goal_states_tiled)
455
+ if self.algo_config.actor.enabled:
456
+ actor = self.nets["actor"]
457
+ if for_target_update:
458
+ actor = self.nets["actor_target"]
459
+ # perturb the actions with the policy
460
+ sampled_actions = actor(states_tiled, sampled_actions, goal_states_tiled)
461
+
462
+ return sampled_actions
463
+
464
+ def _get_target_values_from_sampled_actions(self, next_states_tiled, next_sampled_actions, goal_states_tiled, rewards, dones):
465
+ """
466
+ Helper function to get target values for training Q-function with TD-loss. The function
467
+ assumes that action candidates to maximize over have already been computed, and that
468
+ the input states have been tiled (repeated) to be compatible with the sampled actions.
469
+
470
+ Args:
471
+ next_states_tiled (dict): next observations to use for sampling actions. Assumes that
472
+ tiling has already occurred - so that if the batch size is B, and N samples are
473
+ desired for each observation in the batch, the leading dimension for each
474
+ observation in the dict is B * N
475
+
476
+ next_sampled_actions (torch.Tensor): actions sampled from the action sampler. This function
477
+ will maximize the critic over these action candidates (using the TD3 trick)
478
+
479
+ goal_states_tiled (dict): if not None, goal observations
480
+
481
+ rewards (torch.Tensor): batch of rewards - should be shape (B, 1)
482
+
483
+ dones (torch.Tensor): batch of done signals - should be shape (B, 1)
484
+
485
+ Returns:
486
+ q_targets (torch.Tensor): target Q-values to use for TD loss
487
+ """
488
+ with torch.no_grad():
489
+ # feed tiled observations and sampled actions into the critics and then
490
+ # reshape to get all Q-values in second dimension per observation in batch.
491
+ all_value_targets = self.nets["critic_target"][0](next_states_tiled, next_sampled_actions, goal_states_tiled).reshape(
492
+ -1, self.algo_config.critic.num_action_samples)
493
+ max_value_targets = all_value_targets
494
+ min_value_targets = all_value_targets
495
+
496
+ # TD3 trick to combine max and min over all Q-ensemble estimates into single target estimates
497
+ for critic_target in self.nets["critic_target"][1:]:
498
+ all_value_targets = critic_target(next_states_tiled, next_sampled_actions, goal_states_tiled).reshape(
499
+ -1, self.algo_config.critic.num_action_samples)
500
+ max_value_targets = torch.max(max_value_targets, all_value_targets)
501
+ min_value_targets = torch.min(min_value_targets, all_value_targets)
502
+ all_value_targets = self.algo_config.critic.ensemble.weight * min_value_targets + \
503
+ (1. - self.algo_config.critic.ensemble.weight) * max_value_targets
504
+
505
+ # take maximum over all sampled action values per observation and compute targets
506
+ value_targets = torch.max(all_value_targets, dim=1, keepdim=True)[0]
507
+ q_targets = rewards + dones * self.discount * value_targets
508
+
509
+ return q_targets
510
+
511
+ def _compute_critic_loss(self, critic, states, actions, goal_states, q_targets):
512
+ """
513
+ Helper function to compute loss between estimated Q-values and target Q-values.
514
+ It should also return outputs needed for downstream training (for training the
515
+ actor).
516
+
517
+ Args:
518
+ critic (torch.nn.Module): critic network
519
+ states (dict): batch of observations
520
+ actions (torch.Tensor): batch of actions
521
+ goal_states (dict): if not None, batch of goal observations
522
+ q_targets (torch.Tensor): batch of target q-values for the TD loss
523
+
524
+ Returns:
525
+ critic_loss (torch.Tensor): critic loss
526
+ critic_output (dict): additional outputs from the critic. This function
527
+ returns None, but subclasses may want to provide some information
528
+ here.
529
+ """
530
+ q_estimated = critic(states, actions, goal_states)
531
+ if self.algo_config.critic.use_huber:
532
+ critic_loss = nn.SmoothL1Loss()(q_estimated, q_targets)
533
+ else:
534
+ critic_loss = nn.MSELoss()(q_estimated, q_targets)
535
+ return critic_loss, None
536
+
537
+ def train_on_batch(self, batch, epoch, validate=False):
538
+ """
539
+ Training on a single batch of data.
540
+
541
+ Args:
542
+ batch (dict): dictionary with torch.Tensors sampled
543
+ from a data loader and filtered by @process_batch_for_training
544
+
545
+ epoch (int): epoch number - required by some Algos that need
546
+ to perform staged training and early stopping
547
+
548
+ validate (bool): if True, don't perform any learning updates.
549
+
550
+ Returns:
551
+ info (dict): dictionary of relevant inputs, outputs, and losses
552
+ that might be relevant for logging
553
+ """
554
+ with TorchUtils.maybe_no_grad(no_grad=validate):
555
+ info = PolicyAlgo.train_on_batch(self, batch, epoch, validate=validate)
556
+
557
+ # Action Sampler training
558
+ no_action_sampler_backprop = validate or (not self._check_epoch(net_name="action_sampler", epoch=epoch))
559
+ with TorchUtils.maybe_no_grad(no_grad=no_action_sampler_backprop):
560
+ action_sampler_info, action_sampler_outputs = self._train_action_sampler_on_batch(
561
+ batch=batch,
562
+ epoch=epoch,
563
+ no_backprop=no_action_sampler_backprop,
564
+ )
565
+ info.update(action_sampler_info)
566
+
567
+ # make sure action sampler is in eval mode for models like GMM which may require low-noise
568
+ # samples when sampling actions.
569
+ self.nets["action_sampler"].eval()
570
+
571
+ # Critic training
572
+ no_critic_backprop = validate or (not self._check_epoch(net_name="critic", epoch=epoch))
573
+ with TorchUtils.maybe_no_grad(no_grad=no_critic_backprop):
574
+ critic_info, critic_outputs = self._train_critic_on_batch(
575
+ batch=batch,
576
+ action_sampler_outputs=action_sampler_outputs,
577
+ epoch=epoch,
578
+ no_backprop=no_critic_backprop,
579
+ )
580
+ info.update(critic_info)
581
+
582
+ if self.algo_config.actor.enabled:
583
+ # Actor training
584
+ no_actor_backprop = validate or (not self._check_epoch(net_name="actor", epoch=epoch))
585
+ with TorchUtils.maybe_no_grad(no_grad=no_actor_backprop):
586
+ actor_info = self._train_actor_on_batch(
587
+ batch=batch,
588
+ action_sampler_outputs=action_sampler_outputs,
589
+ critic_outputs=critic_outputs,
590
+ epoch=epoch,
591
+ no_backprop=no_actor_backprop,
592
+ )
593
+ info.update(actor_info)
594
+
595
+ if not validate:
596
+ # restore to train mode if necessary
597
+ self.nets["action_sampler"].train()
598
+
599
+ # update the target critic networks (only when critic has gradient update)
600
+ if not no_critic_backprop:
601
+ with torch.no_grad():
602
+ for critic_ind in range(len(self.nets["critic"])):
603
+ TorchUtils.soft_update(
604
+ source=self.nets["critic"][critic_ind],
605
+ target=self.nets["critic_target"][critic_ind],
606
+ tau=self.algo_config.target_tau,
607
+ )
608
+
609
+ # update target actor network (only when actor has gradient update)
610
+ if self.algo_config.actor.enabled and (not no_actor_backprop):
611
+ with torch.no_grad():
612
+ TorchUtils.soft_update(
613
+ source=self.nets["actor"],
614
+ target=self.nets["actor_target"],
615
+ tau=self.algo_config.target_tau,
616
+ )
617
+
618
+ return info
619
+
620
+ def log_info(self, info):
621
+ """
622
+ Process info dictionary from @train_on_batch to summarize
623
+ information to pass to tensorboard for logging.
624
+
625
+ Args:
626
+ info (dict): dictionary of info
627
+
628
+ Returns:
629
+ loss_log (dict): name -> summary statistic
630
+ """
631
+ loss_log = OrderedDict()
632
+
633
+ # record current optimizer learning rates
634
+ for k in self.optimizers:
635
+ keys = [k]
636
+ optims = [self.optimizers[k]]
637
+ if k == "critic":
638
+ # account for critic having one optimizer per ensemble member
639
+ keys = ["{}{}".format(k, critic_ind) for critic_ind in range(len(self.nets["critic"]))]
640
+ optims = self.optimizers[k]
641
+ for kp, optimizer in zip(keys, optims):
642
+ for i, param_group in enumerate(optimizer.param_groups):
643
+ loss_log["Optimizer/{}{}_lr".format(kp, i)] = param_group["lr"]
644
+
645
+ # extract relevant logs for action sampler, critic, and actor
646
+ loss_log["Loss"] = 0.
647
+ for loss_logger in [self._log_action_sampler_info, self._log_critic_info, self._log_actor_info]:
648
+ this_log = loss_logger(info)
649
+ if "Loss" in this_log:
650
+ # manually merge total loss
651
+ loss_log["Loss"] += this_log["Loss"]
652
+ del this_log["Loss"]
653
+ loss_log.update(this_log)
654
+
655
+ return loss_log
656
+
657
+ def _log_action_sampler_info(self, info):
658
+ """
659
+ Helper function to extract action sampler-relevant information for logging.
660
+ """
661
+ loss_log = OrderedDict()
662
+ loss_log["Action_Sampler/Loss"] = info["action_sampler/loss"].item()
663
+ loss_log["Action_Sampler/Reconsruction_Loss"] = info["action_sampler/recons_loss"].item()
664
+ loss_log["Action_Sampler/KL_Loss"] = info["action_sampler/kl_loss"].item()
665
+ if self.algo_config.action_sampler.vae.prior.use_categorical:
666
+ loss_log["Action_Sampler/Gumbel_Temperature"] = self.nets["action_sampler"].get_gumbel_temperature()
667
+ else:
668
+ loss_log["Action_Sampler/Encoder_Variance"] = info["action_sampler/encoder_variance"].item()
669
+ if "action_sampler/grad_norms" in info:
670
+ loss_log["Action_Sampler/Grad_Norms"] = info["action_sampler/grad_norms"]
671
+ loss_log["Loss"] = loss_log["Action_Sampler/Loss"]
672
+ return loss_log
673
+
674
+ def _log_critic_info(self, info):
675
+ """
676
+ Helper function to extract critic-relevant information for logging.
677
+ """
678
+ loss_log = OrderedDict()
679
+ if "done_masks" in info:
680
+ loss_log["Critic/Done_Mask_Percentage"] = 100. * torch.mean(info["done_masks"]).item()
681
+ if "critic/q_targets" in info:
682
+ loss_log["Critic/Q_Targets"] = info["critic/q_targets"].mean().item()
683
+ loss_log["Loss"] = 0.
684
+ for critic_ind in range(len(self.nets["critic"])):
685
+ loss_log["Critic/Critic{}_Loss".format(critic_ind + 1)] = info["critic/critic{}_loss".format(critic_ind + 1)].item()
686
+ if "critic/critic{}_grad_norms".format(critic_ind + 1) in info:
687
+ loss_log["Critic/Critic{}_Grad_Norms".format(critic_ind + 1)] = info["critic/critic{}_grad_norms".format(critic_ind + 1)]
688
+ loss_log["Loss"] += loss_log["Critic/Critic{}_Loss".format(critic_ind + 1)]
689
+ return loss_log
690
+
691
+ def _log_actor_info(self, info):
692
+ """
693
+ Helper function to extract actor-relevant information for logging.
694
+ """
695
+ loss_log = OrderedDict()
696
+ if self.algo_config.actor.enabled:
697
+ loss_log["Actor/Loss"] = info["actor/loss"].item()
698
+ if "actor/grad_norms" in info:
699
+ loss_log["Actor/Grad_Norms"] = info["actor/grad_norms"]
700
+ loss_log["Loss"] = loss_log["Actor/Loss"]
701
+ return loss_log
702
+
703
+ def set_train(self):
704
+ """
705
+ Prepare networks for evaluation. Update from super class to make sure
706
+ target networks stay in evaluation mode all the time.
707
+ """
708
+ self.nets.train()
709
+
710
+ # target networks always in eval
711
+ for critic_ind in range(len(self.nets["critic_target"])):
712
+ self.nets["critic_target"][critic_ind].eval()
713
+
714
+ if self.algo_config.actor.enabled:
715
+ self.nets["actor_target"].eval()
716
+
717
+ def on_epoch_end(self, epoch):
718
+ """
719
+ Called at the end of each epoch.
720
+ """
721
+
722
+ # LR scheduling updates
723
+ for lr_sc in self.lr_schedulers["critic"]:
724
+ if lr_sc is not None:
725
+ lr_sc.step()
726
+
727
+ if self.lr_schedulers["action_sampler"] is not None:
728
+ self.lr_schedulers["action_sampler"].step()
729
+
730
+ if self.algo_config.actor.enabled and self.lr_schedulers["actor"] is not None:
731
+ self.lr_schedulers["actor"].step()
732
+
733
+ def _get_best_value(self, obs_dict, goal_dict=None):
734
+ """
735
+ Internal helper function for getting the best value for a given state and
736
+ the corresponding best action. Meant to be used at test-time. Key differences
737
+ between this and retrieving target values at train-time are that (1) only a
738
+ single critic is used for the value estimate and (2) the critic and actor
739
+ are used instead of the target critic and target actor.
740
+
741
+ Args:
742
+ obs_dict (dict): batch of current observations
743
+ goal_dict (dict): (optional) goal
744
+
745
+ Returns:
746
+ best_value (torch.Tensor): best values
747
+ best_action (torch.Tensor): best actions
748
+ """
749
+ assert not self.nets.training
750
+
751
+ random_key = list(obs_dict.keys())[0]
752
+ batch_size = obs_dict[random_key].shape[0]
753
+
754
+ # number of action proposals from action sampler
755
+ num_action_samples = self.algo_config.critic.num_action_samples_rollout
756
+
757
+ # we need to stack the observations with redundancy @num_action_samples here, then decode
758
+ # to get all sampled actions. for example, if we generate 2 samples per observation and
759
+ # the batch size is 3, then ob_tiled = [ob1; ob1; ob2; ob2; ob3; ob3]
760
+ ob_tiled = ObsUtils.repeat_and_stack_observation(obs_dict, n=num_action_samples)
761
+ goal_tiled = None
762
+ if len(self.goal_shapes) > 0:
763
+ goal_tiled = ObsUtils.repeat_and_stack_observation(goal_dict, n=num_action_samples)
764
+
765
+ sampled_actions = self._sample_actions_for_value_maximization(
766
+ states_tiled=ob_tiled,
767
+ goal_states_tiled=goal_tiled,
768
+ for_target_update=False,
769
+ )
770
+
771
+ # feed tiled observations and perturbed sampled actions into the critic and then
772
+ # reshape to get all Q-values in second dimension per observation in batch.
773
+ # finally, just take a maximum across that second dimension to take the best sampled action
774
+ all_critic_values = self.nets["critic"][0](ob_tiled, sampled_actions, goal_tiled).reshape(-1, num_action_samples)
775
+ best_action_index = torch.argmax(all_critic_values, dim=1)
776
+
777
+ all_actions = sampled_actions.reshape(batch_size, num_action_samples, -1)
778
+ best_action = all_actions[torch.arange(all_actions.shape[0]), best_action_index]
779
+ best_value = all_critic_values[torch.arange(all_critic_values.shape[0]), best_action_index].unsqueeze(1)
780
+
781
+ return best_value, best_action
782
+
783
+ def get_action(self, obs_dict, goal_dict=None):
784
+ """
785
+ Get policy action outputs.
786
+
787
+ Args:
788
+ obs_dict (dict): current observation
789
+ goal_dict (dict): (optional) goal
790
+
791
+ Returns:
792
+ action (torch.Tensor): action tensor
793
+ """
794
+ assert not self.nets.training
795
+
796
+ _, best_action = self._get_best_value(obs_dict=obs_dict, goal_dict=goal_dict)
797
+ return best_action
798
+
799
+ def get_state_value(self, obs_dict, goal_dict=None):
800
+ """
801
+ Get state value outputs.
802
+
803
+ Args:
804
+ obs_dict (dict): current observation
805
+ goal_dict (dict): (optional) goal
806
+
807
+ Returns:
808
+ value (torch.Tensor): value tensor
809
+ """
810
+ assert not self.nets.training
811
+
812
+ best_value, _ = self._get_best_value(obs_dict=obs_dict, goal_dict=goal_dict)
813
+ return best_value
814
+
815
+ def get_state_action_value(self, obs_dict, actions, goal_dict=None):
816
+ """
817
+ Get state-action value outputs.
818
+
819
+ Args:
820
+ obs_dict (dict): current observation
821
+ actions (torch.Tensor): action
822
+ goal_dict (dict): (optional) goal
823
+
824
+ Returns:
825
+ value (torch.Tensor): value tensor
826
+ """
827
+ assert not self.nets.training
828
+
829
+ return self.nets["critic"][0](obs_dict, actions, goal_dict)
830
+
831
+
832
+ class BCQ_GMM(BCQ):
833
+ """
834
+ A simple modification to BCQ that replaces the VAE used to sample action proposals from the
835
+ batch with a GMM.
836
+ """
837
+ def _create_action_sampler(self):
838
+ """
839
+ Called in @_create_networks to make action sampler network.
840
+ """
841
+ assert self.algo_config.action_sampler.gmm.enabled
842
+
843
+ # GMM network for approximate sampling from batch dataset
844
+ self.nets["action_sampler"] = PolicyNets.GMMActorNetwork(
845
+ obs_shapes=self.obs_shapes,
846
+ goal_shapes=self.goal_shapes,
847
+ ac_dim=self.ac_dim,
848
+ mlp_layer_dims=self.algo_config.action_sampler.actor_layer_dims,
849
+ num_modes=self.algo_config.action_sampler.gmm.num_modes,
850
+ min_std=self.algo_config.action_sampler.gmm.min_std,
851
+ std_activation=self.algo_config.action_sampler.gmm.std_activation,
852
+ low_noise_eval=self.algo_config.action_sampler.gmm.low_noise_eval,
853
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
854
+ )
855
+
856
+ def _train_action_sampler_on_batch(self, batch, epoch, no_backprop=False):
857
+ """
858
+ Modify this helper function from superclass to train GMM action sampler
859
+ with maximum likelihood.
860
+
861
+ Args:
862
+ batch (dict): dictionary with torch.Tensors sampled
863
+ from a data loader and filtered by @process_batch_for_training
864
+
865
+ epoch (int): epoch number - required by some Algos that need
866
+ to perform staged training and early stopping
867
+
868
+ no_backprop (bool): if True, don't perform any learning updates.
869
+
870
+ Returns:
871
+ info (dict): dictionary of relevant inputs, outputs, and losses
872
+ that might be relevant for logging
873
+ outputs (dict): dictionary of outputs to use during critic training
874
+ (for computing target values)
875
+ """
876
+ info = OrderedDict()
877
+
878
+ # GMM forward
879
+ dists = self.nets["action_sampler"].forward_train(
880
+ obs_dict=batch["obs"],
881
+ goal_dict=batch["goal_obs"],
882
+ )
883
+
884
+ # make sure that this is a batch of multivariate action distributions, so that
885
+ # the log probability computation will be correct
886
+ assert len(dists.batch_shape) == 1
887
+ log_probs = dists.log_prob(batch["actions"])
888
+ loss = -log_probs.mean()
889
+ info["action_sampler/loss"] = loss
890
+
891
+ # GMM gradient step
892
+ if not no_backprop:
893
+ gmm_grad_norms = TorchUtils.backprop_for_loss(
894
+ net=self.nets["action_sampler"],
895
+ optim=self.optimizers["action_sampler"],
896
+ loss=loss,
897
+ )
898
+ info["action_sampler/grad_norms"] = gmm_grad_norms
899
+ return info, None
900
+
901
+ def _log_action_sampler_info(self, info):
902
+ """
903
+ Update from superclass for GMM (no KL loss).
904
+ """
905
+ loss_log = OrderedDict()
906
+ loss_log["Action_Sampler/Loss"] = info["action_sampler/loss"].item()
907
+ if "action_sampler/grad_norms" in info:
908
+ loss_log["Action_Sampler/Grad_Norms"] = info["action_sampler/grad_norms"]
909
+ loss_log["Loss"] = loss_log["Action_Sampler/Loss"]
910
+ return loss_log
911
+
912
+
913
+ class BCQ_Distributional(BCQ):
914
+ """
915
+ BCQ with distributional critics. Distributional critics output categorical
916
+ distributions over a discrete set of values instead of expected returns.
917
+ Some parts of this implementation were adapted from ACME (https://github.com/deepmind/acme).
918
+ """
919
+ def _create_critics(self):
920
+ """
921
+ Called in @_create_networks to make critic networks.
922
+ """
923
+ assert self.algo_config.critic.distributional.enabled
924
+ critic_class = ValueNets.DistributionalActionValueNetwork
925
+ critic_args = dict(
926
+ obs_shapes=self.obs_shapes,
927
+ ac_dim=self.ac_dim,
928
+ mlp_layer_dims=self.algo_config.critic.layer_dims,
929
+ value_bounds=self.algo_config.critic.value_bounds,
930
+ num_atoms=self.algo_config.critic.distributional.num_atoms,
931
+ goal_shapes=self.goal_shapes,
932
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
933
+ )
934
+
935
+ # Q network ensemble and target ensemble
936
+ self.nets["critic"] = nn.ModuleList()
937
+ self.nets["critic_target"] = nn.ModuleList()
938
+
939
+ # NOTE: ensemble value in config is ignored, and only 1 critic is used.
940
+ critic = critic_class(**critic_args)
941
+ self.nets["critic"].append(critic)
942
+
943
+ critic_target = critic_class(**critic_args)
944
+ self.nets["critic_target"].append(critic_target)
945
+
946
+ def _get_target_values_from_sampled_actions(self, next_states_tiled, next_sampled_actions, goal_states_tiled, rewards, dones):
947
+ """
948
+ Helper function to get target values for training Q-function with TD-loss. Update from superclass
949
+ to account for distributional value functions.
950
+
951
+ Args:
952
+ next_states_tiled (dict): next observations to use for sampling actions. Assumes that
953
+ tiling has already occurred - so that if the batch size is B, and N samples are
954
+ desired for each observation in the batch, the leading dimension for each
955
+ observation in the dict is B * N
956
+
957
+ next_sampled_actions (torch.Tensor): actions sampled from the action sampler. This function
958
+ will maximize the critic over these action candidates (using the TD3 trick)
959
+
960
+ goal_states_tiled (dict): if not None, goal observations
961
+
962
+ rewards (torch.Tensor): batch of rewards - should be shape (B, 1)
963
+
964
+ dones (torch.Tensor): batch of done signals - should be shape (B, 1)
965
+
966
+ Returns:
967
+ target_categorical_probabilities (torch.Tensor): target categorical probabilities
968
+ to use in the bellman backup
969
+ """
970
+
971
+ with torch.no_grad():
972
+ # compute expected returns of the sampled actions and maximize to find the best action
973
+ all_vds = self.nets["critic_target"][0].forward_train(next_states_tiled, next_sampled_actions, goal_states_tiled)
974
+ expected_values = all_vds.mean().reshape(-1, self.algo_config.critic.num_action_samples)
975
+ best_action_index = torch.argmax(expected_values, dim=1)
976
+ all_actions = next_sampled_actions.reshape(-1, self.algo_config.critic.num_action_samples, self.ac_dim)
977
+ best_action = all_actions[torch.arange(all_actions.shape[0]), best_action_index]
978
+
979
+ # get the corresponding probabilities for the categorical distributions corresponding to the best actions
980
+ all_vd_probs = all_vds.probs.reshape(-1, self.algo_config.critic.num_action_samples, self.algo_config.critic.distributional.num_atoms)
981
+ target_vd_probs = all_vd_probs[torch.arange(all_vd_probs.shape[0]), best_action_index]
982
+
983
+ # bellman backup to get a new grid of values - then project onto the canonical atoms to obtain a
984
+ # target set of categorical probabilities over the atoms
985
+ atom_value_grid = all_vds.values
986
+ target_value_grid = rewards + dones * self.discount * atom_value_grid
987
+ target_categorical_probabilities = LossUtils.project_values_onto_atoms(
988
+ values=target_value_grid,
989
+ probabilities=target_vd_probs,
990
+ atoms=atom_value_grid,
991
+ )
992
+
993
+ return target_categorical_probabilities
994
+
995
+ def _compute_critic_loss(self, critic, states, actions, goal_states, q_targets):
996
+ """
997
+ Overrides super class to compute a distributional loss. Since values are
998
+ categorical distributions, this is just computing a cross-entropy
999
+ loss between the two distributions.
1000
+
1001
+ NOTE: q_targets is expected to be a batch of normalized probability vectors that correspond to
1002
+ the target categorical distributions over the value atoms.
1003
+
1004
+ Args:
1005
+ critic (torch.nn.Module): critic network
1006
+ states (dict): batch of observations
1007
+ actions (torch.Tensor): batch of actions
1008
+ goal_states (dict): if not None, batch of goal observations
1009
+ q_targets (torch.Tensor): batch of target q-values for the TD loss
1010
+
1011
+ Returns:
1012
+ critic_loss (torch.Tensor): critic loss
1013
+ critic_output (dict): additional outputs from the critic. This function
1014
+ returns None, but subclasses may want to provide some information
1015
+ here.
1016
+ """
1017
+
1018
+ # this should be the equivalent of softmax with logits from tf
1019
+ vd = critic.forward_train(states, actions, goal_states)
1020
+ log_probs = F.log_softmax(vd.logits, dim=-1)
1021
+ critic_loss = nn.KLDivLoss(reduction='batchmean')(log_probs, q_targets)
1022
+ return critic_loss, None
aloha-devel/robomimic/algo/cql.py ADDED
@@ -0,0 +1,668 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Implementation of Conservative Q-Learning (CQL).
3
+ Based off of https://github.com/aviralkumar2907/CQL.
4
+ (Paper - https://arxiv.org/abs/2006.04779).
5
+ """
6
+ import numpy as np
7
+ from collections import OrderedDict
8
+
9
+ import torch
10
+ import torch.nn as nn
11
+ import torch.optim as optim
12
+
13
+ import robomimic.models.base_nets as BaseNets
14
+ import robomimic.models.obs_nets as ObsNets
15
+ import robomimic.models.policy_nets as PolicyNets
16
+ import robomimic.models.value_nets as ValueNets
17
+ import robomimic.utils.obs_utils as ObsUtils
18
+ import robomimic.utils.tensor_utils as TensorUtils
19
+ import robomimic.utils.torch_utils as TorchUtils
20
+ from robomimic.algo import register_algo_factory_func, ValueAlgo, PolicyAlgo
21
+
22
+
23
+ @register_algo_factory_func("cql")
24
+ def algo_config_to_class(algo_config):
25
+ """
26
+ Maps algo config to the CQL algo class to instantiate, along with additional algo kwargs.
27
+
28
+ Args:
29
+ algo_config (Config instance): algo config
30
+
31
+ Returns:
32
+ algo_class: subclass of Algo
33
+ algo_kwargs (dict): dictionary of additional kwargs to pass to algorithm
34
+ """
35
+ return CQL, {}
36
+
37
+
38
+ class CQL(PolicyAlgo, ValueAlgo):
39
+ """
40
+ CQL-extension of SAC for the off-policy, offline setting. See https://arxiv.org/abs/2006.04779
41
+ """
42
+ def __init__(self, **kwargs):
43
+ # Store entropy / cql settings first since the super init call requires them
44
+ self.automatic_entropy_tuning = kwargs["algo_config"].actor.target_entropy is not None
45
+ self.automatic_cql_tuning = kwargs["algo_config"].critic.target_q_gap is not None and \
46
+ kwargs["algo_config"].critic.target_q_gap >= 0.0
47
+
48
+ # Run super init first
49
+ super().__init__(**kwargs)
50
+
51
+ # Reward settings
52
+ self.n_step = self.algo_config.n_step
53
+ self.discount = self.algo_config.discount ** self.n_step
54
+
55
+ # Now also store additional SAC- and CQL-specific stuff from the config
56
+ self._num_batch_steps = 0
57
+ self.bc_start_steps = self.algo_config.actor.bc_start_steps
58
+ self.deterministic_backup = self.algo_config.critic.deterministic_backup
59
+ self.td_loss_fcn = nn.SmoothL1Loss() if self.algo_config.critic.use_huber else nn.MSELoss()
60
+
61
+ # Entropy settings
62
+ self.target_entropy = -np.prod(self.ac_dim) if self.algo_config.actor.target_entropy in {None, "default"} else\
63
+ self.algo_config.actor.target_entropy
64
+
65
+ # CQL settings
66
+ self.min_q_weight = self.algo_config.critic.min_q_weight
67
+ self.target_q_gap = self.algo_config.critic.target_q_gap if self.automatic_cql_tuning else 0.0
68
+
69
+ @property
70
+ def log_entropy_weight(self):
71
+ return self.nets["log_entropy_weight"]() if self.automatic_entropy_tuning else\
72
+ torch.zeros(1, requires_grad=False, device=self.device)
73
+
74
+ @property
75
+ def log_cql_weight(self):
76
+ return self.nets["log_cql_weight"]() if self.automatic_cql_tuning else\
77
+ torch.log(torch.tensor(self.algo_config.critic.cql_weight, requires_grad=False, device=self.device))
78
+
79
+ def _create_networks(self):
80
+ """
81
+ Creates networks and places them into @self.nets.
82
+
83
+ Networks for this algo: critic (potentially ensemble), policy
84
+ """
85
+
86
+ # Create nets
87
+ self.nets = nn.ModuleDict()
88
+
89
+ # Assemble args to pass to actor
90
+ actor_args = dict(self.algo_config.actor.net.common)
91
+
92
+ # Add network-specific args and define network class
93
+ if self.algo_config.actor.net.type == "gaussian":
94
+ actor_cls = PolicyNets.GaussianActorNetwork
95
+ actor_args.update(dict(self.algo_config.actor.net.gaussian))
96
+ else:
97
+ # Unsupported actor type!
98
+ raise ValueError(f"Unsupported actor requested. "
99
+ f"Requested: {self.algo_config.actor.net.type}, "
100
+ f"valid options are: {['gaussian']}")
101
+
102
+ # Policy
103
+ self.nets["actor"] = actor_cls(
104
+ obs_shapes=self.obs_shapes,
105
+ goal_shapes=self.goal_shapes,
106
+ ac_dim=self.ac_dim,
107
+ mlp_layer_dims=self.algo_config.actor.layer_dims,
108
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
109
+ **actor_args,
110
+ )
111
+
112
+ # Critics
113
+ self.nets["critic"] = nn.ModuleList()
114
+ self.nets["critic_target"] = nn.ModuleList()
115
+ for _ in range(self.algo_config.critic.ensemble.n):
116
+ for net_list in (self.nets["critic"], self.nets["critic_target"]):
117
+ critic = ValueNets.ActionValueNetwork(
118
+ obs_shapes=self.obs_shapes,
119
+ ac_dim=self.ac_dim,
120
+ mlp_layer_dims=self.algo_config.critic.layer_dims,
121
+ value_bounds=self.algo_config.critic.value_bounds,
122
+ goal_shapes=self.goal_shapes,
123
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
124
+ )
125
+ net_list.append(critic)
126
+
127
+ # Entropy (if automatically tuning)
128
+ if self.automatic_entropy_tuning:
129
+ self.nets["log_entropy_weight"] = BaseNets.Parameter(torch.zeros(1))
130
+
131
+ # CQL (if automatically tuning)
132
+ if self.automatic_cql_tuning:
133
+ self.nets["log_cql_weight"] = BaseNets.Parameter(torch.zeros(1))
134
+
135
+ # Send networks to appropriate device
136
+ self.nets = self.nets.float().to(self.device)
137
+
138
+ # sync target networks at beginning of training
139
+ with torch.no_grad():
140
+ for critic, critic_target in zip(self.nets["critic"], self.nets["critic_target"]):
141
+ TorchUtils.hard_update(
142
+ source=critic,
143
+ target=critic_target,
144
+ )
145
+
146
+ def _create_optimizers(self):
147
+ """
148
+ Creates optimizers using @self.optim_params and places them into @self.optimizers.
149
+
150
+ Overrides base method since we might need to create aditional optimizers for the entropy
151
+ and cql weight parameters (by default, the base class only creates optimizers for all
152
+ entries in @self.nets that have corresponding entries in `self.optim_params` but these
153
+ parameters do not).
154
+ """
155
+
156
+ # Create actor and critic optimizers via super method
157
+ super()._create_optimizers()
158
+
159
+ # We still need to potentially create additional optimizers based on algo settings
160
+
161
+ # entropy (if automatically tuning)
162
+ if self.automatic_entropy_tuning:
163
+ self.optimizers["entropy"] = optim.Adam(
164
+ params=self.nets["log_entropy_weight"].parameters(),
165
+ lr=self.optim_params["actor"]["learning_rate"]["initial"],
166
+ weight_decay=0.0,
167
+ )
168
+
169
+ # cql (if automatically tuning)
170
+ if self.automatic_cql_tuning:
171
+ self.optimizers["cql"] = optim.Adam(
172
+ params=self.nets["log_cql_weight"].parameters(),
173
+ lr=self.optim_params["critic"]["learning_rate"]["initial"],
174
+ weight_decay=0.0,
175
+ )
176
+
177
+ def process_batch_for_training(self, batch):
178
+ """
179
+ Processes input batch from a data loader to filter out relevant info and prepare the batch for training.
180
+
181
+ Args:
182
+ batch (dict): dictionary with torch.Tensors sampled
183
+ from a data loader
184
+
185
+ Returns:
186
+ input_batch (dict): processed and filtered batch that
187
+ will be used for training
188
+ """
189
+ input_batch = dict()
190
+
191
+ # Make sure the trajectory of actions received is greater than our step horizon
192
+ assert batch["actions"].shape[1] >= self.n_step
193
+
194
+ # remove temporal batches for all
195
+ input_batch["obs"] = {k: batch["obs"][k][:, 0, :] for k in batch["obs"]}
196
+ input_batch["next_obs"] = {k: batch["next_obs"][k][:, self.n_step - 1, :] for k in batch["next_obs"]}
197
+ input_batch["goal_obs"] = batch.get("goal_obs", None) # goals may not be present
198
+ input_batch["actions"] = batch["actions"][:, 0, :]
199
+
200
+ # note: ensure scalar signals (rewards, done) retain last dimension of 1 to be compatible with model outputs
201
+
202
+ # single timestep reward is discounted sum of intermediate rewards in sequence
203
+ reward_seq = batch["rewards"][:, :self.n_step]
204
+ discounts = torch.pow(self.algo_config.discount, torch.arange(self.n_step).float()).unsqueeze(0)
205
+ input_batch["rewards"] = (reward_seq * discounts).sum(dim=1).unsqueeze(1)
206
+
207
+ # consider this n-step seqeunce done if any intermediate dones are present
208
+ done_seq = batch["dones"][:, :self.n_step]
209
+ input_batch["dones"] = (done_seq.sum(dim=1) > 0).float().unsqueeze(1)
210
+
211
+ # we move to device first before float conversion because image observation modalities will be uint8 -
212
+ # this minimizes the amount of data transferred to GPU
213
+ return TensorUtils.to_float(TensorUtils.to_device(input_batch, self.device))
214
+
215
+ def train_on_batch(self, batch, epoch, validate=False):
216
+ """
217
+ Training on a single batch of data.
218
+
219
+ Args:
220
+ batch (dict): dictionary with torch.Tensors sampled
221
+ from a data loader and filtered by @process_batch_for_training
222
+
223
+ epoch (int): epoch number - required by some Algos that need
224
+ to perform staged training and early stopping
225
+
226
+ validate (bool): if True, don't perform any learning updates.
227
+
228
+ Returns:
229
+ info (dict): dictionary of relevant inputs, outputs, and losses
230
+ that might be relevant for logging
231
+ """
232
+ info = OrderedDict()
233
+
234
+ # Set the correct context for this training step
235
+ with TorchUtils.maybe_no_grad(no_grad=validate):
236
+ # Always run super call first
237
+ super_info = super().train_on_batch(batch, epoch, validate=validate)
238
+ # Train actor
239
+ actor_info = self._train_policy_on_batch(batch, epoch, validate)
240
+ # Train critic(s)
241
+ critic_info = self._train_critic_on_batch(batch, epoch, validate)
242
+ # Update info
243
+ info.update(super_info)
244
+ info.update(actor_info)
245
+ info.update(critic_info)
246
+
247
+ # Return stats
248
+ return info
249
+
250
+ def _train_policy_on_batch(self, batch, epoch, validate=False):
251
+ """
252
+ Training policy on a single batch of data.
253
+
254
+ Loss is the ExpValue over sampled states of the (weighted) logprob of a sampled action
255
+ under the current policy minus the Q value of associated with the (s, a) combo
256
+
257
+ Intuitively, this tries to improve the odds of sampling actions with high Q values while simultaneously
258
+ penalizing high probability actions.
259
+
260
+ Since we're in the continuous setting, we monte carlo sample.
261
+
262
+ Concretely:
263
+ Loss = Average[ entropy_weight * logprob(f(eps; s) | s) - Q(s, f(eps; s) ]
264
+
265
+ where we use the reparameterization trick with Gaussian function f(*) to parameterize
266
+ actions as a function of the sampled noise param eps given input state s
267
+
268
+ Additionally, we update the (log) entropy weight parameter if we're tuning that as well.
269
+
270
+ Args:
271
+ batch (dict): dictionary with torch.Tensors sampled
272
+ from a data loader and filtered by @process_batch_for_training
273
+
274
+ epoch (int): epoch number - required by some Algos that need
275
+ to perform staged training and early stopping
276
+
277
+ validate (bool): if True, don't perform any learning updates.
278
+
279
+ Returns:
280
+ info (dict): dictionary of relevant inputs, outputs, and losses
281
+ that might be relevant for logging
282
+ """
283
+ info = OrderedDict()
284
+
285
+ # Sample actions from policy and get log probs
286
+ dist = self.nets["actor"].forward_train(obs_dict=batch["obs"], goal_dict=batch["goal_obs"])
287
+ actions, log_prob = self._get_actions_and_log_prob(dist=dist)
288
+
289
+ # Calculate alpha
290
+ entropy_weight_loss = -(self.log_entropy_weight * (log_prob + self.target_entropy).detach()).mean() if\
291
+ self.automatic_entropy_tuning else 0.0
292
+ entropy_weight = self.log_entropy_weight.exp()
293
+
294
+ # Get predicted Q-values for all state, action pairs
295
+ pred_qs = [critic(obs_dict=batch["obs"], acts=actions, goal_dict=batch["goal_obs"])
296
+ for critic in self.nets["critic"]]
297
+ # We take the minimum for stability
298
+ pred_qs, _ = torch.cat(pred_qs, dim=1).min(dim=1, keepdim=True)
299
+
300
+ # Use BC if we're in the beginning of training, otherwise calculate policy loss normally
301
+ baseline = dist.log_prob(batch["actions"]).unsqueeze(dim=-1) if\
302
+ self._num_batch_steps < self.bc_start_steps else pred_qs
303
+ policy_loss = (entropy_weight * log_prob - baseline).mean()
304
+
305
+ # Add info
306
+ info["entropy_weight"] = entropy_weight.item()
307
+ info["entropy_weight_loss"] = entropy_weight_loss.item() if \
308
+ self.automatic_entropy_tuning else entropy_weight_loss
309
+ info["actor/loss"] = policy_loss
310
+
311
+ # Take a training step if we're not validating
312
+ if not validate:
313
+ # Update batch step
314
+ self._num_batch_steps += 1
315
+ if self.automatic_entropy_tuning:
316
+ # Alpha
317
+ self.optimizers["entropy"].zero_grad()
318
+ entropy_weight_loss.backward()
319
+ self.optimizers["entropy"].step()
320
+ info["entropy_grad_norms"] = self.log_entropy_weight.grad.data.norm(2).pow(2).item()
321
+
322
+ # Policy
323
+ actor_grad_norms = TorchUtils.backprop_for_loss(
324
+ net=self.nets["actor"],
325
+ optim=self.optimizers["actor"],
326
+ loss=policy_loss,
327
+ max_grad_norm=self.algo_config.actor.max_gradient_norm,
328
+ )
329
+ # Add info
330
+ info["actor/grad_norms"] = actor_grad_norms
331
+
332
+ # Return stats
333
+ return info
334
+
335
+ def _train_critic_on_batch(self, batch, epoch, validate=False):
336
+ """
337
+ Training critic(s) on a single batch of data.
338
+
339
+ For a given batch of (s, a, r, s') tuples and n sampled actions (a_, a'_ corresponding to actions
340
+ sampled from the learned policy at states s and s', respectively; a~ corresponding to uniformly random
341
+ sampled actions):
342
+
343
+ Loss = CQL_loss + SAC_loss
344
+
345
+ Since we're in the continuous setting, we monte carlo sample for all ExpValues, which become Averages instead
346
+
347
+ SAC_loss is the standard single-step TD error, corresponding to the following:
348
+
349
+ SAC_loss = 0.5 * Average[ (Q(s,a) - (r + Average over a'_ [ Q(s', a'_) ]))^2 ]
350
+
351
+ The CQL_loss corresponds to a weighted secondary objective, corresponding to the (ExpValue of Q values over
352
+ sampled states and sampled actions from the LEARNED policy) minus the (ExpValue of Q values over
353
+ sampled states and sampled actions from the DATASET policy) plus a regularizer as a function
354
+ of the learned policy.
355
+
356
+ Intuitively, this tries to penalize Q-values arbitrarily resulting from the learned policy (which may produce
357
+ out-of-distribution (s,a) pairs) while preserving (known) Q-values taken from the dataset policy.
358
+
359
+ As we are using SAC, we choose our regularizer to correspond to the negative KL divergence between our
360
+ learned policy and a uniform distribution such that the first term in the CQL loss corresponds to the
361
+ soft maximum over all Q values at any state s.
362
+
363
+ For stability, we importance sample actions over random actions and from the current policy at s, s'.
364
+
365
+ Moreover, if we want to tune the cql_weight automatically, we include the threshold value target_q_gap
366
+ to penalize Q values that are overly-optimistic by the given threshold.
367
+
368
+ In this case, the CQL_loss is as follows:
369
+
370
+ CQL_loss = cql_weight * (Average [log (Average over a` in {a~, a_, a_'}: exp(Q(s,a`) - logprob(a`)) - Average [Q(s,a)]] - target_q_gap)
371
+
372
+ Args:
373
+ batch (dict): dictionary with torch.Tensors sampled
374
+ from a data loader and filtered by @process_batch_for_training
375
+
376
+ epoch (int): epoch number - required by some Algos that need
377
+ to perform staged training and early stopping
378
+
379
+ validate (bool): if True, don't perform any learning updates.
380
+
381
+ Returns:
382
+ info (dict): dictionary of relevant inputs, outputs, and losses
383
+ that might be relevant for logging
384
+ """
385
+ info = OrderedDict()
386
+ B, A = batch["actions"].shape
387
+ N = self.algo_config.critic.num_random_actions
388
+
389
+ # Get predicted Q-values from taken actions
390
+ q_preds = [critic(obs_dict=batch["obs"], acts=batch["actions"], goal_dict=batch["goal_obs"])
391
+ for critic in self.nets["critic"]]
392
+
393
+ # Sample actions at the current and next step
394
+ curr_dist = self.nets["actor"].forward_train(obs_dict=batch["obs"], goal_dict=batch["goal_obs"])
395
+ next_dist = self.nets["actor"].forward_train(obs_dict=batch["next_obs"], goal_dict=batch["goal_obs"])
396
+ next_actions, next_log_prob = self._get_actions_and_log_prob(dist=next_dist)
397
+
398
+ # Don't capture gradients here, since the critic target network doesn't get trained (only soft updated)
399
+ with torch.no_grad():
400
+ # We take the max over all samples if the number of action samples is > 1
401
+ if self.algo_config.critic.num_action_samples > 1:
402
+ # Generate the target q values, using the backup from the next state
403
+ temp_actions = next_dist.rsample(sample_shape=(self.algo_config.critic.num_action_samples,)).permute(1, 0, 2)
404
+ target_qs = [self._get_qs_from_actions(
405
+ obs_dict=batch["next_obs"], actions=temp_actions, goal_dict=batch["goal_obs"], q_net=critic)
406
+ .max(dim=1, keepdim=True)[0] for critic in self.nets["critic_target"]]
407
+ else:
408
+ target_qs = [critic(obs_dict=batch["next_obs"], acts=next_actions, goal_dict=batch["goal_obs"])
409
+ for critic in self.nets["critic_target"]]
410
+ # Take the minimum over all critics
411
+ target_qs, _ = torch.cat(target_qs, dim=1).min(dim=1, keepdim=True)
412
+ # If only sampled once from each critic and not using a deterministic backup, subtract the logprob as well
413
+ if self.algo_config.critic.num_action_samples == 1 and not self.deterministic_backup:
414
+ target_qs = target_qs - self.log_entropy_weight.exp() * next_log_prob
415
+
416
+ # Calculate the q target values
417
+ done_mask_batch = 1. - batch["dones"]
418
+ info["done_masks"] = done_mask_batch
419
+ q_target = batch["rewards"] + done_mask_batch * self.discount * target_qs
420
+
421
+ # Calculate CQL stuff
422
+ cql_random_actions = torch.FloatTensor(N, B, A).uniform_(-1., 1.).to(self.device) # shape (N, B, A)
423
+ cql_random_log_prob = np.log(0.5 ** A)
424
+ cql_curr_actions, cql_curr_log_prob = self._get_actions_and_log_prob(dist=curr_dist, sample_shape=(N,)) # shape (N, B, A) and (N, B, 1)
425
+ cql_next_actions, cql_next_log_prob = self._get_actions_and_log_prob(dist=next_dist, sample_shape=(N,)) # shape (N, B, A) and (N, B, 1)
426
+ cql_curr_log_prob = cql_curr_log_prob.squeeze(dim=-1).permute(1, 0).detach() # shape (B, N)
427
+ cql_next_log_prob = cql_next_log_prob.squeeze(dim=-1).permute(1, 0).detach() # shape (B, N)
428
+ q_cats = [] # Each entry shape will be (B, N)
429
+
430
+ for critic, q_pred in zip(self.nets["critic"], q_preds):
431
+ # Compose Q values over all sampled actions (importance sampled)
432
+ q_rand = self._get_qs_from_actions(obs_dict=batch["obs"], actions=cql_random_actions.permute(1, 0, 2), goal_dict=batch["goal_obs"], q_net=critic)
433
+ q_curr = self._get_qs_from_actions(obs_dict=batch["obs"], actions=cql_curr_actions.permute(1, 0, 2), goal_dict=batch["goal_obs"], q_net=critic)
434
+ q_next = self._get_qs_from_actions(obs_dict=batch["obs"], actions=cql_next_actions.permute(1, 0, 2), goal_dict=batch["goal_obs"], q_net=critic)
435
+ q_cat = torch.cat([
436
+ q_rand - cql_random_log_prob,
437
+ q_next - cql_next_log_prob,
438
+ q_curr - cql_curr_log_prob,
439
+ ], dim=1) # shape (B, 3 * N)
440
+ q_cats.append(q_cat)
441
+
442
+ # Calculate the losses for all critics
443
+ cql_losses = []
444
+ critic_losses = []
445
+ cql_weight = torch.clamp(self.log_cql_weight.exp(), min=0.0, max=1000000.0)
446
+ info["critic/cql_weight"] = cql_weight.item()
447
+ for i, (q_pred, q_cat) in enumerate(zip(q_preds, q_cats)):
448
+ # Calculate td error loss
449
+ td_loss = self.td_loss_fcn(q_pred, q_target)
450
+ # Calculate cql loss
451
+ cql_loss = cql_weight * (self.min_q_weight * (torch.logsumexp(q_cat, dim=1).mean() - q_pred.mean()) -
452
+ self.target_q_gap)
453
+ cql_losses.append(cql_loss)
454
+ # Calculate total loss
455
+ loss = td_loss + cql_loss
456
+ critic_losses.append(loss)
457
+ info[f"critic/critic{i+1}_loss"] = loss
458
+
459
+ # Run gradient descent if we're not validating
460
+ if not validate:
461
+ # Train CQL weight if tuning automatically
462
+ if self.automatic_cql_tuning:
463
+ cql_weight_loss = -torch.stack(cql_losses).mean()
464
+ info[
465
+ "critic/cql_weight_loss"] = cql_weight_loss.item() # Make sure to not store computation graph since we retain graph after backward() call
466
+ self.optimizers["cql"].zero_grad()
467
+ cql_weight_loss.backward(retain_graph=True)
468
+ self.optimizers["cql"].step()
469
+ info["critic/cql_grad_norms"] = self.log_cql_weight.grad.data.norm(2).pow(2).item()
470
+
471
+ # Train critics
472
+ for i, (critic_loss, critic, critic_target, optimizer) in enumerate(zip(
473
+ critic_losses, self.nets["critic"], self.nets["critic_target"], self.optimizers["critic"]
474
+ )):
475
+ retain_graph = (i < (len(critic_losses) - 1))
476
+ critic_grad_norms = TorchUtils.backprop_for_loss(
477
+ net=critic,
478
+ optim=optimizer,
479
+ loss=critic_loss,
480
+ max_grad_norm=self.algo_config.critic.max_gradient_norm,
481
+ retain_graph=retain_graph,
482
+ )
483
+ info[f"critic/critic{i+1}_grad_norms"] = critic_grad_norms
484
+ with torch.no_grad():
485
+ TorchUtils.soft_update(source=critic, target=critic_target, tau=self.algo_config.target_tau)
486
+
487
+ # Return stats
488
+ return info
489
+
490
+ def _get_actions_and_log_prob(self, dist, sample_shape=torch.Size()):
491
+ """
492
+ Helper method to sample actions and compute corresponding log probabilities
493
+
494
+ Args:
495
+ dist (Distribution): Distribution to sample from
496
+ sample_shape (torch.Size or tuple): Shape of output when sampling (number of samples)
497
+
498
+ Returns:
499
+ 2-tuple:
500
+ - (tensor) sampled actions (..., B, ..., A)
501
+ - (tensor) corresponding log probabilities (..., B, ..., 1)
502
+ """
503
+ # Process networks with tanh differently than normal distributions
504
+ if self.algo_config.actor.net.common.use_tanh:
505
+ actions, actions_pre_tanh = dist.rsample(sample_shape=sample_shape, return_pretanh_value=True)
506
+ log_prob = dist.log_prob(actions, pre_tanh_value=actions_pre_tanh).unsqueeze(dim=-1)
507
+ else:
508
+ actions = dist.rsample(sample_shape=sample_shape)
509
+ log_prob = dist.log_prob(actions)
510
+
511
+ return actions, log_prob
512
+
513
+ @staticmethod
514
+ def _get_qs_from_actions(obs_dict, actions, goal_dict, q_net):
515
+ """
516
+ Helper function for grabbing Q values given a single state and multiple (N) sampled actions.
517
+
518
+ Args:
519
+ obs_dict (dict): Observation dict from batch
520
+ actions (tensor): Torch tensor, with dim1 assumed to be the extra sampled dimension
521
+ goal_dict (dict): Goal dict from batch
522
+ q_net (nn.Module): Q net to pass the observations and actions
523
+
524
+ Returns:
525
+ tensor: (B, N) corresponding Q values
526
+ """
527
+ # Get the number of sampled actions
528
+ B, N, D = actions.shape
529
+
530
+ # Repeat obs and goals in the batch dimension
531
+ obs_dict_stacked = ObsUtils.repeat_and_stack_observation(obs_dict, N)
532
+ goal_dict_stacked = ObsUtils.repeat_and_stack_observation(goal_dict, N)
533
+
534
+ # Pass the obs and (flattened) actions through to get the Q values
535
+ qs = q_net(obs_dict=obs_dict_stacked, acts=actions.reshape(-1, D), goal_dict=goal_dict_stacked)
536
+
537
+ # Unflatten output
538
+ qs = qs.reshape(B, N)
539
+
540
+ return qs
541
+
542
+ def log_info(self, info):
543
+ """
544
+ Process info dictionary from @train_on_batch to summarize
545
+ information to pass to tensorboard for logging.
546
+
547
+ Args:
548
+ info (dict): dictionary of info
549
+
550
+ Returns:
551
+ loss_log (dict): name -> summary statistic
552
+ """
553
+ loss_log = OrderedDict()
554
+
555
+ # record current optimizer learning rates
556
+ for k in self.optimizers:
557
+ keys = [k]
558
+ optims = [self.optimizers[k]]
559
+ if k == "critic":
560
+ # account for critic having one optimizer per ensemble member
561
+ keys = ["{}{}".format(k, critic_ind) for critic_ind in range(len(self.nets["critic"]))]
562
+ optims = self.optimizers[k]
563
+ for kp, optimizer in zip(keys, optims):
564
+ for i, param_group in enumerate(optimizer.param_groups):
565
+ loss_log["Optimizer/{}{}_lr".format(kp, i)] = param_group["lr"]
566
+
567
+ # extract relevant logs for critic, and actor
568
+ loss_log["Loss"] = 0.
569
+ for loss_logger in [self._log_critic_info, self._log_actor_info]:
570
+ this_log = loss_logger(info)
571
+ if "Loss" in this_log:
572
+ # manually merge total loss
573
+ loss_log["Loss"] += this_log["Loss"]
574
+ del this_log["Loss"]
575
+ loss_log.update(this_log)
576
+
577
+ return loss_log
578
+
579
+ def _log_critic_info(self, info):
580
+ """
581
+ Helper function to extract critic-relevant information for logging.
582
+ """
583
+ loss_log = OrderedDict()
584
+ if "done_masks" in info:
585
+ loss_log["Critic/Done_Mask_Percentage"] = 100. * torch.mean(info["done_masks"]).item()
586
+ if "critic/q_targets" in info:
587
+ loss_log["Critic/Q_Targets"] = info["critic/q_targets"].mean().item()
588
+ loss_log["Loss"] = 0.
589
+ for critic_ind in range(len(self.nets["critic"])):
590
+ loss_log["Critic/Critic{}_Loss".format(critic_ind + 1)] = info["critic/critic{}_loss".format(critic_ind + 1)].item()
591
+ if "critic/critic{}_grad_norms".format(critic_ind + 1) in info:
592
+ loss_log["Critic/Critic{}_Grad_Norms".format(critic_ind + 1)] = info["critic/critic{}_grad_norms".format(critic_ind + 1)]
593
+ loss_log["Loss"] += loss_log["Critic/Critic{}_Loss".format(critic_ind + 1)]
594
+ if "critic/cql_weight_loss" in info:
595
+ loss_log["Critic/CQL_Weight"] = info["critic/cql_weight"]
596
+ loss_log["Critic/CQL_Weight_Loss"] = info["critic/cql_weight_loss"]
597
+ loss_log["Critic/CQL_Grad_Norms"] = info["critic/cql_grad_norms"]
598
+ return loss_log
599
+
600
+ def _log_actor_info(self, info):
601
+ """
602
+ Helper function to extract actor-relevant information for logging.
603
+ """
604
+ loss_log = OrderedDict()
605
+ loss_log["Actor/Loss"] = info["actor/loss"].item()
606
+ if "actor/grad_norms" in info:
607
+ loss_log["Actor/Grad_Norms"] = info["actor/grad_norms"]
608
+ loss_log["Loss"] = loss_log["Actor/Loss"]
609
+ loss_log["Entropy_Weight_Loss"] = info["entropy_weight_loss"]
610
+ loss_log["Entropy_Weight"] = info["entropy_weight"]
611
+ if "entropy_grad_norms" in info:
612
+ loss_log["Entropy_Grad_Norms"] = info["entropy_grad_norms"]
613
+ return loss_log
614
+
615
+ def set_train(self):
616
+ """
617
+ Prepare networks for evaluation. Update from super class to make sure
618
+ target networks stay in evaluation mode all the time.
619
+ """
620
+ self.nets.train()
621
+
622
+ # target networks always in eval
623
+ for critic in self.nets["critic_target"]:
624
+ critic.eval()
625
+
626
+ def on_epoch_end(self, epoch):
627
+ """
628
+ Called at the end of each epoch.
629
+ """
630
+
631
+ # LR scheduling updates
632
+ for lr_sc in self.lr_schedulers["critic"]:
633
+ if lr_sc is not None:
634
+ lr_sc.step()
635
+
636
+ if self.lr_schedulers["actor"] is not None:
637
+ self.lr_schedulers["actor"].step()
638
+
639
+ def get_action(self, obs_dict, goal_dict=None):
640
+ """
641
+ Get policy action outputs.
642
+
643
+ Args:
644
+ obs_dict (dict): current observation
645
+ goal_dict (dict): (optional) goal
646
+
647
+ Returns:
648
+ action (torch.Tensor): action tensor
649
+ """
650
+ assert not self.nets.training
651
+
652
+ return self.nets["actor"](obs_dict=obs_dict, goal_dict=goal_dict)
653
+
654
+ def get_state_action_value(self, obs_dict, actions, goal_dict=None):
655
+ """
656
+ Get state-action value outputs.
657
+
658
+ Args:
659
+ obs_dict (dict): current observation
660
+ actions (torch.Tensor): action
661
+ goal_dict (dict): (optional) goal
662
+
663
+ Returns:
664
+ value (torch.Tensor): value tensor
665
+ """
666
+ assert not self.nets.training
667
+
668
+ return self.nets["critic"][0](obs_dict, actions, goal_dict)
aloha-devel/robomimic/algo/diffusion_policy.py ADDED
@@ -0,0 +1,700 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Implementation of Diffusion Policy https://diffusion-policy.cs.columbia.edu/ by Cheng Chi
3
+ """
4
+ from typing import Callable, Union
5
+ import math
6
+ from collections import OrderedDict, deque
7
+ from packaging.version import parse as parse_version
8
+ import random
9
+ import torch
10
+ import torch.nn as nn
11
+ import torch.nn.functional as F
12
+ # requires diffusers==0.11.1
13
+ from diffusers.schedulers.scheduling_ddpm import DDPMScheduler
14
+ from diffusers.schedulers.scheduling_ddim import DDIMScheduler
15
+ from diffusers.training_utils import EMAModel
16
+
17
+ import robomimic.models.obs_nets as ObsNets
18
+ import robomimic.utils.tensor_utils as TensorUtils
19
+ import robomimic.utils.torch_utils as TorchUtils
20
+ import robomimic.utils.obs_utils as ObsUtils
21
+
22
+ from robomimic.algo import register_algo_factory_func, PolicyAlgo
23
+
24
+ import random
25
+ import robomimic.utils.torch_utils as TorchUtils
26
+ import robomimic.utils.tensor_utils as TensorUtils
27
+ import robomimic.utils.obs_utils as ObsUtils
28
+
29
+ @register_algo_factory_func("diffusion_policy")
30
+ def algo_config_to_class(algo_config):
31
+ """
32
+ Maps algo config to the BC algo class to instantiate, along with additional algo kwargs.
33
+
34
+ Args:
35
+ algo_config (Config instance): algo config
36
+
37
+ Returns:
38
+ algo_class: subclass of Algo
39
+ algo_kwargs (dict): dictionary of additional kwargs to pass to algorithm
40
+ """
41
+
42
+ if algo_config.unet.enabled:
43
+ return DiffusionPolicyUNet, {}
44
+ elif algo_config.transformer.enabled:
45
+ raise NotImplementedError()
46
+ else:
47
+ raise RuntimeError()
48
+
49
+ class DiffusionPolicyUNet(PolicyAlgo):
50
+ def _create_networks(self):
51
+ """
52
+ Creates networks and places them into @self.nets.
53
+ """
54
+ if self.algo_config.language_conditioned:
55
+ self.obs_shapes["lang_emb"] = [768] # clip is 768-dim embedding
56
+
57
+ # set up different observation groups for @MIMO_MLP
58
+ observation_group_shapes = OrderedDict()
59
+ observation_group_shapes["obs"] = OrderedDict(self.obs_shapes)
60
+ encoder_kwargs = ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder)
61
+
62
+ obs_encoder = ObsNets.ObservationGroupEncoder(
63
+ observation_group_shapes=observation_group_shapes,
64
+ encoder_kwargs=encoder_kwargs,
65
+ )
66
+ # IMPORTANT!
67
+ # replace all BatchNorm with GroupNorm to work with EMA
68
+ # performance will tank if you forget to do this!
69
+ obs_encoder = replace_bn_with_gn(obs_encoder)
70
+
71
+ obs_dim = obs_encoder.output_shape()[0]
72
+
73
+ # create network object
74
+ noise_pred_net = ConditionalUnet1D(
75
+ input_dim=self.ac_dim,
76
+ global_cond_dim=obs_dim*self.algo_config.horizon.observation_horizon
77
+ )
78
+
79
+ # the final arch has 2 parts
80
+ nets = nn.ModuleDict({
81
+ 'policy': nn.ModuleDict({
82
+ 'obs_encoder': obs_encoder,
83
+ 'noise_pred_net': noise_pred_net
84
+ })
85
+ })
86
+
87
+ nets = nets.float().to(self.device)
88
+
89
+ # setup noise scheduler
90
+ noise_scheduler = None
91
+ if self.algo_config.ddpm.enabled:
92
+ noise_scheduler = DDPMScheduler(
93
+ num_train_timesteps=self.algo_config.ddpm.num_train_timesteps,
94
+ beta_schedule=self.algo_config.ddpm.beta_schedule,
95
+ clip_sample=self.algo_config.ddpm.clip_sample,
96
+ prediction_type=self.algo_config.ddpm.prediction_type
97
+ )
98
+ elif self.algo_config.ddim.enabled:
99
+ noise_scheduler = DDIMScheduler(
100
+ num_train_timesteps=self.algo_config.ddim.num_train_timesteps,
101
+ beta_schedule=self.algo_config.ddim.beta_schedule,
102
+ clip_sample=self.algo_config.ddim.clip_sample,
103
+ set_alpha_to_one=self.algo_config.ddim.set_alpha_to_one,
104
+ steps_offset=self.algo_config.ddim.steps_offset,
105
+ prediction_type=self.algo_config.ddim.prediction_type
106
+ )
107
+ else:
108
+ raise RuntimeError()
109
+
110
+ # setup EMA
111
+ ema = None
112
+ if self.algo_config.ema.enabled:
113
+ ema = EMAModel(model=nets, power=self.algo_config.ema.power)
114
+
115
+ # set attrs
116
+ self.nets = nets
117
+ self.noise_scheduler = noise_scheduler
118
+ self.ema = ema
119
+ self.action_check_done = False
120
+ self.obs_queue = None
121
+ self.action_queue = None
122
+
123
+ def process_batch_for_training(self, batch):
124
+ """
125
+ Processes input batch from a data loader to filter out
126
+ relevant information and prepare the batch for training.
127
+
128
+ Args:
129
+ batch (dict): dictionary with torch.Tensors sampled
130
+ from a data loader
131
+
132
+ Returns:
133
+ input_batch (dict): processed and filtered batch that
134
+ will be used for training
135
+ """
136
+ To = self.algo_config.horizon.observation_horizon
137
+ Ta = self.algo_config.horizon.action_horizon
138
+ Tp = self.algo_config.horizon.prediction_horizon
139
+
140
+ input_batch = dict()
141
+ input_batch["obs"] = {k: batch["obs"][k][:, :To, :] for k in batch["obs"]}
142
+ input_batch["goal_obs"] = batch.get("goal_obs", None) # goals may not be present
143
+ input_batch["actions"] = batch["actions"][:, :Tp, :]
144
+
145
+ # check if actions are normalized to [-1,1]
146
+ if not self.action_check_done:
147
+ actions = input_batch["actions"]
148
+ in_range = (-1 <= actions) & (actions <= 1)
149
+ all_in_range = torch.all(in_range).item()
150
+ if not all_in_range:
151
+ raise ValueError('"actions" must be in range [-1,1] for Diffusion Policy! Check if hdf5_normalize_action is enabled.')
152
+ self.action_check_done = True
153
+
154
+ return TensorUtils.to_device(TensorUtils.to_float(input_batch), self.device)
155
+
156
+ def train_on_batch(self, batch, epoch, validate=False):
157
+ """
158
+ Training on a single batch of data.
159
+
160
+ Args:
161
+ batch (dict): dictionary with torch.Tensors sampled
162
+ from a data loader and filtered by @process_batch_for_training
163
+
164
+ epoch (int): epoch number - required by some Algos that need
165
+ to perform staged training and early stopping
166
+
167
+ validate (bool): if True, don't perform any learning updates.
168
+
169
+ Returns:
170
+ info (dict): dictionary of relevant inputs, outputs, and losses
171
+ that might be relevant for logging
172
+ """
173
+ To = self.algo_config.horizon.observation_horizon
174
+ Ta = self.algo_config.horizon.action_horizon
175
+ Tp = self.algo_config.horizon.prediction_horizon
176
+ action_dim = self.ac_dim
177
+ B = batch['actions'].shape[0]
178
+
179
+
180
+ with TorchUtils.maybe_no_grad(no_grad=validate):
181
+ info = super(DiffusionPolicyUNet, self).train_on_batch(batch, epoch, validate=validate)
182
+ actions = batch['actions']
183
+
184
+ # encode obs
185
+ inputs = {
186
+ 'obs': batch["obs"],
187
+ 'goal': batch["goal_obs"]
188
+ }
189
+ for k in self.obs_shapes:
190
+ # first two dimensions should be [B, T] for inputs
191
+ assert inputs['obs'][k].ndim - 2 == len(self.obs_shapes[k])
192
+
193
+ obs_features = TensorUtils.time_distributed(inputs, self.nets['policy']['obs_encoder'], inputs_as_kwargs=True)
194
+ assert obs_features.ndim == 3 # [B, T, D]
195
+
196
+ obs_cond = obs_features.flatten(start_dim=1)
197
+
198
+ # sample noise to add to actions
199
+ noise = torch.randn(actions.shape, device=self.device)
200
+
201
+ # sample a diffusion iteration for each data point
202
+ timesteps = torch.randint(
203
+ 0, self.noise_scheduler.config.num_train_timesteps,
204
+ (B,), device=self.device
205
+ ).long()
206
+
207
+ # add noise to the clean actions according to the noise magnitude at each diffusion iteration
208
+ # (this is the forward diffusion process)
209
+ noisy_actions = self.noise_scheduler.add_noise(
210
+ actions, noise, timesteps)
211
+
212
+ # predict the noise residual
213
+ noise_pred = self.nets['policy']['noise_pred_net'](
214
+ noisy_actions, timesteps, global_cond=obs_cond)
215
+
216
+ # L2 loss
217
+ loss = F.mse_loss(noise_pred, noise)
218
+
219
+ # logging
220
+ losses = {
221
+ 'l2_loss': loss
222
+ }
223
+ info["losses"] = TensorUtils.detach(losses)
224
+
225
+ if not validate:
226
+ # gradient step
227
+ policy_grad_norms = TorchUtils.backprop_for_loss(
228
+ net=self.nets,
229
+ optim=self.optimizers["policy"],
230
+ loss=loss,
231
+ )
232
+
233
+ # update Exponential Moving Average of the model weights
234
+ if self.ema is not None:
235
+ self.ema.step(self.nets)
236
+
237
+ step_info = {
238
+ 'policy_grad_norms': policy_grad_norms
239
+ }
240
+ info.update(step_info)
241
+
242
+ return info
243
+
244
+ def log_info(self, info):
245
+ """
246
+ Process info dictionary from @train_on_batch to summarize
247
+ information to pass to tensorboard for logging.
248
+
249
+ Args:
250
+ info (dict): dictionary of info
251
+
252
+ Returns:
253
+ loss_log (dict): name -> summary statistic
254
+ """
255
+ log = super(DiffusionPolicyUNet, self).log_info(info)
256
+ log["Loss"] = info["losses"]["l2_loss"].item()
257
+ if "policy_grad_norms" in info:
258
+ log["Policy_Grad_Norms"] = info["policy_grad_norms"]
259
+ return log
260
+
261
+ def reset(self):
262
+ """
263
+ Reset algo state to prepare for environment rollouts.
264
+ """
265
+ # setup inference queues
266
+ To = self.algo_config.horizon.observation_horizon
267
+ Ta = self.algo_config.horizon.action_horizon
268
+ obs_queue = deque(maxlen=To)
269
+ action_queue = deque(maxlen=Ta)
270
+ self.obs_queue = obs_queue
271
+ self.action_queue = action_queue
272
+
273
+ def get_action(self, obs_dict, goal_dict=None):
274
+ """
275
+ Get policy action outputs.
276
+
277
+ Args:
278
+ obs_dict (dict): current observation [1, Do]
279
+ goal_dict (dict): (optional) goal
280
+
281
+ Returns:
282
+ action (torch.Tensor): action tensor [1, Da]
283
+ """
284
+ # obs_dict: key: [1,D]
285
+ To = self.algo_config.horizon.observation_horizon
286
+ Ta = self.algo_config.horizon.action_horizon
287
+
288
+ # TODO: obs_queue already handled by frame_stack
289
+ # make sure we have at least To observations in obs_queue
290
+ # if not enough, repeat
291
+ # if already full, append one to the obs_queue
292
+ # n_repeats = max(To - len(self.obs_queue), 1)
293
+ # self.obs_queue.extend([obs_dict] * n_repeats)
294
+
295
+ if len(self.action_queue) == 0:
296
+ # no actions left, run inference
297
+ # turn obs_queue into dict of tensors (concat at T dim)
298
+ # import pdb; pdb.set_trace()
299
+ # obs_dict_list = TensorUtils.list_of_flat_dict_to_dict_of_list(list(self.obs_queue))
300
+ # obs_dict_tensor = dict((k, torch.cat(v, dim=0).unsqueeze(0)) for k,v in obs_dict_list.items())
301
+
302
+ # run inference
303
+ # [1,T,Da]
304
+ action_sequence = self._get_action_trajectory(obs_dict=obs_dict)
305
+
306
+ # put actions into the queue
307
+ self.action_queue.extend(action_sequence[0])
308
+
309
+ # has action, execute from left to right
310
+ # [Da]
311
+ action = self.action_queue.popleft()
312
+
313
+ # [1,Da]
314
+ action = action.unsqueeze(0)
315
+ return action
316
+
317
+ def _get_action_trajectory(self, obs_dict, goal_dict=None):
318
+ assert not self.nets.training
319
+ To = self.algo_config.horizon.observation_horizon
320
+ Ta = self.algo_config.horizon.action_horizon
321
+ Tp = self.algo_config.horizon.prediction_horizon
322
+ action_dim = self.ac_dim
323
+ if self.algo_config.ddpm.enabled is True:
324
+ num_inference_timesteps = self.algo_config.ddpm.num_inference_timesteps
325
+ elif self.algo_config.ddim.enabled is True:
326
+ num_inference_timesteps = self.algo_config.ddim.num_inference_timesteps
327
+ else:
328
+ raise ValueError
329
+
330
+ # select network
331
+ nets = self.nets
332
+ if self.ema is not None:
333
+ nets = self.ema.averaged_model
334
+
335
+ # encode obs
336
+ inputs = {
337
+ 'obs': obs_dict,
338
+ 'goal': goal_dict
339
+ }
340
+ for k in self.obs_shapes:
341
+ # first two dimensions should be [B, T] for inputs
342
+ assert inputs['obs'][k].ndim - 2 == len(self.obs_shapes[k])
343
+ obs_features = TensorUtils.time_distributed(inputs, nets['policy']['obs_encoder'], inputs_as_kwargs=True)
344
+ assert obs_features.ndim == 3 # [B, T, D]
345
+ B = obs_features.shape[0]
346
+
347
+ # reshape observation to (B,obs_horizon*obs_dim)
348
+ obs_cond = obs_features.flatten(start_dim=1)
349
+
350
+ # initialize action from Guassian noise
351
+ noisy_action = torch.randn(
352
+ (B, Tp, action_dim), device=self.device)
353
+ naction = noisy_action
354
+
355
+ # init scheduler
356
+ self.noise_scheduler.set_timesteps(num_inference_timesteps)
357
+
358
+ for k in self.noise_scheduler.timesteps:
359
+ # predict noise
360
+ noise_pred = nets['policy']['noise_pred_net'](
361
+ sample=naction,
362
+ timestep=k,
363
+ global_cond=obs_cond
364
+ )
365
+
366
+ # inverse diffusion step (remove noise)
367
+ naction = self.noise_scheduler.step(
368
+ model_output=noise_pred,
369
+ timestep=k,
370
+ sample=naction
371
+ ).prev_sample
372
+
373
+ # process action using Ta
374
+ start = To - 1
375
+ end = start + Ta
376
+ action = naction[:,start:end]
377
+ return action
378
+
379
+ def serialize(self):
380
+ """
381
+ Get dictionary of current model parameters.
382
+ """
383
+ return {
384
+ "nets": self.nets.state_dict(),
385
+ "ema": self.ema.averaged_model.state_dict() if self.ema is not None else None,
386
+ }
387
+
388
+ def deserialize(self, model_dict):
389
+ """
390
+ Load model from a checkpoint.
391
+
392
+ Args:
393
+ model_dict (dict): a dictionary saved by self.serialize() that contains
394
+ the same keys as @self.network_classes
395
+ """
396
+ self.nets.load_state_dict(model_dict["nets"])
397
+ if model_dict.get("ema", None) is not None:
398
+ self.ema.averaged_model.load_state_dict(model_dict["ema"])
399
+
400
+
401
+
402
+
403
+
404
+ # =================== Vision Encoder Utils =====================
405
+ def replace_submodules(
406
+ root_module: nn.Module,
407
+ predicate: Callable[[nn.Module], bool],
408
+ func: Callable[[nn.Module], nn.Module]) -> nn.Module:
409
+ """
410
+ Replace all submodules selected by the predicate with
411
+ the output of func.
412
+
413
+ predicate: Return true if the module is to be replaced.
414
+ func: Return new module to use.
415
+ """
416
+ if predicate(root_module):
417
+ return func(root_module)
418
+
419
+ if parse_version(torch.__version__) < parse_version('1.9.0'):
420
+ raise ImportError('This function requires pytorch >= 1.9.0')
421
+
422
+ bn_list = [k.split('.') for k, m
423
+ in root_module.named_modules(remove_duplicate=True)
424
+ if predicate(m)]
425
+ for *parent, k in bn_list:
426
+ parent_module = root_module
427
+ if len(parent) > 0:
428
+ parent_module = root_module.get_submodule('.'.join(parent))
429
+ if isinstance(parent_module, nn.Sequential):
430
+ src_module = parent_module[int(k)]
431
+ else:
432
+ src_module = getattr(parent_module, k)
433
+ tgt_module = func(src_module)
434
+ if isinstance(parent_module, nn.Sequential):
435
+ parent_module[int(k)] = tgt_module
436
+ else:
437
+ setattr(parent_module, k, tgt_module)
438
+ # verify that all modules are replaced
439
+ bn_list = [k.split('.') for k, m
440
+ in root_module.named_modules(remove_duplicate=True)
441
+ if predicate(m)]
442
+ assert len(bn_list) == 0
443
+ return root_module
444
+
445
+ def replace_bn_with_gn(
446
+ root_module: nn.Module,
447
+ features_per_group: int=16) -> nn.Module:
448
+ """
449
+ Relace all BatchNorm layers with GroupNorm.
450
+ """
451
+ replace_submodules(
452
+ root_module=root_module,
453
+ predicate=lambda x: isinstance(x, nn.BatchNorm2d),
454
+ func=lambda x: nn.GroupNorm(
455
+ num_groups=x.num_features//features_per_group,
456
+ num_channels=x.num_features)
457
+ )
458
+ return root_module
459
+
460
+ # =================== UNet for Diffusion ==============
461
+
462
+ class SinusoidalPosEmb(nn.Module):
463
+ def __init__(self, dim):
464
+ super().__init__()
465
+ self.dim = dim
466
+
467
+ def forward(self, x):
468
+ device = x.device
469
+ half_dim = self.dim // 2
470
+ emb = math.log(10000) / (half_dim - 1)
471
+ emb = torch.exp(torch.arange(half_dim, device=device) * -emb)
472
+ emb = x[:, None] * emb[None, :]
473
+ emb = torch.cat((emb.sin(), emb.cos()), dim=-1)
474
+ return emb
475
+
476
+
477
+ class Downsample1d(nn.Module):
478
+ def __init__(self, dim):
479
+ super().__init__()
480
+ self.conv = nn.Conv1d(dim, dim, 3, 2, 1)
481
+
482
+ def forward(self, x):
483
+ return self.conv(x)
484
+
485
+ class Upsample1d(nn.Module):
486
+ def __init__(self, dim):
487
+ super().__init__()
488
+ self.conv = nn.ConvTranspose1d(dim, dim, 4, 2, 1)
489
+
490
+ def forward(self, x):
491
+ return self.conv(x)
492
+
493
+
494
+ class Conv1dBlock(nn.Module):
495
+ '''
496
+ Conv1d --> GroupNorm --> Mish
497
+ '''
498
+
499
+ def __init__(self, inp_channels, out_channels, kernel_size, n_groups=8):
500
+ super().__init__()
501
+
502
+ self.block = nn.Sequential(
503
+ nn.Conv1d(inp_channels, out_channels, kernel_size, padding=kernel_size // 2),
504
+ nn.GroupNorm(n_groups, out_channels),
505
+ nn.Mish(),
506
+ )
507
+
508
+ def forward(self, x):
509
+ return self.block(x)
510
+
511
+
512
+ class ConditionalResidualBlock1D(nn.Module):
513
+ def __init__(self,
514
+ in_channels,
515
+ out_channels,
516
+ cond_dim,
517
+ kernel_size=3,
518
+ n_groups=8):
519
+ super().__init__()
520
+
521
+ self.blocks = nn.ModuleList([
522
+ Conv1dBlock(in_channels, out_channels, kernel_size, n_groups=n_groups),
523
+ Conv1dBlock(out_channels, out_channels, kernel_size, n_groups=n_groups),
524
+ ])
525
+
526
+ # FiLM modulation https://arxiv.org/abs/1709.07871
527
+ # predicts per-channel scale and bias
528
+ cond_channels = out_channels * 2
529
+ self.out_channels = out_channels
530
+ self.cond_encoder = nn.Sequential(
531
+ nn.Mish(),
532
+ nn.Linear(cond_dim, cond_channels),
533
+ nn.Unflatten(-1, (-1, 1))
534
+ )
535
+
536
+ # make sure dimensions compatible
537
+ self.residual_conv = nn.Conv1d(in_channels, out_channels, 1) \
538
+ if in_channels != out_channels else nn.Identity()
539
+
540
+ def forward(self, x, cond):
541
+ '''
542
+ x : [ batch_size x in_channels x horizon ]
543
+ cond : [ batch_size x cond_dim]
544
+
545
+ returns:
546
+ out : [ batch_size x out_channels x horizon ]
547
+ '''
548
+ out = self.blocks[0](x)
549
+ embed = self.cond_encoder(cond)
550
+
551
+ embed = embed.reshape(
552
+ embed.shape[0], 2, self.out_channels, 1)
553
+ scale = embed[:,0,...]
554
+ bias = embed[:,1,...]
555
+ out = scale * out + bias
556
+
557
+ out = self.blocks[1](out)
558
+ out = out + self.residual_conv(x)
559
+ return out
560
+
561
+
562
+ class ConditionalUnet1D(nn.Module):
563
+ def __init__(self,
564
+ input_dim,
565
+ global_cond_dim,
566
+ diffusion_step_embed_dim=256,
567
+ down_dims=[256,512,1024],
568
+ kernel_size=5,
569
+ n_groups=8
570
+ ):
571
+ """
572
+ input_dim: Dim of actions.
573
+ global_cond_dim: Dim of global conditioning applied with FiLM
574
+ in addition to diffusion step embedding. This is usually obs_horizon * obs_dim
575
+ diffusion_step_embed_dim: Size of positional encoding for diffusion iteration k
576
+ down_dims: Channel size for each UNet level.
577
+ The length of this array determines numebr of levels.
578
+ kernel_size: Conv kernel size
579
+ n_groups: Number of groups for GroupNorm
580
+ """
581
+
582
+ super().__init__()
583
+ all_dims = [input_dim] + list(down_dims)
584
+ start_dim = down_dims[0]
585
+
586
+ dsed = diffusion_step_embed_dim
587
+ diffusion_step_encoder = nn.Sequential(
588
+ SinusoidalPosEmb(dsed),
589
+ nn.Linear(dsed, dsed * 4),
590
+ nn.Mish(),
591
+ nn.Linear(dsed * 4, dsed),
592
+ )
593
+ cond_dim = dsed + global_cond_dim
594
+
595
+ in_out = list(zip(all_dims[:-1], all_dims[1:]))
596
+ mid_dim = all_dims[-1]
597
+ self.mid_modules = nn.ModuleList([
598
+ ConditionalResidualBlock1D(
599
+ mid_dim, mid_dim, cond_dim=cond_dim,
600
+ kernel_size=kernel_size, n_groups=n_groups
601
+ ),
602
+ ConditionalResidualBlock1D(
603
+ mid_dim, mid_dim, cond_dim=cond_dim,
604
+ kernel_size=kernel_size, n_groups=n_groups
605
+ ),
606
+ ])
607
+
608
+ down_modules = nn.ModuleList([])
609
+ for ind, (dim_in, dim_out) in enumerate(in_out):
610
+ is_last = ind >= (len(in_out) - 1)
611
+ down_modules.append(nn.ModuleList([
612
+ ConditionalResidualBlock1D(
613
+ dim_in, dim_out, cond_dim=cond_dim,
614
+ kernel_size=kernel_size, n_groups=n_groups),
615
+ ConditionalResidualBlock1D(
616
+ dim_out, dim_out, cond_dim=cond_dim,
617
+ kernel_size=kernel_size, n_groups=n_groups),
618
+ Downsample1d(dim_out) if not is_last else nn.Identity()
619
+ ]))
620
+
621
+ up_modules = nn.ModuleList([])
622
+ for ind, (dim_in, dim_out) in enumerate(reversed(in_out[1:])):
623
+ is_last = ind >= (len(in_out) - 1)
624
+ up_modules.append(nn.ModuleList([
625
+ ConditionalResidualBlock1D(
626
+ dim_out*2, dim_in, cond_dim=cond_dim,
627
+ kernel_size=kernel_size, n_groups=n_groups),
628
+ ConditionalResidualBlock1D(
629
+ dim_in, dim_in, cond_dim=cond_dim,
630
+ kernel_size=kernel_size, n_groups=n_groups),
631
+ Upsample1d(dim_in) if not is_last else nn.Identity()
632
+ ]))
633
+
634
+ final_conv = nn.Sequential(
635
+ Conv1dBlock(start_dim, start_dim, kernel_size=kernel_size),
636
+ nn.Conv1d(start_dim, input_dim, 1),
637
+ )
638
+
639
+ self.diffusion_step_encoder = diffusion_step_encoder
640
+ self.up_modules = up_modules
641
+ self.down_modules = down_modules
642
+ self.final_conv = final_conv
643
+
644
+ print("number of parameters: {:e}".format(
645
+ sum(p.numel() for p in self.parameters()))
646
+ )
647
+
648
+ def forward(self,
649
+ sample: torch.Tensor,
650
+ timestep: Union[torch.Tensor, float, int],
651
+ global_cond=None):
652
+ """
653
+ x: (B,T,input_dim)
654
+ timestep: (B,) or int, diffusion step
655
+ global_cond: (B,global_cond_dim)
656
+ output: (B,T,input_dim)
657
+ """
658
+ # (B,T,C)
659
+ sample = sample.moveaxis(-1,-2)
660
+ # (B,C,T)
661
+
662
+ # 1. time
663
+ timesteps = timestep
664
+ if not torch.is_tensor(timesteps):
665
+ timesteps = torch.tensor([timesteps], dtype=torch.long, device=sample.device)
666
+ elif torch.is_tensor(timesteps) and len(timesteps.shape) == 0:
667
+ timesteps = timesteps[None].to(sample.device)
668
+ # broadcast to batch dimension in a way that's compatible with ONNX/Core ML
669
+ timesteps = timesteps.expand(sample.shape[0])
670
+
671
+ global_feature = self.diffusion_step_encoder(timesteps)
672
+
673
+ if global_cond is not None:
674
+ global_feature = torch.cat([
675
+ global_feature, global_cond
676
+ ], axis=-1)
677
+
678
+ x = sample
679
+ h = []
680
+ for idx, (resnet, resnet2, downsample) in enumerate(self.down_modules):
681
+ x = resnet(x, global_feature)
682
+ x = resnet2(x, global_feature)
683
+ h.append(x)
684
+ x = downsample(x)
685
+
686
+ for mid_module in self.mid_modules:
687
+ x = mid_module(x, global_feature)
688
+
689
+ for idx, (resnet, resnet2, upsample) in enumerate(self.up_modules):
690
+ x = torch.cat((x, h.pop()), dim=1)
691
+ x = resnet(x, global_feature)
692
+ x = resnet2(x, global_feature)
693
+ x = upsample(x)
694
+
695
+ x = self.final_conv(x)
696
+
697
+ # (B,C,T)
698
+ x = x.moveaxis(-1,-2)
699
+ # (B,T,C)
700
+ return x
aloha-devel/robomimic/algo/gl.py ADDED
@@ -0,0 +1,775 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Subgoal prediction models, used in HBC / IRIS.
3
+ """
4
+ import numpy as np
5
+ from collections import OrderedDict
6
+ from copy import deepcopy
7
+
8
+ import torch
9
+ import torch.nn as nn
10
+
11
+ import robomimic.models.obs_nets as ObsNets
12
+ import robomimic.models.vae_nets as VAENets
13
+ import robomimic.utils.tensor_utils as TensorUtils
14
+ import robomimic.utils.torch_utils as TorchUtils
15
+ import robomimic.utils.obs_utils as ObsUtils
16
+
17
+ from robomimic.algo import register_algo_factory_func, PlannerAlgo, ValueAlgo
18
+
19
+
20
+ @register_algo_factory_func("gl")
21
+ def algo_config_to_class(algo_config):
22
+ """
23
+ Maps algo config to the GL algo class to instantiate, along with additional algo kwargs.
24
+
25
+ Args:
26
+ algo_config (Config instance): algo config
27
+
28
+ Returns:
29
+ algo_class: subclass of Algo
30
+ algo_kwargs (dict): dictionary of additional kwargs to pass to algorithm
31
+ """
32
+ if algo_config.vae.enabled:
33
+ return GL_VAE, {}
34
+ return GL, {}
35
+
36
+
37
+ class GL(PlannerAlgo):
38
+ """
39
+ Implements goal prediction component for HBC and IRIS.
40
+ """
41
+ def __init__(
42
+ self,
43
+ algo_config,
44
+ obs_config,
45
+ global_config,
46
+ obs_key_shapes,
47
+ ac_dim,
48
+ device
49
+ ):
50
+ """
51
+ Args:
52
+ algo_config (Config object): instance of Config corresponding to the algo section
53
+ of the config
54
+
55
+ obs_config (Config object): instance of Config corresponding to the observation
56
+ section of the config
57
+
58
+ global_config (Config object): global training config
59
+
60
+ obs_key_shapes (OrderedDict): dictionary that maps observation keys to shapes
61
+
62
+ ac_dim (int): dimension of action space
63
+
64
+ device (torch.Device): where the algo should live (i.e. cpu, gpu)
65
+ """
66
+
67
+ self._subgoal_horizon = algo_config.subgoal_horizon
68
+ super(GL, self).__init__(
69
+ algo_config=algo_config,
70
+ obs_config=obs_config,
71
+ global_config=global_config,
72
+ obs_key_shapes=obs_key_shapes,
73
+ ac_dim=ac_dim,
74
+ device=device
75
+ )
76
+
77
+ def _create_networks(self):
78
+ """
79
+ Creates networks and places them into @self.nets.
80
+ """
81
+ self.nets = nn.ModuleDict()
82
+
83
+ obs_group_shapes = OrderedDict()
84
+ obs_group_shapes["obs"] = OrderedDict(self.obs_shapes)
85
+ if len(self.goal_shapes) > 0:
86
+ obs_group_shapes["goal"] = OrderedDict(self.goal_shapes)
87
+
88
+ # deterministic goal prediction network
89
+ self.nets["goal_network"] = ObsNets.MIMO_MLP(
90
+ input_obs_group_shapes=obs_group_shapes,
91
+ output_shapes=self.subgoal_shapes,
92
+ layer_dims=self.algo_config.ae.planner_layer_dims,
93
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
94
+ )
95
+
96
+ self.nets = self.nets.float().to(self.device)
97
+
98
+ def process_batch_for_training(self, batch):
99
+ """
100
+ Processes input batch from a data loader to filter out
101
+ relevant information and prepare the batch for training.
102
+
103
+ Args:
104
+ batch (dict): dictionary with torch.Tensors sampled
105
+ from a data loader
106
+
107
+ Returns:
108
+ input_batch (dict): processed and filtered batch that
109
+ will be used for training
110
+ """
111
+ input_batch = dict()
112
+
113
+ # remove temporal batches for all except scalar signals (to be compatible with model outputs)
114
+ input_batch["obs"] = { k: batch["obs"][k][:, 0, :] for k in batch["obs"] }
115
+ # extract multi-horizon subgoal target
116
+ input_batch["subgoals"] = {k: batch["next_obs"][k][:, self._subgoal_horizon - 1, :] for k in batch["next_obs"]}
117
+ input_batch["target_subgoals"] = input_batch["subgoals"]
118
+ input_batch["goal_obs"] = batch.get("goal_obs", None) # goals may not be present
119
+
120
+ # we move to device first before float conversion because image observation modalities will be uint8 -
121
+ # this minimizes the amount of data transferred to GPU
122
+ return TensorUtils.to_float(TensorUtils.to_device(input_batch, self.device))
123
+
124
+ def get_actor_goal_for_training_from_processed_batch(self, processed_batch, **kwargs):
125
+ """
126
+ Retrieve subgoals from processed batch to use for training the actor. Subclasses
127
+ can modify this function to change the subgoals.
128
+
129
+ Args:
130
+ processed_batch (dict): processed batch from @process_batch_for_training
131
+
132
+ Returns:
133
+ actor_subgoals (dict): subgoal observations to condition actor on
134
+ """
135
+ return processed_batch["target_subgoals"]
136
+
137
+ def train_on_batch(self, batch, epoch, validate=False):
138
+ """
139
+ Training on a single batch of data.
140
+
141
+ Args:
142
+ batch (dict): dictionary with torch.Tensors sampled
143
+ from a data loader and filtered by @process_batch_for_training
144
+
145
+ epoch (int): epoch number - required by some Algos that need
146
+ to perform staged training and early stopping
147
+
148
+ validate (bool): if True, don't perform any learning updates.
149
+
150
+ Returns:
151
+ info (dict): dictionary of relevant inputs, outputs, and losses
152
+ that might be relevant for logging
153
+ """
154
+ with TorchUtils.maybe_no_grad(no_grad=validate):
155
+ info = super(GL, self).train_on_batch(batch, epoch, validate=validate)
156
+
157
+ # predict subgoal observations with goal network
158
+ pred_subgoals = self.nets["goal_network"](obs=batch["obs"], goal=batch["goal_obs"])
159
+
160
+ # compute loss as L2 error for each observation key
161
+ losses = OrderedDict()
162
+ target_subgoals = batch["target_subgoals"] # targets for network prediction
163
+ goal_loss = 0.
164
+ for k in pred_subgoals:
165
+ assert pred_subgoals[k].shape == target_subgoals[k].shape, "mismatch in predicted and target subgoals!"
166
+ mode_loss = nn.MSELoss()(pred_subgoals[k], target_subgoals[k])
167
+ goal_loss += mode_loss
168
+ losses["goal_{}_loss".format(k)] = mode_loss
169
+ losses["goal_loss"] = goal_loss
170
+ info.update(TensorUtils.detach(losses))
171
+
172
+ if not validate:
173
+ # gradient step
174
+ goal_grad_norms = TorchUtils.backprop_for_loss(
175
+ net=self.nets["goal_network"],
176
+ optim=self.optimizers["goal_network"],
177
+ loss=losses["goal_loss"],
178
+ )
179
+ info["goal_grad_norms"] = goal_grad_norms
180
+
181
+ return info
182
+
183
+ def log_info(self, info):
184
+ """
185
+ Process info dictionary from @train_on_batch to summarize
186
+ information to pass to tensorboard for logging.
187
+
188
+ Args:
189
+ info (dict): dictionary of info
190
+
191
+ Returns:
192
+ loss_log (dict): name -> summary statistic
193
+ """
194
+ loss_log = super(GL, self).log_info(info)
195
+
196
+ loss_log["Loss"] = info["goal_loss"].item()
197
+ for k in info:
198
+ if k.endswith("_loss"):
199
+ loss_log[k] = info[k].item()
200
+ if "goal_grad_norms" in info:
201
+ loss_log["Grad_Norms"] = info["goal_grad_norms"]
202
+
203
+ return loss_log
204
+
205
+ def get_subgoal_predictions(self, obs_dict, goal_dict=None):
206
+ """
207
+ Takes a batch of observations and predicts a batch of subgoals.
208
+
209
+ Args:
210
+ obs_dict (dict): current observation
211
+ goal_dict (dict): (optional) goal
212
+
213
+ Returns:
214
+ subgoal prediction (dict): name -> Tensor [batch_size, ...]
215
+ """
216
+ return self.nets["goal_network"](obs=obs_dict, goal=goal_dict)
217
+
218
+ def sample_subgoals(self, obs_dict, goal_dict=None, num_samples=1):
219
+ """
220
+ Sample @num_samples subgoals from the network per observation.
221
+ Since this class implements a deterministic subgoal prediction,
222
+ this function returns identical subgoals for each input observation.
223
+
224
+ Args:
225
+ obs_dict (dict): current observation
226
+ goal_dict (dict): (optional) goal
227
+
228
+ Returns:
229
+ subgoals (dict): name -> Tensor [batch_size, num_samples, ...]
230
+ """
231
+
232
+ # stack observations to get all samples in one forward pass
233
+ obs_tiled = ObsUtils.repeat_and_stack_observation(obs_dict, n=num_samples)
234
+ goal_tiled = None
235
+ if goal_dict is not None:
236
+ goal_tiled = ObsUtils.repeat_and_stack_observation(goal_dict, n=num_samples)
237
+
238
+ # [batch_size * num_samples, ...]
239
+ goals = self.get_subgoal_predictions(obs_dict=obs_tiled, goal_dict=goal_tiled)
240
+ # reshape to [batch_size, num_samples, ...]
241
+ return TensorUtils.reshape_dimensions(goals, begin_axis=0, end_axis=0, target_dims=(-1, num_samples))
242
+
243
+ def get_action(self, obs_dict, goal_dict=None):
244
+ """
245
+ Get policy action outputs. Assumes one input observation (first dimension should be 1).
246
+
247
+ Args:
248
+ obs_dict (dict): current observation
249
+ goal_dict (dict): (optional) goal
250
+
251
+ Returns:
252
+ action (torch.Tensor): action tensor
253
+ """
254
+ raise Exception("Rollouts are not supported by GL")
255
+
256
+
257
+ class GL_VAE(GL):
258
+ """
259
+ Implements goal prediction via VAE.
260
+ """
261
+ def _create_networks(self):
262
+ """
263
+ Creates networks and places them into @self.nets.
264
+ """
265
+ self.nets = nn.ModuleDict()
266
+
267
+ self.nets["goal_network"] = VAENets.VAE(
268
+ input_shapes=self.subgoal_shapes,
269
+ output_shapes=self.subgoal_shapes,
270
+ condition_shapes=self.obs_shapes,
271
+ goal_shapes=self.goal_shapes,
272
+ device=self.device,
273
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
274
+ **VAENets.vae_args_from_config(self.algo_config.vae),
275
+ )
276
+
277
+ self.nets = self.nets.float().to(self.device)
278
+
279
+ def get_actor_goal_for_training_from_processed_batch(
280
+ self,
281
+ processed_batch,
282
+ use_latent_subgoals=False,
283
+ use_prior_correction=False,
284
+ num_prior_samples=100,
285
+ **kwargs,
286
+ ):
287
+ """
288
+ Modify from superclass to support a @use_latent_subgoals option.
289
+ The VAE can optionally return latent subgoals by passing the subgoal
290
+ observations in the batch through the encoder.
291
+
292
+ Args:
293
+ processed_batch (dict): processed batch from @process_batch_for_training
294
+
295
+ use_latent_subgoals (bool): if True, condition the actor on latent subgoals
296
+ by using the VAE encoder to encode subgoal observations at train-time,
297
+ and using the VAE prior to generate latent subgoals at test-time
298
+
299
+ use_prior_correction (bool): if True, use a "prior correction" trick to
300
+ choose a latent subgoal sampled from the prior that is close to the
301
+ latent from the VAE encoder (posterior). This can help with issues at
302
+ test-time where the encoder latent distribution might not match
303
+ the prior latent distribution.
304
+
305
+ num_prior_samples (int): number of VAE prior samples to take and choose among,
306
+ if @use_prior_correction is true
307
+
308
+ Returns:
309
+ actor_subgoals (dict): subgoal observations to condition actor on
310
+ """
311
+
312
+ if not use_latent_subgoals:
313
+ return processed_batch["target_subgoals"]
314
+
315
+ # batch variables
316
+ obs = processed_batch["obs"]
317
+ subgoals = processed_batch["subgoals"] # full subgoal observations
318
+ target_subgoals = processed_batch["target_subgoals"] # targets for network prediction
319
+ goal_obs = processed_batch["goal_obs"]
320
+
321
+ with torch.no_grad():
322
+ # run VAE forward pass to get samples from posterior for the current observation and subgoal
323
+ vae_outputs = self.nets["goal_network"](
324
+ inputs=subgoals, # encoder takes full subgoals
325
+ outputs=target_subgoals, # reconstruct target subgoals
326
+ goals=goal_obs,
327
+ conditions=obs, # condition on observations
328
+ )
329
+ posterior_z = vae_outputs["encoder_z"]
330
+ latent_subgoals = posterior_z
331
+
332
+ if use_prior_correction:
333
+ # instead of treating posterior samples as latent subgoals, sample latents from
334
+ # the prior and choose the closest one as the latent subgoal
335
+
336
+ random_key = list(obs.keys())[0]
337
+ batch_size = obs[random_key].shape[0]
338
+
339
+ # for each batch member, get @num_prior_samples samples from the prior
340
+ obs_tiled = ObsUtils.repeat_and_stack_observation(obs, n=num_prior_samples)
341
+ goal_tiled = None
342
+ if len(self.goal_shapes) > 0:
343
+ goal_tiled = ObsUtils.repeat_and_stack_observation(goal_obs, n=num_prior_samples)
344
+
345
+ prior_z_samples = self.nets["goal_network"].sample_prior(
346
+ conditions=obs_tiled,
347
+ goals=goal_tiled,
348
+ )
349
+
350
+ # choose prior samples that are closest to the sampled posterior latents
351
+ # note: every posterior sample in the batch has @num_prior_samples corresponding prior samples
352
+
353
+ # reshape prior samples to (batch_size, num_samples, latent_dim)
354
+ prior_z_samples = prior_z_samples.reshape(batch_size, num_prior_samples, -1)
355
+
356
+ # reshape posterior latents to (batch_size, 1, latent_dim)
357
+ posterior_z_expanded = posterior_z.unsqueeze(1)
358
+
359
+ # compute distances with broadcasting so that each posterior sample
360
+ # has distances to all of its prior samples
361
+ distances = (prior_z_samples - posterior_z_expanded).pow(2).sum(dim=2)
362
+
363
+ # then gather the closest prior sample for each posterior sample
364
+ neighbors = torch.argmin(distances, dim=1)
365
+ latent_subgoals = prior_z_samples[torch.arange(batch_size).long(), neighbors]
366
+
367
+ return { "latent_subgoal" : latent_subgoals }
368
+
369
+ def train_on_batch(self, batch, epoch, validate=False):
370
+ """
371
+ Training on a single batch of data.
372
+
373
+ Args:
374
+ batch (dict): dictionary with torch.Tensors sampled
375
+ from a data loader and filtered by @process_batch_for_training
376
+
377
+ epoch (int): epoch number - required by some Algos that need
378
+ to perform staged training and early stopping
379
+
380
+ validate (bool): if True, don't perform any learning updates.
381
+
382
+ Returns:
383
+ info (dict): dictionary of relevant inputs, outputs, and losses
384
+ that might be relevant for logging
385
+ """
386
+ with TorchUtils.maybe_no_grad(no_grad=validate):
387
+ info = super(GL, self).train_on_batch(batch, epoch, validate=validate)
388
+
389
+ if self.algo_config.vae.prior.use_categorical:
390
+ temperature = self.algo_config.vae.prior.categorical_init_temp - epoch * self.algo_config.vae.prior.categorical_temp_anneal_step
391
+ temperature = max(temperature, self.algo_config.vae.prior.categorical_min_temp)
392
+ self.nets["goal_network"].set_gumbel_temperature(temperature)
393
+
394
+ # batch variables
395
+ obs = batch["obs"]
396
+ subgoals = batch["subgoals"] # full subgoal observations
397
+ target_subgoals = batch["target_subgoals"] # targets for network prediction
398
+ goal_obs = batch["goal_obs"]
399
+
400
+ vae_outputs = self.nets["goal_network"](
401
+ inputs=subgoals, # encoder takes full subgoals
402
+ outputs=target_subgoals, # reconstruct target subgoals
403
+ goals=goal_obs,
404
+ conditions=obs, # condition on observations
405
+ )
406
+ recons_loss = vae_outputs["reconstruction_loss"]
407
+ kl_loss = vae_outputs["kl_loss"]
408
+ goal_loss = recons_loss + self.algo_config.vae.kl_weight * kl_loss
409
+ info["recons_loss"] = recons_loss
410
+ info["kl_loss"] = kl_loss
411
+ info["goal_loss"] = goal_loss
412
+
413
+ if not self.algo_config.vae.prior.use_categorical:
414
+ with torch.no_grad():
415
+ info["encoder_variance"] = torch.exp(vae_outputs["encoder_params"]["logvar"])
416
+
417
+ # VAE gradient step
418
+ if not validate:
419
+ goal_grad_norms = TorchUtils.backprop_for_loss(
420
+ net=self.nets["goal_network"],
421
+ optim=self.optimizers["goal_network"],
422
+ loss=goal_loss,
423
+ )
424
+ info["goal_grad_norms"] = goal_grad_norms
425
+
426
+ return info
427
+
428
+ def log_info(self, info):
429
+ """
430
+ Process info dictionary from @train_on_batch to summarize
431
+ information to pass to tensorboard for logging.
432
+
433
+ Args:
434
+ info (dict): dictionary of info
435
+
436
+ Returns:
437
+ loss_log (dict): name -> summary statistic
438
+ """
439
+ loss_log = super(GL_VAE, self).log_info(info)
440
+ loss_log["Reconstruction_Loss"] = info["recons_loss"].item()
441
+ loss_log["KL_Loss"] = info["kl_loss"].item()
442
+ if self.algo_config.vae.prior.use_categorical:
443
+ loss_log["Gumbel_Temperature"] = self.nets["goal_network"].get_gumbel_temperature()
444
+ else:
445
+ loss_log["Encoder_Variance"] = info["encoder_variance"].mean().item()
446
+ return loss_log
447
+
448
+ def get_subgoal_predictions(self, obs_dict, goal_dict=None):
449
+ """
450
+ Takes a batch of observations and predicts a batch of subgoals.
451
+
452
+ Args:
453
+ obs_dict (dict): current observation
454
+ goal_dict (dict): (optional) goal
455
+
456
+ Returns:
457
+ subgoal prediction (dict): name -> Tensor [batch_size, ...]
458
+ """
459
+
460
+ if self.global_config.algo.latent_subgoal.enabled:
461
+ # latent subgoals from sampling prior
462
+ latent_subgoals = self.nets["goal_network"].sample_prior(
463
+ conditions=obs_dict,
464
+ goals=goal_dict,
465
+ )
466
+
467
+ return OrderedDict(latent_subgoal=latent_subgoals)
468
+
469
+ # sample a single goal from the VAE
470
+ goals = self.sample_subgoals(obs_dict=obs_dict, goal_dict=goal_dict, num_samples=1)
471
+ return { k : goals[k][:, 0, ...] for k in goals }
472
+
473
+ def sample_subgoals(self, obs_dict, goal_dict=None, num_samples=1):
474
+ """
475
+ Sample @num_samples subgoals from the VAE per observation.
476
+
477
+ Args:
478
+ obs_dict (dict): current observation
479
+ goal_dict (dict): (optional) goal
480
+
481
+ Returns:
482
+ subgoals (dict): name -> Tensor [batch_size, num_samples, ...]
483
+ """
484
+
485
+ # stack observations to get all samples in one forward pass
486
+ obs_tiled = ObsUtils.repeat_and_stack_observation(obs_dict, n=num_samples)
487
+ goal_tiled = None
488
+ if goal_dict is not None:
489
+ goal_tiled = ObsUtils.repeat_and_stack_observation(goal_dict, n=num_samples)
490
+
491
+ # VAE decode expects number of samples explicitly
492
+ mod = list(obs_tiled.keys())[0]
493
+ n = obs_tiled[mod].shape[0]
494
+ # [batch_size * num_samples, ...]
495
+ goals = self.nets["goal_network"].decode(n=n, conditions=obs_tiled, goals=goal_tiled)
496
+ # reshape to [batch_size, num_samples, ...]
497
+ return TensorUtils.reshape_dimensions(goals, begin_axis=0, end_axis=0, target_dims=(-1, num_samples))
498
+
499
+
500
+ class ValuePlanner(PlannerAlgo, ValueAlgo):
501
+ """
502
+ Base class for all algorithms that are used for planning subgoals
503
+ based on (1) a @PlannerAlgo that is used to sample candidate subgoals
504
+ and (2) a @ValueAlgo that is used to select one of the subgoals.
505
+ """
506
+ def __init__(
507
+ self,
508
+ planner_algo_class,
509
+ value_algo_class,
510
+ algo_config,
511
+ obs_config,
512
+ global_config,
513
+ obs_key_shapes,
514
+ ac_dim,
515
+ device,
516
+
517
+ ):
518
+ """
519
+ Args:
520
+ planner_algo_class (Algo class): algo class for the planner
521
+
522
+ value_algo_class (Algo class): algo class for the value network
523
+
524
+ algo_config (Config object): instance of Config corresponding to the algo section
525
+ of the config
526
+
527
+ obs_config (Config object): instance of Config corresponding to the observation
528
+ section of the config
529
+
530
+ global_config (Config object); global config
531
+
532
+ obs_key_shapes (OrderedDict): dictionary that maps input/output observation keys to shapes
533
+
534
+ ac_dim (int): action dimension
535
+
536
+ device: torch device
537
+ """
538
+ self.algo_config = algo_config
539
+ self.obs_config = obs_config
540
+ self.global_config = global_config
541
+
542
+ self.ac_dim = ac_dim
543
+ self.device = device
544
+
545
+ self.planner = planner_algo_class(
546
+ algo_config=algo_config.planner,
547
+ obs_config=obs_config.planner,
548
+ global_config=global_config,
549
+ obs_key_shapes=obs_key_shapes,
550
+ ac_dim=ac_dim,
551
+ device=device
552
+ )
553
+
554
+ self.value_net = value_algo_class(
555
+ algo_config=algo_config.value,
556
+ obs_config=obs_config.value,
557
+ global_config=global_config,
558
+ obs_key_shapes=obs_key_shapes,
559
+ ac_dim=ac_dim,
560
+ device=device
561
+ )
562
+
563
+ self.subgoal_shapes = self.planner.subgoal_shapes
564
+
565
+ def process_batch_for_training(self, batch):
566
+ """
567
+ Processes input batch from a data loader to filter out
568
+ relevant information and prepare the batch for training.
569
+
570
+ Args:
571
+ batch (dict): dictionary with torch.Tensors sampled
572
+ from a data loader
573
+
574
+ Returns:
575
+ input_batch (dict): processed and filtered batch that
576
+ will be used for training
577
+ """
578
+ input_batch = dict()
579
+
580
+ input_batch["planner"] = self.planner.process_batch_for_training(batch)
581
+ input_batch["value_net"] = self.value_net.process_batch_for_training(batch)
582
+
583
+ # we move to device first before float conversion because image observation modalities will be uint8 -
584
+ # this minimizes the amount of data transferred to GPU
585
+ return TensorUtils.to_float(TensorUtils.to_device(input_batch, self.device))
586
+
587
+ def train_on_batch(self, batch, epoch, validate=False):
588
+ """
589
+ Training on a single batch of data.
590
+
591
+ Args:
592
+ batch (dict): dictionary with torch.Tensors sampled
593
+ from a data loader and filtered by @process_batch_for_training
594
+
595
+ epoch (int): epoch number - required by some Algos that need
596
+ to perform staged training and early stopping
597
+
598
+ validate (bool): if True, don't perform any learning updates.
599
+
600
+ Returns:
601
+ info (dict): dictionary of relevant inputs, outputs, and losses
602
+ that might be relevant for logging
603
+ """
604
+ if validate:
605
+ assert not self.planner.nets.training
606
+ assert not self.value_net.nets.training
607
+
608
+ info = dict(planner=dict(), value_net=dict())
609
+
610
+ # train planner
611
+ info["planner"].update(self.planner.train_on_batch(batch["planner"], epoch, validate=validate))
612
+
613
+ # train value network
614
+ info["value_net"].update(self.value_net.train_on_batch(batch["value_net"], epoch, validate=validate))
615
+
616
+ return info
617
+
618
+ def log_info(self, info):
619
+ """
620
+ Process info dictionary from @train_on_batch to summarize
621
+ information to pass to tensorboard for logging.
622
+
623
+ Args:
624
+ info (dict): dictionary of info
625
+
626
+ Returns:
627
+ loss_log (dict): name -> summary statistic
628
+ """
629
+ loss = 0.
630
+
631
+ # planner
632
+ planner_log = self.planner.log_info(info["planner"])
633
+ planner_log = dict(("Planner/" + k, v) for k, v in planner_log.items())
634
+ loss += planner_log["Planner/Loss"]
635
+
636
+ # value network
637
+ value_net_log = self.value_net.log_info(info["value_net"])
638
+ value_net_log = dict(("ValueNetwork/" + k, v) for k, v in value_net_log.items())
639
+ loss += value_net_log["ValueNetwork/Loss"]
640
+ planner_log.update(value_net_log)
641
+
642
+ planner_log["Loss"] = loss
643
+ return planner_log
644
+
645
+ def on_epoch_end(self, epoch):
646
+ """
647
+ Called at the end of each epoch.
648
+ """
649
+ self.planner.on_epoch_end(epoch)
650
+ self.value_net.on_epoch_end(epoch)
651
+
652
+ def set_eval(self):
653
+ """
654
+ Prepare networks for evaluation.
655
+ """
656
+ self.planner.set_eval()
657
+ self.value_net.set_eval()
658
+
659
+ def set_train(self):
660
+ """
661
+ Prepare networks for training.
662
+ """
663
+ self.planner.set_train()
664
+ self.value_net.set_train()
665
+
666
+ def serialize(self):
667
+ """
668
+ Get dictionary of current model parameters.
669
+ """
670
+ return dict(
671
+ planner=self.planner.serialize(),
672
+ value_net=self.value_net.serialize(),
673
+ )
674
+
675
+ def deserialize(self, model_dict):
676
+ """
677
+ Load model from a checkpoint.
678
+
679
+ Args:
680
+ model_dict (dict): a dictionary saved by self.serialize() that contains
681
+ the same keys as @self.network_classes
682
+ """
683
+ self.planner.deserialize(model_dict["planner"])
684
+ self.value_net.deserialize(model_dict["value_net"])
685
+
686
+ def reset(self):
687
+ """
688
+ Reset algo state to prepare for environment rollouts.
689
+ """
690
+ self.planner.reset()
691
+ self.value_net.reset()
692
+
693
+ def __repr__(self):
694
+ """
695
+ Pretty print algorithm and network description.
696
+ """
697
+ msg = str(self.__class__.__name__)
698
+ import textwrap
699
+ return msg + "Planner:\n" + textwrap.indent(self.planner.__repr__(), ' ') + \
700
+ "\n\nValue Network:\n" + textwrap.indent(self.value_net.__repr__(), ' ')
701
+
702
+ def get_subgoal_predictions(self, obs_dict, goal_dict=None):
703
+ """
704
+ Takes a batch of observations and predicts a batch of subgoals.
705
+
706
+ Args:
707
+ obs_dict (dict): current observation
708
+ goal_dict (dict): (optional) goal
709
+
710
+ Returns:
711
+ subgoal prediction (dict): name -> Tensor [batch_size, ...]
712
+ """
713
+
714
+ num_samples = self.algo_config.num_samples
715
+
716
+ # sample subgoals from the planner (shape: [batch_size, num_samples, ...])
717
+ subgoals = self.sample_subgoals(obs_dict=obs_dict, goal_dict=goal_dict, num_samples=num_samples)
718
+
719
+ # stack subgoals to get all values in one forward pass (shape [batch_size * num_samples, ...])
720
+ k = list(obs_dict.keys())[0]
721
+ bsize = obs_dict[k].shape[0]
722
+ subgoals_tiled = TensorUtils.reshape_dimensions(subgoals, begin_axis=0, end_axis=1, target_dims=(bsize * num_samples,))
723
+
724
+ # also repeat goals if necessary
725
+ goal_tiled = None
726
+ if len(self.planner.goal_shapes) > 0:
727
+ goal_tiled = ObsUtils.repeat_and_stack_observation(goal_dict, n=num_samples)
728
+
729
+ # evaluate the value of each subgoal
730
+ subgoal_values = self.value_net.get_state_value(obs_dict=subgoals_tiled, goal_dict=goal_tiled).reshape(-1, num_samples)
731
+
732
+ # pick the best subgoal
733
+ best_index = torch.argmax(subgoal_values, dim=1)
734
+ best_subgoal = {k: subgoals[k][torch.arange(bsize), best_index] for k in subgoals}
735
+ return best_subgoal
736
+
737
+ def sample_subgoals(self, obs_dict, goal_dict, num_samples=1):
738
+ """
739
+ Sample @num_samples subgoals from the planner algo per observation.
740
+
741
+ Args:
742
+ obs_dict (dict): current observation
743
+ goal_dict (dict): (optional) goal
744
+
745
+ Returns:
746
+ subgoals (dict): name -> Tensor [batch_size, num_samples, ...]
747
+ """
748
+ return self.planner.sample_subgoals(obs_dict=obs_dict, goal_dict=goal_dict, num_samples=num_samples)
749
+
750
+ def get_state_value(self, obs_dict, goal_dict=None):
751
+ """
752
+ Get state value outputs.
753
+
754
+ Args:
755
+ obs_dict (dict): current observation
756
+ goal_dict (dict): (optional) goal
757
+
758
+ Returns:
759
+ value (torch.Tensor): value tensor
760
+ """
761
+ return self.value_net.get_state_value(obs_dict=obs_dict, goal_dict=goal_dict)
762
+
763
+ def get_state_action_value(self, obs_dict, actions, goal_dict=None):
764
+ """
765
+ Get state-action value outputs.
766
+
767
+ Args:
768
+ obs_dict (dict): current observation
769
+ actions (torch.Tensor): action
770
+ goal_dict (dict): (optional) goal
771
+
772
+ Returns:
773
+ value (torch.Tensor): value tensor
774
+ """
775
+ return self.value_net.get_state_action_value(obs_dict=obs_dict, actions=actions, goal_dict=goal_dict)
aloha-devel/robomimic/algo/hbc.py ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Implementation of Hierarchical Behavioral Cloning, where
3
+ a planner model outputs subgoals (future observations), and
4
+ an actor model is conditioned on the subgoals to try and
5
+ reach them. Largely based on the Generalization Through Imitation (GTI)
6
+ paper (see https://arxiv.org/abs/2003.06085).
7
+ """
8
+ import textwrap
9
+ import numpy as np
10
+ from collections import OrderedDict
11
+ from copy import deepcopy
12
+
13
+ import torch
14
+
15
+ import robomimic.utils.tensor_utils as TensorUtils
16
+ import robomimic.utils.obs_utils as ObsUtils
17
+ from robomimic.config.config import Config
18
+ from robomimic.algo import register_algo_factory_func, algo_name_to_factory_func, HierarchicalAlgo, GL_VAE
19
+
20
+
21
+ @register_algo_factory_func("hbc")
22
+ def algo_config_to_class(algo_config):
23
+ """
24
+ Maps algo config to the HBC algo class to instantiate, along with additional algo kwargs.
25
+
26
+ Args:
27
+ algo_config (Config instance): algo config
28
+
29
+ Returns:
30
+ algo_class: subclass of Algo
31
+ algo_kwargs (dict): dictionary of additional kwargs to pass to algorithm
32
+ """
33
+ pol_cls, _ = algo_name_to_factory_func("bc")(algo_config.actor)
34
+ plan_cls, _ = algo_name_to_factory_func("gl")(algo_config.planner)
35
+ return HBC, dict(policy_algo_class=pol_cls, planner_algo_class=plan_cls)
36
+
37
+
38
+ class HBC(HierarchicalAlgo):
39
+ """
40
+ Default HBC training, largely based on https://arxiv.org/abs/2003.06085
41
+ """
42
+ def __init__(
43
+ self,
44
+ planner_algo_class,
45
+ policy_algo_class,
46
+ algo_config,
47
+ obs_config,
48
+ global_config,
49
+ obs_key_shapes,
50
+ ac_dim,
51
+ device,
52
+ ):
53
+ """
54
+ Args:
55
+ planner_algo_class (Algo class): algo class for the planner
56
+
57
+ policy_algo_class (Algo class): algo class for the policy
58
+
59
+ algo_config (Config object): instance of Config corresponding to the algo section
60
+ of the config
61
+
62
+ obs_config (Config object): instance of Config corresponding to the observation
63
+ section of the config
64
+
65
+ global_config (Config object): global training config
66
+
67
+ obs_key_shapes (dict): dictionary that maps input/output observation keys to shapes
68
+
69
+ ac_dim (int): action dimension
70
+
71
+ device: torch device
72
+ """
73
+ self.algo_config = algo_config
74
+ self.obs_config = obs_config
75
+ self.global_config = global_config
76
+
77
+ self.ac_dim = ac_dim
78
+ self.device = device
79
+
80
+ self._subgoal_step_count = 0 # current step count for deciding when to update subgoal
81
+ self._current_subgoal = None # latest subgoal
82
+ self._subgoal_update_interval = self.algo_config.subgoal_update_interval # subgoal update frequency
83
+ self._subgoal_horizon = self.algo_config.planner.subgoal_horizon
84
+ self._actor_horizon = self.algo_config.actor.rnn.horizon
85
+
86
+ self._algo_mode = self.algo_config.mode
87
+ assert self._algo_mode in ["separate", "cascade"]
88
+
89
+ self.planner = planner_algo_class(
90
+ algo_config=algo_config.planner,
91
+ obs_config=obs_config.planner,
92
+ global_config=global_config,
93
+ obs_key_shapes=obs_key_shapes,
94
+ ac_dim=ac_dim,
95
+ device=device
96
+ )
97
+
98
+ # goal-conditional actor follows goals set by the planner
99
+ self.actor_goal_shapes = self.planner.subgoal_shapes
100
+ if self.algo_config.latent_subgoal.enabled:
101
+ assert planner_algo_class == GL_VAE # only VAE supported for now
102
+ self.actor_goal_shapes = OrderedDict(latent_subgoal=(self.planner.algo_config.vae.latent_dim,))
103
+
104
+ # only for the actor: override goal modalities and shapes to match the subgoal set by the planner
105
+ actor_obs_key_shapes = deepcopy(obs_key_shapes)
106
+ # make sure we are not modifying existing observation key shapes
107
+ for k in self.actor_goal_shapes:
108
+ if k in actor_obs_key_shapes:
109
+ assert actor_obs_key_shapes[k] == self.actor_goal_shapes[k]
110
+ actor_obs_key_shapes.update(self.actor_goal_shapes)
111
+
112
+ goal_obs_keys = {obs_modality: [] for obs_modality in ObsUtils.OBS_MODALITY_CLASSES.keys()}
113
+ for k in self.actor_goal_shapes.keys():
114
+ goal_obs_keys[ObsUtils.OBS_KEYS_TO_MODALITIES[k]].append(k)
115
+
116
+ actor_obs_config = deepcopy(obs_config.actor)
117
+ with actor_obs_config.unlocked():
118
+ actor_obs_config["goal"] = Config(**goal_obs_keys)
119
+
120
+ self.actor = policy_algo_class(
121
+ algo_config=algo_config.actor,
122
+ obs_config=actor_obs_config,
123
+ global_config=global_config,
124
+ obs_key_shapes=actor_obs_key_shapes,
125
+ ac_dim=ac_dim,
126
+ device=device,
127
+ )
128
+
129
+ def process_batch_for_training(self, batch):
130
+ """
131
+ Processes input batch from a data loader to filter out
132
+ relevant information and prepare the batch for training.
133
+
134
+ Args:
135
+ batch (dict): dictionary with torch.Tensors sampled
136
+ from a data loader
137
+
138
+ Returns:
139
+ input_batch (dict): processed and filtered batch that
140
+ will be used for training
141
+ """
142
+ input_batch = dict()
143
+
144
+ input_batch["planner"] = self.planner.process_batch_for_training(batch)
145
+ input_batch["actor"] = self.actor.process_batch_for_training(batch)
146
+
147
+ if self.algo_config.actor_use_random_subgoals:
148
+ # optionally use randomly sampled step between [1, seq_length] as policy goal
149
+ policy_subgoal_indices = torch.randint(
150
+ low=0, high=self.global_config.train.seq_length, size=(batch["actions"].shape[0],))
151
+ goal_obs = TensorUtils.gather_sequence(batch["next_obs"], policy_subgoal_indices)
152
+ goal_obs = TensorUtils.to_float(TensorUtils.to_device(goal_obs, self.device))
153
+ input_batch["actor"]["goal_obs"] = \
154
+ self.planner.get_actor_goal_for_training_from_processed_batch(
155
+ goal_obs,
156
+ use_latent_subgoals=self.algo_config.latent_subgoal.enabled,
157
+ use_prior_correction=self.algo_config.latent_subgoal.prior_correction.enabled,
158
+ num_prior_samples=self.algo_config.latent_subgoal.prior_correction.num_samples,
159
+ )
160
+ else:
161
+ # otherwise, use planner subgoal target as goal for the policy
162
+ input_batch["actor"]["goal_obs"] = \
163
+ self.planner.get_actor_goal_for_training_from_processed_batch(
164
+ input_batch["planner"],
165
+ use_latent_subgoals=self.algo_config.latent_subgoal.enabled,
166
+ use_prior_correction=self.algo_config.latent_subgoal.prior_correction.enabled,
167
+ num_prior_samples=self.algo_config.latent_subgoal.prior_correction.num_samples,
168
+ )
169
+
170
+ # we move to device first before float conversion because image observation modalities will be uint8 -
171
+ # this minimizes the amount of data transferred to GPU
172
+ return TensorUtils.to_float(TensorUtils.to_device(input_batch, self.device))
173
+
174
+ def train_on_batch(self, batch, epoch, validate=False):
175
+ """
176
+ Training on a single batch of data.
177
+
178
+ Args:
179
+ batch (dict): dictionary with torch.Tensors sampled
180
+ from a data loader and filtered by @process_batch_for_training
181
+
182
+ epoch (int): epoch number - required by some Algos that need
183
+ to perform staged training and early stopping
184
+
185
+ validate (bool): if True, don't perform any learning updates.
186
+
187
+ Returns:
188
+ info (dict): dictionary of relevant inputs, outputs, and losses
189
+ that might be relevant for logging
190
+ """
191
+ info = dict(planner=dict(), actor=dict())
192
+ # train planner
193
+ info["planner"].update(self.planner.train_on_batch(batch["planner"], epoch, validate=validate))
194
+
195
+ # train actor
196
+ if self._algo_mode == "separate":
197
+ # train low-level actor by getting subgoals from the dataset
198
+ info["actor"].update(self.actor.train_on_batch(batch["actor"], epoch, validate=validate))
199
+
200
+ elif self._algo_mode == "cascade":
201
+ # get predictions from the planner
202
+ with torch.no_grad():
203
+ batch["actor"]["goal_obs"] = self.planner.get_subgoal_predictions(
204
+ obs_dict=batch["planner"]["obs"], goal_dict=batch["planner"]["goal_obs"])
205
+
206
+ # train actor with the predicted goal
207
+ info["actor"].update(self.actor.train_on_batch(batch["actor"], epoch, validate=validate))
208
+
209
+ else:
210
+ raise NotImplementedError("algo mode {} is not implemented".format(self._algo_mode))
211
+
212
+ return info
213
+
214
+ def log_info(self, info):
215
+ """
216
+ Process info dictionary from @train_on_batch to summarize
217
+ information to pass to tensorboard for logging.
218
+
219
+ Args:
220
+ info (dict): dictionary of info
221
+
222
+ Returns:
223
+ loss_log (dict): name -> summary statistic
224
+ """
225
+ planner_log = dict()
226
+ actor_log = dict()
227
+ loss = 0.
228
+
229
+ planner_log = self.planner.log_info(info["planner"])
230
+ planner_log = dict(("Planner/" + k, v) for k, v in planner_log.items())
231
+ loss += planner_log["Planner/Loss"]
232
+
233
+ actor_log = self.actor.log_info(info["actor"])
234
+ actor_log = dict(("Actor/" + k, v) for k, v in actor_log.items())
235
+ loss += actor_log["Actor/Loss"]
236
+
237
+ planner_log.update(actor_log)
238
+ planner_log["Loss"] = loss
239
+ return planner_log
240
+
241
+ def on_epoch_end(self, epoch):
242
+ """
243
+ Called at the end of each epoch.
244
+ """
245
+ self.planner.on_epoch_end(epoch)
246
+ self.actor.on_epoch_end(epoch)
247
+
248
+ def set_eval(self):
249
+ """
250
+ Prepare networks for evaluation.
251
+ """
252
+ self.planner.set_eval()
253
+ self.actor.set_eval()
254
+
255
+ def set_train(self):
256
+ """
257
+ Prepare networks for training.
258
+ """
259
+ self.planner.set_train()
260
+ self.actor.set_train()
261
+
262
+ def serialize(self):
263
+ """
264
+ Get dictionary of current model parameters.
265
+ """
266
+ return dict(
267
+ planner=self.planner.serialize(),
268
+ actor=self.actor.serialize(),
269
+ )
270
+
271
+ def deserialize(self, model_dict):
272
+ """
273
+ Load model from a checkpoint.
274
+
275
+ Args:
276
+ model_dict (dict): a dictionary saved by self.serialize() that contains
277
+ the same keys as @self.network_classes
278
+ """
279
+ self.actor.deserialize(model_dict["actor"])
280
+ self.planner.deserialize(model_dict["planner"])
281
+
282
+ @property
283
+ def current_subgoal(self):
284
+ """
285
+ Return the current subgoal (at rollout time) with shape (batch, ...)
286
+ """
287
+ return { k : self._current_subgoal[k].clone() for k in self._current_subgoal }
288
+
289
+ @current_subgoal.setter
290
+ def current_subgoal(self, sg):
291
+ """
292
+ Sets the current subgoal being used by the actor.
293
+ """
294
+ for k, v in sg.items():
295
+ if not self.algo_config.latent_subgoal.enabled:
296
+ # subgoal should only match subgoal shapes if not using latent subgoals
297
+ assert list(v.shape[1:]) == list(self.planner.subgoal_shapes[k])
298
+ # subgoal shapes should always match actor goal shapes
299
+ assert list(v.shape[1:]) == list(self.actor_goal_shapes[k])
300
+ self._current_subgoal = { k : sg[k].clone() for k in sg }
301
+
302
+ def get_action(self, obs_dict, goal_dict=None):
303
+ """
304
+ Get policy action outputs.
305
+
306
+ Args:
307
+ obs_dict (dict): current observation
308
+ goal_dict (dict): (optional) goal
309
+
310
+ Returns:
311
+ action (torch.Tensor): action tensor
312
+ """
313
+ if self._current_subgoal is None or self._subgoal_step_count % self._subgoal_update_interval == 0:
314
+ # update current subgoal
315
+ self.current_subgoal = self.planner.get_subgoal_predictions(obs_dict=obs_dict, goal_dict=goal_dict)
316
+
317
+ action = self.actor.get_action(obs_dict=obs_dict, goal_dict=self.current_subgoal)
318
+ self._subgoal_step_count += 1
319
+ return action
320
+
321
+ def reset(self):
322
+ """
323
+ Reset algo state to prepare for environment rollouts.
324
+ """
325
+ self._current_subgoal = None
326
+ self._subgoal_step_count = 0
327
+ self.planner.reset()
328
+ self.actor.reset()
329
+
330
+ def __repr__(self):
331
+ """
332
+ Pretty print algorithm and network description.
333
+ """
334
+ msg = str(self.__class__.__name__)
335
+ msg += "(subgoal_horizon={}, actor_horizon={}, subgoal_update_interval={}, mode={}, " \
336
+ "actor_use_random_subgoals={})\n".format(
337
+ self._subgoal_horizon,
338
+ self._actor_horizon,
339
+ self._subgoal_update_interval,
340
+ self._algo_mode,
341
+ self.algo_config.actor_use_random_subgoals
342
+ )
343
+ return msg + "Planner:\n" + textwrap.indent(self.planner.__repr__(), ' ') + \
344
+ "\n\nPolicy:\n" + textwrap.indent(self.actor.__repr__(), ' ')
aloha-devel/robomimic/algo/iql.py ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Implementation of Implicit Q-Learning (IQL).
3
+ Based off of https://github.com/rail-berkeley/rlkit/blob/master/rlkit/torch/sac/iql_trainer.py.
4
+ (Paper - https://arxiv.org/abs/2110.06169).
5
+ """
6
+ import numpy as np
7
+ from collections import OrderedDict
8
+
9
+ import torch
10
+ import torch.nn as nn
11
+ import torch.nn.functional as F
12
+
13
+ import robomimic.models.policy_nets as PolicyNets
14
+ import robomimic.models.value_nets as ValueNets
15
+ import robomimic.utils.obs_utils as ObsUtils
16
+ import robomimic.utils.tensor_utils as TensorUtils
17
+ import robomimic.utils.torch_utils as TorchUtils
18
+ from robomimic.algo import register_algo_factory_func, ValueAlgo, PolicyAlgo
19
+
20
+
21
+ @register_algo_factory_func("iql")
22
+ def algo_config_to_class(algo_config):
23
+ """
24
+ Maps algo config to the IQL algo class to instantiate, along with additional algo kwargs.
25
+
26
+ Args:
27
+ algo_config (Config instance): algo config
28
+
29
+ Returns:
30
+ algo_class: subclass of Algo
31
+ algo_kwargs (dict): dictionary of additional kwargs to pass to algorithm
32
+ """
33
+ return IQL, {}
34
+
35
+
36
+ class IQL(PolicyAlgo, ValueAlgo):
37
+ def _create_networks(self):
38
+ """
39
+ Creates networks and places them into @self.nets.
40
+
41
+ Networks for this algo: critic (potentially ensemble), actor, value function
42
+ """
43
+
44
+ # Create nets
45
+ self.nets = nn.ModuleDict()
46
+
47
+ # Assemble args to pass to actor
48
+ actor_args = dict(self.algo_config.actor.net.common)
49
+
50
+ # Add network-specific args and define network class
51
+ if self.algo_config.actor.net.type == "gaussian":
52
+ actor_cls = PolicyNets.GaussianActorNetwork
53
+ actor_args.update(dict(self.algo_config.actor.net.gaussian))
54
+ elif self.algo_config.actor.net.type == "gmm":
55
+ actor_cls = PolicyNets.GMMActorNetwork
56
+ actor_args.update(dict(self.algo_config.actor.net.gmm))
57
+ else:
58
+ # Unsupported actor type!
59
+ raise ValueError(f"Unsupported actor requested. "
60
+ f"Requested: {self.algo_config.actor.net.type}, "
61
+ f"valid options are: {['gaussian', 'gmm']}")
62
+
63
+ # Actor
64
+ self.nets["actor"] = actor_cls(
65
+ obs_shapes=self.obs_shapes,
66
+ goal_shapes=self.goal_shapes,
67
+ ac_dim=self.ac_dim,
68
+ mlp_layer_dims=self.algo_config.actor.layer_dims,
69
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
70
+ **actor_args,
71
+ )
72
+
73
+ # Critics
74
+ self.nets["critic"] = nn.ModuleList()
75
+ self.nets["critic_target"] = nn.ModuleList()
76
+ for _ in range(self.algo_config.critic.ensemble.n):
77
+ for net_list in (self.nets["critic"], self.nets["critic_target"]):
78
+ critic = ValueNets.ActionValueNetwork(
79
+ obs_shapes=self.obs_shapes,
80
+ ac_dim=self.ac_dim,
81
+ mlp_layer_dims=self.algo_config.critic.layer_dims,
82
+ goal_shapes=self.goal_shapes,
83
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
84
+ )
85
+ net_list.append(critic)
86
+
87
+ # Value function network
88
+ self.nets["vf"] = ValueNets.ValueNetwork(
89
+ obs_shapes=self.obs_shapes,
90
+ mlp_layer_dims=self.algo_config.critic.layer_dims,
91
+ goal_shapes=self.goal_shapes,
92
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
93
+ )
94
+
95
+ # Send networks to appropriate device
96
+ self.nets = self.nets.float().to(self.device)
97
+
98
+ # sync target networks at beginning of training
99
+ with torch.no_grad():
100
+ for critic, critic_target in zip(self.nets["critic"], self.nets["critic_target"]):
101
+ TorchUtils.hard_update(
102
+ source=critic,
103
+ target=critic_target,
104
+ )
105
+
106
+ def process_batch_for_training(self, batch):
107
+ """
108
+ Processes input batch from a data loader to filter out relevant info and prepare the batch for training.
109
+
110
+ Args:
111
+ batch (dict): dictionary with torch.Tensors sampled
112
+ from a data loader
113
+
114
+ Returns:
115
+ input_batch (dict): processed and filtered batch that
116
+ will be used for training
117
+ """
118
+
119
+ input_batch = dict()
120
+
121
+ # remove temporal batches for all
122
+ input_batch["obs"] = {k: batch["obs"][k][:, 0, :] for k in batch["obs"]}
123
+ input_batch["next_obs"] = {k: batch["next_obs"][k][:, 0, :] for k in batch["next_obs"]}
124
+ input_batch["goal_obs"] = batch.get("goal_obs", None) # goals may not be present
125
+ input_batch["actions"] = batch["actions"][:, 0, :]
126
+ input_batch["dones"] = batch["dones"][:, 0]
127
+ input_batch["rewards"] = batch["rewards"][:, 0]
128
+
129
+ return TensorUtils.to_device(TensorUtils.to_float(input_batch), self.device)
130
+
131
+ def train_on_batch(self, batch, epoch, validate=False):
132
+ """
133
+ Training on a single batch of data.
134
+
135
+ Args:
136
+ batch (dict): dictionary with torch.Tensors sampled
137
+ from a data loader and filtered by @process_batch_for_training
138
+
139
+ epoch (int): epoch number - required by some Algos that need
140
+ to perform staged training and early stopping
141
+
142
+ validate (bool): if True, don't perform any learning updates.
143
+
144
+ Returns:
145
+ info (dict): dictionary of relevant inputs, outputs, and losses
146
+ that might be relevant for logging
147
+ """
148
+ info = OrderedDict()
149
+
150
+ # Set the correct context for this training step
151
+ with TorchUtils.maybe_no_grad(no_grad=validate):
152
+ # Always run super call first
153
+ info = super().train_on_batch(batch, epoch, validate=validate)
154
+
155
+ # Compute loss for critic(s)
156
+ critic_losses, vf_loss, critic_info = self._compute_critic_loss(batch)
157
+ # Compute loss for actor
158
+ actor_loss, actor_info = self._compute_actor_loss(batch, critic_info)
159
+
160
+ if not validate:
161
+ # Critic update
162
+ self._update_critic(critic_losses, vf_loss)
163
+
164
+ # Actor update
165
+ self._update_actor(actor_loss)
166
+
167
+ # Update info
168
+ info.update(actor_info)
169
+ info.update(critic_info)
170
+
171
+ # Return stats
172
+ return info
173
+
174
+ def _compute_critic_loss(self, batch):
175
+ """
176
+ Helper function for computing Q and V losses. Called by @train_on_batch
177
+
178
+ Args:
179
+ batch (dict): dictionary with torch.Tensors sampled
180
+ from a data loader and filtered by @process_batch_for_training
181
+
182
+ Returns:
183
+ critic_losses (list): list of critic (Q function) losses
184
+ vf_loss (torch.Tensor): value function loss
185
+ info (dict): dictionary of Q / V predictions and losses
186
+ """
187
+ info = OrderedDict()
188
+
189
+ # get batch values
190
+ obs = batch["obs"]
191
+ actions = batch["actions"]
192
+ next_obs = batch["next_obs"]
193
+ goal_obs = batch["goal_obs"]
194
+ rewards = torch.unsqueeze(batch["rewards"], 1)
195
+ dones = torch.unsqueeze(batch["dones"], 1)
196
+
197
+ # Q predictions
198
+ pred_qs = [critic(obs_dict=obs, acts=actions, goal_dict=goal_obs)
199
+ for critic in self.nets["critic"]]
200
+
201
+ info["critic/critic1_pred"] = pred_qs[0].mean()
202
+
203
+ # Q target values
204
+ target_vf_pred = self.nets["vf"](obs_dict=next_obs, goal_dict=goal_obs).detach()
205
+ q_target = rewards + (1. - dones) * self.algo_config.discount * target_vf_pred
206
+ q_target = q_target.detach()
207
+
208
+ # Q losses
209
+ critic_losses = []
210
+ td_loss_fcn = nn.SmoothL1Loss() if self.algo_config.critic.use_huber else nn.MSELoss()
211
+ for (i, q_pred) in enumerate(pred_qs):
212
+ # Calculate td error loss
213
+ td_loss = td_loss_fcn(q_pred, q_target)
214
+ info[f"critic/critic{i+1}_loss"] = td_loss
215
+ critic_losses.append(td_loss)
216
+
217
+ # V predictions
218
+ pred_qs = [critic(obs_dict=obs, acts=actions, goal_dict=goal_obs)
219
+ for critic in self.nets["critic_target"]]
220
+ q_pred, _ = torch.cat(pred_qs, dim=1).min(dim=1, keepdim=True)
221
+ q_pred = q_pred.detach()
222
+ vf_pred = self.nets["vf"](obs)
223
+
224
+ # V losses: expectile regression. see section 4.1 in https://arxiv.org/pdf/2110.06169.pdf
225
+ vf_err = vf_pred - q_pred
226
+ vf_sign = (vf_err > 0).float()
227
+ vf_weight = (1 - vf_sign) * self.algo_config.vf_quantile + vf_sign * (1 - self.algo_config.vf_quantile)
228
+ vf_loss = (vf_weight * (vf_err ** 2)).mean()
229
+
230
+ # update logs for V loss
231
+ info["vf/q_pred"] = q_pred
232
+ info["vf/v_pred"] = vf_pred
233
+ info["vf/v_loss"] = vf_loss
234
+
235
+ # Return stats
236
+ return critic_losses, vf_loss, info
237
+
238
+ def _update_critic(self, critic_losses, vf_loss):
239
+ """
240
+ Helper function for updating critic and vf networks. Called by @train_on_batch
241
+
242
+ Args:
243
+ critic_losses (list): list of critic (Q function) losses
244
+ vf_loss (torch.Tensor): value function loss
245
+ """
246
+
247
+ # update ensemble of critics
248
+ for (critic_loss, critic, critic_target, optimizer) in zip(
249
+ critic_losses, self.nets["critic"], self.nets["critic_target"], self.optimizers["critic"]
250
+ ):
251
+ TorchUtils.backprop_for_loss(
252
+ net=critic,
253
+ optim=optimizer,
254
+ loss=critic_loss,
255
+ max_grad_norm=self.algo_config.critic.max_gradient_norm,
256
+ retain_graph=False,
257
+ )
258
+
259
+ # update target network
260
+ with torch.no_grad():
261
+ TorchUtils.soft_update(source=critic, target=critic_target, tau=self.algo_config.target_tau)
262
+
263
+ # update V function network
264
+ TorchUtils.backprop_for_loss(
265
+ net=self.nets["vf"],
266
+ optim=self.optimizers["vf"],
267
+ loss=vf_loss,
268
+ max_grad_norm=self.algo_config.critic.max_gradient_norm,
269
+ retain_graph=False,
270
+ )
271
+
272
+ def _compute_actor_loss(self, batch, critic_info):
273
+ """
274
+ Helper function for computing actor loss. Called by @train_on_batch
275
+
276
+ Args:
277
+ batch (dict): dictionary with torch.Tensors sampled
278
+ from a data loader and filtered by @process_batch_for_training
279
+
280
+ critic_info (dict): dictionary containing Q and V function predictions,
281
+ to be used for computing advantage estimates
282
+
283
+ Returns:
284
+ actor_loss (torch.Tensor): actor loss
285
+ info (dict): dictionary of actor losses, log_probs, advantages, and weights
286
+ """
287
+ info = OrderedDict()
288
+
289
+ # compute log probability of batch actions
290
+ dist = self.nets["actor"].forward_train(obs_dict=batch["obs"], goal_dict=batch["goal_obs"])
291
+ log_prob = dist.log_prob(batch["actions"])
292
+
293
+ info["actor/log_prob"] = log_prob.mean()
294
+
295
+ # compute advantage estimate
296
+ q_pred = critic_info["vf/q_pred"]
297
+ v_pred = critic_info["vf/v_pred"]
298
+ adv = q_pred - v_pred
299
+
300
+ # compute weights
301
+ weights = self._get_adv_weights(adv)
302
+
303
+ # compute advantage weighted actor loss. disable gradients through weights
304
+ actor_loss = (-log_prob * weights.detach()).mean()
305
+
306
+ info["actor/loss"] = actor_loss
307
+
308
+ # log adv-related values
309
+ info["adv/adv"] = adv
310
+ info["adv/adv_weight"] = weights
311
+
312
+ # Return stats
313
+ return actor_loss, info
314
+
315
+ def _update_actor(self, actor_loss):
316
+ """
317
+ Helper function for updating actor network. Called by @train_on_batch
318
+
319
+ Args:
320
+ actor_loss (torch.Tensor): actor loss
321
+ """
322
+
323
+ TorchUtils.backprop_for_loss(
324
+ net=self.nets["actor"],
325
+ optim=self.optimizers["actor"],
326
+ loss=actor_loss,
327
+ max_grad_norm=self.algo_config.actor.max_gradient_norm,
328
+ )
329
+
330
+ def _get_adv_weights(self, adv):
331
+ """
332
+ Helper function for computing advantage weights. Called by @_compute_actor_loss
333
+
334
+ Args:
335
+ adv (torch.Tensor): raw advantage estimates
336
+
337
+ Returns:
338
+ weights (torch.Tensor): weights computed based on advantage estimates,
339
+ in shape (B,) where B is batch size
340
+ """
341
+
342
+ # clip raw advantage values
343
+ if self.algo_config.adv.clip_adv_value is not None:
344
+ adv = adv.clamp(max=self.algo_config.adv.clip_adv_value)
345
+
346
+ # compute weights based on advantage values
347
+ beta = self.algo_config.adv.beta # temprature factor
348
+ weights = torch.exp(adv / beta)
349
+
350
+ # clip final weights
351
+ if self.algo_config.adv.use_final_clip is True:
352
+ weights = weights.clamp(-100.0, 100.0)
353
+
354
+ # reshape from (B, 1) to (B,)
355
+ return weights[:, 0]
356
+
357
+ def log_info(self, info):
358
+ """
359
+ Process info dictionary from @train_on_batch to summarize
360
+ information to pass to tensorboard for logging.
361
+
362
+ Args:
363
+ info (dict): dictionary of info
364
+
365
+ Returns:
366
+ loss_log (dict): name -> summary statistic
367
+ """
368
+ log = OrderedDict()
369
+
370
+ log["actor/log_prob"] = info["actor/log_prob"].item()
371
+ log["actor/loss"] = info["actor/loss"].item()
372
+
373
+ log["critic/critic1_pred"] = info["critic/critic1_pred"].item()
374
+ log["critic/critic1_loss"] = info["critic/critic1_loss"].item()
375
+
376
+ log["vf/v_loss"] = info["vf/v_loss"].item()
377
+
378
+ self._log_data_attributes(log, info, "vf/q_pred")
379
+ self._log_data_attributes(log, info, "vf/v_pred")
380
+ self._log_data_attributes(log, info, "adv/adv")
381
+ self._log_data_attributes(log, info, "adv/adv_weight")
382
+
383
+ return log
384
+
385
+ def _log_data_attributes(self, log, info, key):
386
+ """
387
+ Helper function for logging statistics. Moodifies log in-place
388
+
389
+ Args:
390
+ log (dict): existing log dictionary
391
+ log (dict): existing dictionary of tensors containing raw stats
392
+ key (str): key to log
393
+ """
394
+ log[key + "/max"] = info[key].max().item()
395
+ log[key + "/min"] = info[key].min().item()
396
+ log[key + "/mean"] = info[key].mean().item()
397
+ log[key + "/std"] = info[key].std().item()
398
+
399
+ def on_epoch_end(self, epoch):
400
+ """
401
+ Called at the end of each epoch.
402
+ """
403
+
404
+ # LR scheduling updates
405
+ for lr_sc in self.lr_schedulers["critic"]:
406
+ if lr_sc is not None:
407
+ lr_sc.step()
408
+
409
+ if self.lr_schedulers["vf"] is not None:
410
+ self.lr_schedulers["vf"].step()
411
+
412
+ if self.lr_schedulers["actor"] is not None:
413
+ self.lr_schedulers["actor"].step()
414
+
415
+ def get_action(self, obs_dict, goal_dict=None):
416
+ """
417
+ Get policy action outputs.
418
+
419
+ Args:
420
+ obs_dict (dict): current observation
421
+ goal_dict (dict): (optional) goal
422
+
423
+ Returns:
424
+ action (torch.Tensor): action tensor
425
+ """
426
+ assert not self.nets.training
427
+
428
+ return self.nets["actor"](obs_dict=obs_dict, goal_dict=goal_dict)
aloha-devel/robomimic/algo/iris.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Implementation of IRIS (https://arxiv.org/abs/1911.05321).
3
+ """
4
+ import numpy as np
5
+ from collections import OrderedDict
6
+ from copy import deepcopy
7
+
8
+ import torch
9
+
10
+ import robomimic.utils.tensor_utils as TensorUtils
11
+ import robomimic.utils.obs_utils as ObsUtils
12
+ from robomimic.config.config import Config
13
+ from robomimic.algo import register_algo_factory_func, algo_name_to_factory_func, HBC, ValuePlanner, ValueAlgo, GL_VAE
14
+
15
+
16
+ @register_algo_factory_func("iris")
17
+ def algo_config_to_class(algo_config):
18
+ """
19
+ Maps algo config to the IRIS algo class to instantiate, along with additional algo kwargs.
20
+
21
+ Args:
22
+ algo_config (Config instance): algo config
23
+
24
+ Returns:
25
+ algo_class: subclass of Algo
26
+ algo_kwargs (dict): dictionary of additional kwargs to pass to algorithm
27
+ """
28
+ pol_cls, _ = algo_name_to_factory_func("bc")(algo_config.actor)
29
+ plan_cls, _ = algo_name_to_factory_func("gl")(algo_config.value_planner.planner)
30
+ value_cls, _ = algo_name_to_factory_func("bcq")(algo_config.value_planner.value)
31
+ return IRIS, dict(policy_algo_class=pol_cls, planner_algo_class=plan_cls, value_algo_class=value_cls)
32
+
33
+
34
+ class IRIS(HBC, ValueAlgo):
35
+ """
36
+ Implementation of IRIS (https://arxiv.org/abs/1911.05321).
37
+ """
38
+ def __init__(
39
+ self,
40
+ planner_algo_class,
41
+ value_algo_class,
42
+ policy_algo_class,
43
+ algo_config,
44
+ obs_config,
45
+ global_config,
46
+ obs_key_shapes,
47
+ ac_dim,
48
+ device,
49
+ ):
50
+ """
51
+ Args:
52
+ planner_algo_class (Algo class): algo class for the planner
53
+
54
+ policy_algo_class (Algo class): algo class for the policy
55
+
56
+ algo_config (Config object): instance of Config corresponding to the algo section
57
+ of the config
58
+
59
+ obs_config (Config object): instance of Config corresponding to the observation
60
+ section of the config
61
+
62
+ global_config (Config object): global training config
63
+
64
+ obs_key_shapes (OrderedDict): dictionary that maps input/output observation keys to shapes
65
+
66
+ ac_dim (int): action dimension
67
+
68
+ device: torch device
69
+ """
70
+ self.algo_config = algo_config
71
+ self.obs_config = obs_config
72
+ self.global_config = global_config
73
+
74
+ self.ac_dim = ac_dim
75
+ self.device = device
76
+
77
+ self._subgoal_step_count = 0 # current step count for deciding when to update subgoal
78
+ self._current_subgoal = None # latest subgoal
79
+ self._subgoal_update_interval = self.algo_config.subgoal_update_interval # subgoal update frequency
80
+ self._subgoal_horizon = self.algo_config.value_planner.planner.subgoal_horizon
81
+ self._actor_horizon = self.algo_config.actor.rnn.horizon
82
+
83
+ self._algo_mode = self.algo_config.mode
84
+ assert self._algo_mode in ["separate", "cascade"]
85
+
86
+ self.planner = ValuePlanner(
87
+ planner_algo_class=planner_algo_class,
88
+ value_algo_class=value_algo_class,
89
+ algo_config=algo_config.value_planner,
90
+ obs_config=obs_config.value_planner,
91
+ global_config=global_config,
92
+ obs_key_shapes=obs_key_shapes,
93
+ ac_dim=ac_dim,
94
+ device=device
95
+ )
96
+
97
+ self.actor_goal_shapes = self.planner.subgoal_shapes
98
+ assert not algo_config.latent_subgoal.enabled, "IRIS does not support latent subgoals"
99
+
100
+ # only for the actor: override goal modalities and shapes to match the subgoal set by the planner
101
+ actor_obs_key_shapes = deepcopy(obs_key_shapes)
102
+ # make sure we are not modifying existing observation key shapes
103
+ for k in self.actor_goal_shapes:
104
+ if k in actor_obs_key_shapes:
105
+ assert actor_obs_key_shapes[k] == self.actor_goal_shapes[k]
106
+ actor_obs_key_shapes.update(self.actor_goal_shapes)
107
+
108
+ goal_modalities = {obs_modality: [] for obs_modality in ObsUtils.OBS_MODALITY_CLASSES.keys()}
109
+ for k in self.actor_goal_shapes.keys():
110
+ goal_modalities[ObsUtils.OBS_KEYS_TO_MODALITIES[k]].append(k)
111
+
112
+ actor_obs_config = deepcopy(obs_config.actor)
113
+ with actor_obs_config.unlocked():
114
+ actor_obs_config["goal"] = Config(**goal_modalities)
115
+
116
+ self.actor = policy_algo_class(
117
+ algo_config=algo_config.actor,
118
+ obs_config=actor_obs_config,
119
+ global_config=global_config,
120
+ obs_key_shapes=actor_obs_key_shapes,
121
+ ac_dim=ac_dim,
122
+ device=device
123
+ )
124
+
125
+ def process_batch_for_training(self, batch):
126
+ """
127
+ Processes input batch from a data loader to filter out
128
+ relevant information and prepare the batch for training.
129
+
130
+ Args:
131
+ batch (dict): dictionary with torch.Tensors sampled
132
+ from a data loader
133
+
134
+ Returns:
135
+ input_batch (dict): processed and filtered batch that
136
+ will be used for training
137
+ """
138
+ input_batch = dict()
139
+
140
+ input_batch["planner"] = self.planner.process_batch_for_training(batch)
141
+ input_batch["actor"] = self.actor.process_batch_for_training(batch)
142
+
143
+ if self.algo_config.actor_use_random_subgoals:
144
+ # optionally use randomly sampled step between [1, seq_length] as policy goal
145
+ policy_subgoal_indices = torch.randint(
146
+ low=0, high=self.global_config.train.seq_length, size=(batch["actions"].shape[0],))
147
+ goal_obs = TensorUtils.gather_sequence(batch["next_obs"], policy_subgoal_indices)
148
+ goal_obs = TensorUtils.to_float(TensorUtils.to_device(goal_obs, self.device))
149
+ input_batch["actor"]["goal_obs"] = goal_obs
150
+ else:
151
+ # otherwise, use planner subgoal target as goal for the policy
152
+ input_batch["actor"]["goal_obs"] = input_batch["planner"]["planner"]["target_subgoals"]
153
+
154
+ # we move to device first before float conversion because image observation modalities will be uint8 -
155
+ # this minimizes the amount of data transferred to GPU
156
+ return TensorUtils.to_float(TensorUtils.to_device(input_batch, self.device))
157
+
158
+ def get_state_value(self, obs_dict, goal_dict=None):
159
+ """
160
+ Get state value outputs.
161
+
162
+ Args:
163
+ obs_dict (dict): current observation
164
+ goal_dict (dict): (optional) goal
165
+
166
+ Returns:
167
+ value (torch.Tensor): value tensor
168
+ """
169
+ return self.planner.get_state_value(obs_dict=obs_dict, goal_dict=goal_dict)
170
+
171
+ def get_state_action_value(self, obs_dict, actions, goal_dict=None):
172
+ """
173
+ Get state-action value outputs.
174
+
175
+ Args:
176
+ obs_dict (dict): current observation
177
+ actions (torch.Tensor): action
178
+ goal_dict (dict): (optional) goal
179
+
180
+ Returns:
181
+ value (torch.Tensor): value tensor
182
+ """
183
+ return self.planner.get_state_action_value(obs_dict=obs_dict, actions=actions, goal_dict=goal_dict)
aloha-devel/robomimic/algo/td3_bc.py ADDED
@@ -0,0 +1,567 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Implementation of TD3-BC.
3
+ Based on https://github.com/sfujim/TD3_BC
4
+ (Paper - https://arxiv.org/abs/1812.02900).
5
+
6
+ Note that several parts are exactly the same as the BCQ implementation,
7
+ such as @_create_critics, @process_batch_for_training, and
8
+ @_train_critic_on_batch. They are replicated here (instead of subclassing
9
+ from the BCQ algo class) to be explicit and have implementation details
10
+ self-contained in this file.
11
+ """
12
+ from collections import OrderedDict
13
+
14
+ import torch
15
+ import torch.nn as nn
16
+ import torch.nn.functional as F
17
+
18
+ import robomimic.models.obs_nets as ObsNets
19
+ import robomimic.models.policy_nets as PolicyNets
20
+ import robomimic.models.value_nets as ValueNets
21
+ import robomimic.models.vae_nets as VAENets
22
+ import robomimic.utils.tensor_utils as TensorUtils
23
+ import robomimic.utils.torch_utils as TorchUtils
24
+ import robomimic.utils.obs_utils as ObsUtils
25
+ import robomimic.utils.loss_utils as LossUtils
26
+
27
+ from robomimic.algo import register_algo_factory_func, PolicyAlgo, ValueAlgo
28
+
29
+
30
+ @register_algo_factory_func("td3_bc")
31
+ def algo_config_to_class(algo_config):
32
+ """
33
+ Maps algo config to the TD3_BC algo class to instantiate, along with additional algo kwargs.
34
+
35
+ Args:
36
+ algo_config (Config instance): algo config
37
+
38
+ Returns:
39
+ algo_class: subclass of Algo
40
+ algo_kwargs (dict): dictionary of additional kwargs to pass to algorithm
41
+ """
42
+ # only one variant of TD3_BC for now
43
+ return TD3_BC, {}
44
+
45
+
46
+ class TD3_BC(PolicyAlgo, ValueAlgo):
47
+ """
48
+ Default TD3_BC training, based on https://arxiv.org/abs/2106.06860 and
49
+ https://github.com/sfujim/TD3_BC.
50
+ """
51
+ def __init__(self, **kwargs):
52
+ PolicyAlgo.__init__(self, **kwargs)
53
+
54
+ # save the discount factor - it may be overriden later
55
+ self.set_discount(self.algo_config.discount)
56
+
57
+ # initialize actor update counter. This is used to train the actor at a lower freq than critic
58
+ self.actor_update_counter = 0
59
+
60
+ def _create_networks(self):
61
+ """
62
+ Creates networks and places them into @self.nets.
63
+ """
64
+ self.nets = nn.ModuleDict()
65
+
66
+ self._create_critics()
67
+ self._create_actor()
68
+
69
+ # sync target networks at beginning of training
70
+ with torch.no_grad():
71
+ for critic_ind in range(len(self.nets["critic"])):
72
+ TorchUtils.hard_update(
73
+ source=self.nets["critic"][critic_ind],
74
+ target=self.nets["critic_target"][critic_ind],
75
+ )
76
+
77
+ TorchUtils.hard_update(
78
+ source=self.nets["actor"],
79
+ target=self.nets["actor_target"],
80
+ )
81
+
82
+ self.nets = self.nets.float().to(self.device)
83
+
84
+ def _create_critics(self):
85
+ """
86
+ Called in @_create_networks to make critic networks.
87
+
88
+ Exactly the same as BCQ.
89
+ """
90
+ critic_class = ValueNets.ActionValueNetwork
91
+ critic_args = dict(
92
+ obs_shapes=self.obs_shapes,
93
+ ac_dim=self.ac_dim,
94
+ mlp_layer_dims=self.algo_config.critic.layer_dims,
95
+ value_bounds=self.algo_config.critic.value_bounds,
96
+ goal_shapes=self.goal_shapes,
97
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
98
+ )
99
+
100
+ # Q network ensemble and target ensemble
101
+ self.nets["critic"] = nn.ModuleList()
102
+ self.nets["critic_target"] = nn.ModuleList()
103
+ for _ in range(self.algo_config.critic.ensemble.n):
104
+ critic = critic_class(**critic_args)
105
+ self.nets["critic"].append(critic)
106
+
107
+ critic_target = critic_class(**critic_args)
108
+ self.nets["critic_target"].append(critic_target)
109
+
110
+ def _create_actor(self):
111
+ """
112
+ Called in @_create_networks to make actor network.
113
+ """
114
+ actor_class = PolicyNets.ActorNetwork
115
+ actor_args = dict(
116
+ obs_shapes=self.obs_shapes,
117
+ goal_shapes=self.goal_shapes,
118
+ ac_dim=self.ac_dim,
119
+ mlp_layer_dims=self.algo_config.actor.layer_dims,
120
+ encoder_kwargs=ObsUtils.obs_encoder_kwargs_from_config(self.obs_config.encoder),
121
+ )
122
+
123
+ self.nets["actor"] = actor_class(**actor_args)
124
+ self.nets["actor_target"] = actor_class(**actor_args)
125
+
126
+ def _check_epoch(self, net_name, epoch):
127
+ """
128
+ Helper function to check whether backprop should happen this epoch.
129
+
130
+ Args:
131
+ net_name (str): name of network in @self.nets and @self.optim_params
132
+ epoch (int): epoch number
133
+ """
134
+ epoch_start_check = (self.optim_params[net_name]["start_epoch"] == -1) or (epoch >= self.optim_params[net_name]["start_epoch"])
135
+ epoch_end_check = (self.optim_params[net_name]["end_epoch"] == -1) or (epoch < self.optim_params[net_name]["end_epoch"])
136
+ return (epoch_start_check and epoch_end_check)
137
+
138
+ def set_discount(self, discount):
139
+ """
140
+ Useful function to modify discount factor if necessary (e.g. for n-step returns).
141
+ """
142
+ self.discount = discount
143
+
144
+ def process_batch_for_training(self, batch):
145
+ """
146
+ Processes input batch from a data loader to filter out
147
+ relevant information and prepare the batch for training.
148
+
149
+ Exactly the same as BCQ.
150
+
151
+ Args:
152
+ batch (dict): dictionary with torch.Tensors sampled
153
+ from a data loader
154
+
155
+ Returns:
156
+ input_batch (dict): processed and filtered batch that
157
+ will be used for training
158
+ """
159
+ input_batch = dict()
160
+
161
+ # n-step returns (default is 1)
162
+ n_step = self.algo_config.n_step
163
+ assert batch["actions"].shape[1] >= n_step
164
+
165
+ # remove temporal batches for all
166
+ input_batch["obs"] = {k: batch["obs"][k][:, 0, :] for k in batch["obs"]}
167
+ input_batch["next_obs"] = {k: batch["next_obs"][k][:, n_step - 1, :] for k in batch["next_obs"]}
168
+ input_batch["goal_obs"] = batch.get("goal_obs", None) # goals may not be present
169
+ input_batch["actions"] = batch["actions"][:, 0, :]
170
+
171
+ # note: ensure scalar signals (rewards, done) retain last dimension of 1 to be compatible with model outputs
172
+
173
+ # single timestep reward is discounted sum of intermediate rewards in sequence
174
+ reward_seq = batch["rewards"][:, :n_step]
175
+ discounts = torch.pow(self.algo_config.discount, torch.arange(n_step).float()).unsqueeze(0)
176
+ input_batch["rewards"] = (reward_seq * discounts).sum(dim=1).unsqueeze(1)
177
+
178
+ # discount rate will be gamma^N for computing n-step returns
179
+ new_discount = (self.algo_config.discount ** n_step)
180
+ self.set_discount(new_discount)
181
+
182
+ # consider this n-step seqeunce done if any intermediate dones are present
183
+ done_seq = batch["dones"][:, :n_step]
184
+ input_batch["dones"] = (done_seq.sum(dim=1) > 0).float().unsqueeze(1)
185
+
186
+ if self.algo_config.infinite_horizon:
187
+ # scale terminal rewards by 1 / (1 - gamma) for infinite horizon MDPs
188
+ done_inds = input_batch["dones"].round().long().nonzero(as_tuple=False)[:, 0]
189
+ if done_inds.shape[0] > 0:
190
+ input_batch["rewards"][done_inds] = input_batch["rewards"][done_inds] * (1. / (1. - self.discount))
191
+
192
+ # we move to device first before float conversion because image observation modalities will be uint8 -
193
+ # this minimizes the amount of data transferred to GPU
194
+ return TensorUtils.to_float(TensorUtils.to_device(input_batch, self.device))
195
+
196
+ def _train_critic_on_batch(self, batch, epoch, no_backprop=False):
197
+ """
198
+ A modular helper function that can be overridden in case
199
+ subclasses would like to modify training behavior for the
200
+ critics.
201
+
202
+ Exactly the same as BCQ (except for removal of @action_sampler_outputs and @critic_outputs)
203
+
204
+ Args:
205
+ batch (dict): dictionary with torch.Tensors sampled
206
+ from a data loader and filtered by @process_batch_for_training
207
+
208
+ epoch (int): epoch number - required by some Algos that need
209
+ to perform staged training and early stopping
210
+
211
+ no_backprop (bool): if True, don't perform any learning updates.
212
+
213
+ Returns:
214
+ info (dict): dictionary of relevant inputs, outputs, and losses
215
+ that might be relevant for logging
216
+ """
217
+ info = OrderedDict()
218
+
219
+ # batch variables
220
+ s_batch = batch["obs"]
221
+ a_batch = batch["actions"]
222
+ r_batch = batch["rewards"]
223
+ ns_batch = batch["next_obs"]
224
+ goal_s_batch = batch["goal_obs"]
225
+
226
+ # 1 if not done, 0 otherwise
227
+ done_mask_batch = 1. - batch["dones"]
228
+ info["done_masks"] = done_mask_batch
229
+
230
+ # Bellman backup for Q-targets
231
+ q_targets = self._get_target_values(
232
+ next_states=ns_batch,
233
+ goal_states=goal_s_batch,
234
+ rewards=r_batch,
235
+ dones=done_mask_batch,
236
+ )
237
+ info["critic/q_targets"] = q_targets
238
+
239
+ # Train all critics using this set of targets for regression
240
+ for critic_ind, critic in enumerate(self.nets["critic"]):
241
+ critic_loss = self._compute_critic_loss(
242
+ critic=critic,
243
+ states=s_batch,
244
+ actions=a_batch,
245
+ goal_states=goal_s_batch,
246
+ q_targets=q_targets,
247
+ )
248
+ info["critic/critic{}_loss".format(critic_ind + 1)] = critic_loss
249
+
250
+ if not no_backprop:
251
+ critic_grad_norms = TorchUtils.backprop_for_loss(
252
+ net=self.nets["critic"][critic_ind],
253
+ optim=self.optimizers["critic"][critic_ind],
254
+ loss=critic_loss,
255
+ max_grad_norm=self.algo_config.critic.max_gradient_norm,
256
+ )
257
+ info["critic/critic{}_grad_norms".format(critic_ind + 1)] = critic_grad_norms
258
+
259
+ return info
260
+
261
+ def _train_actor_on_batch(self, batch, epoch, no_backprop=False):
262
+ """
263
+ A modular helper function that can be overridden in case
264
+ subclasses would like to modify training behavior for the
265
+ actor.
266
+
267
+ Args:
268
+ batch (dict): dictionary with torch.Tensors sampled
269
+ from a data loader and filtered by @process_batch_for_training
270
+
271
+ epoch (int): epoch number - required by some Algos that need
272
+ to perform staged training and early stopping
273
+
274
+ no_backprop (bool): if True, don't perform any learning updates.
275
+
276
+ Returns:
277
+ info (dict): dictionary of relevant inputs, outputs, and losses
278
+ that might be relevant for logging
279
+ """
280
+ info = OrderedDict()
281
+
282
+ # Actor loss (update with mixture of DDPG loss and BC loss)
283
+ s_batch = batch["obs"]
284
+ a_batch = batch["actions"]
285
+ goal_s_batch = batch["goal_obs"]
286
+
287
+ # lambda mixture weight is combination of hyperparameter (alpha) and Q-value normalization
288
+ actor_actions = self.nets["actor"](s_batch, goal_s_batch)
289
+ Q_values = self.nets["critic"][0](s_batch, actor_actions, goal_s_batch)
290
+ lam = self.algo_config.alpha / Q_values.abs().mean().detach()
291
+ actor_loss = -lam * Q_values.mean() + nn.MSELoss()(actor_actions, a_batch)
292
+ info["actor/loss"] = actor_loss
293
+
294
+ if not no_backprop:
295
+ actor_grad_norms = TorchUtils.backprop_for_loss(
296
+ net=self.nets["actor"],
297
+ optim=self.optimizers["actor"],
298
+ loss=actor_loss,
299
+ )
300
+ info["actor/grad_norms"] = actor_grad_norms
301
+
302
+ return info
303
+
304
+ def _get_target_values(self, next_states, goal_states, rewards, dones):
305
+ """
306
+ Helper function to get target values for training Q-function with TD-loss.
307
+
308
+ Args:
309
+ next_states (dict): batch of next observations
310
+ goal_states (dict): if not None, batch of goal observations
311
+ rewards (torch.Tensor): batch of rewards - should be shape (B, 1)
312
+ dones (torch.Tensor): batch of done signals - should be shape (B, 1)
313
+
314
+ Returns:
315
+ q_targets (torch.Tensor): target Q-values to use for TD loss
316
+ """
317
+
318
+ with torch.no_grad():
319
+ # get next actions via target actor and noise
320
+ next_target_actions = self.nets["actor_target"](next_states, goal_states)
321
+ noise = (
322
+ torch.randn_like(next_target_actions) * self.algo_config.actor.noise_std
323
+ ).clamp(-self.algo_config.actor.noise_clip, self.algo_config.actor.noise_clip)
324
+ next_actions = (next_target_actions + noise).clamp(-1.0, 1.0)
325
+
326
+ # TD3 trick to combine max and min over all Q-ensemble estimates into single target estimates
327
+ all_value_targets = self.nets["critic_target"][0](next_states, next_actions, goal_states).reshape(-1, 1)
328
+ max_value_targets = all_value_targets
329
+ min_value_targets = all_value_targets
330
+ for critic_target in self.nets["critic_target"][1:]:
331
+ all_value_targets = critic_target(next_states, next_actions, goal_states).reshape(-1, 1)
332
+ max_value_targets = torch.max(max_value_targets, all_value_targets)
333
+ min_value_targets = torch.min(min_value_targets, all_value_targets)
334
+ value_targets = self.algo_config.critic.ensemble.weight * min_value_targets + \
335
+ (1. - self.algo_config.critic.ensemble.weight) * max_value_targets
336
+ q_targets = rewards + dones * self.discount * value_targets
337
+
338
+ return q_targets
339
+
340
+ def _compute_critic_loss(self, critic, states, actions, goal_states, q_targets):
341
+ """
342
+ Helper function to compute loss between estimated Q-values and target Q-values.
343
+
344
+ Nearly the same as BCQ (return type slightly different).
345
+
346
+ Args:
347
+ critic (torch.nn.Module): critic network
348
+ states (dict): batch of observations
349
+ actions (torch.Tensor): batch of actions
350
+ goal_states (dict): if not None, batch of goal observations
351
+ q_targets (torch.Tensor): batch of target q-values for the TD loss
352
+
353
+ Returns:
354
+ critic_loss (torch.Tensor): critic loss
355
+ """
356
+ q_estimated = critic(states, actions, goal_states)
357
+ if self.algo_config.critic.use_huber:
358
+ critic_loss = nn.SmoothL1Loss()(q_estimated, q_targets)
359
+ else:
360
+ critic_loss = nn.MSELoss()(q_estimated, q_targets)
361
+ return critic_loss
362
+
363
+ def train_on_batch(self, batch, epoch, validate=False):
364
+ """
365
+ Training on a single batch of data.
366
+
367
+ Args:
368
+ batch (dict): dictionary with torch.Tensors sampled
369
+ from a data loader and filtered by @process_batch_for_training
370
+
371
+ epoch (int): epoch number - required by some Algos that need
372
+ to perform staged training and early stopping
373
+
374
+ validate (bool): if True, don't perform any learning updates.
375
+
376
+ Returns:
377
+ info (dict): dictionary of relevant inputs, outputs, and losses
378
+ that might be relevant for logging
379
+ """
380
+ with TorchUtils.maybe_no_grad(no_grad=validate):
381
+ info = PolicyAlgo.train_on_batch(self, batch, epoch, validate=validate)
382
+
383
+ # Critic training
384
+ no_critic_backprop = validate or (not self._check_epoch(net_name="critic", epoch=epoch))
385
+ with TorchUtils.maybe_no_grad(no_grad=no_critic_backprop):
386
+ critic_info = self._train_critic_on_batch(
387
+ batch=batch,
388
+ epoch=epoch,
389
+ no_backprop=no_critic_backprop,
390
+ )
391
+ info.update(critic_info)
392
+
393
+ # update actor and target networks at lower frequency
394
+ if not no_critic_backprop:
395
+ # update counter only on critic training gradient steps
396
+ self.actor_update_counter += 1
397
+ do_actor_update = (self.actor_update_counter % self.algo_config.actor.update_freq == 0)
398
+
399
+ # Actor training
400
+ no_actor_backprop = validate or (not self._check_epoch(net_name="actor", epoch=epoch))
401
+ no_actor_backprop = no_actor_backprop or (not do_actor_update)
402
+ with TorchUtils.maybe_no_grad(no_grad=no_actor_backprop):
403
+ actor_info = self._train_actor_on_batch(
404
+ batch=batch,
405
+ epoch=epoch,
406
+ no_backprop=no_actor_backprop,
407
+ )
408
+ info.update(actor_info)
409
+
410
+ if not no_actor_backprop:
411
+ # to match original implementation, only update target networks on
412
+ # actor gradient steps
413
+ with torch.no_grad():
414
+ # update the target critic networks
415
+ for critic_ind in range(len(self.nets["critic"])):
416
+ TorchUtils.soft_update(
417
+ source=self.nets["critic"][critic_ind],
418
+ target=self.nets["critic_target"][critic_ind],
419
+ tau=self.algo_config.target_tau,
420
+ )
421
+
422
+ # update target actor network
423
+ TorchUtils.soft_update(
424
+ source=self.nets["actor"],
425
+ target=self.nets["actor_target"],
426
+ tau=self.algo_config.target_tau,
427
+ )
428
+
429
+ return info
430
+
431
+ def log_info(self, info):
432
+ """
433
+ Process info dictionary from @train_on_batch to summarize
434
+ information to pass to tensorboard for logging.
435
+
436
+ Args:
437
+ info (dict): dictionary of info
438
+
439
+ Returns:
440
+ loss_log (dict): name -> summary statistic
441
+ """
442
+ loss_log = OrderedDict()
443
+
444
+ # record current optimizer learning rates
445
+ for k in self.optimizers:
446
+ keys = [k]
447
+ optims = [self.optimizers[k]]
448
+ if k == "critic":
449
+ # account for critic having one optimizer per ensemble member
450
+ keys = ["{}{}".format(k, critic_ind) for critic_ind in range(len(self.nets["critic"]))]
451
+ optims = self.optimizers[k]
452
+ for kp, optimizer in zip(keys, optims):
453
+ for i, param_group in enumerate(optimizer.param_groups):
454
+ loss_log["Optimizer/{}{}_lr".format(kp, i)] = param_group["lr"]
455
+
456
+ # extract relevant logs for critic, and actor
457
+ loss_log["Loss"] = 0.
458
+ for loss_logger in [self._log_critic_info, self._log_actor_info]:
459
+ this_log = loss_logger(info)
460
+ if "Loss" in this_log:
461
+ # manually merge total loss
462
+ loss_log["Loss"] += this_log["Loss"]
463
+ del this_log["Loss"]
464
+ loss_log.update(this_log)
465
+
466
+ return loss_log
467
+
468
+ def _log_critic_info(self, info):
469
+ """
470
+ Helper function to extract critic-relevant information for logging.
471
+ """
472
+ loss_log = OrderedDict()
473
+ if "done_masks" in info:
474
+ loss_log["Critic/Done_Mask_Percentage"] = 100. * torch.mean(info["done_masks"]).item()
475
+ if "critic/q_targets" in info:
476
+ loss_log["Critic/Q_Targets"] = info["critic/q_targets"].mean().item()
477
+ loss_log["Loss"] = 0.
478
+ for critic_ind in range(len(self.nets["critic"])):
479
+ loss_log["Critic/Critic{}_Loss".format(critic_ind + 1)] = info["critic/critic{}_loss".format(critic_ind + 1)].item()
480
+ if "critic/critic{}_grad_norms".format(critic_ind + 1) in info:
481
+ loss_log["Critic/Critic{}_Grad_Norms".format(critic_ind + 1)] = info["critic/critic{}_grad_norms".format(critic_ind + 1)]
482
+ loss_log["Loss"] += loss_log["Critic/Critic{}_Loss".format(critic_ind + 1)]
483
+ return loss_log
484
+
485
+ def _log_actor_info(self, info):
486
+ """
487
+ Helper function to extract actor-relevant information for logging.
488
+ """
489
+ loss_log = OrderedDict()
490
+ loss_log["Actor/Loss"] = info["actor/loss"].item()
491
+ if "actor/grad_norms" in info:
492
+ loss_log["Actor/Grad_Norms"] = info["actor/grad_norms"]
493
+ loss_log["Loss"] = loss_log["Actor/Loss"]
494
+ return loss_log
495
+
496
+ def set_train(self):
497
+ """
498
+ Prepare networks for evaluation. Update from super class to make sure
499
+ target networks stay in evaluation mode all the time.
500
+ """
501
+ self.nets.train()
502
+
503
+ # target networks always in eval
504
+ for critic_ind in range(len(self.nets["critic_target"])):
505
+ self.nets["critic_target"][critic_ind].eval()
506
+
507
+ self.nets["actor_target"].eval()
508
+
509
+ def on_epoch_end(self, epoch):
510
+ """
511
+ Called at the end of each epoch.
512
+ """
513
+
514
+ # LR scheduling updates
515
+ for lr_sc in self.lr_schedulers["critic"]:
516
+ if lr_sc is not None:
517
+ lr_sc.step()
518
+
519
+ if self.lr_schedulers["actor"] is not None:
520
+ self.lr_schedulers["actor"].step()
521
+
522
+ def get_action(self, obs_dict, goal_dict=None):
523
+ """
524
+ Get policy action outputs.
525
+
526
+ Args:
527
+ obs_dict (dict): current observation
528
+ goal_dict (dict): (optional) goal
529
+
530
+ Returns:
531
+ action (torch.Tensor): action tensor
532
+ """
533
+ assert not self.nets.training
534
+
535
+ return self.nets["actor"](obs_dict=obs_dict, goal_dict=goal_dict)
536
+
537
+ def get_state_value(self, obs_dict, goal_dict=None):
538
+ """
539
+ Get state value outputs.
540
+
541
+ Args:
542
+ obs_dict (dict): current observation
543
+ goal_dict (dict): (optional) goal
544
+
545
+ Returns:
546
+ value (torch.Tensor): value tensor
547
+ """
548
+ assert not self.nets.training
549
+
550
+ actions = self.nets["actor"](obs_dict=obs_dict, goal_dict=goal_dict)
551
+ return self.nets["critic"][0](obs_dict, actions, goal_dict)
552
+
553
+ def get_state_action_value(self, obs_dict, actions, goal_dict=None):
554
+ """
555
+ Get state-action value outputs.
556
+
557
+ Args:
558
+ obs_dict (dict): current observation
559
+ actions (torch.Tensor): action
560
+ goal_dict (dict): (optional) goal
561
+
562
+ Returns:
563
+ value (torch.Tensor): value tensor
564
+ """
565
+ assert not self.nets.training
566
+
567
+ return self.nets["critic"][0](obs_dict, actions, goal_dict)
aloha-devel/robomimic/config/__pycache__/__init__.cpython-38.pyc ADDED
Binary file (955 Bytes). View file
 
aloha-devel/robomimic/config/__pycache__/bc_config.cpython-38.pyc ADDED
Binary file (3.22 kB). View file
 
aloha-devel/robomimic/config/__pycache__/bcq_config.cpython-38.pyc ADDED
Binary file (2.14 kB). View file
 
aloha-devel/robomimic/config/__pycache__/diffusion_policy_config.cpython-38.pyc ADDED
Binary file (1.89 kB). View file
 
aloha-devel/robomimic/config/__pycache__/gl_config.cpython-38.pyc ADDED
Binary file (3.04 kB). View file
 
aloha-devel/robomimic/config/__pycache__/hbc_config.cpython-38.pyc ADDED
Binary file (2.98 kB). View file
 
aloha-devel/robomimic/config/__pycache__/iql_config.cpython-38.pyc ADDED
Binary file (1.96 kB). View file
 
aloha-devel/robomimic/config/__pycache__/iris_config.cpython-38.pyc ADDED
Binary file (2.91 kB). View file
 
aloha-devel/robomimic/config/act_config.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Config for BC algorithm.
3
+ """
4
+
5
+ from robomimic.config.base_config import BaseConfig
6
+
7
+
8
+ class ACTConfig(BaseConfig):
9
+ ALGO_NAME = "act"
10
+
11
+ def train_config(self):
12
+ """
13
+ BC algorithms don't need "next_obs" from hdf5 - so save on storage and compute by disabling it.
14
+ """
15
+ super(ACTConfig, self).train_config()
16
+ self.train.hdf5_load_next_obs = False
17
+
18
+ def algo_config(self):
19
+ """
20
+ This function populates the `config.algo` attribute of the config, and is given to the
21
+ `Algo` subclass (see `algo/algo.py`) for each algorithm through the `algo_config`
22
+ argument to the constructor. Any parameter that an algorithm needs to determine its
23
+ training and test-time behavior should be populated here.
24
+ """
25
+
26
+ # optimization parameters
27
+ self.algo.optim_params.policy.optimizer_type = "adamw"
28
+ self.algo.optim_params.policy.learning_rate.initial = 5e-5 # policy learning rate
29
+ self.algo.optim_params.policy.learning_rate.decay_factor = 1 # factor to decay LR by (if epoch schedule non-empty)
30
+ self.algo.optim_params.policy.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
31
+ self.algo.optim_params.policy.learning_rate.scheduler_type = "linear" # learning rate scheduler ("multistep", "linear", etc)
32
+ self.algo.optim_params.policy.regularization.L2 = 0.0001 # L2 regularization strength
33
+
34
+ # loss weights
35
+ self.algo.loss.l2_weight = 0.0 # L2 loss weight
36
+ self.algo.loss.l1_weight = 1.0 # L1 loss weight
37
+ self.algo.loss.cos_weight = 0.0 # cosine loss weight
38
+
39
+ # ACT policy settings
40
+ self.algo.act.hidden_dim = 512 # length of (s, a) seqeunces to feed to transformer - should usually match train.frame_stack
41
+ self.algo.act.dim_feedforward = 3200 # dimension for embeddings used by transformer
42
+ self.algo.act.backbone = "resnet18" # number of transformer blocks to stack
43
+ self.algo.act.enc_layers = 4 # number of attention heads for each transformer block (should divide embed_dim evenly)
44
+ self.algo.act.dec_layers = 7 # dropout probability for embedding inputs in transformer
45
+ self.algo.act.nheads = 8 # dropout probability for attention outputs for each transformer block
46
+ self.algo.act.latent_dim = 32 # latent dim of VAE
47
+ self.algo.act.kl_weight = 20 # KL weight of VAE
aloha-devel/robomimic/config/base_config.py ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ The base config class that is used for all algorithm configs in this repository.
3
+ Subclasses get registered into a global dictionary, making it easy to instantiate
4
+ the correct config class given the algorithm name.
5
+ """
6
+
7
+ import six # preserve metaclass compatibility between python 2 and 3
8
+ from copy import deepcopy
9
+
10
+ import robomimic
11
+ from robomimic.config.config import Config
12
+
13
+ # global dictionary for remembering name - class mappings
14
+ REGISTERED_CONFIGS = {}
15
+
16
+
17
+ def get_all_registered_configs():
18
+ """
19
+ Give access to dictionary of all registered configs for external use.
20
+ """
21
+ return deepcopy(REGISTERED_CONFIGS)
22
+
23
+
24
+ def config_factory(algo_name, dic=None):
25
+ """
26
+ Creates an instance of a config from the algo name. Optionally pass
27
+ a dictionary to instantiate the config from the dictionary.
28
+ """
29
+ if algo_name not in REGISTERED_CONFIGS:
30
+ raise Exception("Config for algo name {} not found. Make sure it is a registered config among: {}".format(
31
+ algo_name, ', '.join(REGISTERED_CONFIGS)))
32
+ return REGISTERED_CONFIGS[algo_name](dict_to_load=dic)
33
+
34
+
35
+ class ConfigMeta(type):
36
+ """
37
+ Define a metaclass for constructing a config class.
38
+ It registers configs into the global registry.
39
+ """
40
+ def __new__(meta, name, bases, class_dict):
41
+ cls = super(ConfigMeta, meta).__new__(meta, name, bases, class_dict)
42
+ if cls.__name__ != "BaseConfig":
43
+ REGISTERED_CONFIGS[cls.ALGO_NAME] = cls
44
+ return cls
45
+
46
+
47
+ @six.add_metaclass(ConfigMeta)
48
+ class BaseConfig(Config):
49
+ def __init__(self, dict_to_load=None):
50
+ if dict_to_load is not None:
51
+ super(BaseConfig, self).__init__(dict_to_load)
52
+ return
53
+
54
+ super(BaseConfig, self).__init__()
55
+
56
+ # store algo name class property in the config (must be implemented by subclasses)
57
+ self.algo_name = type(self).ALGO_NAME
58
+
59
+ self.experiment_config()
60
+ self.train_config()
61
+ self.algo_config()
62
+ self.observation_config()
63
+ self.meta_config()
64
+
65
+ # After Config init, new keys cannot be added to the config, except under nested
66
+ # attributes that have called @do_not_lock_keys
67
+ self.lock_keys()
68
+
69
+ @property
70
+ @classmethod
71
+ def ALGO_NAME(cls):
72
+ # must be specified by subclasses
73
+ raise NotImplementedError
74
+
75
+ def experiment_config(self):
76
+ """
77
+ This function populates the `config.experiment` attribute of the config,
78
+ which has several experiment settings such as the name of the training run,
79
+ whether to do logging, whether to save models (and how often), whether to render
80
+ videos, and whether to do rollouts (and how often). This class has a default
81
+ implementation that usually doesn't need to be overriden.
82
+ """
83
+
84
+ self.experiment.name = "test" # name of experiment used to make log files
85
+ self.experiment.validate = False # whether to do validation or not
86
+ self.experiment.logging.terminal_output_to_txt = True # whether to log stdout to txt file
87
+ self.experiment.logging.log_tb = True # enable tensorboard logging
88
+ self.experiment.logging.log_wandb = False # enable wandb logging
89
+ self.experiment.logging.wandb_proj_name = "debug" # project name if using wandb
90
+
91
+ # log model prediction MSE
92
+ self.experiment.mse.enabled = False # whether to log model prediction MSE
93
+ self.experiment.mse.every_n_epochs = 50 # log model prediction MSE every n epochs
94
+ self.experiment.mse.on_save_ckpt = True # log model prediction MSE on model checkpoint
95
+ self.experiment.mse.num_samples = 20 # number of datapoints to use for MSE prediction
96
+ self.experiment.mse.visualize = True # save model prediction visualizations
97
+
98
+ ## save config - if and when to save model checkpoints ##
99
+ self.experiment.save.enabled = True # whether model saving should be enabled or disabled
100
+ self.experiment.save.every_n_seconds = None # save model every n seconds (set to None to disable)
101
+ self.experiment.save.every_n_epochs = 50 # save model every n epochs (set to None to disable)
102
+ self.experiment.save.epochs = [] # save model on these specific epochs
103
+ self.experiment.save.on_best_validation = False # save models that achieve best validation score
104
+ self.experiment.save.on_best_rollout_return = False # save models that achieve best rollout return
105
+ self.experiment.save.on_best_rollout_success_rate = True # save models that achieve best success rate
106
+
107
+ # epoch definitions - if not None, set an epoch to be this many gradient steps, else the full dataset size will be used
108
+ self.experiment.epoch_every_n_steps = 100 # number of gradient steps in train epoch (None for full dataset pass)
109
+ self.experiment.validation_epoch_every_n_steps = 10 # number of gradient steps in valid epoch (None for full dataset pass)
110
+
111
+ # envs to evaluate model on (assuming rollouts are enabled), to override the metadata stored in dataset
112
+ self.experiment.env = None # no need to set this (unless you want to override)
113
+ self.experiment.additional_envs = None # additional environments that should get evaluated
114
+
115
+
116
+ ## rendering config ##
117
+ self.experiment.render = False # render on-screen or not
118
+ self.experiment.render_video = True # render evaluation rollouts to videos
119
+ self.experiment.keep_all_videos = False # save all videos, instead of only saving those for saved model checkpoints
120
+ self.experiment.video_skip = 5 # render video frame every n environment steps during rollout
121
+
122
+
123
+ ## evaluation rollout config ##
124
+ self.experiment.rollout.enabled = True # enable evaluation rollouts
125
+ self.experiment.rollout.n = 50 # number of rollouts per evaluation
126
+ self.experiment.rollout.horizon = 400 # maximum number of env steps per rollout
127
+ self.experiment.rollout.rate = 50 # do rollouts every @rate epochs
128
+ self.experiment.rollout.warmstart = 0 # number of epochs to wait before starting rollouts
129
+ self.experiment.rollout.terminate_on_success = True # end rollout early after task success
130
+ self.experiment.rollout.batched = False # whether to parallelize evaluations over batched environments
131
+ self.experiment.rollout.num_batch_envs = 5 # number of batched environments to use (applicable if experiment.rollout.batched is True)
132
+
133
+ # for updating the evaluation env meta data
134
+ self.experiment.env_meta_update_dict = Config()
135
+ self.experiment.env_meta_update_dict.do_not_lock_keys()
136
+
137
+ # whether to load in a previously trained model checkpoint
138
+ self.experiment.ckpt_path = None
139
+
140
+ def train_config(self):
141
+ """
142
+ This function populates the `config.train` attribute of the config, which
143
+ has several settings related to the training process, such as the dataset
144
+ to use for training, and how the data loader should load the data. This
145
+ class has a default implementation that usually doesn't need to be overriden.
146
+ """
147
+
148
+ # Path to hdf5 dataset to use for training
149
+ self.train.data = None
150
+
151
+ # Write all results to this directory. A new folder with the timestamp will be created
152
+ # in this directory, and it will contain three subfolders - "log", "models", and "videos".
153
+ # The "log" directory will contain tensorboard and stdout txt logs. The "models" directory
154
+ # will contain saved model checkpoints. The "videos" directory contains evaluation rollout
155
+ # videos.
156
+ self.train.output_dir = "../{}_trained_models".format(self.algo_name)
157
+
158
+
159
+ ## dataset loader config ##
160
+
161
+ # num workers for loading data - generally set to 0 for low-dim datasets, and 2 for image datasets
162
+ self.train.num_data_workers = 0
163
+
164
+ # One of ["all", "low_dim", or None]. Set to "all" to cache entire hdf5 in memory - this is
165
+ # by far the fastest for data loading. Set to "low_dim" to cache all non-image data. Set
166
+ # to None to use no caching - in this case, every batch sample is retrieved via file i/o.
167
+ # You should almost never set this to None, even for large image datasets.
168
+ self.train.hdf5_cache_mode = "all"
169
+
170
+ # used for parallel data loading
171
+ self.train.hdf5_use_swmr = True
172
+
173
+ # whether to load "next_obs" group from hdf5 - only needed for batch / offline RL algorithms
174
+ self.train.hdf5_load_next_obs = True
175
+
176
+ # if true, normalize observations at train and test time, using the global mean and standard deviation
177
+ # of each observation in each dimension, computed across the training set. See SequenceDataset.normalize_obs
178
+ # in utils/dataset.py for more information.
179
+ self.train.hdf5_normalize_obs = False
180
+
181
+ # if provided, use the list of demo keys under the hdf5 group "mask/@hdf5_filter_key" for training, instead
182
+ # of the full dataset. This provides a convenient way to train on only a subset of the trajectories in a dataset.
183
+ self.train.hdf5_filter_key = None
184
+
185
+ # if provided, use the list of demo keys under the hdf5 group "mask/@hdf5_validation_filter_key" for validation.
186
+ # Must be provided if @experiment.validate is True.
187
+ self.train.hdf5_validation_filter_key = None
188
+
189
+ # length of experience sequence to fetch from the dataset
190
+ # and whether to pad the beginning / end of the sequence at boundaries of trajectory in dataset
191
+ self.train.seq_length = 1
192
+ self.train.pad_seq_length = True
193
+ self.train.frame_stack = 1
194
+ self.train.pad_frame_stack = True
195
+
196
+ # keys from hdf5 to load into each batch, besides "obs" and "next_obs". If algorithms
197
+ # require additional keys from each trajectory in the hdf5, they should be specified here.
198
+ self.train.dataset_keys = (
199
+ "actions",
200
+ "rewards",
201
+ "dones",
202
+ )
203
+
204
+ self.train.action_keys = ["actions"]
205
+
206
+ # specifing each action keys to load and their corresponding normalization/conversion requirement
207
+ # e.g. for dataset keys "action/eef_pos" and "action/eef_rot"
208
+ # the desired value of self.train.action_config is:
209
+ # {
210
+ # "action/eef_pos": {
211
+ # "normalization": "min_max",
212
+ # "rot_conversion: None
213
+ # },
214
+ # "action/eef_rot": {
215
+ # "normalization": None,
216
+ # "rot_conversion: "axis_angle_to_6d"
217
+ # }
218
+ # }
219
+ # self.train.action_config.actions.normalization = None # "min_max"
220
+ # self.train.action_config.actions.rot_conversion = None # "axis_angle_to_6d"
221
+ self.train.action_config = {}
222
+ # self.train.action_config.do_not_lock_keys()
223
+
224
+ # one of [None, "last"] - set to "last" to include goal observations in each batch
225
+ self.train.goal_mode = None
226
+
227
+
228
+ ## learning config ##
229
+ self.train.cuda = True # use GPU or not
230
+ self.train.batch_size = 100 # batch size
231
+ self.train.num_epochs = 2000 # number of training epochs
232
+ self.train.seed = 1 # seed for training (for reproducibility)
233
+
234
+ self.train.max_grad_norm = None # clip gradient norms (see `backprop_for_loss` function in torch_utils.py)
235
+
236
+ self.train.data_format = "robomimic" # either "robomimic" or "r2d2"
237
+
238
+ # list of observation keys to shuffle randomly in the dataset.
239
+ # must be list of tuples pairs, with each pair representing
240
+ # the corresponding observation key groups to shuffle
241
+ self.train.shuffled_obs_key_groups = None
242
+
243
+ def algo_config(self):
244
+ """
245
+ This function populates the `config.algo` attribute of the config, and is given to the
246
+ `Algo` subclass (see `algo/algo.py`) for each algorithm through the `algo_config`
247
+ argument to the constructor. Any parameter that an algorithm needs to determine its
248
+ training and test-time behavior should be populated here. This function should be
249
+ implemented by every subclass.
250
+ """
251
+ pass
252
+
253
+ def observation_config(self):
254
+ """
255
+ This function populates the `config.observation` attribute of the config, and is given
256
+ to the `Algo` subclass (see `algo/algo.py`) for each algorithm through the `obs_config`
257
+ argument to the constructor. This portion of the config is used to specify what
258
+ observation modalities should be used by the networks for training, and how the
259
+ observation modalities should be encoded by the networks. While this class has a
260
+ default implementation that usually doesn't need to be overriden, certain algorithm
261
+ configs may choose to, in order to have seperate configs for different networks
262
+ in the algorithm.
263
+ """
264
+
265
+ # observation modalities
266
+ self.observation.modalities.obs.low_dim = [ # specify low-dim observations for agent
267
+ "robot0_eef_pos",
268
+ "robot0_eef_quat",
269
+ "robot0_gripper_qpos",
270
+ "object",
271
+ ]
272
+ self.observation.modalities.obs.rgb = [] # specify rgb image observations for agent
273
+ self.observation.modalities.obs.depth = []
274
+ self.observation.modalities.obs.scan = []
275
+ self.observation.modalities.goal.low_dim = [] # specify low-dim goal observations to condition agent on
276
+ self.observation.modalities.goal.rgb = [] # specify rgb image goal observations to condition agent on
277
+ self.observation.modalities.goal.depth = []
278
+ self.observation.modalities.goal.scan = []
279
+ self.observation.modalities.obs.do_not_lock_keys()
280
+ self.observation.modalities.goal.do_not_lock_keys()
281
+
282
+ # observation encoder architectures (per obs modality)
283
+ # This applies to all networks that take observation dicts as input
284
+
285
+ # =============== Low Dim default encoder (no encoder) ===============
286
+ self.observation.encoder.low_dim.core_class = None
287
+ self.observation.encoder.low_dim.core_kwargs = Config() # No kwargs by default
288
+ self.observation.encoder.low_dim.core_kwargs.do_not_lock_keys()
289
+
290
+ # Low Dim: Obs Randomizer settings
291
+ self.observation.encoder.low_dim.obs_randomizer_class = None
292
+ self.observation.encoder.low_dim.obs_randomizer_kwargs = Config() # No kwargs by default
293
+ self.observation.encoder.low_dim.obs_randomizer_kwargs.do_not_lock_keys()
294
+
295
+ # =============== RGB default encoder (ResNet backbone + linear layer output) ===============
296
+ self.observation.encoder.rgb.core_class = "VisualCore" # Default VisualCore class combines backbone (like ResNet-18) with pooling operation (like spatial softmax)
297
+ self.observation.encoder.rgb.core_kwargs = Config() # See models/obs_core.py for important kwargs to set and defaults used
298
+ self.observation.encoder.rgb.core_kwargs.do_not_lock_keys()
299
+
300
+ # RGB: Obs Randomizer settings
301
+ self.observation.encoder.rgb.obs_randomizer_class = None # Can set to 'CropRandomizer' to use crop randomization
302
+ self.observation.encoder.rgb.obs_randomizer_kwargs = Config() # See models/obs_core.py for important kwargs to set and defaults used
303
+ self.observation.encoder.rgb.obs_randomizer_kwargs.do_not_lock_keys()
304
+
305
+ # Allow for other custom modalities to be specified
306
+ self.observation.encoder.do_not_lock_keys()
307
+
308
+ # =============== Depth default encoder (same as rgb) ===============
309
+ self.observation.encoder.depth = deepcopy(self.observation.encoder.rgb)
310
+
311
+ # =============== Scan default encoder (Conv1d backbone + linear layer output) ===============
312
+ self.observation.encoder.scan = deepcopy(self.observation.encoder.rgb)
313
+
314
+ # Scan: Modify the core class + kwargs, otherwise, is same as rgb encoder
315
+ self.observation.encoder.scan.core_class = "ScanCore" # Default ScanCore class uses Conv1D to process this modality
316
+ self.observation.encoder.scan.core_kwargs = Config() # See models/obs_core.py for important kwargs to set and defaults used
317
+ self.observation.encoder.scan.core_kwargs.do_not_lock_keys()
318
+
319
+ def meta_config(self):
320
+ """
321
+ This function populates the `config.meta` attribute of the config. This portion of the config
322
+ is used to specify job information primarily for hyperparameter sweeps.
323
+ It contains hyperparameter keys and values, which are populated automatically
324
+ by the hyperparameter config generator (see `utils/hyperparam_utils.py`).
325
+ These values are read by the wandb logger (see `utils/log_utils.py`) to set job tags.
326
+ """
327
+
328
+ self.meta.hp_base_config_file = None # base config file in hyperparam sweep
329
+ self.meta.hp_keys = [] # relevant keys (swept) in hyperparam sweep
330
+ self.meta.hp_values = [] # values corresponding to keys in hyperparam sweep
331
+
332
+ @property
333
+ def use_goals(self):
334
+ # whether the agent is goal-conditioned
335
+ return len([obs_key for modality in self.observation.modalities.goal.values() for obs_key in modality]) > 0
336
+
337
+ @property
338
+ def all_obs_keys(self):
339
+ """
340
+ This grabs the union of observation keys over all modalities (e.g.: low_dim, rgb, depth, etc.) and over all
341
+ modality groups (e.g: obs, goal, subgoal, etc...)
342
+
343
+ Returns:
344
+ n-array: all observation keys used for this model
345
+ """
346
+ # pool all modalities
347
+ return sorted(tuple(set([
348
+ obs_key for group in [
349
+ self.observation.modalities.obs.values(),
350
+ self.observation.modalities.goal.values()
351
+ ]
352
+ for modality in group
353
+ for obs_key in modality
354
+ ])))
aloha-devel/robomimic/config/bc_config.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Config for BC algorithm.
3
+ """
4
+
5
+ from robomimic.config.base_config import BaseConfig
6
+
7
+
8
+ class BCConfig(BaseConfig):
9
+ ALGO_NAME = "bc"
10
+
11
+ def train_config(self):
12
+ """
13
+ BC algorithms don't need "next_obs" from hdf5 - so save on storage and compute by disabling it.
14
+ """
15
+ super(BCConfig, self).train_config()
16
+ self.train.hdf5_load_next_obs = False
17
+
18
+ def algo_config(self):
19
+ """
20
+ This function populates the `config.algo` attribute of the config, and is given to the
21
+ `Algo` subclass (see `algo/algo.py`) for each algorithm through the `algo_config`
22
+ argument to the constructor. Any parameter that an algorithm needs to determine its
23
+ training and test-time behavior should be populated here.
24
+ """
25
+
26
+ # optimization parameters
27
+ self.algo.optim_params.policy.optimizer_type = "adam"
28
+ self.algo.optim_params.policy.learning_rate.initial = 1e-4 # policy learning rate
29
+ self.algo.optim_params.policy.learning_rate.decay_factor = 0.1 # factor to decay LR by (if epoch schedule non-empty)
30
+ self.algo.optim_params.policy.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
31
+ self.algo.optim_params.policy.learning_rate.scheduler_type = "multistep" # learning rate scheduler ("multistep", "linear", etc)
32
+ self.algo.optim_params.policy.regularization.L2 = 0.00 # L2 regularization strength
33
+
34
+ # loss weights
35
+ self.algo.loss.l2_weight = 1.0 # L2 loss weight
36
+ self.algo.loss.l1_weight = 0.0 # L1 loss weight
37
+ self.algo.loss.cos_weight = 0.0 # cosine loss weight
38
+
39
+ # MLP network architecture (layers after observation encoder and RNN, if present)
40
+ self.algo.actor_layer_dims = (1024, 1024)
41
+
42
+ # stochastic Gaussian policy settings
43
+ self.algo.gaussian.enabled = False # whether to train a Gaussian policy
44
+ self.algo.gaussian.fixed_std = False # whether to train std output or keep it constant
45
+ self.algo.gaussian.init_std = 0.1 # initial standard deviation (or constant)
46
+ self.algo.gaussian.min_std = 0.01 # minimum std output from network
47
+ self.algo.gaussian.std_activation = "softplus" # activation to use for std output from policy net
48
+ self.algo.gaussian.low_noise_eval = True # low-std at test-time
49
+
50
+ # stochastic GMM policy settings
51
+ self.algo.gmm.enabled = False # whether to train a GMM policy
52
+ self.algo.gmm.num_modes = 5 # number of GMM modes
53
+ self.algo.gmm.min_std = 0.0001 # minimum std output from network
54
+ self.algo.gmm.std_activation = "softplus" # activation to use for std output from policy net
55
+ self.algo.gmm.low_noise_eval = True # low-std at test-time
56
+
57
+ # stochastic VAE policy settings
58
+ self.algo.vae.enabled = False # whether to train a VAE policy
59
+ self.algo.vae.latent_dim = 14 # VAE latent dimnsion - set to twice the dimensionality of action space
60
+ self.algo.vae.latent_clip = None # clip latent space when decoding (set to None to disable)
61
+ self.algo.vae.kl_weight = 1. # beta-VAE weight to scale KL loss relative to reconstruction loss in ELBO
62
+
63
+ # VAE decoder settings
64
+ self.algo.vae.decoder.is_conditioned = True # whether decoder should condition on observation
65
+ self.algo.vae.decoder.reconstruction_sum_across_elements = False # sum instead of mean for reconstruction loss
66
+
67
+ # VAE prior settings
68
+ self.algo.vae.prior.learn = False # learn Gaussian / GMM prior instead of N(0, 1)
69
+ self.algo.vae.prior.is_conditioned = False # whether to condition prior on observations
70
+ self.algo.vae.prior.use_gmm = False # whether to use GMM prior
71
+ self.algo.vae.prior.gmm_num_modes = 10 # number of GMM modes
72
+ self.algo.vae.prior.gmm_learn_weights = False # whether to learn GMM weights
73
+ self.algo.vae.prior.use_categorical = False # whether to use categorical prior
74
+ self.algo.vae.prior.categorical_dim = 10 # the number of categorical classes for each latent dimension
75
+ self.algo.vae.prior.categorical_gumbel_softmax_hard = False # use hard selection in forward pass
76
+ self.algo.vae.prior.categorical_init_temp = 1.0 # initial gumbel-softmax temp
77
+ self.algo.vae.prior.categorical_temp_anneal_step = 0.001 # linear temp annealing rate
78
+ self.algo.vae.prior.categorical_min_temp = 0.3 # lowest gumbel-softmax temp
79
+
80
+ self.algo.vae.encoder_layer_dims = (300, 400) # encoder MLP layer dimensions
81
+ self.algo.vae.decoder_layer_dims = (300, 400) # decoder MLP layer dimensions
82
+ self.algo.vae.prior_layer_dims = (300, 400) # prior MLP layer dimensions (if learning conditioned prior)
83
+
84
+ # RNN policy settings
85
+ self.algo.rnn.enabled = False # whether to train RNN policy
86
+ self.algo.rnn.horizon = 10 # unroll length for RNN - should usually match train.seq_length
87
+ self.algo.rnn.hidden_dim = 400 # hidden dimension size
88
+ self.algo.rnn.rnn_type = "LSTM" # rnn type - one of "LSTM" or "GRU"
89
+ self.algo.rnn.num_layers = 2 # number of RNN layers that are stacked
90
+ self.algo.rnn.open_loop = False # if True, action predictions are only based on a single observation (not sequence)
91
+ self.algo.rnn.kwargs.bidirectional = False # rnn kwargs
92
+ self.algo.rnn.kwargs.do_not_lock_keys()
93
+
94
+ # Transformer policy settings
95
+ self.algo.transformer.enabled = False # whether to train transformer policy
96
+ self.algo.transformer.context_length = 10 # length of (s, a) seqeunces to feed to transformer - should usually match train.frame_stack
97
+ self.algo.transformer.embed_dim = 512 # dimension for embeddings used by transformer
98
+ self.algo.transformer.num_layers = 6 # number of transformer blocks to stack
99
+ self.algo.transformer.num_heads = 8 # number of attention heads for each transformer block (should divide embed_dim evenly)
100
+ self.algo.transformer.emb_dropout = 0.1 # dropout probability for embedding inputs in transformer
101
+ self.algo.transformer.attn_dropout = 0.1 # dropout probability for attention outputs for each transformer block
102
+ self.algo.transformer.block_output_dropout = 0.1 # dropout probability for final outputs for each transformer block
103
+ self.algo.transformer.sinusoidal_embedding = False # if True, use standard positional encodings (sin/cos)
104
+ self.algo.transformer.activation = "gelu" # activation function for MLP in Transformer Block
105
+ self.algo.transformer.supervise_all_steps = False # if true, supervise all intermediate actions, otherwise only final one
106
+ self.algo.transformer.nn_parameter_for_timesteps = True # if true, use nn.Parameter otherwise use nn.Embedding
107
+ self.algo.transformer.pred_future_acs = False # shift action prediction forward to predict future actions instead of past actions
108
+ self.algo.transformer.causal = True # whether the transformer is causal
109
+
110
+ self.algo.language_conditioned = False # whether policy is language conditioned
aloha-devel/robomimic/config/cql_config.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Config for CQL algorithm.
3
+ """
4
+
5
+ from robomimic.config.base_config import BaseConfig
6
+
7
+
8
+ class CQLConfig(BaseConfig):
9
+ ALGO_NAME = "cql"
10
+
11
+ def train_config(self):
12
+ """
13
+ Update from superclass to change default batch size.
14
+ """
15
+ super(CQLConfig, self).train_config()
16
+
17
+ # increase batch size to 1024 (found to work better for most manipulation experiments)
18
+ self.train.batch_size = 1024
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
+ # optimization parameters
29
+ self.algo.optim_params.critic.learning_rate.initial = 1e-3 # critic learning rate
30
+ self.algo.optim_params.critic.learning_rate.decay_factor = 0.0 # factor to decay LR by (if epoch schedule non-empty)
31
+ self.algo.optim_params.critic.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
32
+ self.algo.optim_params.critic.regularization.L2 = 0.00 # L2 regularization strength
33
+
34
+ self.algo.optim_params.actor.learning_rate.initial = 3e-4 # actor learning rate
35
+ self.algo.optim_params.actor.learning_rate.decay_factor = 0.0 # factor to decay LR by (if epoch schedule non-empty)
36
+ self.algo.optim_params.actor.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
37
+ self.algo.optim_params.actor.regularization.L2 = 0.00 # L2 regularization strength
38
+
39
+ # target network related parameters
40
+ self.algo.discount = 0.99 # discount factor to use
41
+ self.algo.n_step = 1 # for using n-step returns in TD-updates
42
+ self.algo.target_tau = 0.005 # update rate for target networks
43
+
44
+ # ================== Actor Network Config ===================
45
+ self.algo.actor.bc_start_steps = 0 # uses BC policy loss for first n-training steps
46
+ self.algo.actor.target_entropy = "default" # None is fixed entropy, otherwise is automatically tuned to match target. Can specify "default" as well for default tuning target
47
+ self.algo.actor.max_gradient_norm = None # L2 gradient clipping for actor
48
+
49
+ # Actor network settings
50
+ self.algo.actor.net.type = "gaussian" # Options are currently only "gaussian" (no support for GMM yet)
51
+
52
+ # Actor network settings - shared
53
+ self.algo.actor.net.common.std_activation = "exp" # Activation to use for std output from policy net
54
+ self.algo.actor.net.common.use_tanh = True # Whether to use tanh at output of actor network
55
+ self.algo.actor.net.common.low_noise_eval = True # Whether to use deterministic action sampling at eval stage
56
+
57
+ # Actor network settings - gaussian
58
+ 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
59
+ self.algo.actor.net.gaussian.init_std = 0.3 # Relative scaling factor for std from policy net
60
+ self.algo.actor.net.gaussian.fixed_std = False # Whether to learn std dev or not
61
+
62
+ self.algo.actor.layer_dims = (300, 400) # actor MLP layer dimensions
63
+
64
+ # ================== Critic Network Config ===================
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 critic (None to use no clipping)
67
+
68
+ self.algo.critic.value_bounds = None # optional 2-tuple to ensure lower and upper bound on value estimates
69
+
70
+ self.algo.critic.num_action_samples = 1 # number of actions to sample per training batch to get target critic value; use maximum Q value from n random sampled actions when doing TD error backup
71
+
72
+ # cql settings for critic
73
+ self.algo.critic.cql_weight = 1.0 # weighting for cql component of critic loss (only used if target_q_gap is < 0 or None)
74
+ self.algo.critic.deterministic_backup = True # if not set, subtract weighted logprob of action when doing backup
75
+ self.algo.critic.min_q_weight = 1.0 # min q weight (scaling factor) to apply
76
+ self.algo.critic.target_q_gap = 5.0 # if set, sets the diff threshold at which Q-values will be penalized more (note: this overrides cql weight above!) Use None or a negative value if not set
77
+ self.algo.critic.num_random_actions = 10 # Number of random actions to sample when calculating CQL loss
78
+
79
+ # critic ensemble parameters (TD3 trick)
80
+ self.algo.critic.ensemble.n = 2 # number of Q networks in the ensemble
81
+
82
+ self.algo.critic.layer_dims = (300, 400) # critic MLP layer dimensions
aloha-devel/robomimic/config/diffusion_policy_config.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Config for Diffusion Policy algorithm.
3
+ """
4
+
5
+ from robomimic.config.base_config import BaseConfig
6
+
7
+ class DiffusionPolicyConfig(BaseConfig):
8
+ ALGO_NAME = "diffusion_policy"
9
+
10
+ def algo_config(self):
11
+ """
12
+ This function populates the `config.algo` attribute of the config, and is given to the
13
+ `Algo` subclass (see `algo/algo.py`) for each algorithm through the `algo_config`
14
+ argument to the constructor. Any parameter that an algorithm needs to determine its
15
+ training and test-time behavior should be populated here.
16
+ """
17
+
18
+ # optimization parameters
19
+ self.algo.optim_params.policy.learning_rate.initial = 1e-4 # policy learning rate
20
+ self.algo.optim_params.policy.learning_rate.decay_factor = 0.1 # factor to decay LR by (if epoch schedule non-empty)
21
+ self.algo.optim_params.policy.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
22
+ self.algo.optim_params.policy.regularization.L2 = 0.00 # L2 regularization strength
23
+
24
+ # horizon parameters
25
+ self.algo.horizon.observation_horizon = 2
26
+ self.algo.horizon.action_horizon = 8
27
+ self.algo.horizon.prediction_horizon = 16
28
+
29
+ # UNet parameters
30
+ self.algo.unet.enabled = True
31
+ self.algo.unet.diffusion_step_embed_dim = 256
32
+ self.algo.unet.down_dims = [256,512,1024]
33
+ self.algo.unet.kernel_size = 5
34
+ self.algo.unet.n_groups = 8
35
+
36
+ # EMA parameters
37
+ self.algo.ema.enabled = True
38
+ self.algo.ema.power = 0.75
39
+
40
+ # Noise Scheduler
41
+ ## DDPM
42
+ self.algo.ddpm.enabled = True
43
+ self.algo.ddpm.num_train_timesteps = 100
44
+ self.algo.ddpm.num_inference_timesteps = 100
45
+ self.algo.ddpm.beta_schedule = 'squaredcos_cap_v2'
46
+ self.algo.ddpm.clip_sample = True
47
+ self.algo.ddpm.prediction_type = 'epsilon'
48
+
49
+ ## DDIM
50
+ self.algo.ddim.enabled = False
51
+ self.algo.ddim.num_train_timesteps = 100
52
+ self.algo.ddim.num_inference_timesteps = 10
53
+ self.algo.ddim.beta_schedule = 'squaredcos_cap_v2'
54
+ self.algo.ddim.clip_sample = True
55
+ self.algo.ddim.set_alpha_to_one = True
56
+ self.algo.ddim.steps_offset = 0
57
+ self.algo.ddim.prediction_type = 'epsilon'
58
+
59
+ self.algo.language_conditioned = False # whether policy is language conditioned
60
+
aloha-devel/robomimic/config/gl_config.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Config for Goal Learning (sub-algorithm used by hierarchical models like HBC and IRIS).
3
+ This class of model predicts (or samples) subgoal observations given a current observation.
4
+ """
5
+
6
+ from robomimic.config.base_config import BaseConfig
7
+
8
+
9
+ class GLConfig(BaseConfig):
10
+ ALGO_NAME = "gl"
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.goal_network.learning_rate.initial = 1e-4 # goal network learning rate
22
+ self.algo.optim_params.goal_network.learning_rate.decay_factor = 0.1 # factor to decay LR by (if epoch schedule non-empty)
23
+ self.algo.optim_params.goal_network.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
24
+ self.algo.optim_params.goal_network.regularization.L2 = 0.00
25
+
26
+ # subgoal definition: observation that is @subgoal_horizon number of timesteps in future from current observation
27
+ self.algo.subgoal_horizon = 10
28
+
29
+ # MLP size for deterministic goal network (unused if VAE is enabled)
30
+ self.algo.ae.planner_layer_dims = (300, 400)
31
+
32
+ # ================== VAE config ==================
33
+ self.algo.vae.enabled = True # set to true to use VAE network
34
+ self.algo.vae.latent_dim = 16 # VAE latent dimension
35
+ self.algo.vae.latent_clip = None # clip latent space when decoding (set to None to disable)
36
+ self.algo.vae.kl_weight = 1. # beta-VAE weight to scale KL loss relative to reconstruction loss in ELBO
37
+
38
+ # VAE decoder settings
39
+ self.algo.vae.decoder.is_conditioned = True # whether decoder should condition on observation
40
+ self.algo.vae.decoder.reconstruction_sum_across_elements = False # sum instead of mean for reconstruction loss
41
+
42
+ # VAE prior settings
43
+ self.algo.vae.prior.learn = False # learn Gaussian / GMM prior instead of N(0, 1)
44
+ self.algo.vae.prior.is_conditioned = False # whether to condition prior on observations
45
+ self.algo.vae.prior.use_gmm = False # whether to use GMM prior
46
+ self.algo.vae.prior.gmm_num_modes = 10 # number of GMM modes
47
+ self.algo.vae.prior.gmm_learn_weights = False # whether to learn GMM weights
48
+ self.algo.vae.prior.use_categorical = False # whether to use categorical prior
49
+ self.algo.vae.prior.categorical_dim = 10 # the number of categorical classes for each latent dimension
50
+ self.algo.vae.prior.categorical_gumbel_softmax_hard = False # use hard selection in forward pass
51
+ self.algo.vae.prior.categorical_init_temp = 1.0 # initial gumbel-softmax temp
52
+ self.algo.vae.prior.categorical_temp_anneal_step = 0.001 # linear temp annealing rate
53
+ self.algo.vae.prior.categorical_min_temp = 0.3 # lowest gumbel-softmax temp
54
+
55
+ self.algo.vae.encoder_layer_dims = (300, 400) # encoder MLP layer dimensions
56
+ self.algo.vae.decoder_layer_dims = (300, 400) # decoder MLP layer dimensions
57
+ self.algo.vae.prior_layer_dims = (300, 400) # prior MLP layer dimensions (if learning conditioned prior)
58
+
59
+ def observation_config(self):
60
+ """
61
+ Update from superclass to specify subgoal modalities.
62
+ """
63
+ super(GLConfig, self).observation_config()
64
+ self.observation.modalities.subgoal.low_dim = [ # specify low-dim subgoal observations for agent to predict
65
+ "robot0_eef_pos",
66
+ "robot0_eef_quat",
67
+ "robot0_gripper_qpos",
68
+ "object",
69
+ ]
70
+ self.observation.modalities.subgoal.rgb = [] # specify rgb image subgoal observations for agent to predict
71
+ self.observation.modalities.subgoal.depth = []
72
+ self.observation.modalities.subgoal.scan = []
73
+ self.observation.modalities.subgoal.do_not_lock_keys()
74
+
75
+ @property
76
+ def all_obs_keys(self):
77
+ """
78
+ Update from superclass to include subgoals.
79
+ """
80
+ # pool all modalities
81
+ return sorted(tuple(set([
82
+ obs_key for group in [
83
+ self.observation.modalities.obs.values(),
84
+ self.observation.modalities.goal.values(),
85
+ self.observation.modalities.subgoal.values(),
86
+ ]
87
+ for modality in group
88
+ for obs_key in modality
89
+ ])))
aloha-devel/robomimic/config/iris_config.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Config for IRIS algorithm.
3
+ """
4
+
5
+ from robomimic.config.bcq_config import BCQConfig
6
+ from robomimic.config.gl_config import GLConfig
7
+ from robomimic.config.bc_config import BCConfig
8
+ from robomimic.config.hbc_config import HBCConfig
9
+
10
+
11
+ class IRISConfig(HBCConfig):
12
+ ALGO_NAME = "iris"
13
+
14
+ def algo_config(self):
15
+ """
16
+ This function populates the `config.algo` attribute of the config, and is given to the
17
+ `Algo` subclass (see `algo/algo.py`) for each algorithm through the `algo_config`
18
+ argument to the constructor. Any parameter that an algorithm needs to determine its
19
+ training and test-time behavior should be populated here.
20
+ """
21
+
22
+ # One of ["separate", "cascade"]. In "separate" mode (default),
23
+ # the planner and actor are trained independently and then the planner subgoal predictions are
24
+ # used to condition the actor at test-time. In "cascade" mode, the actor is trained directly
25
+ # on planner subgoal predictions. In "actor_only" mode, only the actor is trained, and in
26
+ # "planner_only" mode, only the planner is trained.
27
+ self.algo.mode = "separate"
28
+
29
+ self.algo.actor_use_random_subgoals = False # whether to sample subgoal index from [1, subgoal_horizon]
30
+ self.algo.subgoal_update_interval = 10 # how frequently the subgoal should be updated at test-time (usually matches train.seq_length)
31
+
32
+ # ================== Latent Subgoal Config ==================
33
+
34
+ # NOTE: latent subgoals are not supported by IRIS, but superclass expects this config
35
+ self.algo.latent_subgoal.enabled = False
36
+ self.algo.latent_subgoal.prior_correction.enabled = False
37
+ self.algo.latent_subgoal.prior_correction.num_samples = 100
38
+
39
+ # ================== Planner Config ==================
40
+
41
+ # The ValuePlanner planner component is a Goal Learning VAE model
42
+ self.algo.value_planner.planner = GLConfig().algo # config for goal learning
43
+ # set subgoal horizon explicitly
44
+ self.algo.value_planner.planner.subgoal_horizon = 10
45
+ # ensure VAE is used
46
+ self.algo.value_planner.planner.vae.enabled = True
47
+
48
+ # The ValuePlanner value component is a BCQ model
49
+ self.algo.value_planner.value = BCQConfig().algo
50
+ self.algo.value_planner.value.actor.enabled = False # ensure no BCQ actor
51
+ # number of subgoal samples to use for value planner
52
+ self.algo.value_planner.num_samples = 100
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 value planner and actor each get their own obs config.
67
+ """
68
+ self.observation.value_planner.planner = GLConfig().observation
69
+ self.observation.value_planner.value = BCQConfig().observation
70
+ self.observation.actor = BCConfig().observation
71
+
72
+ @property
73
+ def use_goals(self):
74
+ """
75
+ Update from superclass - value planner goal modalities determine goal-conditioning.
76
+ """
77
+ return len(
78
+ self.observation.value_planner.planner.modalities.goal.low_dim +
79
+ self.observation.value_planner.planner.modalities.goal.rgb) > 0
80
+
81
+ @property
82
+ def all_obs_keys(self):
83
+ """
84
+ Update from superclass to include modalities from value planner and actor.
85
+ """
86
+ # pool all modalities
87
+ return sorted(tuple(set([
88
+ obs_key for group in [
89
+ self.observation.value_planner.planner.modalities.obs.values(),
90
+ self.observation.value_planner.planner.modalities.goal.values(),
91
+ self.observation.value_planner.planner.modalities.subgoal.values(),
92
+ self.observation.value_planner.value.modalities.obs.values(),
93
+ self.observation.value_planner.value.modalities.goal.values(),
94
+ self.observation.actor.modalities.obs.values(),
95
+ self.observation.actor.modalities.goal.values(),
96
+ ]
97
+ for modality in group
98
+ for obs_key in modality
99
+ ])))
aloha-devel/robomimic/config/td3_bc_config.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Config for TD3_BC.
3
+ """
4
+
5
+ from robomimic.config.base_config import BaseConfig
6
+
7
+
8
+ class TD3_BCConfig(BaseConfig):
9
+ ALGO_NAME = "td3_bc"
10
+
11
+ def experiment_config(self):
12
+ """
13
+ Update from subclass to set paper defaults for gym envs.
14
+ """
15
+ super(TD3_BCConfig, self).experiment_config()
16
+
17
+ # no validation and no video rendering
18
+ self.experiment.validate = False
19
+ self.experiment.render_video = False
20
+
21
+ # save 10 checkpoints throughout training
22
+ self.experiment.save.every_n_epochs = 20
23
+
24
+ # save models that achieve best rollout return instead of best success rate
25
+ self.experiment.save.on_best_rollout_return = True
26
+ self.experiment.save.on_best_rollout_success_rate = False
27
+
28
+ # epoch definition - 5000 gradient steps per epoch, with 200 epochs = 1M gradient steps, and eval every 1 epochs
29
+ self.experiment.epoch_every_n_steps = 5000
30
+
31
+ # evaluate with normal environment rollouts
32
+ self.experiment.rollout.enabled = True
33
+ self.experiment.rollout.n = 50 # paper uses 10, but we can afford to do 50
34
+ self.experiment.rollout.horizon = 1000
35
+ self.experiment.rollout.rate = 1 # rollout every epoch to match paper
36
+
37
+ def train_config(self):
38
+ """
39
+ Update from subclass to set paper defaults for gym envs.
40
+ """
41
+ super(TD3_BCConfig, self).train_config()
42
+
43
+ # update to normalize observations
44
+ self.train.hdf5_normalize_obs = True
45
+
46
+ # increase batch size to 256
47
+ self.train.batch_size = 256
48
+
49
+ # 200 epochs, with each epoch lasting 5000 gradient steps, for 1M total steps
50
+ self.train.num_epochs = 200
51
+
52
+ def algo_config(self):
53
+ """
54
+ This function populates the `config.algo` attribute of the config, and is given to the
55
+ `Algo` subclass (see `algo/algo.py`) for each algorithm through the `algo_config`
56
+ argument to the constructor. Any parameter that an algorithm needs to determine its
57
+ training and test-time behavior should be populated here.
58
+ """
59
+
60
+ # optimization parameters
61
+ self.algo.optim_params.critic.learning_rate.initial = 3e-4 # critic learning rate
62
+ self.algo.optim_params.critic.learning_rate.decay_factor = 0.1 # factor to decay LR by (if epoch schedule non-empty)
63
+ self.algo.optim_params.critic.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
64
+ self.algo.optim_params.critic.regularization.L2 = 0.00 # L2 regularization strength
65
+ self.algo.optim_params.critic.start_epoch = -1 # number of epochs before starting critic training (-1 means start right away)
66
+ self.algo.optim_params.critic.end_epoch = -1 # number of epochs before ending critic training (-1 means start right away)
67
+
68
+ self.algo.optim_params.actor.learning_rate.initial = 3e-4 # actor learning rate
69
+ self.algo.optim_params.actor.learning_rate.decay_factor = 0.1 # factor to decay LR by (if epoch schedule non-empty)
70
+ self.algo.optim_params.actor.learning_rate.epoch_schedule = [] # epochs where LR decay occurs
71
+ self.algo.optim_params.actor.regularization.L2 = 0.00 # L2 regularization strength
72
+ self.algo.optim_params.actor.start_epoch = -1 # number of epochs before starting actor training (-1 means start right away)
73
+ self.algo.optim_params.actor.end_epoch = -1 # number of epochs before ending actor training (-1 means start right away)
74
+
75
+ # alpha value - for weighting critic loss vs. BC loss
76
+ self.algo.alpha = 2.5
77
+
78
+ # target network related parameters
79
+ self.algo.discount = 0.99 # discount factor to use
80
+ self.algo.n_step = 1 # for using n-step returns in TD-updates
81
+ self.algo.target_tau = 0.005 # update rate for target networks
82
+ self.algo.infinite_horizon = False # if True, scale terminal rewards by 1 / (1 - discount) to treat as infinite horizon
83
+
84
+ # ================== Critic Network Config ===================
85
+ self.algo.critic.use_huber = False # Huber Loss instead of L2 for critic
86
+ self.algo.critic.max_gradient_norm = None # L2 gradient clipping for critic (None to use no clipping)
87
+ self.algo.critic.value_bounds = None # optional 2-tuple to ensure lower and upper bound on value estimates
88
+
89
+ # critic ensemble parameters (TD3 trick)
90
+ self.algo.critic.ensemble.n = 2 # number of Q networks in the ensemble
91
+ self.algo.critic.ensemble.weight = 1.0 # weighting for mixing min and max for target Q value
92
+
93
+ self.algo.critic.layer_dims = (256, 256) # size of critic MLP
94
+
95
+ # ================== Actor Network Config ===================
96
+
97
+ # update actor and target networks every n gradients steps for each critic gradient step
98
+ self.algo.actor.update_freq = 2
99
+
100
+ # exploration noise used to form target action for Q-update - clipped Gaussian noise
101
+ self.algo.actor.noise_std = 0.2 # zero-mean gaussian noise with this std is applied to actions
102
+ self.algo.actor.noise_clip = 0.5 # noise is clipped in each dimension to (-noise_clip, noise_clip)
103
+
104
+ self.algo.actor.layer_dims = (256, 256) # size of actor MLP
105
+
106
+ def observation_config(self):
107
+ """
108
+ Update from superclass to use flat observations from gym envs.
109
+ """
110
+ super(TD3_BCConfig, self).observation_config()
111
+ self.observation.modalities.obs.low_dim = ["flat"]
aloha-devel/robomimic/exps/templates/bcq.json ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "algo_name": "bcq",
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": "../bcq_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.001,
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
+ "action_sampler": {
78
+ "learning_rate": {
79
+ "initial": 0.001,
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
+ "actor": {
90
+ "learning_rate": {
91
+ "initial": 0.001,
92
+ "decay_factor": 0.1,
93
+ "epoch_schedule": []
94
+ },
95
+ "regularization": {
96
+ "L2": 0.0
97
+ },
98
+ "start_epoch": -1,
99
+ "end_epoch": -1
100
+ }
101
+ },
102
+ "discount": 0.99,
103
+ "n_step": 1,
104
+ "target_tau": 0.005,
105
+ "infinite_horizon": false,
106
+ "critic": {
107
+ "use_huber": false,
108
+ "max_gradient_norm": null,
109
+ "value_bounds": null,
110
+ "num_action_samples": 10,
111
+ "num_action_samples_rollout": 100,
112
+ "ensemble": {
113
+ "n": 2,
114
+ "weight": 0.75
115
+ },
116
+ "distributional": {
117
+ "enabled": false,
118
+ "num_atoms": 51
119
+ },
120
+ "layer_dims": [
121
+ 300,
122
+ 400
123
+ ]
124
+ },
125
+ "action_sampler": {
126
+ "actor_layer_dims": [
127
+ 1024,
128
+ 1024
129
+ ],
130
+ "gmm": {
131
+ "enabled": false,
132
+ "num_modes": 5,
133
+ "min_std": 0.0001,
134
+ "std_activation": "softplus",
135
+ "low_noise_eval": true
136
+ },
137
+ "vae": {
138
+ "enabled": true,
139
+ "latent_dim": 14,
140
+ "latent_clip": null,
141
+ "kl_weight": 1.0,
142
+ "decoder": {
143
+ "is_conditioned": true,
144
+ "reconstruction_sum_across_elements": false
145
+ },
146
+ "prior": {
147
+ "learn": false,
148
+ "is_conditioned": false,
149
+ "use_gmm": false,
150
+ "gmm_num_modes": 10,
151
+ "gmm_learn_weights": false,
152
+ "use_categorical": false,
153
+ "categorical_dim": 10,
154
+ "categorical_gumbel_softmax_hard": false,
155
+ "categorical_init_temp": 1.0,
156
+ "categorical_temp_anneal_step": 0.001,
157
+ "categorical_min_temp": 0.3
158
+ },
159
+ "encoder_layer_dims": [
160
+ 300,
161
+ 400
162
+ ],
163
+ "decoder_layer_dims": [
164
+ 300,
165
+ 400
166
+ ],
167
+ "prior_layer_dims": [
168
+ 300,
169
+ 400
170
+ ]
171
+ },
172
+ "freeze_encoder_epoch": -1
173
+ },
174
+ "actor": {
175
+ "enabled": false,
176
+ "perturbation_scale": 0.05,
177
+ "layer_dims": [
178
+ 300,
179
+ 400
180
+ ]
181
+ }
182
+ },
183
+ "observation": {
184
+ "modalities": {
185
+ "obs": {
186
+ "low_dim": [
187
+ "robot0_eef_pos",
188
+ "robot0_eef_quat",
189
+ "robot0_gripper_qpos",
190
+ "object"
191
+ ],
192
+ "rgb": [],
193
+ "depth": [],
194
+ "scan": []
195
+ },
196
+ "goal": {
197
+ "low_dim": [],
198
+ "rgb": [],
199
+ "depth": [],
200
+ "scan": []
201
+ }
202
+ },
203
+ "encoder": {
204
+ "low_dim": {
205
+ "core_class": null,
206
+ "core_kwargs": {},
207
+ "obs_randomizer_class": null,
208
+ "obs_randomizer_kwargs": {}
209
+ },
210
+ "rgb": {
211
+ "core_class": "VisualCore",
212
+ "core_kwargs": {},
213
+ "obs_randomizer_class": null,
214
+ "obs_randomizer_kwargs": {}
215
+ },
216
+ "depth": {
217
+ "core_class": "VisualCore",
218
+ "core_kwargs": {},
219
+ "obs_randomizer_class": null,
220
+ "obs_randomizer_kwargs": {}
221
+ },
222
+ "scan": {
223
+ "core_class": "ScanCore",
224
+ "core_kwargs": {},
225
+ "obs_randomizer_class": null,
226
+ "obs_randomizer_kwargs": {}
227
+ }
228
+ }
229
+ },
230
+ "meta": {
231
+ "hp_base_config_file": null,
232
+ "hp_keys": [],
233
+ "hp_values": []
234
+ }
235
+ }
aloha-devel/robomimic/exps/templates/diffusion_policy.json ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "algo_name": "diffusion_policy",
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":"../diffusion_policy_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": 15,
49
+ "pad_seq_length": true,
50
+ "frame_stack": 2,
51
+ "pad_frame_stack": true,
52
+ "dataset_keys": [
53
+ "actions"
54
+ ],
55
+ "goal_mode": null,
56
+ "cuda": true,
57
+ "batch_size": 256,
58
+ "num_epochs": 2000,
59
+ "seed": 1
60
+ },
61
+ "algo": {
62
+ "optim_params": {
63
+ "policy": {
64
+ "learning_rate": {
65
+ "initial": 0.0001,
66
+ "decay_factor": 0.1,
67
+ "epoch_schedule": []
68
+ },
69
+ "regularization": {
70
+ "L2": 0.0
71
+ }
72
+ }
73
+ },
74
+ "horizon": {
75
+ "observation_horizon": 2,
76
+ "action_horizon": 8,
77
+ "prediction_horizon": 16
78
+ },
79
+ "unet": {
80
+ "enabled": true,
81
+ "diffusion_step_embed_dim": 256,
82
+ "down_dims": [256,512,1024],
83
+ "kernel_size": 5,
84
+ "n_groups": 8
85
+ },
86
+ "ema": {
87
+ "enabled": true,
88
+ "power": 0.75
89
+ },
90
+ "ddpm": {
91
+ "enabled": true,
92
+ "num_train_timesteps": 100,
93
+ "num_inference_timesteps": 100,
94
+ "beta_schedule": "squaredcos_cap_v2",
95
+ "clip_sample": true,
96
+ "prediction_type": "epsilon"
97
+ },
98
+ "ddim": {
99
+ "enabled": false,
100
+ "num_train_timesteps": 100,
101
+ "num_inference_timesteps": 10,
102
+ "beta_schedule": "squaredcos_cap_v2",
103
+ "clip_sample": true,
104
+ "set_alpha_to_one": true,
105
+ "steps_offset": 0,
106
+ "prediction_type": "epsilon"
107
+ }
108
+ },
109
+ "observation": {
110
+ "modalities": {
111
+ "obs": {
112
+ "low_dim": [
113
+ "robot0_eef_pos",
114
+ "robot0_eef_quat",
115
+ "robot0_gripper_qpos",
116
+ "object"
117
+ ],
118
+ "rgb": [],
119
+ "depth": [],
120
+ "scan": []
121
+ },
122
+ "goal": {
123
+ "low_dim": [],
124
+ "rgb": [],
125
+ "depth": [],
126
+ "scan": []
127
+ }
128
+ },
129
+ "encoder": {
130
+ "low_dim": {
131
+ "core_class": null,
132
+ "core_kwargs": {},
133
+ "obs_randomizer_class": null,
134
+ "obs_randomizer_kwargs": {}
135
+ },
136
+ "rgb": {
137
+ "core_class": "VisualCore",
138
+ "core_kwargs": {
139
+ "feature_dimension": 64,
140
+ "backbone_class": "ResNet18Conv",
141
+ "backbone_kwargs": {
142
+ "pretrained": false,
143
+ "input_coord_conv": false
144
+ },
145
+ "pool_class": "SpatialSoftmax",
146
+ "pool_kwargs": {
147
+ "num_kp": 32,
148
+ "learnable_temperature": false,
149
+ "temperature": 1.0,
150
+ "noise_std": 0.0
151
+ }
152
+ },
153
+ "obs_randomizer_class": "CropRandomizer",
154
+ "obs_randomizer_kwargs": {
155
+ "crop_height": 76,
156
+ "crop_width": 76,
157
+ "num_crops": 1,
158
+ "pos_enc": false
159
+ }
160
+ },
161
+ "depth": {
162
+ "core_class": "VisualCore",
163
+ "core_kwargs": {},
164
+ "obs_randomizer_class": null,
165
+ "obs_randomizer_kwargs": {}
166
+ },
167
+ "scan": {
168
+ "core_class": "ScanCore",
169
+ "core_kwargs": {},
170
+ "obs_randomizer_class": null,
171
+ "obs_randomizer_kwargs": {}
172
+ }
173
+ }
174
+ }
175
+ }
aloha-devel/robomimic/models/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .obs_core import EncoderCore, Randomizer
aloha-devel/robomimic/models/__pycache__/obs_core.cpython-38.pyc ADDED
Binary file (26.6 kB). View file
 
aloha-devel/robomimic/models/__pycache__/obs_nets.cpython-38.pyc ADDED
Binary file (36.8 kB). View file
 
aloha-devel/robomimic/models/__pycache__/vae_nets.cpython-38.pyc ADDED
Binary file (43.2 kB). View file
 
aloha-devel/robomimic/models/distributions.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Contains distribution models used as parts of other networks. These
3
+ classes usually inherit or emulate torch distributions.
4
+ """
5
+ import torch
6
+ import torch.nn as nn
7
+ import torch.nn.functional as F
8
+ import torch.distributions as D
9
+
10
+
11
+ class TanhWrappedDistribution(D.Distribution):
12
+ """
13
+ Class that wraps another valid torch distribution, such that sampled values from the base distribution are
14
+ passed through a tanh layer. The corresponding (log) probabilities are also modified accordingly.
15
+ Tanh Normal distribution - adapted from rlkit and CQL codebase
16
+ (https://github.com/aviralkumar2907/CQL/blob/d67dbe9cf5d2b96e3b462b6146f249b3d6569796/d4rl/rlkit/torch/distributions.py#L6).
17
+ """
18
+ def __init__(self, base_dist, scale=1.0, epsilon=1e-6):
19
+ """
20
+ Args:
21
+ base_dist (Distribution): Distribution to wrap with tanh output
22
+ scale (float): Scale of output
23
+ epsilon (float): Numerical stability epsilon when computing log-prob.
24
+ """
25
+ self.base_dist = base_dist
26
+ self.scale = scale
27
+ self.tanh_epsilon = epsilon
28
+ super(TanhWrappedDistribution, self).__init__()
29
+
30
+ def log_prob(self, value, pre_tanh_value=None):
31
+ """
32
+ Args:
33
+ value (torch.Tensor): some tensor to compute log probabilities for
34
+ pre_tanh_value: If specified, will not calculate atanh manually from @value. More numerically stable
35
+ """
36
+ value = value / self.scale
37
+ if pre_tanh_value is None:
38
+ one_plus_x = (1. + value).clamp(min=self.tanh_epsilon)
39
+ one_minus_x = (1. - value).clamp(min=self.tanh_epsilon)
40
+ pre_tanh_value = 0.5 * torch.log(one_plus_x / one_minus_x)
41
+ lp = self.base_dist.log_prob(pre_tanh_value)
42
+ tanh_lp = torch.log(1 - value * value + self.tanh_epsilon)
43
+ # In case the base dist already sums up the log probs, make sure we do the same
44
+ return lp - tanh_lp if len(lp.shape) == len(tanh_lp.shape) else lp - tanh_lp.sum(-1)
45
+
46
+ def sample(self, sample_shape=torch.Size(), return_pretanh_value=False):
47
+ """
48
+ Gradients will and should *not* pass through this operation.
49
+ See https://github.com/pytorch/pytorch/issues/4620 for discussion.
50
+ """
51
+ z = self.base_dist.sample(sample_shape=sample_shape).detach()
52
+
53
+ if return_pretanh_value:
54
+ return torch.tanh(z) * self.scale, z
55
+ else:
56
+ return torch.tanh(z) * self.scale
57
+
58
+ def rsample(self, sample_shape=torch.Size(), return_pretanh_value=False):
59
+ """
60
+ Sampling in the reparameterization case - for differentiable samples.
61
+ """
62
+ z = self.base_dist.rsample(sample_shape=sample_shape)
63
+
64
+ if return_pretanh_value:
65
+ return torch.tanh(z) * self.scale, z
66
+ else:
67
+ return torch.tanh(z) * self.scale
68
+
69
+ @property
70
+ def mean(self):
71
+ return self.base_dist.mean
72
+
73
+ @property
74
+ def stddev(self):
75
+ return self.base_dist.stddev
76
+
77
+
78
+ class DiscreteValueDistribution(object):
79
+ """
80
+ Extension to torch categorical probability distribution in order to keep track
81
+ of the support (categorical values, or in this case, value atoms). This is
82
+ used for distributional value networks.
83
+ """
84
+ def __init__(self, values, probs=None, logits=None):
85
+ """
86
+ Creates a categorical distribution parameterized by either @probs or
87
+ @logits (but not both). Expects inputs to be consistent in shape
88
+ for broadcasting operations (e.g. multiplication).
89
+ """
90
+ self._values = values
91
+ self._categorical_dist = D.Categorical(probs=probs, logits=logits)
92
+
93
+ @property
94
+ def values(self):
95
+ return self._values
96
+
97
+ @property
98
+ def probs(self):
99
+ return self._categorical_dist.probs
100
+
101
+ @property
102
+ def logits(self):
103
+ return self._categorical_dist.logits
104
+
105
+ def mean(self):
106
+ """
107
+ Categorical distribution mean, taking the value support into account.
108
+ """
109
+ return (self._categorical_dist.probs * self._values).sum(dim=-1)
110
+
111
+ def variance(self):
112
+ """
113
+ Categorical distribution variance, taking the value support into account.
114
+ """
115
+ dist_squared = (self.mean().unsqueeze(-1) - self.values).pow(2)
116
+ return (self._categorical_dist.probs * dist_squared).sum(dim=-1)
117
+
118
+ def sample(self, sample_shape=torch.Size()):
119
+ """
120
+ Sample from the distribution. Make sure to return value atoms, not categorical class indices.
121
+ """
122
+ inds = self._categorical_dist.sample(sample_shape=sample_shape)
123
+ return torch.gather(self.values, inds, dim=-1)
aloha-devel/robomimic/models/obs_core.py ADDED
@@ -0,0 +1,829 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Contains torch Modules for core observation processing blocks
3
+ such as encoders (e.g. EncoderCore, VisualCore, ScanCore, ...)
4
+ and randomizers (e.g. Randomizer, CropRandomizer).
5
+ """
6
+
7
+ import abc
8
+ import numpy as np
9
+ import textwrap
10
+ import random
11
+
12
+ import torch
13
+ import torch.nn as nn
14
+
15
+ import robomimic.models.base_nets as BaseNets
16
+ import robomimic.utils.tensor_utils as TensorUtils
17
+ import robomimic.utils.obs_utils as ObsUtils
18
+ from robomimic.utils.python_utils import extract_class_init_kwargs_from_dict
19
+
20
+ # NOTE: this is required for the backbone classes to be found by the `eval` call in the core networks
21
+ from robomimic.models.base_nets import *
22
+ from robomimic.utils.vis_utils import visualize_image_randomizer
23
+ from robomimic.macros import VISUALIZE_RANDOMIZER
24
+
25
+ import torchvision.transforms.functional as TVF
26
+ from torchvision.transforms import Lambda, Compose
27
+
28
+ """
29
+ ================================================
30
+ Encoder Core Networks (Abstract class)
31
+ ================================================
32
+ """
33
+ class EncoderCore(BaseNets.Module):
34
+ """
35
+ Abstract class used to categorize all cores used to encode observations
36
+ """
37
+ def __init__(self, input_shape):
38
+ self.input_shape = input_shape
39
+ super(EncoderCore, self).__init__()
40
+
41
+ def __init_subclass__(cls, **kwargs):
42
+ """
43
+ Hook method to automatically register all valid subclasses so we can keep track of valid observation encoders
44
+ in a global dict.
45
+
46
+ This global dict stores mapping from observation encoder network name to class.
47
+ We keep track of these registries to enable automated class inference at runtime, allowing
48
+ users to simply extend our base encoder class and refer to that class in string form
49
+ in their config, without having to manually register their class internally.
50
+ This also future-proofs us for any additional encoder classes we would
51
+ like to add ourselves.
52
+ """
53
+ ObsUtils.register_encoder_core(cls)
54
+
55
+
56
+ """
57
+ ================================================
58
+ Visual Core Networks (Backbone + Pool)
59
+ ================================================
60
+ """
61
+ class VisualCore(EncoderCore, BaseNets.ConvBase):
62
+ """
63
+ A network block that combines a visual backbone network with optional pooling
64
+ and linear layers.
65
+ """
66
+ def __init__(
67
+ self,
68
+ input_shape,
69
+ backbone_class="ResNet18Conv",
70
+ pool_class="SpatialSoftmax",
71
+ backbone_kwargs=None,
72
+ pool_kwargs=None,
73
+ flatten=True,
74
+ feature_dimension=64,
75
+ ):
76
+ """
77
+ Args:
78
+ input_shape (tuple): shape of input (not including batch dimension)
79
+ backbone_class (str): class name for the visual backbone network. Defaults
80
+ to "ResNet18Conv".
81
+ pool_class (str): class name for the visual feature pooler (optional)
82
+ Common options are "SpatialSoftmax" and "SpatialMeanPool". Defaults to
83
+ "SpatialSoftmax".
84
+ backbone_kwargs (dict): kwargs for the visual backbone network (optional)
85
+ pool_kwargs (dict): kwargs for the visual feature pooler (optional)
86
+ flatten (bool): whether to flatten the visual features
87
+ feature_dimension (int): if not None, add a Linear layer to
88
+ project output into a desired feature dimension
89
+ """
90
+ super(VisualCore, self).__init__(input_shape=input_shape)
91
+ self.flatten = flatten
92
+
93
+ if backbone_kwargs is None:
94
+ backbone_kwargs = dict()
95
+
96
+ # add input channel dimension to visual core inputs
97
+ backbone_kwargs["input_channel"] = input_shape[0]
98
+
99
+ # extract only relevant kwargs for this specific backbone
100
+ backbone_kwargs = extract_class_init_kwargs_from_dict(cls=eval(backbone_class), dic=backbone_kwargs, copy=True)
101
+
102
+ # visual backbone
103
+ assert isinstance(backbone_class, str)
104
+ self.backbone = eval(backbone_class)(**backbone_kwargs)
105
+
106
+ assert isinstance(self.backbone, BaseNets.ConvBase)
107
+
108
+ feat_shape = self.backbone.output_shape(input_shape)
109
+ net_list = [self.backbone]
110
+
111
+ # maybe make pool net
112
+ if pool_class is not None:
113
+ assert isinstance(pool_class, str)
114
+ # feed output shape of backbone to pool net
115
+ if pool_kwargs is None:
116
+ pool_kwargs = dict()
117
+ # extract only relevant kwargs for this specific backbone
118
+ pool_kwargs["input_shape"] = feat_shape
119
+ pool_kwargs = extract_class_init_kwargs_from_dict(cls=eval(pool_class), dic=pool_kwargs, copy=True)
120
+ self.pool = eval(pool_class)(**pool_kwargs)
121
+ assert isinstance(self.pool, BaseNets.Module)
122
+
123
+ feat_shape = self.pool.output_shape(feat_shape)
124
+ net_list.append(self.pool)
125
+ else:
126
+ self.pool = None
127
+
128
+ # flatten layer
129
+ if self.flatten:
130
+ net_list.append(torch.nn.Flatten(start_dim=1, end_dim=-1))
131
+
132
+ # maybe linear layer
133
+ self.feature_dimension = feature_dimension
134
+ if feature_dimension is not None:
135
+ assert self.flatten
136
+ linear = torch.nn.Linear(int(np.prod(feat_shape)), feature_dimension)
137
+ net_list.append(linear)
138
+
139
+ self.nets = nn.Sequential(*net_list)
140
+
141
+ def output_shape(self, input_shape):
142
+ """
143
+ Function to compute output shape from inputs to this module.
144
+
145
+ Args:
146
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
147
+ Some modules may not need this argument, if their output does not depend
148
+ on the size of the input, or if they assume fixed size input.
149
+
150
+ Returns:
151
+ out_shape ([int]): list of integers corresponding to output shape
152
+ """
153
+ if self.feature_dimension is not None:
154
+ # linear output
155
+ return [self.feature_dimension]
156
+ feat_shape = self.backbone.output_shape(input_shape)
157
+ if self.pool is not None:
158
+ # pool output
159
+ feat_shape = self.pool.output_shape(feat_shape)
160
+ # backbone + flat output
161
+ if self.flatten:
162
+ return [np.prod(feat_shape)]
163
+ else:
164
+ return feat_shape
165
+
166
+ def forward(self, inputs):
167
+ """
168
+ Forward pass through visual core.
169
+ """
170
+ ndim = len(self.input_shape)
171
+ assert tuple(inputs.shape)[-ndim:] == tuple(self.input_shape)
172
+ return super(VisualCore, self).forward(inputs)
173
+
174
+ def __repr__(self):
175
+ """Pretty print network."""
176
+ header = '{}'.format(str(self.__class__.__name__))
177
+ msg = ''
178
+ indent = ' ' * 2
179
+ msg += textwrap.indent(
180
+ "\ninput_shape={}\noutput_shape={}".format(self.input_shape, self.output_shape(self.input_shape)), indent)
181
+ msg += textwrap.indent("\nbackbone_net={}".format(self.backbone), indent)
182
+ msg += textwrap.indent("\npool_net={}".format(self.pool), indent)
183
+ msg = header + '(' + msg + '\n)'
184
+ return msg
185
+
186
+
187
+ """
188
+ ================================================
189
+ Scan Core Networks (Conv1D Sequential + Pool)
190
+ ================================================
191
+ """
192
+ class ScanCore(EncoderCore, BaseNets.ConvBase):
193
+ """
194
+ A network block that combines a Conv1D backbone network with optional pooling
195
+ and linear layers.
196
+ """
197
+ def __init__(
198
+ self,
199
+ input_shape,
200
+ conv_kwargs=None,
201
+ conv_activation="relu",
202
+ pool_class=None,
203
+ pool_kwargs=None,
204
+ flatten=True,
205
+ feature_dimension=None,
206
+ ):
207
+ """
208
+ Args:
209
+ input_shape (tuple): shape of input (not including batch dimension)
210
+ conv_kwargs (dict): kwargs for the conv1d backbone network. Should contain lists for the following values:
211
+ out_channels (int)
212
+ kernel_size (int)
213
+ stride (int)
214
+ ...
215
+
216
+ If not specified, or an empty dictionary is specified, some default settings will be used.
217
+ conv_activation (str or None): Activation to use between conv layers. Default is relu.
218
+ Currently, valid options are {relu}
219
+ pool_class (str): class name for the visual feature pooler (optional)
220
+ Common options are "SpatialSoftmax" and "SpatialMeanPool"
221
+ pool_kwargs (dict): kwargs for the visual feature pooler (optional)
222
+ flatten (bool): whether to flatten the network output
223
+ feature_dimension (int): if not None, add a Linear layer to
224
+ project output into a desired feature dimension (note: flatten must be set to True!)
225
+ """
226
+ super(ScanCore, self).__init__(input_shape=input_shape)
227
+ self.flatten = flatten
228
+ self.feature_dimension = feature_dimension
229
+
230
+ if conv_kwargs is None:
231
+ conv_kwargs = dict()
232
+
233
+ # Generate backbone network
234
+ self.backbone = BaseNets.Conv1dBase(
235
+ input_channel=1,
236
+ activation=conv_activation,
237
+ **conv_kwargs,
238
+ )
239
+ feat_shape = self.backbone.output_shape(input_shape=input_shape)
240
+
241
+ # Create netlist of all generated networks
242
+ net_list = [self.backbone]
243
+
244
+ # Possibly add pooling network
245
+ if pool_class is not None:
246
+ # Add an unsqueeze network so that the shape is correct to pass to pooling network
247
+ self.unsqueeze = Unsqueeze(dim=-1)
248
+ net_list.append(self.unsqueeze)
249
+ # Get output shape
250
+ feat_shape = self.unsqueeze.output_shape(feat_shape)
251
+ # Create pooling network
252
+ self.pool = eval(pool_class)(input_shape=feat_shape, **pool_kwargs)
253
+ net_list.append(self.pool)
254
+ feat_shape = self.pool.output_shape(feat_shape)
255
+ else:
256
+ self.unsqueeze, self.pool = None, None
257
+
258
+ # flatten layer
259
+ if self.flatten:
260
+ net_list.append(torch.nn.Flatten(start_dim=1, end_dim=-1))
261
+
262
+ # maybe linear layer
263
+ if self.feature_dimension is not None:
264
+ assert self.flatten
265
+ linear = torch.nn.Linear(int(np.prod(feat_shape)), self.feature_dimension)
266
+ net_list.append(linear)
267
+
268
+ # Generate final network
269
+ self.nets = nn.Sequential(*net_list)
270
+
271
+ def output_shape(self, input_shape):
272
+ """
273
+ Function to compute output shape from inputs to this module.
274
+
275
+ Args:
276
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
277
+ Some modules may not need this argument, if their output does not depend
278
+ on the size of the input, or if they assume fixed size input.
279
+
280
+ Returns:
281
+ out_shape ([int]): list of integers corresponding to output shape
282
+ """
283
+ if self.feature_dimension is not None:
284
+ # linear output
285
+ return [self.feature_dimension]
286
+ feat_shape = self.backbone.output_shape(input_shape)
287
+ if self.pool is not None:
288
+ # pool output
289
+ feat_shape = self.pool.output_shape(self.unsqueeze.output_shape(feat_shape))
290
+ # backbone + flat output
291
+ return [np.prod(feat_shape)] if self.flatten else feat_shape
292
+
293
+ def forward(self, inputs):
294
+ """
295
+ Forward pass through visual core.
296
+ """
297
+ ndim = len(self.input_shape)
298
+ assert tuple(inputs.shape)[-ndim:] == tuple(self.input_shape)
299
+ return super(ScanCore, self).forward(inputs)
300
+
301
+ def __repr__(self):
302
+ """Pretty print network."""
303
+ header = '{}'.format(str(self.__class__.__name__))
304
+ msg = ''
305
+ indent = ' ' * 2
306
+ msg += textwrap.indent(
307
+ "\ninput_shape={}\noutput_shape={}".format(self.input_shape, self.output_shape(self.input_shape)), indent)
308
+ msg += textwrap.indent("\nbackbone_net={}".format(self.backbone), indent)
309
+ msg += textwrap.indent("\npool_net={}".format(self.pool), indent)
310
+ msg = header + '(' + msg + '\n)'
311
+ return msg
312
+
313
+
314
+ """
315
+ ================================================
316
+ Observation Randomizer Networks
317
+ ================================================
318
+ """
319
+ class Randomizer(BaseNets.Module):
320
+ """
321
+ Base class for randomizer networks. Each randomizer should implement the @output_shape_in,
322
+ @output_shape_out, @forward_in, and @forward_out methods. The randomizer's @forward_in
323
+ method is invoked on raw inputs, and @forward_out is invoked on processed inputs
324
+ (usually processed by a @VisualCore instance). Note that the self.training property
325
+ can be used to change the randomizer's behavior at train vs. test time.
326
+ """
327
+ def __init__(self):
328
+ super(Randomizer, self).__init__()
329
+
330
+ def __init_subclass__(cls, **kwargs):
331
+ """
332
+ Hook method to automatically register all valid subclasses so we can keep track of valid observation randomizers
333
+ in a global dict.
334
+
335
+ This global dict stores mapping from observation randomizer network name to class.
336
+ We keep track of these registries to enable automated class inference at runtime, allowing
337
+ users to simply extend our base randomizer class and refer to that class in string form
338
+ in their config, without having to manually register their class internally.
339
+ This also future-proofs us for any additional randomizer classes we would
340
+ like to add ourselves.
341
+ """
342
+ ObsUtils.register_randomizer(cls)
343
+
344
+ def output_shape(self, input_shape=None):
345
+ """
346
+ This function is unused. See @output_shape_in and @output_shape_out.
347
+ """
348
+ raise NotImplementedError
349
+
350
+ @abc.abstractmethod
351
+ def output_shape_in(self, input_shape=None):
352
+ """
353
+ Function to compute output shape from inputs to this module. Corresponds to
354
+ the @forward_in operation, where raw inputs (usually observation modalities)
355
+ are passed in.
356
+
357
+ Args:
358
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
359
+ Some modules may not need this argument, if their output does not depend
360
+ on the size of the input, or if they assume fixed size input.
361
+
362
+ Returns:
363
+ out_shape ([int]): list of integers corresponding to output shape
364
+ """
365
+ raise NotImplementedError
366
+
367
+ @abc.abstractmethod
368
+ def output_shape_out(self, input_shape=None):
369
+ """
370
+ Function to compute output shape from inputs to this module. Corresponds to
371
+ the @forward_out operation, where processed inputs (usually encoded observation
372
+ modalities) are passed in.
373
+
374
+ Args:
375
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
376
+ Some modules may not need this argument, if their output does not depend
377
+ on the size of the input, or if they assume fixed size input.
378
+
379
+ Returns:
380
+ out_shape ([int]): list of integers corresponding to output shape
381
+ """
382
+ raise NotImplementedError
383
+
384
+ def forward_in(self, inputs):
385
+ """
386
+ Randomize raw inputs if training.
387
+ """
388
+ if self.training:
389
+ randomized_inputs = self._forward_in(inputs=inputs)
390
+ if VISUALIZE_RANDOMIZER:
391
+ num_samples_to_visualize = min(4, inputs.shape[0])
392
+ self._visualize(inputs, randomized_inputs, num_samples_to_visualize=num_samples_to_visualize)
393
+ return randomized_inputs
394
+ else:
395
+ return self._forward_in_eval(inputs)
396
+
397
+ def forward_out(self, inputs):
398
+ """
399
+ Processing for network outputs.
400
+ """
401
+ if self.training:
402
+ return self._forward_out(inputs)
403
+ else:
404
+ return self._forward_out_eval(inputs)
405
+
406
+ @abc.abstractmethod
407
+ def _forward_in(self, inputs):
408
+ """
409
+ Randomize raw inputs.
410
+ """
411
+ raise NotImplementedError
412
+
413
+ def _forward_in_eval(self, inputs):
414
+ """
415
+ Test-time behavior for the randomizer
416
+ """
417
+ return inputs
418
+
419
+ @abc.abstractmethod
420
+ def _forward_out(self, inputs):
421
+ """
422
+ Processing for network outputs.
423
+ """
424
+ return inputs
425
+
426
+ def _forward_out_eval(self, inputs):
427
+ """
428
+ Test-time behavior for the randomizer
429
+ """
430
+ return inputs
431
+
432
+ @abc.abstractmethod
433
+ def _visualize(self, pre_random_input, randomized_input, num_samples_to_visualize=2):
434
+ """
435
+ Visualize the original input and the randomized input for _forward_in for debugging purposes.
436
+ """
437
+ pass
438
+
439
+
440
+ class CropRandomizer(Randomizer):
441
+ """
442
+ Randomly sample crops at input, and then average across crop features at output.
443
+ """
444
+ def __init__(
445
+ self,
446
+ input_shape,
447
+ crop_height=76,
448
+ crop_width=76,
449
+ num_crops=1,
450
+ pos_enc=False,
451
+ ):
452
+ """
453
+ Args:
454
+ input_shape (tuple, list): shape of input (not including batch dimension)
455
+ crop_height (int): crop height
456
+ crop_width (int): crop width
457
+ num_crops (int): number of random crops to take
458
+ pos_enc (bool): if True, add 2 channels to the output to encode the spatial
459
+ location of the cropped pixels in the source image
460
+ """
461
+ super(CropRandomizer, self).__init__()
462
+
463
+ assert len(input_shape) == 3 # (C, H, W)
464
+ assert crop_height < input_shape[1]
465
+ assert crop_width < input_shape[2]
466
+
467
+ self.input_shape = input_shape
468
+ self.crop_height = crop_height
469
+ self.crop_width = crop_width
470
+ self.num_crops = num_crops
471
+ self.pos_enc = pos_enc
472
+
473
+ def output_shape_in(self, input_shape=None):
474
+ """
475
+ Function to compute output shape from inputs to this module. Corresponds to
476
+ the @forward_in operation, where raw inputs (usually observation modalities)
477
+ are passed in.
478
+
479
+ Args:
480
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
481
+ Some modules may not need this argument, if their output does not depend
482
+ on the size of the input, or if they assume fixed size input.
483
+
484
+ Returns:
485
+ out_shape ([int]): list of integers corresponding to output shape
486
+ """
487
+
488
+ # outputs are shape (C, CH, CW), or maybe C + 2 if using position encoding, because
489
+ # the number of crops are reshaped into the batch dimension, increasing the batch
490
+ # size from B to B * N
491
+ out_c = self.input_shape[0] + 2 if self.pos_enc else self.input_shape[0]
492
+ return [out_c, self.crop_height, self.crop_width]
493
+
494
+ def output_shape_out(self, input_shape=None):
495
+ """
496
+ Function to compute output shape from inputs to this module. Corresponds to
497
+ the @forward_out operation, where processed inputs (usually encoded observation
498
+ modalities) are passed in.
499
+
500
+ Args:
501
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
502
+ Some modules may not need this argument, if their output does not depend
503
+ on the size of the input, or if they assume fixed size input.
504
+
505
+ Returns:
506
+ out_shape ([int]): list of integers corresponding to output shape
507
+ """
508
+
509
+ # since the forward_out operation splits [B * N, ...] -> [B, N, ...]
510
+ # and then pools to result in [B, ...], only the batch dimension changes,
511
+ # and so the other dimensions retain their shape.
512
+ return list(input_shape)
513
+
514
+ def _forward_in(self, inputs):
515
+ """
516
+ Samples N random crops for each input in the batch, and then reshapes
517
+ inputs to [B * N, ...].
518
+ """
519
+ assert len(inputs.shape) >= 3 # must have at least (C, H, W) dimensions
520
+ out, _ = ObsUtils.sample_random_image_crops(
521
+ images=inputs,
522
+ crop_height=self.crop_height,
523
+ crop_width=self.crop_width,
524
+ num_crops=self.num_crops,
525
+ pos_enc=self.pos_enc,
526
+ )
527
+ # [B, N, ...] -> [B * N, ...]
528
+ return TensorUtils.join_dimensions(out, 0, 1)
529
+
530
+ def _forward_in_eval(self, inputs):
531
+ """
532
+ Do center crops during eval
533
+ """
534
+ assert len(inputs.shape) >= 3 # must have at least (C, H, W) dimensions
535
+ inputs = inputs.permute(*range(inputs.dim()-3), inputs.dim()-2, inputs.dim()-1, inputs.dim()-3)
536
+ out = ObsUtils.center_crop(inputs, self.crop_height, self.crop_width)
537
+ out = out.permute(*range(out.dim()-3), out.dim()-1, out.dim()-3, out.dim()-2)
538
+ return out
539
+
540
+ def _forward_out(self, inputs):
541
+ """
542
+ Splits the outputs from shape [B * N, ...] -> [B, N, ...] and then average across N
543
+ to result in shape [B, ...] to make sure the network output is consistent with
544
+ what would have happened if there were no randomization.
545
+ """
546
+ batch_size = (inputs.shape[0] // self.num_crops)
547
+ out = TensorUtils.reshape_dimensions(inputs, begin_axis=0, end_axis=0,
548
+ target_dims=(batch_size, self.num_crops))
549
+ return out.mean(dim=1)
550
+
551
+ def _visualize(self, pre_random_input, randomized_input, num_samples_to_visualize=2):
552
+ batch_size = pre_random_input.shape[0]
553
+ random_sample_inds = torch.randint(0, batch_size, size=(num_samples_to_visualize,))
554
+ pre_random_input_np = TensorUtils.to_numpy(pre_random_input)[random_sample_inds]
555
+ randomized_input = TensorUtils.reshape_dimensions(
556
+ randomized_input,
557
+ begin_axis=0,
558
+ end_axis=0,
559
+ target_dims=(batch_size, self.num_crops)
560
+ ) # [B * N, ...] -> [B, N, ...]
561
+ randomized_input_np = TensorUtils.to_numpy(randomized_input[random_sample_inds])
562
+
563
+ pre_random_input_np = pre_random_input_np.transpose((0, 2, 3, 1)) # [B, C, H, W] -> [B, H, W, C]
564
+ randomized_input_np = randomized_input_np.transpose((0, 1, 3, 4, 2)) # [B, N, C, H, W] -> [B, N, H, W, C]
565
+
566
+ visualize_image_randomizer(
567
+ pre_random_input_np,
568
+ randomized_input_np,
569
+ randomizer_name='{}'.format(str(self.__class__.__name__))
570
+ )
571
+
572
+ def __repr__(self):
573
+ """Pretty print network."""
574
+ header = '{}'.format(str(self.__class__.__name__))
575
+ msg = header + "(input_shape={}, crop_size=[{}, {}], num_crops={})".format(
576
+ self.input_shape, self.crop_height, self.crop_width, self.num_crops)
577
+ return msg
578
+
579
+
580
+ class ColorRandomizer(Randomizer):
581
+ """
582
+ Randomly sample color jitter at input, and then average across color jtters at output.
583
+ """
584
+ def __init__(
585
+ self,
586
+ input_shape,
587
+ brightness=0.3,
588
+ contrast=0.3,
589
+ saturation=0.3,
590
+ hue=0.3,
591
+ num_samples=1,
592
+ ):
593
+ """
594
+ Args:
595
+ input_shape (tuple, list): shape of input (not including batch dimension)
596
+ brightness (None or float or 2-tuple): How much to jitter brightness. brightness_factor is chosen uniformly
597
+ from [max(0, 1 - brightness), 1 + brightness] or the given [min, max]. Should be non negative numbers.
598
+ contrast (None or float or 2-tuple): How much to jitter contrast. contrast_factor is chosen uniformly
599
+ from [max(0, 1 - contrast), 1 + contrast] or the given [min, max]. Should be non negative numbers.
600
+ saturation (None or float or 2-tuple): How much to jitter saturation. saturation_factor is chosen uniformly
601
+ from [max(0, 1 - saturation), 1 + saturation] or the given [min, max]. Should be non negative numbers.
602
+ hue (None or float or 2-tuple): How much to jitter hue. hue_factor is chosen uniformly from [-hue, hue] or
603
+ the given [min, max]. Should have 0<= hue <= 0.5 or -0.5 <= min <= max <= 0.5. To jitter hue, the pixel
604
+ values of the input image has to be non-negative for conversion to HSV space; thus it does not work
605
+ if you normalize your image to an interval with negative values, or use an interpolation that
606
+ generates negative values before using this function.
607
+ num_samples (int): number of random color jitters to take
608
+ """
609
+ super(ColorRandomizer, self).__init__()
610
+
611
+ assert len(input_shape) == 3 # (C, H, W)
612
+
613
+ self.input_shape = input_shape
614
+ self.brightness = [max(0, 1 - brightness), 1 + brightness] if type(brightness) in {float, int} else brightness
615
+ self.contrast = [max(0, 1 - contrast), 1 + contrast] if type(contrast) in {float, int} else contrast
616
+ self.saturation = [max(0, 1 - saturation), 1 + saturation] if type(saturation) in {float, int} else saturation
617
+ self.hue = [-hue, hue] if type(hue) in {float, int} else hue
618
+ self.num_samples = num_samples
619
+
620
+ @torch.jit.unused
621
+ def get_transform(self):
622
+ """
623
+ Get a randomized transform to be applied on image.
624
+
625
+ Implementation taken directly from:
626
+
627
+ https://github.com/pytorch/vision/blob/2f40a483d73018ae6e1488a484c5927f2b309969/torchvision/transforms/transforms.py#L1053-L1085
628
+
629
+ Returns:
630
+ Transform: Transform which randomly adjusts brightness, contrast and
631
+ saturation in a random order.
632
+ """
633
+ transforms = []
634
+
635
+ if self.brightness is not None:
636
+ brightness_factor = random.uniform(self.brightness[0], self.brightness[1])
637
+ transforms.append(Lambda(lambda img: TVF.adjust_brightness(img, brightness_factor)))
638
+
639
+ if self.contrast is not None:
640
+ contrast_factor = random.uniform(self.contrast[0], self.contrast[1])
641
+ transforms.append(Lambda(lambda img: TVF.adjust_contrast(img, contrast_factor)))
642
+
643
+ if self.saturation is not None:
644
+ saturation_factor = random.uniform(self.saturation[0], self.saturation[1])
645
+ transforms.append(Lambda(lambda img: TVF.adjust_saturation(img, saturation_factor)))
646
+
647
+ if self.hue is not None:
648
+ hue_factor = random.uniform(self.hue[0], self.hue[1])
649
+ transforms.append(Lambda(lambda img: TVF.adjust_hue(img, hue_factor)))
650
+
651
+ random.shuffle(transforms)
652
+ transform = Compose(transforms)
653
+
654
+ return transform
655
+
656
+ def get_batch_transform(self, N):
657
+ """
658
+ Generates a batch transform, where each set of sample(s) along the batch (first) dimension will have the same
659
+ @N unique ColorJitter transforms applied.
660
+
661
+ Args:
662
+ N (int): Number of ColorJitter transforms to apply per set of sample(s) along the batch (first) dimension
663
+
664
+ Returns:
665
+ Lambda: Aggregated transform which will autoamtically apply a different ColorJitter transforms to
666
+ each sub-set of samples along batch dimension, assumed to be the FIRST dimension in the inputted tensor
667
+ Note: This function will MULTIPLY the first dimension by N
668
+ """
669
+ return Lambda(lambda x: torch.stack([self.get_transform()(x_) for x_ in x for _ in range(N)]))
670
+
671
+ def output_shape_in(self, input_shape=None):
672
+ # outputs are same shape as inputs
673
+ return list(input_shape)
674
+
675
+ def output_shape_out(self, input_shape=None):
676
+ # since the forward_out operation splits [B * N, ...] -> [B, N, ...]
677
+ # and then pools to result in [B, ...], only the batch dimension changes,
678
+ # and so the other dimensions retain their shape.
679
+ return list(input_shape)
680
+
681
+ def _forward_in(self, inputs):
682
+ """
683
+ Samples N random color jitters for each input in the batch, and then reshapes
684
+ inputs to [B * N, ...].
685
+ """
686
+ assert len(inputs.shape) >= 3 # must have at least (C, H, W) dimensions
687
+
688
+ # Make sure shape is exactly 4
689
+ if len(inputs.shape) == 3:
690
+ inputs = torch.unsqueeze(inputs, dim=0)
691
+
692
+ # TODO: Make more efficient other than implicit for-loop?
693
+ # Create lambda to aggregate all color randomizings at once
694
+ transform = self.get_batch_transform(N=self.num_samples)
695
+
696
+ return transform(inputs)
697
+
698
+ def _forward_out(self, inputs):
699
+ """
700
+ Splits the outputs from shape [B * N, ...] -> [B, N, ...] and then average across N
701
+ to result in shape [B, ...] to make sure the network output is consistent with
702
+ what would have happened if there were no randomization.
703
+ """
704
+ batch_size = (inputs.shape[0] // self.num_samples)
705
+ out = TensorUtils.reshape_dimensions(inputs, begin_axis=0, end_axis=0,
706
+ target_dims=(batch_size, self.num_samples))
707
+ return out.mean(dim=1)
708
+
709
+ def _visualize(self, pre_random_input, randomized_input, num_samples_to_visualize=2):
710
+ batch_size = pre_random_input.shape[0]
711
+ random_sample_inds = torch.randint(0, batch_size, size=(num_samples_to_visualize,))
712
+ pre_random_input_np = TensorUtils.to_numpy(pre_random_input)[random_sample_inds]
713
+ randomized_input = TensorUtils.reshape_dimensions(
714
+ randomized_input,
715
+ begin_axis=0,
716
+ end_axis=0,
717
+ target_dims=(batch_size, self.num_samples)
718
+ ) # [B * N, ...] -> [B, N, ...]
719
+ randomized_input_np = TensorUtils.to_numpy(randomized_input[random_sample_inds])
720
+
721
+ pre_random_input_np = pre_random_input_np.transpose((0, 2, 3, 1)) # [B, C, H, W] -> [B, H, W, C]
722
+ randomized_input_np = randomized_input_np.transpose((0, 1, 3, 4, 2)) # [B, N, C, H, W] -> [B, N, H, W, C]
723
+
724
+ visualize_image_randomizer(
725
+ pre_random_input_np,
726
+ randomized_input_np,
727
+ randomizer_name='{}'.format(str(self.__class__.__name__))
728
+ )
729
+
730
+ def __repr__(self):
731
+ """Pretty print network."""
732
+ header = '{}'.format(str(self.__class__.__name__))
733
+ msg = header + f"(input_shape={self.input_shape}, brightness={self.brightness}, contrast={self.contrast}, " \
734
+ f"saturation={self.saturation}, hue={self.hue}, num_samples={self.num_samples})"
735
+ return msg
736
+
737
+
738
+ class GaussianNoiseRandomizer(Randomizer):
739
+ """
740
+ Randomly sample gaussian noise at input, and then average across noises at output.
741
+ """
742
+ def __init__(
743
+ self,
744
+ input_shape,
745
+ noise_mean=0.0,
746
+ noise_std=0.3,
747
+ limits=None,
748
+ num_samples=1,
749
+ ):
750
+ """
751
+ Args:
752
+ input_shape (tuple, list): shape of input (not including batch dimension)
753
+ noise_mean (float): Mean of noise to apply
754
+ noise_std (float): Standard deviation of noise to apply
755
+ limits (None or 2-tuple): If specified, should be the (min, max) values to clamp all noisied samples to
756
+ num_samples (int): number of random color jitters to take
757
+ """
758
+ super(GaussianNoiseRandomizer, self).__init__()
759
+
760
+ self.input_shape = input_shape
761
+ self.noise_mean = noise_mean
762
+ self.noise_std = noise_std
763
+ self.limits = limits
764
+ self.num_samples = num_samples
765
+
766
+ def output_shape_in(self, input_shape=None):
767
+ # outputs are same shape as inputs
768
+ return list(input_shape)
769
+
770
+ def output_shape_out(self, input_shape=None):
771
+ # since the forward_out operation splits [B * N, ...] -> [B, N, ...]
772
+ # and then pools to result in [B, ...], only the batch dimension changes,
773
+ # and so the other dimensions retain their shape.
774
+ return list(input_shape)
775
+
776
+ def _forward_in(self, inputs):
777
+ """
778
+ Samples N random gaussian noises for each input in the batch, and then reshapes
779
+ inputs to [B * N, ...].
780
+ """
781
+ out = TensorUtils.repeat_by_expand_at(inputs, repeats=self.num_samples, dim=0)
782
+
783
+ # Sample noise across all samples
784
+ out = torch.rand(size=out.shape) * self.noise_std + self.noise_mean + out
785
+
786
+ # Possibly clamp
787
+ if self.limits is not None:
788
+ out = torch.clip(out, min=self.limits[0], max=self.limits[1])
789
+
790
+ return out
791
+
792
+ def _forward_out(self, inputs):
793
+ """
794
+ Splits the outputs from shape [B * N, ...] -> [B, N, ...] and then average across N
795
+ to result in shape [B, ...] to make sure the network output is consistent with
796
+ what would have happened if there were no randomization.
797
+ """
798
+ batch_size = (inputs.shape[0] // self.num_samples)
799
+ out = TensorUtils.reshape_dimensions(inputs, begin_axis=0, end_axis=0,
800
+ target_dims=(batch_size, self.num_samples))
801
+ return out.mean(dim=1)
802
+
803
+ def _visualize(self, pre_random_input, randomized_input, num_samples_to_visualize=2):
804
+ batch_size = pre_random_input.shape[0]
805
+ random_sample_inds = torch.randint(0, batch_size, size=(num_samples_to_visualize,))
806
+ pre_random_input_np = TensorUtils.to_numpy(pre_random_input)[random_sample_inds]
807
+ randomized_input = TensorUtils.reshape_dimensions(
808
+ randomized_input,
809
+ begin_axis=0,
810
+ end_axis=0,
811
+ target_dims=(batch_size, self.num_samples)
812
+ ) # [B * N, ...] -> [B, N, ...]
813
+ randomized_input_np = TensorUtils.to_numpy(randomized_input[random_sample_inds])
814
+
815
+ pre_random_input_np = pre_random_input_np.transpose((0, 2, 3, 1)) # [B, C, H, W] -> [B, H, W, C]
816
+ randomized_input_np = randomized_input_np.transpose((0, 1, 3, 4, 2)) # [B, N, C, H, W] -> [B, N, H, W, C]
817
+
818
+ visualize_image_randomizer(
819
+ pre_random_input_np,
820
+ randomized_input_np,
821
+ randomizer_name='{}'.format(str(self.__class__.__name__))
822
+ )
823
+
824
+ def __repr__(self):
825
+ """Pretty print network."""
826
+ header = '{}'.format(str(self.__class__.__name__))
827
+ msg = header + f"(input_shape={self.input_shape}, noise_mean={self.noise_mean}, noise_std={self.noise_std}, " \
828
+ f"limits={self.limits}, num_samples={self.num_samples})"
829
+ return msg
aloha-devel/robomimic/models/policy_nets.py ADDED
@@ -0,0 +1,1570 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Contains torch Modules for policy networks. These networks take an
3
+ observation dictionary as input (and possibly additional conditioning,
4
+ such as subgoal or goal dictionaries) and produce action predictions,
5
+ samples, or distributions as outputs. Note that actions
6
+ are assumed to lie in [-1, 1], and most networks will have a final
7
+ tanh activation to help ensure this range.
8
+ """
9
+ import textwrap
10
+ import numpy as np
11
+ from collections import OrderedDict
12
+
13
+ import torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+ import torch.distributions as D
17
+
18
+ import robomimic.utils.tensor_utils as TensorUtils
19
+ from robomimic.models.base_nets import Module
20
+ from robomimic.models.transformers import GPT_Backbone
21
+ from robomimic.models.obs_nets import MIMO_MLP, RNN_MIMO_MLP, MIMO_Transformer, ObservationDecoder
22
+ from robomimic.models.vae_nets import VAE
23
+ from robomimic.models.distributions import TanhWrappedDistribution
24
+
25
+
26
+ class ActorNetwork(MIMO_MLP):
27
+ """
28
+ A basic policy network that predicts actions from observations.
29
+ Can optionally be goal conditioned on future observations.
30
+ """
31
+ def __init__(
32
+ self,
33
+ obs_shapes,
34
+ ac_dim,
35
+ mlp_layer_dims,
36
+ goal_shapes=None,
37
+ encoder_kwargs=None,
38
+ ):
39
+ """
40
+ Args:
41
+ obs_shapes (OrderedDict): a dictionary that maps observation keys to
42
+ expected shapes for observations.
43
+
44
+ ac_dim (int): dimension of action space.
45
+
46
+ mlp_layer_dims ([int]): sequence of integers for the MLP hidden layers sizes.
47
+
48
+ goal_shapes (OrderedDict): a dictionary that maps observation keys to
49
+ expected shapes for goal observations.
50
+
51
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
52
+ be nested dictionary containing relevant per-observation key information for encoder networks.
53
+ Should be of form:
54
+
55
+ obs_modality1: dict
56
+ feature_dimension: int
57
+ core_class: str
58
+ core_kwargs: dict
59
+ ...
60
+ ...
61
+ obs_randomizer_class: str
62
+ obs_randomizer_kwargs: dict
63
+ ...
64
+ ...
65
+ obs_modality2: dict
66
+ ...
67
+ """
68
+ assert isinstance(obs_shapes, OrderedDict)
69
+ self.obs_shapes = obs_shapes
70
+ self.ac_dim = ac_dim
71
+
72
+ # set up different observation groups for @MIMO_MLP
73
+ observation_group_shapes = OrderedDict()
74
+ observation_group_shapes["obs"] = OrderedDict(self.obs_shapes)
75
+
76
+ self._is_goal_conditioned = False
77
+ if goal_shapes is not None and len(goal_shapes) > 0:
78
+ assert isinstance(goal_shapes, OrderedDict)
79
+ self._is_goal_conditioned = True
80
+ self.goal_shapes = OrderedDict(goal_shapes)
81
+ observation_group_shapes["goal"] = OrderedDict(self.goal_shapes)
82
+ else:
83
+ self.goal_shapes = OrderedDict()
84
+
85
+ output_shapes = self._get_output_shapes()
86
+ super(ActorNetwork, self).__init__(
87
+ input_obs_group_shapes=observation_group_shapes,
88
+ output_shapes=output_shapes,
89
+ layer_dims=mlp_layer_dims,
90
+ encoder_kwargs=encoder_kwargs,
91
+ )
92
+
93
+ def _get_output_shapes(self):
94
+ """
95
+ Allow subclasses to re-define outputs from @MIMO_MLP, since we won't
96
+ always directly predict actions, but may instead predict the parameters
97
+ of a action distribution.
98
+ """
99
+ return OrderedDict(action=(self.ac_dim,))
100
+
101
+ def output_shape(self, input_shape=None):
102
+ return [self.ac_dim]
103
+
104
+ def forward(self, obs_dict, goal_dict=None):
105
+ actions = super(ActorNetwork, self).forward(obs=obs_dict, goal=goal_dict)["action"]
106
+ # apply tanh squashing to ensure actions are in [-1, 1]
107
+ return torch.tanh(actions)
108
+
109
+ def _to_string(self):
110
+ """Info to pretty print."""
111
+ return "action_dim={}".format(self.ac_dim)
112
+
113
+
114
+ class PerturbationActorNetwork(ActorNetwork):
115
+ """
116
+ An action perturbation network - primarily used in BCQ.
117
+ It takes states and actions and returns action perturbations.
118
+ """
119
+ def __init__(
120
+ self,
121
+ obs_shapes,
122
+ ac_dim,
123
+ mlp_layer_dims,
124
+ perturbation_scale=0.05,
125
+ goal_shapes=None,
126
+ encoder_kwargs=None,
127
+ ):
128
+ """
129
+ Args:
130
+ obs_shapes (OrderedDict): a dictionary that maps observation keys to
131
+ expected shapes for observations.
132
+
133
+ ac_dim (int): dimension of action space.
134
+
135
+ mlp_layer_dims ([int]): sequence of integers for the MLP hidden layers sizes.
136
+
137
+ perturbation_scale (float): the perturbation network output is always squashed to
138
+ lie in +/- @perturbation_scale. The final action output is equal to the original
139
+ input action added to the output perturbation (and clipped to lie in [-1, 1]).
140
+
141
+ goal_shapes (OrderedDict): a dictionary that maps modality to
142
+ expected shapes for goal observations.
143
+
144
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
145
+ be nested dictionary containing relevant per-modality information for encoder networks.
146
+ Should be of form:
147
+
148
+ obs_modality1: dict
149
+ feature_dimension: int
150
+ core_class: str
151
+ core_kwargs: dict
152
+ ...
153
+ ...
154
+ obs_randomizer_class: str
155
+ obs_randomizer_kwargs: dict
156
+ ...
157
+ ...
158
+ obs_modality2: dict
159
+ ...
160
+ """
161
+ self.perturbation_scale = perturbation_scale
162
+
163
+ # add in action as a modality
164
+ new_obs_shapes = OrderedDict(obs_shapes)
165
+ new_obs_shapes["action"] = (ac_dim,)
166
+
167
+ # pass to super class to instantiate network
168
+ super(PerturbationActorNetwork, self).__init__(
169
+ obs_shapes=new_obs_shapes,
170
+ ac_dim=ac_dim,
171
+ mlp_layer_dims=mlp_layer_dims,
172
+ goal_shapes=goal_shapes,
173
+ encoder_kwargs=encoder_kwargs,
174
+ )
175
+
176
+ def forward(self, obs_dict, acts, goal_dict=None):
177
+ """Forward pass through perturbation actor."""
178
+ # add in actions
179
+ inputs = dict(obs_dict)
180
+ inputs["action"] = acts
181
+ perturbations = super(PerturbationActorNetwork, self).forward(inputs, goal_dict)
182
+
183
+ # add perturbations from network to original actions, and ensure the new actions lie in [-1, 1]
184
+ output_actions = acts + self.perturbation_scale * perturbations
185
+ output_actions = output_actions.clamp(-1.0, 1.0)
186
+ return output_actions
187
+
188
+ def _to_string(self):
189
+ """Info to pretty print."""
190
+ return "action_dim={}, perturbation_scale={}".format(self.ac_dim, self.perturbation_scale)
191
+
192
+
193
+ class GaussianActorNetwork(ActorNetwork):
194
+ """
195
+ Variant of actor network that learns a diagonal unimodal Gaussian distribution
196
+ over actions.
197
+ """
198
+ def __init__(
199
+ self,
200
+ obs_shapes,
201
+ ac_dim,
202
+ mlp_layer_dims,
203
+ fixed_std=False,
204
+ std_activation="softplus",
205
+ init_last_fc_weight=None,
206
+ init_std=0.3,
207
+ mean_limits=(-9.0, 9.0),
208
+ std_limits=(0.007, 7.5),
209
+ low_noise_eval=True,
210
+ use_tanh=False,
211
+ goal_shapes=None,
212
+ encoder_kwargs=None,
213
+ ):
214
+ """
215
+ Args:
216
+ obs_shapes (OrderedDict): a dictionary that maps modality to
217
+ expected shapes for observations.
218
+
219
+ ac_dim (int): dimension of action space.
220
+
221
+ mlp_layer_dims ([int]): sequence of integers for the MLP hidden layers sizes.
222
+
223
+ fixed_std (bool): if True, std is not learned, but kept constant at @init_std
224
+
225
+ std_activation (None or str): type of activation to use for std deviation. Options are:
226
+
227
+ None: no activation applied (not recommended unless using fixed std)
228
+
229
+ `'softplus'`: Only applicable if not using fixed std. Softplus activation applied, after which the
230
+ output is scaled by init_std / softplus(0)
231
+
232
+ `'exp'`: Only applicable if not using fixed std. Exp applied; this corresponds to network output
233
+ as being interpreted as log_std instead of std
234
+
235
+ NOTE: In all cases, the final result is clipped to be within @std_limits
236
+
237
+ init_last_fc_weight (None or float): if specified, will intialize the final layer network weights to be
238
+ uniformly sampled from [-init_weight, init_weight]
239
+
240
+ init_std (None or float): approximate initial scaling for standard deviation outputs
241
+ from network. If None
242
+
243
+ mean_limits (2-array): (min, max) to clamp final mean output by
244
+
245
+ std_limits (2-array): (min, max) to clamp final std output by
246
+
247
+ low_noise_eval (float): if True, model will output means of Gaussian distribution
248
+ at eval time.
249
+
250
+ use_tanh (bool): if True, use a tanh-Gaussian distribution
251
+
252
+ goal_shapes (OrderedDict): a dictionary that maps modality to
253
+ expected shapes for goal observations.
254
+
255
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
256
+ be nested dictionary containing relevant per-modality information for encoder networks.
257
+ Should be of form:
258
+
259
+ obs_modality1: dict
260
+ feature_dimension: int
261
+ core_class: str
262
+ core_kwargs: dict
263
+ ...
264
+ ...
265
+ obs_randomizer_class: str
266
+ obs_randomizer_kwargs: dict
267
+ ...
268
+ ...
269
+ obs_modality2: dict
270
+ ...
271
+ """
272
+
273
+ # parameters specific to Gaussian actor
274
+ self.fixed_std = fixed_std
275
+ self.init_std = init_std
276
+ self.mean_limits = np.array(mean_limits)
277
+ self.std_limits = np.array(std_limits)
278
+
279
+ # Define activations to use
280
+ def softplus_scaled(x):
281
+ out = F.softplus(x)
282
+ out = out * (self.init_std / F.softplus(torch.zeros(1).to(x.device)))
283
+ return out
284
+
285
+ self.activations = {
286
+ None: lambda x: x,
287
+ "softplus": softplus_scaled,
288
+ "exp": torch.exp,
289
+ }
290
+ assert std_activation in self.activations, \
291
+ "std_activation must be one of: {}; instead got: {}".format(self.activations.keys(), std_activation)
292
+ self.std_activation = std_activation if not self.fixed_std else None
293
+
294
+ self.low_noise_eval = low_noise_eval
295
+ self.use_tanh = use_tanh
296
+
297
+ super(GaussianActorNetwork, self).__init__(
298
+ obs_shapes=obs_shapes,
299
+ ac_dim=ac_dim,
300
+ mlp_layer_dims=mlp_layer_dims,
301
+ goal_shapes=goal_shapes,
302
+ encoder_kwargs=encoder_kwargs,
303
+ )
304
+
305
+ # If initialization weight was specified, make sure all final layer network weights are specified correctly
306
+ if init_last_fc_weight is not None:
307
+ with torch.no_grad():
308
+ for name, layer in self.nets["decoder"].nets.items():
309
+ torch.nn.init.uniform_(layer.weight, -init_last_fc_weight, init_last_fc_weight)
310
+ torch.nn.init.uniform_(layer.bias, -init_last_fc_weight, init_last_fc_weight)
311
+
312
+ def _get_output_shapes(self):
313
+ """
314
+ Tells @MIMO_MLP superclass about the output dictionary that should be generated
315
+ at the last layer. Network outputs parameters of Gaussian distribution.
316
+ """
317
+ return OrderedDict(
318
+ mean=(self.ac_dim,),
319
+ scale=(self.ac_dim,),
320
+ )
321
+
322
+ def forward_train(self, obs_dict, goal_dict=None):
323
+ """
324
+ Return full Gaussian distribution, which is useful for computing
325
+ quantities necessary at train-time, like log-likelihood, KL
326
+ divergence, etc.
327
+
328
+ Args:
329
+ obs_dict (dict): batch of observations
330
+ goal_dict (dict): if not None, batch of goal observations
331
+
332
+ Returns:
333
+ dist (Distribution): Gaussian distribution
334
+ """
335
+ out = MIMO_MLP.forward(self, obs=obs_dict, goal=goal_dict)
336
+ mean = out["mean"]
337
+ # Use either constant std or learned std depending on setting
338
+ scale = out["scale"] if not self.fixed_std else torch.ones_like(mean) * self.init_std
339
+
340
+ # Clamp the mean
341
+ mean = torch.clamp(mean, min=self.mean_limits[0], max=self.mean_limits[1])
342
+
343
+ # apply tanh squashing to mean if not using tanh-Gaussian to ensure mean is in [-1, 1]
344
+ if not self.use_tanh:
345
+ mean = torch.tanh(mean)
346
+
347
+ # Calculate scale
348
+ if self.low_noise_eval and (not self.training):
349
+ # override std value so that you always approximately sample the mean
350
+ scale = torch.ones_like(mean) * 1e-4
351
+ else:
352
+ # Post-process the scale accordingly
353
+ scale = self.activations[self.std_activation](scale)
354
+ # Clamp the scale
355
+ scale = torch.clamp(scale, min=self.std_limits[0], max=self.std_limits[1])
356
+
357
+
358
+ # the Independent call will make it so that `batch_shape` for dist will be equal to batch size
359
+ # while `event_shape` will be equal to action dimension - ensuring that log-probability
360
+ # computations are summed across the action dimension
361
+ dist = D.Normal(loc=mean, scale=scale)
362
+ dist = D.Independent(dist, 1)
363
+
364
+ if self.use_tanh:
365
+ # Wrap distribution with Tanh
366
+ dist = TanhWrappedDistribution(base_dist=dist, scale=1.)
367
+
368
+ return dist
369
+
370
+ def forward(self, obs_dict, goal_dict=None):
371
+ """
372
+ Samples actions from the policy distribution.
373
+
374
+ Args:
375
+ obs_dict (dict): batch of observations
376
+ goal_dict (dict): if not None, batch of goal observations
377
+
378
+ Returns:
379
+ action (torch.Tensor): batch of actions from policy distribution
380
+ """
381
+ dist = self.forward_train(obs_dict, goal_dict)
382
+ if self.low_noise_eval and (not self.training):
383
+ if self.use_tanh:
384
+ # # scaling factor lets us output actions like [-1. 1.] and is consistent with the distribution transform
385
+ # return (1. + 1e-6) * torch.tanh(dist.base_dist.mean)
386
+ return torch.tanh(dist.mean)
387
+ return dist.mean
388
+ return dist.sample()
389
+
390
+ def _to_string(self):
391
+ """Info to pretty print."""
392
+ msg = "action_dim={}\nfixed_std={}\nstd_activation={}\ninit_std={}\nmean_limits={}\nstd_limits={}\nlow_noise_eval={}".format(
393
+ self.ac_dim, self.fixed_std, self.std_activation, self.init_std, self.mean_limits, self.std_limits, self.low_noise_eval)
394
+ return msg
395
+
396
+
397
+ class GMMActorNetwork(ActorNetwork):
398
+ """
399
+ Variant of actor network that learns a multimodal Gaussian mixture distribution
400
+ over actions.
401
+ """
402
+ def __init__(
403
+ self,
404
+ obs_shapes,
405
+ ac_dim,
406
+ mlp_layer_dims,
407
+ num_modes=5,
408
+ min_std=0.01,
409
+ std_activation="softplus",
410
+ low_noise_eval=True,
411
+ use_tanh=False,
412
+ goal_shapes=None,
413
+ encoder_kwargs=None,
414
+ ):
415
+ """
416
+ Args:
417
+ obs_shapes (OrderedDict): a dictionary that maps modality to
418
+ expected shapes for observations.
419
+
420
+ ac_dim (int): dimension of action space.
421
+
422
+ mlp_layer_dims ([int]): sequence of integers for the MLP hidden layers sizes.
423
+
424
+ num_modes (int): number of GMM modes
425
+
426
+ min_std (float): minimum std output from network
427
+
428
+ std_activation (None or str): type of activation to use for std deviation. Options are:
429
+
430
+ `'softplus'`: Softplus activation applied
431
+
432
+ `'exp'`: Exp applied; this corresponds to network output being interpreted as log_std instead of std
433
+
434
+ low_noise_eval (float): if True, model will sample from GMM with low std, so that
435
+ one of the GMM modes will be sampled (approximately)
436
+
437
+ use_tanh (bool): if True, use a tanh-Gaussian distribution
438
+
439
+ goal_shapes (OrderedDict): a dictionary that maps modality to
440
+ expected shapes for goal observations.
441
+
442
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
443
+ be nested dictionary containing relevant per-modality information for encoder networks.
444
+ Should be of form:
445
+
446
+ obs_modality1: dict
447
+ feature_dimension: int
448
+ core_class: str
449
+ core_kwargs: dict
450
+ ...
451
+ ...
452
+ obs_randomizer_class: str
453
+ obs_randomizer_kwargs: dict
454
+ ...
455
+ ...
456
+ obs_modality2: dict
457
+ ...
458
+ """
459
+
460
+ # parameters specific to GMM actor
461
+ self.num_modes = num_modes
462
+ self.min_std = min_std
463
+ self.low_noise_eval = low_noise_eval
464
+ self.use_tanh = use_tanh
465
+
466
+ # Define activations to use
467
+ self.activations = {
468
+ "softplus": F.softplus,
469
+ "exp": torch.exp,
470
+ }
471
+ assert std_activation in self.activations, \
472
+ "std_activation must be one of: {}; instead got: {}".format(self.activations.keys(), std_activation)
473
+ self.std_activation = std_activation
474
+
475
+ super(GMMActorNetwork, self).__init__(
476
+ obs_shapes=obs_shapes,
477
+ ac_dim=ac_dim,
478
+ mlp_layer_dims=mlp_layer_dims,
479
+ goal_shapes=goal_shapes,
480
+ encoder_kwargs=encoder_kwargs,
481
+ )
482
+
483
+ def _get_output_shapes(self):
484
+ """
485
+ Tells @MIMO_MLP superclass about the output dictionary that should be generated
486
+ at the last layer. Network outputs parameters of GMM distribution.
487
+ """
488
+ return OrderedDict(
489
+ mean=(self.num_modes, self.ac_dim),
490
+ scale=(self.num_modes, self.ac_dim),
491
+ logits=(self.num_modes,),
492
+ )
493
+
494
+ def forward_train(self, obs_dict, goal_dict=None):
495
+ """
496
+ Return full GMM distribution, which is useful for computing
497
+ quantities necessary at train-time, like log-likelihood, KL
498
+ divergence, etc.
499
+
500
+ Args:
501
+ obs_dict (dict): batch of observations
502
+ goal_dict (dict): if not None, batch of goal observations
503
+
504
+ Returns:
505
+ dist (Distribution): GMM distribution
506
+ """
507
+ out = MIMO_MLP.forward(self, obs=obs_dict, goal=goal_dict)
508
+ means = out["mean"]
509
+ scales = out["scale"]
510
+ logits = out["logits"]
511
+
512
+ # apply tanh squashing to means if not using tanh-GMM to ensure means are in [-1, 1]
513
+ if not self.use_tanh:
514
+ means = torch.tanh(means)
515
+
516
+ # Calculate scale
517
+ if self.low_noise_eval and (not self.training):
518
+ # low-noise for all Gaussian dists
519
+ scales = torch.ones_like(means) * 1e-4
520
+ else:
521
+ # post-process the scale accordingly
522
+ scales = self.activations[self.std_activation](scales) + self.min_std
523
+
524
+ # mixture components - make sure that `batch_shape` for the distribution is equal
525
+ # to (batch_size, num_modes) since MixtureSameFamily expects this shape
526
+ component_distribution = D.Normal(loc=means, scale=scales)
527
+ component_distribution = D.Independent(component_distribution, 1)
528
+
529
+ # unnormalized logits to categorical distribution for mixing the modes
530
+ mixture_distribution = D.Categorical(logits=logits)
531
+
532
+ dist = D.MixtureSameFamily(
533
+ mixture_distribution=mixture_distribution,
534
+ component_distribution=component_distribution,
535
+ )
536
+
537
+ if self.use_tanh:
538
+ # Wrap distribution with Tanh
539
+ dist = TanhWrappedDistribution(base_dist=dist, scale=1.)
540
+
541
+ return dist
542
+
543
+ def forward(self, obs_dict, goal_dict=None):
544
+ """
545
+ Samples actions from the policy distribution.
546
+
547
+ Args:
548
+ obs_dict (dict): batch of observations
549
+ goal_dict (dict): if not None, batch of goal observations
550
+
551
+ Returns:
552
+ action (torch.Tensor): batch of actions from policy distribution
553
+ """
554
+ dist = self.forward_train(obs_dict, goal_dict)
555
+ return dist.sample()
556
+
557
+ def _to_string(self):
558
+ """Info to pretty print."""
559
+ return "action_dim={}\nnum_modes={}\nmin_std={}\nstd_activation={}\nlow_noise_eval={}".format(
560
+ self.ac_dim, self.num_modes, self.min_std, self.std_activation, self.low_noise_eval)
561
+
562
+
563
+ class RNNActorNetwork(RNN_MIMO_MLP):
564
+ """
565
+ An RNN policy network that predicts actions from observations.
566
+ """
567
+ def __init__(
568
+ self,
569
+ obs_shapes,
570
+ ac_dim,
571
+ mlp_layer_dims,
572
+ rnn_hidden_dim,
573
+ rnn_num_layers,
574
+ rnn_type="LSTM", # [LSTM, GRU]
575
+ rnn_kwargs=None,
576
+ goal_shapes=None,
577
+ encoder_kwargs=None,
578
+ ):
579
+ """
580
+ Args:
581
+ obs_shapes (OrderedDict): a dictionary that maps modality to
582
+ expected shapes for observations.
583
+
584
+ ac_dim (int): dimension of action space.
585
+
586
+ mlp_layer_dims ([int]): sequence of integers for the MLP hidden layers sizes.
587
+
588
+ rnn_hidden_dim (int): RNN hidden dimension
589
+
590
+ rnn_num_layers (int): number of RNN layers
591
+
592
+ rnn_type (str): [LSTM, GRU]
593
+
594
+ rnn_kwargs (dict): kwargs for the torch.nn.LSTM / GRU
595
+
596
+ goal_shapes (OrderedDict): a dictionary that maps modality to
597
+ expected shapes for goal observations.
598
+
599
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
600
+ be nested dictionary containing relevant per-modality information for encoder networks.
601
+ Should be of form:
602
+
603
+ obs_modality1: dict
604
+ feature_dimension: int
605
+ core_class: str
606
+ core_kwargs: dict
607
+ ...
608
+ ...
609
+ obs_randomizer_class: str
610
+ obs_randomizer_kwargs: dict
611
+ ...
612
+ ...
613
+ obs_modality2: dict
614
+ ...
615
+ """
616
+ self.ac_dim = ac_dim
617
+
618
+ assert isinstance(obs_shapes, OrderedDict)
619
+ self.obs_shapes = obs_shapes
620
+
621
+ # set up different observation groups for @RNN_MIMO_MLP
622
+ observation_group_shapes = OrderedDict()
623
+ observation_group_shapes["obs"] = OrderedDict(self.obs_shapes)
624
+
625
+ self._is_goal_conditioned = False
626
+ if goal_shapes is not None and len(goal_shapes) > 0:
627
+ assert isinstance(goal_shapes, OrderedDict)
628
+ self._is_goal_conditioned = True
629
+ self.goal_shapes = OrderedDict(goal_shapes)
630
+ observation_group_shapes["goal"] = OrderedDict(self.goal_shapes)
631
+ else:
632
+ self.goal_shapes = OrderedDict()
633
+
634
+ output_shapes = self._get_output_shapes()
635
+ super(RNNActorNetwork, self).__init__(
636
+ input_obs_group_shapes=observation_group_shapes,
637
+ output_shapes=output_shapes,
638
+ mlp_layer_dims=mlp_layer_dims,
639
+ mlp_activation=nn.ReLU,
640
+ mlp_layer_func=nn.Linear,
641
+ rnn_hidden_dim=rnn_hidden_dim,
642
+ rnn_num_layers=rnn_num_layers,
643
+ rnn_type=rnn_type,
644
+ rnn_kwargs=rnn_kwargs,
645
+ per_step=True,
646
+ encoder_kwargs=encoder_kwargs,
647
+ )
648
+
649
+ def _get_output_shapes(self):
650
+ """
651
+ Allow subclasses to re-define outputs from @RNN_MIMO_MLP, since we won't
652
+ always directly predict actions, but may instead predict the parameters
653
+ of a action distribution.
654
+ """
655
+ return OrderedDict(action=(self.ac_dim,))
656
+
657
+ def output_shape(self, input_shape):
658
+ # note: @input_shape should be dictionary (key: mod)
659
+ # infers temporal dimension from input shape
660
+ mod = list(self.obs_shapes.keys())[0]
661
+ T = input_shape[mod][0]
662
+ TensorUtils.assert_size_at_dim(input_shape, size=T, dim=0,
663
+ msg="RNNActorNetwork: input_shape inconsistent in temporal dimension")
664
+ return [T, self.ac_dim]
665
+
666
+ def forward(self, obs_dict, goal_dict=None, rnn_init_state=None, return_state=False):
667
+ """
668
+ Forward a sequence of inputs through the RNN and the per-step network.
669
+
670
+ Args:
671
+ obs_dict (dict): batch of observations - each tensor in the dictionary
672
+ should have leading dimensions batch and time [B, T, ...]
673
+ goal_dict (dict): if not None, batch of goal observations
674
+ rnn_init_state: rnn hidden state, initialize to zero state if set to None
675
+ return_state (bool): whether to return hidden state
676
+
677
+ Returns:
678
+ actions (torch.Tensor): predicted action sequence
679
+ rnn_state: return rnn state at the end if return_state is set to True
680
+ """
681
+ if self._is_goal_conditioned:
682
+ assert goal_dict is not None
683
+ # repeat the goal observation in time to match dimension with obs_dict
684
+ mod = list(obs_dict.keys())[0]
685
+ goal_dict = TensorUtils.unsqueeze_expand_at(goal_dict, size=obs_dict[mod].shape[1], dim=1)
686
+
687
+ outputs = super(RNNActorNetwork, self).forward(
688
+ obs=obs_dict, goal=goal_dict, rnn_init_state=rnn_init_state, return_state=return_state)
689
+
690
+ if return_state:
691
+ actions, state = outputs
692
+ else:
693
+ actions = outputs
694
+ state = None
695
+
696
+ # apply tanh squashing to ensure actions are in [-1, 1]
697
+ actions = torch.tanh(actions["action"])
698
+
699
+ if return_state:
700
+ return actions, state
701
+ else:
702
+ return actions
703
+
704
+ def forward_step(self, obs_dict, goal_dict=None, rnn_state=None):
705
+ """
706
+ Unroll RNN over single timestep to get actions.
707
+
708
+ Args:
709
+ obs_dict (dict): batch of observations. Should not contain
710
+ time dimension.
711
+ goal_dict (dict): if not None, batch of goal observations
712
+ rnn_state: rnn hidden state, initialize to zero state if set to None
713
+
714
+ Returns:
715
+ actions (torch.Tensor): batch of actions - does not contain time dimension
716
+ state: updated rnn state
717
+ """
718
+ obs_dict = TensorUtils.to_sequence(obs_dict)
719
+ action, state = self.forward(
720
+ obs_dict, goal_dict, rnn_init_state=rnn_state, return_state=True)
721
+ return action[:, 0], state
722
+
723
+ def _to_string(self):
724
+ """Info to pretty print."""
725
+ return "action_dim={}".format(self.ac_dim)
726
+
727
+
728
+ class RNNGMMActorNetwork(RNNActorNetwork):
729
+ """
730
+ An RNN GMM policy network that predicts sequences of action distributions from observation sequences.
731
+ """
732
+ def __init__(
733
+ self,
734
+ obs_shapes,
735
+ ac_dim,
736
+ mlp_layer_dims,
737
+ rnn_hidden_dim,
738
+ rnn_num_layers,
739
+ rnn_type="LSTM", # [LSTM, GRU]
740
+ rnn_kwargs=None,
741
+ num_modes=5,
742
+ min_std=0.01,
743
+ std_activation="softplus",
744
+ low_noise_eval=True,
745
+ use_tanh=False,
746
+ goal_shapes=None,
747
+ encoder_kwargs=None,
748
+ ):
749
+ """
750
+ Args:
751
+
752
+ rnn_hidden_dim (int): RNN hidden dimension
753
+
754
+ rnn_num_layers (int): number of RNN layers
755
+
756
+ rnn_type (str): [LSTM, GRU]
757
+
758
+ rnn_kwargs (dict): kwargs for the torch.nn.LSTM / GRU
759
+
760
+ num_modes (int): number of GMM modes
761
+
762
+ min_std (float): minimum std output from network
763
+
764
+ std_activation (None or str): type of activation to use for std deviation. Options are:
765
+
766
+ `'softplus'`: Softplus activation applied
767
+
768
+ `'exp'`: Exp applied; this corresponds to network output being interpreted as log_std instead of std
769
+
770
+ low_noise_eval (float): if True, model will sample from GMM with low std, so that
771
+ one of the GMM modes will be sampled (approximately)
772
+
773
+ use_tanh (bool): if True, use a tanh-Gaussian distribution
774
+
775
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
776
+ be nested dictionary containing relevant per-modality information for encoder networks.
777
+ Should be of form:
778
+
779
+ obs_modality1: dict
780
+ feature_dimension: int
781
+ core_class: str
782
+ core_kwargs: dict
783
+ ...
784
+ ...
785
+ obs_randomizer_class: str
786
+ obs_randomizer_kwargs: dict
787
+ ...
788
+ ...
789
+ obs_modality2: dict
790
+ ...
791
+ """
792
+
793
+ # parameters specific to GMM actor
794
+ self.num_modes = num_modes
795
+ self.min_std = min_std
796
+ self.low_noise_eval = low_noise_eval
797
+ self.use_tanh = use_tanh
798
+
799
+ # Define activations to use
800
+ self.activations = {
801
+ "softplus": F.softplus,
802
+ "exp": torch.exp,
803
+ }
804
+ assert std_activation in self.activations, \
805
+ "std_activation must be one of: {}; instead got: {}".format(self.activations.keys(), std_activation)
806
+ self.std_activation = std_activation
807
+
808
+ super(RNNGMMActorNetwork, self).__init__(
809
+ obs_shapes=obs_shapes,
810
+ ac_dim=ac_dim,
811
+ mlp_layer_dims=mlp_layer_dims,
812
+ rnn_hidden_dim=rnn_hidden_dim,
813
+ rnn_num_layers=rnn_num_layers,
814
+ rnn_type=rnn_type,
815
+ rnn_kwargs=rnn_kwargs,
816
+ goal_shapes=goal_shapes,
817
+ encoder_kwargs=encoder_kwargs,
818
+ )
819
+
820
+ def _get_output_shapes(self):
821
+ """
822
+ Tells @MIMO_MLP superclass about the output dictionary that should be generated
823
+ at the last layer. Network outputs parameters of GMM distribution.
824
+ """
825
+ return OrderedDict(
826
+ mean=(self.num_modes, self.ac_dim),
827
+ scale=(self.num_modes, self.ac_dim),
828
+ logits=(self.num_modes,),
829
+ )
830
+
831
+ def forward_train(self, obs_dict, goal_dict=None, rnn_init_state=None, return_state=False):
832
+ """
833
+ Return full GMM distribution, which is useful for computing
834
+ quantities necessary at train-time, like log-likelihood, KL
835
+ divergence, etc.
836
+
837
+ Args:
838
+ obs_dict (dict): batch of observations
839
+ goal_dict (dict): if not None, batch of goal observations
840
+ rnn_init_state: rnn hidden state, initialize to zero state if set to None
841
+ return_state (bool): whether to return hidden state
842
+
843
+ Returns:
844
+ dists (Distribution): sequence of GMM distributions over the timesteps
845
+ rnn_state: return rnn state at the end if return_state is set to True
846
+ """
847
+ if self._is_goal_conditioned:
848
+ assert goal_dict is not None
849
+ # repeat the goal observation in time to match dimension with obs_dict
850
+ mod = list(obs_dict.keys())[0]
851
+ goal_dict = TensorUtils.unsqueeze_expand_at(goal_dict, size=obs_dict[mod].shape[1], dim=1)
852
+
853
+ outputs = RNN_MIMO_MLP.forward(
854
+ self, obs=obs_dict, goal=goal_dict, rnn_init_state=rnn_init_state, return_state=return_state)
855
+
856
+ if return_state:
857
+ outputs, state = outputs
858
+ else:
859
+ state = None
860
+
861
+ means = outputs["mean"]
862
+ scales = outputs["scale"]
863
+ logits = outputs["logits"]
864
+
865
+ # apply tanh squashing to mean if not using tanh-GMM to ensure means are in [-1, 1]
866
+ if not self.use_tanh:
867
+ means = torch.tanh(means)
868
+
869
+ if self.low_noise_eval and (not self.training):
870
+ # low-noise for all Gaussian dists
871
+ scales = torch.ones_like(means) * 1e-4
872
+ else:
873
+ # post-process the scale accordingly
874
+ scales = self.activations[self.std_activation](scales) + self.min_std
875
+
876
+ # mixture components - make sure that `batch_shape` for the distribution is equal
877
+ # to (batch_size, timesteps, num_modes) since MixtureSameFamily expects this shape
878
+ component_distribution = D.Normal(loc=means, scale=scales)
879
+ component_distribution = D.Independent(component_distribution, 1) # shift action dim to event shape
880
+
881
+ # unnormalized logits to categorical distribution for mixing the modes
882
+ mixture_distribution = D.Categorical(logits=logits)
883
+
884
+ dists = D.MixtureSameFamily(
885
+ mixture_distribution=mixture_distribution,
886
+ component_distribution=component_distribution,
887
+ )
888
+
889
+ if self.use_tanh:
890
+ # Wrap distribution with Tanh
891
+ dists = TanhWrappedDistribution(base_dist=dists, scale=1.)
892
+
893
+ if return_state:
894
+ return dists, state
895
+ else:
896
+ return dists
897
+
898
+ def forward(self, obs_dict, goal_dict=None, rnn_init_state=None, return_state=False):
899
+ """
900
+ Samples actions from the policy distribution.
901
+
902
+ Args:
903
+ obs_dict (dict): batch of observations
904
+ goal_dict (dict): if not None, batch of goal observations
905
+
906
+ Returns:
907
+ action (torch.Tensor): batch of actions from policy distribution
908
+ """
909
+ out = self.forward_train(obs_dict=obs_dict, goal_dict=goal_dict, rnn_init_state=rnn_init_state, return_state=return_state)
910
+ if return_state:
911
+ ad, state = out
912
+ return ad.sample(), state
913
+ return out.sample()
914
+
915
+ def forward_train_step(self, obs_dict, goal_dict=None, rnn_state=None):
916
+ """
917
+ Unroll RNN over single timestep to get action GMM distribution, which
918
+ is useful for computing quantities necessary at train-time, like
919
+ log-likelihood, KL divergence, etc.
920
+
921
+ Args:
922
+ obs_dict (dict): batch of observations. Should not contain
923
+ time dimension.
924
+ goal_dict (dict): if not None, batch of goal observations
925
+ rnn_state: rnn hidden state, initialize to zero state if set to None
926
+
927
+ Returns:
928
+ ad (Distribution): GMM action distributions
929
+ state: updated rnn state
930
+ """
931
+ obs_dict = TensorUtils.to_sequence(obs_dict)
932
+ ad, state = self.forward_train(
933
+ obs_dict, goal_dict, rnn_init_state=rnn_state, return_state=True)
934
+
935
+ # to squeeze time dimension, make another action distribution
936
+ assert ad.component_distribution.base_dist.loc.shape[1] == 1
937
+ assert ad.component_distribution.base_dist.scale.shape[1] == 1
938
+ assert ad.mixture_distribution.logits.shape[1] == 1
939
+ component_distribution = D.Normal(
940
+ loc=ad.component_distribution.base_dist.loc.squeeze(1),
941
+ scale=ad.component_distribution.base_dist.scale.squeeze(1),
942
+ )
943
+ component_distribution = D.Independent(component_distribution, 1)
944
+ mixture_distribution = D.Categorical(logits=ad.mixture_distribution.logits.squeeze(1))
945
+ ad = D.MixtureSameFamily(
946
+ mixture_distribution=mixture_distribution,
947
+ component_distribution=component_distribution,
948
+ )
949
+ return ad, state
950
+
951
+ def forward_step(self, obs_dict, goal_dict=None, rnn_state=None):
952
+ """
953
+ Unroll RNN over single timestep to get sampled actions.
954
+
955
+ Args:
956
+ obs_dict (dict): batch of observations. Should not contain
957
+ time dimension.
958
+ goal_dict (dict): if not None, batch of goal observations
959
+ rnn_state: rnn hidden state, initialize to zero state if set to None
960
+
961
+ Returns:
962
+ acts (torch.Tensor): batch of actions - does not contain time dimension
963
+ state: updated rnn state
964
+ """
965
+ obs_dict = TensorUtils.to_sequence(obs_dict)
966
+ acts, state = self.forward(
967
+ obs_dict, goal_dict, rnn_init_state=rnn_state, return_state=True)
968
+ assert acts.shape[1] == 1
969
+ return acts[:, 0], state
970
+
971
+ def _to_string(self):
972
+ """Info to pretty print."""
973
+ msg = "action_dim={}, std_activation={}, low_noise_eval={}, num_nodes={}, min_std={}".format(
974
+ self.ac_dim, self.std_activation, self.low_noise_eval, self.num_modes, self.min_std)
975
+ return msg
976
+
977
+
978
+ class TransformerActorNetwork(MIMO_Transformer):
979
+ """
980
+ An Transformer policy network that predicts actions from observation sequences (assumed to be frame stacked
981
+ from previous observations) and possible from previous actions as well (in an autoregressive manner).
982
+ """
983
+ def __init__(
984
+ self,
985
+ obs_shapes,
986
+ ac_dim,
987
+ transformer_embed_dim,
988
+ transformer_num_layers,
989
+ transformer_num_heads,
990
+ transformer_context_length,
991
+ transformer_emb_dropout=0.1,
992
+ transformer_attn_dropout=0.1,
993
+ transformer_block_output_dropout=0.1,
994
+ transformer_sinusoidal_embedding=False,
995
+ transformer_activation="gelu",
996
+ transformer_nn_parameter_for_timesteps=False,
997
+ goal_shapes=None,
998
+ encoder_kwargs=None,
999
+ ):
1000
+ """
1001
+ Args:
1002
+
1003
+ obs_shapes (OrderedDict): a dictionary that maps modality to
1004
+ expected shapes for observations.
1005
+
1006
+ ac_dim (int): dimension of action space.
1007
+
1008
+ transformer_embed_dim (int): dimension for embeddings used by transformer
1009
+
1010
+ transformer_num_layers (int): number of transformer blocks to stack
1011
+
1012
+ transformer_num_heads (int): number of attention heads for each
1013
+ transformer block - must divide @transformer_embed_dim evenly. Self-attention is
1014
+ computed over this many partitions of the embedding dimension separately.
1015
+
1016
+ transformer_context_length (int): expected length of input sequences
1017
+
1018
+ transformer_embedding_dropout (float): dropout probability for embedding inputs in transformer
1019
+
1020
+ transformer_attn_dropout (float): dropout probability for attention outputs for each transformer block
1021
+
1022
+ transformer_block_output_dropout (float): dropout probability for final outputs for each transformer block
1023
+
1024
+ goal_shapes (OrderedDict): a dictionary that maps modality to
1025
+ expected shapes for goal observations.
1026
+
1027
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
1028
+ be nested dictionary containing relevant per-modality information for encoder networks.
1029
+ Should be of form:
1030
+
1031
+ obs_modality1: dict
1032
+ feature_dimension: int
1033
+ core_class: str
1034
+ core_kwargs: dict
1035
+ ...
1036
+ ...
1037
+ obs_randomizer_class: str
1038
+ obs_randomizer_kwargs: dict
1039
+ ...
1040
+ ...
1041
+ obs_modality2: dict
1042
+ ...
1043
+ """
1044
+ self.ac_dim = ac_dim
1045
+
1046
+ assert isinstance(obs_shapes, OrderedDict)
1047
+ self.obs_shapes = obs_shapes
1048
+
1049
+ self.transformer_nn_parameter_for_timesteps = transformer_nn_parameter_for_timesteps
1050
+
1051
+ # set up different observation groups for @RNN_MIMO_MLP
1052
+ observation_group_shapes = OrderedDict()
1053
+ observation_group_shapes["obs"] = OrderedDict(self.obs_shapes)
1054
+
1055
+ self._is_goal_conditioned = False
1056
+ if goal_shapes is not None and len(goal_shapes) > 0:
1057
+ assert isinstance(goal_shapes, OrderedDict)
1058
+ self._is_goal_conditioned = True
1059
+ self.goal_shapes = OrderedDict(goal_shapes)
1060
+ observation_group_shapes["goal"] = OrderedDict(self.goal_shapes)
1061
+ else:
1062
+ self.goal_shapes = OrderedDict()
1063
+
1064
+ output_shapes = self._get_output_shapes()
1065
+ super(TransformerActorNetwork, self).__init__(
1066
+ input_obs_group_shapes=observation_group_shapes,
1067
+ output_shapes=output_shapes,
1068
+ transformer_embed_dim=transformer_embed_dim,
1069
+ transformer_num_layers=transformer_num_layers,
1070
+ transformer_num_heads=transformer_num_heads,
1071
+ transformer_context_length=transformer_context_length,
1072
+ transformer_emb_dropout=transformer_emb_dropout,
1073
+ transformer_attn_dropout=transformer_attn_dropout,
1074
+ transformer_block_output_dropout=transformer_block_output_dropout,
1075
+ transformer_sinusoidal_embedding=transformer_sinusoidal_embedding,
1076
+ transformer_activation=transformer_activation,
1077
+ transformer_nn_parameter_for_timesteps=transformer_nn_parameter_for_timesteps,
1078
+
1079
+ encoder_kwargs=encoder_kwargs,
1080
+ )
1081
+
1082
+ def _get_output_shapes(self):
1083
+ """
1084
+ Allow subclasses to re-define outputs from @MIMO_Transformer, since we won't
1085
+ always directly predict actions, but may instead predict the parameters
1086
+ of a action distribution.
1087
+ """
1088
+ output_shapes = OrderedDict(action=(self.ac_dim,))
1089
+ return output_shapes
1090
+
1091
+ def output_shape(self, input_shape):
1092
+ # note: @input_shape should be dictionary (key: mod)
1093
+ # infers temporal dimension from input shape
1094
+ mod = list(self.obs_shapes.keys())[0]
1095
+ T = input_shape[mod][0]
1096
+ TensorUtils.assert_size_at_dim(input_shape, size=T, dim=0,
1097
+ msg="TransformerActorNetwork: input_shape inconsistent in temporal dimension")
1098
+ return [T, self.ac_dim]
1099
+
1100
+ def forward(self, obs_dict, actions=None, goal_dict=None):
1101
+ """
1102
+ Forward a sequence of inputs through the Transformer.
1103
+ Args:
1104
+ obs_dict (dict): batch of observations - each tensor in the dictionary
1105
+ should have leading dimensions batch and time [B, T, ...]
1106
+ actions (torch.Tensor): batch of actions of shape [B, T, D]
1107
+ goal_dict (dict): if not None, batch of goal observations
1108
+ Returns:
1109
+ outputs (torch.Tensor or dict): contains predicted action sequence, or dictionary
1110
+ with predicted action sequence and predicted observation sequences
1111
+ """
1112
+ if self._is_goal_conditioned:
1113
+ assert goal_dict is not None
1114
+ # repeat the goal observation in time to match dimension with obs_dict
1115
+ mod = list(obs_dict.keys())[0]
1116
+ goal_dict = TensorUtils.unsqueeze_expand_at(goal_dict, size=obs_dict[mod].shape[1], dim=1)
1117
+
1118
+ forward_kwargs = dict(obs=obs_dict, goal=goal_dict)
1119
+ outputs = super(TransformerActorNetwork, self).forward(**forward_kwargs)
1120
+
1121
+ # apply tanh squashing to ensure actions are in [-1, 1]
1122
+ outputs["action"] = torch.tanh(outputs["action"])
1123
+
1124
+ return outputs["action"] # only action sequences
1125
+
1126
+ def _to_string(self):
1127
+ """Info to pretty print."""
1128
+ return "action_dim={}".format(self.ac_dim)
1129
+
1130
+
1131
+ class TransformerGMMActorNetwork(TransformerActorNetwork):
1132
+ """
1133
+ A Transformer GMM policy network that predicts sequences of action distributions from observation
1134
+ sequences (assumed to be frame stacked from previous observations).
1135
+ """
1136
+ def __init__(
1137
+ self,
1138
+ obs_shapes,
1139
+ ac_dim,
1140
+ transformer_embed_dim,
1141
+ transformer_num_layers,
1142
+ transformer_num_heads,
1143
+ transformer_context_length,
1144
+ transformer_emb_dropout=0.1,
1145
+ transformer_attn_dropout=0.1,
1146
+ transformer_block_output_dropout=0.1,
1147
+ transformer_sinusoidal_embedding=False,
1148
+ transformer_activation="gelu",
1149
+ transformer_nn_parameter_for_timesteps=False,
1150
+ num_modes=5,
1151
+ min_std=0.01,
1152
+ std_activation="softplus",
1153
+ low_noise_eval=True,
1154
+ use_tanh=False,
1155
+ goal_shapes=None,
1156
+ encoder_kwargs=None,
1157
+ ):
1158
+ """
1159
+ Args:
1160
+
1161
+ obs_shapes (OrderedDict): a dictionary that maps modality to
1162
+ expected shapes for observations.
1163
+
1164
+ ac_dim (int): dimension of action space.
1165
+
1166
+ transformer_embed_dim (int): dimension for embeddings used by transformer
1167
+
1168
+ transformer_num_layers (int): number of transformer blocks to stack
1169
+
1170
+ transformer_num_heads (int): number of attention heads for each
1171
+ transformer block - must divide @transformer_embed_dim evenly. Self-attention is
1172
+ computed over this many partitions of the embedding dimension separately.
1173
+
1174
+ transformer_context_length (int): expected length of input sequences
1175
+
1176
+ transformer_embedding_dropout (float): dropout probability for embedding inputs in transformer
1177
+
1178
+ transformer_attn_dropout (float): dropout probability for attention outputs for each transformer block
1179
+
1180
+ transformer_block_output_dropout (float): dropout probability for final outputs for each transformer block
1181
+
1182
+ num_modes (int): number of GMM modes
1183
+
1184
+ min_std (float): minimum std output from network
1185
+
1186
+ std_activation (None or str): type of activation to use for std deviation. Options are:
1187
+
1188
+ `'softplus'`: Softplus activation applied
1189
+
1190
+ `'exp'`: Exp applied; this corresponds to network output being interpreted as log_std instead of std
1191
+
1192
+ low_noise_eval (float): if True, model will sample from GMM with low std, so that
1193
+ one of the GMM modes will be sampled (approximately)
1194
+
1195
+ use_tanh (bool): if True, use a tanh-Gaussian distribution
1196
+
1197
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
1198
+ be nested dictionary containing relevant per-modality information for encoder networks.
1199
+ Should be of form:
1200
+
1201
+ obs_modality1: dict
1202
+ feature_dimension: int
1203
+ core_class: str
1204
+ core_kwargs: dict
1205
+ ...
1206
+ ...
1207
+ obs_randomizer_class: str
1208
+ obs_randomizer_kwargs: dict
1209
+ ...
1210
+ ...
1211
+ obs_modality2: dict
1212
+ ...
1213
+ """
1214
+
1215
+ # parameters specific to GMM actor
1216
+ self.num_modes = num_modes
1217
+ self.min_std = min_std
1218
+ self.low_noise_eval = low_noise_eval
1219
+ self.use_tanh = use_tanh
1220
+
1221
+ # Define activations to use
1222
+ self.activations = {
1223
+ "softplus": F.softplus,
1224
+ "exp": torch.exp,
1225
+ }
1226
+ assert std_activation in self.activations, \
1227
+ "std_activation must be one of: {}; instead got: {}".format(self.activations.keys(), std_activation)
1228
+ self.std_activation = std_activation
1229
+
1230
+ super(TransformerGMMActorNetwork, self).__init__(
1231
+ obs_shapes=obs_shapes,
1232
+ ac_dim=ac_dim,
1233
+ transformer_embed_dim=transformer_embed_dim,
1234
+ transformer_num_layers=transformer_num_layers,
1235
+ transformer_num_heads=transformer_num_heads,
1236
+ transformer_context_length=transformer_context_length,
1237
+ transformer_emb_dropout=transformer_emb_dropout,
1238
+ transformer_attn_dropout=transformer_attn_dropout,
1239
+ transformer_block_output_dropout=transformer_block_output_dropout,
1240
+ transformer_sinusoidal_embedding=transformer_sinusoidal_embedding,
1241
+ transformer_activation=transformer_activation,
1242
+ transformer_nn_parameter_for_timesteps=transformer_nn_parameter_for_timesteps,
1243
+ encoder_kwargs=encoder_kwargs,
1244
+ goal_shapes=goal_shapes,
1245
+ )
1246
+
1247
+ def _get_output_shapes(self):
1248
+ """
1249
+ Tells @MIMO_Transformer superclass about the output dictionary that should be generated
1250
+ at the last layer. Network outputs parameters of GMM distribution.
1251
+ """
1252
+ return OrderedDict(
1253
+ mean=(self.num_modes, self.ac_dim),
1254
+ scale=(self.num_modes, self.ac_dim),
1255
+ logits=(self.num_modes,),
1256
+ )
1257
+
1258
+ def forward_train(self, obs_dict, actions=None, goal_dict=None, low_noise_eval=None):
1259
+ """
1260
+ Return full GMM distribution, which is useful for computing
1261
+ quantities necessary at train-time, like log-likelihood, KL
1262
+ divergence, etc.
1263
+ Args:
1264
+ obs_dict (dict): batch of observations
1265
+ actions (torch.Tensor): batch of actions
1266
+ goal_dict (dict): if not None, batch of goal observations
1267
+ Returns:
1268
+ dists (Distribution): sequence of GMM distributions over the timesteps
1269
+ """
1270
+ if self._is_goal_conditioned:
1271
+ assert goal_dict is not None
1272
+ # repeat the goal observation in time to match dimension with obs_dict
1273
+ mod = list(obs_dict.keys())[0]
1274
+ goal_dict = TensorUtils.unsqueeze_expand_at(goal_dict, size=obs_dict[mod].shape[1], dim=1)
1275
+
1276
+ forward_kwargs = dict(obs=obs_dict, goal=goal_dict)
1277
+
1278
+ outputs = MIMO_Transformer.forward(self, **forward_kwargs)
1279
+
1280
+ means = outputs["mean"]
1281
+ scales = outputs["scale"]
1282
+ logits = outputs["logits"]
1283
+
1284
+ # apply tanh squashing to mean if not using tanh-GMM to ensure means are in [-1, 1]
1285
+ if not self.use_tanh:
1286
+ means = torch.tanh(means)
1287
+
1288
+ if low_noise_eval is None:
1289
+ low_noise_eval = self.low_noise_eval
1290
+ if low_noise_eval and (not self.training):
1291
+ # low-noise for all Gaussian dists
1292
+ scales = torch.ones_like(means) * 1e-4
1293
+ else:
1294
+ # post-process the scale accordingly
1295
+ scales = self.activations[self.std_activation](scales) + self.min_std
1296
+
1297
+ # mixture components - make sure that `batch_shape` for the distribution is equal
1298
+ # to (batch_size, timesteps, num_modes) since MixtureSameFamily expects this shape
1299
+ component_distribution = D.Normal(loc=means, scale=scales)
1300
+ component_distribution = D.Independent(component_distribution, 1) # shift action dim to event shape
1301
+
1302
+ # unnormalized logits to categorical distribution for mixing the modes
1303
+ mixture_distribution = D.Categorical(logits=logits)
1304
+
1305
+ dists = D.MixtureSameFamily(
1306
+ mixture_distribution=mixture_distribution,
1307
+ component_distribution=component_distribution,
1308
+ )
1309
+
1310
+ if self.use_tanh:
1311
+ # Wrap distribution with Tanh
1312
+ dists = TanhWrappedDistribution(base_dist=dists, scale=1.)
1313
+
1314
+ return dists
1315
+
1316
+ def forward(self, obs_dict, actions=None, goal_dict=None):
1317
+ """
1318
+ Samples actions from the policy distribution.
1319
+ Args:
1320
+ obs_dict (dict): batch of observations
1321
+ actions (torch.Tensor): batch of actions
1322
+ goal_dict (dict): if not None, batch of goal observations
1323
+ Returns:
1324
+ action (torch.Tensor): batch of actions from policy distribution
1325
+ """
1326
+ out = self.forward_train(obs_dict=obs_dict, actions=actions, goal_dict=goal_dict)
1327
+ return out.sample()
1328
+
1329
+ def _to_string(self):
1330
+ """Info to pretty print."""
1331
+ msg = "action_dim={}, std_activation={}, low_noise_eval={}, num_nodes={}, min_std={}".format(
1332
+ self.ac_dim, self.std_activation, self.low_noise_eval, self.num_modes, self.min_std)
1333
+ return msg
1334
+
1335
+
1336
+ class VAEActor(Module):
1337
+ """
1338
+ A VAE that models a distribution of actions conditioned on observations.
1339
+ The VAE prior and decoder are used at test-time as the policy.
1340
+ """
1341
+ def __init__(
1342
+ self,
1343
+ obs_shapes,
1344
+ ac_dim,
1345
+ encoder_layer_dims,
1346
+ decoder_layer_dims,
1347
+ latent_dim,
1348
+ device,
1349
+ decoder_is_conditioned=True,
1350
+ decoder_reconstruction_sum_across_elements=False,
1351
+ latent_clip=None,
1352
+ prior_learn=False,
1353
+ prior_is_conditioned=False,
1354
+ prior_layer_dims=(),
1355
+ prior_use_gmm=False,
1356
+ prior_gmm_num_modes=10,
1357
+ prior_gmm_learn_weights=False,
1358
+ prior_use_categorical=False,
1359
+ prior_categorical_dim=10,
1360
+ prior_categorical_gumbel_softmax_hard=False,
1361
+ goal_shapes=None,
1362
+ encoder_kwargs=None,
1363
+ ):
1364
+ """
1365
+ Args:
1366
+ obs_shapes (OrderedDict): a dictionary that maps modality to
1367
+ expected shapes for observations.
1368
+
1369
+ ac_dim (int): dimension of action space.
1370
+
1371
+ goal_shapes (OrderedDict): a dictionary that maps modality to
1372
+ expected shapes for goal observations.
1373
+
1374
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
1375
+ be nested dictionary containing relevant per-modality information for encoder networks.
1376
+ Should be of form:
1377
+
1378
+ obs_modality1: dict
1379
+ feature_dimension: int
1380
+ core_class: str
1381
+ core_kwargs: dict
1382
+ ...
1383
+ ...
1384
+ obs_randomizer_class: str
1385
+ obs_randomizer_kwargs: dict
1386
+ ...
1387
+ ...
1388
+ obs_modality2: dict
1389
+ ...
1390
+ """
1391
+ super(VAEActor, self).__init__()
1392
+
1393
+ self.obs_shapes = obs_shapes
1394
+ self.ac_dim = ac_dim
1395
+ action_shapes = OrderedDict(action=(self.ac_dim,))
1396
+
1397
+ # ensure VAE decoder will squash actions into [-1, 1]
1398
+ output_squash = ['action']
1399
+ output_scales = OrderedDict(action=1.)
1400
+
1401
+ self._vae = VAE(
1402
+ input_shapes=action_shapes,
1403
+ output_shapes=action_shapes,
1404
+ encoder_layer_dims=encoder_layer_dims,
1405
+ decoder_layer_dims=decoder_layer_dims,
1406
+ latent_dim=latent_dim,
1407
+ device=device,
1408
+ condition_shapes=self.obs_shapes,
1409
+ decoder_is_conditioned=decoder_is_conditioned,
1410
+ decoder_reconstruction_sum_across_elements=decoder_reconstruction_sum_across_elements,
1411
+ latent_clip=latent_clip,
1412
+ output_squash=output_squash,
1413
+ output_scales=output_scales,
1414
+ prior_learn=prior_learn,
1415
+ prior_is_conditioned=prior_is_conditioned,
1416
+ prior_layer_dims=prior_layer_dims,
1417
+ prior_use_gmm=prior_use_gmm,
1418
+ prior_gmm_num_modes=prior_gmm_num_modes,
1419
+ prior_gmm_learn_weights=prior_gmm_learn_weights,
1420
+ prior_use_categorical=prior_use_categorical,
1421
+ prior_categorical_dim=prior_categorical_dim,
1422
+ prior_categorical_gumbel_softmax_hard=prior_categorical_gumbel_softmax_hard,
1423
+ goal_shapes=goal_shapes,
1424
+ encoder_kwargs=encoder_kwargs,
1425
+ )
1426
+
1427
+ def encode(self, actions, obs_dict, goal_dict=None):
1428
+ """
1429
+ Args:
1430
+ actions (torch.Tensor): a batch of actions
1431
+
1432
+ obs_dict (dict): a dictionary that maps modalities to torch.Tensor
1433
+ batches. These should correspond to the observation modalities
1434
+ used for conditioning in either the decoder or the prior (or both).
1435
+
1436
+ goal_dict (dict): a dictionary that maps modalities to torch.Tensor
1437
+ batches. These should correspond to goal modalities.
1438
+
1439
+ Returns:
1440
+ posterior params (dict): dictionary with the following keys:
1441
+
1442
+ mean (torch.Tensor): posterior encoder means
1443
+
1444
+ logvar (torch.Tensor): posterior encoder logvars
1445
+ """
1446
+ inputs = OrderedDict(action=actions)
1447
+ return self._vae.encode(inputs=inputs, conditions=obs_dict, goals=goal_dict)
1448
+
1449
+ def decode(self, obs_dict=None, goal_dict=None, z=None, n=None):
1450
+ """
1451
+ Thin wrapper around @VaeNets.VAE implementation.
1452
+
1453
+ Args:
1454
+ obs_dict (dict): a dictionary that maps modalities to torch.Tensor
1455
+ batches. Only needs to be provided if @decoder_is_conditioned
1456
+ or @z is None (since the prior will require it to generate z).
1457
+
1458
+ goal_dict (dict): a dictionary that maps modalities to torch.Tensor
1459
+ batches. These should correspond to goal modalities.
1460
+
1461
+ z (torch.Tensor): if provided, these latents are used to generate
1462
+ reconstructions from the VAE, and the prior is not sampled.
1463
+
1464
+ n (int): this argument is used to specify the number of samples to
1465
+ generate from the prior. Only required if @z is None - i.e.
1466
+ sampling takes place
1467
+
1468
+ Returns:
1469
+ recons (dict): dictionary of reconstructed inputs (this will be a dictionary
1470
+ with a single "action" key)
1471
+ """
1472
+ return self._vae.decode(conditions=obs_dict, goals=goal_dict, z=z, n=n)
1473
+
1474
+ def sample_prior(self, obs_dict=None, goal_dict=None, n=None):
1475
+ """
1476
+ Thin wrapper around @VaeNets.VAE implementation.
1477
+
1478
+ Args:
1479
+ n (int): this argument is used to specify the number
1480
+ of samples to generate from the prior.
1481
+
1482
+ obs_dict (dict): a dictionary that maps modalities to torch.Tensor
1483
+ batches. Only needs to be provided if @prior_is_conditioned.
1484
+
1485
+ goal_dict (dict): a dictionary that maps modalities to torch.Tensor
1486
+ batches. These should correspond to goal modalities.
1487
+
1488
+ Returns:
1489
+ z (torch.Tensor): latents sampled from the prior
1490
+ """
1491
+ return self._vae.sample_prior(n=n, conditions=obs_dict, goals=goal_dict)
1492
+
1493
+ def set_gumbel_temperature(self, temperature):
1494
+ """
1495
+ Used by external algorithms to schedule Gumbel-Softmax temperature,
1496
+ which is used during reparametrization at train-time. Should only be
1497
+ used if @prior_use_categorical is True.
1498
+ """
1499
+ self._vae.set_gumbel_temperature(temperature)
1500
+
1501
+ def get_gumbel_temperature(self):
1502
+ """
1503
+ Return current Gumbel-Softmax temperature. Should only be used if
1504
+ @prior_use_categorical is True.
1505
+ """
1506
+ return self._vae.get_gumbel_temperature()
1507
+
1508
+ def output_shape(self, input_shape=None):
1509
+ """
1510
+ This implementation is required by the Module superclass, but is unused since we
1511
+ never chain this module to other ones.
1512
+ """
1513
+ return [self.ac_dim]
1514
+
1515
+ def forward_train(self, actions, obs_dict, goal_dict=None, freeze_encoder=False):
1516
+ """
1517
+ A full pass through the VAE network used during training to construct KL
1518
+ and reconstruction losses. See @VAE class for more info.
1519
+
1520
+ Args:
1521
+ actions (torch.Tensor): a batch of actions
1522
+
1523
+ obs_dict (dict): a dictionary that maps modalities to torch.Tensor
1524
+ batches. These should correspond to the observation modalities
1525
+ used for conditioning in either the decoder or the prior (or both).
1526
+
1527
+ goal_dict (dict): a dictionary that maps modalities to torch.Tensor
1528
+ batches. These should correspond to goal modalities.
1529
+
1530
+ Returns:
1531
+ vae_outputs (dict): a dictionary that contains the following outputs.
1532
+
1533
+ encoder_params (dict): parameters for the posterior distribution
1534
+ from the encoder forward pass
1535
+
1536
+ encoder_z (torch.Tensor): latents sampled from the encoder posterior
1537
+
1538
+ decoder_outputs (dict): action reconstructions from the decoder
1539
+
1540
+ kl_loss (torch.Tensor): KL loss over the batch of data
1541
+
1542
+ reconstruction_loss (torch.Tensor): reconstruction loss over the batch of data
1543
+ """
1544
+ action_inputs = OrderedDict(action=actions)
1545
+ return self._vae.forward(
1546
+ inputs=action_inputs,
1547
+ outputs=action_inputs,
1548
+ conditions=obs_dict,
1549
+ goals=goal_dict,
1550
+ freeze_encoder=freeze_encoder)
1551
+
1552
+ def forward(self, obs_dict, goal_dict=None, z=None):
1553
+ """
1554
+ Samples actions from the policy distribution.
1555
+
1556
+ Args:
1557
+ obs_dict (dict): batch of observations
1558
+ goal_dict (dict): if not None, batch of goal observations
1559
+ z (torch.Tensor): if not None, use the provided batch of latents instead
1560
+ of sampling from the prior
1561
+
1562
+ Returns:
1563
+ action (torch.Tensor): batch of actions from policy distribution
1564
+ """
1565
+ n = None
1566
+ if z is None:
1567
+ # prior will be sampled - so we must provide number of samples explicitly
1568
+ mod = list(obs_dict.keys())[0]
1569
+ n = obs_dict[mod].shape[0]
1570
+ return self.decode(obs_dict=obs_dict, goal_dict=goal_dict, z=z, n=n)["action"]
aloha-devel/robomimic/models/transformers.py ADDED
@@ -0,0 +1,426 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Implementation of transformers, mostly based on Andrej's minGPT model.
3
+ See https://github.com/karpathy/minGPT/blob/master/mingpt/model.py
4
+ for more details.
5
+ """
6
+
7
+ import math
8
+ import numpy as np
9
+
10
+ import torch
11
+ import torch.nn as nn
12
+ import torch.nn.functional as F
13
+
14
+ from robomimic.models.base_nets import Module
15
+ import robomimic.utils.tensor_utils as TensorUtils
16
+ import robomimic.utils.torch_utils as TorchUtils
17
+
18
+ class GEGLU(nn.Module):
19
+ """
20
+ References:
21
+ Shazeer et al., "GLU Variants Improve Transformer," 2020.
22
+ https://arxiv.org/abs/2002.05202
23
+ Implementation: https://github.com/pfnet-research/deep-table/blob/237c8be8a405349ce6ab78075234c60d9bfe60b7/deep_table/nn/layers/activation.py
24
+ """
25
+
26
+ def geglu(self, x):
27
+ assert x.shape[-1] % 2 == 0
28
+ a, b = x.chunk(2, dim=-1)
29
+ return a * F.gelu(b)
30
+
31
+ def forward(self, x):
32
+ return self.geglu(x)
33
+
34
+
35
+ class PositionalEncoding(nn.Module):
36
+ """
37
+ Taken from https://pytorch.org/tutorials/beginner/transformer_tutorial.html.
38
+ """
39
+
40
+ def __init__(self, embed_dim):
41
+ """
42
+ Standard sinusoidal positional encoding scheme in transformers.
43
+
44
+ Positional encoding of the k'th position in the sequence is given by:
45
+ p(k, 2i) = sin(k/n^(i/d))
46
+ p(k, 2i+1) = sin(k/n^(i/d))
47
+
48
+ n: set to 10K in original Transformer paper
49
+ d: the embedding dimension
50
+ i: positions along the projected embedding space (ranges from 0 to d/2)
51
+
52
+ Args:
53
+ embed_dim: The number of dimensions to project the timesteps into.
54
+ """
55
+ super().__init__()
56
+ self.embed_dim = embed_dim
57
+
58
+ def forward(self, x):
59
+ """
60
+ Input timestep of shape BxT
61
+ """
62
+ position = x
63
+
64
+ # computing 1/n^(i/d) in log space and then exponentiating and fixing the shape
65
+ div_term = (
66
+ torch.exp(
67
+ torch.arange(0, self.embed_dim, 2, device=x.device)
68
+ * (-math.log(10000.0) / self.embed_dim)
69
+ )
70
+ .unsqueeze(0)
71
+ .unsqueeze(0)
72
+ .repeat(x.shape[0], x.shape[1], 1)
73
+ )
74
+ pe = torch.zeros((x.shape[0], x.shape[1], self.embed_dim), device=x.device)
75
+ pe[:, :, 0::2] = torch.sin(position.unsqueeze(-1) * div_term)
76
+ pe[:, :, 1::2] = torch.cos(position.unsqueeze(-1) * div_term)
77
+ return pe.detach()
78
+
79
+
80
+ class CausalSelfAttention(Module):
81
+ def __init__(
82
+ self,
83
+ embed_dim,
84
+ num_heads,
85
+ context_length,
86
+ attn_dropout=0.1,
87
+ output_dropout=0.1,
88
+ ):
89
+ """
90
+ Multi-head masked self-attention layer + projection (MLP layer).
91
+
92
+ For normal self-attention (@num_heads = 1), every single input in the sequence is
93
+ mapped to a key, query, and value embedding of size @embed_dim. For each input,
94
+ its query vector is compared (using dot-product) with all other key vectors in the
95
+ sequence, and softmax normalized to compute an attention over all members of the
96
+ sequence. This is used to take a linear combination of corresponding value embeddings.
97
+
98
+ The @num_heads argument is for multi-head attention, where the self-attention operation above
99
+ is performed in parallel over equal size partitions of the @embed_dim, allowing for different
100
+ portions of the embedding dimension to model different kinds of attention. The attention
101
+ output for each head is concatenated together.
102
+
103
+ Finally, we use a causal mask here to ensure that each output only depends on inputs that come
104
+ before it.
105
+
106
+ Args:
107
+ embed_dim (int): dimension of embeddings to use for keys, queries, and values
108
+ used in self-attention
109
+
110
+ num_heads (int): number of attention heads - must divide @embed_dim evenly. Self-attention is
111
+ computed over this many partitions of the embedding dimension separately.
112
+
113
+ context_length (int): expected length of input sequences
114
+
115
+ attn_dropout (float): dropout probability for attention outputs
116
+
117
+ output_dropout (float): dropout probability for final outputs
118
+ """
119
+ super(CausalSelfAttention, self).__init__()
120
+
121
+ assert (
122
+ embed_dim % num_heads == 0
123
+ ), "num_heads: {} does not divide embed_dim: {} exactly".format(num_heads, embed_dim)
124
+
125
+ self.embed_dim = embed_dim
126
+ self.num_heads = num_heads
127
+ self.context_length = context_length
128
+ self.attn_dropout = attn_dropout
129
+ self.output_dropout = output_dropout
130
+ self.nets = nn.ModuleDict()
131
+
132
+ # projection layers for key, query, value, across all attention heads
133
+ self.nets["qkv"] = nn.Linear(self.embed_dim, 3 * self.embed_dim, bias=False)
134
+
135
+ # dropout layers
136
+ self.nets["attn_dropout"] = nn.Dropout(self.attn_dropout)
137
+ self.nets["output_dropout"] = nn.Dropout(self.output_dropout)
138
+
139
+ # output layer
140
+ self.nets["output"] = nn.Linear(self.embed_dim, self.embed_dim)
141
+
142
+ # causal mask (ensures attention is only over previous inputs) - just a lower triangular matrix of 1s
143
+ mask = torch.tril(torch.ones(context_length, context_length)).view(
144
+ 1, 1, context_length, context_length
145
+ )
146
+ self.register_buffer("mask", mask)
147
+
148
+ def forward(self, x):
149
+ """
150
+ Forward pass through Self-Attention block.
151
+ Input should be shape (B, T, D) where B is batch size, T is seq length (@self.context_length), and
152
+ D is input dimension (@self.embed_dim).
153
+ """
154
+
155
+ # enforce shape consistency
156
+ assert len(x.shape) == 3
157
+ B, T, D = x.shape
158
+ assert (
159
+ T <= self.context_length
160
+ ), "self-attention module can only handle sequences up to {} in length but got length {}".format(
161
+ self.context_length, T
162
+ )
163
+ assert D == self.embed_dim
164
+ NH = self.num_heads # number of attention heads
165
+ DH = D // NH # embed dimension for each attention head
166
+
167
+ # compute key, query, and value vectors for each member of sequence, and split across attention heads
168
+ qkv = self.nets["qkv"](x)
169
+ q, k, v = torch.chunk(qkv, 3, dim=-1)
170
+ k = k.view(B, T, NH, DH).transpose(1, 2) # [B, NH, T, DH]
171
+ q = q.view(B, T, NH, DH).transpose(1, 2) # [B, NH, T, DH]
172
+ v = v.view(B, T, NH, DH).transpose(1, 2) # [B, NH, T, DH]
173
+
174
+ # causal self-attention mechanism
175
+
176
+ # batched matrix multiplication between queries and keys to get all pair-wise dot-products.
177
+ # We broadcast across batch and attention heads and get pair-wise dot-products between all pairs of timesteps
178
+ # [B, NH, T, DH] x [B, NH, DH, T] -> [B, NH, T, T]
179
+ att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))
180
+
181
+ # use mask to replace entries in dot products with negative inf to ensure they don't contribute to softmax,
182
+ # then take softmax over last dimension to end up with attention score for each member of sequence.
183
+ # Note the use of [:T, :T] - this makes it so we can handle sequences less than @self.context_length in length.
184
+ att = att.masked_fill(self.mask[..., :T, :T] == 0, float("-inf"))
185
+ att = F.softmax(
186
+ att, dim=-1
187
+ ) # shape [B, NH, T, T], last dimension has score over all T for each sequence member
188
+
189
+ # dropout on attention
190
+ att = self.nets["attn_dropout"](att)
191
+
192
+ # take weighted sum of value vectors over whole sequence according to attention, with batched matrix multiplication
193
+ # [B, NH, T, T] x [B, NH, T, DH] -> [B, NH, T, DH]
194
+ y = att @ v
195
+ # reshape [B, NH, T, DH] -> [B, T, NH, DH] -> [B, T, NH * DH] = [B, T, D]
196
+ y = y.transpose(1, 2).contiguous().view(B, T, D)
197
+
198
+ # pass through output layer + dropout
199
+ y = self.nets["output"](y)
200
+ y = self.nets["output_dropout"](y)
201
+ return y
202
+
203
+ def output_shape(self, input_shape=None):
204
+ """
205
+ Function to compute output shape from inputs to this module.
206
+
207
+ Args:
208
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
209
+ Some modules may not need this argument, if their output does not depend
210
+ on the size of the input, or if they assume fixed size input.
211
+
212
+ Returns:
213
+ out_shape ([int]): list of integers corresponding to output shape
214
+ """
215
+
216
+ # this module doesn't modify the size of the input, it goes from (B, T, D) -> (B, T, D)
217
+ return list(input_shape)
218
+
219
+
220
+ class SelfAttentionBlock(Module):
221
+ """
222
+ A single Transformer Block, that can be chained together repeatedly.
223
+ It consists of a @CausalSelfAttention module and a small MLP, along with
224
+ layer normalization and residual connections on each input.
225
+ """
226
+
227
+ def __init__(
228
+ self,
229
+ embed_dim,
230
+ num_heads,
231
+ context_length,
232
+ attn_dropout=0.1,
233
+ output_dropout=0.1,
234
+ activation=nn.GELU(),
235
+ ):
236
+ """
237
+ Args:
238
+ embed_dim (int): dimension of embeddings to use for keys, queries, and values
239
+ used in self-attention
240
+
241
+ num_heads (int): number of attention heads - must divide @embed_dim evenly. Self-attention is
242
+ computed over this many partitions of the embedding dimension separately.
243
+
244
+ context_length (int): expected length of input sequences
245
+
246
+ attn_dropout (float): dropout probability for attention outputs
247
+
248
+ output_dropout (float): dropout probability for final outputs
249
+
250
+ activation (str): string denoting the activation function to use in each transformer block
251
+ """
252
+ super(SelfAttentionBlock, self).__init__()
253
+
254
+ self.embed_dim = embed_dim
255
+ self.num_heads = num_heads
256
+ self.context_length = context_length
257
+ self.attn_dropout = attn_dropout
258
+ self.output_dropout = output_dropout
259
+ self.nets = nn.ModuleDict()
260
+
261
+ # self-attention block
262
+ self.nets["attention"] = CausalSelfAttention(
263
+ embed_dim=embed_dim,
264
+ num_heads=num_heads,
265
+ context_length=context_length,
266
+ attn_dropout=attn_dropout,
267
+ output_dropout=output_dropout,
268
+ )
269
+
270
+ if type(activation) == GEGLU:
271
+ mult = 2
272
+ else:
273
+ mult = 1
274
+
275
+ # small 2-layer MLP
276
+ self.nets["mlp"] = nn.Sequential(
277
+ nn.Linear(embed_dim, 4 * embed_dim * mult),
278
+ activation,
279
+ nn.Linear(4 * embed_dim, embed_dim),
280
+ nn.Dropout(output_dropout)
281
+ )
282
+
283
+ # layer normalization for inputs to self-attention module and MLP
284
+ self.nets["ln1"] = nn.LayerNorm(embed_dim)
285
+ self.nets["ln2"] = nn.LayerNorm(embed_dim)
286
+
287
+ def forward(self, x):
288
+ """
289
+ Forward pass - chain self-attention + MLP blocks, with residual connections and layer norms.
290
+ """
291
+ x = x + self.nets["attention"](self.nets["ln1"](x))
292
+ x = x + self.nets["mlp"](self.nets["ln2"](x))
293
+ return x
294
+
295
+ def output_shape(self, input_shape=None):
296
+ """
297
+ Function to compute output shape from inputs to this module.
298
+
299
+ Args:
300
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
301
+ Some modules may not need this argument, if their output does not depend
302
+ on the size of the input, or if they assume fixed size input.
303
+
304
+ Returns:
305
+ out_shape ([int]): list of integers corresponding to output shape
306
+ """
307
+
308
+ # this module doesn't modify the size of the input, it goes from (B, T, D) -> (B, T, D)
309
+ return list(input_shape)
310
+
311
+
312
+ class GPT_Backbone(Module):
313
+ """the GPT model, with a context size of block_size"""
314
+
315
+ def __init__(
316
+ self,
317
+ embed_dim,
318
+ context_length,
319
+ attn_dropout=0.1,
320
+ block_output_dropout=0.1,
321
+ num_layers=6,
322
+ num_heads=8,
323
+ activation="gelu",
324
+ ):
325
+ """
326
+ Args:
327
+ embed_dim (int): dimension of embeddings to use for keys, queries, and values
328
+ used in self-attention
329
+
330
+ context_length (int): expected length of input sequences
331
+
332
+ attn_dropout (float): dropout probability for attention outputs for each transformer block
333
+
334
+ block_output_dropout (float): dropout probability for final outputs for each transformer block
335
+
336
+ num_layers (int): number of transformer blocks to stack
337
+
338
+ num_heads (int): number of attention heads - must divide @embed_dim evenly. Self-attention is
339
+ computed over this many partitions of the embedding dimension separately.
340
+
341
+ activation (str): string denoting the activation function to use in each transformer block
342
+
343
+ """
344
+ super(GPT_Backbone, self).__init__()
345
+
346
+ self.embed_dim = embed_dim
347
+ self.num_layers = num_layers
348
+ self.num_heads = num_heads
349
+ self.context_length = context_length
350
+ self.attn_dropout = attn_dropout
351
+ self.block_output_dropout = block_output_dropout
352
+
353
+ if activation == "gelu":
354
+ self.activation = nn.GELU()
355
+ elif activation == "geglu":
356
+ self.activation = GEGLU()
357
+
358
+ # create networks
359
+ self._create_networks()
360
+
361
+ # initialize weights
362
+ self.apply(self._init_weights)
363
+
364
+ print(
365
+ "Created {} model with number of parameters: {}".format(
366
+ self.__class__.__name__, sum(p.numel() for p in self.parameters())
367
+ )
368
+ )
369
+
370
+ def _create_networks(self):
371
+ """
372
+ Helper function to create networks.
373
+ """
374
+ self.nets = nn.ModuleDict()
375
+
376
+ # transformer - cascaded transformer blocks
377
+ self.nets["transformer"] = nn.Sequential(
378
+ *[
379
+ SelfAttentionBlock(
380
+ embed_dim=self.embed_dim,
381
+ num_heads=self.num_heads,
382
+ context_length=self.context_length,
383
+ attn_dropout=self.attn_dropout,
384
+ output_dropout=self.block_output_dropout,
385
+ activation=self.activation,
386
+ )
387
+ for _ in range(self.num_layers)
388
+ ]
389
+ )
390
+
391
+ # decoder head
392
+ self.nets["output_ln"] = nn.LayerNorm(self.embed_dim)
393
+
394
+ def _init_weights(self, module):
395
+ """
396
+ Weight initializer.
397
+ """
398
+ if isinstance(module, (nn.Linear, nn.Embedding)):
399
+ module.weight.data.normal_(mean=0.0, std=0.02)
400
+ if isinstance(module, nn.Linear) and module.bias is not None:
401
+ module.bias.data.zero_()
402
+ elif isinstance(module, nn.LayerNorm):
403
+ module.bias.data.zero_()
404
+ module.weight.data.fill_(1.0)
405
+
406
+ def output_shape(self, input_shape=None):
407
+ """
408
+ Function to compute output shape from inputs to this module.
409
+
410
+ Args:
411
+ input_shape (iterable of int): shape of input. Does not include batch dimension.
412
+ Some modules may not need this argument, if their output does not depend
413
+ on the size of the input, or if they assume fixed size input.
414
+
415
+ Returns:
416
+ out_shape ([int]): list of integers corresponding to output shape
417
+ """
418
+
419
+ # this module takes inputs (B, T, @self.input_dim) and produces outputs (B, T, @self.output_dim)
420
+ return input_shape[:-1] + [self.output_dim]
421
+
422
+ def forward(self, inputs):
423
+ assert inputs.shape[1:] == (self.context_length, self.embed_dim), inputs.shape
424
+ x = self.nets["transformer"](inputs)
425
+ transformer_output = self.nets["output_ln"](x)
426
+ return transformer_output
aloha-devel/robomimic/models/vae_nets.py ADDED
@@ -0,0 +1,1386 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Contains an implementation of Variational Autoencoder (VAE) and other
3
+ variants, including other priors, and RNN-VAEs.
4
+ """
5
+ import textwrap
6
+ import numpy as np
7
+ from copy import deepcopy
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.loss_utils as LossUtils
16
+ import robomimic.utils.tensor_utils as TensorUtils
17
+ import robomimic.utils.torch_utils as TorchUtils
18
+ from robomimic.models.base_nets import Module
19
+ from robomimic.models.obs_nets import MIMO_MLP
20
+
21
+
22
+ def vae_args_from_config(vae_config):
23
+ """
24
+ Generate a set of VAE args that are read from the VAE-specific part
25
+ of a config (for example see `config.algo.vae` in BCConfig).
26
+ """
27
+ vae_args = dict(
28
+ encoder_layer_dims=vae_config.encoder_layer_dims,
29
+ decoder_layer_dims=vae_config.decoder_layer_dims,
30
+ latent_dim=vae_config.latent_dim,
31
+ decoder_is_conditioned=vae_config.decoder.is_conditioned,
32
+ decoder_reconstruction_sum_across_elements=vae_config.decoder.reconstruction_sum_across_elements,
33
+ latent_clip=vae_config.latent_clip,
34
+ prior_learn=vae_config.prior.learn,
35
+ prior_is_conditioned=vae_config.prior.is_conditioned,
36
+ prior_layer_dims=vae_config.prior_layer_dims,
37
+ prior_use_gmm=vae_config.prior.use_gmm,
38
+ prior_gmm_num_modes=vae_config.prior.gmm_num_modes,
39
+ prior_gmm_learn_weights=vae_config.prior.gmm_learn_weights,
40
+ prior_use_categorical=vae_config.prior.use_categorical,
41
+ prior_categorical_dim=vae_config.prior.categorical_dim,
42
+ prior_categorical_gumbel_softmax_hard=vae_config.prior.categorical_gumbel_softmax_hard,
43
+ )
44
+ return vae_args
45
+
46
+
47
+ class Prior(Module):
48
+ """
49
+ Base class for VAE priors. It's basically the same as a @MIMO_MLP network (it
50
+ instantiates one) but it supports additional methods such as KL loss computation
51
+ and sampling, and also may learn prior parameters as observation-independent
52
+ torch Parameters instead of observation-dependent mappings.
53
+ """
54
+ def __init__(
55
+ self,
56
+ param_shapes,
57
+ param_obs_dependent,
58
+ obs_shapes=None,
59
+ mlp_layer_dims=(),
60
+ goal_shapes=None,
61
+ encoder_kwargs=None,
62
+ ):
63
+ """
64
+ Args:
65
+ param_shapes (OrderedDict): a dictionary that maps modality to
66
+ expected shapes for parameters that determine the prior
67
+ distribution.
68
+
69
+ param_obs_dependent (OrderedDict): a dictionary with boolean
70
+ values consistent with @param_shapes which determines whether
71
+ to learn parameters as part of the (obs-dependent) network or
72
+ directly as learnable parameters.
73
+
74
+ obs_shapes (OrderedDict): a dictionary that maps modality to
75
+ expected shapes for observations.
76
+
77
+ mlp_layer_dims ([int]): sequence of integers for the MLP hidden layer sizes
78
+
79
+ goal_shapes (OrderedDict): a dictionary that maps modality to
80
+ expected shapes for goal observations.
81
+
82
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
83
+ be nested dictionary containing relevant per-modality information for encoder networks.
84
+ Should be of form:
85
+
86
+ obs_modality1: dict
87
+ feature_dimension: int
88
+ core_class: str
89
+ core_kwargs: dict
90
+ ...
91
+ ...
92
+ obs_randomizer_class: str
93
+ obs_randomizer_kwargs: dict
94
+ ...
95
+ ...
96
+ obs_modality2: dict
97
+ ...
98
+ """
99
+ super(Prior, self).__init__()
100
+
101
+ assert isinstance(param_shapes, OrderedDict) and isinstance(param_obs_dependent, OrderedDict)
102
+ assert set(param_shapes.keys()) == set(param_obs_dependent.keys())
103
+ self.param_shapes = param_shapes
104
+ self.param_obs_dependent = param_obs_dependent
105
+
106
+ net_kwargs = dict(
107
+ obs_shapes=obs_shapes,
108
+ mlp_layer_dims=mlp_layer_dims,
109
+ goal_shapes=goal_shapes,
110
+ encoder_kwargs=encoder_kwargs,
111
+ )
112
+ self._create_layers(net_kwargs)
113
+
114
+ def _create_layers(self, net_kwargs):
115
+ """
116
+ Create networks and parameters needed by the prior.
117
+ """
118
+ self.prior_params = nn.ParameterDict()
119
+
120
+ self._is_obs_dependent = False
121
+ mlp_output_shapes = OrderedDict()
122
+ for pp in self.param_shapes:
123
+ if self.param_obs_dependent[pp]:
124
+ # prior parameters will be a function of observations using a network
125
+ mlp_output_shapes[pp] = self.param_shapes[pp]
126
+ else:
127
+ # learnable prior parameters independent of observation
128
+ param_init = torch.randn(*self.param_shapes[pp]) / np.sqrt(np.prod(self.param_shapes[pp]))
129
+ self.prior_params[pp] = torch.nn.Parameter(param_init)
130
+
131
+ # only make networks if we have obs-dependent prior parameters
132
+ self.prior_module = None
133
+ if len(mlp_output_shapes) > 0:
134
+ # create @MIMO_MLP that takes obs and goal dicts and returns prior params
135
+ self._is_obs_dependent = True
136
+ obs_shapes = net_kwargs["obs_shapes"]
137
+ goal_shapes = net_kwargs["goal_shapes"]
138
+ obs_group_shapes = OrderedDict()
139
+ assert isinstance(obs_shapes, OrderedDict)
140
+ obs_group_shapes["obs"] = OrderedDict(obs_shapes)
141
+ if goal_shapes is not None and len(goal_shapes) > 0:
142
+ assert isinstance(goal_shapes, OrderedDict)
143
+ obs_group_shapes["goal"] = OrderedDict(goal_shapes)
144
+ self.prior_module = MIMO_MLP(
145
+ input_obs_group_shapes=obs_group_shapes,
146
+ output_shapes=mlp_output_shapes,
147
+ layer_dims=net_kwargs["mlp_layer_dims"],
148
+ encoder_kwargs=net_kwargs["encoder_kwargs"],
149
+ )
150
+
151
+ def sample(self, n, obs_dict=None, goal_dict=None):
152
+ """
153
+ Returns a batch of samples from the prior distribution.
154
+
155
+ Args:
156
+ n (int): this argument is used to specify the number
157
+ of samples to generate from the prior.
158
+
159
+ obs_dict (dict): inputs according to @obs_shapes. Only needs to be provided
160
+ if any prior parameters are obs-dependent. Leading dimension should
161
+ be consistent with @n, the number of samples to generate.
162
+
163
+ goal_dict (dict): inputs according to @goal_shapes (only if using goal observations)
164
+
165
+ Returns:
166
+ z (torch.Tensor): batch of sampled latent vectors.
167
+ """
168
+ raise NotImplementedError
169
+
170
+ def kl_loss(self, posterior_params, z=None, obs_dict=None, goal_dict=None):
171
+ """
172
+ Computes sample-based KL divergence loss between the Gaussian distribution
173
+ given by @mu, @logvar and the prior distribution.
174
+
175
+ Args:
176
+ posterior_params (dict): dictionary with keys "mu" and "logvar" corresponding
177
+ to torch.Tensor batch of means and log-variances of posterior Gaussian
178
+ distribution.
179
+
180
+ z (torch.Tensor): samples from the Gaussian distribution parametrized by
181
+ @mu and @logvar. May not be needed depending on the prior.
182
+
183
+ obs_dict (dict): inputs according to @obs_shapes. Only needs to be provided
184
+ if any prior parameters are obs-dependent.
185
+
186
+ goal_dict (dict): inputs according to @goal_shapes (only if using goal observations)
187
+
188
+ Returns:
189
+ kl_loss (torch.Tensor): KL divergence loss
190
+ """
191
+ raise NotImplementedError
192
+
193
+ def output_shape(self, input_shape=None):
194
+ """
195
+ Returns output shape for this module, which is a dictionary instead
196
+ of a list since outputs are dictionaries.
197
+ """
198
+ if self.prior_module is not None:
199
+ return self.prior_module.output_shape(input_shape)
200
+ return { k : list(self.param_shapes[k]) for k in self.param_shapes }
201
+
202
+ def forward(self, batch_size, obs_dict=None, goal_dict=None):
203
+ """
204
+ Computes prior parameters.
205
+
206
+ Args:
207
+ batch_size (int): batch size - this is needed for parameters that are
208
+ not obs-dependent, to make sure the leading dimension is correct
209
+ for downstream sampling and loss computation purposes
210
+
211
+ obs_dict (dict): inputs according to @obs_shapes. Only needs to be provided
212
+ if any prior parameters are obs-dependent.
213
+
214
+ goal_dict (dict): inputs according to @goal_shapes (only if using goal observations)
215
+
216
+ Returns:
217
+ prior_params (dict): dictionary containing prior parameters
218
+ """
219
+ prior_params = dict()
220
+ if self._is_obs_dependent:
221
+ # forward through network for obs-dependent params
222
+ prior_params = self.prior_module.forward(obs=obs_dict, goal=goal_dict)
223
+
224
+ # return params that do not depend on obs as well
225
+ for pp in self.param_shapes:
226
+ if not self.param_obs_dependent[pp]:
227
+ # ensure leading dimension will be consistent with other params
228
+ prior_params[pp] = TensorUtils.expand_at(self.prior_params[pp], size=batch_size, dim=0)
229
+
230
+ # ensure leading dimensions are all consistent
231
+ TensorUtils.assert_size_at_dim(prior_params, size=batch_size, dim=0,
232
+ msg="prior params dim 0 mismatch in forward")
233
+
234
+ return prior_params
235
+
236
+
237
+ class GaussianPrior(Prior):
238
+ """
239
+ A class that holds functionality for learning both unimodal Gaussian priors and
240
+ multimodal Gaussian Mixture Model priors for use in VAEs.
241
+ """
242
+ def __init__(
243
+ self,
244
+ latent_dim,
245
+ device,
246
+ latent_clip=None,
247
+ learnable=False,
248
+ use_gmm=False,
249
+ gmm_num_modes=10,
250
+ gmm_learn_weights=False,
251
+ obs_shapes=None,
252
+ mlp_layer_dims=(),
253
+ goal_shapes=None,
254
+ encoder_kwargs=None,
255
+ ):
256
+ """
257
+ Args:
258
+ latent_dim (int): size of latent dimension for the prior
259
+
260
+ device (torch.Device): where the module should live (i.e. cpu, gpu)
261
+
262
+ latent_clip (float): if provided, clip all latents sampled at
263
+ test-time in each dimension to (-@latent_clip, @latent_clip)
264
+
265
+ learnable (bool): if True, learn the parameters of the prior (as opposed
266
+ to a default N(0, 1) prior)
267
+
268
+ use_gmm (bool): if True, learn a Gaussian Mixture Model (GMM)
269
+ prior instead of a unimodal Gaussian prior. To use this option,
270
+ @learnable must be set to True.
271
+
272
+ gmm_num_modes (int): number of GMM modes to learn. Only
273
+ used if @use_gmm is True.
274
+
275
+ gmm_learn_weights (bool): if True, learn the weights of the GMM
276
+ model instead of setting them to be uniform across all the modes.
277
+ Only used if @use_gmm is True.
278
+
279
+ obs_shapes (OrderedDict): a dictionary that maps modality to
280
+ expected shapes for observations. If provided, assumes that
281
+ the prior should depend on observation inputs, and networks
282
+ will be created to output prior parameters.
283
+
284
+ mlp_layer_dims ([int]): sequence of integers for the MLP hidden layer sizes
285
+
286
+ goal_shapes (OrderedDict): a dictionary that maps modality to
287
+ expected shapes for goal observations.
288
+
289
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
290
+ be nested dictionary containing relevant per-modality information for encoder networks.
291
+ Should be of form:
292
+
293
+ obs_modality1: dict
294
+ feature_dimension: int
295
+ core_class: str
296
+ core_kwargs: dict
297
+ ...
298
+ ...
299
+ obs_randomizer_class: str
300
+ obs_randomizer_kwargs: dict
301
+ ...
302
+ ...
303
+ obs_modality2: dict
304
+ ...
305
+ """
306
+ self.device = device
307
+ self.latent_dim = latent_dim
308
+ self.latent_clip = latent_clip
309
+ self.learnable = learnable
310
+
311
+ self.use_gmm = use_gmm
312
+ if self.use_gmm:
313
+ self.num_modes = gmm_num_modes
314
+ else:
315
+ # unimodal Gaussian prior
316
+ self.num_modes = 1
317
+ self.gmm_learn_weights = gmm_learn_weights
318
+
319
+ self._input_dependent = (obs_shapes is not None) and (len(obs_shapes) > 0)
320
+
321
+ if self._input_dependent:
322
+ assert learnable
323
+ assert isinstance(obs_shapes, OrderedDict)
324
+
325
+ # network will generate mean and logvar
326
+ param_shapes = OrderedDict(
327
+ mean=(self.num_modes, self.latent_dim,),
328
+ logvar=(self.num_modes, self.latent_dim,),
329
+ )
330
+ param_obs_dependent = OrderedDict(mean=True, logvar=True)
331
+
332
+ if self.use_gmm and self.gmm_learn_weights:
333
+ # network generates GMM weights
334
+ param_shapes["weight"] = (self.num_modes,)
335
+ param_obs_dependent["weight"] = True
336
+ else:
337
+ # learn obs-indep mean / logvar
338
+ param_shapes = OrderedDict(
339
+ mean=(1, self.num_modes, self.latent_dim),
340
+ logvar=(1, self.num_modes, self.latent_dim),
341
+ )
342
+ param_obs_dependent = OrderedDict(mean=False, logvar=False)
343
+
344
+ if self.use_gmm and self.gmm_learn_weights:
345
+ # learn obs-indep GMM weights
346
+ param_shapes["weight"] = (1, self.num_modes)
347
+ param_obs_dependent["weight"] = False
348
+
349
+ super(GaussianPrior, self).__init__(
350
+ param_shapes=param_shapes,
351
+ param_obs_dependent=param_obs_dependent,
352
+ obs_shapes=obs_shapes,
353
+ mlp_layer_dims=mlp_layer_dims,
354
+ goal_shapes=goal_shapes,
355
+ encoder_kwargs=encoder_kwargs,
356
+ )
357
+
358
+ def _create_layers(self, net_kwargs):
359
+ """
360
+ Update from superclass to only create parameters / networks if not using
361
+ N(0, 1) Gaussian prior.
362
+ """
363
+ if self.learnable:
364
+ super(GaussianPrior, self)._create_layers(net_kwargs)
365
+
366
+ def sample(self, n, obs_dict=None, goal_dict=None):
367
+ """
368
+ Returns a batch of samples from the prior distribution.
369
+
370
+ Args:
371
+ n (int): this argument is used to specify the number
372
+ of samples to generate from the prior.
373
+
374
+ obs_dict (dict): inputs according to @obs_shapes. Only needs to be provided
375
+ if any prior parameters are obs-dependent. Leading dimension should
376
+ be consistent with @n, the number of samples to generate.
377
+
378
+ goal_dict (dict): inputs according to @goal_shapes (only if using goal observations)
379
+
380
+ Returns:
381
+ z (torch.Tensor): batch of sampled latent vectors.
382
+ """
383
+
384
+ # check consistency between n and obs_dict
385
+ if self._input_dependent:
386
+ TensorUtils.assert_size_at_dim(obs_dict, size=n, dim=0,
387
+ msg="obs dict and n mismatch in @sample")
388
+
389
+ if self.learnable:
390
+
391
+ # forward to get parameters
392
+ out = self.forward(batch_size=n, obs_dict=obs_dict, goal_dict=goal_dict)
393
+ prior_means, prior_logvars, prior_logweights = out["means"], out["logvars"], out["logweights"]
394
+
395
+ if prior_logweights is not None:
396
+ prior_weights = torch.exp(prior_logweights)
397
+
398
+ if self.use_gmm:
399
+ # learned GMM
400
+
401
+ # make uniform weights (in the case that weights were not learned)
402
+ if not self.gmm_learn_weights:
403
+ prior_weights = torch.ones(n, self.num_modes).to(prior_means.device) / self.num_modes
404
+
405
+ # sample modes
406
+ gmm_mode_indices = D.Categorical(prior_weights).sample()
407
+
408
+ # get GMM centers and sample using reparametrization trick
409
+ selected_means = TensorUtils.gather_sequence(prior_means, indices=gmm_mode_indices)
410
+ selected_logvars = TensorUtils.gather_sequence(prior_logvars, indices=gmm_mode_indices)
411
+ z = TorchUtils.reparameterize(selected_means, selected_logvars)
412
+
413
+ else:
414
+ # learned unimodal Gaussian - remove mode dim and sample from Gaussian using reparametrization trick
415
+ z = TorchUtils.reparameterize(prior_means[:, 0, :], prior_logvars[:, 0, :])
416
+
417
+ else:
418
+ # sample from N(0, 1)
419
+ z = torch.randn(n, self.latent_dim).float().to(self.device)
420
+
421
+ if self.latent_clip is not None:
422
+ z = z.clamp(-self.latent_clip, self.latent_clip)
423
+
424
+ return z
425
+
426
+ def kl_loss(self, posterior_params, z=None, obs_dict=None, goal_dict=None):
427
+ """
428
+ Computes sample-based KL divergence loss between the Gaussian distribution
429
+ given by @mu, @logvar and the prior distribution.
430
+
431
+ Args:
432
+ posterior_params (dict): dictionary with keys "mu" and "logvar" corresponding
433
+ to torch.Tensor batch of means and log-variances of posterior Gaussian
434
+ distribution.
435
+
436
+ z (torch.Tensor): samples from the Gaussian distribution parametrized by
437
+ @mu and @logvar. Only needed if @self.use_gmm is True.
438
+
439
+ obs_dict (dict): inputs according to @obs_shapes. Only needs to be provided
440
+ if any prior parameters are obs-dependent.
441
+
442
+ goal_dict (dict): inputs according to @goal_shapes (only if using goal observations)
443
+
444
+ Returns:
445
+ kl_loss (torch.Tensor): KL divergence loss
446
+ """
447
+ mu = posterior_params["mean"]
448
+ logvar = posterior_params["logvar"]
449
+
450
+ if not self.learnable:
451
+ # closed-form Gaussian KL from N(0, 1) prior
452
+ return LossUtils.KLD_0_1_loss(mu=mu, logvar=logvar)
453
+
454
+ # forward to get parameters
455
+ out = self.forward(batch_size=mu.shape[0], obs_dict=obs_dict, goal_dict=goal_dict)
456
+ prior_means, prior_logvars, prior_logweights = out["means"], out["logvars"], out["logweights"]
457
+
458
+ if not self.use_gmm:
459
+ # collapse mode dimension and compute Gaussian KL in closed-form
460
+ prior_means = prior_means[:, 0, :]
461
+ prior_logvars = prior_logvars[:, 0, :]
462
+ return LossUtils.KLD_gaussian_loss(
463
+ mu_1=mu,
464
+ logvar_1=logvar,
465
+ mu_2=prior_means,
466
+ logvar_2=prior_logvars,
467
+ )
468
+
469
+ # GMM KL loss computation
470
+ var = torch.exp(logvar.clamp(-8, 30)) # clamp for numerical stability
471
+ prior_vars = torch.exp(prior_logvars.clamp(-8, 30))
472
+ kl_loss = LossUtils.log_normal(x=z, m=mu, v=var) \
473
+ - LossUtils.log_normal_mixture(x=z, m=prior_means, v=prior_vars, log_w=prior_logweights)
474
+ return kl_loss.mean()
475
+
476
+ def forward(self, batch_size, obs_dict=None, goal_dict=None):
477
+ """
478
+ Computes means, logvars, and GMM weights (if using GMM and learning weights).
479
+
480
+ Args:
481
+ batch_size (int): batch size - this is needed for parameters that are
482
+ not obs-dependent, to make sure the leading dimension is correct
483
+ for downstream sampling and loss computation purposes
484
+
485
+ obs_dict (dict): inputs according to @obs_shapes. Only needs to be provided
486
+ if any prior parameters are obs-dependent.
487
+
488
+ goal_dict (dict): inputs according to @goal_shapes (only if using goal observations)
489
+
490
+ Returns:
491
+ prior_params (dict): dictionary containing prior parameters
492
+ """
493
+ assert self.learnable
494
+ prior_params = super(GaussianPrior, self).forward(
495
+ batch_size=batch_size, obs_dict=obs_dict, goal_dict=goal_dict)
496
+
497
+ if self.use_gmm and self.gmm_learn_weights:
498
+ # normalize learned weight outputs to sum to 1
499
+ logweights = F.log_softmax(prior_params["weight"], dim=-1)
500
+ else:
501
+ logweights = None
502
+ assert "weight" not in prior_params
503
+
504
+ out = dict(means=prior_params["mean"], logvars=prior_params["logvar"], logweights=logweights)
505
+ return out
506
+
507
+ def __repr__(self):
508
+ """Pretty print network"""
509
+ header = '{}'.format(str(self.__class__.__name__))
510
+ msg = ''
511
+ indent = ' ' * 4
512
+ msg += textwrap.indent("latent_dim={}\n".format(self.latent_dim), indent)
513
+ msg += textwrap.indent("latent_clip={}\n".format(self.latent_clip), indent)
514
+ msg += textwrap.indent("learnable={}\n".format(self.learnable), indent)
515
+ msg += textwrap.indent("input_dependent={}\n".format(self._input_dependent), indent)
516
+ msg += textwrap.indent("use_gmm={}\n".format(self.use_gmm), indent)
517
+ if self.use_gmm:
518
+ msg += textwrap.indent("gmm_num_nodes={}\n".format(self.num_modes), indent)
519
+ msg += textwrap.indent("gmm_learn_weights={}\n".format(self.gmm_learn_weights), indent)
520
+ if self.learnable:
521
+ if self.prior_module is not None:
522
+ msg += textwrap.indent("\nprior_module={}\n".format(self.prior_module), indent)
523
+ msg += textwrap.indent("prior_params={}\n".format(self.prior_params), indent)
524
+ msg = header + '(\n' + msg + ')'
525
+ return msg
526
+
527
+
528
+ class CategoricalPrior(Prior):
529
+ """
530
+ A class that holds functionality for learning categorical priors for use
531
+ in VAEs.
532
+ """
533
+ def __init__(
534
+ self,
535
+ latent_dim,
536
+ categorical_dim,
537
+ device,
538
+ learnable=False,
539
+ obs_shapes=None,
540
+ mlp_layer_dims=(),
541
+ goal_shapes=None,
542
+ encoder_kwargs=None,
543
+
544
+ ):
545
+ """
546
+ Args:
547
+ latent_dim (int): size of latent dimension for the prior
548
+
549
+ categorical_dim (int): size of categorical dimension (number of classes
550
+ for each dimension of latent space)
551
+
552
+ device (torch.Device): where the module should live (i.e. cpu, gpu)
553
+
554
+ learnable (bool): if True, learn the parameters of the prior (as opposed
555
+ to a default N(0, 1) prior)
556
+
557
+ obs_shapes (OrderedDict): a dictionary that maps modality to
558
+ expected shapes for observations. If provided, assumes that
559
+ the prior should depend on observation inputs, and networks
560
+ will be created to output prior parameters.
561
+
562
+ mlp_layer_dims ([int]): sequence of integers for the MLP hidden layer sizes
563
+
564
+ goal_shapes (OrderedDict): a dictionary that maps modality to
565
+ expected shapes for goal observations.
566
+
567
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
568
+ be nested dictionary containing relevant per-modality information for encoder networks.
569
+ Should be of form:
570
+
571
+ obs_modality1: dict
572
+ feature_dimension: int
573
+ core_class: str
574
+ core_kwargs: dict
575
+ ...
576
+ ...
577
+ obs_randomizer_class: str
578
+ obs_randomizer_kwargs: dict
579
+ ...
580
+ ...
581
+ obs_modality2: dict
582
+ ...
583
+ """
584
+ self.device = device
585
+ self.latent_dim = latent_dim
586
+ self.categorical_dim = categorical_dim
587
+ self.learnable = learnable
588
+
589
+ self._input_dependent = (obs_shapes is not None) and (len(obs_shapes) > 0)
590
+
591
+ if self._input_dependent:
592
+ assert learnable
593
+ assert isinstance(obs_shapes, OrderedDict)
594
+
595
+ # network will generate logits for categorical distributions
596
+ param_shapes = OrderedDict(
597
+ logit=(self.latent_dim, self.categorical_dim,)
598
+ )
599
+ param_obs_dependent = OrderedDict(logit=True)
600
+ else:
601
+ # learn obs-indep mean / logvar
602
+ param_shapes = OrderedDict(
603
+ logit=(1, self.latent_dim, self.categorical_dim),
604
+ )
605
+ param_obs_dependent = OrderedDict(logit=False)
606
+
607
+ super(CategoricalPrior, self).__init__(
608
+ param_shapes=param_shapes,
609
+ param_obs_dependent=param_obs_dependent,
610
+ obs_shapes=obs_shapes,
611
+ mlp_layer_dims=mlp_layer_dims,
612
+ goal_shapes=goal_shapes,
613
+ encoder_kwargs=encoder_kwargs,
614
+ )
615
+
616
+ def _create_layers(self, net_kwargs):
617
+ """
618
+ Update from superclass to only create parameters / networks if not using
619
+ uniform categorical prior.
620
+ """
621
+ if self.learnable:
622
+ super(CategoricalPrior, self)._create_layers(net_kwargs)
623
+
624
+ def sample(self, n, obs_dict=None, goal_dict=None):
625
+ """
626
+ Returns a batch of samples from the prior distribution.
627
+
628
+ Args:
629
+ n (int): this argument is used to specify the number
630
+ of samples to generate from the prior.
631
+
632
+ obs_dict (dict): inputs according to @obs_shapes. Only needs to be provided
633
+ if any prior parameters are obs-dependent. Leading dimension should
634
+ be consistent with @n, the number of samples to generate.
635
+
636
+ goal_dict (dict): inputs according to @goal_shapes (only if using goal observations)
637
+
638
+ Returns:
639
+ z (torch.Tensor): batch of sampled latent vectors.
640
+ """
641
+
642
+ # check consistency between n and obs_dict
643
+ if self._input_dependent:
644
+ TensorUtils.assert_size_at_dim(obs_dict, size=n, dim=0,
645
+ msg="obs dict and n mismatch in @sample")
646
+
647
+ if self.learnable:
648
+
649
+ # forward to get parameters
650
+ out = self.forward(batch_size=n, obs_dict=obs_dict, goal_dict=goal_dict)
651
+ prior_logits = out["logit"]
652
+
653
+ # sample one-hot latents from categorical distribution
654
+ dist = D.Categorical(logits=prior_logits)
655
+ z = TensorUtils.to_one_hot(dist.sample(), num_class=self.categorical_dim)
656
+
657
+ else:
658
+ # try to include a categorical sample for each class if possible (ensuring rough uniformity)
659
+ if (self.latent_dim == 1) and (self.categorical_dim <= n):
660
+ # include samples [0, 1, ..., C - 1] and then repeat until batch is filled
661
+ dist_samples = torch.arange(n).remainder(self.categorical_dim).unsqueeze(-1).to(self.device)
662
+ else:
663
+ # sample one-hot latents from uniform categorical distribution for each latent dimension
664
+ probs = torch.ones(n, self.latent_dim, self.categorical_dim).float().to(self.device)
665
+ dist_samples = D.Categorical(probs=probs).sample()
666
+ z = TensorUtils.to_one_hot(dist_samples, num_class=self.categorical_dim)
667
+
668
+ # reshape [B, D, C] to [B, D * C] to be consistent with other priors that return flat latents
669
+ z = z.reshape(*z.shape[:-2], -1)
670
+ return z
671
+
672
+ def kl_loss(self, posterior_params, z=None, obs_dict=None, goal_dict=None):
673
+ """
674
+ Computes KL divergence loss between the Categorical distribution
675
+ given by the unnormalized logits @logits and the prior distribution.
676
+
677
+ Args:
678
+ posterior_params (dict): dictionary with key "logits" corresponding
679
+ to torch.Tensor batch of unnormalized logits of shape [B, D * C]
680
+ that corresponds to the posterior categorical distribution
681
+
682
+ z (torch.Tensor): samples from encoder - unused for this prior
683
+
684
+ obs_dict (dict): inputs according to @obs_shapes. Only needs to be provided
685
+ if any prior parameters are obs-dependent.
686
+
687
+ goal_dict (dict): inputs according to @goal_shapes (only if using goal observations)
688
+
689
+ Returns:
690
+ kl_loss (torch.Tensor): KL divergence loss
691
+ """
692
+ logits = posterior_params["logit"].reshape(-1, self.latent_dim, self.categorical_dim)
693
+ if not self.learnable:
694
+ # prior logits correspond to uniform categorical distribution
695
+ prior_logits = torch.zeros_like(logits)
696
+ else:
697
+ # forward to get parameters
698
+ out = self.forward(batch_size=posterior_params["logit"].shape[0], obs_dict=obs_dict, goal_dict=goal_dict)
699
+ prior_logits = out["logit"]
700
+
701
+ prior_dist = D.Categorical(logits=prior_logits)
702
+ posterior_dist = D.Categorical(logits=logits)
703
+
704
+ # sum over latent dimensions, but average over batch dimension
705
+ kl_loss = D.kl_divergence(posterior_dist, prior_dist)
706
+ assert len(kl_loss.shape) == 2
707
+ return kl_loss.sum(-1).mean()
708
+
709
+ def forward(self, batch_size, obs_dict=None, goal_dict=None):
710
+ """
711
+ Computes prior logits (unnormalized log-probs).
712
+
713
+ Args:
714
+ batch_size (int): batch size - this is needed for parameters that are
715
+ not obs-dependent, to make sure the leading dimension is correct
716
+ for downstream sampling and loss computation purposes
717
+
718
+ obs_dict (dict): inputs according to @obs_shapes. Only needs to be provided
719
+ if any prior parameters are obs-dependent.
720
+
721
+ goal_dict (dict): inputs according to @goal_shapes (only if using goal observations)
722
+
723
+ Returns:
724
+ prior_params (dict): dictionary containing prior parameters
725
+ """
726
+ assert self.learnable
727
+ return super(CategoricalPrior, self).forward(
728
+ batch_size=batch_size, obs_dict=obs_dict, goal_dict=goal_dict)
729
+
730
+ def __repr__(self):
731
+ """Pretty print network"""
732
+ header = '{}'.format(str(self.__class__.__name__))
733
+ msg = ''
734
+ indent = ' ' * 4
735
+ msg += textwrap.indent("latent_dim={}\n".format(self.latent_dim), indent)
736
+ msg += textwrap.indent("categorical_dim={}\n".format(self.categorical_dim), indent)
737
+ msg += textwrap.indent("learnable={}\n".format(self.learnable), indent)
738
+ msg += textwrap.indent("input_dependent={}\n".format(self._input_dependent), indent)
739
+ if self.learnable:
740
+ if self.prior_module is not None:
741
+ msg += textwrap.indent("\nprior_module={}\n".format(self.prior_module), indent)
742
+ msg += textwrap.indent("prior_params={}\n".format(self.prior_params), indent)
743
+ msg = header + '(\n' + msg + ')'
744
+ return msg
745
+
746
+
747
+ class VAE(torch.nn.Module):
748
+ """
749
+ A Variational Autoencoder (VAE), as described in https://arxiv.org/abs/1312.6114.
750
+
751
+ Models a distribution p(X) or a conditional distribution p(X | Y), where each
752
+ variable can consist of multiple modalities. The target variable X, whose
753
+ distribution is modeled, is specified through the @input_shapes argument,
754
+ which is a map between modalities (strings) and expected shapes. In this way,
755
+ a variable that consists of multiple kinds of data (e.g. image and flat-dimensional)
756
+ can be modeled as well. A separate @output_shapes argument is used to specify the
757
+ expected reconstructions - this allows for asymmetric reconstruction (for example,
758
+ reconstructing low-resolution images).
759
+
760
+ This implementation supports learning conditional distributions as well (cVAE).
761
+ The conditioning variable Y is specified through the @condition_shapes argument,
762
+ which is also a map between modalities (strings) and expected shapes. In this way,
763
+ variables with multiple kinds of data (e.g. image and flat-dimensional) can
764
+ jointly be conditioned on. By default, the decoder takes the conditioning
765
+ variable Y as input. To force the decoder to reconstruct from just the latent,
766
+ set @decoder_is_conditioned to False (in this case, the prior must be conditioned).
767
+
768
+ The implementation also supports learning expressive priors instead of using
769
+ the usual N(0, 1) prior. There are three kinds of priors supported - Gaussian,
770
+ Gaussian Mixture Model (GMM), and Categorical. For each prior, the parameters can
771
+ be learned as independent parameters, or be learned as functions of the conditioning
772
+ variable Y (by setting @prior_is_conditioned).
773
+ """
774
+ def __init__(
775
+ self,
776
+ input_shapes,
777
+ output_shapes,
778
+ encoder_layer_dims,
779
+ decoder_layer_dims,
780
+ latent_dim,
781
+ device,
782
+ condition_shapes=None,
783
+ decoder_is_conditioned=True,
784
+ decoder_reconstruction_sum_across_elements=False,
785
+ latent_clip=None,
786
+ output_squash=(),
787
+ output_scales=None,
788
+ output_ranges=None,
789
+ prior_learn=False,
790
+ prior_is_conditioned=False,
791
+ prior_layer_dims=(),
792
+ prior_use_gmm=False,
793
+ prior_gmm_num_modes=10,
794
+ prior_gmm_learn_weights=False,
795
+ prior_use_categorical=False,
796
+ prior_categorical_dim=10,
797
+ prior_categorical_gumbel_softmax_hard=False,
798
+ goal_shapes=None,
799
+ encoder_kwargs=None,
800
+ ):
801
+ """
802
+ Args:
803
+ input_shapes (OrderedDict): a dictionary that maps modality to
804
+ expected shapes for all encoder-specific inputs. This corresponds
805
+ to the variable X whose distribution we are learning.
806
+
807
+ output_shapes (OrderedDict): a dictionary that maps modality to
808
+ expected shape for outputs to reconstruct. Usually, this is
809
+ the same as @input_shapes but this argument allows
810
+ for asymmetries, such as reconstructing low-resolution
811
+ images.
812
+
813
+ encoder_layer_dims ([int]): sequence of integers for the encoder hidden
814
+ layer sizes.
815
+
816
+ decoder_layer_dims ([int]): sequence of integers for the decoder hidden
817
+ layer sizes.
818
+
819
+ latent_dim (int): dimension of latent space for the VAE
820
+
821
+ device (torch.Device): where the module should live (i.e. cpu, gpu)
822
+
823
+ condition_shapes (OrderedDict): a dictionary that maps modality to
824
+ expected shapes for all conditioning inputs. If this is provided,
825
+ a conditional distribution is modeled (cVAE). Conditioning takes
826
+ place in the decoder by default, and optionally, the prior.
827
+
828
+ decoder_is_conditioned (bool): whether to condition the decoder
829
+ on the conditioning variables. True by default. Only used if
830
+ @condition_shapes is not empty.
831
+
832
+ decoder_reconstruction_sum_across_elements (bool): by default, VAEs
833
+ average across modality elements and modalities when computing
834
+ reconstruction loss. If this is True, sum across all dimensions
835
+ and modalities instead.
836
+
837
+ latent_clip (float): if provided, clip all latents sampled at
838
+ test-time in each dimension to (-@latent_clip, @latent_clip)
839
+
840
+ output_squash ([str]): an iterable of modalities that should be
841
+ a subset of @output_shapes. The decoder outputs for these
842
+ modalities will be squashed into a symmetric range [-a, a]
843
+ by using a tanh layer and then scaling the output with the
844
+ corresponding value in the @output_scales dictionary.
845
+
846
+ output_scales (dict): a dictionary that maps modality to a
847
+ scaling value. Used in conjunction with @output_squash.
848
+
849
+ output_ranges (dict): a dictionary of [a, b] specifying the output range.
850
+ when output_ranges is specified (not None), output_scales should be None
851
+
852
+ prior_learn (bool): if True, the prior distribution parameters
853
+ are also learned through the KL-divergence loss (instead
854
+ of being constrained to a N(0, 1) Gaussian distribution).
855
+ If @prior_is_conditioned is True, a global set of parameters
856
+ are learned, otherwise, a prior network that maps between
857
+ modalities in @condition_shapes and prior parameters is
858
+ learned. By default, a Gaussian prior is learned, unless
859
+ @prior_use_gmm is True, in which case a Gaussian Mixture
860
+ Model (GMM) prior is learned.
861
+
862
+ prior_is_conditioned (bool): whether to condition the prior
863
+ on the conditioning variables. False by default. Only used if
864
+ @condition_shapes is not empty. If this is set to True,
865
+ @prior_learn must be True.
866
+
867
+ prior_layer_dims ([int]): sequence of integers for the prior hidden layer
868
+ sizes. Only used for learned priors that take condition variables as
869
+ input (i.e. when @prior_learn and @prior_is_conditioned are set to True,
870
+ and @condition_shapes is not empty).
871
+
872
+ prior_use_gmm (bool): if True, learn a Gaussian Mixture Model (GMM)
873
+ prior instead of a unimodal Gaussian prior. To use this option,
874
+ @prior_learn must be set to True.
875
+
876
+ prior_gmm_num_modes (int): number of GMM modes to learn. Only
877
+ used if @prior_use_gmm is True.
878
+
879
+ prior_gmm_learn_weights (bool): if True, learn the weights of the GMM
880
+ model instead of setting them to be uniform across all the modes.
881
+ Only used if @prior_use_gmm is True.
882
+
883
+ prior_use_categorical (bool): if True, use a categorical prior instead of
884
+ a unimodal Gaussian prior. This will also cause the encoder to output
885
+ a categorical distribution, and will use the Gumbel-Softmax trick
886
+ for reparametrization.
887
+
888
+ prior_categorical_dim (int): categorical dimension - each latent sampled
889
+ from the prior will be of shape (@latent_dim, @prior_categorical_dim)
890
+ and will be "one-hot" in the latter dimension. Only used if
891
+ @prior_use_categorical is True.
892
+
893
+ prior_categorical_gumbel_softmax_hard (bool): if True, use the "hard" version of
894
+ Gumbel Softmax for reparametrization. Only used if @prior_use_categorical is True.
895
+
896
+ goal_shapes (OrderedDict): a dictionary that maps modality to
897
+ expected shapes for goal observations. Goals are treates as additional
898
+ conditioning inputs. They are usually specified separately because
899
+ they have duplicate modalities as the conditioning inputs (otherwise
900
+ they could just be added to the set of conditioning inputs).
901
+
902
+ encoder_kwargs (dict or None): If None, results in default encoder_kwargs being applied. Otherwise, should
903
+ be nested dictionary containing relevant per-modality information for encoder networks.
904
+ Should be of form:
905
+
906
+ obs_modality1: dict
907
+ feature_dimension: int
908
+ core_class: str
909
+ core_kwargs: dict
910
+ ...
911
+ ...
912
+ obs_randomizer_class: str
913
+ obs_randomizer_kwargs: dict
914
+ ...
915
+ ...
916
+ obs_modality2: dict
917
+ ...
918
+ """
919
+ super(VAE, self).__init__()
920
+
921
+ self.latent_dim = latent_dim
922
+ self.latent_clip = latent_clip
923
+ self.device = device
924
+
925
+ # encoder and decoder input dicts and output shapes dict for reconstruction
926
+ assert isinstance(input_shapes, OrderedDict)
927
+ assert isinstance(output_shapes, OrderedDict)
928
+ self.input_shapes = deepcopy(input_shapes)
929
+ self.output_shapes = deepcopy(output_shapes)
930
+
931
+ # check for conditioning (cVAE)
932
+ self._is_cvae = False
933
+ self.condition_shapes = deepcopy(condition_shapes) if condition_shapes is not None else OrderedDict()
934
+ if len(self.condition_shapes) > 0:
935
+ # this is a cVAE - we learn a conditional distribution p(X | Y)
936
+ assert isinstance(self.condition_shapes, OrderedDict)
937
+ self._is_cvae = True
938
+ self.decoder_is_conditioned = decoder_is_conditioned
939
+ self.prior_is_conditioned = prior_is_conditioned
940
+ assert self.decoder_is_conditioned or self.prior_is_conditioned, \
941
+ "cVAE must be conditioned in decoder and/or prior"
942
+ if self.prior_is_conditioned:
943
+ assert prior_learn, "to pass conditioning inputs to prior, prior must be learned"
944
+
945
+ # check for goal conditioning
946
+ self._is_goal_conditioned = False
947
+ self.goal_shapes = deepcopy(goal_shapes) if goal_shapes is not None else OrderedDict()
948
+ if len(self.goal_shapes) > 0:
949
+ assert self._is_cvae, "to condition VAE on goals, it must be a cVAE"
950
+ assert isinstance(self.goal_shapes, OrderedDict)
951
+ self._is_goal_conditioned = True
952
+
953
+ self.encoder_layer_dims = encoder_layer_dims
954
+ self.decoder_layer_dims = decoder_layer_dims
955
+
956
+ # determines whether outputs are squashed with tanh and if so, to what scaling
957
+ assert not (output_scales is not None and output_ranges is not None)
958
+ self.output_squash = output_squash
959
+ self.output_scales = output_scales if output_scales is not None else OrderedDict()
960
+ self.output_ranges = output_ranges if output_ranges is not None else OrderedDict()
961
+
962
+ assert set(self.output_squash) == set(self.output_scales.keys())
963
+ assert set(self.output_squash).issubset(set(self.output_shapes))
964
+
965
+ # decoder settings
966
+ self.decoder_reconstruction_sum_across_elements = decoder_reconstruction_sum_across_elements
967
+
968
+ # prior parameters
969
+ self.prior_learn = prior_learn
970
+ self.prior_layer_dims = prior_layer_dims
971
+ self.prior_use_gmm = prior_use_gmm
972
+ self.prior_gmm_num_modes = prior_gmm_num_modes
973
+ self.prior_gmm_learn_weights = prior_gmm_learn_weights
974
+ self.prior_use_categorical = prior_use_categorical
975
+ self.prior_categorical_dim = prior_categorical_dim
976
+ self.prior_categorical_gumbel_softmax_hard = prior_categorical_gumbel_softmax_hard
977
+ assert np.sum([self.prior_use_gmm, self.prior_use_categorical]) <= 1
978
+
979
+ # for obs core
980
+ self._encoder_kwargs = encoder_kwargs
981
+
982
+ if self.prior_use_gmm:
983
+ assert self.prior_learn, "GMM must be learned"
984
+
985
+ if self.prior_use_categorical:
986
+ # initialize temperature for Gumbel-Softmax
987
+ self.set_gumbel_temperature(1.0)
988
+
989
+ # create encoder, decoder, prior
990
+ self._create_layers()
991
+
992
+ def _create_layers(self):
993
+ """
994
+ Creates the encoder, decoder, and prior networks.
995
+ """
996
+ self.nets = nn.ModuleDict()
997
+
998
+ # VAE Encoder
999
+ self._create_encoder()
1000
+
1001
+ # VAE Decoder
1002
+ self._create_decoder()
1003
+
1004
+ # VAE Prior.
1005
+ self._create_prior()
1006
+
1007
+ def _create_encoder(self):
1008
+ """
1009
+ Helper function to create encoder.
1010
+ """
1011
+
1012
+ # encoder takes "input" dictionary and possibly "condition" (if cVAE) and "goal" (if goal-conditioned)
1013
+ encoder_obs_group_shapes = OrderedDict()
1014
+ encoder_obs_group_shapes["input"] = OrderedDict(self.input_shapes)
1015
+ if self._is_cvae:
1016
+ encoder_obs_group_shapes["condition"] = OrderedDict(self.condition_shapes)
1017
+ if self._is_goal_conditioned:
1018
+ encoder_obs_group_shapes["goal"] = OrderedDict(self.goal_shapes)
1019
+
1020
+ # encoder outputs posterior distribution parameters
1021
+ if self.prior_use_categorical:
1022
+ encoder_output_shapes = OrderedDict(
1023
+ logit=(self.latent_dim * self.prior_categorical_dim,),
1024
+ )
1025
+ else:
1026
+ encoder_output_shapes = OrderedDict(
1027
+ mean=(self.latent_dim,),
1028
+ logvar=(self.latent_dim,),
1029
+ )
1030
+
1031
+ self.nets["encoder"] = MIMO_MLP(
1032
+ input_obs_group_shapes=encoder_obs_group_shapes,
1033
+ output_shapes=encoder_output_shapes,
1034
+ layer_dims=self.encoder_layer_dims,
1035
+ encoder_kwargs=self._encoder_kwargs,
1036
+ )
1037
+
1038
+ def _create_decoder(self):
1039
+ """
1040
+ Helper function to create decoder.
1041
+ """
1042
+
1043
+ # decoder takes latent (included as "input" observation group) and possibly "condition" (if cVAE) and "goal" (if goal-conditioned)
1044
+ decoder_obs_group_shapes = OrderedDict()
1045
+ latent_shape = (self.latent_dim,)
1046
+ if self.prior_use_categorical:
1047
+ latent_shape = (self.latent_dim * self.prior_categorical_dim,)
1048
+ decoder_obs_group_shapes["input"] = OrderedDict(latent=latent_shape)
1049
+ if self._is_cvae:
1050
+ decoder_obs_group_shapes["condition"] = OrderedDict(self.condition_shapes)
1051
+ if self._is_goal_conditioned:
1052
+ decoder_obs_group_shapes["goal"] = OrderedDict(self.goal_shapes)
1053
+
1054
+ self.nets["decoder"] = MIMO_MLP(
1055
+ input_obs_group_shapes=decoder_obs_group_shapes,
1056
+ output_shapes=self.output_shapes,
1057
+ layer_dims=self.decoder_layer_dims,
1058
+ encoder_kwargs=self._encoder_kwargs,
1059
+ )
1060
+
1061
+ def _create_prior(self):
1062
+ """
1063
+ Helper function to create prior.
1064
+ """
1065
+
1066
+ # prior possibly takes "condition" (if cVAE) and "goal" (if goal-conditioned)
1067
+ prior_obs_group_shapes = OrderedDict(condition=None, goal=None)
1068
+ if self._is_cvae and self.prior_is_conditioned:
1069
+ prior_obs_group_shapes["condition"] = OrderedDict(self.condition_shapes)
1070
+ if self._is_goal_conditioned:
1071
+ prior_obs_group_shapes["goal"] = OrderedDict(self.goal_shapes)
1072
+
1073
+ if self.prior_use_categorical:
1074
+ self.nets["prior"] = CategoricalPrior(
1075
+ latent_dim=self.latent_dim,
1076
+ categorical_dim=self.prior_categorical_dim,
1077
+ device=self.device,
1078
+ learnable=self.prior_learn,
1079
+ obs_shapes=prior_obs_group_shapes["condition"],
1080
+ mlp_layer_dims=self.prior_layer_dims,
1081
+ goal_shapes=prior_obs_group_shapes["goal"],
1082
+ encoder_kwargs=self._encoder_kwargs,
1083
+ )
1084
+ else:
1085
+ self.nets["prior"] = GaussianPrior(
1086
+ latent_dim=self.latent_dim,
1087
+ device=self.device,
1088
+ latent_clip=self.latent_clip,
1089
+ learnable=self.prior_learn,
1090
+ use_gmm=self.prior_use_gmm,
1091
+ gmm_num_modes=self.prior_gmm_num_modes,
1092
+ gmm_learn_weights=self.prior_gmm_learn_weights,
1093
+ obs_shapes=prior_obs_group_shapes["condition"],
1094
+ mlp_layer_dims=self.prior_layer_dims,
1095
+ goal_shapes=prior_obs_group_shapes["goal"],
1096
+ encoder_kwargs=self._encoder_kwargs,
1097
+ )
1098
+
1099
+ def encode(self, inputs, conditions=None, goals=None):
1100
+ """
1101
+ Args:
1102
+ inputs (dict): a dictionary that maps input modalities to torch.Tensor
1103
+ batches. These should correspond to the encoder-only modalities
1104
+ (i.e. @self.encoder_only_shapes).
1105
+
1106
+ conditions (dict): a dictionary that maps modalities to torch.Tensor
1107
+ batches. These should correspond to the modalities used for conditioning
1108
+ in either the decoder or the prior (or both). Only for cVAEs.
1109
+
1110
+ goals (dict): a dictionary that maps modalities to torch.Tensor
1111
+ batches. These should correspond to goal modalities. Only for cVAEs.
1112
+
1113
+ Returns:
1114
+ posterior params (dict): dictionary with posterior parameters
1115
+ """
1116
+ return self.nets["encoder"](
1117
+ input=inputs,
1118
+ condition=conditions,
1119
+ goal=goals,
1120
+ )
1121
+
1122
+ def reparameterize(self, posterior_params):
1123
+ """
1124
+ Args:
1125
+ posterior params (dict): dictionary from encoder forward pass that
1126
+ parametrizes the encoder distribution
1127
+
1128
+ Returns:
1129
+ z (torch.Tensor): sampled latents that are also differentiable
1130
+ """
1131
+ if self.prior_use_categorical:
1132
+ # reshape to [B, D, C] to take softmax across categorical classes
1133
+ logits = posterior_params["logit"].reshape(-1, self.latent_dim, self.prior_categorical_dim)
1134
+ z = F.gumbel_softmax(
1135
+ logits=logits,
1136
+ tau=self._gumbel_temperature,
1137
+ hard=self.prior_categorical_gumbel_softmax_hard,
1138
+ dim=-1,
1139
+ )
1140
+ # reshape to [B, D * C], since downstream networks expect flat latents
1141
+ return TensorUtils.flatten(z)
1142
+
1143
+ return TorchUtils.reparameterize(
1144
+ mu=posterior_params["mean"],
1145
+ logvar=posterior_params["logvar"],
1146
+ )
1147
+
1148
+ def decode(self, conditions=None, goals=None, z=None, n=None):
1149
+ """
1150
+ Pass latents through decoder. Latents should be passed in to
1151
+ this function at train-time for backpropagation, but they
1152
+ can be left out at test-time. In this case, latents will
1153
+ be sampled using the VAE prior.
1154
+
1155
+ Args:
1156
+ conditions (dict): a dictionary that maps modalities to torch.Tensor
1157
+ batches. These should correspond to the modalities used for conditioning
1158
+ in either the decoder or the prior (or both). Only for cVAEs.
1159
+
1160
+ goals (dict): a dictionary that maps modalities to torch.Tensor
1161
+ batches. These should correspond to goal modalities. Only for cVAEs.
1162
+
1163
+ z (torch.Tensor): if provided, these latents are used to generate
1164
+ reconstructions from the VAE, and the prior is not sampled.
1165
+
1166
+ n (int): this argument is used to specify the number of samples to
1167
+ generate from the prior. Only required if @z is None - i.e.
1168
+ sampling takes place
1169
+
1170
+ Returns:
1171
+ recons (dict): dictionary of reconstructed inputs
1172
+ """
1173
+
1174
+ if z is None:
1175
+ # sample latents from prior distribution
1176
+ assert n is not None
1177
+ z = self.sample_prior(n=n, conditions=conditions, goals=goals)
1178
+
1179
+ # decoder takes latents as input, and maybe condition variables
1180
+ # and goal variables
1181
+ inputs = dict(
1182
+ input=dict(latent=z),
1183
+ condition=conditions,
1184
+ goal=goals,
1185
+ )
1186
+
1187
+ # pass through decoder to reconstruct variables in @self.output_shapes
1188
+ recons = self.nets["decoder"](**inputs)
1189
+
1190
+ # apply tanh squashing to output modalities
1191
+ for k in self.output_squash:
1192
+ recons[k] = self.output_scales[k] * torch.tanh(recons[k])
1193
+
1194
+ for k, v_range in self.output_ranges.items():
1195
+ assert v_range[1] > v_range[0]
1196
+ recons[k] = torch.sigmoid(recons[k]) * (v_range[1] - v_range[0]) + v_range[0]
1197
+ return recons
1198
+
1199
+ def sample_prior(self, n, conditions=None, goals=None):
1200
+ """
1201
+ Samples from the prior using the prior parameters.
1202
+
1203
+ Args:
1204
+ n (int): this argument is used to specify the number
1205
+ of samples to generate from the prior.
1206
+
1207
+ conditions (dict): a dictionary that maps modalities to torch.Tensor
1208
+ batches. These should correspond to the modalities used for conditioning
1209
+ in either the decoder or the prior (or both). Only for cVAEs.
1210
+
1211
+ goals (dict): a dictionary that maps modalities to torch.Tensor
1212
+ batches. These should correspond to goal modalities. Only for cVAEs.
1213
+
1214
+ Returns:
1215
+ z (torch.Tensor): sampled latents from the prior
1216
+ """
1217
+ return self.nets["prior"].sample(n=n, obs_dict=conditions, goal_dict=goals)
1218
+
1219
+ def kl_loss(self, posterior_params, encoder_z=None, conditions=None, goals=None):
1220
+ """
1221
+ Computes KL divergence loss given the results of the VAE encoder forward
1222
+ pass and the conditioning and goal modalities (if the prior is input-dependent).
1223
+
1224
+ Args:
1225
+ posterior_params (dict): dictionary with keys "mu" and "logvar" corresponding
1226
+ to torch.Tensor batch of means and log-variances of posterior Gaussian
1227
+ distribution. This is the output of @self.encode.
1228
+
1229
+ encoder_z (torch.Tensor): samples from the Gaussian distribution parametrized by
1230
+ @mu and @logvar. Only required if using a GMM prior.
1231
+
1232
+ conditions (dict): inputs according to @self.condition_shapes. Only needs to be provided
1233
+ if any prior parameters are input-dependent.
1234
+
1235
+ goal_dict (dict): inputs according to @self.goal_shapes (only if using goal observations)
1236
+
1237
+ Returns:
1238
+ kl_loss (torch.Tensor): VAE KL divergence loss
1239
+ """
1240
+ return self.nets["prior"].kl_loss(
1241
+ posterior_params=posterior_params,
1242
+ z=encoder_z,
1243
+ obs_dict=conditions,
1244
+ goal_dict=goals,
1245
+ )
1246
+
1247
+ def reconstruction_loss(self, reconstructions, targets):
1248
+ """
1249
+ Reconstruction loss. Note that we compute the average per-dimension error
1250
+ in each modality and then average across all the modalities.
1251
+
1252
+ The beta term for weighting between reconstruction and kl losses will
1253
+ need to be tuned in practice for each situation (see
1254
+ https://twitter.com/memotv/status/973323454350090240 for more
1255
+ discussion).
1256
+
1257
+ Args:
1258
+ reconstructions (dict): reconstructed inputs, consistent with
1259
+ @self.output_shapes
1260
+ targets (dict): reconstruction targets, consistent with
1261
+ @self.output_shapes
1262
+
1263
+ Returns:
1264
+ reconstruction_loss (torch.Tensor): VAE reconstruction loss
1265
+ """
1266
+ random_key = list(reconstructions.keys())[0]
1267
+ batch_size = reconstructions[random_key].shape[0]
1268
+ num_mods = len(reconstructions.keys())
1269
+
1270
+ # collect errors per modality, while preserving shapes in @reconstructions
1271
+ recons_errors = []
1272
+ for k in reconstructions:
1273
+ L2_loss = (reconstructions[k] - targets[k]).pow(2)
1274
+ recons_errors.append(L2_loss)
1275
+
1276
+ # reduce errors across modalities and dimensions
1277
+ if self.decoder_reconstruction_sum_across_elements:
1278
+ # average across batch but sum across modalities and dimensions
1279
+ loss = sum([x.sum() for x in recons_errors])
1280
+ loss /= batch_size
1281
+ else:
1282
+ # compute mse loss in each modality and average across modalities
1283
+ loss = sum([x.mean() for x in recons_errors])
1284
+ loss /= num_mods
1285
+ return loss
1286
+
1287
+ def forward(self, inputs, outputs, conditions=None, goals=None, freeze_encoder=False):
1288
+ """
1289
+ A full pass through the VAE network to construct KL and reconstruction
1290
+ losses.
1291
+
1292
+ Args:
1293
+ inputs (dict): a dictionary that maps input modalities to torch.Tensor
1294
+ batches. These should correspond to the encoder-only modalities
1295
+ (i.e. @self.encoder_only_shapes).
1296
+
1297
+ outputs (dict): a dictionary that maps output modalities to torch.Tensor
1298
+ batches. These should correspond to the modalities used for
1299
+ reconstruction (i.e. @self.output_shapes).
1300
+
1301
+ conditions (dict): a dictionary that maps modalities to torch.Tensor
1302
+ batches. These should correspond to the modalities used for conditioning
1303
+ in either the decoder or the prior (or both). Only for cVAEs.
1304
+
1305
+ goals (dict): a dictionary that maps modalities to torch.Tensor
1306
+ batches. These should correspond to goal modalities. Only for cVAEs.
1307
+
1308
+ freeze_encoder (bool): if True, don't backprop into encoder by detaching
1309
+ encoder outputs. Useful for doing staged VAE training.
1310
+
1311
+ Returns:
1312
+ vae_outputs (dict): a dictionary that contains the following outputs.
1313
+
1314
+ encoder_params (dict): parameters for the posterior distribution
1315
+ from the encoder forward pass
1316
+
1317
+ encoder_z (torch.Tensor): latents sampled from the encoder posterior
1318
+
1319
+ decoder_outputs (dict): reconstructions from the decoder
1320
+
1321
+ kl_loss (torch.Tensor): KL loss over the batch of data
1322
+
1323
+ reconstruction_loss (torch.Tensor): reconstruction loss over the batch of data
1324
+ """
1325
+
1326
+ # In the comments below, X = inputs, Y = conditions, and we seek to learn P(X | Y).
1327
+ # The decoder and prior only have knowledge about Y and try to reconstruct X.
1328
+ # Notice that when Y is the empty set, this reduces to a normal VAE.
1329
+
1330
+ # mu, logvar <- Enc(X, Y)
1331
+ posterior_params = self.encode(
1332
+ inputs=inputs,
1333
+ conditions=conditions,
1334
+ goals=goals,
1335
+ )
1336
+
1337
+ if freeze_encoder:
1338
+ posterior_params = TensorUtils.detach(posterior_params)
1339
+
1340
+ # z ~ Enc(z | X, Y)
1341
+ encoder_z = self.reparameterize(posterior_params)
1342
+
1343
+ # hat(X) = Dec(z, Y)
1344
+ reconstructions = self.decode(
1345
+ conditions=conditions,
1346
+ goals=goals,
1347
+ z=encoder_z,
1348
+ )
1349
+
1350
+ # this will also train prior network z ~ Prior(z | Y)
1351
+ kl_loss = self.kl_loss(
1352
+ posterior_params=posterior_params,
1353
+ encoder_z=encoder_z,
1354
+ conditions=conditions,
1355
+ goals=goals,
1356
+ )
1357
+
1358
+ reconstruction_loss = self.reconstruction_loss(
1359
+ reconstructions=reconstructions,
1360
+ targets=outputs,
1361
+ )
1362
+
1363
+ return {
1364
+ "encoder_params" : posterior_params,
1365
+ "encoder_z" : encoder_z,
1366
+ "decoder_outputs" : reconstructions,
1367
+ "kl_loss" : kl_loss,
1368
+ "reconstruction_loss" : reconstruction_loss,
1369
+ }
1370
+
1371
+ def set_gumbel_temperature(self, temperature):
1372
+ """
1373
+ Used by external algorithms to schedule Gumbel-Softmax temperature,
1374
+ which is used during reparametrization at train-time. Should only
1375
+ be used if @self.prior_use_categorical is True.
1376
+ """
1377
+ assert self.prior_use_categorical
1378
+ self._gumbel_temperature = temperature
1379
+
1380
+ def get_gumbel_temperature(self):
1381
+ """
1382
+ Return current Gumbel-Softmax temperature. Should only be used if
1383
+ @self.prior_use_categorical is True.
1384
+ """
1385
+ assert self.prior_use_categorical
1386
+ return self._gumbel_temperature
aloha-devel/robomimic/scripts/config_gen/act_gen.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from robomimic.scripts.config_gen.helper import *
2
+
3
+ def make_generator_helper(args):
4
+ algo_name_short = "act"
5
+ generator = get_generator(
6
+ algo_name="act",
7
+ config_file=os.path.join(base_path, 'robomimic/exps/templates/act.json'),
8
+ args=args,
9
+ algo_name_short=algo_name_short,
10
+ pt=True,
11
+ )
12
+ if args.ckpt_mode is None:
13
+ args.ckpt_mode = "off"
14
+
15
+
16
+ generator.add_param(
17
+ key="train.num_epochs",
18
+ name="",
19
+ group=-1,
20
+ values=[1000],
21
+ )
22
+
23
+ generator.add_param(
24
+ key="train.batch_size",
25
+ name="",
26
+ group=-1,
27
+ values=[64],
28
+ )
29
+
30
+ generator.add_param(
31
+ key="train.max_grad_norm",
32
+ name="",
33
+ group=-1,
34
+ values=[100.0],
35
+ )
36
+
37
+ if args.env == "r2d2":
38
+ generator.add_param(
39
+ key="train.data",
40
+ name="ds",
41
+ group=2,
42
+ values=[
43
+ [{"path": p} for p in scan_datasets("~/Downloads/example_pen_in_cup", postfix="trajectory_im128.h5")],
44
+ ],
45
+ value_names=[
46
+ "pen-in-cup",
47
+ ],
48
+ )
49
+ generator.add_param(
50
+ key="train.action_keys",
51
+ name="ac_keys",
52
+ group=-1,
53
+ values=[
54
+ [
55
+ "action/abs_pos",
56
+ "action/abs_rot_6d",
57
+ "action/gripper_position",
58
+ ],
59
+ ],
60
+ value_names=[
61
+ "abs",
62
+ ],
63
+ )
64
+ elif args.env == "kitchen":
65
+ raise NotImplementedError
66
+ elif args.env == "square":
67
+ generator.add_param(
68
+ key="train.data",
69
+ name="ds",
70
+ group=2,
71
+ values=[
72
+ [
73
+ {"path": "TODO.hdf5"}, # replace with your own path
74
+ ],
75
+ ],
76
+ value_names=[
77
+ "square",
78
+ ],
79
+ )
80
+
81
+ # update env config to use absolute action control
82
+ generator.add_param(
83
+ key="experiment.env_meta_update_dict",
84
+ name="",
85
+ group=-1,
86
+ values=[
87
+ {"env_kwargs": {"controller_configs": {"control_delta": False}}}
88
+ ],
89
+ )
90
+
91
+ generator.add_param(
92
+ key="train.action_keys",
93
+ name="ac_keys",
94
+ group=-1,
95
+ values=[
96
+ [
97
+ "action_dict/abs_pos",
98
+ "action_dict/abs_rot_6d",
99
+ "action_dict/gripper",
100
+ # "actions",
101
+ ],
102
+ ],
103
+ value_names=[
104
+ "abs",
105
+ ],
106
+ )
107
+
108
+
109
+ else:
110
+ raise ValueError
111
+
112
+ generator.add_param(
113
+ key="train.output_dir",
114
+ name="",
115
+ group=-1,
116
+ values=[
117
+ "~/expdata/{env}/{mod}/{algo_name_short}".format(
118
+ env=args.env,
119
+ mod=args.mod,
120
+ algo_name_short=algo_name_short,
121
+ )
122
+ ],
123
+ )
124
+
125
+ return generator
126
+
127
+ if __name__ == "__main__":
128
+ parser = get_argparser()
129
+
130
+ args = parser.parse_args()
131
+ make_generator(args, make_generator_helper)