Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- aloha-devel/act/detr/README.md +9 -0
- aloha-devel/act/detr/setup.py +10 -0
- aloha-devel/act/detr/util/__pycache__/__init__.cpython-38.pyc +0 -0
- aloha-devel/act/inference.py +766 -0
- aloha-devel/act/policy.py +166 -0
- aloha-devel/act/test_inference.py +180 -0
- aloha-devel/act/train.py +376 -0
- aloha-devel/robomimic/__pycache__/macros.cpython-38.pyc +0 -0
- aloha-devel/robomimic/algo/__init__.py +13 -0
- aloha-devel/robomimic/algo/__pycache__/__init__.cpython-38.pyc +0 -0
- aloha-devel/robomimic/algo/__pycache__/bc.cpython-38.pyc +0 -0
- aloha-devel/robomimic/algo/act.py +247 -0
- aloha-devel/robomimic/algo/algo.py +674 -0
- aloha-devel/robomimic/algo/bc.py +899 -0
- aloha-devel/robomimic/algo/bcq.py +1022 -0
- aloha-devel/robomimic/algo/cql.py +668 -0
- aloha-devel/robomimic/algo/diffusion_policy.py +700 -0
- aloha-devel/robomimic/algo/gl.py +775 -0
- aloha-devel/robomimic/algo/hbc.py +344 -0
- aloha-devel/robomimic/algo/iql.py +428 -0
- aloha-devel/robomimic/algo/iris.py +183 -0
- aloha-devel/robomimic/algo/td3_bc.py +567 -0
- aloha-devel/robomimic/config/__pycache__/__init__.cpython-38.pyc +0 -0
- aloha-devel/robomimic/config/__pycache__/bc_config.cpython-38.pyc +0 -0
- aloha-devel/robomimic/config/__pycache__/bcq_config.cpython-38.pyc +0 -0
- aloha-devel/robomimic/config/__pycache__/diffusion_policy_config.cpython-38.pyc +0 -0
- aloha-devel/robomimic/config/__pycache__/gl_config.cpython-38.pyc +0 -0
- aloha-devel/robomimic/config/__pycache__/hbc_config.cpython-38.pyc +0 -0
- aloha-devel/robomimic/config/__pycache__/iql_config.cpython-38.pyc +0 -0
- aloha-devel/robomimic/config/__pycache__/iris_config.cpython-38.pyc +0 -0
- aloha-devel/robomimic/config/act_config.py +47 -0
- aloha-devel/robomimic/config/base_config.py +354 -0
- aloha-devel/robomimic/config/bc_config.py +110 -0
- aloha-devel/robomimic/config/cql_config.py +82 -0
- aloha-devel/robomimic/config/diffusion_policy_config.py +60 -0
- aloha-devel/robomimic/config/gl_config.py +89 -0
- aloha-devel/robomimic/config/iris_config.py +99 -0
- aloha-devel/robomimic/config/td3_bc_config.py +111 -0
- aloha-devel/robomimic/exps/templates/bcq.json +235 -0
- aloha-devel/robomimic/exps/templates/diffusion_policy.json +175 -0
- aloha-devel/robomimic/models/__init__.py +1 -0
- aloha-devel/robomimic/models/__pycache__/obs_core.cpython-38.pyc +0 -0
- aloha-devel/robomimic/models/__pycache__/obs_nets.cpython-38.pyc +0 -0
- aloha-devel/robomimic/models/__pycache__/vae_nets.cpython-38.pyc +0 -0
- aloha-devel/robomimic/models/distributions.py +123 -0
- aloha-devel/robomimic/models/obs_core.py +829 -0
- aloha-devel/robomimic/models/policy_nets.py +1570 -0
- aloha-devel/robomimic/models/transformers.py +426 -0
- aloha-devel/robomimic/models/vae_nets.py +1386 -0
- 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)
|