Repository: Shuijing725/CrowdNav_Prediction_AttnGraph Branch: main Commit: 390773137be0 Files: 111 Total size: 603.4 KB Directory structure: gitextract_a_pq4yd8/ ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── arguments.py ├── collect_data.py ├── crowd_nav/ │ ├── __init__.py │ ├── configs/ │ │ ├── __init__.py │ │ └── config.py │ └── policy/ │ ├── orca.py │ ├── policy.py │ ├── policy_factory.py │ ├── social_force.py │ └── srnn.py ├── crowd_sim/ │ ├── README.md │ ├── __init__.py │ └── envs/ │ ├── __init__.py │ ├── crowd_sim.py │ ├── crowd_sim_pred.py │ ├── crowd_sim_pred_real_gst.py │ ├── crowd_sim_var_num.py │ ├── crowd_sim_var_num_collect.py │ ├── ros_turtlebot2i_env.py │ └── utils/ │ ├── __init__.py │ ├── action.py │ ├── agent.py │ ├── human.py │ ├── info.py │ ├── recorder.py │ ├── robot.py │ └── state.py ├── gst_updated/ │ ├── .gitignore │ ├── LICENSE │ ├── README-old.md │ ├── README.md │ ├── __init__.py │ ├── requirements.txt │ ├── run/ │ │ ├── create_batch_datasets_eth_ucy.sh │ │ ├── create_batch_datasets_self_eth_ucy.sh │ │ ├── create_batch_datasets_synth.sh │ │ ├── download_datasets_models.sh │ │ ├── download_wrapper_data_model.sh │ │ └── make_dirs.sh │ ├── scripts/ │ │ ├── experiments/ │ │ │ ├── draft.py │ │ │ ├── eval.py │ │ │ ├── eval_trajnet.py │ │ │ ├── load_trajnet_test_data.py │ │ │ ├── pathhack.py │ │ │ ├── test.py │ │ │ ├── test_gst.py │ │ │ └── train.py │ │ └── wrapper/ │ │ ├── crowd_nav_interface_multi_env_parallel.py │ │ ├── crowd_nav_interface_multi_env_visualization_test_single_batch.py │ │ ├── crowd_nav_interface_parallel.py │ │ └── pathhack.py │ ├── src/ │ │ ├── gumbel_social_transformer/ │ │ │ ├── edge_selector_ghost.py │ │ │ ├── edge_selector_no_ghost.py │ │ │ ├── gumbel_social_transformer.py │ │ │ ├── mha.py │ │ │ ├── node_encoder_layer_ghost.py │ │ │ ├── node_encoder_layer_no_ghost.py │ │ │ ├── pathhack.py │ │ │ ├── st_model.py │ │ │ ├── temperature_scheduler.py │ │ │ ├── temporal_convolution_net.py │ │ │ └── utils.py │ │ ├── mgnn/ │ │ │ ├── batch_trajectories.py │ │ │ ├── trajectories.py │ │ │ ├── trajectories_sdd.py │ │ │ ├── trajectories_trajnet.py │ │ │ ├── trajectories_trajnet_testset.py │ │ │ └── utils.py │ │ └── pec_net/ │ │ ├── config/ │ │ │ └── optimal.yaml │ │ ├── sdd_trajectories.py │ │ └── social_utils.py │ └── tuning/ │ ├── 211130-train_shuijing.sh │ ├── 211203-eval_shuijing.sh │ ├── 211203-train_shuijing.sh │ └── 211209-test_shuijing.sh ├── plot.py ├── requirements.txt ├── rl/ │ ├── .gitignore │ ├── __init__.py │ ├── evaluation.py │ ├── networks/ │ │ ├── __init__.py │ │ ├── distributions.py │ │ ├── dummy_vec_env.py │ │ ├── envs.py │ │ ├── model.py │ │ ├── network_utils.py │ │ ├── selfAttn_srnn_temp_node.py │ │ ├── shmem_vec_env.py │ │ ├── srnn_model.py │ │ └── storage.py │ ├── ppo/ │ │ ├── __init__.py │ │ └── ppo.py │ └── vec_env/ │ ├── __init__.py │ ├── dummy_vec_env.py │ ├── envs.py │ ├── logger.py │ ├── running_mean_std.py │ ├── shmem_vec_env.py │ ├── tile_images.py │ ├── util.py │ ├── vec_env.py │ ├── vec_frame_stack.py │ ├── vec_normalize.py │ ├── vec_pretext_normalize.py │ └── vec_remove_dict_obs.py ├── test.py └── train.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ gst_datasets/ gst_updated/scripts/data trained_models/ .idea/ venv/ *.pyc *__pycache__* train1.py test1.py test2.py check_env.py ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. ## 2023-03-28 ### Changed - Changed instructions for Pytorch installation (moved Pytorch out of requirements.txt, and added link of official website in readme.md) - Fixed bug for 'visible_masks' in generate_ob function in crowd_sim_var_num.py and crowd_sim_pred_real_gst.py, so that the shape of observation will not throw errors when config.sim.human_num_range > 0 ## 2023-01 Initial commit ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2023 Shuijing Liu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # CrowdNav++ This repository contains the codes for our paper titled "Intention Aware Robot Crowd Navigation with Attention-Based Interaction Graph" in ICRA 2023. For more details, please refer to the [project website](https://sites.google.com/view/intention-aware-crowdnav/home) and [arXiv preprint](https://arxiv.org/abs/2203.01821). For experiment demonstrations, please refer to the [youtube video](https://www.youtube.com/watch?v=nxpxhF019VA). **[News]** - Please check out our most recent follow-up work below: - [HEIGHT: Heterogeneous Interaction Graph Transformer for Robot Navigation in Crowded and Constrained Environments](https://github.com/Shuijing725/CrowdNav_HEIGHT) - Please check out our open-sourced sim2real tutorial [here](https://github.com/Shuijing725/CrowdNav_Sim2Real_Turtlebot) - Please check out my curated paper list for robot social navigation [here](https://github.com/Shuijing725/awesome-robot-social-navigation) (It is under active development) ## Abstract We study the problem of safe and intention-aware robot navigation in dense and interactive crowds. Most previous reinforcement learning (RL) based methods fail to consider different types of interactions among all agents or ignore the intentions of people, which results in performance degradation. In this paper, we propose a novel recurrent graph neural network with attention mechanisms to capture heterogeneous interactions among agents through space and time. To encourage longsighted robot behaviors, we infer the intentions of dynamic agents by predicting their future trajectories for several timesteps. The predictions are incorporated into a model-free RL framework to prevent the robot from intruding into the intended paths of other agents. We demonstrate that our method enables the robot to achieve good navigation performance and non-invasiveness in challenging crowd navigation scenarios. We successfully transfer the policy learned in simulation to a real-world TurtleBot 2i.

## Setup 1. In a conda environment or virtual environment with Python 3.x, install the required python package ``` pip install -r requirements.txt ``` 2. Install Pytorch 1.12.1 following the instructions [here](https://pytorch.org/get-started/previous-versions/#v1121) 3. Install [OpenAI Baselines](https://github.com/openai/baselines#installation) ``` git clone https://github.com/openai/baselines.git cd baselines pip install -e . ``` 4. Install [Python-RVO2](https://github.com/sybrenstuvel/Python-RVO2) library ## Overview This repository is organized in five parts: - `crowd_nav/` folder contains configurations and policies used in the simulator. - `crowd_sim/` folder contains the simulation environment. - `gst_updated/` folder contains the code for running inference of a human trajectory predictor, named Gumbel Social Transformer (GST) [2]. - `rl/` contains the code for the RL policy networks, wrappers for the prediction network, and ppo algorithm. - `trained_models/` contains some pretrained models provided by us. Note that this repository does not include codes for training a trajectory prediction network. Please refer to from [this repo](https://github.com/tedhuang96/gst) instead. ## Run the code ### Training - Modify the configurations. 1. Environment configurations: Modify `crowd_nav/configs/config.py`. Especially, - Choice of human trajectory predictor: - Set `sim.predict_method = 'inferred'` if a learning-based GST predictor is used [2]. Please also change `pred.model_dir` to be the directory of a trained GST model. We provide two pretrained models [here](https://github.com/Shuijing725/CrowdNav_Prediction_AttnGraph/tree/main/gst_updated/results/). - Set `sim.predict_method = 'const_vel'` if constant velocity model is used. - Set `sim.predict_method = 'truth'` if ground truth predictor is used. - Set `sim.predict_method = 'none'` if you do not want to use future trajectories to change the observation and reward. - Randomization of human behaviors: If you want to randomize the ORCA humans, - set `env.randomize_attributes` to True to randomize the preferred velocity and radius of humans; - set `humans.random_goal_changing` to True to let humans randomly change goals before they arrive at their original goals. 2. PPO and network configurations: modify `arguments.py` - `env_name` (must be consistent with `sim.predict_method` in `crowd_nav/configs/config.py`): - If you use the GST predictor, set to `CrowdSimPredRealGST-v0`. - If you use the ground truth predictor or constant velocity predictor, set to `CrowdSimPred-v0`. - If you don't want to use prediction, set to `CrowdSimVarNum-v0`. - `use_self_attn`: human-human attention network will be included if set to True, else there will be no human-human attention. - `use_hr_attn`: robot-human attention network will be included if set to True, else there will be no robot-human attention. - After you change the configurations, run ``` python train.py ``` - The checkpoints and configuration files will be saved to the folder specified by `output_dir` in `arguments.py`. ### Testing Please modify the test arguments in line 20-33 of `test.py` (**Don't set the argument values in terminal!**), and run ``` python test.py ``` Note that the `config.py` and `arguments.py` in the testing folder will be loaded, instead of those in the root directory. The testing results are logged in `trained_models/your_output_dir/test/` folder, and are also printed on terminal. If you set `visualize=True` in `test.py`, you will be able to see visualizations like this: #### Test pre-trained models provided by us | Method | `--model_dir` in test.py | `--test_model` in test.py | |----------------------------------------|----------------------------------------|---------------------------| | Ours without randomized humans | `trained_models/GST_predictor_no_rand` | `41200.pt` | | ORCA without randomized humans | `trained_models/ORCA_no_rand` | `00000.pt` | | Social force without randomized humans | `trained_models/SF_no_rand` | `00000.pt` | | Ours with randomized humans | `trained_models/GST_predictor_rand` | `41665.pt` | #### Plot predicted future human positions To visualize the episodes with predicted human trajectories, as well as saving visualizations to disk, please refer to [save_slides branch](https://github.com/Shuijing725/CrowdNav_Prediction_AttnGraph/tree/save_slides). Note that the above visualization and file saving will slow down testing significantly! - Set `save_slides=True` in `test.py` and all rendered frames will be saved in a subfolder inside the `trained_models/your_output_dir/social_eval/`. ### Plot the training curves ``` python plot.py ``` Here are example learning curves of our proposed network model with GST predictor. ## Sim2Real We are happy to announce that our sim2real tutorial and code are released [here](https://github.com/Shuijing725/CrowdNav_Sim2Real_Turtlebot)! **Note:** This repo only serves as a reference point for the sim2real transfer of crowd navigation. Since there are lots of uncertainties in real-world experiments that may affect performance, we cannot guarantee that it is reproducible on all cases. ## Disclaimer 1. We only tested our code in Ubuntu with Python 3.6 and Python 3.8. The code may work on other OS or other versions of Python, but we do not have any guarantee. 2. The performance of our code can vary depending on the choice of hyperparameters and random seeds (see [this reddit post](https://www.reddit.com/r/MachineLearning/comments/rkewa3/d_what_are_your_machine_learning_superstitions/)). Unfortunately, we do not have time or resources for a thorough hyperparameter search. Thus, if your results are slightly worse than what is claimed in the paper, it is normal. To achieve the best performance, we recommend some manual hyperparameter tuning. ## Citation If you find the code or the paper useful for your research, please cite the following papers: ``` @inproceedings{liu2022intention, title={Intention Aware Robot Crowd Navigation with Attention-Based Interaction Graph}, author={Liu, Shuijing and Chang, Peixin and Huang, Zhe and Chakraborty, Neeloy and Hong, Kaiwen and Liang, Weihang and Livingston McPherson, D. and Geng, Junyi and Driggs-Campbell, Katherine}, booktitle={IEEE International Conference on Robotics and Automation (ICRA)}, year={2023}, pages={12015-12021} } @inproceedings{liu2020decentralized, title={Decentralized Structural-RNN for Robot Crowd Navigation with Deep Reinforcement Learning}, author={Liu, Shuijing and Chang, Peixin and Liang, Weihang and Chakraborty, Neeloy and Driggs-Campbell, Katherine}, booktitle={IEEE International Conference on Robotics and Automation (ICRA)}, year={2021}, pages={3517-3524} } ``` ## Credits Other contributors: [Peixin Chang](https://github.com/PeixinC) [Zhe Huang](https://github.com/tedhuang96) [Neeloy Chakraborty](https://github.com/TheNeeloy) Part of the code is based on the following repositories: [1] S. Liu, P. Chang, W. Liang, N. Chakraborty, and K. Driggs-Campbell, "Decentralized Structural-RNN for Robot Crowd Navigation with Deep Reinforcement Learning," in IEEE International Conference on Robotics and Automation (ICRA), 2019, pp. 3517-3524. (Github: https://github.com/Shuijing725/CrowdNav_DSRNN) [2] Z. Huang, R. Li, K. Shin, and K. Driggs-Campbell. "Learning Sparse Interaction Graphs of Partially Detected Pedestrians for Trajectory Prediction," in IEEE Robotics and Automation Letters, vol. 7, no. 2, pp. 1198–1205, 2022. (Github: https://github.com/tedhuang96/gst) ## Contact If you have any questions or find any bugs, please feel free to open an issue or pull request. ================================================ FILE: arguments.py ================================================ import argparse import torch def get_args(): parser = argparse.ArgumentParser(description='RL') # the saving directory for train.py parser.add_argument( '--output_dir', type=str, default='trained_models/my_model') # resume training from an existing checkpoint or not parser.add_argument( '--resume', default=False, action='store_true') # if resume = True, load from the following checkpoint parser.add_argument( '--load-path', default='trained_models/GST_predictor_non_rand/checkpoints/41200.pt', help='path of weights for resume training') parser.add_argument( '--overwrite', default=True, action='store_true', help = "whether to overwrite the output directory in training") parser.add_argument( '--num_threads', type=int, default=1, help="number of threads used for intraop parallelism on CPU") # only implement in testing parser.add_argument( '--phase', type=str, default='test') parser.add_argument( '--cuda-deterministic', action='store_true', default=False, help="sets flags for determinism when using CUDA (potentially slow!)") # only works for gpu only (although you can make it work on cpu after some minor fixes) parser.add_argument( '--no-cuda', action='store_true', default=False, help='disables CUDA training') parser.add_argument( '--seed', type=int, default=425, help='random seed (default: 1)') parser.add_argument( '--num-processes', type=int, default=16, help='how many training processes to use (default: 16)') parser.add_argument( '--num-mini-batch', type=int, default=2, help='number of batches for ppo (default: 32)') parser.add_argument( '--num-steps', type=int, default=30, help='number of forward steps in A2C (default: 5)') parser.add_argument( '--recurrent-policy', action='store_true', default=True, help='use a recurrent policy') parser.add_argument( '--ppo-epoch', type=int, default=5, help='number of ppo epochs (default: 4)') parser.add_argument( '--clip-param', type=float, default=0.2, help='ppo clip parameter (default: 0.2)') parser.add_argument( '--value-loss-coef', type=float, default=0.5, help='value loss coefficient (default: 0.5)') parser.add_argument( '--entropy-coef', type=float, default=0.0, help='entropy term coefficient (default: 0.01)') parser.add_argument( '--lr', type=float, default=4e-5, help='learning rate (default: 7e-4)') parser.add_argument( '--eps', type=float, default=1e-5, help='RMSprop optimizer epsilon (default: 1e-5)') parser.add_argument( '--alpha', type=float, default=0.99, help='RMSprop optimizer apha (default: 0.99)') parser.add_argument( '--gamma', type=float, default=0.99, help='discount factor for rewards (default: 0.99)') parser.add_argument( '--max-grad-norm', type=float, default=0.5, help='max norm of gradients (default: 0.5)') # 10e6 for holonomic, 20e6 for unicycle parser.add_argument( '--num-env-steps', type=int, default=20e6, help='number of environment steps to train (default: 10e6)') # True for unicycle, False for holonomic parser.add_argument( '--use-linear-lr-decay', action='store_true', default=False, help='use a linear schedule on the learning rate') parser.add_argument( '--algo', default='ppo', help='algorithm to use: a2c | ppo | acktr') parser.add_argument( '--save-interval', type=int, default=200, help='save interval, one save per n updates (default: 100)') parser.add_argument( '--use-gae', action='store_true', default=True, help='use generalized advantage estimation') parser.add_argument( '--gae-lambda', type=float, default=0.95, help='gae lambda parameter (default: 0.95)') parser.add_argument( '--log-interval', type=int, default=20, help='log interval, one log per n updates (default: 10)') parser.add_argument( '--use-proper-time-limits', action='store_true', default=False, help='compute returns taking into account time limits') # for srnn only # RNN size parser.add_argument('--human_node_rnn_size', type=int, default=128, help='Size of Human Node RNN hidden state') parser.add_argument('--human_human_edge_rnn_size', type=int, default=256, help='Size of Human Human Edge RNN hidden state') parser.add_argument( '--aux-loss', action='store_true', default=False, help='auxiliary loss on human nodes outputs') # Input and output size parser.add_argument('--human_node_input_size', type=int, default=3, help='Dimension of the node features') parser.add_argument('--human_human_edge_input_size', type=int, default=2, help='Dimension of the edge features') parser.add_argument('--human_node_output_size', type=int, default=256, help='Dimension of the node output') # Embedding size parser.add_argument('--human_node_embedding_size', type=int, default=64, help='Embedding size of node features') parser.add_argument('--human_human_edge_embedding_size', type=int, default=64, help='Embedding size of edge features') # Attention vector dimension parser.add_argument('--attention_size', type=int, default=64, help='Attention size') # Sequence length parser.add_argument('--seq_length', type=int, default=30, help='Sequence length') # use self attn in human states or not parser.add_argument('--use_self_attn', type=bool, default=True, help='Attention size') # use attn between humans and robots or not (todo: implment this in network models) parser.add_argument('--use_hr_attn', type=bool, default=True, help='Attention size') # No prediction: for orca, sf, old_srnn, selfAttn_srnn_noPred ablation: 'CrowdSimVarNum-v0', # for constant velocity Pred, ground truth Pred: 'CrowdSimPred-v0' # gst pred: 'CrowdSimPredRealGST-v0' parser.add_argument( '--env-name', default='CrowdSimPredRealGST-v0', help='name of the environment') # sort all humans and squeeze them to the front or not parser.add_argument('--sort_humans', type=bool, default=True) args = parser.parse_args() args.cuda = not args.no_cuda and torch.cuda.is_available() assert args.algo in ['a2c', 'ppo', 'acktr'] if args.recurrent_policy: assert args.algo in ['a2c', 'ppo'], \ 'Recurrent policy is not implemented for ACKTR' return args ================================================ FILE: collect_data.py ================================================ import matplotlib.pyplot as plt import torch import copy import os import numpy as np from tqdm import trange from crowd_nav.configs.config import Config from rl.networks.envs import make_vec_envs from crowd_sim.envs import * # train_data: True if collect training data, False if collect testing data def collectData(device, train_data, config): # set robot policy to orca config.robot.policy = 'orca' env_name = 'CrowdSimVarNumCollect-v0' # for render env_num = 1 if config.data.render else config.data.num_processes if config.data.render: fig, ax = plt.subplots(figsize=(7, 7)) ax.set_xlim(-10, 10) ax.set_ylim(-10, 10) ax.set_xlabel('x(m)', fontsize=16) ax.set_ylabel('y(m)', fontsize=16) plt.ion() plt.show() else: ax = None # create parallel envs seed = np.random.randint(0, np.iinfo(np.uint32).max) envs = make_vec_envs(env_name, seed, env_num, config.reward.gamma, None, device, allow_early_resets=True, config=config, ax=ax, wrap_pytorch=False) # collect data for pretext training data = [] # list for all data collected for i in range(env_num): data.append([]) obs = envs.reset() # 1 epoch -> 1 file # todo: add pretext arguments to config.py # make one prediction every "pred_interval" simulation steps pred_interval = config.data.pred_timestep // config.env.time_step tot_steps = int(config.data.tot_steps * pred_interval) for step in trange(tot_steps): if config.data.render: envs.render() if step % pred_interval == 0: # append a single data one by one for i in range(env_num): non_empty_obs = obs['pred_info'][i][np.logical_not(np.isinf(obs['pred_info'][i, :, -1]))] non_empty_obs = non_empty_obs.reshape((-1, 4)).tolist() if non_empty_obs: data[i].extend(copy.deepcopy(non_empty_obs)) action = np.zeros((env_num, 2)) # action is is dummy action! obs, rew, done, info = envs.step(action) # save observations as pickle files filePath = os.path.join(config.data.data_save_dir, 'train') if train_data \ else os.path.join(config.data.data_save_dir, 'test') if not os.path.isdir(filePath): os.makedirs(filePath) for i in range(env_num): with open(os.path.join(filePath, str(i)+'.txt'), 'w') as f: for item in data[i]: item = str(item[0]) + '\t' + str(item[1]) + '\t' + str(item[2]) + '\t' + str(item[3]) f.write("%s\n" % item) envs.close() if __name__ == '__main__': config = Config() device = torch.device("cuda") collectData(device, config.data.collect_train_data, config) ================================================ FILE: crowd_nav/__init__.py ================================================ ================================================ FILE: crowd_nav/configs/__init__.py ================================================ ================================================ FILE: crowd_nav/configs/config.py ================================================ import numpy as np from arguments import get_args class BaseConfig(object): def __init__(self): pass class Config(object): # for now, import all args from arguments.py args = get_args() training = BaseConfig() training.device = "cuda:0" if args.cuda else "cpu" # general configs for OpenAI gym env env = BaseConfig() env.time_limit = 50 env.time_step = 0.25 env.val_size = 100 env.test_size = 500 # if randomize human behaviors, set to True, else set to False env.randomize_attributes = True env.num_processes = args.num_processes # record robot states and actions an episode for system identification in sim2real env.record = False env.load_act = False # config for reward function reward = BaseConfig() reward.success_reward = 10 reward.collision_penalty = -20 # discomfort distance reward.discomfort_dist = 0.25 reward.discomfort_penalty_factor = 10 reward.gamma = 0.99 # config for simulation sim = BaseConfig() sim.circle_radius = 6 * np.sqrt(2) sim.arena_size = 6 sim.human_num = 20 # actual human num in each timestep, in [human_num-human_num_range, human_num+human_num_range] sim.human_num_range = 0 sim.predict_steps = 5 # 'const_vel': constant velocity model, # 'truth': ground truth future traj (with info in robot's fov) # 'inferred': inferred future traj from GST network # 'none': no prediction sim.predict_method = 'inferred' # render the simulation during training or not sim.render = False # for save_traj only render_traj = False save_slides = False save_path = None # whether wrap the vec env with VecPretextNormalize class # = True only if we are using a network for human trajectory prediction (sim.predict_method = 'inferred') if sim.predict_method == 'inferred': env.use_wrapper = True else: env.use_wrapper = False # human config humans = BaseConfig() humans.visible = True # orca or social_force for now humans.policy = "orca" humans.radius = 0.3 humans.v_pref = 1 humans.sensor = "coordinates" # FOV = this values * PI humans.FOV = 2. # a human may change its goal before it reaches its old goal # if randomize human behaviors, set to True, else set to False humans.random_goal_changing = True humans.goal_change_chance = 0.5 # a human may change its goal after it reaches its old goal humans.end_goal_changing = True humans.end_goal_change_chance = 1.0 # a human may change its radius and/or v_pref after it reaches its current goal humans.random_radii = False humans.random_v_pref = False # one human may have a random chance to be blind to other agents at every time step humans.random_unobservability = False humans.unobservable_chance = 0.3 humans.random_policy_changing = False # robot config robot = BaseConfig() # whether robot is visible to humans (whether humans respond to the robot's motion) robot.visible = False # For baseline: srnn; our method: selfAttn_merge_srnn robot.policy = 'selfAttn_merge_srnn' robot.radius = 0.3 robot.v_pref = 1 robot.sensor = "coordinates" # FOV = this values * PI robot.FOV = 2 # radius of perception range robot.sensor_range = 5 # action space of the robot action_space = BaseConfig() # holonomic or unicycle action_space.kinematics = "holonomic" # config for ORCA orca = BaseConfig() orca.neighbor_dist = 10 orca.safety_space = 0.15 orca.time_horizon = 5 orca.time_horizon_obst = 5 # config for social force sf = BaseConfig() sf.A = 2. sf.B = 1 sf.KI = 1 # config for data collection for training the GST predictor data = BaseConfig() data.tot_steps = 40000 data.render = False data.collect_train_data = False data.num_processes = 5 data.data_save_dir = 'gst_updated/datasets/orca_20humans_no_rand' # number of seconds between each position in traj pred model data.pred_timestep = 0.25 # config for the GST predictor pred = BaseConfig() # see 'gst_updated/results/README.md' for how to set this variable # If randomized humans: gst_updated/results/100-gumbel_social_transformer-faster_lstm-lr_0.001-init_temp_0.5-edge_head_0-ebd_64-snl_1-snh_8-seed_1000_rand/sj # else: gst_updated/results/100-gumbel_social_transformer-faster_lstm-lr_0.001-init_temp_0.5-edge_head_0-ebd_64-snl_1-snh_8-seed_1000/sj pred.model_dir = 'gst_updated/results/100-gumbel_social_transformer-faster_lstm-lr_0.001-init_temp_0.5-edge_head_0-ebd_64-snl_1-snh_8-seed_1000_rand/sj' # LIDAR config lidar = BaseConfig() # angular resolution (offset angle between neighboring rays) in degrees lidar.angular_res = 5 # range in meters lidar.range = 10 # config for sim2real sim2real = BaseConfig() # use dummy robot and human states or not sim2real.use_dummy_detect = True sim2real.record = False sim2real.load_act = False sim2real.ROSStepInterval = 0.03 sim2real.fixed_time_interval = 0.1 sim2real.use_fixed_time_interval = True if sim.predict_method == 'inferred' and env.use_wrapper == False: raise ValueError("If using inferred prediction, you must wrap the envs!") if sim.predict_method != 'inferred' and env.use_wrapper: raise ValueError("If not using inferred prediction, you must NOT wrap the envs!") ================================================ FILE: crowd_nav/policy/orca.py ================================================ import numpy as np import rvo2 from crowd_nav.policy.policy import Policy from crowd_sim.envs.utils.action import ActionXY class ORCA(Policy): def __init__(self, config): """ timeStep The time step of the simulation. Must be positive. neighborDist The default maximum distance (center point to center point) to other agents a new agent takes into account in the navigation. The larger this number, the longer the running time of the simulation. If the number is too low, the simulation will not be safe. Must be non-negative. maxNeighbors The default maximum number of other agents a new agent takes into account in the navigation. The larger this number, the longer the running time of the simulation. If the number is too low, the simulation will not be safe. timeHorizon The default minimal amount of time for which a new agent's velocities that are computed by the simulation are safe with respect to other agents. The larger this number, the sooner an agent will respond to the presence of other agents, but the less freedom the agent has in choosing its velocities. Must be positive. timeHorizonObst The default minimal amount of time for which a new agent's velocities that are computed by the simulation are safe with respect to obstacles. The larger this number, the sooner an agent will respond to the presence of obstacles, but the less freedom the agent has in choosing its velocities. Must be positive. radius The default radius of a new agent. Must be non-negative. maxSpeed The default maximum speed of a new agent. Must be non-negative. velocity The default initial two-dimensional linear velocity of a new agent (optional). ORCA first uses neighborDist and maxNeighbors to find neighbors that need to be taken into account. Here set them to be large enough so that all agents will be considered as neighbors. Time_horizon should be set that at least it's safe for one time step In this work, obstacles are not considered. So the value of time_horizon_obst doesn't matter. """ super().__init__(config) self.name = 'ORCA' self.max_neighbors = None self.radius = None self.max_speed = 1 # the ego agent assumes that all other agents have this max speed self.sim = None self.safety_space = self.config.orca.safety_space def predict(self, state): """ Create a rvo2 simulation at each time step and run one step Python-RVO2 API: https://github.com/sybrenstuvel/Python-RVO2/blob/master/src/rvo2.pyx How simulation is done in RVO2: https://github.com/sybrenstuvel/Python-RVO2/blob/master/src/Agent.cpp Agent doesn't stop moving after it reaches the goal, because once it stops moving, the reciprocal rule is broken :param state: :return: """ self_state = state.self_state # max number of humans = current number of humans self.max_neighbors = len(state.human_states) self.radius = state.self_state.radius params = self.config.orca.neighbor_dist, self.max_neighbors, self.config.orca.time_horizon, self.config.orca.time_horizon_obst if self.sim is not None and self.sim.getNumAgents() != len(state.human_states) + 1: del self.sim self.sim = None if self.sim is None: self.sim = rvo2.PyRVOSimulator(self.time_step, *params, self.radius, self.max_speed) self.sim.addAgent((self_state.px, self_state.py), *params, self_state.radius + 0.01 + self.safety_space, self_state.v_pref, (self_state.vx, self_state.vy)) for human_state in state.human_states: self.sim.addAgent((human_state.px, human_state.py), *params, human_state.radius + 0.01 + self.config.orca.safety_space, self.max_speed, (human_state.vx, human_state.vy)) else: self.sim.setAgentPosition(0, (self_state.px, self_state.py)) self.sim.setAgentVelocity(0, (self_state.vx, self_state.vy)) for i, human_state in enumerate(state.human_states): self.sim.setAgentPosition(i + 1, (human_state.px, human_state.py)) self.sim.setAgentVelocity(i + 1, (human_state.vx, human_state.vy)) # Set the preferred velocity to be a vector of unit magnitude (speed) in the direction of the goal. velocity = np.array((self_state.gx - self_state.px, self_state.gy - self_state.py)) speed = np.linalg.norm(velocity) pref_vel = velocity / speed if speed > 1 else velocity # Perturb a little to avoid deadlocks due to perfect symmetry. # perturb_angle = np.random.random() * 2 * np.pi # perturb_dist = np.random.random() * 0.01 # perturb_vel = np.array((np.cos(perturb_angle), np.sin(perturb_angle))) * perturb_dist # pref_vel += perturb_vel self.sim.setAgentPrefVelocity(0, tuple(pref_vel)) for i, human_state in enumerate(state.human_states): # unknown goal position of other humans self.sim.setAgentPrefVelocity(i + 1, (0, 0)) self.sim.doStep() action = ActionXY(*self.sim.getAgentVelocity(0)) self.last_state = state return action ================================================ FILE: crowd_nav/policy/policy.py ================================================ import abc import numpy as np class Policy(object): def __init__(self, config): """ Base class for all policies, has an abstract method predict(). """ self.trainable = False self.phase = None self.model = None self.device = None self.last_state = None self.time_step = None # if agent is assumed to know the dynamics of real world self.env = None self.config = config @abc.abstractmethod def predict(self, state): """ Policy takes state as input and output an action """ return @staticmethod def reach_destination(state): self_state = state.self_state if np.linalg.norm((self_state.py - self_state.gy, self_state.px - self_state.gx)) < self_state.radius: return True else: return False ================================================ FILE: crowd_nav/policy/policy_factory.py ================================================ policy_factory = dict() def none_policy(): return None from crowd_nav.policy.orca import ORCA from crowd_nav.policy.social_force import SOCIAL_FORCE from crowd_nav.policy.srnn import SRNN, selfAttn_merge_SRNN policy_factory['orca'] = ORCA policy_factory['none'] = none_policy policy_factory['social_force'] = SOCIAL_FORCE policy_factory['srnn'] = SRNN policy_factory['selfAttn_merge_srnn'] = selfAttn_merge_SRNN ================================================ FILE: crowd_nav/policy/social_force.py ================================================ import numpy as np from crowd_nav.policy.policy import Policy from crowd_sim.envs.utils.action import ActionXY class SOCIAL_FORCE(Policy): def __init__(self, config): super().__init__(config) self.name = 'social_force' def predict(self, state): """ Produce action for agent with circular specification of social force model. """ # Pull force to goal delta_x = state.self_state.gx - state.self_state.px delta_y = state.self_state.gy - state.self_state.py dist_to_goal = np.sqrt(delta_x**2 + delta_y**2) desired_vx = (delta_x / dist_to_goal) * state.self_state.v_pref desired_vy = (delta_y / dist_to_goal) * state.self_state.v_pref KI = self.config.sf.KI # Inverse of relocation time K_i curr_delta_vx = KI * (desired_vx - state.self_state.vx) curr_delta_vy = KI * (desired_vy - state.self_state.vy) # Push force(s) from other agents A = self.config.sf.A # Other observations' interaction strength: 1.5 B = self.config.sf.B # Other observations' interaction range: 1.0 interaction_vx = 0 interaction_vy = 0 for other_human_state in state.human_states: delta_x = state.self_state.px - other_human_state.px delta_y = state.self_state.py - other_human_state.py dist_to_human = np.sqrt(delta_x**2 + delta_y**2) interaction_vx += A * np.exp((state.self_state.radius + other_human_state.radius - dist_to_human) / B) * (delta_x / dist_to_human) interaction_vy += A * np.exp((state.self_state.radius + other_human_state.radius - dist_to_human) / B) * (delta_y / dist_to_human) # Sum of push & pull forces total_delta_vx = (curr_delta_vx + interaction_vx) * self.config.env.time_step total_delta_vy = (curr_delta_vy + interaction_vy) * self.config.env.time_step # clip the speed so that sqrt(vx^2 + vy^2) <= v_pref new_vx = state.self_state.vx + total_delta_vx new_vy = state.self_state.vy + total_delta_vy act_norm = np.linalg.norm([new_vx, new_vy]) if act_norm > state.self_state.v_pref: return ActionXY(new_vx / act_norm * state.self_state.v_pref, new_vy / act_norm * state.self_state.v_pref) else: return ActionXY(new_vx, new_vy) ================================================ FILE: crowd_nav/policy/srnn.py ================================================ from crowd_nav.policy.policy import Policy import numpy as np from crowd_sim.envs.utils.action import ActionRot, ActionXY class SRNN(Policy): def __init__(self, config): super().__init__(config) self.time_step = self.config.env.time_step # Todo: is this needed? self.name = 'srnn' self.trainable = True self.multiagent_training = True # clip the self.raw_action and return the clipped action def clip_action(self, raw_action, v_pref): """ Input state is the joint state of robot concatenated by the observable state of other agents To predict the best action, agent samples actions and propagates one step to see how good the next state is thus the reward function is needed """ # quantize the action holonomic = True if self.config.action_space.kinematics == 'holonomic' else False # clip the action if holonomic: act_norm = np.linalg.norm(raw_action) if act_norm > v_pref: raw_action[0] = raw_action[0] / act_norm * v_pref raw_action[1] = raw_action[1] / act_norm * v_pref return ActionXY(raw_action[0], raw_action[1]) else: # for sim2real # old value: -0.1, 0.1 raw_action[0] = np.clip(raw_action[0], -0.1, 0.087) # action[0] is change of v # raw[0, 1] = np.clip(raw[0, 1], -0.25, 0.25) # action[1] is change of w # raw[0, 0] = np.clip(raw[0, 0], -state.self_state.v_pref, state.self_state.v_pref) # action[0] is v # old value: -0.1, 0.1 # 0.073 raw_action[1] = np.clip(raw_action[1], -0.06, 0.06) # action[1] is change of theta return ActionRot(raw_action[0], raw_action[1]) class selfAttn_merge_SRNN(SRNN): def __init__(self, config): super().__init__(config) self.name = 'selfAttn_merge_srnn' ================================================ FILE: crowd_sim/README.md ================================================ # Simulation Framework ## Environment The environment contains n+1 agents. N of them are humans controlled by certain a fixed policy. The other is robot and it's controlled by a trainable policy. The environment is built on top of OpenAI gym library, and has implemented two abstract methods. * reset(): the environment will reset positions for all the agents and return observation for robot. Observation for one agent is the observable states of all other agents. * step(action): taking action of the robot as input, the environment computes observation for each agent and call agent.act(observation) to get actions of agents. Then environment detects whether there is a collision between agents. If not, the states of agents will be updated. Then observation, reward, done, info will be returned. ## Agent Agent is a base class, and has two derived class of human and robot. Agent class holds all the physical properties of an agent, including position, velocity, orientation, policy and etc. * visibility: humans and robot can be set to be visible or invisible * sensor: can be either visual input or coordinate input * kinematics: can be either holonomic (move in any direction) or unicycle (has rotation constraints) * act(observation): transform observation to state and pass it to policy ## Policy Policy takes state as input and output an action. Current available policies: * ORCA: model other agents as velocity obstacles to find optimal collision-free velocities under reciprocal assumption * Social force: models the interactions in crowds using attractive and repulsive forces * DS-RNN: our method ## State There are multiple definition of states in different cases. The state of an agent representing all the knowledge of environments is defined as JointState, and it's different from the state of the whole environment. * ObservableState: position, velocity, radius of one agent * FullState: position, velocity, radius, goal position, preferred velocity, rotation of one agent * JoinState: concatenation of one agent's full state and all other agents' observable states ## Action There are two types of actions depending on what kinematics constraint the agent has. * ActionXY: (vx, vy) if kinematics == 'holonomic' * ActionRot: (velocity, rotation angle) if kinematics == 'unicycle' ================================================ FILE: crowd_sim/__init__.py ================================================ from gym.envs.registration import register register( id='CrowdSim-v0', entry_point='crowd_sim.envs:CrowdSim', ) register( id='CrowdSimPred-v0', entry_point='crowd_sim.envs:CrowdSimPred', ) register( id='CrowdSimVarNum-v0', entry_point='crowd_sim.envs:CrowdSimVarNum', ) register( id='CrowdSimVarNumCollect-v0', entry_point='crowd_sim.envs:CrowdSimVarNumCollect', ) register( id='CrowdSimPredRealGST-v0', entry_point='crowd_sim.envs:CrowdSimPredRealGST', ) register( id='rosTurtlebot2iEnv-v0', entry_point='crowd_sim.envs.ros_turtlebot2i_env:rosTurtlebot2iEnv', ) ================================================ FILE: crowd_sim/envs/__init__.py ================================================ from .crowd_sim import CrowdSim from .crowd_sim_pred import CrowdSimPred from .crowd_sim_var_num import CrowdSimVarNum from .crowd_sim_var_num_collect import CrowdSimVarNumCollect from .crowd_sim_pred_real_gst import CrowdSimPredRealGST ================================================ FILE: crowd_sim/envs/crowd_sim.py ================================================ import logging import gym import numpy as np import rvo2 import random import copy from numpy.linalg import norm from crowd_sim.envs.utils.human import Human from crowd_sim.envs.utils.robot import Robot from crowd_sim.envs.utils.info import * from crowd_nav.policy.orca import ORCA from crowd_sim.envs.utils.state import * from crowd_sim.envs.utils.action import ActionRot, ActionXY from crowd_sim.envs.utils.recorder import Recoder class CrowdSim(gym.Env): """ A base environment treat it as an abstract class, all other environments inherit from this one """ def __init__(self): """ Movement simulation for n+1 agents Agent can either be human or robot. humans are controlled by a unknown and fixed policy. robot is controlled by a known and learnable policy. """ self.time_limit = None self.time_step = None self.robot = None # a Robot instance representing the robot self.humans = None # a list of Human instances, representing all humans in the environment self.global_time = None self.step_counter=0 # reward function self.success_reward = None self.collision_penalty = None self.discomfort_dist = None self.discomfort_penalty_factor = None # simulation configuration self.config = None self.case_capacity = None self.case_size = None self.case_counter = None self.randomize_attributes = None self.circle_radius = None self.human_num = None self.action_space=None self.observation_space=None # limit FOV self.robot_fov = None self.human_fov = None self.dummy_human = None self.dummy_robot = None #seed self.thisSeed=None # the seed will be set when the env is created #nenv self.nenv=None # the number of env will be set when the env is created. # Because the human crossing cases are controlled by random seed, we will calculate unique random seed for each # parallel env. self.phase=None # set the phase to be train, val or test self.test_case=None # the test case ID, which will be used to calculate a seed to generate a human crossing case # for render self.render_axis=None self.humans = [] self.potential = None self.desiredVelocity = [0.0, 0.0] self.last_left = 0. self.last_right = 0. def configure(self, config): """ read the config to the environment variables """ self.config = config self.time_limit = config.env.time_limit self.time_step = config.env.time_step self.randomize_attributes = config.env.randomize_attributes self.success_reward = config.reward.success_reward self.collision_penalty = config.reward.collision_penalty self.discomfort_dist = config.reward.discomfort_dist self.discomfort_penalty_factor = config.reward.discomfort_penalty_factor self.case_capacity = {'train': np.iinfo(np.uint32).max - 2000, 'val': 1000, 'test': 1000} self.case_size = {'train': np.iinfo(np.uint32).max - 2000, 'val': self.config.env.val_size, 'test': self.config.env.test_size} self.circle_radius = config.sim.circle_radius self.human_num = config.sim.human_num self.arena_size = config.sim.arena_size self.case_counter = {'train': 0, 'test': 0, 'val': 0} logging.info('human number: {}'.format(self.human_num)) if self.randomize_attributes: logging.info("Randomize human's radius and preferred speed") else: logging.info("Not randomize human's radius and preferred speed") logging.info('Circle width: {}'.format(self.circle_radius)) self.robot_fov = np.pi * config.robot.FOV self.human_fov = np.pi * config.humans.FOV logging.info('robot FOV %f', self.robot_fov) logging.info('humans FOV %f', self.human_fov) # set dummy human and dummy robot # dummy humans, used if any human is not in view of other agents self.dummy_human = Human(self.config, 'humans') # if a human is not in view, set its state to (px = 100, py = 100, vx = 0, vy = 0, theta = 0, radius = 0) self.dummy_human.set(7, 7, 7, 7, 0, 0, 0) # (7, 7, 7, 7, 0, 0, 0) self.dummy_human.time_step = config.env.time_step self.dummy_robot = Robot(self.config, 'robot') self.dummy_robot.set(7, 7, 7, 7, 0, 0, 0) self.dummy_robot.time_step = config.env.time_step self.dummy_robot.kinematics = 'holonomic' self.dummy_robot.policy = ORCA(config) self.r = self.config.humans.radius # configure randomized goal changing of humans midway through episode self.random_goal_changing = config.humans.random_goal_changing if self.random_goal_changing: self.goal_change_chance = config.humans.goal_change_chance # configure randomized goal changing of humans after reaching their respective goals self.end_goal_changing = config.humans.end_goal_changing if self.end_goal_changing: self.end_goal_change_chance = config.humans.end_goal_change_chance self.last_human_states = np.zeros((self.human_num, 5)) self.predict_steps = config.sim.predict_steps self.human_num_range = config.sim.human_num_range assert self.human_num > self.human_num_range self.max_human_num = self.human_num + self.human_num_range self.min_human_num = self.human_num - self.human_num_range # for sim2real dynamics check self.record=config.sim2real.record self.load_act=config.sim2real.load_act self.ROSStepInterval=config.sim2real.ROSStepInterval self.fixed_time_interval=config.sim2real.fixed_time_interval self.use_fixed_time_interval = config.sim2real.use_fixed_time_interval if self.record: self.episodeRecoder=Recoder() self.load_act=config.sim2real.load_act if self.load_act: self.episodeRecoder.loadActions() # use dummy robot and human states or use detected states from sensors self.use_dummy_detect = config.sim2real.use_dummy_detect # prediction period / control (or simulation) period self.pred_interval = int(config.data.pred_timestep // config.env.time_step) self.buffer_len = self.predict_steps * self.pred_interval # set robot for this envs rob_RL = Robot(config, 'robot') self.set_robot(rob_RL) def set_robot(self, robot): raise NotImplementedError def generate_random_human_position(self, human_num): """ Calls generate_circle_crossing_human function to generate a certain number of random humans :param human_num: the total number of humans to be generated :return: None """ # initial min separation distance to avoid danger penalty at beginning for i in range(human_num): self.humans.append(self.generate_circle_crossing_human()) def generate_circle_crossing_human(self): """Generate a human: generate start position on a circle, goal position is at the opposite side""" human = Human(self.config, 'humans') if self.randomize_attributes: human.sample_random_attributes() while True: angle = np.random.random() * np.pi * 2 # add some noise to simulate all the possible cases robot could meet with human v_pref = 1.0 if human.v_pref == 0 else human.v_pref px_noise = (np.random.random() - 0.5) * v_pref py_noise = (np.random.random() - 0.5) * v_pref px = self.circle_radius * np.cos(angle) + px_noise py = self.circle_radius * np.sin(angle) + py_noise collide = False for i, agent in enumerate([self.robot] + self.humans): # keep human at least 3 meters away from robot if self.robot.kinematics == 'unicycle' and i == 0: min_dist = self.circle_radius / 2 # Todo: if circle_radius <= 4, it will get stuck here else: min_dist = human.radius + agent.radius + self.discomfort_dist if norm((px - agent.px, py - agent.py)) < min_dist or \ norm((px - agent.gx, py - agent.gy)) < min_dist: collide = True break if not collide: break # px = np.random.uniform(-6, 6) # py = np.random.uniform(-3, 3.5) # human.set(px, py, px, py, 0, 0, 0) human.set(px, py, -px, -py, 0, 0, 0) return human # update the robot belief of human states # if a human is visible, its state is updated to its current ground truth state # else we assume it keeps going in a straight line with last observed velocity def update_last_human_states(self, human_visibility, reset): """ update the self.last_human_states array human_visibility: list of booleans returned by get_human_in_fov (e.x. [T, F, F, T, F]) reset: True if this function is called by reset, False if called by step :return: """ # keep the order of 5 humans at each timestep for i in range(self.human_num): if human_visibility[i]: humanS = np.array(self.humans[i].get_observable_state_list()) self.last_human_states[i, :] = humanS else: if reset: humanS = np.array([15., 15., 0., 0., 0.3]) self.last_human_states[i, :] = humanS else: # Plan A: linear approximation of human's next position px, py, vx, vy, r = self.last_human_states[i, :] px = px + vx * self.time_step py = py + vy * self.time_step self.last_human_states[i, :] = np.array([px, py, vx, vy, r]) # Plan B: assume the human doesn't move, use last observation # self.last_human_states[i, :] = np.array([px, py, 0., 0., r]) # Plan C: put invisible humans in a dummy position # humanS = np.array([15., 15., 0., 0., 0.3]) # self.last_human_states[i, :] = humanS # return the ground truth locations of all humans def get_true_human_states(self): true_human_states = np.zeros((self.human_num, 2)) for i in range(self.human_num): humanS = np.array(self.humans[i].get_observable_state_list()) true_human_states[i, :] = humanS[:2] return true_human_states # set robot initial state and generate all humans for reset function # for crowd nav: human_num == self.human_num # for leader follower: human_num = self.human_num - 1 def generate_robot_humans(self, phase, human_num=None): if human_num is None: human_num = self.human_num if self.robot.kinematics == 'unicycle': angle = np.random.uniform(0, np.pi * 2) px = self.circle_radius * np.cos(angle) py = self.circle_radius * np.sin(angle) while True: gx, gy = np.random.uniform(-self.circle_radius, self.circle_radius, 2) if np.linalg.norm([px - gx, py - gy]) >= 6: # 1 was 6 break self.robot.set(px, py, gx, gy, 0, 0, np.random.uniform(0, 2*np.pi)) # randomize init orientation # randomize starting position and goal position else: while True: px, py, gx, gy = np.random.uniform(-self.circle_radius, self.circle_radius, 4) if np.linalg.norm([px - gx, py - gy]) >= 6: break self.robot.set(px, py, gx, gy, 0, 0, np.pi/2) # generate humans self.generate_random_human_position(human_num=human_num) def smooth_action(self, action): """ mimic the dynamics of Turtlebot2i for sim2real """ # if action.r is delta theta w = action.r / self.time_step # if action.r is w # w = action.r beta = 0.1 left = (2 * action.v - 0.23 * w) / (2 * 0.035) right = (2 * action.v + 0.23 * w) / (2 * 0.035) left = np.clip(left, -17.5, 17.5) right = np.clip(right, -17.5, 17.5) # print('Before: left:', left, 'right:', right) if self.phase == 'test': left = (1. - beta) * self.last_left + beta * left right = (1. - beta) * self.last_right + beta * right self.last_left = copy.deepcopy(left) self.last_right = copy.deepcopy(right) # subtract a noisy amount of delay from wheel speeds to simulate the delay in tb2 # do this in the last step because this happens after we send action commands to tb2 if left > 0: adjust_left = left - np.random.normal(loc=1.8, scale=0.15) left = max(0., adjust_left) else: adjust_left = left + np.random.normal(loc=1.8, scale=0.15) left = min(0., adjust_left) if right > 0: adjust_right = right - np.random.normal(loc=1.8, scale=0.15) right = max(0., adjust_right) else: adjust_right = right + np.random.normal(loc=1.8, scale=0.15) right = min(0., adjust_right) if self.record: self.episodeRecoder.wheelVelList.append([left, right]) # print('After: left:', left, 'right:', right) v = 0.035 / 2 * (left + right) r = 0.035 / 0.23 * (right - left) * self.time_step return ActionRot(v, r) def reset(self, phase='train', test_case=None): """ Reset the environment :return: """ if self.phase is not None: phase = self.phase if self.test_case is not None: test_case=self.test_case if self.robot is None: raise AttributeError('robot has to be set!') assert phase in ['train', 'val', 'test'] if test_case is not None: self.case_counter[phase] = test_case # test case is passed in to calculate specific seed to generate case self.global_time = 0 self.step_counter=0 self.humans = [] # train, val, and test phase should start with different seed. # case capacity: the maximum number for train(max possible int -2000), val(1000), and test(1000) # val start from seed=0, test start from seed=case_capacity['val']=1000 # train start from self.case_capacity['val'] + self.case_capacity['test']=2000 counter_offset = {'train': self.case_capacity['val'] + self.case_capacity['test'], 'val': 0, 'test': self.case_capacity['val']} np.random.seed(counter_offset[phase] + self.case_counter[phase] + self.thisSeed) self.generate_robot_humans(phase) for agent in [self.robot] + self.humans: agent.time_step = self.time_step agent.policy.time_step = self.time_step # case size is used to make sure that the case_counter is always between 0 and case_size[phase] self.case_counter[phase] = (self.case_counter[phase] + int(1*self.nenv)) % self.case_size[phase] # get current observation ob = self.generate_ob(reset=True) # initialize potential self.potential = -abs(np.linalg.norm(np.array([self.robot.px, self.robot.py]) - np.array([self.robot.gx, self.robot.gy]))) return ob # Update the humans' end goals in the environment # Produces valid end goals for each human def update_human_goals_randomly(self): # Update humans' goals randomly for human in self.humans: if human.isObstacle or human.v_pref == 0: continue if np.random.random() <= self.goal_change_chance: humans_copy = [] for h in self.humans: if h != human: humans_copy.append(h) # Produce valid goal for human in case of circle setting while True: angle = np.random.random() * np.pi * 2 # add some noise to simulate all the possible cases robot could meet with human v_pref = 1.0 if human.v_pref == 0 else human.v_pref gx_noise = (np.random.random() - 0.5) * v_pref gy_noise = (np.random.random() - 0.5) * v_pref gx = self.circle_radius * np.cos(angle) + gx_noise gy = self.circle_radius * np.sin(angle) + gy_noise collide = False for agent in [self.robot] + humans_copy: min_dist = human.radius + agent.radius + self.discomfort_dist if norm((gx - agent.px, gy - agent.py)) < min_dist or \ norm((gx - agent.gx, gy - agent.gy)) < min_dist: collide = True break if not collide: break # Give human new goal human.gx = gx human.gy = gy return # Update the specified human's end goals in the environment randomly def update_human_goal(self, human): # Update human's goals randomly if np.random.random() <= self.end_goal_change_chance: humans_copy = [] for h in self.humans: if h != human: humans_copy.append(h) while True: angle = np.random.random() * np.pi * 2 # add some noise to simulate all the possible cases robot could meet with human v_pref = 1.0 if human.v_pref == 0 else human.v_pref gx_noise = (np.random.random() - 0.5) * v_pref gy_noise = (np.random.random() - 0.5) * v_pref gx = self.circle_radius * np.cos(angle) + gx_noise gy = self.circle_radius * np.sin(angle) + gy_noise collide = False for agent in [self.robot] + humans_copy: min_dist = human.radius + agent.radius + self.discomfort_dist if norm((gx - agent.px, gy - agent.py)) < min_dist or \ norm((gx - agent.gx, gy - agent.gy)) < min_dist: collide = True break if not collide: break # Give human new goal human.gx = gx human.gy = gy return # calculate the angle between the direction vector of state1 and the vector from state1 to state2 def calc_offset_angle(self, state1, state2): if self.robot.kinematics == 'holonomic': real_theta = np.arctan2(state1.vy, state1.vx) else: real_theta = state1.theta # angle of center line of FOV of agent1 v_fov = [np.cos(real_theta), np.sin(real_theta)] # angle between agent1 and agent2 v_12 = [state2.px - state1.px, state2.py - state1.py] # angle between center of FOV and agent 2 v_fov = v_fov / np.linalg.norm(v_fov) v_12 = v_12 / np.linalg.norm(v_12) offset = np.arccos(np.clip(np.dot(v_fov, v_12), a_min=-1, a_max=1)) return offset # Caculate whether agent2 is in agent1's FOV # Not the same as whether agent1 is in agent2's FOV!!!! # arguments: # state1, state2: can be agent instance OR state instance # robot1: is True if state1 is robot, else is False # return value: # return True if state2 is visible to state1, else return False def detect_visible(self, state1, state2, robot1 = False, custom_fov=None, custom_sensor_range=None): if self.robot.kinematics == 'holonomic': real_theta = np.arctan2(state1.vy, state1.vx) else: real_theta = state1.theta # angle of center line of FOV of agent1 v_fov = [np.cos(real_theta), np.sin(real_theta)] # angle between agent1 and agent2 v_12 = [state2.px - state1.px, state2.py - state1.py] # angle between center of FOV and agent 2 v_fov = v_fov / np.linalg.norm(v_fov) v_12 = v_12 / np.linalg.norm(v_12) offset = np.arccos(np.clip(np.dot(v_fov, v_12), a_min=-1, a_max=1)) if custom_fov: fov = custom_fov else: if robot1: fov = self.robot_fov else: fov = self.human_fov if np.abs(offset) <= fov / 2: inFov = True else: inFov = False # detect whether state2 is in state1's sensor_range dist = np.linalg.norm([state1.px - state2.px, state1.py - state2.py]) - state1.radius - state2.radius if custom_sensor_range: inSensorRange = dist <= custom_sensor_range else: if robot1: inSensorRange = dist <= self.robot.sensor_range else: inSensorRange = True return (inFov and inSensorRange) # for robot: # return only visible humans to robot and number of visible humans # and a list of True/False to indicate whether each human is visible def get_num_human_in_fov(self): human_ids = [] humans_in_view = [] num_humans_in_view = 0 for i in range(self.human_num): visible = self.detect_visible(self.robot, self.humans[i], robot1=True) if visible: humans_in_view.append(self.humans[i]) num_humans_in_view = num_humans_in_view + 1 human_ids.append(True) else: human_ids.append(False) return humans_in_view, num_humans_in_view, human_ids def last_human_states_obj(self): ''' convert self.last_human_states to a list of observable state objects for old algorithms to use ''' humans = [] for i in range(self.human_num): h = ObservableState(*self.last_human_states[i]) humans.append(h) return humans # calculate the reward at current timestep R(s, a) def calc_reward(self, action): # collision detection dmin = float('inf') danger_dists = [] collision = False for i, human in enumerate(self.humans): dx = human.px - self.robot.px dy = human.py - self.robot.py closest_dist = (dx ** 2 + dy ** 2) ** (1 / 2) - human.radius - self.robot.radius if closest_dist < self.discomfort_dist: danger_dists.append(closest_dist) if closest_dist < 0: collision = True # logging.debug("Collision: distance between robot and p{} is {:.2E}".format(i, closest_dist)) break elif closest_dist < dmin: dmin = closest_dist # check if reaching the goal reaching_goal = norm(np.array(self.robot.get_position()) - np.array(self.robot.get_goal_position())) < self.robot.radius if self.global_time >= self.time_limit - 1: reward = 0 done = True episode_info = Timeout() elif collision: reward = self.collision_penalty done = True episode_info = Collision() elif reaching_goal: reward = self.success_reward done = True episode_info = ReachGoal() elif dmin < self.discomfort_dist: # only penalize agent for getting too close if it's visible # adjust the reward based on FPS # print(dmin) reward = (dmin - self.discomfort_dist) * self.discomfort_penalty_factor * self.time_step done = False episode_info = Danger(dmin) else: # potential reward potential_cur = np.linalg.norm( np.array([self.robot.px, self.robot.py]) - np.array(self.robot.get_goal_position())) reward = 2 * (-abs(potential_cur) - self.potential) self.potential = -abs(potential_cur) done = False episode_info = Nothing() # if the robot is near collision/arrival, it should be able to turn a large angle if self.robot.kinematics == 'unicycle': # add a rotational penalty # if action.r is w, factor = -0.02 if w in [-1.5, 1.5], factor = -0.045 if w in [-1, 1]; # if action.r is delta theta, factor = -2 if r in [-0.15, 0.15], factor = -4.5 if r in [-0.1, 0.1] r_spin = -5 * action.r**2 # add a penalty for going backwards if action.v < 0: r_back = -2 * abs(action.v) else: r_back = 0. reward = reward + r_spin + r_back return reward, done, episode_info # compute the observation def generate_ob(self, reset): visible_human_states, num_visible_humans, human_visibility = self.get_num_human_in_fov() self.update_last_human_states(human_visibility, reset=reset) if self.robot.policy.name in ['lstm_ppo', 'srnn']: ob = [num_visible_humans] # append robot's state robotS = np.array(self.robot.get_full_state_list()) ob.extend(list(robotS)) ob.extend(list(np.ravel(self.last_human_states))) ob = np.array(ob) else: # for orca and sf ob = self.last_human_states_obj() return ob def get_human_actions(self): # step all humans human_actions = [] # a list of all humans' actions for i, human in enumerate(self.humans): # observation for humans is always coordinates ob = [] for other_human in self.humans: if other_human != human: # Else detectable humans are always observable to each other if self.detect_visible(human, other_human): ob.append(other_human.get_observable_state()) else: ob.append(self.dummy_human.get_observable_state()) if self.robot.visible: if self.detect_visible(self.humans[i], self.robot): ob += [self.robot.get_observable_state()] else: ob += [self.dummy_robot.get_observable_state()] human_actions.append(human.act(ob)) return human_actions def step(self, action, update=True): """ Compute actions for all agents, detect collision, update environment and return (ob, reward, done, info) """ # clip the action to obey robot's constraint action = self.robot.policy.clip_action(action, self.robot.v_pref) human_actions = self.get_human_actions() # compute reward and episode info reward, done, episode_info = self.calc_reward(action) # apply action and update all agents self.robot.step(action) for i, human_action in enumerate(human_actions): self.humans[i].step(human_action) self.global_time += self.time_step # max episode length=time_limit/time_step self.step_counter=self.step_counter+1 ##### compute_ob goes here!!!!! ob = self.generate_ob(reset=False) if self.robot.policy.name in ['srnn']: info={'info':episode_info} else: # for orca and sf info=episode_info # Update all humans' goals randomly midway through episode if self.random_goal_changing: if self.global_time % 5 == 0: self.update_human_goals_randomly() # Update a specific human's goal once its reached its original goal if self.end_goal_changing: for human in self.humans: if not human.isObstacle and human.v_pref != 0 and norm((human.gx - human.px, human.gy - human.py)) < human.radius: self.update_human_goal(human) return ob, reward, done, info def render(self, mode='human'): """ Render the current status of the environment using matplotlib """ import matplotlib.pyplot as plt import matplotlib.lines as mlines from matplotlib import patches plt.rcParams['animation.ffmpeg_path'] = '/usr/bin/ffmpeg' robot_color = 'yellow' goal_color = 'red' arrow_color = 'red' arrow_style = patches.ArrowStyle("->", head_length=4, head_width=2) def calcFOVLineEndPoint(ang, point, extendFactor): # choose the extendFactor big enough # so that the endPoints of the FOVLine is out of xlim and ylim of the figure FOVLineRot = np.array([[np.cos(ang), -np.sin(ang), 0], [np.sin(ang), np.cos(ang), 0], [0, 0, 1]]) point.extend([1]) # apply rotation matrix newPoint = np.matmul(FOVLineRot, np.reshape(point, [3, 1])) # increase the distance between the line start point and the end point newPoint = [extendFactor * newPoint[0, 0], extendFactor * newPoint[1, 0], 1] return newPoint ax=self.render_axis artists=[] # add goal goal=mlines.Line2D([self.robot.gx], [self.robot.gy], color=goal_color, marker='*', linestyle='None', markersize=15, label='Goal') ax.add_artist(goal) artists.append(goal) # add robot robotX,robotY=self.robot.get_position() robot=plt.Circle((robotX,robotY), self.robot.radius, fill=True, color=robot_color) ax.add_artist(robot) artists.append(robot) plt.legend([robot, goal], ['Robot', 'Goal'], fontsize=16) # compute orientation in each step and add arrow to show the direction radius = self.robot.radius arrowStartEnd=[] robot_theta = self.robot.theta if self.robot.kinematics == 'unicycle' else np.arctan2(self.robot.vy, self.robot.vx) arrowStartEnd.append(((robotX, robotY), (robotX + radius * np.cos(robot_theta), robotY + radius * np.sin(robot_theta)))) for i, human in enumerate(self.humans): theta = np.arctan2(human.vy, human.vx) arrowStartEnd.append(((human.px, human.py), (human.px + radius * np.cos(theta), human.py + radius * np.sin(theta)))) arrows = [patches.FancyArrowPatch(*arrow, color=arrow_color, arrowstyle=arrow_style) for arrow in arrowStartEnd] for arrow in arrows: ax.add_artist(arrow) artists.append(arrow) # draw FOV for the robot # add robot FOV if self.robot.FOV < 2 * np.pi: FOVAng = self.robot_fov / 2 FOVLine1 = mlines.Line2D([0, 0], [0, 0], linestyle='--') FOVLine2 = mlines.Line2D([0, 0], [0, 0], linestyle='--') startPointX = robotX startPointY = robotY endPointX = robotX + radius * np.cos(robot_theta) endPointY = robotY + radius * np.sin(robot_theta) # transform the vector back to world frame origin, apply rotation matrix, and get end point of FOVLine # the start point of the FOVLine is the center of the robot FOVEndPoint1 = calcFOVLineEndPoint(FOVAng, [endPointX - startPointX, endPointY - startPointY], 20. / self.robot.radius) FOVLine1.set_xdata(np.array([startPointX, startPointX + FOVEndPoint1[0]])) FOVLine1.set_ydata(np.array([startPointY, startPointY + FOVEndPoint1[1]])) FOVEndPoint2 = calcFOVLineEndPoint(-FOVAng, [endPointX - startPointX, endPointY - startPointY], 20. / self.robot.radius) FOVLine2.set_xdata(np.array([startPointX, startPointX + FOVEndPoint2[0]])) FOVLine2.set_ydata(np.array([startPointY, startPointY + FOVEndPoint2[1]])) ax.add_artist(FOVLine1) ax.add_artist(FOVLine2) artists.append(FOVLine1) artists.append(FOVLine2) # add an arc of robot's sensor range sensor_range = plt.Circle(self.robot.get_position(), self.robot.sensor_range, fill=False, linestyle='--') ax.add_artist(sensor_range) artists.append(sensor_range) # add humans and change the color of them based on visibility human_circles = [plt.Circle(human.get_position(), human.radius, fill=False) for human in self.humans] for i in range(len(self.humans)): ax.add_artist(human_circles[i]) artists.append(human_circles[i]) # green: visible; red: invisible if self.detect_visible(self.robot, self.humans[i], robot1=True): human_circles[i].set_color(c='g') else: human_circles[i].set_color(c='r') # label numbers on each human # plt.text(self.humans[i].px - 0.1, self.humans[i].py - 0.1, str(self.humans[i].id), color='black', fontsize=12) plt.text(self.humans[i].px - 0.1, self.humans[i].py - 0.1, i, color='black', fontsize=12) plt.pause(0.1) for item in artists: item.remove() # there should be a better way to do this. For example, # initially use add_artist and draw_artist later on for t in ax.texts: t.set_visible(False) ================================================ FILE: crowd_sim/envs/crowd_sim_pred.py ================================================ import gym import numpy as np from numpy.linalg import norm import copy from crowd_sim.envs.utils.action import ActionRot, ActionXY from crowd_sim.envs.crowd_sim_var_num import CrowdSimVarNum class CrowdSimPred(CrowdSimVarNum): """ The environment for our model with non-neural network trajectory predictors, including const vel predictor and ground truth predictor The number of humans at each timestep can change within a range """ def __init__(self): """ Movement simulation for n+1 agents Agent can either be human or robot. humans are controlled by a unknown and fixed policy. robot is controlled by a known and learnable policy. """ super(CrowdSimPred, self).__init__() self.pred_method = None self.cur_human_states=None def configure(self, config): """ read the config to the environment variables """ super().configure(config) # 'const_vel', 'truth', or 'inferred' self.pred_method = config.sim.predict_method def reset(self, phase='train', test_case=None): ob = super().reset(phase=phase, test_case=test_case) return ob # set observation space and action space def set_robot(self, robot): self.robot = robot # we set the max and min of action/observation space as inf # clip the action and observation as you need d = {} # robot node: num_visible_humans, px, py, r, gx, gy, v_pref, theta d['robot_node'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(1, 7,), dtype=np.float32) # only consider all temporal edges (human_num+1) and spatial edges pointing to robot (human_num) d['temporal_edges'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(1, 2,), dtype=np.float32) d['spatial_edges'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.config.sim.human_num + self.config.sim.human_num_range, int(2*(self.predict_steps+1))), dtype=np.float32) # number of humans detected at each timestep d['detected_human_num'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.float32) self.observation_space = gym.spaces.Dict(d) high = np.inf * np.ones([2, ]) self.action_space = gym.spaces.Box(-high, high, dtype=np.float32) # reset = True: reset calls this function; reset = False: step calls this function def generate_ob(self, reset, sort=True): """Generate observation for reset and step functions""" ob = {} # nodes visible_humans, num_visibles, self.human_visibility = self.get_num_human_in_fov() ob['robot_node'] = self.robot.get_full_state_list_noV() self.prev_human_pos = copy.deepcopy(self.last_human_states) self.update_last_human_states(self.human_visibility, reset=reset) # edges ob['temporal_edges'] = np.array([self.robot.vx, self.robot.vy]) # initialize storage space for max_human_num humans ob['spatial_edges'] = np.ones((self.config.sim.human_num + self.config.sim.human_num_range, int(2*(self.predict_steps+1)))) * np.inf # calculate the current and future positions of present humans (first self.human_num humans in ob['spatial_edges']) predicted_states = self.calc_human_future_traj(method=self.pred_method) # [pred_steps + 1, human_num, 4] # [human_num, pred_steps + 1, 2] predicted_pos = np.transpose(predicted_states[:, :, :2], (1, 0, 2)) - np.array([self.robot.px, self.robot.py]) # [human_num, (pred_steps + 1)*2] ob['spatial_edges'][:self.human_num][self.human_visibility] = predicted_pos.reshape((self.human_num, -1))[self.human_visibility] # sort all humans by distance (invisible humans will be in the end automatically) if self.config.args.sort_humans: ob['spatial_edges'] = np.array(sorted(ob['spatial_edges'], key=lambda x: np.linalg.norm(x[:2]))) ob['spatial_edges'][np.isinf(ob['spatial_edges'])] = 15 ob['detected_human_num'] = num_visibles # if no human is detected, assume there is one dummy human at (15, 15) to make the pack_padded_sequence work if ob['detected_human_num'] == 0: ob['detected_human_num'] = 1 return ob def step(self, action, update=True): """ step function Compute actions for all agents, detect collision, update environment and return (ob, reward, done, info) """ if self.robot.policy.name == 'ORCA': # assemble observation for orca: px, py, vx, vy, r # include all observable humans from t to t+t_pred _, _, human_visibility = self.get_num_human_in_fov() # [self.predict_steps + 1, self.human_num, 4] human_states = copy.deepcopy(self.calc_human_future_traj(method='truth')) # append the radius, convert it to [human_num*(self.predict_steps+1), 5] by treating each predicted pos as a new human human_states = np.concatenate((human_states.reshape((-1, 4)), np.tile(self.last_human_states[:, -1], self.predict_steps+1).reshape((-1, 1))), axis=1) # get orca action action = self.robot.act(human_states.tolist()) else: action = self.robot.policy.clip_action(action, self.robot.v_pref) if self.robot.kinematics == 'unicycle': self.desiredVelocity[0] = np.clip(self.desiredVelocity[0] + action.v, -self.robot.v_pref, self.robot.v_pref) action = ActionRot(self.desiredVelocity[0], action.r) # if action.r is delta theta action = ActionRot(self.desiredVelocity[0], action.r) if self.record: self.episodeRecoder.unsmoothed_actions.append(list(action)) action = self.smooth_action(action) human_actions = self.get_human_actions() # need to update self.human_future_traj in testing to calculate number of intrusions if self.phase == 'test': # use ground truth future positions of humans self.calc_human_future_traj(method='truth') # compute reward and episode info reward, done, episode_info = self.calc_reward(action, danger_zone='future') if self.record: self.episodeRecoder.actionList.append(list(action)) self.episodeRecoder.positionList.append([self.robot.px, self.robot.py]) self.episodeRecoder.orientationList.append(self.robot.theta) if done: self.episodeRecoder.robot_goal.append([self.robot.gx, self.robot.gy]) self.episodeRecoder.saveEpisode(self.case_counter['test']) # apply action and update all agents self.robot.step(action) for i, human_action in enumerate(human_actions): self.humans[i].step(human_action) self.cur_human_states[i] = np.array([self.humans[i].px, self.humans[i].py, self.humans[i].radius]) self.global_time += self.time_step # max episode length=time_limit/time_step self.step_counter = self.step_counter+1 info={'info':episode_info} # Add or remove at most self.human_num_range humans # if self.human_num_range == 0 -> human_num is fixed at all times if self.human_num_range > 0 and self.global_time % 5 == 0: # remove humans if np.random.rand() < 0.5: # if no human is visible, anyone can be removed if len(self.observed_human_ids) == 0: max_remove_num = self.human_num - 1 else: max_remove_num = (self.human_num - 1) - max(self.observed_human_ids) remove_num = np.random.randint(low=0, high=min(self.human_num_range, max_remove_num) + 1) for _ in range(remove_num): self.humans.pop() self.human_num = self.human_num - remove_num self.last_human_states = self.last_human_states[:self.human_num] # add humans else: add_num = np.random.randint(low=0, high=self.human_num_range + 1) if add_num > 0: # set human ids true_add_num = 0 for i in range(self.human_num, self.human_num + add_num): if i == self.config.sim.human_num + self.human_num_range: break self.generate_random_human_position(human_num=1) self.humans[i].id = i true_add_num = true_add_num + 1 self.human_num = self.human_num + true_add_num if true_add_num > 0: self.last_human_states = np.concatenate((self.last_human_states, np.array([[15, 15, 0, 0, 0.3]]*true_add_num)), axis=0) # compute the observation ob = self.generate_ob(reset=False) # Update all humans' goals randomly midway through episode if self.random_goal_changing: if self.global_time % 5 == 0: self.update_human_goals_randomly() # Update a specific human's goal once its reached its original goal if self.end_goal_changing and not self.record: for i, human in enumerate(self.humans): if norm((human.gx - human.px, human.gy - human.py)) < human.radius: self.humans[i] = self.generate_circle_crossing_human() self.humans[i].id = i return ob, reward, done, info def calc_reward(self, action, danger_zone='future'): """Calculate the reward, which includes the functionality reward and social reward""" # compute the functionality reward, same as the reward function with no prediction reward, done, episode_info = super().calc_reward(action, danger_zone=danger_zone) # compute the social reward # if the robot collides with future states, give it a collision penalty relative_pos = self.human_future_traj[1:, :, :2] - np.array([self.robot.px, self.robot.py]) # assumes constant radius of each personal zone circle collision_idx = np.linalg.norm(relative_pos, axis=-1) < self.robot.radius + self.config.humans.radius # [predict_steps, human_num] coefficients = 2. ** np.arange(2, self.predict_steps + 2).reshape((self.predict_steps, 1)) # 4, 8, 16, 32 collision_penalties = self.collision_penalty / coefficients reward_future = collision_idx * collision_penalties # [predict_steps, human_num] reward_future = np.min(reward_future) return reward + reward_future, done, episode_info def render(self, mode='human'): """ Render the current status of the environment using matplotlib """ import matplotlib.pyplot as plt import matplotlib.lines as mlines from matplotlib import patches plt.rcParams['animation.ffmpeg_path'] = '/usr/bin/ffmpeg' robot_color = 'gold' goal_color = 'red' arrow_color = 'red' arrow_style = patches.ArrowStyle("->", head_length=4, head_width=2) def calcFOVLineEndPoint(ang, point, extendFactor): # choose the extendFactor big enough # so that the endPoints of the FOVLine is out of xlim and ylim of the figure FOVLineRot = np.array([[np.cos(ang), -np.sin(ang), 0], [np.sin(ang), np.cos(ang), 0], [0, 0, 1]]) point.extend([1]) # apply rotation matrix newPoint = np.matmul(FOVLineRot, np.reshape(point, [3, 1])) # increase the distance between the line start point and the end point newPoint = [extendFactor * newPoint[0, 0], extendFactor * newPoint[1, 0], 1] return newPoint ax=self.render_axis artists=[] # add goal goal=mlines.Line2D([self.robot.gx], [self.robot.gy], color=goal_color, marker='*', linestyle='None', markersize=15, label='Goal') ax.add_artist(goal) artists.append(goal) # add robot robotX,robotY=self.robot.get_position() robot=plt.Circle((robotX,robotY), self.robot.radius, fill=True, color=robot_color) ax.add_artist(robot) artists.append(robot) # plt.legend([robot, goal], ['Robot', 'Goal'], fontsize=16) # compute orientation in each step and add arrow to show the direction radius = self.robot.radius arrowStartEnd=[] robot_theta = self.robot.theta if self.robot.kinematics == 'unicycle' else np.arctan2(self.robot.vy, self.robot.vx) arrowStartEnd.append(((robotX, robotY), (robotX + radius * np.cos(robot_theta), robotY + radius * np.sin(robot_theta)))) for i, human in enumerate(self.humans): theta = np.arctan2(human.vy, human.vx) arrowStartEnd.append(((human.px, human.py), (human.px + radius * np.cos(theta), human.py + radius * np.sin(theta)))) arrows = [patches.FancyArrowPatch(*arrow, color=arrow_color, arrowstyle=arrow_style) for arrow in arrowStartEnd] for arrow in arrows: ax.add_artist(arrow) artists.append(arrow) # draw FOV for the robot # add robot FOV if self.robot.FOV < 2 * np.pi: FOVAng = self.robot_fov / 2 FOVLine1 = mlines.Line2D([0, 0], [0, 0], linestyle='--') FOVLine2 = mlines.Line2D([0, 0], [0, 0], linestyle='--') startPointX = robotX startPointY = robotY endPointX = robotX + radius * np.cos(robot_theta) endPointY = robotY + radius * np.sin(robot_theta) # transform the vector back to world frame origin, apply rotation matrix, and get end point of FOVLine # the start point of the FOVLine is the center of the robot FOVEndPoint1 = calcFOVLineEndPoint(FOVAng, [endPointX - startPointX, endPointY - startPointY], 20. / self.robot.radius) FOVLine1.set_xdata(np.array([startPointX, startPointX + FOVEndPoint1[0]])) FOVLine1.set_ydata(np.array([startPointY, startPointY + FOVEndPoint1[1]])) FOVEndPoint2 = calcFOVLineEndPoint(-FOVAng, [endPointX - startPointX, endPointY - startPointY], 20. / self.robot.radius) FOVLine2.set_xdata(np.array([startPointX, startPointX + FOVEndPoint2[0]])) FOVLine2.set_ydata(np.array([startPointY, startPointY + FOVEndPoint2[1]])) ax.add_artist(FOVLine1) ax.add_artist(FOVLine2) artists.append(FOVLine1) artists.append(FOVLine2) # add an arc of robot's sensor range sensor_range = plt.Circle(self.robot.get_position(), self.robot.sensor_range + self.robot.radius+self.config.humans.radius, fill=False, linestyle='--') ax.add_artist(sensor_range) artists.append(sensor_range) # add humans and change the color of them based on visibility human_circles = [plt.Circle(human.get_position(), human.radius, fill=False, linewidth=1.5) for human in self.humans] # human_circles = [plt.Circle(human.get_position(), 2, fill=False) for human in self.humans] # hardcoded for now actual_arena_size = self.arena_size + 0.5 # plot current human states for i in range(len(self.humans)): ax.add_artist(human_circles[i]) artists.append(human_circles[i]) # green: visible; red: invisible if self.detect_visible(self.robot, self.humans[i], robot1=True): human_circles[i].set_color(c='b') else: human_circles[i].set_color(c='r') if -actual_arena_size <= self.humans[i].px <= actual_arena_size and -actual_arena_size <= self.humans[ i].py <= actual_arena_size: # label numbers on each human # plt.text(self.humans[i].px - 0.1, self.humans[i].py - 0.1, str(self.humans[i].id), color='black', fontsize=12) plt.text(self.humans[i].px - 0.1, self.humans[i].py - 0.1, i, color='black', fontsize=12) # save plot for slide show if self.config.save_slides: import os folder_path = os.path.join(self.config.save_path, str(self.rand_seed)+'pred') if not os.path.isdir(folder_path): os.makedirs(folder_path, exist_ok = True) plt.savefig(os.path.join(folder_path, str(self.step_counter)+'.png'), dpi=300) plt.pause(0.1) for item in artists: item.remove() # there should be a better way to do this. For example, # initially use add_artist and draw_artist later on for t in ax.texts: t.set_visible(False) ================================================ FILE: crowd_sim/envs/crowd_sim_pred_real_gst.py ================================================ import gym import numpy as np from crowd_sim.envs.crowd_sim_pred import CrowdSimPred class CrowdSimPredRealGST(CrowdSimPred): ''' Same as CrowdSimPred, except that The future human traj in 'spatial_edges' are dummy placeholders and will be replaced by the outputs of a real GST pred model in the wrapper function in vec_pretext_normalize.py ''' def __init__(self): """ Movement simulation for n+1 agents Agent can either be human or robot. humans are controlled by a unknown and fixed policy. robot is controlled by a known and learnable policy. """ super(CrowdSimPredRealGST, self).__init__() self.pred_method = None # to receive data from gst pred model self.gst_out_traj = None def set_robot(self, robot): """set observation space and action space""" self.robot = robot # we set the max and min of action/observation space as inf # clip the action and observation as you need d = {} # robot node: num_visible_humans, px, py, r, gx, gy, v_pref, theta d['robot_node'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(1, 7,), dtype=np.float32) # only consider all temporal edges (human_num+1) and spatial edges pointing to robot (human_num) d['temporal_edges'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(1, 2,), dtype=np.float32) ''' format of spatial_edges: [max_human_num, [state_t, state_(t+1), ..., state(t+self.pred_steps)]] ''' # predictions only include mu_x, mu_y (or px, py) self.spatial_edge_dim = int(2*(self.predict_steps+1)) d['spatial_edges'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.config.sim.human_num + self.config.sim.human_num_range, self.spatial_edge_dim), dtype=np.float32) # masks for gst pred model # whether each human is visible to robot (ordered by human ID, should not be sorted) d['visible_masks'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.config.sim.human_num + self.config.sim.human_num_range,), dtype=np.bool) # number of humans detected at each timestep d['detected_human_num'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.float32) self.observation_space = gym.spaces.Dict(d) high = np.inf * np.ones([2, ]) self.action_space = gym.spaces.Box(-high, high, dtype=np.float32) def talk2Env(self, data): """ Call this function when you want extra information to send to/recv from the env :param data: data that is sent from gst_predictor network to the env, it has 2 parts: output predicted traj and output masks :return: True means received """ self.gst_out_traj=data return True # reset = True: reset calls this function; reset = False: step calls this function def generate_ob(self, reset, sort=False): """Generate observation for reset and step functions""" # since gst pred model needs ID tracking, don't sort all humans # inherit from crowd_sim_lstm, not crowd_sim_pred to avoid computation of true pred! # sort=False because we will sort in wrapper in vec_pretext_normalize.py later parent_ob = super(CrowdSimPred, self).generate_ob(reset=reset, sort=False) # add additional keys, removed unused keys ob = {} ob['visible_masks'] = parent_ob['visible_masks'] ob['robot_node'] = parent_ob['robot_node'] ob['temporal_edges'] = parent_ob['temporal_edges'] ob['spatial_edges'] = np.tile(parent_ob['spatial_edges'], self.predict_steps+1) ob['detected_human_num'] = parent_ob['detected_human_num'] return ob def calc_reward(self, action, danger_zone='future'): # inherit from crowd_sim_lstm, not crowd_sim_pred to prevent social reward calculation # since we don't know the GST predictions yet reward, done, episode_info = super(CrowdSimPred, self).calc_reward(action, danger_zone=danger_zone) return reward, done, episode_info def render(self, mode='human'): """ render function use talk2env to plot the predicted future traj of humans """ import matplotlib.pyplot as plt import matplotlib.lines as mlines from matplotlib import patches plt.rcParams['animation.ffmpeg_path'] = '/usr/bin/ffmpeg' robot_color = 'gold' goal_color = 'red' arrow_color = 'red' arrow_style = patches.ArrowStyle("->", head_length=4, head_width=2) def calcFOVLineEndPoint(ang, point, extendFactor): # choose the extendFactor big enough # so that the endPoints of the FOVLine is out of xlim and ylim of the figure FOVLineRot = np.array([[np.cos(ang), -np.sin(ang), 0], [np.sin(ang), np.cos(ang), 0], [0, 0, 1]]) point.extend([1]) # apply rotation matrix newPoint = np.matmul(FOVLineRot, np.reshape(point, [3, 1])) # increase the distance between the line start point and the end point newPoint = [extendFactor * newPoint[0, 0], extendFactor * newPoint[1, 0], 1] return newPoint ax=self.render_axis artists=[] # add goal goal=mlines.Line2D([self.robot.gx], [self.robot.gy], color=goal_color, marker='*', linestyle='None', markersize=15, label='Goal') ax.add_artist(goal) artists.append(goal) # add robot robotX,robotY=self.robot.get_position() robot=plt.Circle((robotX,robotY), self.robot.radius, fill=True, color=robot_color) ax.add_artist(robot) artists.append(robot) # compute orientation in each step and add arrow to show the direction radius = self.robot.radius arrowStartEnd=[] robot_theta = self.robot.theta if self.robot.kinematics == 'unicycle' else np.arctan2(self.robot.vy, self.robot.vx) arrowStartEnd.append(((robotX, robotY), (robotX + radius * np.cos(robot_theta), robotY + radius * np.sin(robot_theta)))) for i, human in enumerate(self.humans): theta = np.arctan2(human.vy, human.vx) arrowStartEnd.append(((human.px, human.py), (human.px + radius * np.cos(theta), human.py + radius * np.sin(theta)))) arrows = [patches.FancyArrowPatch(*arrow, color=arrow_color, arrowstyle=arrow_style) for arrow in arrowStartEnd] for arrow in arrows: ax.add_artist(arrow) artists.append(arrow) # draw FOV for the robot # add robot FOV if self.robot.FOV < 2 * np.pi: FOVAng = self.robot_fov / 2 FOVLine1 = mlines.Line2D([0, 0], [0, 0], linestyle='--') FOVLine2 = mlines.Line2D([0, 0], [0, 0], linestyle='--') startPointX = robotX startPointY = robotY endPointX = robotX + radius * np.cos(robot_theta) endPointY = robotY + radius * np.sin(robot_theta) # transform the vector back to world frame origin, apply rotation matrix, and get end point of FOVLine # the start point of the FOVLine is the center of the robot FOVEndPoint1 = calcFOVLineEndPoint(FOVAng, [endPointX - startPointX, endPointY - startPointY], 20. / self.robot.radius) FOVLine1.set_xdata(np.array([startPointX, startPointX + FOVEndPoint1[0]])) FOVLine1.set_ydata(np.array([startPointY, startPointY + FOVEndPoint1[1]])) FOVEndPoint2 = calcFOVLineEndPoint(-FOVAng, [endPointX - startPointX, endPointY - startPointY], 20. / self.robot.radius) FOVLine2.set_xdata(np.array([startPointX, startPointX + FOVEndPoint2[0]])) FOVLine2.set_ydata(np.array([startPointY, startPointY + FOVEndPoint2[1]])) ax.add_artist(FOVLine1) ax.add_artist(FOVLine2) artists.append(FOVLine1) artists.append(FOVLine2) # add an arc of robot's sensor range sensor_range = plt.Circle(self.robot.get_position(), self.robot.sensor_range + self.robot.radius+self.config.humans.radius, fill=False, linestyle='--') ax.add_artist(sensor_range) artists.append(sensor_range) # add humans and change the color of them based on visibility human_circles = [plt.Circle(human.get_position(), human.radius, fill=False, linewidth=1.5) for human in self.humans] # hardcoded for now actual_arena_size = self.arena_size + 0.5 # plot the current human states for i in range(len(self.humans)): ax.add_artist(human_circles[i]) artists.append(human_circles[i]) # green: visible; red: invisible if self.human_visibility[i]: human_circles[i].set_color(c='b') else: human_circles[i].set_color(c='r') if -actual_arena_size <= self.humans[i].px <= actual_arena_size and -actual_arena_size <= self.humans[ i].py <= actual_arena_size: # label numbers on each human # plt.text(self.humans[i].px - 0.1, self.humans[i].py - 0.1, str(self.humans[i].id), color='black', fontsize=12) plt.text(self.humans[i].px , self.humans[i].py , i, color='black', fontsize=12) # plot predicted human positions for i in range(len(self.humans)): # add future predicted positions of each human if self.gst_out_traj is not None: for j in range(self.predict_steps): circle = plt.Circle(self.gst_out_traj[i, (2 * j):(2 * j + 2)] + np.array([robotX, robotY]), self.config.humans.radius, fill=False, color='tab:orange', linewidth=1.5) # circle = plt.Circle(np.array([robotX, robotY]), # self.humans[i].radius, fill=False) ax.add_artist(circle) artists.append(circle) plt.pause(0.1) for item in artists: item.remove() # there should be a better way to do this. For example, # initially use add_artist and draw_artist later on for t in ax.texts: t.set_visible(False) ================================================ FILE: crowd_sim/envs/crowd_sim_var_num.py ================================================ import gym import numpy as np from numpy.linalg import norm import copy from crowd_sim.envs.utils.action import ActionRot, ActionXY from crowd_sim.envs import * from crowd_sim.envs.utils.info import * from crowd_sim.envs.utils.human import Human from crowd_sim.envs.utils.state import JointState class CrowdSimVarNum(CrowdSim): """ The environment for our model with no trajectory prediction, or the baseline models with no prediction The number of humans at each timestep can change within a range """ def __init__(self): """ Movement simulation for n+1 agents Agent can either be human or robot. humans are controlled by a unknown and fixed policy. robot is controlled by a known and learnable policy. """ super().__init__() self.id_counter = None self.observed_human_ids = None self.pred_method = None def configure(self, config): """ read the config to the environment variables """ super(CrowdSimVarNum, self).configure(config) self.action_type=config.action_space.kinematics # set observation space and action space def set_robot(self, robot): self.robot = robot # we set the max and min of action/observation space as inf # clip the action and observation as you need d={} # robot node: px, py, r, gx, gy, v_pref, theta d['robot_node'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(1,7,), dtype = np.float32) # only consider all temporal edges (human_num+1) and spatial edges pointing to robot (human_num) d['temporal_edges'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(1, 2,), dtype=np.float32) d['spatial_edges'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.max_human_num, 2), dtype=np.float32) # number of humans detected at each timestep d['detected_human_num'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(1, ), dtype=np.float32) # whether each human is visible to robot (ordered by human ID, should not be sorted) d['visible_masks'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.max_human_num,), dtype=np.bool) self.observation_space=gym.spaces.Dict(d) high = np.inf * np.ones([2, ]) self.action_space = gym.spaces.Box(-high, high, dtype=np.float32) # set robot initial state and generate all humans for reset function # for crowd nav: human_num == self.human_num # for leader follower: human_num = self.human_num - 1 def generate_robot_humans(self, phase, human_num=None): if self.record: px, py = 0, 0 gx, gy = 0, -1.5 self.robot.set(px, py, gx, gy, 0, 0, np.pi / 2) # generate a dummy human for i in range(self.max_human_num): human = Human(self.config, 'humans') human.set(15, 15, 15, 15, 0, 0, 0) human.isObstacle = True self.humans.append(human) else: # for sim2real if self.robot.kinematics == 'unicycle': # generate robot angle = np.random.uniform(0, np.pi * 2) px = self.arena_size * np.cos(angle) py = self.arena_size * np.sin(angle) while True: gx, gy = np.random.uniform(-self.arena_size, self.arena_size, 2) if np.linalg.norm([px - gx, py - gy]) >= 4: # 1 was 6 break self.robot.set(px, py, gx, gy, 0, 0, np.random.uniform(0, 2 * np.pi)) # randomize init orientation # 1 to 4 humans self.human_num = np.random.randint(1, self.config.sim.human_num + self.human_num_range + 1) # print('human_num:', self.human_num) # self.human_num = 4 # for sim exp else: # generate robot while True: px, py, gx, gy = np.random.uniform(-self.arena_size, self.arena_size, 4) if np.linalg.norm([px - gx, py - gy]) >= 8: # 6 break self.robot.set(px, py, gx, gy, 0, 0, np.pi / 2) # generate humans self.human_num = np.random.randint(low=self.config.sim.human_num - self.human_num_range, high=self.config.sim.human_num + self.human_num_range + 1) self.generate_random_human_position(human_num=self.human_num) self.last_human_states = np.zeros((self.human_num, 5)) # set human ids for i in range(self.human_num): self.humans[i].id = i # generate a human that starts on a circle, and its goal is on the opposite side of the circle def generate_circle_crossing_human(self): human = Human(self.config, 'humans') if self.randomize_attributes: human.sample_random_attributes() while True: angle = np.random.random() * np.pi * 2 # add some noise to simulate all the possible cases robot could meet with human noise_range = 2 px_noise = np.random.uniform(0, 1) * noise_range py_noise = np.random.uniform(0, 1) * noise_range px = self.circle_radius * np.cos(angle) + px_noise py = self.circle_radius * np.sin(angle) + py_noise collide = False for i, agent in enumerate([self.robot] + self.humans): # keep human at least 3 meters away from robot if self.robot.kinematics == 'unicycle' and i == 0: min_dist = self.circle_radius / 2 # Todo: if circle_radius <= 4, it will get stuck here else: min_dist = human.radius + agent.radius + self.discomfort_dist if norm((px - agent.px, py - agent.py)) < min_dist or \ norm((px - agent.gx, py - agent.gy)) < min_dist: collide = True break if not collide: break human.set(px, py, -px, -py, 0, 0, 0) return human # calculate the ground truth future trajectory of humans # if robot is visible: assume linear motion for robot # ret val: [self.predict_steps + 1, self.human_num, 4] # method: 'truth' or 'const_vel' or 'inferred' def calc_human_future_traj(self, method): # if the robot is invisible, it won't affect human motions # else it will human_num = self.human_num + 1 if self.robot.visible else self.human_num # buffer to store predicted future traj of all humans [px, py, vx, vy] # [time, human id, features] if method == 'truth': self.human_future_traj = np.zeros((self.buffer_len + 1, human_num, 4)) elif method == 'const_vel': self.human_future_traj = np.zeros((self.predict_steps + 1, human_num, 4)) else: raise NotImplementedError # initialize the 0-th position with current states for i in range(self.human_num): # use true states for now, to count for invisible humans' influence on visible humans # take px, py, vx, vy, remove radius self.human_future_traj[0, i] = np.array(self.humans[i].get_observable_state_list()[:-1]) # if we are using constant velocity model, we need to use displacement to approximate velocity (pos_t - pos_t-1) # we shouldn't use true velocity for fair comparison with GST inferred pred if method == 'const_vel': self.human_future_traj[0, :, 2:4] = self.prev_human_pos[:, 2:4] # add robot to the end of the array if self.robot.visible: self.human_future_traj[0, -1] = np.array(self.robot.get_observable_state_list()[:-1]) if method == 'truth': for i in range(1, self.buffer_len + 1): for j in range(self.human_num): # prepare joint state for all humans full_state = np.concatenate( (self.human_future_traj[i - 1, j], self.humans[j].get_full_state_list()[4:])) observable_states = [] for k in range(self.human_num): if j == k: continue observable_states.append( np.concatenate((self.human_future_traj[i - 1, k], [self.humans[k].radius]))) # use joint states to get actions from the states in the last step (i-1) action = self.humans[j].act_joint_state(JointState(full_state, observable_states)) # step all humans with action self.human_future_traj[i, j] = self.humans[j].one_step_lookahead( self.human_future_traj[i - 1, j, :2], action) if self.robot.visible: action = ActionXY(*self.human_future_traj[i - 1, -1, 2:]) # update px, py, vx, vy self.human_future_traj[i, -1] = self.robot.one_step_lookahead(self.human_future_traj[i - 1, -1, :2], action) # only take predictions every self.pred_interval steps self.human_future_traj = self.human_future_traj[::self.pred_interval] # for const vel model elif method == 'const_vel': # [self.pred_steps+1, human_num, 4] self.human_future_traj = np.tile(self.human_future_traj[0].reshape(1, human_num, 4), (self.predict_steps+1, 1, 1)) # [self.pred_steps+1, human_num, 2] pred_timestep = np.tile(np.arange(0, self.predict_steps+1, dtype=float).reshape((self.predict_steps+1, 1, 1)) * self.time_step * self.pred_interval, [1, human_num, 2]) pred_disp = pred_timestep * self.human_future_traj[:, :, 2:] self.human_future_traj[:, :, :2] = self.human_future_traj[:, :, :2] + pred_disp else: raise NotImplementedError # remove the robot if it is visible if self.robot.visible: self.human_future_traj = self.human_future_traj[:, :-1] # remove invisible humans self.human_future_traj[:, np.logical_not(self.human_visibility), :2] = 15 self.human_future_traj[:, np.logical_not(self.human_visibility), 2:] = 0 return self.human_future_traj # reset = True: reset calls this function; reset = False: step calls this function # sorted: sort all humans by distance to robot or not def generate_ob(self, reset, sort=False): """Generate observation for reset and step functions""" ob = {} # nodes visible_humans, num_visibles, self.human_visibility = self.get_num_human_in_fov() ob['robot_node'] = self.robot.get_full_state_list_noV() prev_human_pos = copy.deepcopy(self.last_human_states) self.update_last_human_states(self.human_visibility, reset=reset) # edges ob['temporal_edges'] = np.array([self.robot.vx, self.robot.vy]) # ([relative px, relative py, disp_x, disp_y], human id) all_spatial_edges = np.ones((self.max_human_num, 2)) * np.inf for i in range(self.human_num): if self.human_visibility[i]: # vector pointing from human i to robot relative_pos = np.array( [self.last_human_states[i, 0] - self.robot.px, self.last_human_states[i, 1] - self.robot.py]) all_spatial_edges[self.humans[i].id, :2] = relative_pos ob['visible_masks'] = np.zeros(self.max_human_num, dtype=np.bool) # sort all humans by distance (invisible humans will be in the end automatically) if sort: ob['spatial_edges'] = np.array(sorted(all_spatial_edges, key=lambda x: np.linalg.norm(x))) # after sorting, the visible humans must be in the front if num_visibles > 0: ob['visible_masks'][:num_visibles] = True else: ob['spatial_edges'] = all_spatial_edges ob['visible_masks'][:self.human_num] = self.human_visibility ob['spatial_edges'][np.isinf(ob['spatial_edges'])] = 15 ob['detected_human_num'] = num_visibles # if no human is detected, assume there is one dummy human at (15, 15) to make the pack_padded_sequence work if ob['detected_human_num'] == 0: ob['detected_human_num'] = 1 # update self.observed_human_ids self.observed_human_ids = np.where(self.human_visibility)[0] self.ob = ob return ob # Update the specified human's end goals in the environment randomly def update_human_pos_goal(self, human): while True: angle = np.random.random() * np.pi * 2 # add some noise to simulate all the possible cases robot could meet with human v_pref = 1.0 if human.v_pref == 0 else human.v_pref gx_noise = (np.random.random() - 0.5) * v_pref gy_noise = (np.random.random() - 0.5) * v_pref gx = self.circle_radius * np.cos(angle) + gx_noise gy = self.circle_radius * np.sin(angle) + gy_noise collide = False if not collide: break # Give human new goal human.gx = gx human.gy = gy def reset(self, phase='train', test_case=None): """ Reset the environment :return: """ if self.phase is not None: phase = self.phase if self.test_case is not None: test_case=self.test_case if self.robot is None: raise AttributeError('robot has to be set!') assert phase in ['train', 'val', 'test'] if test_case is not None: self.case_counter[phase] = test_case # test case is passed in to calculate specific seed to generate case self.global_time = 0 self.step_counter = 0 self.id_counter = 0 self.humans = [] # self.human_num = self.config.sim.human_num # initialize a list to store observed humans' IDs self.observed_human_ids = [] # train, val, and test phase should start with different seed. # case capacity: the maximum number for train(max possible int -2000), val(1000), and test(1000) # val start from seed=0, test start from seed=case_capacity['val']=1000 # train start from self.case_capacity['val'] + self.case_capacity['test']=2000 counter_offset = {'train': self.case_capacity['val'] + self.case_capacity['test'], 'val': 0, 'test': self.case_capacity['val']} # here we use a counter to calculate seed. The seed=counter_offset + case_counter self.rand_seed = counter_offset[phase] + self.case_counter[phase] + self.thisSeed np.random.seed(self.rand_seed) self.generate_robot_humans(phase) # record px, py, r of each human, used for crowd_sim_pc env self.cur_human_states = np.zeros((self.max_human_num, 3)) for i in range(self.human_num): self.cur_human_states[i] = np.array([self.humans[i].px, self.humans[i].py, self.humans[i].radius]) # case size is used to make sure that the case_counter is always between 0 and case_size[phase] self.case_counter[phase] = (self.case_counter[phase] + int(1*self.nenv)) % self.case_size[phase] # initialize potential and angular potential rob_goal_vec = np.array([self.robot.gx, self.robot.gy]) - np.array([self.robot.px, self.robot.py]) self.potential = -abs(np.linalg.norm(rob_goal_vec)) self.angle = np.arctan2(rob_goal_vec[1], rob_goal_vec[0]) - self.robot.theta if self.angle > np.pi: # self.abs_angle = np.pi * 2 - self.abs_angle self.angle = self.angle - 2 * np.pi elif self.angle < -np.pi: self.angle = self.angle + 2 * np.pi # get robot observation ob = self.generate_ob(reset=True, sort=self.config.args.sort_humans) return ob def step(self, action, update=True): """ Step the environment forward for one timestep Compute actions for all agents, detect collision, update environment and return (ob, reward, done, info) """ if self.robot.policy.name in ['ORCA', 'social_force']: # assemble observation for orca: px, py, vx, vy, r human_states = copy.deepcopy(self.last_human_states) # get orca action action = self.robot.act(human_states.tolist()) else: action = self.robot.policy.clip_action(action, self.robot.v_pref) if self.robot.kinematics == 'unicycle': self.desiredVelocity[0] = np.clip(self.desiredVelocity[0] + action.v, -self.robot.v_pref, self.robot.v_pref) action = ActionRot(self.desiredVelocity[0], action.r) human_actions = self.get_human_actions() # need to update self.human_future_traj in testing to calculate number of intrusions if self.phase == 'test': # use ground truth future positions of humans self.calc_human_future_traj(method='truth') # compute reward and episode info reward, done, episode_info = self.calc_reward(action, danger_zone='future') # apply action and update all agents self.robot.step(action) for i, human_action in enumerate(human_actions): self.humans[i].step(human_action) self.global_time += self.time_step # max episode length=time_limit/time_step self.step_counter =self.step_counter+1 info={'info':episode_info} # Add or remove at most self.human_num_range humans # if self.human_num_range == 0 -> human_num is fixed at all times if self.human_num_range > 0 and self.global_time % 5 == 0: # remove humans if np.random.rand() < 0.5: # print('before:', self.human_num,', self.min_human_num:', self.min_human_num) # if no human is visible, anyone can be removed if len(self.observed_human_ids) == 0: max_remove_num = self.human_num - self.min_human_num # print('max_remove_num, invisible', max_remove_num) else: max_remove_num = min(self.human_num - self.min_human_num, (self.human_num - 1) - max(self.observed_human_ids)) # print('max_remove_num, visible', max_remove_num) remove_num = np.random.randint(low=0, high=max_remove_num + 1) for _ in range(remove_num): self.humans.pop() self.human_num = self.human_num - remove_num # print('after:', self.human_num) self.last_human_states = self.last_human_states[:self.human_num] # add humans else: add_num = np.random.randint(low=0, high=self.human_num_range + 1) if add_num > 0: # set human ids true_add_num = 0 for i in range(self.human_num, self.human_num + add_num): if i == self.config.sim.human_num + self.human_num_range: break self.generate_random_human_position(human_num=1) self.humans[i].id = i true_add_num = true_add_num + 1 self.human_num = self.human_num + true_add_num if true_add_num > 0: self.last_human_states = np.concatenate((self.last_human_states, np.array([[15, 15, 0, 0, 0.3]]*true_add_num)), axis=0) assert self.min_human_num <= self.human_num <= self.max_human_num # compute the observation ob = self.generate_ob(reset=False, sort=self.config.args.sort_humans) # Update all humans' goals randomly midway through episode if self.random_goal_changing: if self.global_time % 5 == 0: self.update_human_goals_randomly() # Update a specific human's goal once its reached its original goal if self.end_goal_changing: for i, human in enumerate(self.humans): if norm((human.gx - human.px, human.gy - human.py)) < human.radius: if self.robot.kinematics == 'holonomic': self.humans[i] = self.generate_circle_crossing_human() self.humans[i].id = i else: self.update_human_goal(human) return ob, reward, done, info # find R(s, a) # danger_zone: how to define the personal_zone (if the robot intrudes into this zone, the info will be Danger) # circle (traditional) or future (based on true future traj of humans) def calc_reward(self, action, danger_zone='circle'): # collision detection dmin = float('inf') danger_dists = [] collision = False # collision check with humans for i, human in enumerate(self.humans): dx = human.px - self.robot.px dy = human.py - self.robot.py closest_dist = (dx ** 2 + dy ** 2) ** (1 / 2) - human.radius - self.robot.radius if closest_dist < self.discomfort_dist: danger_dists.append(closest_dist) if closest_dist < 0: collision = True break elif closest_dist < dmin: dmin = closest_dist # check if reaching the goal if self.robot.kinematics == 'unicycle': goal_radius = 0.6 else: goal_radius = self.robot.radius reaching_goal = norm( np.array(self.robot.get_position()) - np.array(self.robot.get_goal_position())) < goal_radius # use danger_zone to determine the condition for Danger if danger_zone == 'circle' or self.phase == 'train': danger_cond = dmin < self.discomfort_dist min_danger_dist = 0 else: # if the robot collides with future states, give it a collision penalty relative_pos = self.human_future_traj[1:, :, :2] - np.array([self.robot.px, self.robot.py]) relative_dist = np.linalg.norm(relative_pos, axis=-1) collision_idx = relative_dist < self.robot.radius + self.config.humans.radius # [predict_steps, human_num] danger_cond = np.any(collision_idx) # if robot is dangerously close to any human, calculate the min distance between robot and its closest human if danger_cond: min_danger_dist = np.amin(relative_dist[collision_idx]) else: min_danger_dist = 0 if self.global_time >= self.time_limit - 1: reward = 0 done = True episode_info = Timeout() elif collision: reward = self.collision_penalty done = True episode_info = Collision() elif reaching_goal: reward = self.success_reward done = True episode_info = ReachGoal() elif danger_cond: # only penalize agent for getting too close if it's visible # adjust the reward based on FPS # print(dmin) reward = (dmin - self.discomfort_dist) * self.discomfort_penalty_factor * self.time_step done = False episode_info = Danger(min_danger_dist) else: # potential reward if self.robot.kinematics == 'holonomic': pot_factor = 2 else: pot_factor = 3 potential_cur = np.linalg.norm( np.array([self.robot.px, self.robot.py]) - np.array(self.robot.get_goal_position())) reward = pot_factor * (-abs(potential_cur) - self.potential) self.potential = -abs(potential_cur) done = False episode_info = Nothing() # if the robot is near collision/arrival, it should be able to turn a large angle if self.robot.kinematics == 'unicycle': # add a rotational penalty r_spin = -4.5 * action.r ** 2 # add a penalty for going backwards if action.v < 0: r_back = -2 * abs(action.v) else: r_back = 0. reward = reward + r_spin + r_back return reward, done, episode_info def render(self, mode='human'): """ Render the current status of the environment using matplotlib """ import matplotlib.pyplot as plt import matplotlib.lines as mlines from matplotlib import patches plt.rcParams['animation.ffmpeg_path'] = '/usr/bin/ffmpeg' robot_color = 'gold' goal_color = 'red' arrow_color = 'red' arrow_style = patches.ArrowStyle("->", head_length=4, head_width=2) def calcFOVLineEndPoint(ang, point, extendFactor): # choose the extendFactor big enough # so that the endPoints of the FOVLine is out of xlim and ylim of the figure FOVLineRot = np.array([[np.cos(ang), -np.sin(ang), 0], [np.sin(ang), np.cos(ang), 0], [0, 0, 1]]) point.extend([1]) # apply rotation matrix newPoint = np.matmul(FOVLineRot, np.reshape(point, [3, 1])) # increase the distance between the line start point and the end point newPoint = [extendFactor * newPoint[0, 0], extendFactor * newPoint[1, 0], 1] return newPoint ax=self.render_axis artists=[] # add goal goal=mlines.Line2D([self.robot.gx], [self.robot.gy], color=goal_color, marker='*', linestyle='None', markersize=15, label='Goal') ax.add_artist(goal) artists.append(goal) # add robot robotX,robotY=self.robot.get_position() robot=plt.Circle((robotX,robotY), self.robot.radius, fill=True, color=robot_color) ax.add_artist(robot) artists.append(robot) # plt.legend([robot, goal], ['Robot', 'Goal'], fontsize=16) # compute orientation in each step and add arrow to show the direction radius = self.robot.radius arrowStartEnd=[] robot_theta = self.robot.theta if self.robot.kinematics == 'unicycle' else np.arctan2(self.robot.vy, self.robot.vx) arrowStartEnd.append(((robotX, robotY), (robotX + radius * np.cos(robot_theta), robotY + radius * np.sin(robot_theta)))) for i, human in enumerate(self.humans): theta = np.arctan2(human.vy, human.vx) arrowStartEnd.append(((human.px, human.py), (human.px + radius * np.cos(theta), human.py + radius * np.sin(theta)))) arrows = [patches.FancyArrowPatch(*arrow, color=arrow_color, arrowstyle=arrow_style) for arrow in arrowStartEnd] for arrow in arrows: ax.add_artist(arrow) artists.append(arrow) # draw FOV for the robot # add robot FOV if self.robot.FOV < 2 * np.pi: FOVAng = self.robot_fov / 2 FOVLine1 = mlines.Line2D([0, 0], [0, 0], linestyle='--') FOVLine2 = mlines.Line2D([0, 0], [0, 0], linestyle='--') startPointX = robotX startPointY = robotY endPointX = robotX + radius * np.cos(robot_theta) endPointY = robotY + radius * np.sin(robot_theta) # transform the vector back to world frame origin, apply rotation matrix, and get end point of FOVLine # the start point of the FOVLine is the center of the robot FOVEndPoint1 = calcFOVLineEndPoint(FOVAng, [endPointX - startPointX, endPointY - startPointY], 20. / self.robot.radius) FOVLine1.set_xdata(np.array([startPointX, startPointX + FOVEndPoint1[0]])) FOVLine1.set_ydata(np.array([startPointY, startPointY + FOVEndPoint1[1]])) FOVEndPoint2 = calcFOVLineEndPoint(-FOVAng, [endPointX - startPointX, endPointY - startPointY], 20. / self.robot.radius) FOVLine2.set_xdata(np.array([startPointX, startPointX + FOVEndPoint2[0]])) FOVLine2.set_ydata(np.array([startPointY, startPointY + FOVEndPoint2[1]])) ax.add_artist(FOVLine1) ax.add_artist(FOVLine2) artists.append(FOVLine1) artists.append(FOVLine2) # add an arc of robot's sensor range sensor_range = plt.Circle(self.robot.get_position(), self.robot.sensor_range + self.robot.radius+self.config.humans.radius, fill=False, linestyle='--') ax.add_artist(sensor_range) artists.append(sensor_range) # add humans and change the color of them based on visibility human_circles = [plt.Circle(human.get_position(), human.radius, fill=False, linewidth=1.5) for human in self.humans] # hardcoded for now actual_arena_size = self.arena_size + 0.5 for i in range(len(self.humans)): ax.add_artist(human_circles[i]) artists.append(human_circles[i]) # green: visible; red: invisible # if self.detect_visible(self.robot, self.humans[i], robot1=True): if self.human_visibility[i]: human_circles[i].set_color(c='g') else: human_circles[i].set_color(c='r') if self.humans[i].id in self.observed_human_ids: human_circles[i].set_color(c='b') plt.text(self.humans[i].px - 0.1, self.humans[i].py - 0.1, str(self.humans[i].id), color='black', fontsize=12) plt.pause(0.01) for item in artists: item.remove() # there should be a better way to do this. For example, # initially use add_artist and draw_artist later on for t in ax.texts: t.set_visible(False) ================================================ FILE: crowd_sim/envs/crowd_sim_var_num_collect.py ================================================ import gym import numpy as np from numpy.linalg import norm import copy from crowd_sim.envs import * from crowd_sim.envs.utils.info import * class CrowdSimVarNumCollect(CrowdSimVarNum): """ An environment for collecting a dataset of simulated humans to train GST predictor (used in collect_data.py) The observation contains all detected humans A key in ob indicates how many humans are detected """ def __init__(self): """ Movement simulation for n+1 agents Agent can either be human or robot. humans are controlled by a unknown and fixed policy. robot is controlled by a known and learnable policy. """ super().__init__() def set_robot(self, robot): self.robot = robot # set observation space and action space # we set the max and min of action/observation space as inf # clip the action and observation as you need d={} # frame id, human prediction id, px, py d['pred_info'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.config.sim.human_num + self.config.sim.human_num_range, 4), dtype=np.float32) self.observation_space=gym.spaces.Dict(d) high = np.inf * np.ones([2, ]) self.action_space = gym.spaces.Box(-high, high, dtype=np.float32) def reset(self, phase='train', test_case=None): """ Set px, py, gx, gy, vx, vy, theta for robot and humans :return: """ if self.phase is not None: phase = self.phase if self.test_case is not None: test_case=self.test_case if self.robot is None: raise AttributeError('robot has to be set!') assert phase in ['train', 'val', 'test'] if test_case is not None: self.case_counter[phase] = test_case # test case is passed in to calculate specific seed to generate case self.global_time = 0 self.id_counter = 0 self.humans = [] # self.human_num = self.config.sim.human_num # initialize a list to store observed humans' IDs self.observed_human_ids = [] # train, val, and test phase should start with different seed. # case capacity: the maximum number for train(max possible int -2000), val(1000), and test(1000) # val start from seed=0, test start from seed=case_capacity['val']=1000 # train start from self.case_capacity['val'] + self.case_capacity['test']=2000 counter_offset = {'train': self.case_capacity['val'] + self.case_capacity['test'], 'val': 0, 'test': self.case_capacity['val']} # here we use a counter to calculate seed. The seed=counter_offset + case_counter np.random.seed(counter_offset[phase] + self.case_counter[phase] + self.thisSeed) self.rand_seed = counter_offset[phase] + self.case_counter[phase] + self.thisSeed # print(counter_offset[phase] + self.case_counter[phase] + self.thisSeed) # np.random.seed(1038) self.generate_robot_humans(phase) self.last_human_observability = np.zeros(self.human_num, dtype=bool) self.human_pred_id = np.arange(0, self.human_num) self.max_human_id = self.human_num # case size is used to make sure that the case_counter is always between 0 and case_size[phase] self.case_counter[phase] = (self.case_counter[phase] + int(1*self.nenv)) % self.case_size[phase] # get robot observation ob = self.generate_ob(reset=True) # initialize potential self.potential = -abs(np.linalg.norm(np.array([self.robot.px, self.robot.py]) - np.array([self.robot.gx, self.robot.gy]))) return ob # reset = True: reset calls this function; reset = False: step calls this function def generate_ob(self, reset, sort=False): # we should keep human ID tracking for traj pred! #assert sort == False ob = {} # nodes visible_humans, num_visibles, human_visibility = self.get_num_human_in_fov() humans_out = np.logical_and(self.last_human_observability, np.logical_not(human_visibility)) num_humans_out = np.sum(humans_out) self.human_pred_id[humans_out] = np.arange(self.max_human_id, self.max_human_id+num_humans_out) self.max_human_id = self.max_human_id + num_humans_out self.update_last_human_states(human_visibility, reset=reset) # ([relative px, relative py, disp_x, disp_y], human id) all_spatial_edges = np.ones((self.config.sim.human_num + self.config.sim.human_num_range, 2)) * np.inf for i in range(self.human_num): if human_visibility[i]: all_spatial_edges[self.humans[i].id, :2] = self.last_human_states[i, :2] frame_array = np.repeat(self.global_time/self.config.data.pred_timestep, self.human_num) ob['pred_info'] = np.concatenate((frame_array.reshape((self.human_num, 1)), self.human_pred_id.reshape((self.human_num, 1)), all_spatial_edges), axis=1) # update self.observed_human_ids self.observed_human_ids = np.where(human_visibility)[0] self.ob = ob self.last_human_observability = copy.deepcopy(human_visibility) return ob # find R(s, a) # danger_zone: how to define the personal_zone (if the robot intrudes into this zone, the info will be Danger) # circle (traditional) or future (based on true future traj of humans) def calc_reward(self, action, danger_zone='circle'): # collision detection dmin = float('inf') danger_dists = [] collision = False for i, human in enumerate(self.humans): dx = human.px - self.robot.px dy = human.py - self.robot.py closest_dist = (dx ** 2 + dy ** 2) ** (1 / 2) - human.radius - self.robot.radius if closest_dist < self.discomfort_dist: danger_dists.append(closest_dist) if closest_dist < 0: collision = True # logging.debug("Collision: distance between robot and p{} is {:.2E}".format(i, closest_dist)) break elif closest_dist < dmin: dmin = closest_dist # check if reaching the goal reaching_goal = norm( np.array(self.robot.get_position()) - np.array(self.robot.get_goal_position())) < self.robot.radius if self.global_time >= 40000: done = True episode_info = Timeout() elif collision: done = False episode_info = Collision() elif reaching_goal: done = False episode_info = ReachGoal() # randomize human attributes # median of all humans if np.random.uniform(0, 1) < 0.5: human_pos = np.zeros((self.human_num, 2)) for i, human in enumerate(self.humans): human_pos[i] = np.array([human.px, human.py]) self.robot.gx, self.robot.gy = np.median(human_pos, axis=0) # random goal else: self.robot.gx, self.robot.gy = np.random.uniform(-self.arena_size, self.arena_size, size=2) else: done = False episode_info = Nothing() return 0, done, episode_info ================================================ FILE: crowd_sim/envs/ros_turtlebot2i_env.py ================================================ import gym import numpy as np from numpy.linalg import norm import pandas as pd import os # prevent import error if other code is run in conda env try: # import ROS related packages import rospy import tf2_ros from geometry_msgs.msg import Twist, TransformStamped, PoseArray import tf from sensor_msgs.msg import JointState from threading import Lock from message_filters import ApproximateTimeSynchronizer, TimeSynchronizer, Subscriber except: pass import copy import sys from crowd_sim.envs.crowd_sim_pred_real_gst import CrowdSimPredRealGST class rosTurtlebot2iEnv(CrowdSimPredRealGST): ''' Environment for testing a simulated policy on a real Turtlebot2i To use it, change the env_name in arguments.py in the tested model folder to 'rosTurtlebot2iEnv-v0' ''' metadata = {'render.modes': ['human']} def __init__(self): super(CrowdSimPredRealGST, self).__init__() # subscriber callback function will change these two variables self.robotMsg=None # robot state message self.humanMsg=None # human state message self.jointMsg=None # joint state message self.currentTime=0.0 self.lastTime=0.0 # store time for calculating time interval self.human_visibility=None self.current_human_states = None # (px,py) self.detectedHumanNum=0 # goal positions will be set manually in self.reset() self.goal_x = 0.0 self.goal_y = 0.0 self.last_left = 0. self.last_right = 0. self.last_w = 0.0 self.jointVel=None # to calculate vx, vy self.last_v = 0.0 self.desiredVelocity=[0.0,0.0] self.mutex = Lock() def configure(self, config): super().configure(config) # kalman filter for human tracking if config.sim.predict_method == 'none': self.add_pred = False else: self.add_pred = True # define ob space and action space self.set_ob_act_space() # ROS rospy.init_node('ros_turtlebot2i_env_node', anonymous=True) self.actionPublisher = rospy.Publisher('/cmd_vel_mux/input/navi', Twist, queue_size=1) self.tfBuffer = tf2_ros.Buffer() self.transformListener = tf2_ros.TransformListener(self.tfBuffer) # ROS subscriber jointStateSub = Subscriber("/joint_states", JointState) humanStatesSub = Subscriber('/dr_spaam_detections', PoseArray) # human px, py, visible if self.use_dummy_detect: subList = [jointStateSub] else: subList = [jointStateSub, humanStatesSub] # synchronize the robot base joint states and humnan detections with at most 1 seconds of difference self.ats = ApproximateTimeSynchronizer(subList, queue_size=1, slop=1) # if ignore sensor inputs and use fake human detections if self.use_dummy_detect: self.ats.registerCallback(self.state_cb_dummy) else: self.ats.registerCallback(self.state_cb) rospy.on_shutdown(self.shutdown) def set_robot(self, robot): self.robot = robot def set_ob_act_space(self): # set observation space and action space # we set the max and min of action/observation space as inf # clip the action and observation as you need d = {} # robot node: num_visible_humans, px, py, r, gx, gy, v_pref, theta d['robot_node'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(1, 7,), dtype=np.float32) # only consider all temporal edges (human_num+1) and spatial edges pointing to robot (human_num) d['temporal_edges'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(1, 2,), dtype=np.float32) # add prediction if self.add_pred: self.spatial_edge_dim = int(2 * (self.predict_steps + 1)) d['spatial_edges'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.config.sim.human_num + self.config.sim.human_num_range, self.spatial_edge_dim), dtype=np.float32) # no prediction else: d['spatial_edges'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.config.sim.human_num + self.config.sim.human_num_range, 2), dtype=np.float32) # number of humans detected at each timestep # masks for gst pred model # whether each human is visible to robot d['visible_masks'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.config.sim.human_num + self.config.sim.human_num_range,), dtype=np.bool) # number of humans detected at each timestep d['detected_human_num'] = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(1,), dtype=np.float32) self.observation_space = gym.spaces.Dict(d) high = np.inf * np.ones([2, ]) self.action_space = gym.spaces.Box(-high, high, dtype=np.float32) # (used if self.use_dummy_detect is False) # callback function to store the realtime messages from the robot to this env def state_cb(self, jointStateMsg, humanArrayMsg): self.humanMsg=humanArrayMsg.poses self.jointMsg=jointStateMsg # (used if self.use_dummy_detect is True) # callback function to store the realtime messages from the robot to this env # no need to real human message def state_cb_dummy(self, jointStateMsg): self.jointMsg = jointStateMsg def readMsg(self): """ read messages passed through ROS & prepare for generating obervations this function should be called right before generate_ob() is called """ self.mutex.acquire() # get time # print(self.jointMsg.header.stamp.secs, self.jointMsg.header.stamp.nsecs) self.currentTime = self.jointMsg.header.stamp.secs + self.jointMsg.header.stamp.nsecs / 1e9 # get robot pose from T265 SLAM camera try: self.robotMsg = self.tfBuffer.lookup_transform('t265_odom_frame', 't265_pose_frame', rospy.Time.now(), rospy.Duration(1.0)) except: print("problem in getting transform") # get robot base velocity from the base try: self.jointVel=self.jointMsg.velocity except: print("problem in getting joint velocity") # print(self.robotMsg, "ROBOT mSG") # store the robot pose and robot base velocity in self variables self.robot.px = -self.robotMsg.transform.translation.y self.robot.py = self.robotMsg.transform.translation.x print('robot pos:', self.robot.px, self.robot.py) quaternion = ( self.robotMsg.transform.rotation.x, self.robotMsg.transform.rotation.y, self.robotMsg.transform.rotation.z, self.robotMsg.transform.rotation.w ) if self.use_dummy_detect: self.detectedHumanNum = 1 self.human_visibility = np.zeros((self.max_human_num,), dtype=np.bool) else: # human states self.detectedHumanNum=min(len(self.humanMsg), self.max_human_num) self.current_human_states_raw = np.ones((self.detectedHumanNum, 2)) * 15 self.human_visibility=np.zeros((self.max_human_num, ), dtype=np.bool) for i in range(self.detectedHumanNum): # use hard coded map to filter out obstacles global_x = self.robot.px + self.humanMsg[i].position.x global_y = self.robot.py + self.humanMsg[i].position.y # if -2.5 < global_x < 2.5 and -2.5 < global_y < 2.5: if True: self.current_human_states_raw[i,0]=self.humanMsg[i].position.x self.current_human_states_raw[i,1] = self.humanMsg[i].position.y self.mutex.release() # robot orientation self.robot.theta = tf.transformations.euler_from_quaternion(quaternion)[2] + np.pi / 2 if self.robot.theta < 0: self.robot.theta = self.robot.theta + 2 * np.pi # add 180 degrees because of the transform from lidar frame to t265 camera frame hMatrix = np.array([[np.cos(self.robot.theta+np.pi), -np.sin(self.robot.theta+np.pi), 0, 0], [np.sin(self.robot.theta+np.pi), np.cos(self.robot.theta+np.pi), 0, 0], [0,0,1,0], [0,0,0,1]]) # if we detected at least one person self.current_human_states = np.ones((self.max_human_num, 2)) * 15 if not self.use_dummy_detect: for j in range(self.detectedHumanNum): xy=np.matmul(hMatrix,np.array([[self.current_human_states_raw[j,0], self.current_human_states_raw[j,1], 0, 1]]).T) self.current_human_states[j]=xy[:2,0] else: self.current_human_states[0] = np.array([0, 1]) - np.array([self.robot.px, self.robot.py]) self.robot.vx = self.last_v * np.cos(self.robot.theta) self.robot.vy = self.last_v * np.sin(self.robot.theta) def reset(self): """ Reset function """ # stop the turtlebot self.smoothStop() self.step_counter=0 self.currentTime=0.0 self.lastTime=0.0 self.global_time = 0. self.human_visibility = np.zeros((self.max_human_num,), dtype=np.bool) self.detectedHumanNum=0 self.current_human_states = np.ones((self.max_human_num, 2)) * 15 self.desiredVelocity = [0.0, 0.0] self.last_left = 0. self.last_right = 0. self.last_w = 0.0 self.last_v = 0.0 while True: a = input("Press y for the next episode \t") if a == "y": self.robot.gx=float(input("Input goal location in x-axis\t")) self.robot.gy=float(input("Input goal location in y-axis\t")) break else: sys.exit() if self.record: self.episodeRecoder.robot_goal.append([self.robot.gx, self.robot.gy]) self.readMsg() ob=self.generate_ob() # generate initial obs return ob # input: v, w # output: v, w def smooth(self, v, w): beta = 0.1 #TODO: you use 0.2 in the simulator left = (2. * v - 0.23 * w) / (2. * 0.035) right = (2. * v + 0.23 * w) / (2. * 0.035) # print('199:', left, right) left = np.clip(left, -17.5, 17.5) right = np.clip(right, -17.5, 17.5) # print('202:', left, right) left = (1.-beta) * self.last_left + beta * left right = (1.-beta) * self.last_right + beta * right # print('205:', left, right) self.last_left = copy.deepcopy(left) self.last_right = copy.deepcopy(right) v_smooth = 0.035 / 2 * (left + right) w_smooth = 0.035 / 0.23 * (right - left) return v_smooth, w_smooth def generate_ob(self): ob = {} ob['robot_node'] = np.array([[self.robot.px, self.robot.py, self.robot.radius, self.robot.gx, self.robot.gy, self.robot.v_pref, self.robot.theta]]) ob['temporal_edges']=np.array([[self.robot.vx, self.robot.vy]]) # print(self.current_human_states.shape) spatial_edges=self.current_human_states if self.add_pred: # predicted steps will be filled in the vec_pretext_normalize wrapper spatial_edges=np.concatenate([spatial_edges, np.zeros((self.max_human_num, 2 * self.predict_steps))], axis=1) else: # sort humans by distance to robot spatial_edges = np.array(sorted(spatial_edges, key=lambda x: np.linalg.norm(x))) print(spatial_edges) ob['spatial_edges'] = spatial_edges ob['visible_masks'] = self.human_visibility ob['detected_human_num'] = self.detectedHumanNum if ob['detected_human_num'] == 0: ob['detected_human_num'] = 1 print(ob['detected_human_num']) return ob def step(self, action, update=True): """ Step function """ print("Step", self.step_counter) # process action realAction = Twist() if self.load_act: # load action from file for robot dynamics checking v_unsmooth= self.episodeRecoder.v_list[self.step_counter] # in the simulator we use and recrod delta theta. We convert it to omega by dividing it by the time interval w_unsmooth = self.episodeRecoder.delta_theta_list[self.step_counter] / self.delta_t # v_smooth, w_smooth = self.desiredVelocity[0], self.desiredVelocity[1] v_smooth, w_smooth = self.smooth(v_unsmooth, w_unsmooth) else: action = self.robot.policy.clip_action(action, None) self.desiredVelocity[0] = np.clip(self.desiredVelocity[0] + action.v, -self.robot.v_pref, self.robot.v_pref) self.desiredVelocity[1] = action.r / self.fixed_time_interval # TODO: dynamic time step is not supported now v_smooth, w_smooth = self.smooth(self.desiredVelocity[0], self.desiredVelocity[1]) self.last_v = v_smooth realAction.linear.x = v_smooth realAction.angular.z = w_smooth self.actionPublisher.publish(realAction) rospy.sleep(self.ROSStepInterval) # act as frame skip # get the latest states self.readMsg() # update time if self.step_counter==0: # if it is the first step of the episode self.delta_t = np.inf else: # time interval between two steps if self.use_fixed_time_interval: self.delta_t=self.fixed_time_interval else: self.delta_t = self.currentTime - self.lastTime print('delta_t:', self.currentTime - self.lastTime) #print('actual delta t:', currentTime - self.baseEnv.lastTime) self.global_time = self.global_time + self.delta_t self.step_counter=self.step_counter+1 self.lastTime = self.currentTime # generate new observation ob=self.generate_ob() # calculate reward reward = 0 # determine if the episode ends done=False reaching_goal = norm(np.array([self.robot.gx, self.robot.gy]) - np.array([self.robot.px, self.robot.py])) < 0.6 if self.global_time >= self.time_limit: done = True print("Timeout") elif reaching_goal: done = True print("Goal Achieved") elif self.load_act and self.record: if self.step_counter >= len(self.episodeRecoder.v_list): done = True else: done = False info = {'info': None} if self.record: self.episodeRecoder.wheelVelList.append(self.jointVel) # it is the calculated wheel velocity, not the measured self.episodeRecoder.actionList.append([v_smooth, w_smooth]) self.episodeRecoder.positionList.append([self.robot.px, self.robot.py]) self.episodeRecoder.orientationList.append(self.robot.theta) if done: print('DOne!') if self.record: self.episodeRecoder.saveEpisode(self.case_counter['test']) return ob, reward, done, info def render(self, mode='human'): import matplotlib.pyplot as plt import matplotlib.lines as mlines from matplotlib import patches plt.rcParams['animation.ffmpeg_path'] = '/usr/bin/ffmpeg' robot_color = 'yellow' goal_color = 'red' arrow_color = 'red' arrow_style = patches.ArrowStyle("->", head_length=4, head_width=2) def calcFOVLineEndPoint(ang, point, extendFactor): # choose the extendFactor big enough # so that the endPoints of the FOVLine is out of xlim and ylim of the figure FOVLineRot = np.array([[np.cos(ang), -np.sin(ang), 0], [np.sin(ang), np.cos(ang), 0], [0, 0, 1]]) point.extend([1]) # apply rotation matrix newPoint = np.matmul(FOVLineRot, np.reshape(point, [3, 1])) # increase the distance between the line start point and the end point newPoint = [extendFactor * newPoint[0, 0], extendFactor * newPoint[1, 0], 1] return newPoint ax = self.render_axis artists = [] # add goal goal = mlines.Line2D([self.robot.gx], [self.robot.gy], color=goal_color, marker='*', linestyle='None', markersize=15, label='Goal') ax.add_artist(goal) artists.append(goal) # add robot robotX, robotY = self.robot.px, self.robot.py robot = plt.Circle((robotX, robotY), self.robot.radius, fill=True, color=robot_color) ax.add_artist(robot) artists.append(robot) plt.legend([robot, goal], ['Robot', 'Goal'], fontsize=16) # compute orientation in each step and add arrow to show the direction radius = 0.1 arrowStartEnd = [] robot_theta = self.robot.theta arrowStartEnd.append( ((robotX, robotY), (robotX + radius * np.cos(robot_theta), robotY + radius * np.sin(robot_theta)))) arrows = [patches.FancyArrowPatch(*arrow, color=arrow_color, arrowstyle=arrow_style) for arrow in arrowStartEnd] for arrow in arrows: ax.add_artist(arrow) artists.append(arrow) # add humans and change the color of them based on visibility # print(self.current_human_states.shape) # print(self.track_num) # render tracked humans human_circles = [plt.Circle(self.current_human_states[i] + np.array([self.robot.px, self.robot.py]), 0.2, fill=False) \ for i in range(self.max_human_num)] # render untracked humans # human_circles = [ # plt.Circle(self.current_human_states_raw[i] + np.array([self.robot.px, self.robot.py]), 0.2, fill=False) for i # in range(self.detectedHumanNum)] for i in range(self.max_human_num): # for i in range(self.detectedHumanNum): ax.add_artist(human_circles[i]) artists.append(human_circles[i]) # green: visible; red: invisible if self.human_visibility[i]: human_circles[i].set_color(c='g') else: human_circles[i].set_color(c='r') plt.pause(0.1) for item in artists: item.remove() # there should be a better way to do this. For example, # initially use add_artist and draw_artist later on for t in ax.texts: t.set_visible(False) def shutdown(self): self.smoothStop() print("You are stopping the robot!") self.reset() def smoothStop(self): realAction = Twist() self.actionPublisher.publish(Twist()) ================================================ FILE: crowd_sim/envs/utils/__init__.py ================================================ ================================================ FILE: crowd_sim/envs/utils/action.py ================================================ from collections import namedtuple ActionXY = namedtuple('ActionXY', ['vx', 'vy']) ActionRot = namedtuple('ActionRot', ['v', 'r']) ================================================ FILE: crowd_sim/envs/utils/agent.py ================================================ import numpy as np from numpy.linalg import norm import abc import logging from crowd_nav.policy.policy_factory import policy_factory from crowd_sim.envs.utils.action import ActionXY, ActionRot from crowd_sim.envs.utils.state import ObservableState, FullState class Agent(object): def __init__(self, config, section): """ Base class for robot and human. Have the physical attributes of an agent. """ subconfig = config.robot if section == 'robot' else config.humans self.visible = subconfig.visible self.v_pref = subconfig.v_pref self.radius = subconfig.radius # randomize neighbor_dist of ORCA if config.env.randomize_attributes: config.orca.neighbor_dist = np.random.uniform(5, 10) self.policy = policy_factory[subconfig.policy](config) self.sensor = subconfig.sensor self.FOV = np.pi * subconfig.FOV # for humans: we only have holonomic kinematics; for robot: depend on config self.kinematics = 'holonomic' if section == 'humans' else config.action_space.kinematics self.px = None self.py = None self.gx = None self.gy = None self.vx = None self.vy = None self.theta = None self.time_step = config.env.time_step self.policy.time_step = config.env.time_step def print_info(self): logging.info('Agent is {} and has {} kinematic constraint'.format( 'visible' if self.visible else 'invisible', self.kinematics)) def sample_random_attributes(self): """ Sample agent radius and v_pref attribute from certain distribution :return: """ self.v_pref = np.random.uniform(0.5, 1.5) self.radius = np.random.uniform(0.3, 0.5) def set(self, px, py, gx, gy, vx, vy, theta, radius=None, v_pref=None): self.px = px self.py = py self.gx = gx self.gy = gy self.vx = vx self.vy = vy self.theta = theta if radius is not None: self.radius = radius if v_pref is not None: self.v_pref = v_pref # self.px, self.py, self.vx, self.vy, self.radius, self.gx, self.gy, self.v_pref, self.theta def set_list(self, px, py, vx, vy, radius, gx, gy, v_pref, theta): self.px = px self.py = py self.gx = gx self.gy = gy self.vx = vx self.vy = vy self.theta = theta self.radius = radius self.v_pref = v_pref def get_observable_state(self): return ObservableState(self.px, self.py, self.vx, self.vy, self.radius) def get_observable_state_list(self): return [self.px, self.py, self.vx, self.vy, self.radius] def get_observable_state_list_noV(self): return [self.px, self.py, self.radius] def get_next_observable_state(self, action): self.check_validity(action) pos = self.compute_position(action, self.time_step) next_px, next_py = pos if self.kinematics == 'holonomic': next_vx = action.vx next_vy = action.vy else: next_theta = self.theta + action.r next_vx = action.v * np.cos(next_theta) next_vy = action.v * np.sin(next_theta) return ObservableState(next_px, next_py, next_vx, next_vy, self.radius) def get_full_state(self): return FullState(self.px, self.py, self.vx, self.vy, self.radius, self.gx, self.gy, self.v_pref, self.theta) def get_full_state_list(self): return [self.px, self.py, self.vx, self.vy, self.radius, self.gx, self.gy, self.v_pref, self.theta] def get_full_state_list_noV(self): return [self.px, self.py, self.radius, self.gx, self.gy, self.v_pref, self.theta] # return [self.px, self.py, self.radius, self.gx, self.gy, self.v_pref] def get_position(self): return self.px, self.py def set_position(self, position): self.px = position[0] self.py = position[1] def get_goal_position(self): return self.gx, self.gy def get_velocity(self): return self.vx, self.vy def set_velocity(self, velocity): self.vx = velocity[0] self.vy = velocity[1] @abc.abstractmethod def act(self, ob): """ Compute state using received observation and pass it to policy """ return def check_validity(self, action): if self.kinematics == 'holonomic': assert isinstance(action, ActionXY) else: assert isinstance(action, ActionRot) def compute_position(self, action, delta_t): self.check_validity(action) if self.kinematics == 'holonomic': px = self.px + action.vx * delta_t py = self.py + action.vy * delta_t # unicycle else: # naive dynamics # theta = self.theta + action.r * delta_t # if action.r is w # # theta = self.theta + action.r # if action.r is delta theta # px = self.px + np.cos(theta) * action.v * delta_t # py = self.py + np.sin(theta) * action.v * delta_t # differential drive epsilon = 0.0001 if abs(action.r) < epsilon: R = 0 else: w = action.r/delta_t # action.r is delta theta R = action.v/w px = self.px - R * np.sin(self.theta) + R * np.sin(self.theta + action.r) py = self.py + R * np.cos(self.theta) - R * np.cos(self.theta + action.r) return px, py def step(self, action): """ Perform an action and update the state """ self.check_validity(action) pos = self.compute_position(action, self.time_step) self.px, self.py = pos if self.kinematics == 'holonomic': self.vx = action.vx self.vy = action.vy else: self.theta = (self.theta + action.r) % (2 * np.pi) self.vx = action.v * np.cos(self.theta) self.vy = action.v * np.sin(self.theta) def one_step_lookahead(self, pos, action): px, py = pos self.check_validity(action) new_px = px + action.vx * self.time_step new_py = py + action.vy * self.time_step new_vx = action.vx new_vy = action.vy return [new_px, new_py, new_vx, new_vy] def reached_destination(self): return norm(np.array(self.get_position()) - np.array(self.get_goal_position())) < self.radius ================================================ FILE: crowd_sim/envs/utils/human.py ================================================ from crowd_sim.envs.utils.agent import Agent from crowd_sim.envs.utils.state import JointState class Human(Agent): # see Agent class in agent.py for details!!! def __init__(self, config, section): super().__init__(config, section) self.isObstacle = False # whether the human is a static obstacle (part of wall) or a moving agent self.id = None # the human's ID, used for collecting traj data self.observed_id = -1 # if it is observed, give it a tracking ID # ob: a list of observable states def act(self, ob): """ The state for human is its full state and all other agents' observable states :param ob: :return: """ state = JointState(self.get_full_state(), ob) action = self.policy.predict(state) return action # ob: a joint state (ego agent's full state + other agents' observable states) def act_joint_state(self, ob): """ The state for human is its full state and all other agents' observable states :param ob: :return: """ action = self.policy.predict(ob) return action ================================================ FILE: crowd_sim/envs/utils/info.py ================================================ class Timeout(object): def __init__(self): pass def __str__(self): return 'Timeout' class ReachGoal(object): def __init__(self): pass def __str__(self): return 'Reaching goal' class Danger(object): def __init__(self, min_dist): self.min_dist = min_dist def __str__(self): return 'Too close' class Collision(object): def __init__(self): pass def __str__(self): return 'Collision' class OutRoad(object): def __init__(self): pass def __str__(self): return 'Out of road' class Nothing(object): def __init__(self): pass def __str__(self): return '' ================================================ FILE: crowd_sim/envs/utils/recorder.py ================================================ import pandas as pd import os import numpy as np class Recoder(object): def __init__(self): self.unsmoothed_actions = [] self.actionList = [] # recorded in turtlebot_env.step. [trans,rot] self.wheelVelList = [] # recorded in apply_action(). [left,right] self.orientationList = [] # recorded in calc_state() [angle] self.positionList = [] # recorded in calc_state() [x,y] self.robot_goal = [] self.saveTo='data/unicycle/selfAttn_truePred_noiseActt/record/real_recordings/' # self.loadFrom = 'data/unicycle/selfAttn_truePred_noiseActt/record/sim_recordings/ep0-1/action.csv' self.loadFrom = 'data/unicycle/selfAttn_truePred_noiseActt/record/sim_recordings/ep0-1point5/unsmoothed_action.csv' self.loadedAction=None self.episodeInitNum=0 def saveEpisode(self,episodeCounter): savePath=os.path.join(self.saveTo,'ep'+str(self.episodeInitNum+episodeCounter)) #savePath = os.path.join(self.saveTo, 'ep' + str(4)) if not os.path.exists(savePath): #shutil.rmtree(savePath) os.makedirs(savePath) if len(self.actionList) > 0: action = pd.DataFrame({'trans': np.array(self.actionList)[:,0], 'rot': np.array(self.actionList)[:,1]}) action.to_csv(os.path.join(savePath, 'action.csv'), index=False) if len(self.wheelVelList) > 0: wheelVel=pd.DataFrame({'left': np.array(self.wheelVelList)[:,0], 'right': np.array(self.wheelVelList)[:,1]}) wheelVel.to_csv(os.path.join(savePath, 'wheelVel.csv'), index=False) if len(self.orientationList) > 0: orientation = pd.DataFrame({'ori': np.array(self.orientationList)}) orientation.to_csv(os.path.join(savePath, 'orientation.csv'), index=False) if len(self.positionList) > 0: position = pd.DataFrame({'x': np.array(self.positionList)[:, 0], 'y': np.array(self.positionList)[:, 1]}) position.to_csv(os.path.join(savePath, 'position.csv'), index=False) if len(self.unsmoothed_actions) > 0: unsmooth_action = pd.DataFrame({'trans': np.array(self.unsmoothed_actions)[:,0], 'rot': np.array(self.unsmoothed_actions)[:,1]}) unsmooth_action.to_csv(os.path.join(savePath, 'unsmoothed_action.csv'), index=False) if len(self.robot_goal) > 0: robot_goal = pd.DataFrame({'x': np.array(self.robot_goal)[:, 0], 'y': np.array(self.robot_goal)[:, 1]}) robot_goal.to_csv(os.path.join(savePath, 'robot_goal.csv'), index=False) print("csv written") self.clear() def loadActions(self): self.loadedAction = pd.read_csv(self.loadFrom) self.v_list = self.loadedAction['trans'] self.delta_theta_list = self.loadedAction['rot'] print("Reading actions from", self.loadFrom) def clear(self): self.actionList = [] # recorded in turtlebot_env.step. [trans,rot] self.wheelVelList = [] # recorded in apply_action(). [left,right] self.orientationList = [] # recorded in calc_state() [angle] self.positionList = [] # recorded in calc_state() [x,y] self.unsmoothed_actions = [] self.robot_goal = [] class humanRecoder(object): def __init__(self, human_num): self.human_num = human_num # list of [px, py, radius, v_pref] with length = human_num self.initPosList = [[] for i in range(self.human_num)] # list of goal lists with length = human_num # i-th element is a list i-th human's goals in the episode self.goalPosList = [[] for i in range(self.human_num)] # update self.initPosList # human_id: 0~4 # pos: list of [px, py, radius, v_pref] def addInitPos(self, human_id, px, py, radius, v_pref): self.initPosList[human_id] = [px, py, radius, v_pref] # append [px, py] at the end of i-th element of list def addGoalPos(self, human_id, px, py): self.goalPosList[human_id].append([px, py]) def loadLists(self, initPos, goalPos): self.initPosList = initPos self.goalPosList = goalPos def getInitList(self): return self.initPosList def getGoalList(self): return self.goalPosList def getInitPos(self, human_id): return self.initPosList[human_id] # get next goal position for human i # if the last goal is already gotten before, return None def getNextGoalPos(self, human_id): # if the list is empty if not self.goalPosList[human_id]: return None return self.goalPosList[human_id].pop(0) # check whether human i has used up its goal def goalIsEmpty(self, human_id): if not self.goalPosList[human_id]: return True else: return False def clear(self): self.initPosList = [[] for i in range(self.human_num)] self.goalPosList = [[] for i in range(self.human_num)] class jointStateRecoder(object): def __init__(self, human_num): self.human_num = human_num # px, py, r, gx, gy, v_pref, theta, vx, vy self.robot_s = [] # nested list of [px1, ..., px5] self.humans_px = [] # nested list of [py1, ..., py5] self.humans_py = [] # record the random seed for this episode self.episode_num = [] # data labels: self.episode_num_label = [] self.traj_ratio = [] self.time_ratio = [] self.idle_time = [] # append a joint state to the lists # ob should be the ob returned from env.step() def add_traj(self, seed, ob, srnn): # if ob is a dictionary from crowd_sim_dict.py if srnn: robot_s_noV = ob['robot_node'][0, 0].cpu().numpy() robot_v = ob['edges'][0, 0].cpu().numpy() self.robot_s.append(np.concatenate((robot_s_noV, robot_v))) self.humans_px.append(ob['edges'][0, 1:, 0].cpu().numpy()) self.humans_py.append(ob['edges'][0, 1:, 1].cpu().numpy()) # if ob is a list from crowd_sim.py else: ob = ob[0].cpu().numpy() # px, py, r, gx, gy, v_pref, theta, vx, vy self.robot_s.append(np.concatenate((ob[1:3], ob[5:10], ob[3:5]))) px_list, py_list = [], [] # human num = (ob.shape - 10) // 5 for i in range((ob.shape[0] - 10) // 5): px_idx = 5 * i + 10 py_idx = 5 * i + 11 px_list.append(ob[px_idx]) py_list.append(ob[py_idx]) self.humans_px.append(px_list) self.humans_py.append(py_list) self.episode_num.append(seed) # seed: the random seed of this episode from env # respond_traj_len, respond_time: the total traj length and total timesteps when the user makes last choice # traj_len, tot_time: total traj length and timesteps of the whole episode def add_label(self, seed, respond_traj_len, traj_len, respond_time, tot_time, idle_time): self.traj_ratio.append(respond_traj_len/traj_len) self.time_ratio.append(respond_time/tot_time) self.idle_time.append(idle_time) self.episode_num_label.append(seed) def save_to_file(self, directory): if not os.path.exists(os.path.join('trajectories', directory)): os.makedirs(os.path.join('trajectories', directory)) # 1. save all trajectories # add robot data robot_s = np.array(self.robot_s) data_dict = {'epi_num': np.array(self.episode_num), 'robot_px': robot_s[:, 0], 'robot_py': robot_s[:, 1], 'robot_r': robot_s[:, 2], 'robot_gx': robot_s[:, 3], 'robot_gy': robot_s[:, 4], 'robot_vpref': robot_s[:, 5], 'robot_theta': robot_s[:, 6], 'robot_vx': robot_s[:, 7], 'robot_vy': robot_s[:, 8]} # add humans data humans_px = np.array(self.humans_px) humans_py = np.array(self.humans_py) for i in range(self.human_num): data_dict['human'+str(i)+'px'] = humans_px[:, i] data_dict['human'+str(i)+'py'] = humans_py[:, i] all_data = pd.DataFrame(data_dict) all_data.to_csv(os.path.join('trajectories', directory, 'states.csv'), index=False) # 2. save all labels data_dict = {'epi_num': np.array(self.episode_num_label), 'traj_ratio': np.array(self.traj_ratio), 'time_ratio': np.array(self.time_ratio), 'idle_time': np.array(self.idle_time)} all_data = pd.DataFrame(data_dict) all_data.to_csv(os.path.join('trajectories', directory, 'labels.csv'), index=False) self.clear() def clear(self): # px, py, r, gx, gy, v_pref, theta, vx, vy self.robot_s = [] # nested list of [px1, ..., px5] self.humans_px = [] # nested list of [py1, ..., py5] self.humans_py = [] # record the random seed for this episode self.episode_num = [] # data labels: self.episode_num_label = [] self.traj_ratio = [] self.time_ratio = [] self.idle_time = [] ================================================ FILE: crowd_sim/envs/utils/robot.py ================================================ from crowd_sim.envs.utils.agent import Agent from crowd_sim.envs.utils.state import JointState class Robot(Agent): def __init__(self, config,section): super().__init__(config,section) self.sensor_range = config.robot.sensor_range def act(self, ob): if self.policy is None: raise AttributeError('Policy attribute has to be set!') state = JointState(self.get_full_state(), ob) action = self.policy.predict(state) return action def actWithJointState(self,ob): action = self.policy.predict(ob) return action ================================================ FILE: crowd_sim/envs/utils/state.py ================================================ from collections import namedtuple import numpy as np FullState = namedtuple('FullState', ['px', 'py', 'vx', 'vy', 'radius', 'gx', 'gy', 'v_pref', 'theta']) ObservableState = namedtuple('ObservableState', ['px', 'py', 'vx', 'vy', 'radius']) # JointState has 2 attributes: # self.self_state is a FullState # self.human_states is a list of ObservableStates class JointState(object): # self_state: list of length 9 # human_states: list of length human_num*5 or nested list [human_num, 5] def __init__(self, self_state, human_states): assert len(self_state) == 9 human_states_namedtuple = [] # if human states is a nested list [human_num, 5] if len(np.shape(human_states)) == 2: for human_state in human_states: assert len(human_state) == 5 human_states_namedtuple.append(ObservableState(*human_state)) # if human states is a flatten list of length human_num*5 else: assert len(human_states) % 5 == 0 human_num = len(human_states) // 5 for i in range(human_num): human_states_namedtuple.append(ObservableState(*human_states[int(i*5):(int((i+1)*5))])) self.self_state = FullState(*self_state) self.human_states = human_states_namedtuple # convert a joint state to a flattened list of length 9 + 5 * human_num def to_flatten_list(self): flatten_list = list(self.self_state) for human_state in self.human_states: flatten_list.extend(list(human_state)) return flatten_list ================================================ FILE: gst_updated/.gitignore ================================================ results/ datasets/ logs/ myenv/ *.pickle *.png *.pt ================================================ FILE: gst_updated/LICENSE ================================================ MIT License Copyright (c) 2021 Zhe Huang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: gst_updated/README-old.md ================================================ # gst Gumbel Social Transformer. This is the implementation for the paper ### Learning Sparse Interaction Graphs of Partially Observed Pedestrians for Trajectory Prediction GST is the abbreviation of our model Gumbel Social Transformer. All code was developed and tested on Ubuntu 18.04 with CUDA 10.2, Python 3.6.9, and PyTorch 1.7.1.
### Setup ##### 1. Create a Virtual Environment. (Optional) ``` virtualenv -p /usr/bin/python3 myenv source myenv/bin/activate ``` ##### 2. Install Packages You can run either
``` pip install -r requirements.txt ``` or
``` pip install numpy pip install scipy pip install matplotlib pip install tensorboardX pip install pandas pip install pyyaml pip install jupyterlab pip install torch==1.7.1 pip install tensorflow ``` or for the machine with 3090s, ``` pip install torch==1.9.0+cu111 torchvision==0.10.0+cu111 torchaudio==0.9.0 -f https://download.pytorch.org/whl/torch_stable.html ``` ##### 3. Create Folders and Dataset Files. ``` sh scripts/make_dirs.sh sh scripts/data/create_batch_datasets_eth_ucy.sh python scripts/data/create_batch_datasets_sdd.py ``` ### Training and Evaluation on Various Configurations To train and evaluate a model with n=1, i.e., the target pedestrian pays attention to at most one partially observed pedestrian, run ``` sh scripts/train_sparse.sh sh scripts/eval_sparse.sh ``` To train and evaluate a model with n=1 and temporal component as a temporal convolution network, run ``` sh scripts/train_sparse_tcn.sh sh scripts/eval_sparse_tcn.sh ``` To train and evaluate a model with full connection, i.e., the target pedestrian pays attention to all partially observed pedestrians in the scene, run ``` sh scripts/train_full_connection.sh sh scripts/eval_full_connection.sh ``` To train and evaluate a model in which the target pedestrian pays attention to all fully observed pedestrians in the scene, run ``` sh scripts/train_full_connection_fully_observed.sh sh scripts/eval_full_connection_fully_observed.sh ``` ### Important Arguments for Building Customized Configurations - `--spatial_num_heads_edges`: n, i.e., the upperbound number of pedestrians that the target pedestrian can pay attention to in the scene. When n=0, it is defined as full connection, i.e., the target pedestrian pays attention to all pedestrians in the scene. Default is 4. - `--only_observe_full_period`: The target pedestrian only pays attention to fully observed pedestrians. Default is False. - `--temporal`: Temporal component. `lstm` is Masked LSTM, and `temporal_convolution_net` is temporal convolution network. Default is `lstm`. - `--decode_style`: Decoding style. It has to match the option `--temporal`. `recursive` matches `lstm`, and `readout` matches `temporal_convolution_net`. Default is `recursive`. - `--ghost`: Add a ghost pedestrian in the scene to encourage sparsity. When `--spatial_num_heads_edges` is set as zero, i.e., the target pedestrian pays attention to all pedestrians in the scene, `--ghost` has to be set as False. Default is False. ================================================ FILE: gst_updated/README.md ================================================ # gst Gumbel Social Transformer trained with data provided in Nov, 2021 by Shuijing. ## How to set up ##### 1. Create a Virtual Environment. ``` virtualenv -p /usr/bin/python3 myenv source myenv/bin/activate ``` ##### 2. Install Packages You can run either
``` pip install -r requirements.txt ``` or
``` pip install numpy pip install scipy pip install matplotlib pip install tensorboardX pip install pandas pip install pyyaml pip install jupyterlab pip install torch==1.7.1 pip install tensorflow ``` or for the machine with 3090s, ``` pip install torch==1.9.0+cu111 torchvision==0.10.0+cu111 torchaudio==0.9.0 -f https://download.pytorch.org/whl/torch_stable.html ``` ##### 3. Create Folders, and download datasets and pretrained models. ``` sh run/make_dirs.sh sh run/download_datasets_models.sh ``` ## How to run ##### 1. Test pretrained models. ``` source myenv/bin/activate sh tuning/211209-test_shuijing.sh ``` And you should see outputs which look like below: ``` -------------------------------------------------- dataset: sj No ghost version. new gst new st model Loaded configuration: 100-gumbel_social_transformer-faster_lstm-lr_0.001-init_temp_0.5-edge_head_0-ebd_64-snl_1-snh_8-seed_1000 Test loss from loaded model: -4.0132044252296755 dataset: sj | test aoe: 0.1579 | test aoe std: 0.0175 | test foe: 0.3216 | test foe std: 0.0408 | min aoe: 0.1339, min foe: 0.2638 ``` ##### 2. Test wrapper. Run ``` cd gst_updated sh run/download_wrapper_data_model.sh ``` to collect model and demo data for wrapper. Then run ``` python scripts/wrapper/crowd_nav_interface_parallel.py ``` And you should see outputs which look like below: ``` INPUT DATA number of environments: 4 input_traj shape: torch.Size([4, 15, 5, 2]) input_binary_mask shape: torch.Size([4, 15, 5, 1]) No ghost version. new gst new st model LOADED MODEL device: cuda:0 OUTPUT DATA output_traj shape: torch.Size([4, 15, 5, 5]) output_binary_mask shape: torch.Size([4, 15, 1]) 0.png is created. 1.png is created. 2.png is created. 3.png is created. ``` ## Observation dimensions for traj pred (by Shuijing) As far as I know, the wrapper for SRNN model does not need to handle multiple timesteps simultaneously. So the time dimension is not mentioned here. ### Output format The output of traj pred model (or the input of the planner model) at each timestep t is a 3D float array with size = [number_of_pedestrains, number of prediction steps, 5], where 5 includes [mu_x, mu_y, sigma_x, sigma_y, correlation coefficient]. If number of parallel environments > 1, the array becomes 4D with size = [number of env, number_of_pedestrains, number of prediction steps, 5]. The order of the dimensions can be swapped, and the dimensions can be combined in row-major order if needed. For example, [number of env, number_of_pedestrains, number of prediction steps * 5] is good as long as the last dimension is in row-major order. ### Input format The input format of traj pred model at each timestep t is a 2D float array with size = [number of pedestrains, 2], where 2 includes [px, py] of each pedestrain. If needed, the displacements of all pedestrains can also be added. In this case, input size = [number of pedestrains, 4], where 4 includes [px, py, delta_px, delta_py] of each pedestrain. In addition, there is a binary mask with size = [number of pedestrains, 1] that indicates the presence of each human. If a human with ID = i is present, then mask[i] = True, else mask[i] = False. If number of parallel environments > 1, similarly, the number_of_env is added as the first dimension in addition to the above sizes. ================================================ FILE: gst_updated/__init__.py ================================================ ================================================ FILE: gst_updated/requirements.txt ================================================ absl-py==0.13.0 anyio==3.3.0 argon2-cffi==21.1.0 astunparse==1.6.3 async-generator==1.10 attrs==21.2.0 Babel==2.9.1 backcall==0.2.0 bleach==4.1.0 cached-property==1.5.2 cachetools==4.2.2 certifi==2021.5.30 cffi==1.14.6 charset-normalizer==2.0.4 clang==5.0 contextvars==2.4 cycler==0.10.0 dataclasses==0.8 decorator==5.0.9 defusedxml==0.7.1 entrypoints==0.3 flatbuffers==1.12 gast==0.4.0 google-auth==1.35.0 google-auth-oauthlib==0.4.6 google-pasta==0.2.0 grpcio==1.39.0 h5py==3.1.0 idna==3.2 immutables==0.16 importlib-metadata==4.8.1 ipykernel==5.5.5 ipython==7.16.1 ipython-genutils==0.2.0 jedi==0.18.0 Jinja2==3.0.1 json5==0.9.6 jsonschema==3.2.0 jupyter-client==7.0.2 jupyter-core==4.7.1 jupyter-server==1.10.2 jupyterlab==3.1.10 jupyterlab-pygments==0.1.2 jupyterlab-server==2.7.2 keras==2.6.0 Keras-Preprocessing==1.1.2 kiwisolver==1.3.1 Markdown==3.3.4 MarkupSafe==2.0.1 matplotlib==3.3.4 mistune==0.8.4 nbclassic==0.3.1 nbclient==0.5.4 nbconvert==6.0.7 nbformat==5.1.3 nest-asyncio==1.5.1 notebook==6.4.3 numpy==1.19.5 oauthlib==3.1.1 opt-einsum==3.3.0 packaging==21.0 pandas==1.1.5 pandocfilters==1.4.3 parso==0.8.2 pexpect==4.8.0 pickleshare==0.7.5 Pillow==8.3.1 prometheus-client==0.11.0 prompt-toolkit==3.0.20 protobuf==3.17.3 ptyprocess==0.7.0 pyasn1==0.4.8 pyasn1-modules==0.2.8 pycparser==2.20 Pygments==2.10.0 pyparsing==2.4.7 pyrsistent==0.18.0 python-dateutil==2.8.2 pytz==2021.1 PyYAML==5.4.1 pyzmq==22.2.1 requests==2.26.0 requests-oauthlib==1.3.0 requests-unixsocket==0.2.0 rsa==4.7.2 scipy==1.5.4 Send2Trash==1.8.0 six==1.15.0 sniffio==1.2.0 tensorboard==2.6.0 tensorboard-data-server==0.6.1 tensorboard-plugin-wit==1.8.0 tensorboardX==2.4 tensorflow==2.6.0 tensorflow-estimator==2.6.0 termcolor==1.1.0 terminado==0.11.1 testpath==0.5.0 torch==1.7.1 tornado==6.1 traitlets==4.3.3 typing-extensions==3.7.4.3 urllib3==1.26.6 wcwidth==0.2.5 webencodings==0.5.1 websocket-client==1.2.1 Werkzeug==2.0.1 wrapt==1.12.1 zipp==3.5.0 ================================================ FILE: gst_updated/run/create_batch_datasets_eth_ucy.sh ================================================ for dataset in eth hotel univ zara1 zara2; do python -u scripts/data/create_datasets_eth_ucy.py --dataset $dataset | tee -a logs/create_batch_datasets.txt python -u scripts/data/create_batch_datasets_eth_ucy.py --dataset $dataset | tee -a logs/create_batch_datasets.txt rm -rf datasets/eth_ucy/${dataset}/${dataset}_dset_train_trajectories.pt rm -rf datasets/eth_ucy/${dataset}/${dataset}_dset_val_trajectories.pt rm -rf datasets/eth_ucy/${dataset}/${dataset}_dset_test_trajectories.pt done ================================================ FILE: gst_updated/run/create_batch_datasets_self_eth_ucy.sh ================================================ for dataset in eth hotel univ zara1 zara2; do python -u scripts/data/create_datasets_self_eth_ucy.py --dataset $dataset | tee -a logs/create_batch_datasets.txt python -u scripts/data/create_batch_datasets_self_eth_ucy.py --dataset $dataset | tee -a logs/create_batch_datasets.txt rm -rf datasets/self_eth_ucy/${dataset}/${dataset}_dset_train_trajectories.pt rm -rf datasets/self_eth_ucy/${dataset}/${dataset}_dset_val_trajectories.pt rm -rf datasets/self_eth_ucy/${dataset}/${dataset}_dset_test_trajectories.pt done ================================================ FILE: gst_updated/run/create_batch_datasets_synth.sh ================================================ python -u scripts/data/create_datasets_trajnet.py --dataset synth | tee -a logs/create_datasets.txt python -u scripts/data/create_batch_datasets_trajnet.py --dataset synth | tee -a logs/create_batch_datasets.txt ================================================ FILE: gst_updated/run/download_datasets_models.sh ================================================ cd datasets/shuijing/orca_20humans_fov cd results/visual # use curl for big-sized google drive files to redirect curl -c /tmp/cookies "https://drive.google.com/uc?export=download&id=17QLGgTcCWatIrF4EE1Gai8BaMbqxz6kk" > /tmp/intermezzo.html curl -L -b /tmp/cookies "https://drive.google.com$(cat /tmp/intermezzo.html | grep -Po 'uc-download-link" [^>]* href="\K[^"]*' | sed 's/\&/\&/g')" > sj_dset_test_batch_trajectories.pt cd ../../../results # use wget for small-sized google drive files wget -O 211203-model.zip "https://drive.google.com/uc?export=download&id=1ukON4cAwM63gKKlJJwgjcdLf7-4-f1KH" > /tmp/intermezzo.html unzip 211203-model.zip && rm -rf 211203-model.zip ================================================ FILE: gst_updated/run/download_wrapper_data_model.sh ================================================ mkdir -p datasets/wrapper_demo cd datasets/wrapper_demo # use wget for small-sized google drive files wget -O wrapper_demo_data.pt "https://drive.google.com/uc?export=download&id=1Fjc4nFAeqgCTd9UEUsXhJkESqgErNtlA" > /tmp/intermezzo.html cd ../../results # use wget for small-sized google drive files wget -O 211222-model.zip "https://drive.google.com/uc?export=download&id=1mm364PGbp7hFHJ8uXN_uZI4Nq_3JIHPj" > /tmp/intermezzo.html unzip 211222-model.zip && rm -rf 211222-model.zip ================================================ FILE: gst_updated/run/make_dirs.sh ================================================ mkdir -p datasets/shuijing/orca_20humans_fov mkdir results mkdir logs ================================================ FILE: gst_updated/scripts/experiments/draft.py ================================================ import pathhack import pickle import time from os.path import join, isdir from os import makedirs import torch import numpy as np from tensorboardX import SummaryWriter from torch.optim.lr_scheduler import StepLR from src.mgnn.utils import arg_parse, average_offset_error, final_offset_error, \ negative_log_likelihood, random_rotate_graph, args2writername from src.gumbel_social_transformer.temperature_scheduler import Temp_Scheduler from torch.utils.data import DataLoader from datetime import datetime num_epochs = 100 init_temp = 1. checkpoint_epoch = 50 temperature_scheduler = Temp_Scheduler(num_epochs, init_temp, init_temp, \ temp_min=0.03, last_epoch=checkpoint_epoch-1) tau_list = [] for epoch in range(51, num_epochs+1): tau = temperature_scheduler.step() tau_list.append(tau) temperature_scheduler_new = Temp_Scheduler(num_epochs, init_temp, init_temp, temp_min=0.03) tau_list_new = [] for epoch in range(1, num_epochs+1): tau = temperature_scheduler_new.step() if epoch > 50: tau_list_new.append(tau) tau_list, tau_list_new = np.array(tau_list), np.array(tau_list_new) print(tau_list-tau_list_new) # print(tau_list_new) # def load_batch_dataset(args, pkg_path, subfolder='train', num_workers=4, shuffle=None): # result_filename = args.dataset+'_dset_'+subfolder+'_batch_trajectories.pt' # if args.dataset == 'sdd': # dataset_folderpath = join(pkg_path, 'datasets/sdd/social_pool_data') # else: # dataset_folderpath = join(pkg_path, 'datasets/eth_ucy', args.dataset) # dset = torch.load(join(dataset_folderpath, result_filename)) # if shuffle is None: # if subfolder == 'train': # shuffle = True # else: # shuffle = False # dloader = DataLoader( # dset, # batch_size=1, # shuffle=shuffle, # num_workers=num_workers) # return dloader # def main(args): # print('\n\n') # print('-'*50) # print('arguments: ', args) # torch.manual_seed(args.random_seed) # np.random.seed(args.random_seed) # if args.batch_size != 1: # raise RuntimeError("Batch size must be 1 for BatchTrajectoriesDataset.") # if args.dataset == 'sdd' and args.rotation_pattern is not None: # raise RuntimeError("SDD should not allow rotation since it uses pixels.") # device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # print('device: ', device) # loader_train = load_batch_dataset(args, pathhack.pkg_path, subfolder='train') # if args.dataset == 'sdd': # loader_val = load_batch_dataset(args, pathhack.pkg_path, subfolder='test') # no val for sdd # else: # loader_val = load_batch_dataset(args, pathhack.pkg_path, subfolder='val') # train_data_loaders = [loader_train, loader_val] # print('dataset: ', args.dataset) # writername = args2writername(args) # print('Config: ', writername) # logdir = join(pathhack.pkg_path, 'results', writername, args.dataset) # if isdir(logdir) and not args.resume_training: # print('Error: The result directory was already created and used.') # print('-'*50) # print('\n\n') # return # writer = SummaryWriter(logdir=logdir) # print('-'*50) # print('\n\n') # train(args, train_data_loaders, writer, logdir, device=device) # writer.close() # def load_checkpoint(args, logdir, model, optimizer, lr_scheduler): # # ! to be finished # checkpoint = torch.load(checkpoint_filepath) # model.load_state_dict(checkpoint['state_dict']) # optimizer.load_state_dict(checkpoint['optimizer']) # lr_scheduler.load_state_dict(checkpoint['scheduler']) # return model, optimizer, lr_scheduler, temperature_scheduler # def train(args, data_loaders, writer, logdir, device='cuda:0'): # print('-'*50) # print('Training Phase') # print('-'*50, '\n') # loader_train, loader_val = data_loaders # model = st_model(args, device=device).to(device) # optimizer = torch.optim.Adam(model.parameters(), lr=args.lr) # lr_scheduler = StepLR(optimizer, step_size=50, gamma=0.3) # if args.resume_training: # temperature_scheduler = Temp_Scheduler(args.num_epochs, args.init_temp, args.init_temp, temp_min=0.03) # else: # model, optimizer, lr_scheduler, temperature_scheduler = \ # load_checkpoint(args, logdir, model, optimizer, lr_scheduler) # # def load_ckp(checkpoint_fpath, model, optimizer): # # checkpoint = torch.load(checkpoint_fpath) # # model.load_state_dict(checkpoint['state_dict']) # # optimizer.load_state_dict(checkpoint['optimizer']) # # return model, optimizer, checkpoint['epoch'] # print('Model is initialized.') # print('learning rate: ', args.lr) # checkpoint_dir = join(logdir, 'checkpoint') # if not isdir(checkpoint_dir): # makedirs(checkpoint_dir) # with open(join(checkpoint_dir, 'args.pickle'), 'wb') as f: # pickle.dump(args, f) # print('EPOCHS: ', args.num_epochs) # print('Training started.\n') # train_loss_task, train_aoe_task, train_foe_task = [], [], [] # val_loss_task, val_aoe_task, val_foe_task = [], [], [] # hist = {} # for epoch in range(1, args.num_epochs+1): # model.train() # epoch_start_time = time.time() # tau = temperature_scheduler.step() # train_loss_epoch, train_aoe_epoch, train_foe_epoch, train_loss_mask_epoch = [], [], [], [] # for batch_idx, batch in enumerate(loader_train): # obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ # v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt = batch # if args.rotation_pattern is not None: # (v_obs, A_obs, v_pred_gt, A_pred_gt), _ = \ # random_rotate_graph(args, v_obs, A_obs, v_pred_gt, A_pred_gt) # v_obs, A_obs, v_pred_gt, attn_mask_obs, loss_mask_rel = \ # v_obs.to(device), A_obs.to(device), v_pred_gt.to(device), \ # attn_mask_obs.to(device), loss_mask_rel.to(device) # if args.deterministic: # sampling = False # else: # sampling = True # results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=False, sampling=sampling, device=device) # gaussian_params_pred, x_sample_pred, info = results # loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] # loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] # if args.deterministic: # loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] # offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) # loss = offset_error_sq.sum()/eventual_loss_mask.sum() # else: # loss = negative_log_likelihood(gaussian_params_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) # train_loss_epoch.append(loss.detach().to('cpu').item()) # loss = loss / args.batch_size # loss.backward() # aoe = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) # foe = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) # train_aoe_epoch.append(aoe.detach().to('cpu').numpy()) # train_foe_epoch.append(foe.detach().to('cpu').numpy()) # train_loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) # if args.clip_grad is not None: # torch.nn.utils.clip_grad_norm_( # model.parameters(), args.clip_grad) # optimizer.step() # optimizer.zero_grad() # lr_scheduler.step() # train_aoe_epoch, train_foe_epoch, train_loss_mask_epoch = \ # np.concatenate(train_aoe_epoch, axis=0), \ # np.concatenate(train_foe_epoch, axis=0), \ # np.concatenate(train_loss_mask_epoch, axis=0) # train_loss_epoch, train_aoe_epoch, train_foe_epoch = \ # np.mean(train_loss_epoch), \ # train_aoe_epoch.sum()/train_loss_mask_epoch.sum(), \ # train_foe_epoch.sum()/train_loss_mask_epoch.sum() # train_loss_task.append(train_loss_epoch) # train_aoe_task.append(train_aoe_epoch) # train_foe_task.append(train_foe_epoch) # training_epoch_period = time.time() - epoch_start_time # training_epoch_period_per_sample = training_epoch_period/len(loader_train) # val_loss_epoch, val_aoe_epoch, val_foe_epoch = inference(loader_val, model, args, mode='val', tau=tau, device=device) # val_loss_task.append(val_loss_epoch) # val_aoe_task.append(val_aoe_epoch) # val_foe_task.append(val_foe_epoch) # print('Epoch: {0} | train loss: {1:.4f} | val loss: {2:.4f} | train aoe: {3:.4f} | val aoe: {4:.4f} | train foe: {5:.4f} | val foe: {6:.4f} | period: {7:.2f} sec | time per sample: {8:.4f} sec'\ # .format(epoch, train_loss_epoch, val_loss_epoch,\ # train_aoe_epoch, val_aoe_epoch,\ # train_foe_epoch, val_foe_epoch,\ # training_epoch_period, training_epoch_period_per_sample)) # if epoch % 10 == 0: # model_filename = join(checkpoint_dir, 'epoch_'+str(epoch)+'.pt') # torch.save({ # 'epoch': epoch, # 'model_state_dict': model.state_dict(), # 'optimizer_state_dict': optimizer.state_dict(), # 'train_loss_task': train_loss_task, # 'val_loss_task': val_loss_task, # 'train_aoe_task': train_aoe_task, # 'val_aoe_task': val_aoe_task, # 'train_foe_task': train_foe_task, # 'val_foe_task': val_foe_task, # 'training_date': datetime.today().strftime('%y%m%d'), # }, model_filename) # print('epoch_'+str(epoch)+'.pt is saved.') # hist['epoch'] = epoch # hist['train_loss'], hist['val_loss'] = train_loss_task, val_loss_task # hist['train_aoe'], hist['val_aoe'] = train_aoe_task, val_aoe_task # hist['train_foe'], hist['val_foe'] = train_foe_task, val_foe_task # with open(join(checkpoint_dir, 'train_hist.pickle'), 'wb') as f: # pickle.dump(hist, f) # print(join(checkpoint_dir, 'train_hist.pickle')+' is saved.') # writer.add_scalars('loss', {'train': train_loss_task[-1], 'val': val_loss_task[-1]}, epoch) # writer.add_scalars('aoe', {'train': train_aoe_task[-1], 'val': val_aoe_task[-1]}, epoch) # writer.add_scalars('foe', {'train': train_foe_task[-1], 'val': val_foe_task[-1]}, epoch) # return # def inference(loader, model, args, mode='val', tau=1., device='cuda:0'): # with torch.no_grad(): # model.eval() # epoch_start_time = time.time() # loss_epoch, aoe_epoch, foe_epoch, loss_mask_epoch = [], [], [], [] # for batch_idx, batch in enumerate(loader): # obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ # v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt = batch # v_obs, A_obs, v_pred_gt, attn_mask_obs, loss_mask_rel = \ # v_obs.to(device), A_obs.to(device), v_pred_gt.to(device), \ # attn_mask_obs.to(device), loss_mask_rel.to(device) # if mode == 'val': # if args.deterministic: # sampling = False # else: # sampling = True # results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=False, sampling=sampling, device=device) # gaussian_params_pred, x_sample_pred, info = results # loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] # loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] # if args.deterministic: # loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] # offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) # loss = offset_error_sq.sum()/eventual_loss_mask.sum() # else: # loss = negative_log_likelihood(gaussian_params_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) # aoe = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) # foe = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) # else: # raise RuntimeError('We now only support val mode.') # loss_epoch.append(loss.detach().to('cpu').item()) # aoe_epoch.append(aoe.detach().to('cpu').numpy()) # foe_epoch.append(foe.detach().to('cpu').numpy()) # loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) # aoe_epoch, foe_epoch, loss_mask_epoch = \ # np.concatenate(aoe_epoch, axis=0), \ # np.concatenate(foe_epoch, axis=0), \ # np.concatenate(loss_mask_epoch, axis=0) # loss_epoch, aoe_epoch, foe_epoch = \ # np.mean(loss_epoch), \ # aoe_epoch.sum()/loss_mask_epoch.sum(), \ # foe_epoch.sum()/loss_mask_epoch.sum() # return loss_epoch, aoe_epoch, foe_epoch # if __name__ == "__main__": # args = arg_parse() # if args.temporal == "lstm" or args.temporal == "faster_lstm": # from src.gumbel_social_transformer.st_model import st_model, offset_error_square_full_partial # elif args.temporal == "temporal_convolution_net": # from src.gumbel_social_transformer.st_model_tcn import st_model, offset_error_square_full_partial # else: # raise RuntimeError('The temporal component is not lstm nor tcn nor faster_lstm.') # main(args) ================================================ FILE: gst_updated/scripts/experiments/eval.py ================================================ import pathhack import pickle from os.path import join, isdir import torch import csv import numpy as np from src.mgnn.utils import arg_parse, average_offset_error, final_offset_error, args2writername, load_batch_dataset from src.gumbel_social_transformer.st_model import st_model, offset_error_square_full_partial,\ negative_log_likelihood_full_partial def main(args): print('\n\n') print('-'*50) torch.manual_seed(args.random_seed) np.random.seed(args.random_seed) device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") loader_test = load_batch_dataset(args, pathhack.pkg_path, subfolder='test') if args.dataset == 'sdd': loader_val = load_batch_dataset(args, pathhack.pkg_path, subfolder='test') # no val for sdd else: loader_val = load_batch_dataset(args, pathhack.pkg_path, subfolder='val') print('dataset: ', args.dataset) writername = args2writername(args) logdir = join(pathhack.pkg_path, 'results', writername, args.dataset) if not isdir(logdir): raise RuntimeError('The result directory was not found.') checkpoint_dir = join(logdir, 'checkpoint') model_filename = 'epoch_'+str(args.num_epochs)+'.pt' with open(join(checkpoint_dir, 'args.pickle'), 'rb') as f: args_eval = pickle.load(f) model = st_model(args_eval, device=device).to(device) model_checkpoint = torch.load(join(checkpoint_dir, model_filename), map_location=device) model.load_state_dict(model_checkpoint['model_state_dict']) print('Loaded configuration: ', writername) print('The best validation losses printed below should be the same.') print('Validation loss in the checkpoint: ', model_checkpoint['val_loss_epoch']) val_loss_epoch, val_aoe_epoch, val_foe_epoch = inference(loader_val, model, args, mode='val', tau=0.03, device=device) print('Validation loss from loaded model: ', val_loss_epoch) test_loss_epoch, test_aoe, test_foe, test_aoe_std, test_foe_std, test_aoe_min, test_foe_min = inference(loader_test, model, args, mode='test', tau=0.03, device=device) print('Test loss from loaded model: ', test_loss_epoch) print('dataset: {0} | test aoe: {1:.4f} | test aoe std: {2:.4f} | test foe: {3:.4f} | test foe std: {4:.4f} | min aoe: {5:.4f}, min foe: {6:.4f}'\ .format(args.dataset, test_aoe, test_aoe_std, test_foe, test_foe_std, test_aoe_min, test_foe_min)) # dataset_list = ['eth', 'hotel', 'univ', 'zara1', 'zara2'] # dataset_idx = dataset_list.index(args.dataset) # csv_row_data = np.ones((4,5))*999999. # csv_row_data[:,dataset_idx] = np.array([test_aoe, test_foe, test_aoe_std, test_foe_std]) # csv_row_data = csv_row_data.reshape(-1) # csv_filename = join(checkpoint_dir, '../..', 'test_performance.csv') # with open(csv_filename, mode='a') as test_file: # test_writer = csv.writer(test_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) # test_writer.writerow(csv_row_data) # print(csv_filename+' is written.') def inference(loader, model, args, mode='val', tau=1., device='cuda:0'): with torch.no_grad(): model.eval() loss_epoch, aoe_epoch, foe_epoch, loss_mask_epoch = [], [], [], [] aoe_mean_epoch, aoe_std_epoch, foe_mean_epoch, foe_std_epoch = [], [], [], [] aoe_min_epoch, foe_min_epoch = [], [] for batch_idx, batch in enumerate(loader): obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt = batch v_obs, A_obs, v_pred_gt, attn_mask_obs, loss_mask_rel = \ v_obs.to(device), A_obs.to(device), v_pred_gt.to(device), \ attn_mask_obs.to(device), loss_mask_rel.to(device) if mode == 'val': results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=False, sampling=False, device=device) gaussian_params_pred, x_sample_pred, info = results loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] if args.deterministic: offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss = offset_error_sq.sum()/eventual_loss_mask.sum() else: prob_loss, eventual_loss_mask = negative_log_likelihood_full_partial(gaussian_params_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss = prob_loss.sum()/eventual_loss_mask.sum() aoe = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) foe = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) elif mode == 'test': if args.deterministic: sampling = False else: sampling = True best_aoe_mean = 999999. aoe, foe, loss = [], [], [] for _ in range(20): results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=True, sampling=sampling, device=device) gaussian_params_pred, x_sample_pred, info = results loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] if args.deterministic: offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss_tmp = offset_error_sq.sum()/eventual_loss_mask.sum() else: prob_loss, eventual_loss_mask = negative_log_likelihood_full_partial(gaussian_params_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss_tmp = prob_loss.sum()/eventual_loss_mask.sum() aoe_tmp = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) foe_tmp = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) aoe.append(aoe_tmp) foe.append(foe_tmp) loss.append(loss_tmp) aoe = torch.stack(aoe, dim=0) foe = torch.stack(foe, dim=0) aoe = aoe.sum(1) foe = foe.sum(1) aoe_mean = aoe.mean() aoe_std = aoe.std() foe_mean = foe.mean() foe_std = foe.std() aoe_min, foe_min = aoe.min(), foe.min() loss = sum(loss)/len(loss) else: raise RuntimeError('We now only support val and test mode.') if mode == 'val': loss_epoch.append(loss.detach().to('cpu').item()) aoe_epoch.append(aoe.detach().to('cpu').numpy()) foe_epoch.append(foe.detach().to('cpu').numpy()) loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) elif mode == 'test': loss_epoch.append(loss.detach().to('cpu').item()) aoe_mean_epoch.append(aoe_mean.item()) foe_mean_epoch.append(foe_mean.item()) aoe_std_epoch.append(aoe_std.item()) foe_std_epoch.append(foe_std.item()) aoe_min_epoch.append(aoe_min.item()) foe_min_epoch.append(foe_min.item()) loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) else: raise RuntimeError('We only support val and test mode.') if mode == 'val': aoe_epoch, foe_epoch, loss_mask_epoch = \ np.concatenate(aoe_epoch, axis=0), \ np.concatenate(foe_epoch, axis=0), \ np.concatenate(loss_mask_epoch, axis=0) loss_epoch, aoe_epoch, foe_epoch = \ np.mean(loss_epoch), \ aoe_epoch.sum()/loss_mask_epoch.sum(), \ foe_epoch.sum()/loss_mask_epoch.sum() return loss_epoch, aoe_epoch, foe_epoch elif mode == 'test': loss_mask_epoch = np.concatenate(loss_mask_epoch, axis=0) aoe = sum(aoe_mean_epoch)/loss_mask_epoch.sum() foe = sum(foe_mean_epoch)/loss_mask_epoch.sum() aoe_std = sum(aoe_std_epoch)/loss_mask_epoch.sum() foe_std = sum(foe_std_epoch)/loss_mask_epoch.sum() aoe_min = sum(aoe_min_epoch)/loss_mask_epoch.sum() foe_min = sum(foe_min_epoch)/loss_mask_epoch.sum() loss_epoch = np.mean(loss_epoch) return loss_epoch, aoe, foe, aoe_std, foe_std, aoe_min, foe_min else: raise RuntimeError('We now only support val mode.') if __name__ == "__main__": args = arg_parse() main(args) ================================================ FILE: gst_updated/scripts/experiments/eval_trajnet.py ================================================ import pathhack import pickle from os.path import join, isdir import torch import csv import numpy as np from src.mgnn.utils import arg_parse, average_offset_error, final_offset_error, args2writername, load_batch_dataset from src.gumbel_social_transformer.st_model import st_model, offset_error_square_full_partial,\ negative_log_likelihood_full_partial def main(args): print('\n\n') print('-'*50) torch.manual_seed(args.random_seed) np.random.seed(args.random_seed) device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") loader_test = load_batch_dataset(args, pathhack.pkg_path, subfolder='test') if args.dataset == 'sdd': loader_val = load_batch_dataset(args, pathhack.pkg_path, subfolder='test') # no val for sdd else: loader_val = load_batch_dataset(args, pathhack.pkg_path, subfolder='val') print('dataset: ', args.dataset) writername = args2writername(args) logdir = join(pathhack.pkg_path, 'results', writername, args.dataset) if not isdir(logdir): raise RuntimeError('The result directory was not found.') checkpoint_dir = join(logdir, 'checkpoint') model_filename = 'epoch_'+str(args.num_epochs)+'.pt' with open(join(checkpoint_dir, 'args.pickle'), 'rb') as f: args_eval = pickle.load(f) model = st_model(args_eval, device=device).to(device) model_checkpoint = torch.load(join(checkpoint_dir, model_filename), map_location=device) model.load_state_dict(model_checkpoint['model_state_dict']) print('Loaded configuration: ', writername) print('The best validation losses printed below should be the same.') print('Validation loss in the checkpoint: ', model_checkpoint['val_loss_epoch']) val_loss_epoch, val_aoe_epoch, val_foe_epoch = inference(loader_val, model, args, mode='val', tau=0.03, device=device) print('Validation loss from loaded model: ', val_loss_epoch) test_loss_epoch, test_aoe, test_foe, test_aoe_std, test_foe_std, test_aoe_min, test_foe_min = inference(loader_test, model, args, mode='test', tau=0.03, device=device) print('Test loss from loaded model: ', test_loss_epoch) print('dataset: {0} | test aoe: {1:.4f} | test aoe std: {2:.4f} | test foe: {3:.4f} | test foe std: {4:.4f} | min aoe: {5:.4f}, min foe: {6:.4f}'\ .format(args.dataset, test_aoe, test_aoe_std, test_foe, test_foe_std, test_aoe_min, test_foe_min)) dataset_list = ['eth', 'hotel', 'univ', 'zara1', 'zara2'] dataset_idx = dataset_list.index(args.dataset) csv_row_data = np.ones((4,5))*999999. csv_row_data[:,dataset_idx] = np.array([test_aoe, test_foe, test_aoe_std, test_foe_std]) csv_row_data = csv_row_data.reshape(-1) csv_filename = join(checkpoint_dir, '../..', 'test_performance.csv') with open(csv_filename, mode='a') as test_file: test_writer = csv.writer(test_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) test_writer.writerow(csv_row_data) print(csv_filename+' is written.') def test(loader, model, tau=0.03, device='cuda:0'): with torch.no_grad(): model.eval() for batch_idx, batch in enumerate(loader): obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt, frame_id_seq, ped_id_list = batch v_obs, A_obs, v_pred_gt, attn_mask_obs, loss_mask_rel = \ v_obs.to(device), A_obs.to(device), v_pred_gt.to(device), \ attn_mask_obs.to(device), loss_mask_rel.to(device) results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=False, sampling=False, device=device) gaussian_params_pred, x_sample_pred, info = results import pdb; pdb.set_trace() # loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] # loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] # pos_pred = torch.cumsum(x_sample_pred, dim=1) # pos_target = torch.cumsum(x_target_m, dim=1) # loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] # loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] # gaussian_params_pred, x_sample_pred, info = results # loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] # loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] break def inference(loader, model, args, mode='val', tau=1., device='cuda:0'): with torch.no_grad(): model.eval() loss_epoch, aoe_epoch, foe_epoch, loss_mask_epoch = [], [], [], [] aoe_mean_epoch, aoe_std_epoch, foe_mean_epoch, foe_std_epoch = [], [], [], [] aoe_min_epoch, foe_min_epoch = [], [] for batch_idx, batch in enumerate(loader): obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt = batch v_obs, A_obs, v_pred_gt, attn_mask_obs, loss_mask_rel = \ v_obs.to(device), A_obs.to(device), v_pred_gt.to(device), \ attn_mask_obs.to(device), loss_mask_rel.to(device) if mode == 'val': results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=False, sampling=False, device=device) gaussian_params_pred, x_sample_pred, info = results loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] if args.deterministic: offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss = offset_error_sq.sum()/eventual_loss_mask.sum() else: prob_loss, eventual_loss_mask = negative_log_likelihood_full_partial(gaussian_params_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss = prob_loss.sum()/eventual_loss_mask.sum() aoe = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) foe = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) elif mode == 'test': if args.deterministic: sampling = False else: sampling = True best_aoe_mean = 999999. aoe, foe, loss = [], [], [] for _ in range(20): results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=True, sampling=sampling, device=device) gaussian_params_pred, x_sample_pred, info = results loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] if args.deterministic: offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss_tmp = offset_error_sq.sum()/eventual_loss_mask.sum() else: prob_loss, eventual_loss_mask = negative_log_likelihood_full_partial(gaussian_params_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss_tmp = prob_loss.sum()/eventual_loss_mask.sum() aoe_tmp = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) foe_tmp = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) aoe.append(aoe_tmp) foe.append(foe_tmp) loss.append(loss_tmp) aoe = torch.stack(aoe, dim=0) foe = torch.stack(foe, dim=0) aoe = aoe.sum(1) foe = foe.sum(1) aoe_mean = aoe.mean() aoe_std = aoe.std() foe_mean = foe.mean() foe_std = foe.std() aoe_min, foe_min = aoe.min(), foe.min() loss = sum(loss)/len(loss) else: raise RuntimeError('We now only support val and test mode.') if mode == 'val': loss_epoch.append(loss.detach().to('cpu').item()) aoe_epoch.append(aoe.detach().to('cpu').numpy()) foe_epoch.append(foe.detach().to('cpu').numpy()) loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) elif mode == 'test': loss_epoch.append(loss.detach().to('cpu').item()) aoe_mean_epoch.append(aoe_mean.item()) foe_mean_epoch.append(foe_mean.item()) aoe_std_epoch.append(aoe_std.item()) foe_std_epoch.append(foe_std.item()) aoe_min_epoch.append(aoe_min.item()) foe_min_epoch.append(foe_min.item()) loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) else: raise RuntimeError('We only support val and test mode.') if mode == 'val': aoe_epoch, foe_epoch, loss_mask_epoch = \ np.concatenate(aoe_epoch, axis=0), \ np.concatenate(foe_epoch, axis=0), \ np.concatenate(loss_mask_epoch, axis=0) loss_epoch, aoe_epoch, foe_epoch = \ np.mean(loss_epoch), \ aoe_epoch.sum()/loss_mask_epoch.sum(), \ foe_epoch.sum()/loss_mask_epoch.sum() return loss_epoch, aoe_epoch, foe_epoch elif mode == 'test': loss_mask_epoch = np.concatenate(loss_mask_epoch, axis=0) aoe = sum(aoe_mean_epoch)/loss_mask_epoch.sum() foe = sum(foe_mean_epoch)/loss_mask_epoch.sum() aoe_std = sum(aoe_std_epoch)/loss_mask_epoch.sum() foe_std = sum(foe_std_epoch)/loss_mask_epoch.sum() aoe_min = sum(aoe_min_epoch)/loss_mask_epoch.sum() foe_min = sum(foe_min_epoch)/loss_mask_epoch.sum() loss_epoch = np.mean(loss_epoch) return loss_epoch, aoe, foe, aoe_std, foe_std, aoe_min, foe_min else: raise RuntimeError('We now only support val mode.') if __name__ == "__main__": args = arg_parse() main(args) ================================================ FILE: gst_updated/scripts/experiments/load_trajnet_test_data.py ================================================ import pathhack import pickle from os.path import join, isdir import torch import csv import numpy as np from src.mgnn.utils import arg_parse, average_offset_error, final_offset_error, args2writername, load_batch_dataset from src.gumbel_social_transformer.st_model import st_model, offset_error_square_full_partial,\ negative_log_likelihood_full_partial from torch.utils.data import DataLoader import ndjson # def load_single_batch_dataset_eth_ucy(args, pkg_path, subfolder='train', num_workers=4, shuffle=None): shuffle = None subfolder = 'test' # dset_name = 'orca_synth' # dset_name = 'collision_test' writername = "100-gumbel_social_transformer-faster_lstm-lr_0.001-deterministic-init_temp_0.5-edge_head_4-ebd_64-snl_1-snh_8-ghost-seed_1000" # "100-gumbel_social_transformer-faster_lstm-lr_0.001-deterministic-init_temp_0.5-edge_head_1-ebd_64-snl_1-snh_8-ghost-seed_1000" # "100-gumbel_social_transformer-faster_lstm-lr_0.001-deterministic-init_temp_0.5-edge_head_8-ebd_64-snl_1-snh_8-ghost-seed_1000" for dset_name in ['orca_synth', 'collision_test']: result_filename = dset_name+'_dset_test_trajectories.pt' # result_filename = 'orca_synth_dset_test_trajectories.pt' # result_filename = 'collision_test_dset_test_trajectories.pt' dataset_folderpath = join(pathhack.pkg_path, 'datasets/trajnet++/test') dset = torch.load(join(dataset_folderpath, result_filename)) dataset_folderpath = join(pathhack.pkg_path, 'datasets/trajnet++/test') ndjson_filepath = join(dataset_folderpath, 'synth_data', dset_name+'.ndjson') # ndjson_filepath = join(dataset_folderpath, 'synth_data', 'orca_synth.ndjson') # ndjson_filepath = join(dataset_folderpath, 'synth_data', 'collision_test.ndjson') scene_posts = [] with open(ndjson_filepath) as f: reader = ndjson.reader(f) for post in reader: if "scene" in post.keys(): scene_posts.append(post) if shuffle is None: if subfolder == 'train': shuffle = True else: shuffle = False dloader = DataLoader( dset, batch_size=1, shuffle=shuffle, num_workers=4) frame_diff = 1 for batch_idx, batch in enumerate(dloader): obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt, frame_id_seq, ped_id_list = batch print(v_obs) print(frame_id_seq) print(ped_id_list) print(pred_traj_gt) break device = "cuda:0" logdir = join(pathhack.pkg_path, 'results', writername, "synth") checkpoint_dir = join(logdir, 'checkpoint') model_filename = 'epoch_'+str(100)+'.pt' with open(join(checkpoint_dir, 'args.pickle'), 'rb') as f: args_eval = pickle.load(f) model = st_model(args_eval, device=device).to(device) model_checkpoint = torch.load(join(checkpoint_dir, model_filename), map_location=device) model.load_state_dict(model_checkpoint['model_state_dict']) print('Loaded configuration: ', writername) print('The best validation losses printed below should be the same.') print('Validation loss in the checkpoint: ', model_checkpoint['val_loss_epoch']) ndjson_lines = [] with torch.no_grad(): model.eval() for batch_idx, batch in enumerate(dloader): if batch_idx % 100 == 0: print(batch_idx) obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt, frame_id_seq, ped_id_list = batch v_obs, A_obs, attn_mask_obs, loss_mask_rel = \ v_obs.to(device), A_obs.to(device), \ attn_mask_obs.to(device), loss_mask_rel.to(device) results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=0.03, hard=True, sampling=False, device=device) gaussian_params_pred, x_sample_pred, info = results obs_traj = obs_traj.to("cpu") x_sample_pred = x_sample_pred.to("cpu").permute(0, 2, 3, 1) #[1, 6, 2, 12] pred_traj = torch.cat((obs_traj[:,:,:,-1:], x_sample_pred), dim=3) # [1, 6, 2, 13] pred_traj = torch.cumsum(pred_traj, dim=3) start_frame_id = int(frame_id_seq.item()) pred_traj = pred_traj[:,:,:,1:] # [1, 6, 2, 12] loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'][0] # {"track": {"f": 370, "p": 3, "x": -1.17, "y": 4.24, "prediction_number": 0, "scene_id": 0}} for i in range(len(loss_mask_rel_full_partial)): if loss_mask_rel_full_partial[i]: for tt in range(9, 21): # 9-20 ndjson_line = {} ndjson_line["track"] = {} ndjson_line["track"]["f"] = start_frame_id + int(frame_diff)*tt ndjson_line["track"]["p"] = int(ped_id_list[i].item()) ndjson_line["track"]["x"] = round(pred_traj[0,i,0,tt-9].item(), 2) ndjson_line["track"]["y"] = round(pred_traj[0,i,1,tt-9].item(), 2) ndjson_line["track"]["prediction_number"] = 0 ndjson_line["track"]["scene_id"] = batch_idx ndjson_lines.append(ndjson_line) print("final scene id: ", batch_idx) # with open('./collision_test_predictions.ndjson', 'w') as f: # with open('./orca_synth_predictions.ndjson', 'w') as f: with open('./'+dset_name+'_predictions.ndjson', 'w') as f: writer = ndjson.writer(f, ensure_ascii=False) for post in scene_posts: writer.writerow(post) for post in ndjson_lines: writer.writerow(post) # import pdb; pdb.set_trace() """ # import pdb; pdb.set_trace() # break ndjson_lines = [] # ndjson_line = {} # ndjson_line["track"] = {} start_frame_id = int(frame_id_seq.item()) # ndjson_line["track"]["p"] = int(frame_id_seq.item()[0]) # {"track": {"f": 113, "p": 19973, "x": -0.95, "y": -1.86}} pred_traj = pred_traj[:,:,:,1:] # [1, 6, 2, 12] loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'][0] # {"track": {"f": 370, "p": 3, "x": -1.17, "y": 4.24, "prediction_number": 0, "scene_id": 0}} for i in range(len(loss_mask_rel_full_partial)): if loss_mask_rel_full_partial[i]: for tt in range(9, 21): # 9-20 ndjson_line = {} ndjson_line["track"] = {} ndjson_line["track"]["f"] = start_frame_id + int(frame_diff)*tt ndjson_line["track"]["p"] = int(ped_id_list[i].item()) ndjson_line["track"]["x"] = round(pred_traj[0,i,0,tt-9], 2) ndjson_line["track"]["y"] = round(pred_traj[0,i,0,tt-9], 2) ndjson_line["track"]["prediction_number"] = 0 ndjson_line["track"]["scene_id"] = batch_idx """ # import matplotlib .pyplot as plt # (Pdb) fig, ax = plt.subplots() # (Pdb) [ax.plot(pred_traj[0,i,0,:], pred_traj[0,i,1,:], '.-') for i in range(6)] # [[], [], [], [], [], []] # (Pdb) fig.savefig("tmp4.jpg") # import ndjson # # Streaming lines from ndjson file: # with open('./posts.ndjson') as f: # reader = ndjson.reader(f) # for post in reader: # print(post) # # Writing items to a ndjson file # with open('./posts.ndjson', 'w') as f: # writer = ndjson.writer(f, ensure_ascii=False) # for post in posts: # writer.writerow(post) # print(pred_traj.shape) # obs_traj # obs_traj # (1,6,2,8) # x_sample_pred torch.Size([1, 12, 6, 2]) # pos_pred = torch.cumsum(x_sample_pred, dim=1) # pos_target = torch.cumsum(x_target_m, dim=1) # self.obs_traj[start:end, :], self.pred_traj[start:end, :], # self.obs_traj_rel[start:end, :], self.pred_traj_rel[start:end, :], # self.loss_mask_rel[start:end, :], self.loss_mask[start:end, :], # self.v_obs[index], self.A_obs[index], # self.v_pred[index], self.A_pred[index], # self.attn_mask_obs[index], self.attn_mask_pred[index], # ] # for mode in ['test']: # dataset_filepath = join(dataset_folderpath, 'biwi_eth.ndjson') # dset = TrajectoriesDataset( # dataset_filepath, # obs_seq_len=args.obs_seq_len, # pred_seq_len=args.pred_seq_len, # skip=1, # invalid_value=args.invalid_value, # mode=mode, # ) # result_filename = 'biwi_eth_dset_'+mode+'_trajectories.pt' # torch.save(dset, join(dataset_folderpath, '..', result_filename)) # print(join(dataset_folderpath, '..', result_filename)+' is created.') # def main(args): # print('\n\n') # print('-'*50) # torch.manual_seed(args.random_seed) # np.random.seed(args.random_seed) # device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # loader_test = load_batch_dataset(args, pathhack.pkg_path, subfolder='test') # if args.dataset == 'sdd': # loader_val = load_batch_dataset(args, pathhack.pkg_path, subfolder='test') # no val for sdd # else: # loader_val = load_batch_dataset(args, pathhack.pkg_path, subfolder='val') # print('dataset: ', args.dataset) # writername = args2writername(args) # logdir = join(pathhack.pkg_path, 'results', writername, args.dataset) # if not isdir(logdir): # raise RuntimeError('The result directory was not found.') # checkpoint_dir = join(logdir, 'checkpoint') # model_filename = 'epoch_'+str(args.num_epochs)+'.pt' # with open(join(checkpoint_dir, 'args.pickle'), 'rb') as f: # args_eval = pickle.load(f) # model = st_model(args_eval, device=device).to(device) # model_checkpoint = torch.load(join(checkpoint_dir, model_filename), map_location=device) # model.load_state_dict(model_checkpoint['model_state_dict']) # print('Loaded configuration: ', writername) # print('The best validation losses printed below should be the same.') # print('Validation loss in the checkpoint: ', model_checkpoint['val_loss_epoch']) # val_loss_epoch, val_aoe_epoch, val_foe_epoch = inference(loader_val, model, args, mode='val', tau=0.03, device=device) # print('Validation loss from loaded model: ', val_loss_epoch) # test_loss_epoch, test_aoe, test_foe, test_aoe_std, test_foe_std, test_aoe_min, test_foe_min = inference(loader_test, model, args, mode='test', tau=0.03, device=device) # print('Test loss from loaded model: ', test_loss_epoch) # print('dataset: {0} | test aoe: {1:.4f} | test aoe std: {2:.4f} | test foe: {3:.4f} | test foe std: {4:.4f} | min aoe: {5:.4f}, min foe: {6:.4f}'\ # .format(args.dataset, test_aoe, test_aoe_std, test_foe, test_foe_std, test_aoe_min, test_foe_min)) # dataset_list = ['eth', 'hotel', 'univ', 'zara1', 'zara2'] # dataset_idx = dataset_list.index(args.dataset) # csv_row_data = np.ones((4,5))*999999. # csv_row_data[:,dataset_idx] = np.array([test_aoe, test_foe, test_aoe_std, test_foe_std]) # csv_row_data = csv_row_data.reshape(-1) # csv_filename = join(checkpoint_dir, '../..', 'test_performance.csv') # with open(csv_filename, mode='a') as test_file: # test_writer = csv.writer(test_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) # test_writer.writerow(csv_row_data) # print(csv_filename+' is written.') # def inference(loader, model, args, mode='val', tau=1., device='cuda:0'): # with torch.no_grad(): # model.eval() # loss_epoch, aoe_epoch, foe_epoch, loss_mask_epoch = [], [], [], [] # aoe_mean_epoch, aoe_std_epoch, foe_mean_epoch, foe_std_epoch = [], [], [], [] # aoe_min_epoch, foe_min_epoch = [], [] # for batch_idx, batch in enumerate(loader): # obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ # v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt = batch # v_obs, A_obs, v_pred_gt, attn_mask_obs, loss_mask_rel = \ # v_obs.to(device), A_obs.to(device), v_pred_gt.to(device), \ # attn_mask_obs.to(device), loss_mask_rel.to(device) # if mode == 'val': # results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=False, sampling=False, device=device) # gaussian_params_pred, x_sample_pred, info = results # loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] # loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] # loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] # if args.deterministic: # offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) # loss = offset_error_sq.sum()/eventual_loss_mask.sum() # else: # prob_loss, eventual_loss_mask = negative_log_likelihood_full_partial(gaussian_params_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) # loss = prob_loss.sum()/eventual_loss_mask.sum() # aoe = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) # foe = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) # elif mode == 'test': # if args.deterministic: # sampling = False # else: # sampling = True # best_aoe_mean = 999999. # aoe, foe, loss = [], [], [] # for _ in range(20): # results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=True, sampling=sampling, device=device) # gaussian_params_pred, x_sample_pred, info = results # loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] # loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] # loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] # if args.deterministic: # offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) # loss_tmp = offset_error_sq.sum()/eventual_loss_mask.sum() # else: # prob_loss, eventual_loss_mask = negative_log_likelihood_full_partial(gaussian_params_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) # loss_tmp = prob_loss.sum()/eventual_loss_mask.sum() # aoe_tmp = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) # foe_tmp = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) # aoe.append(aoe_tmp) # foe.append(foe_tmp) # loss.append(loss_tmp) # aoe = torch.stack(aoe, dim=0) # foe = torch.stack(foe, dim=0) # aoe = aoe.sum(1) # foe = foe.sum(1) # aoe_mean = aoe.mean() # aoe_std = aoe.std() # foe_mean = foe.mean() # foe_std = foe.std() # aoe_min, foe_min = aoe.min(), foe.min() # loss = sum(loss)/len(loss) # else: # raise RuntimeError('We now only support val and test mode.') # if mode == 'val': # loss_epoch.append(loss.detach().to('cpu').item()) # aoe_epoch.append(aoe.detach().to('cpu').numpy()) # foe_epoch.append(foe.detach().to('cpu').numpy()) # loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) # elif mode == 'test': # loss_epoch.append(loss.detach().to('cpu').item()) # aoe_mean_epoch.append(aoe_mean.item()) # foe_mean_epoch.append(foe_mean.item()) # aoe_std_epoch.append(aoe_std.item()) # foe_std_epoch.append(foe_std.item()) # aoe_min_epoch.append(aoe_min.item()) # foe_min_epoch.append(foe_min.item()) # loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) # else: # raise RuntimeError('We only support val and test mode.') # if mode == 'val': # aoe_epoch, foe_epoch, loss_mask_epoch = \ # np.concatenate(aoe_epoch, axis=0), \ # np.concatenate(foe_epoch, axis=0), \ # np.concatenate(loss_mask_epoch, axis=0) # loss_epoch, aoe_epoch, foe_epoch = \ # np.mean(loss_epoch), \ # aoe_epoch.sum()/loss_mask_epoch.sum(), \ # foe_epoch.sum()/loss_mask_epoch.sum() # return loss_epoch, aoe_epoch, foe_epoch # elif mode == 'test': # loss_mask_epoch = np.concatenate(loss_mask_epoch, axis=0) # aoe = sum(aoe_mean_epoch)/loss_mask_epoch.sum() # foe = sum(foe_mean_epoch)/loss_mask_epoch.sum() # aoe_std = sum(aoe_std_epoch)/loss_mask_epoch.sum() # foe_std = sum(foe_std_epoch)/loss_mask_epoch.sum() # aoe_min = sum(aoe_min_epoch)/loss_mask_epoch.sum() # foe_min = sum(foe_min_epoch)/loss_mask_epoch.sum() # loss_epoch = np.mean(loss_epoch) # return loss_epoch, aoe, foe, aoe_std, foe_std, aoe_min, foe_min # else: # raise RuntimeError('We now only support val mode.') # if __name__ == "__main__": # args = arg_parse() # main(args) ================================================ FILE: gst_updated/scripts/experiments/pathhack.py ================================================ import sys from os.path import abspath, join, split file_path = split(abspath(__file__))[0] pkg_path = join(file_path, '../..') sys.path.append(pkg_path) ================================================ FILE: gst_updated/scripts/experiments/test.py ================================================ from gst_updated.scripts.data import pathhack import pickle from os.path import join, isdir import torch import csv import numpy as np from gst_updated.src.mgnn.utils import arg_parse, average_offset_error, final_offset_error, args2writername, load_batch_dataset from gst_updated.src.gumbel_social_transformer.st_model import st_model, offset_error_square_full_partial,\ negative_log_likelihood_full_partial def main(args): print('\n\n') print('-'*50) torch.manual_seed(args.random_seed) np.random.seed(args.random_seed) device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") loader_test = load_batch_dataset(args, pathhack.pkg_path, subfolder='test') print('dataset: ', args.dataset) writername = args2writername(args) logdir = join(pathhack.pkg_path, 'results', writername, args.dataset) logdir = '/home/shuijing/Desktop/CrowdNav_Prediction/gst_updated/results/100-gumbel_social_transformer-faster_lstm-lr_0.001-init_temp_0.5-edge_head_0-ebd_64-snl_1-snh_8-seed_1000/sj' if not isdir(logdir): raise RuntimeError('The result directory was not found.') checkpoint_dir = join(logdir, 'checkpoint') model_filename = 'epoch_'+str(args.num_epochs)+'.pt' with open(join(checkpoint_dir, 'args.pickle'), 'rb') as f: args_eval = pickle.load(f) model = st_model(args_eval, device=device).to(device) model_checkpoint = torch.load(join(checkpoint_dir, model_filename), map_location=device) model.load_state_dict(model_checkpoint['model_state_dict']) print('Loaded configuration: ', writername) test_loss_epoch, test_aoe, test_foe, test_aoe_std, test_foe_std, test_aoe_min, test_foe_min = inference(loader_test, model, args, mode='test', tau=0.03, device=device) print('Test loss from loaded model: ', test_loss_epoch) print('dataset: {0} | test aoe: {1:.4f} | test aoe std: {2:.4f} | test foe: {3:.4f} | test foe std: {4:.4f} | min aoe: {5:.4f}, min foe: {6:.4f}'\ .format(args.dataset, test_aoe, test_aoe_std, test_foe, test_foe_std, test_aoe_min, test_foe_min)) # dataset_list = ['eth', 'hotel', 'univ', 'zara1', 'zara2'] # dataset_idx = dataset_list.index(args.dataset) # csv_row_data = np.ones((4,5))*999999. # csv_row_data[:,dataset_idx] = np.array([test_aoe, test_foe, test_aoe_std, test_foe_std]) # csv_row_data = csv_row_data.reshape(-1) # csv_filename = join(checkpoint_dir, '../..', 'test_performance.csv') # with open(csv_filename, mode='a') as test_file: # test_writer = csv.writer(test_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) # test_writer.writerow(csv_row_data) # print(csv_filename+' is written.') def inference(loader, model, args, mode='val', tau=1., device='cuda:0'): with torch.no_grad(): model.eval() loss_epoch, aoe_epoch, foe_epoch, loss_mask_epoch = [], [], [], [] aoe_mean_epoch, aoe_std_epoch, foe_mean_epoch, foe_std_epoch = [], [], [], [] aoe_min_epoch, foe_min_epoch = [], [] for batch_idx, batch in enumerate(loader): obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt = batch v_obs, A_obs, v_pred_gt, attn_mask_obs, loss_mask_rel = \ v_obs.to(device), A_obs.to(device), v_pred_gt.to(device), \ attn_mask_obs.to(device), loss_mask_rel.to(device) if mode == 'val': results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=False, sampling=False, device=device) gaussian_params_pred, x_sample_pred, info = results loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] if args.deterministic: offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss = offset_error_sq.sum()/eventual_loss_mask.sum() else: prob_loss, eventual_loss_mask = negative_log_likelihood_full_partial(gaussian_params_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss = prob_loss.sum()/eventual_loss_mask.sum() aoe = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) foe = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) elif mode == 'test': if args.deterministic: sampling = False else: sampling = True best_aoe_mean = 999999. aoe, foe, loss = [], [], [] for _ in range(20): results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=True, sampling=sampling, device=device) gaussian_params_pred, x_sample_pred, info = results loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] if args.deterministic: offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss_tmp = offset_error_sq.sum()/eventual_loss_mask.sum() else: prob_loss, eventual_loss_mask = negative_log_likelihood_full_partial(gaussian_params_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss_tmp = prob_loss.sum()/eventual_loss_mask.sum() aoe_tmp = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) foe_tmp = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) aoe.append(aoe_tmp) foe.append(foe_tmp) loss.append(loss_tmp) aoe = torch.stack(aoe, dim=0) foe = torch.stack(foe, dim=0) aoe = aoe.sum(1) foe = foe.sum(1) aoe_mean = aoe.mean() aoe_std = aoe.std() foe_mean = foe.mean() foe_std = foe.std() aoe_min, foe_min = aoe.min(), foe.min() loss = sum(loss)/len(loss) else: raise RuntimeError('We now only support val and test mode.') if mode == 'val': loss_epoch.append(loss.detach().to('cpu').item()) aoe_epoch.append(aoe.detach().to('cpu').numpy()) foe_epoch.append(foe.detach().to('cpu').numpy()) loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) elif mode == 'test': loss_epoch.append(loss.detach().to('cpu').item()) aoe_mean_epoch.append(aoe_mean.item()) foe_mean_epoch.append(foe_mean.item()) aoe_std_epoch.append(aoe_std.item()) foe_std_epoch.append(foe_std.item()) aoe_min_epoch.append(aoe_min.item()) foe_min_epoch.append(foe_min.item()) loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) else: raise RuntimeError('We only support val and test mode.') if mode == 'val': aoe_epoch, foe_epoch, loss_mask_epoch = \ np.concatenate(aoe_epoch, axis=0), \ np.concatenate(foe_epoch, axis=0), \ np.concatenate(loss_mask_epoch, axis=0) loss_epoch, aoe_epoch, foe_epoch = \ np.mean(loss_epoch), \ aoe_epoch.sum()/loss_mask_epoch.sum(), \ foe_epoch.sum()/loss_mask_epoch.sum() return loss_epoch, aoe_epoch, foe_epoch elif mode == 'test': loss_mask_epoch = np.concatenate(loss_mask_epoch, axis=0) aoe = sum(aoe_mean_epoch)/loss_mask_epoch.sum() foe = sum(foe_mean_epoch)/loss_mask_epoch.sum() aoe_std = sum(aoe_std_epoch)/loss_mask_epoch.sum() foe_std = sum(foe_std_epoch)/loss_mask_epoch.sum() aoe_min = sum(aoe_min_epoch)/loss_mask_epoch.sum() foe_min = sum(foe_min_epoch)/loss_mask_epoch.sum() loss_epoch = np.mean(loss_epoch) return loss_epoch, aoe, foe, aoe_std, foe_std, aoe_min, foe_min else: raise RuntimeError('We now only support val mode.') if __name__ == "__main__": # args = arg_parse() # main(args) from crowd_nav.configs.config import Config config = Config() main(config.pred) ================================================ FILE: gst_updated/scripts/experiments/test_gst.py ================================================ from gst_updated.scripts.data import pathhack import pickle from os.path import join, isdir import torch import csv import numpy as np from gst_updated.src.mgnn.utils import arg_parse, average_offset_error, final_offset_error, args2writername, load_batch_dataset from gst_updated.src.gumbel_social_transformer.st_model import st_model, offset_error_square_full_partial,\ negative_log_likelihood_full_partial def main(args): print('\n\n') print('-'*50) torch.manual_seed(args.random_seed) np.random.seed(args.random_seed) device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") loader_test = load_batch_dataset(args, pathhack.pkg_path, subfolder='test') print('dataset: ', args.dataset) writername = args2writername(args) logdir = join(pathhack.pkg_path, 'results', writername, args.dataset) logdir = '/home/shuijing/Desktop/CrowdNav_Prediction/gst_updated/results/100-gumbel_social_transformer-faster_lstm-lr_0.001-init_temp_0.5-edge_head_0-ebd_64-snl_1-snh_8-seed_1000/sj' if not isdir(logdir): raise RuntimeError('The result directory was not found.') checkpoint_dir = join(logdir, 'checkpoint') model_filename = 'epoch_'+str(args.num_epochs)+'.pt' with open(join(checkpoint_dir, 'args.pickle'), 'rb') as f: args_eval = pickle.load(f) model = st_model(args_eval, device=device).to(device) model_checkpoint = torch.load(join(checkpoint_dir, model_filename), map_location=device) model.load_state_dict(model_checkpoint['model_state_dict']) print('Loaded configuration: ', writername) test_loss_epoch, test_aoe, test_foe, test_aoe_std, test_foe_std, test_aoe_min, test_foe_min = inference(loader_test, model, args, mode='test', tau=0.03, device=device) print('Test loss from loaded model: ', test_loss_epoch) print('dataset: {0} | test aoe: {1:.4f} | test aoe std: {2:.4f} | test foe: {3:.4f} | test foe std: {4:.4f} | min aoe: {5:.4f}, min foe: {6:.4f}'\ .format(args.dataset, test_aoe, test_aoe_std, test_foe, test_foe_std, test_aoe_min, test_foe_min)) # dataset_list = ['eth', 'hotel', 'univ', 'zara1', 'zara2'] # dataset_idx = dataset_list.index(args.dataset) # csv_row_data = np.ones((4,5))*999999. # csv_row_data[:,dataset_idx] = np.array([test_aoe, test_foe, test_aoe_std, test_foe_std]) # csv_row_data = csv_row_data.reshape(-1) # csv_filename = join(checkpoint_dir, '../..', 'test_performance.csv') # with open(csv_filename, mode='a') as test_file: # test_writer = csv.writer(test_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) # test_writer.writerow(csv_row_data) # print(csv_filename+' is written.') def inference(loader, model, args, mode='val', tau=1., device='cuda:0'): with torch.no_grad(): model.eval() loss_epoch, aoe_epoch, foe_epoch, loss_mask_epoch = [], [], [], [] aoe_mean_epoch, aoe_std_epoch, foe_mean_epoch, foe_std_epoch = [], [], [], [] aoe_min_epoch, foe_min_epoch = [], [] for batch_idx, batch in enumerate(loader): obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt = batch v_obs, A_obs, v_pred_gt, attn_mask_obs, loss_mask_rel = \ v_obs.to(device), A_obs.to(device), v_pred_gt.to(device), \ attn_mask_obs.to(device), loss_mask_rel.to(device) if mode == 'val': results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=False, sampling=False, device=device) gaussian_params_pred, x_sample_pred, info = results loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] if args.deterministic: offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss = offset_error_sq.sum()/eventual_loss_mask.sum() else: prob_loss, eventual_loss_mask = negative_log_likelihood_full_partial(gaussian_params_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss = prob_loss.sum()/eventual_loss_mask.sum() aoe = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) foe = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) elif mode == 'test': if args.deterministic: sampling = False else: sampling = True best_aoe_mean = 999999. aoe, foe, loss = [], [], [] for _ in range(20): results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=True, sampling=sampling, device=device) gaussian_params_pred, x_sample_pred, info = results loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] if args.deterministic: offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss_tmp = offset_error_sq.sum()/eventual_loss_mask.sum() else: prob_loss, eventual_loss_mask = negative_log_likelihood_full_partial(gaussian_params_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss_tmp = prob_loss.sum()/eventual_loss_mask.sum() aoe_tmp = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) foe_tmp = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) aoe.append(aoe_tmp) foe.append(foe_tmp) loss.append(loss_tmp) aoe = torch.stack(aoe, dim=0) foe = torch.stack(foe, dim=0) aoe = aoe.sum(1) foe = foe.sum(1) aoe_mean = aoe.mean() aoe_std = aoe.std() foe_mean = foe.mean() foe_std = foe.std() aoe_min, foe_min = aoe.min(), foe.min() loss = sum(loss)/len(loss) else: raise RuntimeError('We now only support val and test mode.') if mode == 'val': loss_epoch.append(loss.detach().to('cpu').item()) aoe_epoch.append(aoe.detach().to('cpu').numpy()) foe_epoch.append(foe.detach().to('cpu').numpy()) loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) elif mode == 'test': loss_epoch.append(loss.detach().to('cpu').item()) aoe_mean_epoch.append(aoe_mean.item()) foe_mean_epoch.append(foe_mean.item()) aoe_std_epoch.append(aoe_std.item()) foe_std_epoch.append(foe_std.item()) aoe_min_epoch.append(aoe_min.item()) foe_min_epoch.append(foe_min.item()) loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) else: raise RuntimeError('We only support val and test mode.') if mode == 'val': aoe_epoch, foe_epoch, loss_mask_epoch = \ np.concatenate(aoe_epoch, axis=0), \ np.concatenate(foe_epoch, axis=0), \ np.concatenate(loss_mask_epoch, axis=0) loss_epoch, aoe_epoch, foe_epoch = \ np.mean(loss_epoch), \ aoe_epoch.sum()/loss_mask_epoch.sum(), \ foe_epoch.sum()/loss_mask_epoch.sum() return loss_epoch, aoe_epoch, foe_epoch elif mode == 'test': loss_mask_epoch = np.concatenate(loss_mask_epoch, axis=0) aoe = sum(aoe_mean_epoch)/loss_mask_epoch.sum() foe = sum(foe_mean_epoch)/loss_mask_epoch.sum() aoe_std = sum(aoe_std_epoch)/loss_mask_epoch.sum() foe_std = sum(foe_std_epoch)/loss_mask_epoch.sum() aoe_min = sum(aoe_min_epoch)/loss_mask_epoch.sum() foe_min = sum(foe_min_epoch)/loss_mask_epoch.sum() loss_epoch = np.mean(loss_epoch) return loss_epoch, aoe, foe, aoe_std, foe_std, aoe_min, foe_min else: raise RuntimeError('We now only support val mode.') if __name__ == "__main__": # args = arg_parse() # main(args) from crowd_nav.configs.config import Config config = Config() main(config.pred) ================================================ FILE: gst_updated/scripts/experiments/train.py ================================================ import pathhack import pickle import time from os.path import join, isdir from os import makedirs import torch import numpy as np from tensorboardX import SummaryWriter from torch.optim.lr_scheduler import StepLR from src.mgnn.utils import arg_parse, average_offset_error, final_offset_error, random_rotate_graph, args2writername, load_batch_dataset from src.gumbel_social_transformer.temperature_scheduler import Temp_Scheduler from scripts.experiments.eval import inference from datetime import datetime def main(args): print('\n\n') print('-'*50) print('arguments: ', args) torch.manual_seed(args.random_seed) np.random.seed(args.random_seed) if args.batch_size != 1: raise RuntimeError("Batch size must be 1 for BatchTrajectoriesDataset.") if args.dataset == 'sdd' and args.rotation_pattern is not None: raise RuntimeError("SDD should not allow rotation since it uses pixels.") device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") print('device: ', device) loader_train = load_batch_dataset(args, pathhack.pkg_path, subfolder='train') if args.dataset == 'sdd': loader_val = load_batch_dataset(args, pathhack.pkg_path, subfolder='test') # no val for sdd else: loader_val = load_batch_dataset(args, pathhack.pkg_path, subfolder='val') train_data_loaders = [loader_train, loader_val] print('dataset: ', args.dataset) writername = args2writername(args) print('Config: ', writername) logdir = join(pathhack.pkg_path, 'results', writername, args.dataset) if isdir(logdir) and not args.resume_training: print('Error: The result directory was already created and used.') print('-'*50) print('\n\n') return writer = SummaryWriter(logdir=logdir) print('-'*50) print('\n\n') train(args, train_data_loaders, writer, logdir, device=device) writer.close() def train(args, data_loaders, writer, logdir, device='cuda:0'): print('-'*50) print('Training Phase') print('-'*50, '\n') loader_train, loader_val = data_loaders model = st_model(args, device=device).to(device) optimizer = torch.optim.Adam(model.parameters(), lr=args.lr) lr_scheduler = StepLR(optimizer, step_size=int(args.temp_epochs/4), gamma=0.3) checkpoint_dir = join(logdir, 'checkpoint') if args.resume_training: if not isdir(checkpoint_dir): raise RuntimeError("Checkpoint folder does not exist.") with open(join(checkpoint_dir, 'train_hist.pickle'), 'rb') as f: hist = pickle.load(f) print(join(checkpoint_dir, 'train_hist.pickle')+' is loaded.') if args.resume_epoch is None: checkpoint_epoch = hist['epoch'] else: checkpoint_epoch = args.resume_epoch hist['train_loss_task'], hist['val_loss_task'] = \ hist['train_loss_task'][:checkpoint_epoch//args.save_epochs], hist['val_loss_task'][:checkpoint_epoch//args.save_epochs] hist['train_aoe_task'], hist['val_aoe_task'] = \ hist['train_aoe_task'][:checkpoint_epoch//args.save_epochs], hist['val_aoe_task'][:checkpoint_epoch//args.save_epochs] hist['train_foe_task'], hist['val_foe_task'] = \ hist['train_foe_task'][:checkpoint_epoch//args.save_epochs], hist['val_foe_task'][:checkpoint_epoch//args.save_epochs] checkpoint = torch.load(join(checkpoint_dir, 'epoch_'+str(checkpoint_epoch)+'.pt')) # load checkpoint model.load_state_dict(checkpoint['model_state_dict']) optimizer.load_state_dict(checkpoint['optimizer_state_dict']) lr_scheduler.load_state_dict(checkpoint['lr_scheduler_state_dict']) temperature_scheduler = Temp_Scheduler(args.temp_epochs, args.init_temp, args.init_temp, \ temp_min=0.03, last_epoch=checkpoint_epoch-1) start_epoch = checkpoint_epoch+1 print('Model, optimizer, lr_scheduler, and temperature scheduler are loaded.') print('EPOCHS: '+str(start_epoch)+' to '+str(args.num_epochs)) else: if not isdir(checkpoint_dir): makedirs(checkpoint_dir) with open(join(checkpoint_dir, 'args.pickle'), 'wb') as f: pickle.dump(args, f) temperature_scheduler = Temp_Scheduler(args.temp_epochs, args.init_temp, args.init_temp, temp_min=0.03) hist = {} hist['epoch'] = 0 hist['train_loss_task'], hist['val_loss_task'] = [], [] hist['train_aoe_task'], hist['val_aoe_task'] = [], [] hist['train_foe_task'], hist['val_foe_task'] = [], [] start_epoch = 1 print('Model, optimizer, lr_scheduler, and temperature scheduler are initialized.') print('EPOCHS: '+str(start_epoch)+' to '+str(args.num_epochs)) print('Training started.\n') for epoch in range(start_epoch, args.num_epochs+1): model.train() epoch_start_time = time.time() tau = temperature_scheduler.step() train_loss_epoch, train_aoe_epoch, train_foe_epoch, train_loss_mask_epoch = [], [], [], [] batch_len = 0 valid_batch_len = 0 for batch_idx, batch in enumerate(loader_train): batch_len+=1 obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt = batch # print(v_obs.shape) if v_obs.shape[2]>128: continue valid_batch_len += 1 if args.rotation_pattern is not None: (v_obs, A_obs, v_pred_gt, A_pred_gt), _ = \ random_rotate_graph(args, v_obs, A_obs, v_pred_gt, A_pred_gt) v_obs, A_obs, v_pred_gt, attn_mask_obs, loss_mask_rel = \ v_obs.to(device), A_obs.to(device), v_pred_gt.to(device), \ attn_mask_obs.to(device), loss_mask_rel.to(device) results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=False, sampling=False, device=device) gaussian_params_pred, x_sample_pred, info = results loss_mask_per_pedestrian = info['loss_mask_per_pedestrian'] loss_mask_rel_full_partial = info['loss_mask_rel_full_partial'] # value depends on only_observe_full_period loss_mask_rel_pred = loss_mask_rel[:,:,-args.pred_seq_len:] if args.deterministic: offset_error_sq, eventual_loss_mask = offset_error_square_full_partial(x_sample_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss = offset_error_sq.sum()/eventual_loss_mask.sum() else: prob_loss, eventual_loss_mask = negative_log_likelihood_full_partial(gaussian_params_pred, v_pred_gt, loss_mask_rel_full_partial, loss_mask_rel_pred) loss = prob_loss.sum()/eventual_loss_mask.sum() train_loss_epoch.append(loss.detach().to('cpu').item()) loss = loss / args.batch_size loss.backward() # only consider the fully detected pedestrians aoe = average_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) foe = final_offset_error(x_sample_pred, v_pred_gt, loss_mask=loss_mask_per_pedestrian) train_aoe_epoch.append(aoe.detach().to('cpu').numpy()) train_foe_epoch.append(foe.detach().to('cpu').numpy()) train_loss_mask_epoch.append(loss_mask_per_pedestrian[0].detach().to('cpu').numpy()) if args.clip_grad is not None: torch.nn.utils.clip_grad_norm_( model.parameters(), args.clip_grad) optimizer.step() optimizer.zero_grad() lr_scheduler.step() print("valid batch ratio: ", valid_batch_len/batch_len) train_aoe_epoch, train_foe_epoch, train_loss_mask_epoch = \ np.concatenate(train_aoe_epoch, axis=0), \ np.concatenate(train_foe_epoch, axis=0), \ np.concatenate(train_loss_mask_epoch, axis=0) train_loss_epoch, train_aoe_epoch, train_foe_epoch = \ np.mean(train_loss_epoch), \ train_aoe_epoch.sum()/train_loss_mask_epoch.sum(), \ train_foe_epoch.sum()/train_loss_mask_epoch.sum() hist['train_loss_task'].append(train_loss_epoch) hist['train_aoe_task'].append(train_aoe_epoch) hist['train_foe_task'].append(train_foe_epoch) training_epoch_period = time.time() - epoch_start_time training_epoch_period_per_sample = training_epoch_period/len(loader_train) val_loss_epoch, val_aoe_epoch, val_foe_epoch = inference(loader_val, model, args, mode='val', tau=tau, device=device) hist['val_loss_task'].append(val_loss_epoch) hist['val_aoe_task'].append(val_aoe_epoch) hist['val_foe_task'].append(val_foe_epoch) hist['epoch'] = epoch print('Epoch: {0} | train loss: {1:.4f} | val loss: {2:.4f} | train aoe: {3:.4f} | val aoe: {4:.4f} | train foe: {5:.4f} | val foe: {6:.4f} | period: {7:.2f} sec | time per sample: {8:.4f} sec'\ .format(epoch, train_loss_epoch, val_loss_epoch,\ train_aoe_epoch, val_aoe_epoch,\ train_foe_epoch, val_foe_epoch,\ training_epoch_period, training_epoch_period_per_sample)) if epoch % args.save_epochs == 0: model_filename = join(checkpoint_dir, 'epoch_'+str(epoch)+'.pt') torch.save({ 'epoch': epoch, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict(), 'lr_scheduler_state_dict': lr_scheduler.state_dict(), 'train_loss_epoch': train_loss_epoch, 'val_loss_epoch': val_loss_epoch, 'train_aoe_epoch': train_aoe_epoch, 'val_aoe_epoch': val_aoe_epoch, 'train_foe_epoch': train_foe_epoch, 'val_foe_epoch': val_foe_epoch, 'training_date': datetime.today().strftime('%y%m%d'), }, model_filename) print('epoch_'+str(epoch)+'.pt is saved.') with open(join(checkpoint_dir, 'train_hist.pickle'), 'wb') as f: pickle.dump(hist, f) print(join(checkpoint_dir, 'train_hist.pickle')+' is saved.') writer.add_scalars('loss', {'train': hist['train_loss_task'][-1], 'val': hist['val_loss_task'][-1]}, epoch) writer.add_scalars('aoe', {'train': hist['train_aoe_task'][-1], 'val': hist['val_aoe_task'][-1]}, epoch) writer.add_scalars('foe', {'train': hist['train_foe_task'][-1], 'val': hist['val_foe_task'][-1]}, epoch) return if __name__ == "__main__": args = arg_parse() if args.temporal == "lstm" or args.temporal == "faster_lstm": from src.gumbel_social_transformer.st_model import st_model, offset_error_square_full_partial, \ negative_log_likelihood_full_partial else: raise RuntimeError('The temporal component is not lstm nor faster_lstm.') main(args) ================================================ FILE: gst_updated/scripts/wrapper/crowd_nav_interface_multi_env_parallel.py ================================================ from gst_updated.scripts.data import pathhack import pickle from os.path import join, isdir import torch import numpy as np from gst_updated.src.mgnn.utils import seq_to_graph from gst_updated.src.gumbel_social_transformer.st_model import st_model class CrowdNavPredInterfaceMultiEnv(object): def __init__(self, load_path, device, config, num_env): # *** Load model self.args = config self.device = device self.nenv = num_env if not isdir(load_path): raise RuntimeError('The result directory was not found.') checkpoint_dir = join(load_path, 'checkpoint') with open(join(checkpoint_dir, 'args.pickle'), 'rb') as f: args_eval = pickle.load(f) # Uncomment if you want a fixed random seed. # torch.manual_seed(args_eval.random_seed) # np.random.seed(args_eval.random_seed) self.model = st_model(args_eval, device=device).to(device) model_filename = 'epoch_'+str(args_eval.num_epochs)+'.pt' model_checkpoint = torch.load(join(checkpoint_dir, model_filename), map_location=device) self.model.load_state_dict(model_checkpoint['model_state_dict']) self.model.eval() print("LOADED MODEL") print("device: ", device) print() def forward(self, input_traj,input_binary_mask, sampling = True): """ inputs: - input_traj: # numpy # (n_env, num_peds, obs_seq_len, 2) - input_binary_mask: # numpy # (n_env, num_peds, obs_seq_len, 1) # Zhe: I think we should not just have the binary mask of shape (n_env, number of pedestrains, 1) # because some agents are partially detected, and they should not be simply ignored. - sampling: # bool # True means you sample from Gaussian. # False means you choose to use the mean of Gaussian as output. outputs: - output_traj: # torch "cpu" # (n_env, num_peds, pred_seq_len, 5) # where 5 includes [mu_x, mu_y, sigma_x, sigma_y, correlation coefficient] - output_binary_mask: # torch "cpu" # (n_env, num_peds, 1) # Zhe: this means for prediction, if an agent does not show up in the last and second # last observation time step, then the agent will not be predicted. """ input_traj = input_traj.cpu().numpy() input_binary_mask = input_binary_mask.cpu().numpy() obs_seq_len = 5 pred_seq_len = 5 invalid_value = -999. # *** Process input data num_peds_batch = 0 b_num_peds = [] b_obs_traj, b_obs_traj_rel, b_loss_mask_rel, b_loss_mask, b_v_obs, b_A_obs, b_attn_mask_obs = \ [],[],[],[],[],[],[] for i in range(self.nenv): input_traj_i = input_traj[i] input_binary_mask_i = input_binary_mask[i] obs_traj = np.transpose(input_traj_i, (0,2,1)) # (num_peds, 2, obs_seq_len) n_peds = obs_traj.shape[0] loss_mask_obs = input_binary_mask_i[:,:,0] # (num_peds, obs_seq_len) loss_mask_rel_obs = loss_mask_obs[:,:-1] * loss_mask_obs[:,-1:] loss_mask_rel_obs = np.concatenate((loss_mask_obs[:,0:1], loss_mask_rel_obs), axis=1) # (num_peds, obs_seq_len) loss_mask_rel_pred = (np.ones((n_peds, pred_seq_len)) * loss_mask_rel_obs[:,-1:]).astype('bool') # (num_peds) obs_traj_rel = obs_traj[:,:,1:] - obs_traj[:,:,:-1] obs_traj_rel = np.concatenate((np.zeros((n_peds, 2, 1)), obs_traj_rel), axis=2) obs_traj_rel = invalid_value*np.ones_like(obs_traj_rel)*(1-loss_mask_rel_obs[:,np.newaxis,:]) \ + obs_traj_rel*loss_mask_rel_obs[:,np.newaxis,:] obs_traj = torch.from_numpy(obs_traj) obs_traj_rel = torch.from_numpy(obs_traj_rel) # v_obs, A_obs = seq_to_graph(obs_traj, obs_traj_rel, attn_mech='rel_conv') # v_obs, A_obs = v_obs.unsqueeze(0), A_obs.unsqueeze(0) loss_mask_rel = np.concatenate((loss_mask_rel_obs, loss_mask_rel_pred), axis=1)[np.newaxis,:,:] # (1, num_peds, seq_len) loss_mask_rel = torch.from_numpy(loss_mask_rel) loss_mask_rel_obs = torch.from_numpy(loss_mask_rel_obs[np.newaxis,:,:]) attn_mask_obs = [] for tt in range(obs_seq_len): loss_mask_rel_tt = loss_mask_rel_obs[0,:,tt] # (n_peds,) attn_mask_obs.append(torch.outer(loss_mask_rel_tt, loss_mask_rel_tt).float()) # (n_peds, n_peds) attn_mask_obs = torch.stack(attn_mask_obs, dim=0).unsqueeze(0) # (1,obs_seq_len, n_peds, n_peds) obs_traj = obs_traj.unsqueeze(0) obs_traj_rel = obs_traj_rel.unsqueeze(0) loss_mask_pred = loss_mask_rel_pred loss_mask = np.concatenate((loss_mask_obs, loss_mask_pred), axis=1) loss_mask = torch.from_numpy(loss_mask).unsqueeze(0) num_peds_batch += obs_traj.shape[1] b_num_peds.append(obs_traj.shape[1]) b_obs_traj.append(obs_traj[0].float()) b_obs_traj_rel.append(obs_traj_rel[0].float()) b_loss_mask_rel.append(loss_mask_rel[0].float()) b_loss_mask.append(loss_mask[0].float()) # b_v_obs.append(v_obs[0].float()) # b_A_obs.append(A_obs[0].float()) b_attn_mask_obs.append(attn_mask_obs[0].float()) b_obs_traj = torch.cat(b_obs_traj, dim=0) b_obs_traj_rel = torch.cat(b_obs_traj_rel, dim=0) b_loss_mask_rel = torch.cat(b_loss_mask_rel, dim=0) b_loss_mask = torch.cat(b_loss_mask, dim=0) b_v_obs, b_A_obs = seq_to_graph(b_obs_traj, b_obs_traj_rel, attn_mech='rel_conv') b_attn_mask_obs_tensor = torch.zeros(obs_seq_len, num_peds_batch, num_peds_batch).float() curr_ped_idx = 0 for num_peds, attn_mask_obs_single in zip(b_num_peds, b_attn_mask_obs): b_attn_mask_obs_tensor[:,curr_ped_idx:curr_ped_idx+num_peds, \ curr_ped_idx:curr_ped_idx+num_peds] = attn_mask_obs_single curr_ped_idx += num_peds b_obs_traj = b_obs_traj.unsqueeze(0) b_obs_traj_rel = b_obs_traj_rel.unsqueeze(0) b_v_obs = b_v_obs.unsqueeze(0) b_A_obs = b_A_obs.unsqueeze(0) b_loss_mask_rel = b_loss_mask_rel.unsqueeze(0) b_attn_mask_obs_tensor = b_attn_mask_obs_tensor.unsqueeze(0) b_loss_mask = b_loss_mask.unsqueeze(0) # print("PROCESSED DATA") # print("b_obs_traj shape: ", b_obs_traj.shape) # print("b_obs_traj_rel shape: ", b_obs_traj_rel.shape) # print("b_v_obs shape: ", b_v_obs.shape) # print("b_A_obs shape: ", b_A_obs.shape) # print("b_loss_mask_rel shape: ", b_loss_mask_rel.shape) # print("b_attn_mask_obs_tensor shape: ", b_attn_mask_obs_tensor.shape) # print("b_loss_mask shape: ", b_loss_mask.shape) # print() # *** Perform trajectory prediction sampling = False with torch.no_grad(): b_v_obs, b_A_obs, b_attn_mask_obs_tensor, b_loss_mask_rel = \ b_v_obs.to(self.device), b_A_obs.to(self.device), \ b_attn_mask_obs_tensor.float().to(self.device), b_loss_mask_rel.float().to(self.device) results = self.model(b_v_obs, b_A_obs, b_attn_mask_obs_tensor, b_loss_mask_rel, tau=0.03, hard=True, sampling=sampling, device=self.device) gaussian_params_pred, x_sample_pred, info = results mu, sx, sy, corr = gaussian_params_pred # print(mu.shape) # torch.Size([1, 5, 48, 2]) # print(sx.shape) # print(sy.shape) # print(corr.shape) # print(b_obs_traj.shape) # print(b_loss_mask.shape) mu = mu.cumsum(1) sx_squared = sx**2. sy_squared = sy**2. corr_sx_sy = corr*sx*sy sx_squared_cumsum = sx_squared.cumsum(1) sy_squared_cumsum = sy_squared.cumsum(1) corr_sx_sy_cumsum = corr_sx_sy.cumsum(1) sx_cumsum = sx_squared_cumsum**(1./2) sy_cumsum = sy_squared_cumsum**(1./2) corr_cumsum = corr_sx_sy_cumsum/(sx_cumsum*sy_cumsum) mu_cumsum = mu.detach().to("cpu") + np.transpose(b_obs_traj[:,:,:,-1:], (0,3,1,2)) # (batch, time, node, 2) loss_mask_pred = b_loss_mask[:,:,obs_seq_len:].permute(0,2,1).unsqueeze(-1).bool() mu_cumsum = mu_cumsum * loss_mask_pred + invalid_value*(~loss_mask_pred) output_traj = torch.cat((mu_cumsum.detach().to("cpu"), sx_cumsum.detach().to("cpu"), sy_cumsum.detach().to("cpu"), corr_cumsum.detach().to("cpu")), dim=3) output_traj = output_traj.permute(0, 2, 1, 3)[0] # (ped, time, 5) output_binary_mask = loss_mask_pred[0,0,:,:].detach().to("cpu") # (ped, 1) num_total_peds = output_traj.shape[0] output_traj = output_traj.reshape(self.nenv, num_total_peds // self.nenv, output_traj.shape[1], output_traj.shape[2]) # (n_env, ped, time, 5) output_binary_mask = output_binary_mask.reshape(self.nenv, num_total_peds // self.nenv, 1) # (n_env, ped, 1) output_traj, output_binary_mask = output_traj.to(self.device), output_binary_mask.to(self.device) # reshape has been tested # print("PERFORMED PREDICTION") # print("mu_cumsum shape: ", mu_cumsum.shape) # print("sx_cumsum shape: ", sx_cumsum.shape) # print("sy_cumsum shape: ", sy_cumsum.shape) # print("corr_cumsum shape: ", corr_cumsum.shape) # print("output_traj device: ", output_traj.device) # print("output_binary_mask device: ", output_binary_mask.device) # print("output_traj shape: ", output_traj.shape) # print("output_binary_mask shape: ", output_binary_mask.shape) # print() return output_traj, output_binary_mask if __name__ == '__main__': # *** Create an input that aligns with the format of CrowdNav obs_seq_len = 5 pred_seq_len = 5 invalid_value = -999. ped_step = 0.56 x_init = 2. ped_0 = np.stack((x_init+np.arange((obs_seq_len-1)*ped_step, -ped_step, -ped_step),np.zeros(obs_seq_len)), 0) ped_1 = -ped_0 # (2,8) ped_3 = np.stack((np.zeros(obs_seq_len), x_init+np.arange((obs_seq_len-1)*ped_step, -ped_step, -ped_step)), 0) ped_2 = -ped_3 ped_4 = np.ones((2,obs_seq_len))*invalid_value ped_5 = np.ones((2,obs_seq_len))*invalid_value obs_traj_undamaged = np.stack((ped_0,ped_1,ped_2,ped_3,ped_4,ped_5), axis=0) # (num_peds, 2, obs_seq_len) n_peds = obs_traj_undamaged.shape[0] original_obs_traj = obs_traj_undamaged # (num_peds, 2, obs_seq_len) # 0: right, 1: left, 2: up, 3: down, 4,5: invalid agent original_obs_traj[2,:,:2] = invalid_value original_obs_traj[3,:,:2] = invalid_value # * input_traj and input_binary_mask are example inputs from CrowdNav input_traj = np.transpose(original_obs_traj, (0,2,1)) # (num_peds, obs_seq_len, 2) input_binary_mask = (input_traj!=invalid_value).prod(2).astype('bool')[:,:,np.newaxis] # (num_peds, obs_seq_len, 1) n_env = 8 input_traj = np.stack([input_traj for i in range(n_env)], axis=0) # (n_env, num_peds, obs_seq_len, 2) input_binary_mask = np.stack([input_binary_mask for i in range(n_env)],axis=0) # (n_env, num_peds, obs_seq_len, 1) print() print("INPUT DATA") print("input_traj shape: ", input_traj.shape) print("input_binary_mask shape:", input_binary_mask.shape) print() device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") args = "100-gumbel_social_transformer-faster_lstm-lr_0.001-init_temp_0.5-edge_head_0-ebd_64-snl_1-snh_8-seed_1000" model = CrowdNavPredInterfaceMultiEnv(load_path='/home/shuijing/Desktop/CrowdNav_Prediction/gst_updated/results/100-gumbel_social_transformer-faster_lstm-lr_0.001-init_temp_0.5-edge_head_0-ebd_64-snl_1-snh_8-seed_1000/sj', device=device, config = args, num_env=n_env) output_traj, output_binary_mask = model.forward( input_traj, input_binary_mask, sampling = True, ) print() print("OUTPUT DATA") print("output_traj shape: ", output_traj.shape) print("output_binary_mask shape:", output_binary_mask.shape) print() ================================================ FILE: gst_updated/scripts/wrapper/crowd_nav_interface_multi_env_visualization_test_single_batch.py ================================================ import pathhack import pickle from os.path import join, isdir import torch import numpy as np from src.mgnn.utils import seq_to_graph from src.gumbel_social_transformer.st_model import st_model import matplotlib.pyplot as plt # from gst_updated.src.mgnn.utils import average_offset_error, final_offset_error, args2writername, load_batch_dataset # from gst_updated.src.gumbel_social_transformer.st_model import st_model, offset_error_square_full_partial,\ # negative_log_likelihood_full_partial from torch.utils.data import DataLoader def load_batch_dataset(pkg_path, subfolder='test', num_workers=4, shuffle=None): result_filename = 'sj_dset_'+subfolder+'_trajectories.pt' dataset_folderpath = join(pkg_path, 'datasets/shuijing/orca_20humans_fov') dset = torch.load(join(dataset_folderpath, result_filename)) if shuffle is None: if subfolder == 'train': shuffle = True else: shuffle = False dloader = DataLoader( dset, batch_size=1, shuffle=shuffle, num_workers=num_workers) return dloader loader_test = load_batch_dataset(pathhack.pkg_path, subfolder='test') print(len(loader_test)) test_batches = [] max_num_peds = 0 for batch_idx, batch in enumerate(loader_test): if batch_idx % 1000 == 0 and len(test_batches) < 4 and batch_idx != 0: obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt = batch test_batches.append(batch) print("obs traj shape: ", obs_traj.shape) print("loss mask shape: ", loss_mask.shape) if max_num_peds < obs_traj.shape[1]: max_num_peds = obs_traj.shape[1] print("max number of pedestrians: ", max_num_peds) wrapper_input_traj = [] wrapper_input_binary_mask = [] wrapper_output_traj_gt = [] wrapper_output_binary_mask_gt = [] invalid_value = -999. for batch in test_batches: obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, \ v_obs, A_obs, v_pred_gt, A_pred_gt, attn_mask_obs, attn_mask_pred_gt = batch input_traj = obs_traj[0].permute(0,2,1) # (n_peds, obs_seq_len, 2) input_binary_mask = loss_mask[0,:,:input_traj.shape[1]].unsqueeze(2) # (n_peds, obs_seq_len, 1) # print(input_binary_mask.shape) output_traj_gt = pred_traj_gt[0].permute(0,2,1) # (num_peds, pred_seq_len, 2) output_binary_mask_gt = loss_mask[0,:,input_traj.shape[1]:].unsqueeze(2) # (n_peds, pred_seq_len, 1) n_peds, obs_seq_len, pred_seq_len = input_traj.shape[0], input_traj.shape[1], output_traj_gt.shape[1] if n_peds < max_num_peds: input_traj_complement = torch.ones(max_num_peds-n_peds, obs_seq_len, 2)*invalid_value input_binary_mask_complement = torch.zeros(max_num_peds-n_peds, obs_seq_len, 1) output_traj_gt_complement = torch.ones(max_num_peds-n_peds, pred_seq_len, 2)*invalid_value output_binary_mask_gt_complement = torch.zeros(max_num_peds-n_peds, pred_seq_len, 1) input_traj = torch.cat((input_traj, input_traj_complement), dim=0) input_binary_mask = torch.cat((input_binary_mask, input_binary_mask_complement), dim=0) output_traj_gt = torch.cat((output_traj_gt, output_traj_gt_complement), dim=0) output_binary_mask_gt = torch.cat((output_binary_mask_gt, output_binary_mask_gt_complement), dim=0) wrapper_input_traj.append(input_traj) wrapper_input_binary_mask.append(input_binary_mask) wrapper_output_traj_gt.append(output_traj_gt) wrapper_output_binary_mask_gt.append(output_binary_mask_gt) wrapper_input_traj = torch.stack(wrapper_input_traj, dim=0) wrapper_input_binary_mask = torch.stack(wrapper_input_binary_mask, dim=0) wrapper_output_traj_gt = torch.stack(wrapper_output_traj_gt, dim=0) wrapper_output_binary_mask_gt = torch.stack(wrapper_output_binary_mask_gt, dim=0) print("wrapper_input_traj shape: ", wrapper_input_traj.shape) print("wrapper_input_binary_mask shape: ", wrapper_input_binary_mask.shape) print("wrapper_output_traj_gt shape: ", wrapper_output_traj_gt.shape) print("wrapper_output_binary_mask_gt shape: ", wrapper_output_binary_mask_gt.shape) # wrapper_input_traj shape: torch.Size([4, 15, 5, 2]) # wrapper_input_binary_mask shape: torch.Size([4, 15, 5, 1]) # wrapper_output_traj_gt shape: torch.Size([4, 15, 5, 2]) # wrapper_output_binary_mask_gt shape: torch.Size([4, 15, 5, 1]) n_env = wrapper_input_traj.shape[0] input_traj = wrapper_input_traj.numpy() input_binary_mask = wrapper_input_binary_mask.numpy() print() print("INPUT DATA") print("input_traj shape: ", input_traj.shape) print("input_binary_mask shape:", input_binary_mask.shape) print() # *** Visualize input data def visualize_trajectory(obs_traj, loss_mask, sample_index, obs_seq_len=5, pred_seq_len=5): ##### Print Visualization Started ##### n_peds, seq_len = obs_traj.shape[1], obs_seq_len+pred_seq_len full_ped_idx = torch.where(loss_mask.sum(2)[0]==seq_len)[0] # loss_mask tensor: (1, num_peds, seq_len) fig, ax = plt.subplots() # ax.set_xlim((-8, 8)) # ax.set_ylim((-5, 5)) fig.set_tight_layout(True) for ped_idx in range(n_peds): if ped_idx in full_ped_idx: ax.plot(obs_traj[0, ped_idx, 0, :obs_seq_len], obs_traj[0, ped_idx, 1, :obs_seq_len], '.-', c='k') # black for obs else: for t_idx in range(seq_len): if loss_mask[0,ped_idx,t_idx] == 1: if t_idx < obs_seq_len: # obs blue for partially detected pedestrians ax.plot(obs_traj[0, ped_idx, 0, t_idx], obs_traj[0, ped_idx, 1, t_idx], '.', c='b') ax.set_aspect('equal', adjustable='box') ax.plot() fig.savefig("tmp_img_to_be_deleted_"+str(sample_index)+".png") print("tmp_img_to_be_deleted_"+str(sample_index)+".png is created.") return # visualize_trajectory(obs_traj, loss_mask, sample_index, obs_seq_len=obs_seq_len, pred_seq_len=obs_seq_len) def CrowdNavPredInterfaceMultiEnv( input_traj, input_binary_mask, sampling = True, ): """ inputs: - input_traj: # numpy # (n_env, num_peds, obs_seq_len, 2) - input_binary_mask: # numpy # (n_env, num_peds, obs_seq_len, 1) # Zhe: I think we should not just have the binary mask of shape (n_env, number of pedestrains, 1) # because some agents are partially detected, and they should not be simply ignored. - sampling: # bool # True means you sample from Gaussian. # False means you choose to use the mean of Gaussian as output. outputs: - output_traj: # torch "cpu" # (n_env, num_peds, pred_seq_len, 5) # where 5 includes [mu_x, mu_y, sigma_x, sigma_y, correlation coefficient] - output_binary_mask: # torch "cpu" # (n_env, num_peds, 1) # Zhe: this means for prediction, if an agent does not show up in the last and second # last observation time step, then the agent will not be predicted. """ # *** Process input data num_peds_batch = 0 b_num_peds = [] b_obs_traj, b_obs_traj_rel, b_loss_mask_rel, b_loss_mask, b_v_obs, b_A_obs, b_attn_mask_obs = \ [],[],[],[],[],[],[] n_env = 1 for i in range(n_env): input_traj_i = input_traj[i] input_binary_mask_i = input_binary_mask[i] obs_traj = np.transpose(input_traj_i, (0,2,1)) # (num_peds, 2, obs_seq_len) n_peds = obs_traj.shape[0] loss_mask_obs = input_binary_mask_i[:,:,0] # (num_peds, obs_seq_len) loss_mask_rel_obs = loss_mask_obs[:,:-1] * loss_mask_obs[:,-1:] loss_mask_rel_obs = np.concatenate((loss_mask_obs[:,0:1], loss_mask_rel_obs), axis=1) # (num_peds, obs_seq_len) # import pdb; pdb.set_trace() loss_mask_rel_pred = (np.ones((n_peds, pred_seq_len)) * loss_mask_rel_obs[:,-1:]).astype('bool') # (num_peds) obs_traj_rel = obs_traj[:,:,1:] - obs_traj[:,:,:-1] obs_traj_rel = np.concatenate((np.zeros((n_peds, 2, 1)), obs_traj_rel), axis=2) obs_traj_rel = invalid_value*np.ones_like(obs_traj_rel)*(1-loss_mask_rel_obs[:,np.newaxis,:]) \ + obs_traj_rel*loss_mask_rel_obs[:,np.newaxis,:] obs_traj = torch.from_numpy(obs_traj) obs_traj_rel = torch.from_numpy(obs_traj_rel) v_obs, A_obs = seq_to_graph(obs_traj, obs_traj_rel, attn_mech='rel_conv') v_obs, A_obs = v_obs.unsqueeze(0), A_obs.unsqueeze(0) loss_mask_rel = np.concatenate((loss_mask_rel_obs, loss_mask_rel_pred), axis=1)[np.newaxis,:,:] # (1, num_peds, seq_len) loss_mask_rel = torch.from_numpy(loss_mask_rel) loss_mask_rel_obs = torch.from_numpy(loss_mask_rel_obs[np.newaxis,:,:]) attn_mask_obs = [] for tt in range(obs_seq_len): loss_mask_rel_tt = loss_mask_rel_obs[0,:,tt] # (n_peds,) attn_mask_obs.append(torch.outer(loss_mask_rel_tt, loss_mask_rel_tt).float()) # (n_peds, n_peds) attn_mask_obs = torch.stack(attn_mask_obs, dim=0).unsqueeze(0) # (1,obs_seq_len, n_peds, n_peds) obs_traj = obs_traj.unsqueeze(0) obs_traj_rel = obs_traj_rel.unsqueeze(0) loss_mask_pred = loss_mask_rel_pred loss_mask = np.concatenate((loss_mask_obs, loss_mask_pred), axis=1) loss_mask = torch.from_numpy(loss_mask).unsqueeze(0) visualize_trajectory(obs_traj, loss_mask, i, obs_seq_len=obs_seq_len, pred_seq_len=obs_seq_len) num_peds_batch += obs_traj.shape[1] b_num_peds.append(obs_traj.shape[1]) b_obs_traj.append(obs_traj[0].float()) b_obs_traj_rel.append(obs_traj_rel[0].float()) b_loss_mask_rel.append(loss_mask_rel[0].float()) b_loss_mask.append(loss_mask[0].float()) b_v_obs.append(v_obs[0].float()) b_A_obs.append(A_obs[0].float()) b_attn_mask_obs.append(attn_mask_obs[0].float()) b_obs_traj = torch.cat(b_obs_traj, dim=0) b_obs_traj_rel = torch.cat(b_obs_traj_rel, dim=0) b_loss_mask_rel = torch.cat(b_loss_mask_rel, dim=0) b_loss_mask = torch.cat(b_loss_mask, dim=0) b_v_obs, b_A_obs = seq_to_graph(b_obs_traj, b_obs_traj_rel, attn_mech='rel_conv') b_attn_mask_obs_tensor = torch.zeros(obs_seq_len, num_peds_batch, num_peds_batch).float() curr_ped_idx = 0 for num_peds, attn_mask_obs_single in zip(b_num_peds, b_attn_mask_obs): b_attn_mask_obs_tensor[:,curr_ped_idx:curr_ped_idx+num_peds, \ curr_ped_idx:curr_ped_idx+num_peds] = attn_mask_obs_single curr_ped_idx += num_peds b_obs_traj = b_obs_traj.unsqueeze(0) b_obs_traj_rel = b_obs_traj_rel.unsqueeze(0) b_v_obs = b_v_obs.unsqueeze(0) b_A_obs = b_A_obs.unsqueeze(0) b_loss_mask_rel = b_loss_mask_rel.unsqueeze(0) b_attn_mask_obs_tensor = b_attn_mask_obs_tensor.unsqueeze(0) b_loss_mask = b_loss_mask.unsqueeze(0) # print("PROCESSED DATA") # print("b_obs_traj shape: ", b_obs_traj.shape) # print("b_obs_traj_rel shape: ", b_obs_traj_rel.shape) # print("b_v_obs shape: ", b_v_obs.shape) # print("b_A_obs shape: ", b_A_obs.shape) # print("b_loss_mask_rel shape: ", b_loss_mask_rel.shape) # print("b_attn_mask_obs_tensor shape: ", b_attn_mask_obs_tensor.shape) # print("b_loss_mask shape: ", b_loss_mask.shape) # print() # *** Load model writername = "100-gumbel_social_transformer-faster_lstm-lr_0.001-init_temp_0.5-edge_head_0-ebd_64-snl_1-snh_8-seed_1000" dataset = "sj" # Shui Jing logdir = join(pathhack.pkg_path, 'results', writername, dataset) device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") if not isdir(logdir): raise RuntimeError('The result directory was not found.') checkpoint_dir = join(logdir, 'checkpoint') with open(join(checkpoint_dir, 'args.pickle'), 'rb') as f: args_eval = pickle.load(f) # Uncomment if you want a fixed random seed. # torch.manual_seed(args_eval.random_seed) # np.random.seed(args_eval.random_seed) model = st_model(args_eval, device=device).to(device) model_filename = 'epoch_'+str(args_eval.num_epochs)+'.pt' model_checkpoint = torch.load(join(checkpoint_dir, model_filename), map_location=device) model.load_state_dict(model_checkpoint['model_state_dict']) # print("LOADED MODEL") # print("device: ", device) # print('Loaded configuration: ', writername) # print() # *** Perform trajectory prediction sampling = False with torch.no_grad(): model.eval() b_v_obs, b_A_obs, b_attn_mask_obs_tensor, b_loss_mask_rel = \ b_v_obs.to(device), b_A_obs.to(device), \ b_attn_mask_obs_tensor.float().to(device), b_loss_mask_rel.float().to(device) results = model(b_v_obs, b_A_obs, b_attn_mask_obs_tensor, b_loss_mask_rel, tau=0.03, hard=True, sampling=sampling, device=device) gaussian_params_pred, x_sample_pred, info = results mu, sx, sy, corr = gaussian_params_pred # print(mu.shape) # torch.Size([1, 5, 48, 2]) # print(sx.shape) # print(sy.shape) # print(corr.shape) # print(b_obs_traj.shape) # print(b_loss_mask.shape) mu = mu.cumsum(1) sx_squared = sx**2. sy_squared = sy**2. corr_sx_sy = corr*sx*sy sx_squared_cumsum = sx_squared.cumsum(1) sy_squared_cumsum = sy_squared.cumsum(1) corr_sx_sy_cumsum = corr_sx_sy.cumsum(1) sx_cumsum = sx_squared_cumsum**(1./2) sy_cumsum = sy_squared_cumsum**(1./2) corr_cumsum = corr_sx_sy_cumsum/(sx_cumsum*sy_cumsum) mu_cumsum = mu.detach().to("cpu") + np.transpose(b_obs_traj[:,:,:,-1:], (0,3,1,2)) # (batch, time, node, 2) loss_mask_pred = b_loss_mask[:,:,obs_seq_len:].permute(0,2,1).unsqueeze(-1).bool() mu_cumsum = mu_cumsum * loss_mask_pred + invalid_value*(~loss_mask_pred) output_traj = torch.cat((mu_cumsum.detach().to("cpu"), sx_cumsum.detach().to("cpu"), sy_cumsum.detach().to("cpu"), corr_cumsum.detach().to("cpu")), dim=3) output_traj = output_traj.permute(0, 2, 1, 3)[0] # (ped, time, 5) output_binary_mask = loss_mask_pred[0,0,:,:].detach().to("cpu") # (ped, 1) num_total_peds = output_traj.shape[0] output_traj = output_traj.reshape(n_env, num_total_peds // n_env, output_traj.shape[1], output_traj.shape[2]) # (n_env, ped, time, 5) output_binary_mask = output_binary_mask.reshape(n_env, num_total_peds // n_env, 1) # (n_env, ped, 1) output_traj, output_binary_mask = output_traj.numpy(), output_binary_mask.numpy() # reshape has been tested # print("PERFORMED PREDICTION") # print("mu_cumsum shape: ", mu_cumsum.shape) # print("sx_cumsum shape: ", sx_cumsum.shape) # print("sy_cumsum shape: ", sy_cumsum.shape) # print("corr_cumsum shape: ", corr_cumsum.shape) # print("output_traj device: ", output_traj.device) # print("output_binary_mask device: ", output_binary_mask.device) # print("output_traj shape: ", output_traj.shape) # print("output_binary_mask shape: ", output_binary_mask.shape) # print() return output_traj, output_binary_mask output_traj, output_binary_mask = CrowdNavPredInterfaceMultiEnv( input_traj[:1], input_binary_mask[:1], sampling = True, ) print() print("OUTPUT DATA") print("output_traj shape: ", output_traj.shape) print("output_binary_mask shape:", output_binary_mask.shape) print() # wrapper_input_traj shape: torch.Size([4, 15, 5, 2]) # wrapper_input_binary_mask shape: torch.Size([4, 15, 5, 1]) # wrapper_output_traj_gt shape: torch.Size([4, 15, 5, 2]) # wrapper_output_binary_mask_gt shape: torch.Size([4, 15, 5, 1]) # OUTPUT DATA # output_traj shape: torch.Size([4, 15, 5, 5]) # output_binary_mask shape: torch.Size([4, 15, 1]) # n_env = wrapper_input_traj.shape[0] # input_traj = wrapper_input_traj.numpy() # input_binary_mask = wrapper_input_binary_mask.numpy() # print() # print("INPUT DATA") # print("input_traj shape: ", input_traj.shape) # print("input_binary_mask shape:", input_binary_mask.shape) # print() def visualize_output_trajectory_deterministic(input_traj, input_binary_mask, output_traj, output_binary_mask, sample_index, obs_seq_len=5, pred_seq_len=5): ##### Print Visualization Started ##### input_traj_i = input_traj[sample_index] #15, 5, 2]) input_binary_mask_i = input_binary_mask[sample_index] #15, 5, 1]) output_traj_i = output_traj[sample_index] #15, 5, 5]) output_binary_mask_i = output_binary_mask[sample_index] #15, 1]) n_peds, seq_len = input_traj_i.shape[0], obs_seq_len+pred_seq_len full_obs_ped_idx = np.where(input_binary_mask_i.sum(1)[:,0]==obs_seq_len)[0] full_traj = np.concatenate((input_traj_i, output_traj_i[:,:,:2]), axis=1) # (15, 10, 2) output_binary_mask_i_pred_len = np.stack([output_binary_mask_i for j in range(pred_seq_len)], axis=1) # (15, 5, 1) loss_mask = np.concatenate((input_binary_mask_i, output_binary_mask_i_pred_len), axis=1) # (15, 10, 1) fig, ax = plt.subplots() # ax.set_xlim((-8, 8)) # ax.set_ylim((-5, 5)) fig.set_tight_layout(True) for ped_idx in range(n_peds): if ped_idx in full_obs_ped_idx: ax.plot(full_traj[ped_idx, obs_seq_len:, 0], full_traj[ped_idx, obs_seq_len:, 1], '.-', c='r') ax.plot(full_traj[ped_idx, :obs_seq_len, 0], full_traj[ped_idx, :obs_seq_len, 1], '.-', c='k') # black for obs else: for t_idx in range(seq_len): if loss_mask[ped_idx,t_idx,0] == 1: if t_idx < obs_seq_len: # obs blue for partially detected pedestrians ax.plot(full_traj[ped_idx, t_idx, 0], full_traj[ped_idx, t_idx, 1], '.', c='b') else: # pred orange for partially detected pedestrians ax.plot(full_traj[ped_idx, t_idx, 0], full_traj[ped_idx, t_idx, 1], '.', c='C1', alpha=0.2) ax.set_aspect('equal', adjustable='box') ax.plot() fig.savefig("SINGLE_pred_tmp_img_to_be_deleted_"+str(sample_index)+".png") print("SINGLE_pred_tmp_img_to_be_deleted_"+str(sample_index)+".png is created.") return for sample_index in range(n_env): visualize_output_trajectory_deterministic(input_traj, input_binary_mask, output_traj, output_binary_mask, sample_index, obs_seq_len=5, pred_seq_len=5) wrapper_visualization_data = {} wrapper_visualization_data['input_traj'] = input_traj wrapper_visualization_data['input_mask'] = input_binary_mask wrapper_visualization_data['output_traj'] = output_traj wrapper_visualization_data['output_mask'] = output_binary_mask wrapper_visualization_data['output_traj_gt'] = wrapper_output_traj_gt.numpy() wrapper_visualization_data['output_mask_gt'] = wrapper_output_binary_mask_gt.numpy() with open(join('wrapper_visualization_data.pickle'), 'wb') as f: pickle.dump(wrapper_visualization_data, f) print("wrapper_visualization_data.pickle is dumped.") # output_traj, output_binary_mask # wrapper_input_traj = torch.stack(wrapper_input_traj, dim=0) # wrapper_input_binary_mask = torch.stack(wrapper_input_binary_mask, dim=0) # wrapper_output_traj_gt = torch.stack(wrapper_output_traj_gt, dim=0) # wrapper_output_binary_mask_gt = torch.stack(wrapper_output_binary_mask_gt, dim=0) # print("wrapper_input_traj shape: ", wrapper_input_traj.shape) # print("wrapper_input_binary_mask shape: ", wrapper_input_binary_mask.shape) # print("wrapper_output_traj_gt shape: ", wrapper_output_traj_gt.shape) # print("wrapper_output_binary_mask_gt shape: ", wrapper_output_binary_mask_gt.shape) # wrapper_input_traj shape: torch.Size([4, 15, 5, 2]) # wrapper_input_binary_mask shape: torch.Size([4, 15, 5, 1]) # wrapper_output_traj_gt shape: torch.Size([4, 15, 5, 2]) # wrapper_output_binary_mask_gt shape: torch.Size([4, 15, 5, 1]) # n_env = wrapper_input_traj.shape[0] # input_traj = wrapper_input_traj.numpy() # input_binary_mask = wrapper_input_binary_mask.numpy() # with open(join(checkpoint_dir, 'args.pickle'), 'wb') as f: # pickle.dump(args, f) # def visualize_trajectory(obs_traj, loss_mask, sample_index, obs_seq_len=5, pred_seq_len=5): # ##### Print Visualization Started ##### # n_peds, seq_len = obs_traj.shape[1], obs_seq_len+pred_seq_len # full_ped_idx = torch.where(loss_mask.sum(2)[0]==seq_len)[0] # loss_mask tensor: (1, num_peds, seq_len) # fig, ax = plt.subplots() # # ax.set_xlim((-8, 8)) # # ax.set_ylim((-5, 5)) # fig.set_tight_layout(True) # for ped_idx in range(n_peds): # if ped_idx in full_ped_idx: # ax.plot(obs_traj[0, ped_idx, 0, :obs_seq_len], obs_traj[0, ped_idx, 1, :obs_seq_len], '.-', c='k') # black for obs # else: # for t_idx in range(seq_len): # if loss_mask[0,ped_idx,t_idx] == 1: # if t_idx < obs_seq_len: # # obs blue for partially detected pedestrians # ax.plot(obs_traj[0, ped_idx, 0, t_idx], obs_traj[0, ped_idx, 1, t_idx], '.', c='b') # ax.set_aspect('equal', adjustable='box') # ax.plot() # fig.savefig("tmp_img_to_be_deleted_"+str(sample_index)+".png") # print("tmp_img_to_be_deleted_"+str(sample_index)+".png is created.") # return # # lstm # torch.manual_seed(args.random_seed) # np.random.seed(args.random_seed) # target_ped_idx_from_full = 1#2#0#1#2 # 0-2 for zara1 # # print(full_ped_idx[target_ped_idx_from_full]) # with torch.no_grad(): # model.eval() # v_obs, A_obs, attn_mask_obs, loss_mask_rel = \ # v_obs.to(device), A_obs.to(device), \ # attn_mask_obs.float().to(device), loss_mask_rel.float().to(device) # results = model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=tau, hard=True, sampling=False, device=device) # # gaussian_params_pred, x_sample_pred, _, loss_mask_per_pedestrian = results # gaussian_params_pred, x_sample_pred, info = results # pred_traj_rel = torch.cat((obs_traj[:,:,:,-1:], x_sample_pred.permute(0,2,3,1)), dim=3) # (1, num_peds, 2, 1+pred_seq_len) # pred_traj = pred_traj_rel.cumsum(dim=3).to('cpu') # # full_traj = torch.cat((obs_traj, pred_traj_gt), dim=3) # (1, num_peds, 2, seq_len) # full_traj = torch.cat((obs_traj[:,:,:,:-1], pred_traj), dim=3) # (1, num_peds, 2, seq_len) # n_peds, seq_len = full_traj.shape[1], full_traj.shape[3] # # full_ped_idx = torch.where(loss_mask.sum(2)[0]==seq_len)[0] # # loss_mask tensor: (1, num_peds, seq_len) # # full_ped_idx = torch.where(info['loss_mask_rel_full_partial']==1)[1] # # info['loss_mask_rel_full_partial'] # full_ped_idx = torch.where(info['loss_mask_per_pedestrian']==1)[1] # partial_full_ped_idx = torch.where(info['loss_mask_rel_full_partial']==1)[1] # fig, ax = plt.subplots() # ax.set_xlim((-8, 8)) # ax.set_ylim((-5, 5)) # fig.set_tight_layout(True) # for ped_idx in range(n_peds): # if ped_idx in partial_full_ped_idx: # if ped_idx in full_ped_idx: # # ax.plot(full_traj[0, ped_idx, 0, 7:], full_traj[0, ped_idx, 1, 7:], '.-', c='C2') # green for pred gt # ax.plot(full_traj[0, ped_idx, 0, :8], full_traj[0, ped_idx, 1, :8], '.-', c='k') # black for obs # ax.plot(pred_traj[0, ped_idx, 0], pred_traj[0, ped_idx, 1], '.-', c='C1',alpha=0.2) # orange for pred # if ped_idx not in full_ped_idx: # for t_idx in range(seq_len): # if loss_mask[0,ped_idx,t_idx] == 1: # if t_idx < 8: # ax.plot(full_traj[0, ped_idx, 0, t_idx], full_traj[0, ped_idx, 1, t_idx], '.', c='b') # # obs blue # else: # ax.plot(full_traj[0, ped_idx, 0, t_idx], full_traj[0, ped_idx, 1, t_idx], '.', c='r') # # pred red # ax.set_aspect('equal', adjustable='box') # ax.plot() ================================================ FILE: gst_updated/scripts/wrapper/crowd_nav_interface_parallel.py ================================================ from gst_updated.src.gumbel_social_transformer.st_model import st_model from os.path import join, isdir import pickle import torch import numpy as np import matplotlib.pyplot as plt def seq_to_graph(seq_, seq_rel, attn_mech='rel_conv'): """ inputs: - seq_ # (n_env, num_peds, 2, obs_seq_len) - seq_rel # (n_env, num_peds, 2, obs_seq_len) outputs: - V # (n_env, obs_seq_len, num_peds, 2) - A # (n_env, obs_seq_len, num_peds, num_peds, 2) """ V = seq_rel.permute(0, 3, 1, 2) # (n_env, obs_seq_len, num_peds, 2) seq_permute = seq_.permute(0, 3, 1, 2) # (n_env, obs_seq_len, num_peds, 2) A = seq_permute.unsqueeze(3)-seq_permute.unsqueeze(2) # (n_env, obs_seq_len, num_peds, 1, 2) - (n_env, obs_seq_len, 1, num_peds, 2) return V, A class CrowdNavPredInterfaceMultiEnv(object): def __init__(self, load_path, device, config, num_env): # *** Load model self.args = config self.device = device self.nenv = num_env # Uncomment if you want a fixed random seed. # torch.manual_seed(args_eval.random_seed) # np.random.seed(args_eval.random_seed) self.args_eval = config checkpoint_dir = join(load_path, 'checkpoint') self.model = st_model(self.args_eval, device=device).to(device) model_filename = 'epoch_'+str(self.args_eval.num_epochs)+'.pt' model_checkpoint = torch.load(join(checkpoint_dir, model_filename), map_location=device) self.model.load_state_dict(model_checkpoint['model_state_dict']) self.model.eval() print("LOADED MODEL") print("device: ", device) print() def forward(self, input_traj,input_binary_mask, sampling = True): """ inputs: - input_traj: # numpy # (n_env, num_peds, obs_seq_len, 2) - input_binary_mask: # numpy # (n_env, num_peds, obs_seq_len, 1) # Zhe: I think we should not just have the binary mask of shape (n_env, number of pedestrains, 1) # because some agents are partially detected, and they should not be simply ignored. - sampling: # bool # True means you sample from Gaussian. # False means you choose to use the mean of Gaussian as output. outputs: - output_traj: # torch "cpu" # (n_env, num_peds, pred_seq_len, 5) # where 5 includes [mu_x, mu_y, sigma_x, sigma_y, correlation coefficient] - output_binary_mask: # torch "cpu" # (n_env, num_peds, 1) # Zhe: this means for prediction, if an agent does not show up in the last and second # last observation time step, then the agent will not be predicted. """ invalid_value = -999. # *** Process input data obs_traj = input_traj.permute(0,1,3,2) # (n_env, num_peds, 2, obs_seq_len) n_env, num_peds = obs_traj.shape[:2] loss_mask_obs = input_binary_mask[:,:,:,0] # (n_env, num_peds, obs_seq_len) loss_mask_rel_obs = loss_mask_obs[:,:,:-1] * loss_mask_obs[:,:,-1:] loss_mask_rel_obs = torch.cat((loss_mask_obs[:,:,:1], loss_mask_rel_obs), dim=2) # (n_env, num_peds, obs_seq_len) loss_mask_rel_pred = (torch.ones((n_env, num_peds, self.args_eval.pred_seq_len), device=self.device) * loss_mask_rel_obs[:,:,-1:]) loss_mask_rel = torch.cat((loss_mask_rel_obs, loss_mask_rel_pred), dim=2) # (n_env, num_peds, seq_len) loss_mask_pred = loss_mask_rel_pred loss_mask_rel_obs_permute = loss_mask_rel_obs.permute(0,2,1).reshape(n_env*self.args_eval.obs_seq_len, num_peds) # (n_env*obs_seq_len, num_peds) attn_mask_obs = torch.bmm(loss_mask_rel_obs_permute.unsqueeze(2), loss_mask_rel_obs_permute.unsqueeze(1)) # (n_env*obs_seq_len, num_peds, num_peds) attn_mask_obs = attn_mask_obs.reshape(n_env, self.args_eval.obs_seq_len, num_peds, num_peds) obs_traj_rel = obs_traj[:,:,:,1:] - obs_traj[:,:,:,:-1] obs_traj_rel = torch.cat((torch.zeros(n_env, num_peds, 2, 1, device=self.device), obs_traj_rel), dim=3) obs_traj_rel = invalid_value*torch.ones_like(obs_traj_rel)*(1-loss_mask_rel_obs.unsqueeze(2)) \ + obs_traj_rel*loss_mask_rel_obs.unsqueeze(2) v_obs, A_obs = seq_to_graph(obs_traj, obs_traj_rel, attn_mech='rel_conv') # *** Perform trajectory prediction sampling = False with torch.no_grad(): v_obs, A_obs, attn_mask_obs, loss_mask_rel = \ v_obs.to(self.device), A_obs.to(self.device), attn_mask_obs.to(self.device), loss_mask_rel.to(self.device) results = self.model(v_obs, A_obs, attn_mask_obs, loss_mask_rel, tau=0.03, hard=True, sampling=sampling, device=self.device) gaussian_params_pred, x_sample_pred, info = results mu, sx, sy, corr = gaussian_params_pred mu = mu.cumsum(1) sx_squared = sx**2. sy_squared = sy**2. corr_sx_sy = corr*sx*sy sx_squared_cumsum = sx_squared.cumsum(1) sy_squared_cumsum = sy_squared.cumsum(1) corr_sx_sy_cumsum = corr_sx_sy.cumsum(1) sx_cumsum = sx_squared_cumsum**(1./2) sy_cumsum = sy_squared_cumsum**(1./2) corr_cumsum = corr_sx_sy_cumsum/(sx_cumsum*sy_cumsum) mu_cumsum = mu.detach().to(self.device) + obs_traj.permute(0,3,1,2)[:,-1:]# np.transpose(obs_traj[:,:,:,-1:], (0,3,1,2)) # (batch, time, node, 2) mu_cumsum = mu_cumsum * loss_mask_pred.permute(0,2,1).unsqueeze(-1) + invalid_value*(1-loss_mask_pred.permute(0,2,1).unsqueeze(-1)) output_traj = torch.cat((mu_cumsum.detach().to(self.device), sx_cumsum.detach().to(self.device), sy_cumsum.detach().to(self.device), corr_cumsum.detach().to(self.device)), dim=3) output_traj = output_traj.permute(0, 2, 1, 3) # (n_env, num_peds, pred_seq_len, 5) output_binary_mask = loss_mask_pred[:,:,:1].detach().to(self.device) # (n_env, num_peds, 1) # first step same as following in prediction return output_traj, output_binary_mask def visualize_output_trajectory_deterministic(input_traj, input_binary_mask, output_traj, output_binary_mask, sample_index, obs_seq_len=5, pred_seq_len=5): ##### Print Visualization Started ##### input_traj_i = input_traj[sample_index] input_binary_mask_i = input_binary_mask[sample_index] output_traj_i = output_traj[sample_index] output_binary_mask_i = output_binary_mask[sample_index] num_peds, seq_len = input_traj_i.shape[0], obs_seq_len+pred_seq_len full_obs_ped_idx = np.where(input_binary_mask_i.sum(1)[:,0]==obs_seq_len)[0] full_traj = np.concatenate((input_traj_i, output_traj_i[:,:,:2]), axis=1) output_binary_mask_i_pred_len = np.stack([output_binary_mask_i for j in range(pred_seq_len)], axis=1) loss_mask = np.concatenate((input_binary_mask_i, output_binary_mask_i_pred_len), axis=1) fig, ax = plt.subplots() fig.set_tight_layout(True) for ped_idx in range(num_peds): if ped_idx in full_obs_ped_idx: ax.plot(full_traj[ped_idx, obs_seq_len:, 0], full_traj[ped_idx, obs_seq_len:, 1], '.-', c='r') ax.plot(full_traj[ped_idx, :obs_seq_len, 0], full_traj[ped_idx, :obs_seq_len, 1], '.-', c='k') # black for obs else: for t_idx in range(seq_len): if loss_mask[ped_idx,t_idx,0] == 1: if t_idx < obs_seq_len: # obs blue for partially detected pedestrians ax.plot(full_traj[ped_idx, t_idx, 0], full_traj[ped_idx, t_idx, 1], '.', c='b') else: # pred orange for partially detected pedestrians ax.plot(full_traj[ped_idx, t_idx, 0], full_traj[ped_idx, t_idx, 1], '.', c='C1', alpha=0.2) ax.set_aspect('equal', adjustable='box') ax.plot() fig.savefig(str(sample_index)+".png") print(str(sample_index)+".png is created.") return if __name__ == '__main__': # *** Create an input that aligns with the format of CrowdNav obs_seq_len = 5 pred_seq_len = 5 invalid_value = -999. wrapper_demo_data = torch.load('/home/shuijing/Desktop/CrowdNav_Prediction/gst_updated/datasets/wrapper_demo/wrapper_demo_data.pt') print("wrapper_demo_data.pt is loaded.") input_traj, input_binary_mask = wrapper_demo_data['input_traj'], wrapper_demo_data['input_mask'] n_env = input_traj.shape[0] assert input_traj.shape[2] == obs_seq_len """ - input_traj: # tensor # (n_env, num_peds, obs_seq_len, 2) - input_binary_mask: # tensor # (n_env, num_peds, obs_seq_len, 1) """ print() print("INPUT DATA") print("number of environments: ", n_env) print("input_traj shape: ", input_traj.shape) print("input_binary_mask shape:", input_binary_mask.shape) print() load_path = '/home/shuijing/Desktop/CrowdNav_Prediction/gst_updated/results/100-gumbel_social_transformer-faster_lstm-lr_0.001-init_temp_0.5-edge_head_0-ebd_64-snl_1-snh_8-seed_1000/sj' # load_path = join(pathhack.pkg_path, 'results/100-gumbel_social_transformer-faster_lstm-lr_0.001-init_temp_0.5-edge_head_0-ebd_64-snl_1-snh_8-seed_1000/sj') device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") args = "100-gumbel_social_transformer-faster_lstm-lr_0.001-init_temp_0.5-edge_head_0-ebd_64-snl_1-snh_8-seed_1000" model = CrowdNavPredInterfaceMultiEnv(load_path=load_path, device=device, config = args, num_env=n_env) input_traj = input_traj.cuda() input_binary_mask = input_binary_mask.cuda() output_traj, output_binary_mask = model.forward( input_traj, input_binary_mask, sampling = True, ) print() print("OUTPUT DATA") print("output_traj shape: ", output_traj.shape) print("output_binary_mask shape:", output_binary_mask.shape) print() for sample_index in range(n_env): visualize_output_trajectory_deterministic(input_traj, input_binary_mask, output_traj, output_binary_mask, sample_index, obs_seq_len=5, pred_seq_len=5) ================================================ FILE: gst_updated/scripts/wrapper/pathhack.py ================================================ import sys from os.path import abspath, join, split file_path = split(abspath(__file__))[0] pkg_path = join(file_path, '../..') sys.path.append(pkg_path) ================================================ FILE: gst_updated/src/gumbel_social_transformer/edge_selector_ghost.py ================================================ # import pathhack import torch import torch.nn as nn from torch.nn.functional import softmax from src.gumbel_social_transformer.mha import VanillaMultiheadAttention from src.gumbel_social_transformer.utils import _get_activation_fn, gumbel_softmax class EdgeSelector(nn.Module): r"""Ghost version.""" def __init__(self, d_motion, d_model, nhead=4, dropout=0.1, activation="relu"): super(EdgeSelector, self).__init__() assert d_model % nhead == 0 self.nhead = nhead self.head_dim = d_model // nhead self.augmented_edge_embedding = nn.Linear(3*d_motion, d_model) self.self_attn = VanillaMultiheadAttention(d_model, nhead, dropout=0.0) self.norm_augmented_edge = nn.LayerNorm(d_model) self.linear1 = nn.Linear(self.head_dim, self.head_dim) self.linear2 = nn.Linear(self.head_dim, 1) self.dropout1 = nn.Dropout(dropout) self.activation = _get_activation_fn(activation) self.d_model = d_model self.d_motion = d_motion print("new edge selector") def forward(self, x, A, attn_mask, tau=1., hard=False, device='cuda:0'): """ Encode pedestrian edge with node information. inputs: # * done: x, A need to be masked first before processing. - x: vertices representing pedestrians of one sample. # * bsz is batch size corresponding to Transformer setting. # * In pedestrian setting, bsz = batch_size*time_step # (bsz, node, d_motion) - A: edges representation relationships between pedestrians of one sample. # (bsz, node, node, d_motion) # row -> neighbor, col -> target - attn_mask: attention mask provided in advance. # (bsz, target_node, neighbor_node) # 1. means yes, i.e. attention exists. 0. means no. - tau: temperature hyperparameter of gumbel softmax. # ! Need annealing though training. - hard: hard or soft sampling. # True means one-hot sample for evaluation. # False means soft sample for reparametrization. - device: 'cuda:0' or 'cpu'. outputs: - edge_multinomial: The categorical distribution over the connections from targets to the neighbors # (time_step, target_node, num_heads, neighbor_node) # neighbor_node = nnode + 1 in ghost mode - sampled_edges: The edges sampled from edge_multinomial # (time_step, target_node, num_heads, neighbor_node) # neighbor_node = nnode + 1 in ghost mode """ bsz, nnode, d_motion = x.shape assert d_motion == self.d_motion attn_mask = attn_mask.to("cpu") attn_mask_ped = (attn_mask.sum(-1) > 0).float().unsqueeze(-1) # (bsz, nnode, 1) x = x * attn_mask_ped.to(device) x_ghost = torch.zeros(bsz, 1, d_motion).to(device) x_neighbor = torch.cat((x, x_ghost), dim=1) # (bsz, nnode+1, d_motion) x_neighbor = torch.ones(bsz,nnode+1,nnode,d_motion).to(device)*x_neighbor.view(bsz,nnode+1,1,d_motion) # row -> neighbor x_target = torch.ones(bsz,nnode+1,nnode,d_motion).to(device)*x.view(bsz,1,nnode,d_motion) # col -> target x_neighbor_target = torch.cat((x_neighbor, x_target), dim=-1) # (bsz,nnode+1,nnode,2*d_motion) A = A * attn_mask.permute(0,2,1).unsqueeze(-1).to(device) # (bsz, neighbor_node, target_node, d_motion) A_ghost = torch.zeros(bsz, 1, nnode, d_motion).to(device) A = torch.cat((A, A_ghost), dim=1) # (bsz,nnode+1,nnode,d_motion) A = torch.cat((x_neighbor_target, A), dim=-1) # (bsz,nnode+1,nnode,3*d_motion) # n_node==t_node+1==nnode+1 A = self.augmented_edge_embedding(A) # (bsz,nnode+1,nnode,d_model) A = self.norm_augmented_edge(A) A_perm = A.permute(0,2,1,3) # (bsz,nnode,nnode+1,d_model) A_perm = A_perm.reshape(A_perm.shape[0]*A_perm.shape[1], A_perm.shape[2], A_perm.shape[3]) # (time_step*target_node, neighbor_node, d_model) A_perm = A_perm.permute(1,0,2) # (neighbor_node, time_step*target_node, d_model) attn_mask_ghost = torch.ones(bsz, nnode, 1) # ghost exists all the time, not missing attn_mask = torch.cat((attn_mask, attn_mask_ghost), dim=2) #(bsz, nnode, nnode+1) # n_node==t_node+1==nnode+1 attn_mask_neighbors = attn_mask.view(bsz, nnode, nnode+1, 1) * attn_mask.view(bsz, nnode, 1, nnode+1) # (bsz, nnode, nnode+1, nnode+1) attn_mask_neighbors = attn_mask_neighbors.view(bsz*nnode, nnode+1, nnode+1) # (time_step*target_node, neighbor_node, neighbor_node) # attn_mask_neighbors = torch.cat([attn_mask_neighbors for _ in range(self.nhead)], dim=0) # (nhead*time_step*target_node, neighbor_node, neighbor_node) # ! bug fixed attn_mask_neighbors = torch.stack([attn_mask_neighbors for _ in range(self.nhead)], dim=1) # (time_step*target_node, nhead, neighbor_node, neighbor_node) attn_mask_neighbors = attn_mask_neighbors.view(attn_mask_neighbors.shape[0]*attn_mask_neighbors.shape[1], \ attn_mask_neighbors.shape[2], attn_mask_neighbors.shape[3]) # (time_step*target_node*nhead, neighbor_node, neighbor_node) _, _, A2 = self.self_attn(A_perm, A_perm, A_perm, attn_mask=attn_mask_neighbors.to(device)) # inputs: (L, N, E), (S, N, E), (S, N, E), (N*nhead, L, S) # bsz, num_heads, tgt_len, head_dim # A2 # (time_step*target_node, num_heads, neighbor_node, 4*d_model/nhead) # we use head_dim = 4*d_model/nhead A2 = A2.reshape(bsz, nnode, self.nhead, nnode+1, self.head_dim) # (time_step, target_node, num_heads, neighbor_node, head_dim) A2 = self.linear2(self.dropout1(self.activation(self.linear1(A2)))).squeeze(-1) # (time_step, target_node, num_heads, neighbor_node) edge_multinomial = softmax(A2, dim=-1) # (time_step, target_node, num_heads, neighbor_node) edge_multinomial = edge_multinomial * attn_mask.unsqueeze(2).to(device) # (time_step, target_node, num_heads, neighbor_node) edge_multinomial = edge_multinomial / (edge_multinomial.sum(-1).unsqueeze(-1)+1e-10) sampled_edges = self.edge_sampler(edge_multinomial, tau=tau, hard=hard) return edge_multinomial, sampled_edges def edge_sampler(self, edge_multinomial, tau=1., hard=False): r""" Sample from edge_multinomial using gumbel softmax for differentiable search. """ logits = torch.log(edge_multinomial+1e-10) # (time_step, target_node, num_heads, neighbor_node) sampled_edges = gumbel_softmax(logits, tau=tau, hard=hard, eps=1e-10) # (time_step, target_node, num_heads, neighbor_node) return sampled_edges if __name__ == "__main__": device = "cuda:0" edge_selector = EdgeSelector(2, 32, nhead=2, dropout=0.1).to(device) x = torch.randn(8, 3, 2) position = torch.randn(8,3,2) A = position.unsqueeze(2)-position.unsqueeze(1) # (8,3,3,2) loss_mask = torch.ones(3,8) loss_mask[0,:3] = 0. loss_mask[1,:5] = 0. attn_mask = [] x, A = x.to(device), A.to(device) for i in range(8): attn_mask.append(torch.outer(loss_mask[:,i], loss_mask[:,i])) attn_mask = torch.stack(attn_mask, dim=0) attn_mask = attn_mask.to(device) print(attn_mask[3]) edge_multinomial, sampled_edges = edge_selector(x, A, attn_mask, tau=1., hard=False, device=device) print("hello world.") ================================================ FILE: gst_updated/src/gumbel_social_transformer/edge_selector_no_ghost.py ================================================ import torch import torch.nn as nn from torch.nn.functional import softmax from gst_updated.src.gumbel_social_transformer.mha import VanillaMultiheadAttention from gst_updated.src.gumbel_social_transformer.utils import _get_activation_fn, gumbel_softmax class EdgeSelector(nn.Module): r"""No ghost version.""" def __init__(self, d_motion, d_model, nhead=4, dropout=0.1, activation="relu"): super(EdgeSelector, self).__init__() assert d_model % nhead == 0 self.nhead = nhead self.head_dim = d_model // nhead self.augmented_edge_embedding = nn.Linear(3*d_motion, d_model) self.self_attn = VanillaMultiheadAttention(d_model, nhead, dropout=0.0) self.norm_augmented_edge = nn.LayerNorm(d_model) self.linear1 = nn.Linear(self.head_dim, self.head_dim) self.linear2 = nn.Linear(self.head_dim, 1) self.dropout1 = nn.Dropout(dropout) self.activation = _get_activation_fn(activation) self.d_model = d_model self.d_motion = d_motion def forward(self, x, A, attn_mask, tau=1., hard=False, device='cuda:0'): """ Encode pedestrian edge with node information. inputs: # * done: x, A need to be masked first before processing. - x: vertices representing pedestrians of one sample. # * bsz is batch size corresponding to Transformer setting. # * In pedestrian setting, bsz = batch_size*time_step # (bsz, node, d_motion) - A: edges representation relationships between pedestrians of one sample. # (bsz, node, node, 2*d_motion) # row -> neighbor, col -> target - attn_mask: attention mask provided in advance. # (bsz, target_node, neighbor_node) # 1. means yes, i.e. attention exists. 0. means no. - tau: temperature hyperparameter of gumbel softmax. # ! Need annealing though training. - hard: hard or soft sampling. # True means one-hot sample for evaluation. # False means soft sample for reparametrization. - device: 'cuda:0' or 'cpu'. outputs: - edge_multinomial: The categorical distribution over the connections from targets to the neighbors # (time_step, target_node, num_heads, neighbor_node) # neighbor_node = nnode in no ghost mode - sampled_edges: The edges sampled from edge_multinomial # (time_step, target_node, num_heads, neighbor_node) # neighbor_node = nnode in no ghost mode """ bsz, nnode, d_motion = x.shape assert d_motion == self.d_motion attn_mask = attn_mask.to("cpu") attn_mask_ped = (attn_mask.sum(-1) > 0).float().unsqueeze(-1) # (bsz, nnode, 1) x = x * attn_mask_ped.to(device) x_neighbor = torch.ones(bsz,nnode,nnode,d_motion).to(device)*x.view(bsz,nnode,1,d_motion) # row -> neighbor x_target = torch.ones(bsz,nnode,nnode,d_motion).to(device)*x.view(bsz,1,nnode,d_motion) # col -> target x_neighbor_target = torch.cat((x_neighbor, x_target), dim=-1) # (bsz,nnode,nnode,2*d_motion) A = A * attn_mask.permute(0,2,1).unsqueeze(-1).to(device) # (bsz, neighbor_node, target_node, d_motion) A = torch.cat((x_neighbor_target, A), dim=-1) # (bsz,n_node,t_node,3*d_motion) A = self.augmented_edge_embedding(A) # (bsz,n_node,t_node,d_model) A = self.norm_augmented_edge(A) A_perm = A.permute(0,2,1,3) # (bsz,t_node,n_node,d_model) A_perm = A_perm.reshape(A_perm.shape[0]*A_perm.shape[1], A_perm.shape[2], A_perm.shape[3]) # (time_step*target_node, neighbor_node, d_model) A_perm = A_perm.permute(1,0,2) # (neighbor_node, time_step*target_node, d_model) attn_mask_neighbors = attn_mask.view(bsz, nnode, nnode, 1) * attn_mask.view(bsz, nnode, 1, nnode) # (bsz, t_node, n_node, n_nnode) attn_mask_neighbors = attn_mask_neighbors.reshape(bsz*nnode, nnode, nnode) # (time_step*target_node, neighbor_node, neighbor_node) # attn_mask_neighbors = torch.cat([attn_mask_neighbors for _ in range(self.nhead)], dim=0) # (nhead*time_step*target_node, neighbor_node, neighbor_node) # ! bug fixed attn_mask_neighbors = torch.stack([attn_mask_neighbors for _ in range(self.nhead)], dim=1) # (time_step*target_node, nhead, neighbor_node, neighbor_node) attn_mask_neighbors = attn_mask_neighbors.view(attn_mask_neighbors.shape[0]*attn_mask_neighbors.shape[1], \ attn_mask_neighbors.shape[2], attn_mask_neighbors.shape[3]) # (time_step*target_node*nhead, neighbor_node, neighbor_node) _, _, A2 = self.self_attn(A_perm, A_perm, A_perm, attn_mask=attn_mask_neighbors.to(device)) # bsz, num_heads, tgt_len, head_dim # A2 # (time_step*target_node, num_heads, neighbor_node, d_model/nhead) # we use head_dim = 4*d_model/nhead A2 = A2.reshape(bsz, nnode, self.nhead, nnode, self.head_dim) # (time_step, target_node, num_heads, neighbor_node, head_dim) A2 = self.linear2(self.dropout1(self.activation(self.linear1(A2)))).squeeze(-1) # (time_step, target_node, num_heads, neighbor_node) edge_multinomial = softmax(A2, dim=-1) # (time_step, target_node, num_heads, neighbor_node) edge_multinomial = edge_multinomial * attn_mask.unsqueeze(2).to(device) # (time_step, target_node, num_heads, neighbor_node) edge_multinomial = edge_multinomial / (edge_multinomial.sum(-1).unsqueeze(-1)+1e-10) sampled_edges = self.edge_sampler(edge_multinomial, tau=tau, hard=hard) return edge_multinomial, sampled_edges def edge_sampler(self, edge_multinomial, tau=1., hard=False): r""" Sample from edge_multinomial using gumbel softmax for differentiable search. """ logits = torch.log(edge_multinomial+1e-10) # (time_step, target_node, num_heads, neighbor_node) sampled_edges = gumbel_softmax(logits, tau=tau, hard=hard, eps=1e-10) # (time_step, target_node, num_heads, neighbor_node) return sampled_edges ================================================ FILE: gst_updated/src/gumbel_social_transformer/gumbel_social_transformer.py ================================================ import torch import torch.nn as nn from gst_updated.src.gumbel_social_transformer.utils import _get_clones class GumbelSocialTransformer(nn.Module): def __init__(self, d_motion, d_model, nhead_nodes, nhead_edges, nlayer, dim_feedforward=512, dim_hidden=32, \ dropout=0.1, activation="relu", attn_mech="vanilla", ghost=True): super(GumbelSocialTransformer, self).__init__() if ghost: if nhead_edges == 0: raise RuntimeError("Full connectivity conflicts with the Ghost setting.") print("Ghost version.") from gst_updated.src.gumbel_social_transformer.edge_selector_ghost import EdgeSelector from gst_updated.src.gumbel_social_transformer.node_encoder_layer_ghost import NodeEncoderLayer else: print("No ghost version.") from gst_updated.src.gumbel_social_transformer.edge_selector_no_ghost import EdgeSelector from gst_updated.src.gumbel_social_transformer.node_encoder_layer_no_ghost import NodeEncoderLayer if nhead_edges != 0: # 0 means it is fully connected self.edge_selector = EdgeSelector( d_motion, d_model, nhead=nhead_edges, dropout=dropout, activation=activation, ) self.node_embedding = nn.Linear(d_motion, d_model) node_encoder_layer = NodeEncoderLayer( d_model, nhead_nodes, dim_feedforward=dim_feedforward, dropout=dropout, activation=activation, attn_mech=attn_mech, ) self.node_encoder_layers = _get_clones(node_encoder_layer, nlayer) self.nlayer = nlayer self.nhead_nodes = nhead_nodes self.nhead_edges = nhead_edges print("new gst") def forward(self, x, A, attn_mask, tau=1., hard=False, device='cuda:0'): r""" Pass the input through the encoder layers in turn. inputs: - x: vertices representing pedestrians of one sample. # * bsz is batch size corresponding to Transformer setting. # * In pedestrian setting, bsz = batch_size*time_step # (bsz, nnode, d_motion) - A: edges representation relationships between pedestrians of one sample. # (bsz, nnode , nnode , d_motion) # row -> neighbor, col -> target - attn_mask: attention mask provided in advance. # (bsz, nnode , nnode ) # row -> target, col -> neighbor # 1. means yes, i.e. attention exists. 0. means no. - tau: temperature hyperparameter of gumbel softmax. # ! Need annealing though training. 1 is considered really soft at the beginning. - hard: hard or soft sampling. # True means one-hot sample for evaluation. # False means soft sample for reparametrization. - device: 'cuda:0' or 'cpu'. outputs: - x: encoded vertices representing pedestrians of one sample. # (bsz, nnode, d_model) # same as input - sampled_edges: sampled adjacency matrix at the last column. # (bsz, nnode , nhead_edges, neighbor_node) # * where neighbor_node = nnode+1 for ghost==True, # * and neighbor_node = nnode for ghost==False. - edge_multinomial: multinomial where sampled_edges are sampled. # (bsz, nnode , nhead_edges, neighbor_node) - attn_weights: attention weights during self-attention for nodes x. # (nlayer, bsz, nhead, nnode , neighbor_node) """ if self.nhead_edges != 0: # edge_multinomial # (bsz, nnode , nhead_edges, neighbor_node) # sampled_edges # (bsz, nnode , nhead_edges, neighbor_node) # ! Remember here edges does not include self edges automatically. edge_multinomial, sampled_edges = \ self.edge_selector(x, A, attn_mask, tau=tau, hard=hard, device=device) else: # full connectivity bsz, nnode = attn_mask.shape[0], attn_mask.shape[1] sampled_edges = torch.ones(bsz, nnode, 1, nnode).to(device) * attn_mask.unsqueeze(2) edge_multinomial = torch.ones(bsz, nnode, 1, nnode).to(device) * attn_mask.unsqueeze(2) # fake edge_multinomial, meaningless attn_weights_list = [] x = self.node_embedding(x) for i in range(self.nlayer): x, attn_weights_layer = self.node_encoder_layers[i](x, sampled_edges, attn_mask, device=device) attn_weights_list.append(attn_weights_layer) # x (bsz, nnode, d_model) # attn_weights_layer: (bsz, nhead, nnode , neighbor_node) attn_weights = torch.stack(attn_weights_list, dim=0) # (nlayer, bsz, nhead, nnode , neighbor_node) return x, sampled_edges, edge_multinomial, attn_weights ================================================ FILE: gst_updated/src/gumbel_social_transformer/mha.py ================================================ import torch from torch.nn.functional import softmax, dropout, linear from torch import Tensor from typing import Optional, Tuple from torch.nn import Module, Parameter # from torch.nn.modules.linear import _LinearWithBias from torch.nn import Linear from torch.nn.init import xavier_uniform_, constant_ from torch.overrides import has_torch_function def multi_head_attention_forward( query: Tensor, key: Tensor, value: Tensor, embed_dim_to_check: int, num_heads: int, in_proj_weight: Tensor, in_proj_bias: Optional[Tensor], bias_k: Optional[Tensor], bias_v: Optional[Tensor], add_zero_attn: bool, dropout_p: float, out_proj_weight: Tensor, out_proj_bias: Optional[Tensor], training: bool = True, key_padding_mask: Optional[Tensor] = None, need_weights: bool = True, attn_mask: Optional[Tensor] = None, use_separate_proj_weight: bool = False, q_proj_weight: Optional[Tensor] = None, k_proj_weight: Optional[Tensor] = None, v_proj_weight: Optional[Tensor] = None, static_k: Optional[Tensor] = None, static_v: Optional[Tensor] = None, ) -> Tuple[Tensor, Optional[Tensor]]: tens_ops = (query, key, value, in_proj_weight, in_proj_bias, bias_k, bias_v, out_proj_weight, out_proj_bias) if has_torch_function(tens_ops): return handle_torch_function( multi_head_attention_forward, tens_ops, query, key, value, embed_dim_to_check, num_heads, in_proj_weight, in_proj_bias, bias_k, bias_v, add_zero_attn, dropout_p, out_proj_weight, out_proj_bias, training=training, key_padding_mask=key_padding_mask, need_weights=need_weights, attn_mask=attn_mask, use_separate_proj_weight=use_separate_proj_weight, q_proj_weight=q_proj_weight, k_proj_weight=k_proj_weight, v_proj_weight=v_proj_weight, static_k=static_k, static_v=static_v, ) tgt_len, bsz, embed_dim = query.size() assert embed_dim == embed_dim_to_check assert key.size(0) == value.size(0) and key.size(1) == value.size(1) if isinstance(embed_dim, torch.Tensor): head_dim = embed_dim.div(num_heads, rounding_mode='trunc') else: head_dim = embed_dim // num_heads assert head_dim * num_heads == embed_dim, "embed_dim must be divisible by num_heads" scaling = float(head_dim) ** -0.5 if not use_separate_proj_weight: if (query is key or torch.equal(query, key)) and (key is value or torch.equal(key, value)): q, k, v = linear(query, in_proj_weight, in_proj_bias).chunk(3, dim=-1) elif key is value or torch.equal(key, value): _b = in_proj_bias _start = 0 _end = embed_dim _w = in_proj_weight[_start:_end, :] if _b is not None: _b = _b[_start:_end] q = linear(query, _w, _b) if key is None: assert value is None k = None v = None else: _b = in_proj_bias _start = embed_dim _end = None _w = in_proj_weight[_start:, :] if _b is not None: _b = _b[_start:] k, v = linear(key, _w, _b).chunk(2, dim=-1) else: _b = in_proj_bias _start = 0 _end = embed_dim _w = in_proj_weight[_start:_end, :] if _b is not None: _b = _b[_start:_end] q = linear(query, _w, _b) _b = in_proj_bias _start = embed_dim _end = embed_dim * 2 _w = in_proj_weight[_start:_end, :] if _b is not None: _b = _b[_start:_end] k = linear(key, _w, _b) _b = in_proj_bias _start = embed_dim * 2 _end = None _w = in_proj_weight[_start:, :] if _b is not None: _b = _b[_start:] v = linear(value, _w, _b) else: q_proj_weight_non_opt = torch.jit._unwrap_optional(q_proj_weight) len1, len2 = q_proj_weight_non_opt.size() assert len1 == embed_dim and len2 == query.size(-1) k_proj_weight_non_opt = torch.jit._unwrap_optional(k_proj_weight) len1, len2 = k_proj_weight_non_opt.size() assert len1 == embed_dim and len2 == key.size(-1) v_proj_weight_non_opt = torch.jit._unwrap_optional(v_proj_weight) len1, len2 = v_proj_weight_non_opt.size() assert len1 == embed_dim and len2 == value.size(-1) if in_proj_bias is not None: q = linear(query, q_proj_weight_non_opt, in_proj_bias[0:embed_dim]) k = linear(key, k_proj_weight_non_opt, in_proj_bias[embed_dim : (embed_dim * 2)]) v = linear(value, v_proj_weight_non_opt, in_proj_bias[(embed_dim * 2) :]) else: q = linear(query, q_proj_weight_non_opt, in_proj_bias) k = linear(key, k_proj_weight_non_opt, in_proj_bias) v = linear(value, v_proj_weight_non_opt, in_proj_bias) q = q * scaling if attn_mask is not None: assert ( attn_mask.dtype == torch.float32 or attn_mask.dtype == torch.float64 or attn_mask.dtype == torch.float16 or attn_mask.dtype == torch.uint8 or attn_mask.dtype == torch.bool ), "Only float, byte, and bool types are supported for attn_mask, not {}".format(attn_mask.dtype) if attn_mask.dtype == torch.uint8: warnings.warn("Byte tensor for attn_mask in nn.MultiheadAttention is deprecated. Use bool tensor instead.") attn_mask = attn_mask.to(torch.bool) if attn_mask.dim() == 2: attn_mask = attn_mask.unsqueeze(0) if list(attn_mask.size()) != [1, query.size(0), key.size(0)]: raise RuntimeError("The size of the 2D attn_mask is not correct.") elif attn_mask.dim() == 3: if list(attn_mask.size()) != [bsz * num_heads, query.size(0), key.size(0)]: raise RuntimeError("The size of the 3D attn_mask is not correct.") else: raise RuntimeError("attn_mask's dimension {} is not supported".format(attn_mask.dim())) if key_padding_mask is not None and key_padding_mask.dtype == torch.uint8: warnings.warn( "Byte tensor for key_padding_mask in nn.MultiheadAttention is deprecated. Use bool tensor instead." ) key_padding_mask = key_padding_mask.to(torch.bool) if bias_k is not None and bias_v is not None: if static_k is None and static_v is None: k = torch.cat([k, bias_k.repeat(1, bsz, 1)]) v = torch.cat([v, bias_v.repeat(1, bsz, 1)]) if attn_mask is not None: attn_mask = pad(attn_mask, (0, 1)) if key_padding_mask is not None: key_padding_mask = pad(key_padding_mask, (0, 1)) else: assert static_k is None, "bias cannot be added to static key." assert static_v is None, "bias cannot be added to static value." else: assert bias_k is None assert bias_v is None q = q.contiguous().view(tgt_len, bsz * num_heads, head_dim).transpose(0, 1) if k is not None: k = k.contiguous().view(-1, bsz * num_heads, head_dim).transpose(0, 1) if v is not None: v = v.contiguous().view(-1, bsz * num_heads, head_dim).transpose(0, 1) if static_k is not None: assert static_k.size(0) == bsz * num_heads assert static_k.size(2) == head_dim k = static_k if static_v is not None: assert static_v.size(0) == bsz * num_heads assert static_v.size(2) == head_dim v = static_v src_len = k.size(1) if key_padding_mask is not None: assert key_padding_mask.size(0) == bsz assert key_padding_mask.size(1) == src_len if add_zero_attn: src_len += 1 k = torch.cat([k, torch.zeros((k.size(0), 1) + k.size()[2:], dtype=k.dtype, device=k.device)], dim=1) v = torch.cat([v, torch.zeros((v.size(0), 1) + v.size()[2:], dtype=v.dtype, device=v.device)], dim=1) if attn_mask is not None: attn_mask = pad(attn_mask, (0, 1)) if key_padding_mask is not None: key_padding_mask = pad(key_padding_mask, (0, 1)) attn_output_weights = torch.bmm(q, k.transpose(1, 2)) assert list(attn_output_weights.size()) == [bsz * num_heads, tgt_len, src_len] if attn_mask is not None: if attn_mask.dtype == torch.bool: attn_output_weights.masked_fill_(attn_mask, float("-inf")) if key_padding_mask is not None: attn_output_weights = attn_output_weights.view(bsz, num_heads, tgt_len, src_len) attn_output_weights = attn_output_weights.masked_fill( key_padding_mask.unsqueeze(1).unsqueeze(2), float("-inf"), ) attn_output_weights = attn_output_weights.view(bsz * num_heads, tgt_len, src_len) attn_output_weights = softmax(attn_output_weights, dim=-1) if attn_mask is not None: if attn_mask.dtype == torch.float32 \ or attn_mask.dtype == torch.float64 \ or attn_mask.dtype == torch.float16: attn_output_weights = attn_output_weights * attn_mask attn_output_weights = attn_output_weights / (attn_output_weights.sum(-1).unsqueeze(-1)+1e-10) attn_output_weights = dropout(attn_output_weights, p=dropout_p, training=training) attn_output = torch.bmm(attn_output_weights, v) assert list(attn_output.size()) == [bsz * num_heads, tgt_len, head_dim] attn_output_heads = attn_output.reshape(bsz, num_heads, tgt_len, head_dim) attn_output = attn_output.transpose(0, 1).contiguous().view(tgt_len, bsz, embed_dim) attn_output = linear(attn_output, out_proj_weight, out_proj_bias) if need_weights: attn_output_weights = attn_output_weights.view(bsz, num_heads, tgt_len, src_len) return attn_output, attn_output_weights, attn_output_heads else: return attn_output, None, attn_output_heads class VanillaMultiheadAttention(Module): bias_k: Optional[torch.Tensor] bias_v: Optional[torch.Tensor] def __init__(self, embed_dim, num_heads, dropout=0., bias=True, add_bias_kv=False, add_zero_attn=False, kdim=None, vdim=None): super(VanillaMultiheadAttention, self).__init__() self.embed_dim = embed_dim self.kdim = kdim if kdim is not None else embed_dim self.vdim = vdim if vdim is not None else embed_dim self._qkv_same_embed_dim = self.kdim == embed_dim and self.vdim == embed_dim self.num_heads = num_heads self.dropout = dropout self.head_dim = embed_dim // num_heads assert self.head_dim * num_heads == self.embed_dim, "embed_dim must be divisible by num_heads" if self._qkv_same_embed_dim is False: self.q_proj_weight = Parameter(torch.Tensor(embed_dim, embed_dim)) self.k_proj_weight = Parameter(torch.Tensor(embed_dim, self.kdim)) self.v_proj_weight = Parameter(torch.Tensor(embed_dim, self.vdim)) self.register_parameter('in_proj_weight', None) else: self.in_proj_weight = Parameter(torch.empty(3 * embed_dim, embed_dim)) self.register_parameter('q_proj_weight', None) self.register_parameter('k_proj_weight', None) self.register_parameter('v_proj_weight', None) if bias: self.in_proj_bias = Parameter(torch.empty(3 * embed_dim)) else: self.register_parameter('in_proj_bias', None) # self.out_proj = _LinearWithBias(embed_dim, embed_dim) self.out_proj = Linear(embed_dim, embed_dim) if add_bias_kv: self.bias_k = Parameter(torch.empty(1, 1, embed_dim)) self.bias_v = Parameter(torch.empty(1, 1, embed_dim)) else: self.bias_k = self.bias_v = None self.add_zero_attn = add_zero_attn self._reset_parameters() def _reset_parameters(self): if self._qkv_same_embed_dim: xavier_uniform_(self.in_proj_weight) else: xavier_uniform_(self.q_proj_weight) xavier_uniform_(self.k_proj_weight) xavier_uniform_(self.v_proj_weight) if self.in_proj_bias is not None: constant_(self.in_proj_bias, 0.) constant_(self.out_proj.bias, 0.) if self.bias_k is not None: xavier_normal_(self.bias_k) if self.bias_v is not None: xavier_normal_(self.bias_v) def __setstate__(self, state): if '_qkv_same_embed_dim' not in state: state['_qkv_same_embed_dim'] = True super(VanillaMultiheadAttention, self).__setstate__(state) def forward(self, query: Tensor, key: Tensor, value: Tensor, key_padding_mask: Optional[Tensor] = None, need_weights: bool = True, attn_mask: Optional[Tensor] = None) -> Tuple[Tensor, Optional[Tensor]]: if not self._qkv_same_embed_dim: return multi_head_attention_forward( query, key, value, self.embed_dim, self.num_heads, self.in_proj_weight, self.in_proj_bias, self.bias_k, self.bias_v, self.add_zero_attn, self.dropout, self.out_proj.weight, self.out_proj.bias, training=self.training, key_padding_mask=key_padding_mask, need_weights=need_weights, attn_mask=attn_mask, use_separate_proj_weight=True, q_proj_weight=self.q_proj_weight, k_proj_weight=self.k_proj_weight, v_proj_weight=self.v_proj_weight) else: return multi_head_attention_forward( query, key, value, self.embed_dim, self.num_heads, self.in_proj_weight, self.in_proj_bias, self.bias_k, self.bias_v, self.add_zero_attn, self.dropout, self.out_proj.weight, self.out_proj.bias, training=self.training, key_padding_mask=key_padding_mask, need_weights=need_weights, attn_mask=attn_mask) ================================================ FILE: gst_updated/src/gumbel_social_transformer/node_encoder_layer_ghost.py ================================================ import torch import torch.nn as nn from src.gumbel_social_transformer.mha import VanillaMultiheadAttention from src.gumbel_social_transformer.utils import _get_activation_fn class NodeEncoderLayer(nn.Module): r"""Ghost version""" def __init__(self, d_model, nhead, dim_feedforward=512, dropout=0.1, activation="relu", attn_mech='vanilla'): super(NodeEncoderLayer, self).__init__() self.attn_mech = attn_mech if self.attn_mech == 'vanilla': self.self_attn = VanillaMultiheadAttention(d_model, nhead, dropout=dropout) self.norm_node = nn.LayerNorm(d_model) else: raise RuntimeError('NodeEncoderLayer currently only supports vanilla mode.') self.norm1_node = nn.LayerNorm(d_model) self.linear1 = nn.Linear(d_model, dim_feedforward) self.linear2 = nn.Linear(dim_feedforward, d_model) self.dropout = nn.Dropout(dropout) self.dropout1 = nn.Dropout(dropout) self.dropout2 = nn.Dropout(dropout) self.activation = _get_activation_fn(activation) self.nhead = nhead self.d_model = d_model def forward(self, x, sampled_edges, attn_mask, device="cuda:0"): """ Encode pedestrian edge with node information. inputs: - x: vertices representing pedestrians of one sample. # bsz is batch size corresponding to Transformer setting. it corresponds to time steps in pedestrian setting. # (bsz, nnode, d_model) - sampled_edges: sampled adjacency matrix with ghost at the last column. # (time_step, nnode , nhead_edges, neighbor_node) # where neighbor_node = nnode+1 - attn_mask: attention mask provided in advance. # (bsz, nnode , nnode ) # row -> target, col -> neighbor # 1. means yes, i.e. attention exists. 0. means no. - device: 'cuda:0' or 'cpu'. outputs: - x: encoded vertices. # (bsz, nnode, d_model) - attn_weights: attention weights. # (bsz, nhead, nnode , neighbor_node) # where neighbor_node = nnode+1 """ if self.attn_mech == 'vanilla': bsz = x.shape[0] attn_mask_ped = (attn_mask.sum(-1) > 0).float().unsqueeze(-1).to(device) # (bsz, nnode, 1) x = self.norm_node(x) x = x * attn_mask_ped x_ghost = torch.zeros(bsz, 1, self.d_model).to(device) # add zero vector for ghost at every node encoder layer. x_w_ghost = torch.cat((x, x_ghost), dim=1) # (bsz, nnode+1, d_model) # x with ghost x_w_ghost_perm = x_w_ghost.permute(1,0,2) # (nnode+1, bsz, d_model) x_perm = x.permute(1, 0, 2) # (nnode, bsz, d_model) adj_mat = sampled_edges.sum(2) # (bsz, nnode, nnode+1) # adj_mat = torch.cat([adj_mat for _ in range(self.nhead)], dim=0) # (nhead*bsz, target_node, neighbor_node) adj_mat = torch.stack([adj_mat for _ in range(self.nhead)], dim=1) # (bsz, nhead, target_node, neighbor_node) adj_mat = adj_mat.view(bsz*self.nhead, adj_mat.shape[2], adj_mat.shape[3]) # (bsz*nhead, target_node, neighbor_node) # ! really important bug fix x2, attn_weights, _ = self.self_attn(x_perm, x_w_ghost_perm, x_w_ghost_perm, attn_mask=adj_mat) # inputs: (L, N, E), (S, N, E), (S, N, E), (N*nhead, L, S) # x2: (nnode, bsz, d_model); attn_weights: (bsz, nhead, target_node, neighbor_node) x2 = x2.permute(1, 0, 2) # (bsz, node, d_model) x = x + self.dropout(x2) else: raise RuntimeError('NodeEncoderLayer currently only supports vanilla mode.') x2 = self.norm1_node(x) x2 = self.dropout1(self.activation(self.linear1(x2))) x2 = self.dropout2(self.linear2(x2)) x = x + x2 return x, attn_weights ================================================ FILE: gst_updated/src/gumbel_social_transformer/node_encoder_layer_no_ghost.py ================================================ import torch import torch.nn as nn from gst_updated.src.gumbel_social_transformer.mha import VanillaMultiheadAttention from gst_updated.src.gumbel_social_transformer.utils import _get_activation_fn class NodeEncoderLayer(nn.Module): r"""No ghost version""" def __init__(self, d_model, nhead, dim_feedforward=512, dropout=0.1, activation="relu", attn_mech='vanilla'): super(NodeEncoderLayer, self).__init__() self.attn_mech = attn_mech if self.attn_mech == 'vanilla': self.self_attn = VanillaMultiheadAttention(d_model, nhead, dropout=dropout) self.norm_node = nn.LayerNorm(d_model) else: raise RuntimeError('NodeEncoderLayer currently only supports vanilla mode.') self.norm1_node = nn.LayerNorm(d_model) self.linear1 = nn.Linear(d_model, dim_feedforward) self.linear2 = nn.Linear(dim_feedforward, d_model) self.dropout = nn.Dropout(dropout) self.dropout1 = nn.Dropout(dropout) self.dropout2 = nn.Dropout(dropout) self.activation = _get_activation_fn(activation) self.nhead = nhead def forward(self, x, sampled_edges, attn_mask, device="cuda:0"): """ Encode pedestrian edge with node information. inputs: - x: vertices representing pedestrians of one sample. # bsz is batch size corresponding to Transformer setting. it corresponds to time steps in pedestrian setting. # (bsz, nnode, d_model) - sampled_edges: sampled adjacency matrix with ghost at the last column. # (time_step, nnode , nhead_edges, neighbor_node) # where neighbor_node = nnode - attn_mask: attention mask provided in advance. # (bsz, nnode , nnode ) # row -> target, col -> neighbor # 1. means yes, i.e. attention exists. 0. means no. - device: 'cuda:0' or 'cpu'. outputs: - x: encoded vertices. # (bsz, nnode, d_model) - attn_weights: attention weights. # (bsz, nhead, nnode , neighbor_node) # where neighbor_node = nnode """ if self.attn_mech == 'vanilla': bsz = x.shape[0] attn_mask_ped = (attn_mask.sum(-1) > 0).float().unsqueeze(-1).to(device) # (bsz, nnode, 1) x = self.norm_node(x) x = x * attn_mask_ped x_perm = x.permute(1, 0, 2) # (nnode, bsz, d_model) adj_mat = sampled_edges.sum(2) # (bsz, nnode, nnode) # adj_mat = torch.cat([adj_mat for _ in range(self.nhead)], dim=0) # (nhead*bsz, target_node, neighbor_node) adj_mat = torch.stack([adj_mat for _ in range(self.nhead)], dim=1) # (bsz, nhead, target_node, neighbor_node) adj_mat = adj_mat.view(bsz*self.nhead, adj_mat.shape[2], adj_mat.shape[3]) # (bsz*nhead, target_node, neighbor_node) # ! really important bug fix x2, attn_weights, _ = self.self_attn(x_perm, x_perm, x_perm, attn_mask=adj_mat) # inputs: (L, N, E), (S, N, E), (S, N, E), (N*nhead, L, S) # x2: (nnode, bsz, d_model); attn_weights: (bsz, nhead, target_node, neighbor_node) x2 = x2.permute(1, 0, 2) # (bsz, node, d_model) x = x + self.dropout(x2) else: raise RuntimeError('NodeEncoderLayer currently only supports vanilla mode.') x2 = self.norm1_node(x) x2 = self.dropout1(self.activation(self.linear1(x2))) x2 = self.dropout2(self.linear2(x2)) x = x + x2 return x, attn_weights ================================================ FILE: gst_updated/src/gumbel_social_transformer/pathhack.py ================================================ import sys from os.path import abspath, join, split file_path = split(abspath(__file__))[0] pkg_path = join(file_path, '../..') sys.path.append(pkg_path) ================================================ FILE: gst_updated/src/gumbel_social_transformer/st_model.py ================================================ """ st_model.py A multi-pedestrian trajectory prediction model that follows spatial -> temporal encoding manners. """ import torch import torch.nn as nn # from src.social_transformer.social_transformer import SpatialSocialTransformerEncoder from gst_updated.src.gumbel_social_transformer.gumbel_social_transformer import GumbelSocialTransformer from gst_updated.src.gumbel_social_transformer.temporal_convolution_net import TemporalConvolutionNet def offset_error_square_full_partial(x_pred, x_target, loss_mask_ped, loss_mask_pred_seq): """ Offset Error Square between positions. # * average_offset_error and final_offset_error in utils.py are computed for full pedestrians. inputs: - x_pred # prediction on pedestrian displacements in prediction period. # (batch, pred_seq_len, node, motion_dim) # batch = 1 - x_target # ground truth pedestrian displacements in prediction period. # (batch, pred_seq_len, node, motion_dim) - loss_mask_ped # loss mask on each pedestrian. 1 means the pedestrian is valid, and 0 means not valid. # * equivalent as loss_mask_rel_full_partial in st_model. # * Used to filter out the ones we do not predict. (disappear early, not appear until prediction period.) # (batch, node) - loss_mask_pred_seq # loss_mask_rel in prediction sequence. float32 tensor: (batch, num_peds, pred_seq_len) outputs: - offset_error_sq: offset error for each pedestrians. # Already times eventual_loss_mask before output. shape: (pred_seq_len, node) - eventual_loss_mask: eventual loss mask on each pedestrian and each prediction step. # shape: (pred_seq_len, node) """ assert x_pred.shape[0] == loss_mask_ped.shape[0] == loss_mask_pred_seq.shape[0] == 1 # batch assert x_pred.shape[1] == x_target.shape[1] == loss_mask_pred_seq.shape[2] # pred_seq_len assert x_pred.shape[2] == x_target.shape[2] == loss_mask_ped.shape[1] == loss_mask_pred_seq.shape[1] # num_peds assert x_pred.shape[3] == x_target.shape[3] == 2 # motion dim # mask out invalid values loss_mask_rel_pred = loss_mask_pred_seq.permute(0, 2, 1).unsqueeze(-1) # (batch, pred_seq_len, num_peds, 1) x_pred_m = x_pred * loss_mask_rel_pred # (batch, pred_seq_len, node, motion_dim) x_target_m = x_target * loss_mask_rel_pred x_pred_m = x_pred_m * loss_mask_ped.unsqueeze(1).unsqueeze(-1) # (batch, pred_seq_len, node, motion_dim) x_target_m = x_target_m * loss_mask_ped.unsqueeze(1).unsqueeze(-1) # (batch, pred_seq_len, node, motion_dim) pos_pred = torch.cumsum(x_pred_m, dim=1) pos_target = torch.cumsum(x_target_m, dim=1) offset_error_sq = (((pos_pred-pos_target)**2.).sum(3))[0] # (pred_seq_len, node) eventual_loss_mask = loss_mask_rel_pred[0,:,:,0] * loss_mask_ped[0] # (pred_seq_len, node) offset_error_sq = offset_error_sq * eventual_loss_mask return offset_error_sq, eventual_loss_mask def negative_log_likelihood_full_partial(gaussian_params, x_target, loss_mask_ped, loss_mask_pred_seq): """ Compute negative log likelihood of gaussian parameters. inputs: - gaussian_params: tuple. - mu: (batch, pred_seq_len, node, 2) - sx: (batch, pred_seq_len, node, 1) - sy: (batch, pred_seq_len, node, 1) - corr: (batch, pred_seq_len, node, 1) - x_target # ground truth pedestrian displacements in prediction period. # (batch, pred_seq_len, node, motion_dim) - loss_mask_ped # loss mask on each pedestrian. 1 means the pedestrian is valid, and 0 means not valid. # * equivalent as loss_mask_rel_full_partial in st_model. # * Used to filter out the ones we do not predict. (disappear early, not appear until prediction period.) # (batch, node) - loss_mask_pred_seq # loss_mask_rel in prediction sequence. float32 tensor: (batch, num_peds, pred_seq_len) outputs: - prob_loss: (pred_seq_len, node) - eventual_loss_mask: eventual loss mask on each pedestrian and each prediction step. # shape: (pred_seq_len, node) """ mu, sx, sy, corr = gaussian_params loss_mask_rel_pred = loss_mask_pred_seq.permute(0, 2, 1).unsqueeze(-1) # (batch, pred_seq_len, num_peds, 1) mu = mu * loss_mask_rel_pred # (batch, pred_seq_len, node, 2) corr = corr * loss_mask_rel_pred # (batch, pred_seq_len, node, 1) x_target = x_target * loss_mask_rel_pred mu = mu * loss_mask_ped.unsqueeze(1).unsqueeze(-1) # (batch, pred_seq_len, node, 2) corr = corr * loss_mask_ped.unsqueeze(1).unsqueeze(-1) # (batch, pred_seq_len, node, 1) x_target = x_target * loss_mask_ped.unsqueeze(1).unsqueeze(-1) # (batch, pred_seq_len, node, 2) sx = sx * loss_mask_rel_pred + (1.-loss_mask_rel_pred) # sigma should not be zero, so mask with one sy = sy * loss_mask_rel_pred + (1.-loss_mask_rel_pred) # sigma should not be zero, so mask with one sx = sx * loss_mask_ped.unsqueeze(1).unsqueeze(-1) + (1.-loss_mask_ped.unsqueeze(1).unsqueeze(-1)) # (batch, pred_seq_len, node, 1) sy = sy * loss_mask_ped.unsqueeze(1).unsqueeze(-1) + (1.-loss_mask_ped.unsqueeze(1).unsqueeze(-1)) # (batch, pred_seq_len, node, 1) sigma = torch.cat((sx, sy), dim=3) x_target_norm = (x_target-mu)/sigma nx, ny = x_target_norm[:,:,:,0:1], x_target_norm[:,:,:,1:2] loss_term_1 = torch.log(1.-corr**2.)/2.+torch.log(sx)+torch.log(sy) loss_term_2 = (nx**2.-2.*corr*nx*ny+ny**2.)/(2.*(1.-corr**2.)) prob_loss = (loss_term_1+loss_term_2).squeeze(3).squeeze(0) # (pred_seq_len, node) eventual_loss_mask = loss_mask_rel_pred[0,:,:,0] * loss_mask_ped[0] # (pred_seq_len, node) prob_loss = prob_loss * eventual_loss_mask return prob_loss, eventual_loss_mask class st_model(nn.Module): def __init__(self, args, device='cuda:0'): """ Initialize spatial and temporal encoding components. inputs: - args: arguments from user input. Here only list arguments used in st_model. (in __init__) ### * in function __init__() * ### - spatial # spatial encoding methods. options: rel_conv. - temporal # temporal encoding methods. options: lstm. - motion_dim # pedestrian motion is 2D, so motion_dim is always 2. - output_dim # 5 means probabilistic output (mu_x, mu_y, sigma_x, sigma_y, corr) # 2 means deterministic output (x, y) # ! may not do output_dim=2 in our work - embedding_size # size of pedstrian embeddings after spatial encoding. - spatial_num_heads # number of heads for multi-head attention # mechanism in spatial encoding. - spatial_beta # beta used in skip connection as a percentage of original input. # default can be None. If beta is not None, beta = 0.9 means # out <- 0.9 * x + 0.1 * out - lstm_hidden_size # hidden size of lstm. - lstm_num_layers # number of layers of lstm. - lstm_batch_first # batch first or not for lstm. - lstm_dropout # dropout rate of lstm. - decode_style # 'recursive' or 'readout'. # 'recursive' means recursively encode and decode. # 'readout' means encoding and decoding are separated. - detach_sample # bool value on whether detach samples from gaussian_params or not. # detach_sample=False is default. It means using reparametrization trick and enable gradient flow. # detach_sample=True means to disable reparametrization trick. # ! To add # ! args.spatial_num_heads_edges # ! args.ghost ### * in function foward() * ### - pred_seq_len # length of prediction period: 12 - device: 'cuda:0' or 'cpu'. """ super(st_model, self).__init__() ## spatial if args.spatial == 'gumbel_social_transformer': # self.node_embedding = nn.Linear(args.motion_dim, args.embedding_size).to(device) # self.edge_embedding = nn.Linear(args.motion_dim, 2 * args.embedding_size).to(device) self.gumbel_social_transformer = GumbelSocialTransformer( args.motion_dim, args.embedding_size, args.spatial_num_heads, args.spatial_num_heads_edges, args.spatial_num_layers, dim_feedforward=128, dim_hidden=32, dropout=0.1, activation="relu", attn_mech="vanilla", ghost=args.ghost, ).to(device) # self.spatial_social_transformer = SpatialSocialTransformerEncoder(args.embedding_size, args.spatial_num_heads, args.spatial_num_layers, \ # dim_feedforward=256, dim_hidden=32, dropout=0.1, activation="relu", attn_mech="vanilla").to(device) else: raise RuntimeError('The spatial component is not found.') ## temporal if args.temporal == 'lstm' or args.temporal == 'faster_lstm': self.lstm = nn.LSTM( input_size=args.embedding_size, hidden_size=args.lstm_hidden_size, num_layers=args.lstm_num_layers, batch_first=False, dropout=0., bidirectional=False, ).to(device) self.hidden2pos = nn.Linear(args.lstm_num_layers*args.lstm_hidden_size, args.output_dim).to(device) else: raise RuntimeError('The temporal component is not lstm nor faster_lstm.') ## others self.args = args print("new st model") def raw2gaussian(self, prob_raw): """ Turn raw values into gaussian parameters. inputs: - prob_raw: (batch, time, node, output_dim) - device: 'cuda:0' or 'cpu'. outputs: - gaussian_params: tuple. - mu: (batch, time, node, 2) - sx: (batch, time, node, 1) - sy: (batch, time, node, 1) - corr: (batch, time, node, 1) """ mu = prob_raw[:,:,:,:2] sx, sy = torch.exp(prob_raw[:,:,:,2:3]), torch.exp(prob_raw[:,:,:,3:4]) corr = torch.tanh(prob_raw[:,:,:,4:5]) gaussian_params = (mu, sx, sy, corr) return gaussian_params def sample_gaussian(self, gaussian_params, device='cuda:0', detach_sample=False, sampling=True): """ Generate a sample from Gaussian. inputs: - gaussian_params: tuple. - mu: (batch, time, node, 2) - sx: (batch, time, node, 1) - sy: (batch, time, node, 1) - corr: (batch, time, node, 1) - device: 'cuda:0' or 'cpu' - detach_sample: Bool. Default False. # Detach is to cut the gradient flow between gaussian_params and the next sample. # detach_sample=True means reparameterization trick is disabled. # detach_sample=False means reparameterization trick is enabled. # ! if it causes error, we need to manually turn detach_sample=False # ! or we have to change args file for val_best before jan 4, 2021. - sampling: # True means sampling. # False means using mu. outputs: - sample: (batch, time, node, 2) """ mu, sx, sy, corr = gaussian_params if detach_sample: mu, sx, sy, corr = mu.detach(), sx.detach(), sy.detach(), corr.detach() if sampling: sample_unit = torch.empty(mu.shape).normal_().to(device) # N(0,1) with shape (batch, time, node, 2) sample_unit_x, sample_unit_y = sample_unit[:,:,:,0:1], sample_unit[:,:,:,1:2] # (batch, time, node, 1) sample_x = sx*sample_unit_x sample_y = corr*sy*sample_unit_x+((1.-corr**2.)**0.5)*sy*sample_unit_y sample = torch.cat((sample_x, sample_y), dim=3)+mu else: sample = mu return sample def edge_evolution(self, xt_plus, At, device='cuda:0'): """ Compute edges at the next time step (At_plus) based on pedestrian displacements at the next time step (xt_plus) and edges at the current time step (At). inputs: - xt_plus: vertices representing pedestrian displacement from t to t+1. # (batch, unit_time, node, motion_dim) - At: edges representing relative position between pedestrians at time t. At(i, j) is the vector pos_i,t - pos_j,t. I.e. the vector from pedestrian j to pedestrian i. # (batch, unit_time, node, node, edge_feat) # batch = unit_time = 1. # edge_feat = 2. - device: 'cuda:0' or 'cpu'. outputs: - At_plus: edges representing relative position between pedestrians at time t. # (batch, unit_time, node, node, edge_feat) """ # xt_plus # (batch, unit_time, node, motion_dim) # At # (batch, unit_time, node, node, edge_feat) # (batch, unit_time, node, 1, motion_dim) - (batch, unit_time, 1, node, motion_dim) At_plus = At + (xt_plus.unsqueeze(3) - xt_plus.unsqueeze(2)) return At_plus def forward(self, x, A, attn_mask, loss_mask_rel, tau=1., hard=False, sampling=True, device='cuda:0'): """ Forward function. inputs: - x # vertices representing pedestrians during observation period. # (batch, obs_seq_len, node, in_feat) # node: number of pedestrians # in_feat: motion_dim, i.e. 2. # Refer to V_obs in src.mgnn.utils.dataset_format(). - A # edges representation relationships between pedestrians during observation period. # (batch, obs_seq_len, node, node, edge_feat) # edge_feat: feature dim of edges. if spatial encoding is rel_conv, edge_feat = 2. # Refer to A_obs in src.mgnn.utils.dataset_format(). - attn_mask # attention mask on pedestrian interactions in observation period. # row -> neighbor, col -> target # Should neighbor affect target? # 1 means yes, i.e. attention exists. 0 means no. # float32 tensor: (batch, obs_seq_len, neighbor_num_peds, target_num_peds) - loss_mask_rel # loss mask on displacement in the whole period # float32 tensor: (batch, num_peds, seq_len) # 1 means the displacement of pedestrian i at time t is valid. 0 means not valid. # If the displacement of pedestrian i at time t is valid, # then position of pedestrian i at time t and t-1 is valid. # If t is zero, then it means position of pedestrian i at time t is valid. - tau: temperature hyperparameter of gumbel softmax. # ! Need annealing though training. 1 is considered really soft at the beginning. - hard: hard or soft sampling. # True means one-hot sample for evaluation. # False means soft sample for reparametrization. - sampling: sample gaussian (True) or use mean for prediction (False). - device: 'cuda:0' or 'cpu'. outputs: # TODO """ # ! Start writing multiple batches info = {} # processing when missing pedestrians are included batch_size, _, num_peds, _ = x.shape loss_mask_per_pedestrian = (loss_mask_rel.sum(2)==loss_mask_rel.shape[2]).float() # (batch, num_peds) if self.args.only_observe_full_period: attn_mask_single_step = torch.bmm(loss_mask_per_pedestrian.unsqueeze(2), loss_mask_per_pedestrian.unsqueeze(1)) # (batch, num_peds, num_peds) attn_mask = torch.ones(batch_size, self.args.obs_seq_len, num_peds, num_peds).to(device) * attn_mask_single_step.unsqueeze(1) # (batch, obs_seq_len, num_peds, num_peds) ## observation period: spatial if self.args.spatial == 'gumbel_social_transformer': # x_embedding = self.node_embedding(x)[0] # (obs_seq_len, node, d_model) # A_embedding = self.edge_embedding(A)[0] # (obs_seq_len, nnode , nnode , 2*d_model) attn_mask = attn_mask.permute(0, 1, 3, 2) # (batch, obs_seq_len, nnode , nnode ) attn_mask_reshaped = attn_mask.reshape(batch_size*self.args.obs_seq_len, num_peds, num_peds) x_reshaped = x.reshape(batch_size*self.args.obs_seq_len, num_peds, -1) A_reshaped = A.reshape(batch_size*self.args.obs_seq_len, num_peds, num_peds, -1) #(batch, obs_seq_len, node, node, edge_feat) # ! attn_mask = attn_mask[0].permute(0,2,1) # (obs_seq_len, nnode , nnode ) # ! xs, sampled_edges, edge_multinomial, attn_weights = self.gumbel_social_transformer(x[0], A[0], attn_mask, tau=tau, hard=hard, device=device) xs, sampled_edges, edge_multinomial, attn_weights = self.gumbel_social_transformer(x_reshaped, A_reshaped, attn_mask_reshaped, tau=tau, hard=hard, device=device) xs = xs.reshape(batch_size, self.args.obs_seq_len, num_peds, -1) # (batch, obs_seq_len, nnode, embedding_size) info['sampled_edges'], info['edge_multinomial'], info['attn_weights'] = [], [], [] sampled_edges = sampled_edges.reshape(batch_size, self.args.obs_seq_len, sampled_edges.shape[1], sampled_edges.shape[2], sampled_edges.shape[3]) edge_multinomial = edge_multinomial.reshape(batch_size, self.args.obs_seq_len, edge_multinomial.shape[1], edge_multinomial.shape[2], edge_multinomial.shape[3]) attn_weights = attn_weights.reshape(attn_weights.shape[0], batch_size, self.args.obs_seq_len, attn_weights.shape[2], attn_weights.shape[3], attn_weights.shape[4]) info['sampled_edges'].append(sampled_edges.detach().to("cpu")) info['edge_multinomial'].append(edge_multinomial.detach().to("cpu")) info['attn_weights'].append(attn_weights.detach().to("cpu")) else: raise RuntimeError("The spatial component is not found.") ## observation period: temporal ht = torch.zeros(self.args.lstm_num_layers, batch_size*num_peds, self.args.lstm_hidden_size).to(device) ct = torch.zeros(self.args.lstm_num_layers, batch_size*num_peds, self.args.lstm_hidden_size).to(device) if self.args.temporal == 'lstm': for tt in range(self.args.obs_seq_len): loss_mask_rel_tt = loss_mask_rel[:,:,tt:tt+1].reshape(-1,1) # (batch*num_peds, 1) xs_tt = xs[:, tt].reshape(batch_size*num_peds, -1).unsqueeze(0)*loss_mask_rel_tt # (1, batch*num_peds, embedding_size) _, (htp, ctp) = self.lstm(xs_tt, (ht, ct)) # tp == tplus ht = htp * loss_mask_rel_tt + ht * (1.-loss_mask_rel_tt) ct = ctp * loss_mask_rel_tt + ct * (1.-loss_mask_rel_tt) elif self.args.temporal == 'faster_lstm': obs_mask = loss_mask_rel[:,:,:self.args.obs_seq_len].permute(0,2,1).unsqueeze(-1) # (batch, obs_seq_len, num_peds,1) xs_masked = xs*obs_mask # (batch, obs_seq_len, num_peds, embedding_size) xs_masked = xs_masked.permute(1,0,2,3).reshape(self.args.obs_seq_len, batch_size*num_peds, -1) # (obs_seq_len, batch*num_peds, embedding_size) _, (ht, ct) = self.lstm(xs_masked, (ht, ct)) else: raise RuntimeError('The temporal component is not lstm nor faster_lstm.') if self.args.only_observe_full_period: loss_mask_rel_full_partial = loss_mask_per_pedestrian # (batch, num_peds) else: # We predict motion of pedestrians that show up in observation period, and did not disappear after last observed time step. <-> equivalent as only predict pedestrians whose relative position is present at the last observed time step. loss_mask_rel_full_partial = loss_mask_rel[:,:,self.args.obs_seq_len-1] # (batch, num_peds) ht = ht * loss_mask_rel_full_partial.reshape(-1).unsqueeze(-1) ct = ct * loss_mask_rel_full_partial.reshape(-1).unsqueeze(-1) attn_mask_pred = torch.bmm(loss_mask_rel_full_partial.unsqueeze(2), loss_mask_rel_full_partial.unsqueeze(1)).permute(0,2,1) # (batch*unit_time, nnode , nnode ) ## prediction period if self.args.decode_style == 'recursive': if self.args.temporal == 'lstm' or self.args.temporal == 'faster_lstm': # ht # (num_layers, node, hidden_size) # ht # (num_layers, batch_size*num_peds, hidden_size) # batch_size*num_peds, sth prob_raw = self.hidden2pos(ht.permute(1,0,2).reshape(batch_size*num_peds, -1)).reshape(batch_size, num_peds, -1).unsqueeze(1) # (batch, unit_time, node, output_dim) gaussian_params = self.raw2gaussian(prob_raw) mu, sx, sy, corr = gaussian_params x_sample = self.sample_gaussian(gaussian_params, device=device, detach_sample=self.args.detach_sample, sampling=sampling) # x_sample: (batch, unit_time, node, motion_dim) # loss_mask_rel_full_partial: (batch, num_peds) x_sample = x_sample * loss_mask_rel_full_partial.unsqueeze(1).unsqueeze(-1) # mask x_sample here for edge evolution # A: (batch, obs_seq_len, node, node, edge_feat) A_sample = self.edge_evolution(x_sample, A[:,-1:], device=device) # (batch, unit_time, node, node, edge_feat) # starts recursion prob_raw_pred, x_sample_pred, A_sample_pred = [], [], [] mu_pred, sx_pred, sy_pred, corr_pred = [], [], [], [] prob_raw_pred.append(prob_raw) x_sample_pred.append(x_sample) A_sample_pred.append(A_sample) mu_pred.append(mu) sx_pred.append(sx) sy_pred.append(sy) corr_pred.append(corr) for tt in range(1, self.args.pred_seq_len): if self.args.spatial == 'gumbel_social_transformer': # * spatial encoding at prediction step tt # # attn_mask_pred # (batch*unit_time, nnode , nnode ) x_sample_reshaped = x_sample.reshape(batch_size, num_peds, -1) # (batch*unit_time, node, motion_dim) A_sample_reshaped = A_sample.reshape(batch_size, num_peds, num_peds, -1) # (batch*unit_time, node, node, edge_feat) xs_tt, sampled_edges, edge_multinomial, attn_weights = \ self.gumbel_social_transformer(x_sample_reshaped, A_sample_reshaped, attn_mask_pred, \ tau=tau, hard=hard, device=device) sampled_edges = sampled_edges.reshape(batch_size, 1, sampled_edges.shape[1], sampled_edges.shape[2], sampled_edges.shape[3]) edge_multinomial = edge_multinomial.reshape(batch_size, 1, edge_multinomial.shape[1], edge_multinomial.shape[2], edge_multinomial.shape[3]) attn_weights = attn_weights.reshape(attn_weights.shape[0], batch_size, 1, attn_weights.shape[2], attn_weights.shape[3], attn_weights.shape[4]) info['sampled_edges'].append(sampled_edges.detach().to("cpu")) info['edge_multinomial'].append(edge_multinomial.detach().to("cpu")) info['attn_weights'].append(attn_weights.detach().to("cpu")) # xs_tt: # (batch*unit_time, node, d_model) # * temporal encoding at prediction step tt loss_mask_rel_tt = loss_mask_rel_full_partial.reshape(-1,1) # (batch*num_peds,1) xs_tt = xs_tt.reshape(batch_size*num_peds, -1).unsqueeze(0)*loss_mask_rel_tt # (unit_time, batch*num_peds, embedding_size) _, (htp, ctp) = self.lstm(xs_tt, (ht, ct)) # tp == tplus ht = htp * loss_mask_rel_tt + ht * (1.-loss_mask_rel_tt) # (num_layers, batch_size*num_peds, hidden_size) ct = ctp * loss_mask_rel_tt + ct * (1.-loss_mask_rel_tt) # (num_layers, batch_size*num_peds, hidden_size) # * prediction at prediction step tt prob_raw = self.hidden2pos(ht.permute(1,0,2).reshape(batch_size*num_peds, -1)).reshape(batch_size, num_peds, -1).unsqueeze(1) # (batch, unit_time, node, output_dim) gaussian_params = self.raw2gaussian(prob_raw) mu, sx, sy, corr = gaussian_params x_sample = self.sample_gaussian(gaussian_params, device=device, detach_sample=self.args.detach_sample, sampling=sampling) x_sample = x_sample * loss_mask_rel_full_partial.unsqueeze(1).unsqueeze(-1) # mask x_sample here for edge evolution A_sample = self.edge_evolution(x_sample, A_sample, device=device) # (batch, unit_time, node, node, edge_feat) # * append to results prob_raw_pred.append(prob_raw) x_sample_pred.append(x_sample) A_sample_pred.append(A_sample) mu_pred.append(mu) sx_pred.append(sx) sy_pred.append(sy) corr_pred.append(corr) else: raise RuntimeError("The spatial component is not found.") # concatenate predictions together prob_raw_pred = torch.cat(prob_raw_pred, dim=1) # (batch, pred_seq_len, node, output_dim) x_sample_pred = torch.cat(x_sample_pred, dim=1) # (batch, pred_seq_len, node, motion_dim) A_sample_pred = torch.cat(A_sample_pred, dim=1) # (batch, pred_seq_len, node, node, edge_feat) mu_pred = torch.cat(mu_pred, dim=1) # (batch, pred_seq_len, node, 2) sx_pred = torch.cat(sx_pred, dim=1) # (batch, pred_seq_len, node, 1) sy_pred = torch.cat(sy_pred, dim=1) # (batch, pred_seq_len, node, 1) corr_pred = torch.cat(corr_pred, dim=1) # (batch, pred_seq_len, node, 1) gaussian_params_pred = (mu_pred, sx_pred, sy_pred, corr_pred) # gaussian_params_pred = self.raw2gaussian(prob_raw_pred) info['sampled_edges'] = torch.cat(info['sampled_edges'], dim=1) # (batch_size, seq_len-1, nnode , nhead_edges, neighbor_node) info['edge_multinomial'] = torch.cat(info['edge_multinomial'], dim=1) # (batch_size, seq_len-1, nnode , nhead_edges, neighbor_node) info['attn_weights'] = torch.cat(info['attn_weights'], dim=2) # (batch_size, nlayer, seq_len-1, nhead, nnode , neighbor_node) info['A_sample_pred'] = A_sample_pred # (batch, pred_seq_len, node, node, edge_feat) info['loss_mask_rel_full_partial'] = loss_mask_rel_full_partial # (batch, num_peds) info['loss_mask_per_pedestrian'] = loss_mask_per_pedestrian # (batch, num_peds) # pack results results = (gaussian_params_pred, x_sample_pred, info) return results else: raise RuntimeError('The temporal component is not lstm nor faster_lstm.') else: raise RuntimeError("The decoder style is not recursive.") ================================================ FILE: gst_updated/src/gumbel_social_transformer/temperature_scheduler.py ================================================ class Temp_Scheduler(object): def __init__(self, total_epochs, curr_temp, base_temp, temp_min=0.33, last_epoch=-1): self.curr_temp = curr_temp self.base_temp = base_temp self.temp_min = temp_min self.last_epoch = last_epoch self.total_epochs = total_epochs self.step(last_epoch + 1) def step(self, epoch=None): return self.decay_whole_process() def decay_whole_process(self, epoch=None): if epoch is None: epoch = self.last_epoch + 1 self.last_epoch = epoch self.curr_temp = (1 - self.last_epoch / self.total_epochs) * (self.base_temp - self.temp_min) + self.temp_min if self.curr_temp < self.temp_min: self.curr_temp = self.temp_min return self.curr_temp ================================================ FILE: gst_updated/src/gumbel_social_transformer/temporal_convolution_net.py ================================================ import torch.nn as nn from gst_updated.src.gumbel_social_transformer.utils import _get_clones, _get_activation_fn class TemporalConvolutionNet(nn.Module): def __init__( self, in_channels, out_channels, dim_hidden, nconv=2, obs_seq_len=8, pred_seq_len=12, kernel_size=3, stride=1, dropout=0.1, activation="relu", ): super(TemporalConvolutionNet,self).__init__() assert kernel_size % 2 == 1 assert nconv >= 2 padding = ((kernel_size - 1) // 2, 0) norm_layer = nn.LayerNorm(in_channels) timeconv_layer = nn.Conv2d(in_channels, in_channels, (kernel_size, 1), (stride, 1), padding) self.nconv = nconv self.norms = _get_clones(norm_layer, nconv) self.timeconvs = _get_clones(timeconv_layer, nconv) self.timelinear1 = nn.Linear(obs_seq_len, pred_seq_len) self.timelinear2 = nn.Linear(pred_seq_len, pred_seq_len) self.timedropout1 = nn.Dropout(dropout) self.timedropout2 = nn.Dropout(dropout) self.linear1 = nn.Linear(in_channels, dim_hidden) self.linear2 = nn.Linear(dim_hidden, out_channels) self.dropout = nn.Dropout(dropout) self.activation = _get_activation_fn(activation) def forward(self, x): # x # (batch, obs_seq_len, node, embedding_size) # in_channels = embedding_size # out # (batch, pred_seq_len, node, output_size) # output_size = 5 for i in range(self.nconv): x_norm = self.norms[i](x) x_perm = x_norm.permute(0, 3, 1, 2) # (batch, embedding_size, obs_seq_len, node) x_perm = self.activation(self.timeconvs[i](x_perm)) # (N, C, H, W) # (N, channels, T_{in}, V) x_perm = x_perm.permute(0, 2, 3, 1) # (batch, obs_seq_len, node, embedding_size) x = x + x_perm x = x.permute(0, 2, 3, 1) # (batch, node, embedding_size, obs_seq_len) x = self.timedropout1(self.activation(self.timelinear1(x))) x = self.timedropout2(self.activation(self.timelinear2(x))) # (batch, node, embedding_size, pred_seq_len) x = x.permute(0, 3, 1, 2) # (batch, pred_seq_len, node, embedding_size) x = self.dropout(self.activation(self.linear1(x))) out = self.linear2(x) # (batch, pred_seq_len, node, out_channels) return out ================================================ FILE: gst_updated/src/gumbel_social_transformer/utils.py ================================================ import copy import torch import torch.nn.functional as F from torch.autograd import Variable from torch.nn.modules.container import ModuleList def _get_activation_fn(activation): if activation == "relu": return F.relu elif activation == "gelu": return F.gelu raise RuntimeError("activation should be relu/gelu, not {}".format(activation)) def _get_clones(module, N): return ModuleList([copy.deepcopy(module) for i in range(N)]) def gumbel_softmax(logits, tau=1, hard=False, eps=1e-10): y_soft = gumbel_softmax_sample(logits, tau=tau, eps=eps) if hard: shape = logits.size() _, k = y_soft.data.max(-1) y_hard = torch.zeros(*shape) if y_soft.is_cuda: y_hard = y_hard.cuda() y_hard = y_hard.zero_().scatter_(-1, k.view(shape[:-1] + (1,)), 1.0) y = Variable(y_hard - y_soft.data) + y_soft else: y = y_soft return y def gumbel_softmax_sample(logits, tau=1, eps=1e-10): gumbel_noise = sample_gumbel(logits.size(), eps=eps) if logits.is_cuda: gumbel_noise = gumbel_noise.cuda() y = logits + Variable(gumbel_noise) return F.softmax(y / tau, dim=-1) def sample_gumbel(shape, eps=1e-10): uniform = torch.rand(shape).float() return - torch.log(eps - torch.log(uniform + eps)) ================================================ FILE: gst_updated/src/mgnn/batch_trajectories.py ================================================ from torch.utils.data import Dataset class BatchTrajectoriesDataset(Dataset): def __init__( self, ): super(BatchTrajectoriesDataset, self).__init__() self.num_seq = 0 self.obs_traj = [] self.pred_traj = [] self.obs_traj_rel = [] self.pred_traj_rel = [] self.loss_mask_rel = [] self.loss_mask = [] self.v_obs = [] self.A_obs = [] self.v_pred = [] self.A_pred = [] self.attn_mask_obs = [] self.attn_mask_pred = [] def add_batch( self, obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, v_obs, A_obs, v_pred, A_pred, attn_mask_obs, attn_mask_pred, ): self.num_seq += 1 self.obs_traj.append(obs_traj.float()) self.pred_traj.append(pred_traj_gt.float()) self.obs_traj_rel.append(obs_traj_rel.float()) self.pred_traj_rel.append(pred_traj_rel_gt.float()) self.loss_mask_rel.append(loss_mask_rel.float()) self.loss_mask.append(loss_mask.float()) self.v_obs.append(v_obs.float()) self.A_obs.append(A_obs.float()) self.v_pred.append(v_pred.float()) self.A_pred.append(A_pred.float()) self.attn_mask_obs.append(attn_mask_obs.float()) self.attn_mask_pred.append(attn_mask_pred.float()) def __len__(self): return self.num_seq def __getitem__(self, index): out = [ self.obs_traj[index], self.pred_traj[index], self.obs_traj_rel[index], self.pred_traj_rel[index], self.loss_mask_rel[index], self.loss_mask[index], self.v_obs[index], self.A_obs[index], self.v_pred[index], self.A_pred[index], self.attn_mask_obs[index], self.attn_mask_pred[index], ] return out ================================================ FILE: gst_updated/src/mgnn/trajectories.py ================================================ from torch.utils.data import Dataset import os import numpy as np import math import torch from src.mgnn.utils import seq_to_graph class TrajectoriesDataset(Dataset): def __init__( self, data_dir, obs_seq_len=8, pred_seq_len=12, skip=1, delim='\t', invalid_value=-999., mode=None, frame_diff=10., ): super(TrajectoriesDataset, self).__init__() self.data_dir = data_dir self.obs_seq_len = obs_seq_len self.pred_seq_len = pred_seq_len self.skip = skip self.seq_len = self.obs_seq_len + self.pred_seq_len self.delim = delim all_files = os.listdir(self.data_dir) all_files = [os.path.join(self.data_dir, _path) for _path in all_files] num_peds_in_seq = [] seq_list = [] seq_list_rel = [] loss_mask_list = [] loss_mask_rel_list = [] frame_id_seq = [] print("Files to be written into the dataset: ") print(all_files) for path in all_files: print(path) data = read_file(path, delim) frames = np.unique(data[:, 0]).tolist() frame_data = [] for frame in frames: frame_data.append(data[data[:, 0]==frame, :]) num_sequences = math.floor((len(frames)-self.seq_len)/self.skip)+1 if mode is None: idx_range = range(0, num_sequences * self.skip + 1, skip) elif mode == 'train': idx_range = range(0, int((num_sequences * self.skip + 1)*0.8), skip) elif mode == 'val': idx_range = range(int((num_sequences * self.skip + 1)*0.8), num_sequences * self.skip + 1, skip) elif mode == 'test': idx_range = range(int((num_sequences * self.skip + 1)*0.8), num_sequences * self.skip + 1, skip) else: raise RuntimeError("Wrong mode for TrajectoriesDataset.") # idx_range = range(0, num_sequences * self.skip + 1, skip) for idx in idx_range: curr_seq_data = np.concatenate(frame_data[idx:idx + self.seq_len], axis=0) start_frame_id = curr_seq_data[0, 0] peds_in_curr_seq = np.unique(curr_seq_data[:, 1]) ped_survive_all_time = False for _, ped_id in enumerate(peds_in_curr_seq): curr_ped_seq = curr_seq_data[curr_seq_data[:, 1] == ped_id, :] frames_curr_ped_seq = np.unique(curr_ped_seq[:,0]) if len(frames_curr_ped_seq) == self.seq_len and np.all(frames_curr_ped_seq[1:]-frames_curr_ped_seq[:-1]==frame_diff): ped_survive_all_time = True break if not ped_survive_all_time: continue curr_seq = np.ones((len(peds_in_curr_seq), 2, self.seq_len)) * invalid_value curr_seq_rel = np.ones((len(peds_in_curr_seq), 2, self.seq_len)) * invalid_value curr_loss_mask = np.zeros((len(peds_in_curr_seq), self.seq_len)) curr_loss_mask_rel = np.zeros((len(peds_in_curr_seq), self.seq_len)) for ped_id_curr_seq, ped_id in enumerate(peds_in_curr_seq): for tt in range(self.seq_len): frame_id = start_frame_id + tt * frame_diff frame_ped_id = (curr_seq_data[:,0]==frame_id) * (curr_seq_data[:,1]==ped_id) if len(curr_seq_data[frame_ped_id]) == 0: curr_loss_mask[ped_id_curr_seq, tt] = 0 curr_loss_mask_rel[ped_id_curr_seq, tt] = 0 elif len(curr_seq_data[frame_ped_id]) == 1: curr_seq[ped_id_curr_seq,:,tt] = curr_seq_data[frame_ped_id, 2:] curr_loss_mask[ped_id_curr_seq, tt] = 1 if tt == 0: curr_seq_rel[ped_id_curr_seq,:,tt] = np.zeros((2,)) curr_loss_mask_rel[ped_id_curr_seq, tt] = 1 else: if curr_loss_mask[ped_id_curr_seq, tt-1] == 1: curr_seq_rel[ped_id_curr_seq,:,tt] = curr_seq[ped_id_curr_seq,:,tt] - curr_seq[ped_id_curr_seq,:,tt-1] curr_loss_mask_rel[ped_id_curr_seq, tt] = 1 else: curr_loss_mask_rel[ped_id_curr_seq, tt] = 0 else: raise RuntimeError("The same pedestrian has multiple locations in the same frame.") num_peds_in_seq.append(len(peds_in_curr_seq)) seq_list.append(curr_seq) seq_list_rel.append(curr_seq_rel) loss_mask_list.append(curr_loss_mask) loss_mask_rel_list.append(curr_loss_mask_rel) frame_id_seq.append(start_frame_id) self.num_seq = len(seq_list) seq_list = np.concatenate(seq_list, axis=0) seq_list_rel = np.concatenate(seq_list_rel, axis=0) loss_mask_list = np.concatenate(loss_mask_list, axis=0) loss_mask_rel_list = np.concatenate(loss_mask_rel_list, axis=0) self.obs_traj = torch.from_numpy( seq_list[:, :, :self.obs_seq_len]).type(torch.float) self.pred_traj = torch.from_numpy( seq_list[:, :, self.obs_seq_len:]).type(torch.float) self.obs_traj_rel = torch.from_numpy( seq_list_rel[:, :, :self.obs_seq_len]).type(torch.float) self.pred_traj_rel = torch.from_numpy( seq_list_rel[:, :, self.obs_seq_len:]).type(torch.float) self.loss_mask = torch.from_numpy(loss_mask_list).type(torch.float) self.loss_mask_rel = torch.from_numpy(loss_mask_rel_list).type(torch.float) cum_start_idx = [0] + np.cumsum(num_peds_in_seq).tolist() self.seq_start_end = [(start, end) for start, end in zip(cum_start_idx[:-1], cum_start_idx[1:])] self.frame_id_seq = frame_id_seq self.v_obs = [] self.A_obs = [] self.attn_mask_obs = [] self.v_pred = [] self.A_pred = [] self.attn_mask_pred = [] for ss in range(len(self.seq_start_end)): start, end = self.seq_start_end[ss] v_, a_ = seq_to_graph( self.obs_traj[start:end, :], self.obs_traj_rel[start:end, :], attn_mech='rel_conv') self.v_obs.append(v_.clone()) self.A_obs.append(a_.clone()) v_, a_ = seq_to_graph( self.pred_traj[start:end, :], self.pred_traj_rel[start:end, :], attn_mech='rel_conv') self.v_pred.append(v_.clone()) self.A_pred.append(a_.clone()) attn_mask = [] for tt in range(self.seq_len): loss_mask_rel_tt = self.loss_mask_rel[start:end, tt] attn_mask.append(torch.outer(loss_mask_rel_tt, loss_mask_rel_tt).float()) attn_mask = torch.stack(attn_mask, dim=0) self.attn_mask_obs.append(attn_mask[:self.obs_seq_len]) self.attn_mask_pred.append(attn_mask[self.obs_seq_len:]) def __len__(self): return self.num_seq def __getitem__(self, index): start, end = self.seq_start_end[index] out = [ self.obs_traj[start:end, :], self.pred_traj[start:end, :], self.obs_traj_rel[start:end, :], self.pred_traj_rel[start:end, :], self.loss_mask_rel[start:end, :], self.loss_mask[start:end, :], self.v_obs[index], self.A_obs[index], self.v_pred[index], self.A_pred[index], self.attn_mask_obs[index], self.attn_mask_pred[index], ] return out def read_file(_path, delim='\t'): data = [] if delim == 'tab': delim = '\t' elif delim == 'space': delim = ' ' with open(_path, 'r') as f: for line in f: line = line.strip().split(delim) line = [float(i) for i in line] data.append(line) return np.asarray(data) ================================================ FILE: gst_updated/src/mgnn/trajectories_sdd.py ================================================ from torch.utils.data import Dataset import os import numpy as np import math import torch from src.mgnn.utils import seq_to_graph class TrajectoriesDataset(Dataset): def __init__( self, data_dir, obs_seq_len=8, pred_seq_len=12, skip=1, delim='\t', invalid_value=-999., mode=None, frame_diff=10., ): super(TrajectoriesDataset, self).__init__() self.data_dir = data_dir self.obs_seq_len = obs_seq_len self.pred_seq_len = pred_seq_len self.skip = skip self.seq_len = self.obs_seq_len + self.pred_seq_len self.delim = delim self.frame_diff = frame_diff all_files = os.listdir(self.data_dir) all_files = [os.path.join(self.data_dir, _path) for _path in all_files] num_peds_in_seq = [] seq_list = [] seq_list_rel = [] loss_mask_list = [] loss_mask_rel_list = [] frame_id_seq = [] print("Files to be written into the dataset: ") print(all_files) for path in all_files: print(path) print("current sequence list: ", len(seq_list)) data = read_sdd_file(path) frames = np.unique(data[:, 0]).tolist() frame_data = [] for frame in frames: frame_data.append(data[data[:, 0]==frame, :]) num_sequences = math.floor((len(frames)-self.seq_len)/self.skip)+1 if mode is None: idx_range = range(0, num_sequences * self.skip + 1, skip) elif mode == 'train': idx_range = range(0, int((num_sequences * self.skip + 1)*0.8), skip) elif mode == 'val': idx_range = range(int((num_sequences * self.skip + 1)*0.8), int((num_sequences * self.skip + 1)*0.9), skip) elif mode == 'test': idx_range = range(int((num_sequences * self.skip + 1)*0.9), num_sequences * self.skip + 1, skip) else: raise RuntimeError("Wrong mode for TrajectoriesDataset.") for idx in idx_range: curr_seq_data = np.concatenate(frame_data[idx:idx + self.seq_len], axis=0) start_frame_id = curr_seq_data[0, 0] peds_in_curr_seq = np.unique(curr_seq_data[:, 1]) ped_survive_all_time = False for _, ped_id in enumerate(peds_in_curr_seq): curr_ped_seq = curr_seq_data[curr_seq_data[:, 1] == ped_id, :] frames_curr_ped_seq = np.unique(curr_ped_seq[:,0]) if len(frames_curr_ped_seq) == self.seq_len and np.all(frames_curr_ped_seq[1:]-frames_curr_ped_seq[:-1]==self.frame_diff): ped_survive_all_time = True break if not ped_survive_all_time: continue curr_seq = np.ones((len(peds_in_curr_seq), 2, self.seq_len)) * invalid_value curr_seq_rel = np.ones((len(peds_in_curr_seq), 2, self.seq_len)) * invalid_value curr_loss_mask = np.zeros((len(peds_in_curr_seq), self.seq_len)) curr_loss_mask_rel = np.zeros((len(peds_in_curr_seq), self.seq_len)) for ped_id_curr_seq, ped_id in enumerate(peds_in_curr_seq): for tt in range(self.seq_len): frame_id = start_frame_id + tt * self.frame_diff frame_ped_id = (curr_seq_data[:,0]==frame_id) * (curr_seq_data[:,1]==ped_id) if len(curr_seq_data[frame_ped_id]) == 0: curr_loss_mask[ped_id_curr_seq, tt] = 0 curr_loss_mask_rel[ped_id_curr_seq, tt] = 0 elif len(curr_seq_data[frame_ped_id]) == 1: curr_seq[ped_id_curr_seq,:,tt] = curr_seq_data[frame_ped_id, 2:] curr_loss_mask[ped_id_curr_seq, tt] = 1 if tt == 0: curr_seq_rel[ped_id_curr_seq,:,tt] = np.zeros((2,)) curr_loss_mask_rel[ped_id_curr_seq, tt] = 1 else: if curr_loss_mask[ped_id_curr_seq, tt-1] == 1: curr_seq_rel[ped_id_curr_seq,:,tt] = curr_seq[ped_id_curr_seq,:,tt] - curr_seq[ped_id_curr_seq,:,tt-1] curr_loss_mask_rel[ped_id_curr_seq, tt] = 1 else: curr_loss_mask_rel[ped_id_curr_seq, tt] = 0 else: raise RuntimeError("The same pedestrian has multiple locations in the same frame.") num_peds_in_seq.append(len(peds_in_curr_seq)) seq_list.append(curr_seq) seq_list_rel.append(curr_seq_rel) loss_mask_list.append(curr_loss_mask) loss_mask_rel_list.append(curr_loss_mask_rel) frame_id_seq.append(start_frame_id) self.num_seq = len(seq_list) seq_list = np.concatenate(seq_list, axis=0) seq_list_rel = np.concatenate(seq_list_rel, axis=0) loss_mask_list = np.concatenate(loss_mask_list, axis=0) loss_mask_rel_list = np.concatenate(loss_mask_rel_list, axis=0) self.obs_traj = torch.from_numpy( seq_list[:, :, :self.obs_seq_len]).type(torch.float) self.pred_traj = torch.from_numpy( seq_list[:, :, self.obs_seq_len:]).type(torch.float) self.obs_traj_rel = torch.from_numpy( seq_list_rel[:, :, :self.obs_seq_len]).type(torch.float) self.pred_traj_rel = torch.from_numpy( seq_list_rel[:, :, self.obs_seq_len:]).type(torch.float) self.loss_mask = torch.from_numpy(loss_mask_list).type(torch.float) self.loss_mask_rel = torch.from_numpy(loss_mask_rel_list).type(torch.float) cum_start_idx = [0] + np.cumsum(num_peds_in_seq).tolist() self.seq_start_end = [(start, end) for start, end in zip(cum_start_idx[:-1], cum_start_idx[1:])] self.frame_id_seq = frame_id_seq self.v_obs = [] self.A_obs = [] self.attn_mask_obs = [] self.v_pred = [] self.A_pred = [] self.attn_mask_pred = [] for ss in range(len(self.seq_start_end)): start, end = self.seq_start_end[ss] v_, a_ = seq_to_graph( self.obs_traj[start:end, :], self.obs_traj_rel[start:end, :], attn_mech='rel_conv') self.v_obs.append(v_.clone()) self.A_obs.append(a_.clone()) v_, a_ = seq_to_graph( self.pred_traj[start:end, :], self.pred_traj_rel[start:end, :], attn_mech='rel_conv') self.v_pred.append(v_.clone()) self.A_pred.append(a_.clone()) attn_mask = [] for tt in range(self.seq_len): loss_mask_rel_tt = self.loss_mask_rel[start:end, tt] attn_mask.append(torch.outer(loss_mask_rel_tt, loss_mask_rel_tt).float()) attn_mask = torch.stack(attn_mask, dim=0) self.attn_mask_obs.append(attn_mask[:self.obs_seq_len]) self.attn_mask_pred.append(attn_mask[self.obs_seq_len:]) def __len__(self): return self.num_seq def __getitem__(self, index): start, end = self.seq_start_end[index] out = [ self.obs_traj[start:end, :], self.pred_traj[start:end, :], self.obs_traj_rel[start:end, :], self.pred_traj_rel[start:end, :], self.loss_mask_rel[start:end, :], self.loss_mask[start:end, :], self.v_obs[index], self.A_obs[index], self.v_pred[index], self.A_pred[index], self.attn_mask_obs[index], self.attn_mask_pred[index], ] return out def read_file(_path, delim='\t'): data = [] if delim == 'tab': delim = '\t' elif delim == 'space': delim = ' ' with open(_path, 'r') as f: for line in f: line = line.strip().split(delim) line = [float(i) for i in line] data.append(line) return np.asarray(data) def read_sdd_file(_path): data = [] with open(_path, 'r') as f: for line in f: line = line.rstrip() line = line.split() line[-1] = line[-1].strip('"') if line[-1] == "Car": continue line = [float(i) for i in line[:-1]] agent_id,xmin,ymin,xmax,ymax,frame,lost,occluded,generated = line if lost == 1 or frame % 10 != 0: # lost agent, and have to be selected every 10 frames. (30fps) continue f = frame p = agent_id x = (xmin+xmax)/2. y = (ymin+ymax)/2. line = [f,p,x,y] data.append(line) return np.asarray(data) ================================================ FILE: gst_updated/src/mgnn/trajectories_trajnet.py ================================================ from torch.utils.data import Dataset import os import numpy as np import math import torch from src.mgnn.utils import seq_to_graph import ndjson class TrajectoriesDataset(Dataset): def __init__( self, data_dir, obs_seq_len=8, pred_seq_len=12, skip=1, delim='\t', invalid_value=-999., mode=None, ): super(TrajectoriesDataset, self).__init__() self.data_dir = data_dir self.obs_seq_len = obs_seq_len self.pred_seq_len = pred_seq_len self.skip = skip self.seq_len = self.obs_seq_len + self.pred_seq_len self.delim = delim all_files = os.listdir(self.data_dir) all_files = [os.path.join(self.data_dir, _path) for _path in all_files] num_peds_in_seq = [] seq_list = [] seq_list_rel = [] loss_mask_list = [] loss_mask_rel_list = [] frame_id_seq = [] print("Files to be written into the dataset: ") print(all_files) for path in all_files: print("current sequence length: ", len(seq_list)) print(path) data, frame_diff, start_frames_considered, filename_str = read_trajnet_file(path) if len(data) == 0: continue # data = read_file(path, delim) frames_np = np.unique(data[:, 0]) frames = frames_np.tolist() frame_data = [] for frame in frames: frame_data.append(data[data[:, 0]==frame, :]) if filename_str[:3]=="cff": # too big skip = 100 else: skip = self.skip print("skip: ", skip) print("total length: ", len(start_frames_considered[::skip])) if mode is None: start_frames_np = start_frames_considered[::skip] elif mode == 'train': start_frames_np = start_frames_considered[:int(0.8*len(start_frames_considered)):skip] # idx_range = range(0, int((num_sequences * self.skip + 1)*0.8), skip) elif mode == 'val': start_frames_np = start_frames_considered[int(0.8*len(start_frames_considered)):int(0.9*len(start_frames_considered)):skip] # idx_range = range(int((num_sequences * self.skip + 1)*0.8), num_sequences * self.skip + 1, skip) elif mode == 'test': start_frames_np = start_frames_considered[int(0.9*len(start_frames_considered))::skip] # idx_range = range(int((num_sequences * self.skip + 1)*0.8), num_sequences * self.skip + 1, skip) else: raise RuntimeError("Wrong mode for TrajectoriesDataset.") # num_sequences = math.floor((len(frames)-self.seq_len)/self.skip)+1 # if mode is None: # idx_range = range(0, num_sequences * self.skip + 1, skip) # elif mode == 'train': # idx_range = range(0, int((num_sequences * self.skip + 1)*0.8), skip) # elif mode == 'val': # idx_range = range(int((num_sequences * self.skip + 1)*0.8), num_sequences * self.skip + 1, skip) # elif mode == 'test': # idx_range = range(int((num_sequences * self.skip + 1)*0.8), num_sequences * self.skip + 1, skip) # else: # raise RuntimeError("Wrong mode for TrajectoriesDataset.") # idx_range = range(0, num_sequences * self.skip + 1, skip) # print(len(idx_range)) # for idx in idx_range: for start_frame in start_frames_np: idx, = np.where(frames_np==start_frame) idx = idx[0] curr_seq_data = np.concatenate(frame_data[idx:idx + self.seq_len], axis=0) start_frame_id = curr_seq_data[0, 0] peds_in_curr_seq = np.unique(curr_seq_data[:, 1]) ped_survive_all_time = False for _, ped_id in enumerate(peds_in_curr_seq): curr_ped_seq = curr_seq_data[curr_seq_data[:, 1] == ped_id, :] frames_curr_ped_seq = np.unique(curr_ped_seq[:,0]) if len(frames_curr_ped_seq) == self.seq_len and \ np.all(frames_curr_ped_seq[1:]-frames_curr_ped_seq[:-1]==frame_diff): ped_survive_all_time = True break # import pdb; pdb.set_trace() if not ped_survive_all_time: continue curr_seq = np.ones((len(peds_in_curr_seq), 2, self.seq_len)) * invalid_value curr_seq_rel = np.ones((len(peds_in_curr_seq), 2, self.seq_len)) * invalid_value curr_loss_mask = np.zeros((len(peds_in_curr_seq), self.seq_len)) curr_loss_mask_rel = np.zeros((len(peds_in_curr_seq), self.seq_len)) for ped_id_curr_seq, ped_id in enumerate(peds_in_curr_seq): for tt in range(self.seq_len): frame_id = start_frame_id + tt * frame_diff frame_ped_id = (curr_seq_data[:,0]==frame_id) * (curr_seq_data[:,1]==ped_id) if len(curr_seq_data[frame_ped_id]) == 0: curr_loss_mask[ped_id_curr_seq, tt] = 0 curr_loss_mask_rel[ped_id_curr_seq, tt] = 0 elif len(curr_seq_data[frame_ped_id]) == 1: curr_seq[ped_id_curr_seq,:,tt] = curr_seq_data[frame_ped_id, 2:] curr_loss_mask[ped_id_curr_seq, tt] = 1 if tt == 0: curr_seq_rel[ped_id_curr_seq,:,tt] = np.zeros((2,)) curr_loss_mask_rel[ped_id_curr_seq, tt] = 1 else: if curr_loss_mask[ped_id_curr_seq, tt-1] == 1: curr_seq_rel[ped_id_curr_seq,:,tt] = curr_seq[ped_id_curr_seq,:,tt] - curr_seq[ped_id_curr_seq,:,tt-1] curr_loss_mask_rel[ped_id_curr_seq, tt] = 1 else: curr_loss_mask_rel[ped_id_curr_seq, tt] = 0 else: raise RuntimeError("The same pedestrian has multiple locations in the same frame.") num_peds_in_seq.append(len(peds_in_curr_seq)) seq_list.append(curr_seq) seq_list_rel.append(curr_seq_rel) loss_mask_list.append(curr_loss_mask) loss_mask_rel_list.append(curr_loss_mask_rel) frame_id_seq.append(start_frame_id) self.num_seq = len(seq_list) print("total sequence length: ", len(seq_list)) seq_list = np.concatenate(seq_list, axis=0) seq_list_rel = np.concatenate(seq_list_rel, axis=0) loss_mask_list = np.concatenate(loss_mask_list, axis=0) loss_mask_rel_list = np.concatenate(loss_mask_rel_list, axis=0) self.obs_traj = torch.from_numpy( seq_list[:, :, :self.obs_seq_len]).type(torch.float) self.pred_traj = torch.from_numpy( seq_list[:, :, self.obs_seq_len:]).type(torch.float) self.obs_traj_rel = torch.from_numpy( seq_list_rel[:, :, :self.obs_seq_len]).type(torch.float) self.pred_traj_rel = torch.from_numpy( seq_list_rel[:, :, self.obs_seq_len:]).type(torch.float) self.loss_mask = torch.from_numpy(loss_mask_list).type(torch.float) self.loss_mask_rel = torch.from_numpy(loss_mask_rel_list).type(torch.float) cum_start_idx = [0] + np.cumsum(num_peds_in_seq).tolist() self.seq_start_end = [(start, end) for start, end in zip(cum_start_idx[:-1], cum_start_idx[1:])] self.frame_id_seq = frame_id_seq self.v_obs = [] self.A_obs = [] self.attn_mask_obs = [] self.v_pred = [] self.A_pred = [] self.attn_mask_pred = [] for ss in range(len(self.seq_start_end)): start, end = self.seq_start_end[ss] v_, a_ = seq_to_graph( self.obs_traj[start:end, :], self.obs_traj_rel[start:end, :], attn_mech='rel_conv') self.v_obs.append(v_.clone()) self.A_obs.append(a_.clone()) v_, a_ = seq_to_graph( self.pred_traj[start:end, :], self.pred_traj_rel[start:end, :], attn_mech='rel_conv') self.v_pred.append(v_.clone()) self.A_pred.append(a_.clone()) attn_mask = [] for tt in range(self.seq_len): loss_mask_rel_tt = self.loss_mask_rel[start:end, tt] attn_mask.append(torch.outer(loss_mask_rel_tt, loss_mask_rel_tt).float()) attn_mask = torch.stack(attn_mask, dim=0) self.attn_mask_obs.append(attn_mask[:self.obs_seq_len]) self.attn_mask_pred.append(attn_mask[self.obs_seq_len:]) def __len__(self): return self.num_seq def __getitem__(self, index): start, end = self.seq_start_end[index] out = [ self.obs_traj[start:end, :], self.pred_traj[start:end, :], self.obs_traj_rel[start:end, :], self.pred_traj_rel[start:end, :], self.loss_mask_rel[start:end, :], self.loss_mask[start:end, :], self.v_obs[index], self.A_obs[index], self.v_pred[index], self.A_pred[index], self.attn_mask_obs[index], self.attn_mask_pred[index], ] return out def read_file(_path, delim='\t'): data = [] if delim == 'tab': delim = '\t' elif delim == 'space': delim = ' ' with open(_path, 'r') as f: for line in f: line = line.strip().split(delim) line = [float(i) for i in line] data.append(line) return np.asarray(data) def read_trajnet_file(_path): _, filename = os.path.split(_path) print(filename) filename_str, filename_ext = filename.split('.') if filename_ext != "ndjson": print("Not ndjson file for trajnet++.") data = [] frame_diff = 0. start_frames_considered = [] return data, frame_diff, start_frames_considered, filename_str # raise RuntimeError("Not ndjson file for trajnet++.") lines = [] frame_diff = 0. start_frames_considered = [] with open(_path) as f: reader = ndjson.reader(f) for post in reader: # print(type(post)) # print(post.keys()) # print(post['scene']) if "scene" in post.keys():# and len(start_frames_considered) < 20: start_frame = post["scene"]["s"] if frame_diff==0.: frame_diff = (post["scene"]["e"]-post["scene"]["s"])/20 start_frames_considered.append(start_frame) # np.unique(data[:, 0]).tolist() # {"scene": {"id": 0, "p": 24, "s": 500, "e": 700, "fps": 2.5, "tag": [3, [2]]}} # {"scene": {"id": 1, "p": 24, "s": 520, "e": 720, "fps": 2.5, "tag": [4, []]}} # {"scene": {"id": 2, "p": 24, "s": 540, "e": 740, "fps": 2.5, "tag": [4, []]}} # {"scene": {"id": 3, "p": 24, "s": 560, "e": 760, "fps": 2.5, "tag": [4, []]}} if "track" in post.keys(): f = post["track"]["f"] p = post["track"]["p"] x = post["track"]["x"] y = post["track"]["y"] line = [f,p,x,y] # str(f)+'\t'+str(p)+'\t'+str(x)+'\t'+str(y)+'\n' lines.append(line) # if len(lines)==3000: # break data = np.asarray(lines) start_frames_considered = np.unique(start_frames_considered) return data, frame_diff, start_frames_considered, filename_str # import pathhack # import ndjson # import os # raw_folder = 'real_data' # output_folder = raw_folder+'_txt' # data_dir = os.path.join(pathhack.pkg_path, "datasets/trajnet++/train", raw_folder) # output_dir = os.path.join(pathhack.pkg_path, "datasets/trajnet++/train", output_folder) # all_files = os.listdir(data_dir) # all_files = [os.path.join(data_dir, _path) for _path in all_files] # for path in all_files: # print(path) # _, filename = os.path.split(path) # filename_str, filename_ext = filename.split('.') # frame = None # if filename_ext == "ndjson": # lines = [] # with open(path) as f: # reader = ndjson.reader(f) # for post in reader: # # print(type(post)) # # print(post.keys()) # # print(post['scene']) # if "track" in post.keys(): # f = post["track"]["f"] # if frame is None: # frame = f # else: # print(f-frame) # break # p = post["track"]["p"] # x = post["track"]["x"] # y = post["track"]["y"] # line = str(f)+'\t'+str(p)+'\t'+str(x)+'\t'+str(y)+'\n' # lines.append(line) ================================================ FILE: gst_updated/src/mgnn/trajectories_trajnet_testset.py ================================================ from torch.utils.data import Dataset import os import numpy as np import math import torch from src.mgnn.utils import seq_to_graph import ndjson class TrajectoriesDataset(Dataset): def __init__( self, data_filepath, obs_seq_len=8, pred_seq_len=12, skip=1, delim='\t', invalid_value=-999., mode=None, ): super(TrajectoriesDataset, self).__init__() self.data_filepath = data_filepath self.obs_seq_len = obs_seq_len self.pred_seq_len = pred_seq_len self.skip = skip self.seq_len = self.obs_seq_len + self.pred_seq_len self.delim = delim self.frame_diff = 0. # ! all_files = os.listdir(self.data_dir) # ! all_files = [os.path.join(self.data_dir, _path) for _path in all_files] # ! Need to make it only one file!!! all_files = [self.data_filepath] num_peds_in_seq = [] seq_list = [] seq_list_rel = [] loss_mask_list = [] loss_mask_rel_list = [] frame_id_seq = [] ped_id_list = [] print("Files to be written into the dataset: ") print(all_files) for path in all_files: print("current sequence length: ", len(seq_list)) print(path) data, frame_diff, start_frames_considered, filename_str = read_trajnet_file(path) self.frame_diff = frame_diff if len(data) == 0: continue # data = read_file(path, delim) frames_np = np.unique(data[:, 0]) frames = frames_np.tolist() frame_data = [] for frame in frames: frame_data.append(data[data[:, 0]==frame, :]) if filename_str[:3]=="cff": # too big skip = 100 else: skip = self.skip print("skip: ", skip) if mode == 'test': start_frames_np = start_frames_considered else: raise RuntimeError("Wrong mode for TrajectoriesDataset.") for start_frame in start_frames_np: idx, = np.where(frames_np==start_frame) idx = idx[0] curr_seq_data = np.concatenate(frame_data[idx:idx + self.obs_seq_len+1], axis=0) # only observation is available in testset # ! REALLY IMPORTANT: they have 21 frames. 9 obs, 12 pred. i.e. still 8 offset in obs, 12 offset in prediction start_frame_id = curr_seq_data[0, 0] peds_in_curr_seq = np.unique(curr_seq_data[:, 1]) ped_survive_all_time = False for _, ped_id in enumerate(peds_in_curr_seq): curr_ped_seq = curr_seq_data[curr_seq_data[:, 1] == ped_id, :] frames_curr_ped_seq = np.unique(curr_ped_seq[:,0]) # if len(frames_curr_ped_seq) == self.seq_len and \ if len(frames_curr_ped_seq) == self.obs_seq_len+1 and \ np.all(frames_curr_ped_seq[1:]-frames_curr_ped_seq[:-1]==frame_diff): ped_survive_all_time = True break if not ped_survive_all_time: print("Never") continue curr_seq = np.ones((len(peds_in_curr_seq), 2, self.obs_seq_len)) * invalid_value curr_seq_rel = np.ones((len(peds_in_curr_seq), 2, self.obs_seq_len)) * invalid_value curr_loss_mask = np.zeros((len(peds_in_curr_seq), self.obs_seq_len)) curr_loss_mask_rel = np.zeros((len(peds_in_curr_seq), self.obs_seq_len)) ped_id_dict = {} for ped_id_curr_seq, ped_id in enumerate(peds_in_curr_seq): ped_id_dict[ped_id_curr_seq] = ped_id for tt in range(self.obs_seq_len): frame_id = start_frame_id + (tt+1) * frame_diff frame_ped_id = (curr_seq_data[:,0]==frame_id) * (curr_seq_data[:,1]==ped_id) if len(curr_seq_data[frame_ped_id]) == 0: curr_loss_mask[ped_id_curr_seq, tt] = 0 curr_loss_mask_rel[ped_id_curr_seq, tt] = 0 elif len(curr_seq_data[frame_ped_id]) == 1: curr_seq[ped_id_curr_seq,:,tt] = curr_seq_data[frame_ped_id, 2:] curr_loss_mask[ped_id_curr_seq, tt] = 1 if tt == 0: start_frame_ped_id = (curr_seq_data[:,0]==start_frame_id) * (curr_seq_data[:,1]==ped_id) if len(curr_seq_data[start_frame_ped_id]) == 1: curr_seq_rel[ped_id_curr_seq,:,0] = \ curr_seq_data[frame_ped_id, 2:] - curr_seq_data[start_frame_ped_id, 2:] curr_loss_mask_rel[ped_id_curr_seq, 0] = 1 else: curr_loss_mask_rel[ped_id_curr_seq, 0] = 0 else: if curr_loss_mask[ped_id_curr_seq, tt-1] == 1: curr_seq_rel[ped_id_curr_seq,:,tt] = curr_seq[ped_id_curr_seq,:,tt] - curr_seq[ped_id_curr_seq,:,tt-1] curr_loss_mask_rel[ped_id_curr_seq, tt] = 1 else: curr_loss_mask_rel[ped_id_curr_seq, tt] = 0 else: raise RuntimeError("The same pedestrian has multiple locations in the same frame.") num_peds_in_seq.append(len(peds_in_curr_seq)) seq_list.append(curr_seq) seq_list_rel.append(curr_seq_rel) loss_mask_list.append(curr_loss_mask) loss_mask_rel_list.append(curr_loss_mask_rel) frame_id_seq.append(start_frame_id) ped_id_list.append(ped_id_dict) self.num_seq = len(seq_list) print("total sequence length: ", len(seq_list)) seq_list = np.concatenate(seq_list, axis=0) seq_list_rel = np.concatenate(seq_list_rel, axis=0) loss_mask_list = np.concatenate(loss_mask_list, axis=0) loss_mask_rel_list = np.concatenate(loss_mask_rel_list, axis=0) self.obs_traj = torch.from_numpy( seq_list[:, :, :self.obs_seq_len]).type(torch.float) # self.pred_traj = torch.from_numpy( # seq_list[:, :, self.obs_seq_len:]).type(torch.float) self.obs_traj_rel = torch.from_numpy( seq_list_rel[:, :, :self.obs_seq_len]).type(torch.float) # self.pred_traj_rel = torch.from_numpy( # seq_list_rel[:, :, self.obs_seq_len:]).type(torch.float) self.loss_mask = torch.from_numpy(loss_mask_list).type(torch.float) self.loss_mask_rel = torch.from_numpy(loss_mask_rel_list).type(torch.float) cum_start_idx = [0] + np.cumsum(num_peds_in_seq).tolist() self.seq_start_end = [(start, end) for start, end in zip(cum_start_idx[:-1], cum_start_idx[1:])] self.frame_id_seq = frame_id_seq self.ped_id_list = ped_id_list self.v_obs = [] self.A_obs = [] self.attn_mask_obs = [] # self.v_pred = [] # self.A_pred = [] # self.attn_mask_pred = [] for ss in range(len(self.seq_start_end)): start, end = self.seq_start_end[ss] v_, a_ = seq_to_graph( self.obs_traj[start:end, :], self.obs_traj_rel[start:end, :], attn_mech='rel_conv') self.v_obs.append(v_.clone()) self.A_obs.append(a_.clone()) # v_, a_ = seq_to_graph( # self.pred_traj[start:end, :], self.pred_traj_rel[start:end, :], attn_mech='rel_conv') # self.v_pred.append(v_.clone()) # self.A_pred.append(a_.clone()) attn_mask = [] # for tt in range(self.seq_len): for tt in range(self.obs_seq_len): loss_mask_rel_tt = self.loss_mask_rel[start:end, tt] attn_mask.append(torch.outer(loss_mask_rel_tt, loss_mask_rel_tt).float()) attn_mask = torch.stack(attn_mask, dim=0) self.attn_mask_obs.append(attn_mask[:self.obs_seq_len]) # self.attn_mask_pred.append(attn_mask[self.obs_seq_len:]) def __len__(self): return self.num_seq def __getitem__(self, index): start, end = self.seq_start_end[index] # out = [ # self.obs_traj[start:end, :], self.pred_traj[start:end, :], # self.obs_traj_rel[start:end, :], self.pred_traj_rel[start:end, :], # self.loss_mask_rel[start:end, :], self.loss_mask[start:end, :], # self.v_obs[index], self.A_obs[index], # self.v_pred[index], self.A_pred[index], # self.attn_mask_obs[index], self.attn_mask_pred[index], # self.frame_id_seq[index], self.ped_id_list[index] # ] out = [ self.obs_traj[start:end, :], [], self.obs_traj_rel[start:end, :], [], self.loss_mask_rel[start:end, :], self.loss_mask[start:end, :], self.v_obs[index], self.A_obs[index], [], [], self.attn_mask_obs[index], [], self.frame_id_seq[index], self.ped_id_list[index] ] return out def read_file(_path, delim='\t'): data = [] if delim == 'tab': delim = '\t' elif delim == 'space': delim = ' ' with open(_path, 'r') as f: for line in f: line = line.strip().split(delim) line = [float(i) for i in line] data.append(line) return np.asarray(data) def read_trajnet_file(_path): _, filename = os.path.split(_path) print(filename) filename_str, filename_ext = filename.split('.') if filename_ext != "ndjson": print("Not ndjson file for trajnet++.") data = [] frame_diff = 0. start_frames_considered = [] return data, frame_diff, start_frames_considered, filename_str # raise RuntimeError("Not ndjson file for trajnet++.") lines = [] frame_diff = 0. start_frames_considered = [] with open(_path) as f: reader = ndjson.reader(f) for post in reader: # print(type(post)) # print(post.keys()) # print(post['scene']) if "scene" in post.keys():# and len(start_frames_considered) < 10: start_frame = post["scene"]["s"] if frame_diff==0.: frame_diff = (post["scene"]["e"]-post["scene"]["s"])/20 start_frames_considered.append(start_frame) # np.unique(data[:, 0]).tolist() # {"scene": {"id": 0, "p": 24, "s": 500, "e": 700, "fps": 2.5, "tag": [3, [2]]}} # {"scene": {"id": 1, "p": 24, "s": 520, "e": 720, "fps": 2.5, "tag": [4, []]}} # {"scene": {"id": 2, "p": 24, "s": 540, "e": 740, "fps": 2.5, "tag": [4, []]}} # {"scene": {"id": 3, "p": 24, "s": 560, "e": 760, "fps": 2.5, "tag": [4, []]}} if "track" in post.keys(): f = post["track"]["f"] p = post["track"]["p"] x = post["track"]["x"] y = post["track"]["y"] line = [f,p,x,y] # str(f)+'\t'+str(p)+'\t'+str(x)+'\t'+str(y)+'\n' lines.append(line) # if len(lines)==3000: # break data = np.asarray(lines) start_frames_considered = np.unique(start_frames_considered) return data, frame_diff, start_frames_considered, filename_str # import pathhack # import ndjson # import os # raw_folder = 'real_data' # output_folder = raw_folder+'_txt' # data_dir = os.path.join(pathhack.pkg_path, "datasets/trajnet++/train", raw_folder) # output_dir = os.path.join(pathhack.pkg_path, "datasets/trajnet++/train", output_folder) # all_files = os.listdir(data_dir) # all_files = [os.path.join(data_dir, _path) for _path in all_files] # for path in all_files: # print(path) # _, filename = os.path.split(path) # filename_str, filename_ext = filename.split('.') # frame = None # if filename_ext == "ndjson": # lines = [] # with open(path) as f: # reader = ndjson.reader(f) # for post in reader: # # print(type(post)) # # print(post.keys()) # # print(post['scene']) # if "track" in post.keys(): # f = post["track"]["f"] # if frame is None: # frame = f # else: # print(f-frame) # break # p = post["track"]["p"] # x = post["track"]["x"] # y = post["track"]["y"] # line = str(f)+'\t'+str(p)+'\t'+str(x)+'\t'+str(y)+'\n' # lines.append(line) ================================================ FILE: gst_updated/src/mgnn/utils.py ================================================ from os.path import join import argparse import torch import numpy as np from torch.utils.data import DataLoader def average_offset_error(x_pred, x_target, loss_mask=None): assert x_pred.shape[0] == 1 pos_pred = torch.cumsum(x_pred, dim=1) pos_target = torch.cumsum(x_target, dim=1) offset_error = torch.sqrt(((pos_pred-pos_target)**2.).sum(3))[0] aoe = offset_error.mean(0) if loss_mask is not None: aoe = aoe * loss_mask[0] return aoe def final_offset_error(x_pred, x_target, loss_mask=None): assert x_pred.shape[0] == 1 pos_pred = torch.cumsum(x_pred, dim=1) pos_target = torch.cumsum(x_target, dim=1) offset_error = torch.sqrt(((pos_pred-pos_target)**2.).sum(3))[0] foe = offset_error[-1] if loss_mask is not None: foe = foe * loss_mask[0] return foe def negative_log_likelihood(gaussian_params, x_target, loss_mask=None): mu, sx, sy, corr = gaussian_params assert mu.shape[0] == 1 sigma = torch.cat((sx, sy), dim=3) x_target_norm = (x_target-mu)/sigma nx, ny = x_target_norm[:,:,:,0:1], x_target_norm[:,:,:,1:2] loss_term_1 = torch.log(1.-corr**2.)/2.+torch.log(sx)+torch.log(sy) loss_term_2 = (nx**2.-2.*corr*nx*ny+ny**2.)/(2.*(1.-corr**2.)) loss = loss_term_1+loss_term_2 loss = loss.squeeze(3).mean(1) loss = (loss[0] * loss_mask[0]).sum()/loss_mask[0].sum() return loss def seq_to_graph(seq_, seq_rel, attn_mech='glob_kip'): if len(seq_.shape) == 4: seq_ = seq_[0] seq_rel = seq_rel[0] seq_len = seq_.shape[2] max_nodes = seq_.shape[0] V = np.zeros((seq_len, max_nodes, 2)) for s in range(seq_len): step_rel = seq_rel[:, :, s] for h in range(len(step_rel)): V[s, h, :] = step_rel[h] if attn_mech == 'plain': A = np.ones((seq_len, max_nodes, max_nodes))*(1./max_nodes) elif attn_mech == 'rel_conv': A = [] for tt in range(seq_len): x = seq_[:,:,tt] x_row = torch.ones(max_nodes,max_nodes,2)*x.view(max_nodes,1,2) x_col = torch.ones(max_nodes,max_nodes,2)*x.view(1,max_nodes,2) edge_attr = x_row-x_col A.append(edge_attr.numpy()) A = np.stack(A, axis=0) else: raise RuntimeError('Wrong attention mechanism.') return torch.from_numpy(V).type(torch.float),\ torch.from_numpy(A).type(torch.float) def rotate_graph(vtx, adj, theta): vtx_rotated_x = vtx[:,:,:,0:1]*np.cos(theta)-vtx[:,:,:,1:2]*np.sin(theta) vtx_rotated_y = vtx[:,:,:,0:1]*np.sin(theta)+vtx[:,:,:,1:2]*np.cos(theta) vtx_rotated = torch.cat((vtx_rotated_x, vtx_rotated_y), dim=3) adj_rotated_x = adj[:,:,:,:,0:1]*np.cos(theta)-adj[:,:,:,:,1:2]*np.sin(theta) adj_rotated_y = adj[:,:,:,:,0:1]*np.sin(theta)+adj[:,:,:,:,1:2]*np.cos(theta) adj_rotated = torch.cat((adj_rotated_x, adj_rotated_y), dim=4) return vtx_rotated, adj_rotated def random_rotate_graph(args, vtx_obs, adj_obs, vtx_pred_gt, adj_pred_gt): if args.rotation_pattern is None: raise RuntimeError('random_rotate_seq should not be called when rotation_pattern is None.') if args.rotation_pattern == 'right_angle': theta = (torch.randint(0, 4, ()).float()/2.*np.pi).item() elif args.rotation_pattern == 'random': theta = (torch.rand(())*2.*np.pi).item() else: raise RuntimeError('Rotation pattern is not found in args.') vtx_obs_rotated, adj_obs_rotated = rotate_graph(vtx_obs, adj_obs, theta) vtx_pred_gt_rotated, adj_pred_gt_rotated = rotate_graph(vtx_pred_gt, adj_pred_gt, theta) return (vtx_obs_rotated, adj_obs_rotated, vtx_pred_gt_rotated, adj_pred_gt_rotated), theta def load_batch_dataset(args, pkg_path, subfolder='train', num_workers=4, shuffle=None): result_filename = args.dataset+'_dset_'+subfolder+'_batch_trajectories.pt' if args.dataset == 'sdd': dataset_folderpath = join(pkg_path, 'datasets/sdd/social_pool_data') elif args.dataset == 'real' or args.dataset == 'synth' or args.dataset == 'all': dataset_folderpath = join(pkg_path, 'datasets/trajnet++/train/') elif args.dataset == 'deathCircle' or args.dataset == 'hyang': dataset_folderpath = join(pkg_path, 'datasets/sdd', args.dataset) elif args.dataset == 'sj': dataset_folderpath = join(pkg_path, 'datasets/shuijing/orca_20humans_fov') else: # dataset_folderpath = join(pkg_path, 'datasets/eth_ucy', args.dataset) dataset_folderpath = join(pkg_path, 'datasets/self_eth_ucy', args.dataset) print("self_eth_ucy") dset = torch.load(join(dataset_folderpath, result_filename)) if shuffle is None: if subfolder == 'train': shuffle = True else: shuffle = False dloader = DataLoader( dset, batch_size=1, shuffle=shuffle, num_workers=num_workers) return dloader def args2writername(args): writername = str(args.temp_epochs)+'-'+str(args.spatial)\ +'-'+str(args.temporal)+'-lr_'+str(args.lr) if args.deterministic: writername = writername + '-deterministic' # if args.init_temp != 1.: writername = writername + '-init_temp_'+str(args.init_temp) if args.clip_grad is not None: writername = writername + '-clip_grad_'+str(args.clip_grad) # if args.spatial_num_heads_edges != 4: writername = writername + '-edge_head_'+str(args.spatial_num_heads_edges) if args.only_observe_full_period: writername = writername + '-only_full' if args.detach_sample: writername = writername + '-detach' # if args.embedding_size != 64: writername = writername + '-ebd_'+str(args.embedding_size) # if args.spatial_num_layers != 3: writername = writername + '-snl_'+str(args.spatial_num_layers) # if args.spatial_num_heads != 8: writername = writername + '-snh_'+str(args.spatial_num_heads) if args.ghost: writername = writername + '-ghost' writername = writername + '-seed_'+str(args.random_seed) return writername def arg_parse(): parser = argparse.ArgumentParser() parser.add_argument('--spatial', default='rel_conv', help='spatial encoding methods: rel_conv, plain') parser.add_argument('--temporal', default='lstm', help='temporal encoding methods: lstm, temporal_convolution_net, faster_lstm.') parser.add_argument('--output_dim', type=int, default=5, help='5 for mu_x, mu_y, sigma_x, sigma_y, corr') parser.add_argument('--embedding_size', type=int, default=64) parser.add_argument('--spatial_num_heads', type=int, default=8, help='number of heads for multi-head \ attention mechanism in spatial encoding') parser.add_argument('--lstm_hidden_size', type=int, default=64) parser.add_argument('--lstm_num_layers', type=int, default=1) parser.add_argument('--decode_style', default='recursive', help='recursive, readout') parser.add_argument('--detach_sample', action='store_true', help='Default False means using reparameterization trick. \ True means disable the trick and cut gradient flow between \ gaussian parameters and samples in prediction period.') parser.add_argument('--motion_dim', type=int, default=2, help='pedestrian motion is 2D') parser.add_argument('--dataset', default='eth', help='eth,hotel,univ,zara1,zara2') parser.add_argument('--obs_seq_len', type=int, default=8) parser.add_argument('--pred_seq_len', type=int, default=12) parser.add_argument('--rotation_pattern', default=None, help='rotation pattern used for data augmentation in training \ phase: right_angle, or random. None means no rotation.') parser.add_argument('--batch_size', type=int, default=16, help='minibatch size') parser.add_argument('--num_epochs', type=int, default=200, help='number of epochs') parser.add_argument('--lr', type=float, default=1e-3, help='learning rate') parser.add_argument('--clip_grad', type=float, default=None, help='gradient clipping') parser.add_argument('--organize_csv', action='store_true', help='Organize test_performance.csv towards performance_organized.csv.') parser.add_argument('--spatial_num_layers', type=int, default=3) parser.add_argument('--only_observe_full_period', action='store_true', help='Only observe pedestrians that appear in the full period.') parser.add_argument('--spatial_num_heads_edges', type=int, default=4) parser.add_argument('--ghost', action='store_true') parser.add_argument('--random_seed', type=int, default=1000) parser.add_argument('--deterministic', action='store_true') parser.add_argument('--init_temp', type=float, default=1.) parser.add_argument('--resume_training', action='store_true') parser.add_argument('--temp_epochs', type=int, default=200) parser.add_argument('--save_epochs', type=int, default=10) parser.add_argument('--resume_epoch', type=int, default=None, help='the index of epoch where we resume training.') return parser.parse_args() ================================================ FILE: gst_updated/src/pec_net/config/optimal.yaml ================================================ adl_reg: 1 data_scale: 1.86 dataset_type: image dec_size: - 1024 - 512 - 1024 dist_thresh: 100 enc_dest_size: - 8 - 16 enc_latent_size: - 8 - 50 enc_past_size: - 512 - 256 non_local_theta_size: - 256 - 128 - 64 non_local_phi_size: - 256 - 128 - 64 non_local_g_size: - 256 - 128 - 64 non_local_dim: 128 fdim: 16 future_length: 12 gpu_index: 0 kld_reg: 1 learning_rate: 0.0003 mu: 0 n_values: 20 nonlocal_pools: 3 normalize_type: shift_origin num_epochs: 650 num_workers: 0 past_length: 8 predictor_hidden_size: - 1024 - 512 - 256 sigma: 1.3 test_b_size: 4096 time_thresh: 0 train_b_size: 512 zdim: 16 ================================================ FILE: gst_updated/src/pec_net/sdd_trajectories.py ================================================ from torch.utils.data import Dataset import numpy as np class SDDTrajectoriesDataset(Dataset): def __init__( self, ): super(SDDTrajectoriesDataset, self).__init__() self.num_seq = 0 self.obs_traj = [] self.pred_traj = [] self.obs_traj_rel = [] self.pred_traj_rel = [] self.loss_mask_rel = [] self.loss_mask = [] self.v_obs = [] self.A_obs = [] self.v_pred = [] self.A_pred = [] self.attn_mask_obs = [] self.attn_mask_pred = [] def add_batch( self, obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_rel_gt, loss_mask_rel, loss_mask, v_obs, A_obs, v_pred, A_pred, attn_mask_obs, attn_mask_pred, ): self.num_seq += 1 self.obs_traj.append(obs_traj[0].float()) self.pred_traj.append(pred_traj_gt[0].float()) self.obs_traj_rel.append(obs_traj_rel[0].float()) self.pred_traj_rel.append(pred_traj_rel_gt[0].float()) self.loss_mask_rel.append(loss_mask_rel[0].float()) self.loss_mask.append(loss_mask[0].float()) self.v_obs.append(v_obs[0].float()) self.A_obs.append(A_obs[0].float()) self.v_pred.append(v_pred[0].float()) self.A_pred.append(A_pred[0].float()) self.attn_mask_obs.append(attn_mask_obs[0].float()) self.attn_mask_pred.append(attn_mask_pred[0].float()) def __len__(self): return self.num_seq def __getitem__(self, index): out = [ self.obs_traj[index], self.pred_traj[index], self.obs_traj_rel[index], self.pred_traj_rel[index], self.loss_mask_rel[index], self.loss_mask[index], self.v_obs[index], self.A_obs[index], self.v_pred[index], self.A_pred[index], self.attn_mask_obs[index], self.attn_mask_pred[index], ] return out ================================================ FILE: gst_updated/src/pec_net/social_utils.py ================================================ from IPython import embed import glob import pandas as pd import pickle import os import torch from torch import nn from torch.utils import data import random import numpy as np '''for sanity check''' def naive_social(p1_key, p2_key, all_data_dict): if abs(p1_key-p2_key)<4: return True else: return False def find_min_time(t1, t2): '''given two time frame arrays, find then min dist (time)''' min_d = 9e4 t1, t2 = t1[:8], t2[:8] for t in t2: if abs(t1[0]-t)time_thresh: return False if find_min_dist(p1_x, p1_y, p2_x, p2_y)>dist_tresh: return False return True def mark_similar(mask, sim_list): for i in range(len(sim_list)): for j in range(len(sim_list)): mask[sim_list[i]][sim_list[j]] = 1 def collect_data(set_name, dataset_type = 'image', batch_size=512, time_thresh=48, dist_tresh=100, scene=None, verbose=True, root_path="./"): assert set_name in ['train','val','test'] '''Please specify the parent directory of the dataset. In our case data was stored in: root_path/trajnet_image/train/scene_name.txt root_path/trajnet_image/test/scene_name.txt ''' rel_path = '/trajnet_{0}/{1}/stanford'.format(dataset_type, set_name) full_dataset = [] full_masks = [] current_batch = [] mask_batch = [[0 for i in range(int(batch_size*1.5))] for j in range(int(batch_size*1.5))] current_size = 0 social_id = 0 part_file = '/{}.txt'.format('*' if scene == None else scene) for file in glob.glob(root_path + rel_path + part_file): scene_name = file[len(root_path+rel_path)+1:-6] + file[-5] data = np.loadtxt(fname = file, delimiter = ' ') data_by_id = {} for frame_id, person_id, x, y in data: if person_id not in data_by_id.keys(): data_by_id[person_id] = [] data_by_id[person_id].append([person_id, frame_id, x, y]) all_data_dict = data_by_id.copy() if verbose: print("Total People: ", len(list(data_by_id.keys()))) while len(list(data_by_id.keys()))>0: related_list = [] curr_keys = list(data_by_id.keys()) if current_size 1: env.phase = 'train' else: env.phase = 'test' if ax: env.render_axis = ax if test_case >= 0: env.test_case = test_case env.seed(seed + rank) if str(env.__class__.__name__).find('TimeLimit') >= 0: env = TimeLimitMask(env) # if log_dir is not None: env = bench.Monitor( env, None, allow_early_resets=allow_early_resets) print(env) if isinstance(env.observation_space, Box): if is_atari: if len(env.observation_space.shape) == 3: env = wrap_deepmind(env) elif len(env.observation_space.shape) == 3: raise NotImplementedError( "CNN models work only for atari,\n" "please use a custom wrapper for a custom pixel input env.\n" "See wrap_deepmind for an example.") # If the input has shape (W,H,3), wrap for PyTorch convolutions obs_shape = env.observation_space.shape if len(obs_shape) == 3 and obs_shape[2] in [1, 3]: env = TransposeImage(env, op=[2, 0, 1]) return env return _thunk def make_vec_envs(env_name, seed, num_processes, gamma, log_dir, device, allow_early_resets, num_frame_stack=None, config=None, ax=None, test_case=-1, wrap_pytorch=True, pretext_wrapper=False): envs = [ make_env(env_name, seed, i, log_dir, allow_early_resets, config=config, envNum=num_processes, ax=ax, test_case=test_case) for i in range(num_processes) ] test = False if len(envs) > 1 else True if len(envs) > 1: envs = ShmemVecEnv(envs, context='fork') else: envs = DummyVecEnv(envs) # for collect data in supervised learning, we don't need to wrap pytorch if wrap_pytorch: if isinstance(envs.observation_space, Box): if len(envs.observation_space.shape) == 1: if gamma is None: envs = VecNormalize(envs, ret=False, ob=False) else: envs = VecNormalize(envs, gamma=gamma, ob=False, ret=False) envs = VecPyTorch(envs, device) if pretext_wrapper: if gamma is None: envs = VecPretextNormalize(envs, ret=False, ob=False, config=config, test=test) else: envs = VecPretextNormalize(envs, gamma=gamma, ob=False, ret=False, config=config, test=test) if num_frame_stack is not None: envs = VecPyTorchFrameStack(envs, num_frame_stack, device) elif isinstance(envs.observation_space, Box): if len(envs.observation_space.shape) == 3: envs = VecPyTorchFrameStack(envs, 4, device) return envs # Checks whether done was caused my timit limits or not class TimeLimitMask(gym.Wrapper): def step(self, action): obs, rew, done, info = self.env.step(action) if done and self.env._max_episode_steps == self.env._elapsed_steps: info['bad_transition'] = True return obs, rew, done, info def reset(self, **kwargs): return self.env.reset(**kwargs) # Can be used to test recurrent policies for Reacher-v2 class MaskGoal(gym.ObservationWrapper): def observation(self, observation): if self.env._elapsed_steps > 0: observation[-2:] = 0 return observation class TransposeObs(gym.ObservationWrapper): def __init__(self, env=None): """ Transpose observation space (base class) """ super(TransposeObs, self).__init__(env) class TransposeImage(TransposeObs): def __init__(self, env=None, op=[2, 0, 1]): """ Transpose observation space for images """ super(TransposeImage, self).__init__(env) assert len(op) == 3, "Error: Operation, " + str(op) + ", must be dim3" self.op = op obs_shape = self.observation_space.shape self.observation_space = Box( self.observation_space.low[0, 0, 0], self.observation_space.high[0, 0, 0], [ obs_shape[self.op[0]], obs_shape[self.op[1]], obs_shape[self.op[2]] ], dtype=self.observation_space.dtype) def observation(self, ob): return ob.transpose(self.op[0], self.op[1], self.op[2]) class VecPyTorch(VecEnvWrapper): def __init__(self, venv, device): """Return only every `skip`-th frame""" super(VecPyTorch, self).__init__(venv) self.device = device # TODO: Fix data types def reset(self): obs = self.venv.reset() if isinstance(obs, dict): for key in obs: obs[key]=torch.from_numpy(obs[key]).to(self.device) else: obs = torch.from_numpy(obs).float().to(self.device) return obs def step_async(self, actions): if isinstance(actions, torch.LongTensor): # Squeeze the dimension for discrete actions actions = actions.squeeze(1) actions = actions.cpu().numpy() self.venv.step_async(actions) def step_wait(self): obs, reward, done, info = self.venv.step_wait() if isinstance(obs, dict): for key in obs: obs[key] = torch.from_numpy(obs[key]).to(self.device) else: obs = torch.from_numpy(obs).float().to(self.device) reward = torch.from_numpy(reward).unsqueeze(dim=1).float() return obs, reward, done, info def render_traj(self, path, episode_num): if self.venv.num_envs == 1: return self.venv.envs[0].env.render_traj(path, episode_num) else: for i, curr_env in enumerate(self.venv.envs): curr_env.env.render_traj(path, str(episode_num) + '.' + str(i)) class VecNormalize(VecNormalize_): def __init__(self, *args, **kwargs): super(VecNormalize, self).__init__(*args, **kwargs) self.training = True def _obfilt(self, obs, update=True): if self.ob_rms: if self.training and update: self.ob_rms.update(obs) obs = np.clip((obs - self.ob_rms.mean) / np.sqrt(self.ob_rms.var + self.epsilon), -self.clipob, self.clipob) return obs else: return obs def train(self): self.training = True def eval(self): self.training = False # Derived from # https://github.com/openai/baselines/blob/master/baselines/common/vec_env/vec_frame_stack.py class VecPyTorchFrameStack(VecEnvWrapper): def __init__(self, venv, nstack, device=None): self.venv = venv self.nstack = nstack wos = venv.observation_space # wrapped ob space self.shape_dim0 = wos.shape[0] low = np.repeat(wos.low, self.nstack, axis=0) high = np.repeat(wos.high, self.nstack, axis=0) if device is None: device = torch.device('cpu') self.stacked_obs = torch.zeros((venv.num_envs, ) + low.shape).to(device) observation_space = gym.spaces.Box( low=low, high=high, dtype=venv.observation_space.dtype) VecEnvWrapper.__init__(self, venv, observation_space=observation_space) def step_wait(self): obs, rews, news, infos = self.venv.step_wait() self.stacked_obs[:, :-self.shape_dim0] = \ self.stacked_obs[:, self.shape_dim0:].clone() for (i, new) in enumerate(news): if new: self.stacked_obs[i] = 0 self.stacked_obs[:, -self.shape_dim0:] = obs return self.stacked_obs, rews, news, infos def reset(self): obs = self.venv.reset() if torch.backends.cudnn.deterministic: self.stacked_obs = torch.zeros(self.stacked_obs.shape) else: self.stacked_obs.zero_() self.stacked_obs[:, -self.shape_dim0:] = obs return self.stacked_obs def close(self): self.venv.close() ================================================ FILE: rl/networks/model.py ================================================ import torch import torch.nn as nn from rl.networks.distributions import Bernoulli, Categorical, DiagGaussian from .srnn_model import SRNN from .selfAttn_srnn_temp_node import selfAttn_merge_SRNN class Flatten(nn.Module): def forward(self, x): return x.view(x.size(0), -1) class Policy(nn.Module): """ Class for a robot policy network """ def __init__(self, obs_shape, action_space, base=None, base_kwargs=None): super(Policy, self).__init__() if base_kwargs is None: base_kwargs = {} if base == 'srnn': base=SRNN elif base == 'selfAttn_merge_srnn': base = selfAttn_merge_SRNN else: raise NotImplementedError self.base = base(obs_shape, base_kwargs) self.srnn = True if action_space.__class__.__name__ == "Discrete": num_outputs = action_space.n self.dist = Categorical(self.base.output_size, num_outputs) elif action_space.__class__.__name__ == "Box": num_outputs = action_space.shape[0] self.dist = DiagGaussian(self.base.output_size, num_outputs) elif action_space.__class__.__name__ == "MultiBinary": num_outputs = action_space.shape[0] self.dist = Bernoulli(self.base.output_size, num_outputs) else: raise NotImplementedError @property def is_recurrent(self): return self.base.is_recurrent @property def recurrent_hidden_state_size(self): """Size of rnn_hx.""" return self.base.recurrent_hidden_state_size def forward(self, inputs, rnn_hxs, masks): raise NotImplementedError def act(self, inputs, rnn_hxs, masks, deterministic=False): if not hasattr(self, 'srnn'): self.srnn = False if self.srnn: value, actor_features, rnn_hxs = self.base(inputs, rnn_hxs, masks, infer=True) else: value, actor_features, rnn_hxs = self.base(inputs, rnn_hxs, masks) dist = self.dist(actor_features) if deterministic: action = dist.mode() else: action = dist.sample() action_log_probs = dist.log_probs(action) dist_entropy = dist.entropy().mean() return value, action, action_log_probs, rnn_hxs def get_value(self, inputs, rnn_hxs, masks): value, _, _ = self.base(inputs, rnn_hxs, masks, infer=True) return value def evaluate_actions(self, inputs, rnn_hxs, masks, action): value, actor_features, rnn_hxs = self.base(inputs, rnn_hxs, masks) dist = self.dist(actor_features) action_log_probs = dist.log_probs(action) dist_entropy = dist.entropy().mean() return value, action_log_probs, dist_entropy, rnn_hxs ================================================ FILE: rl/networks/network_utils.py ================================================ import glob import os import torch.nn as nn from rl.networks.envs import VecNormalize # Get a render function def get_render_func(venv): if hasattr(venv, 'envs'): return venv.envs[0].render elif hasattr(venv, 'venv'): return get_render_func(venv.venv) elif hasattr(venv, 'env'): return get_render_func(venv.env) return None def get_vec_normalize(venv): if isinstance(venv, VecNormalize): return venv elif hasattr(venv, 'venv'): return get_vec_normalize(venv.venv) return None # Necessary for my KFAC implementation. class AddBias(nn.Module): def __init__(self, bias): super(AddBias, self).__init__() self._bias = nn.Parameter(bias.unsqueeze(1)) def forward(self, x): if x.dim() == 2: bias = self._bias.t().view(1, -1) else: bias = self._bias.t().view(1, -1, 1, 1) return x + bias def update_linear_schedule(optimizer, epoch, total_num_epochs, initial_lr): """Decreases the learning rate linearly""" lr = initial_lr - (initial_lr * (epoch / float(total_num_epochs))) for param_group in optimizer.param_groups: param_group['lr'] = lr def init(module, weight_init, bias_init, gain=1): weight_init(module.weight.data, gain=gain) bias_init(module.bias.data) return module def cleanup_log_dir(log_dir): try: os.makedirs(log_dir) except OSError: files = glob.glob(os.path.join(log_dir, '*.monitor.csv')) for f in files: os.remove(f) ================================================ FILE: rl/networks/selfAttn_srnn_temp_node.py ================================================ import torch.nn.functional as F from .srnn_model import * class SpatialEdgeSelfAttn(nn.Module): """ Class for the human-human attention, uses a multi-head self attention proposed by https://arxiv.org/abs/1706.03762 """ def __init__(self, args): super(SpatialEdgeSelfAttn, self).__init__() self.args = args # Store required sizes # todo: hard-coded for now # with human displacement: + 2 # pred 4 steps + disp: 12 # pred 4 steps + no disp: 10 # pred 5 steps + no disp: 12 # pred 5 steps + no disp + probR: 17 # Gaussian pred 5 steps + no disp: 27 # pred 8 steps + no disp: 18 if args.env_name in ['CrowdSimPred-v0', 'CrowdSimPredRealGST-v0']: self.input_size = 12 elif args.env_name == 'CrowdSimVarNum-v0': self.input_size = 2 # 4 else: raise NotImplementedError self.num_attn_heads=8 self.attn_size=512 # Linear layer to embed input self.embedding_layer = nn.Sequential(nn.Linear(self.input_size, 128), nn.ReLU(), nn.Linear(128, self.attn_size), nn.ReLU() ) self.q_linear = nn.Linear(self.attn_size, self.attn_size) self.v_linear = nn.Linear(self.attn_size, self.attn_size) self.k_linear = nn.Linear(self.attn_size, self.attn_size) # multi-head self attention self.multihead_attn=torch.nn.MultiheadAttention(self.attn_size, self.num_attn_heads) # Given a list of sequence lengths, create a mask to indicate which indices are padded # e.x. Input: [3, 1, 4], max_human_num = 5 # Output: [[1, 1, 1, 0, 0], [1, 0, 0, 0, 0], [1, 1, 1, 1, 0]] def create_attn_mask(self, each_seq_len, seq_len, nenv, max_human_num): # mask with value of False means padding and should be ignored by attention # why +1: use a sentinel in the end to handle the case when each_seq_len = 18 if self.args.no_cuda: mask = torch.zeros(seq_len * nenv, max_human_num + 1).cpu() else: mask = torch.zeros(seq_len*nenv, max_human_num+1).cuda() mask[torch.arange(seq_len*nenv), each_seq_len.long()] = 1. mask = torch.logical_not(mask.cumsum(dim=1)) # remove the sentinel mask = mask[:, :-1].unsqueeze(-2) # seq_len*nenv, 1, max_human_num return mask def forward(self, inp, each_seq_len): ''' Forward pass for the model params: inp : input edge features each_seq_len: if self.args.sort_humans is True, the true length of the sequence. Should be the number of detected humans else, it is the mask itself ''' # inp is padded sequence [seq_len, nenv, max_human_num, 2] seq_len, nenv, max_human_num, _ = inp.size() if self.args.sort_humans: attn_mask = self.create_attn_mask(each_seq_len, seq_len, nenv, max_human_num) # [seq_len*nenv, 1, max_human_num] attn_mask = attn_mask.squeeze(1) # if we use pytorch builtin function else: # combine the first two dimensions attn_mask = each_seq_len.reshape(seq_len*nenv, max_human_num) input_emb=self.embedding_layer(inp).view(seq_len*nenv, max_human_num, -1) input_emb=torch.transpose(input_emb, dim0=0, dim1=1) # if we use pytorch builtin function, v1.7.0 has no batch first option q=self.q_linear(input_emb) k=self.k_linear(input_emb) v=self.v_linear(input_emb) #z=self.multihead_attn(q, k, v, mask=attn_mask) z,_=self.multihead_attn(q, k, v, key_padding_mask=torch.logical_not(attn_mask)) # if we use pytorch builtin function z=torch.transpose(z, dim0=0, dim1=1) # if we use pytorch builtin function return z class EdgeAttention_M(nn.Module): ''' Class for the robot-human attention module ''' def __init__(self, args): ''' Initializer function params: args : Training arguments infer : Training or test time (True at test time) ''' super(EdgeAttention_M, self).__init__() self.args = args # Store required sizes self.human_human_edge_rnn_size = args.human_human_edge_rnn_size self.human_node_rnn_size = args.human_node_rnn_size self.attention_size = args.attention_size # Linear layer to embed temporal edgeRNN hidden state self.temporal_edge_layer=nn.ModuleList() self.spatial_edge_layer=nn.ModuleList() self.temporal_edge_layer.append(nn.Linear(self.human_human_edge_rnn_size, self.attention_size)) # Linear layer to embed spatial edgeRNN hidden states self.spatial_edge_layer.append(nn.Linear(self.human_human_edge_rnn_size, self.attention_size)) # number of agents who have spatial edges (complete graph: all 6 agents; incomplete graph: only the robot) self.agent_num = 1 self.num_attention_head = 1 def create_attn_mask(self, each_seq_len, seq_len, nenv, max_human_num): # mask with value of False means padding and should be ignored by attention # why +1: use a sentinel in the end to handle the case when each_seq_len = 18 if self.args.no_cuda: mask = torch.zeros(seq_len * nenv, max_human_num + 1).cpu() else: mask = torch.zeros(seq_len * nenv, max_human_num + 1).cuda() mask[torch.arange(seq_len * nenv), each_seq_len.long()] = 1. mask = torch.logical_not(mask.cumsum(dim=1)) # remove the sentinel mask = mask[:, :-1].unsqueeze(-2) # seq_len*nenv, 1, max_human_num return mask def att_func(self, temporal_embed, spatial_embed, h_spatials, attn_mask=None): seq_len, nenv, num_edges, h_size = h_spatials.size() # [1, 12, 30, 256] in testing, [12, 30, 256] in training attn = temporal_embed * spatial_embed attn = torch.sum(attn, dim=3) # Variable length temperature = num_edges / np.sqrt(self.attention_size) attn = torch.mul(attn, temperature) # if we don't want to mask invalid humans, attn_mask is None and no mask will be applied # else apply attn masks if attn_mask is not None: attn = attn.masked_fill(attn_mask == 0, -1e9) # Softmax attn = attn.view(seq_len, nenv, self.agent_num, self.human_num) attn = torch.nn.functional.softmax(attn, dim=-1) # print(attn[0, 0, 0].cpu().numpy()) # Compute weighted value # weighted_value = torch.mv(torch.t(h_spatials), attn) # reshape h_spatials and attn # shape[0] = seq_len, shape[1] = num of spatial edges (6*5 = 30), shape[2] = 256 h_spatials = h_spatials.view(seq_len, nenv, self.agent_num, self.human_num, h_size) h_spatials = h_spatials.view(seq_len * nenv * self.agent_num, self.human_num, h_size).permute(0, 2, 1) # [seq_len*nenv*6, 5, 256] -> [seq_len*nenv*6, 256, 5] attn = attn.view(seq_len * nenv * self.agent_num, self.human_num).unsqueeze(-1) # [seq_len*nenv*6, 5, 1] weighted_value = torch.bmm(h_spatials, attn) # [seq_len*nenv*6, 256, 1] # reshape back weighted_value = weighted_value.squeeze(-1).view(seq_len, nenv, self.agent_num, h_size) # [seq_len, 12, 6 or 1, 256] return weighted_value, attn # h_temporal: [seq_len, nenv, 1, 256] # h_spatials: [seq_len, nenv, 5, 256] def forward(self, h_temporal, h_spatials, each_seq_len): ''' Forward pass for the model params: h_temporal : Hidden state of the temporal edgeRNN h_spatials : Hidden states of all spatial edgeRNNs connected to the node. each_seq_len: if self.args.sort_humans is True, the true length of the sequence. Should be the number of detected humans else, it is the mask itself ''' seq_len, nenv, max_human_num, _ = h_spatials.size() # find the number of humans by the size of spatial edgeRNN hidden state self.human_num = max_human_num // self.agent_num weighted_value_list, attn_list=[],[] for i in range(self.num_attention_head): # Embed the temporal edgeRNN hidden state temporal_embed = self.temporal_edge_layer[i](h_temporal) # temporal_embed = temporal_embed.squeeze(0) # Embed the spatial edgeRNN hidden states spatial_embed = self.spatial_edge_layer[i](h_spatials) # Dot based attention temporal_embed = temporal_embed.repeat_interleave(self.human_num, dim=2) if self.args.sort_humans: attn_mask = self.create_attn_mask(each_seq_len, seq_len, nenv, max_human_num) # [seq_len*nenv, 1, max_human_num] attn_mask = attn_mask.squeeze(-2).view(seq_len, nenv, max_human_num) else: attn_mask = each_seq_len weighted_value,attn=self.att_func(temporal_embed, spatial_embed, h_spatials, attn_mask=attn_mask) weighted_value_list.append(weighted_value) attn_list.append(attn) if self.num_attention_head > 1: return self.final_attn_linear(torch.cat(weighted_value_list, dim=-1)), attn_list else: return weighted_value_list[0], attn_list[0] class EndRNN(RNNBase): ''' Class for the GRU ''' def __init__(self, args): ''' Initializer function params: args : Training arguments infer : Training or test time (True at test time) ''' super(EndRNN, self).__init__(args, edge=False) self.args = args # Store required sizes self.rnn_size = args.human_node_rnn_size self.output_size = args.human_node_output_size self.embedding_size = args.human_node_embedding_size self.input_size = args.human_node_input_size self.edge_rnn_size = args.human_human_edge_rnn_size # Linear layer to embed input self.encoder_linear = nn.Linear(256, self.embedding_size) # ReLU and Dropout layers self.relu = nn.ReLU() # Linear layer to embed attention module output self.edge_attention_embed = nn.Linear(self.edge_rnn_size, self.embedding_size) # Output linear layer self.output_linear = nn.Linear(self.rnn_size, self.output_size) def forward(self, robot_s, h_spatial_other, h, masks): ''' Forward pass for the model params: pos : input position h_temporal : hidden state of the temporal edgeRNN corresponding to this node h_spatial_other : output of the attention module h : hidden state of the current nodeRNN c : cell state of the current nodeRNN ''' # Encode the input position encoded_input = self.encoder_linear(robot_s) encoded_input = self.relu(encoded_input) h_edges_embedded = self.relu(self.edge_attention_embed(h_spatial_other)) concat_encoded = torch.cat((encoded_input, h_edges_embedded), -1) x, h_new = self._forward_gru(concat_encoded, h, masks) outputs = self.output_linear(x) return outputs, h_new class selfAttn_merge_SRNN(nn.Module): """ Class for the proposed network """ def __init__(self, obs_space_dict, args, infer=False): """ Initializer function params: args : Training arguments infer : Training or test time (True at test time) """ super(selfAttn_merge_SRNN, self).__init__() self.infer = infer self.is_recurrent = True self.args=args self.human_num = obs_space_dict['spatial_edges'].shape[0] self.seq_length = args.seq_length self.nenv = args.num_processes self.nminibatch = args.num_mini_batch # Store required sizes self.human_node_rnn_size = args.human_node_rnn_size self.human_human_edge_rnn_size = args.human_human_edge_rnn_size self.output_size = args.human_node_output_size # Initialize the Node and Edge RNNs self.humanNodeRNN = EndRNN(args) # Initialize attention module self.attn = EdgeAttention_M(args) init_ = lambda m: init(m, nn.init.orthogonal_, lambda x: nn.init. constant_(x, 0), np.sqrt(2)) num_inputs = hidden_size = self.output_size self.actor = nn.Sequential( init_(nn.Linear(num_inputs, hidden_size)), nn.Tanh(), init_(nn.Linear(hidden_size, hidden_size)), nn.Tanh()) self.critic = nn.Sequential( init_(nn.Linear(num_inputs, hidden_size)), nn.Tanh(), init_(nn.Linear(hidden_size, hidden_size)), nn.Tanh()) self.critic_linear = init_(nn.Linear(hidden_size, 1)) robot_size = 9 self.robot_linear = nn.Sequential(init_(nn.Linear(robot_size, 256)), nn.ReLU()) # todo: check dim self.human_node_final_linear=init_(nn.Linear(self.output_size,2)) if self.args.use_self_attn: self.spatial_attn = SpatialEdgeSelfAttn(args) self.spatial_linear = nn.Sequential(init_(nn.Linear(512, 256)), nn.ReLU()) else: self.spatial_linear = nn.Sequential(init_(nn.Linear(obs_space_dict['spatial_edges'].shape[1], 128)), nn.ReLU(), init_(nn.Linear(128, 256)), nn.ReLU()) self.temporal_edges = [0] self.spatial_edges = np.arange(1, self.human_num+1) dummy_human_mask = [0] * self.human_num dummy_human_mask[0] = 1 if self.args.no_cuda: self.dummy_human_mask = Variable(torch.Tensor([dummy_human_mask]).cpu()) else: self.dummy_human_mask = Variable(torch.Tensor([dummy_human_mask]).cuda()) def forward(self, inputs, rnn_hxs, masks, infer=False): if infer: # Test/rollout time seq_length = 1 nenv = self.nenv else: # Training time seq_length = self.seq_length nenv = self.nenv // self.nminibatch robot_node = reshapeT(inputs['robot_node'], seq_length, nenv) temporal_edges = reshapeT(inputs['temporal_edges'], seq_length, nenv) spatial_edges = reshapeT(inputs['spatial_edges'], seq_length, nenv) # to prevent errors in old models that does not have sort_humans argument if not hasattr(self.args, 'sort_humans'): self.args.sort_humans = True if self.args.sort_humans: detected_human_num = inputs['detected_human_num'].squeeze(-1).cpu().int() else: human_masks = reshapeT(inputs['visible_masks'], seq_length, nenv).float() # [seq_len, nenv, max_human_num] # if no human is detected (human_masks are all False, set the first human to True) human_masks[human_masks.sum(dim=-1)==0] = self.dummy_human_mask hidden_states_node_RNNs = reshapeT(rnn_hxs['human_node_rnn'], 1, nenv) masks = reshapeT(masks, seq_length, nenv) if self.args.no_cuda: all_hidden_states_edge_RNNs = Variable( torch.zeros(1, nenv, 1+self.human_num, rnn_hxs['human_human_edge_rnn'].size()[-1]).cpu()) else: all_hidden_states_edge_RNNs = Variable( torch.zeros(1, nenv, 1+self.human_num, rnn_hxs['human_human_edge_rnn'].size()[-1]).cuda()) robot_states = torch.cat((temporal_edges, robot_node), dim=-1) robot_states = self.robot_linear(robot_states) # attention modules if self.args.sort_humans: # human-human attention if self.args.use_self_attn: spatial_attn_out=self.spatial_attn(spatial_edges, detected_human_num).view(seq_length, nenv, self.human_num, -1) else: spatial_attn_out = spatial_edges output_spatial = self.spatial_linear(spatial_attn_out) # robot-human attention hidden_attn_weighted, _ = self.attn(robot_states, output_spatial, detected_human_num) else: # human-human attention if self.args.use_self_attn: spatial_attn_out = self.spatial_attn(spatial_edges, human_masks).view(seq_length, nenv, self.human_num, -1) else: spatial_attn_out = spatial_edges output_spatial = self.spatial_linear(spatial_attn_out) # robot-human attention hidden_attn_weighted, _ = self.attn(robot_states, output_spatial, human_masks) # Do a forward pass through GRU outputs, h_nodes \ = self.humanNodeRNN(robot_states, hidden_attn_weighted, hidden_states_node_RNNs, masks) # Update the hidden and cell states all_hidden_states_node_RNNs = h_nodes outputs_return = outputs rnn_hxs['human_node_rnn'] = all_hidden_states_node_RNNs rnn_hxs['human_human_edge_rnn'] = all_hidden_states_edge_RNNs # x is the output and will be sent to actor and critic x = outputs_return[:, :, 0, :] hidden_critic = self.critic(x) hidden_actor = self.actor(x) for key in rnn_hxs: rnn_hxs[key] = rnn_hxs[key].squeeze(0) if infer: return self.critic_linear(hidden_critic).squeeze(0), hidden_actor.squeeze(0), rnn_hxs else: return self.critic_linear(hidden_critic).view(-1, 1), hidden_actor.view(-1, self.output_size), rnn_hxs def reshapeT(T, seq_length, nenv): shape = T.size()[1:] return T.unsqueeze(0).reshape((seq_length, nenv, *shape)) ================================================ FILE: rl/networks/shmem_vec_env.py ================================================ """ An interface for asynchronous vectorized environments. """ import multiprocessing as mp import numpy as np from baselines.common.vec_env.vec_env import VecEnv, CloudpickleWrapper, clear_mpi_env_vars import ctypes from baselines import logger from baselines.common.vec_env.util import dict_to_obs, obs_space_info, obs_to_dict _NP_TO_CT = {np.float32: ctypes.c_float, np.int32: ctypes.c_int32, np.int8: ctypes.c_int8, np.uint8: ctypes.c_char, np.bool: ctypes.c_bool, np.bool_: ctypes.c_bool} class ShmemVecEnv(VecEnv): """ Optimized version of SubprocVecEnv that uses shared variables to communicate observations. """ def __init__(self, env_fns, spaces=None, context='spawn'): """ If you don't specify observation_space, we'll have to create a dummy environment to get it. """ ctx = mp.get_context(context) if spaces: observation_space, action_space = spaces else: logger.log('Creating dummy env object to get spaces') with logger.scoped_configure(format_strs=[]): dummy = env_fns[0]() observation_space, action_space = dummy.observation_space, dummy.action_space dummy.close() del dummy VecEnv.__init__(self, len(env_fns), observation_space, action_space) self.obs_keys, self.obs_shapes, self.obs_dtypes = obs_space_info(observation_space) self.obs_bufs = [ {k: ctx.Array(_NP_TO_CT[self.obs_dtypes[k].type], int(np.prod(self.obs_shapes[k]))) for k in self.obs_keys} for _ in env_fns] self.parent_pipes = [] self.procs = [] with clear_mpi_env_vars(): for env_fn, obs_buf in zip(env_fns, self.obs_bufs): wrapped_fn = CloudpickleWrapper(env_fn) parent_pipe, child_pipe = ctx.Pipe() proc = ctx.Process(target=_subproc_worker, args=(child_pipe, parent_pipe, wrapped_fn, obs_buf, self.obs_shapes, self.obs_dtypes, self.obs_keys)) proc.daemon = True self.procs.append(proc) self.parent_pipes.append(parent_pipe) proc.start() child_pipe.close() self.waiting_step = False self.viewer = None def reset(self): if self.waiting_step: logger.warn('Called reset() while waiting for the step to complete') self.step_wait() for pipe in self.parent_pipes: pipe.send(('reset', None)) return self._decode_obses([pipe.recv() for pipe in self.parent_pipes]) def step_async(self, actions): assert len(actions) == len(self.parent_pipes) for pipe, act in zip(self.parent_pipes, actions): pipe.send(('step', act)) self.waiting_step = True def step_wait(self): outs = [pipe.recv() for pipe in self.parent_pipes] self.waiting_step = False obs, rews, dones, infos = zip(*outs) return self._decode_obses(obs), np.array(rews), np.array(dones), infos def talk2Env_async(self, data): assert len(data) == len(self.parent_pipes) for pipe, d in zip(self.parent_pipes, data): pipe.send(('talk2Env', d)) self.waiting_step = True def talk2Env_wait(self): outs = [pipe.recv() for pipe in self.parent_pipes] # pipe.recv() is a blocking call self.waiting_step = False return np.array(outs) def close_extras(self): if self.waiting_step: self.step_wait() for pipe in self.parent_pipes: pipe.send(('close', None)) for pipe in self.parent_pipes: pipe.recv() pipe.close() for proc in self.procs: proc.join() def get_images(self, mode='human'): for pipe in self.parent_pipes: pipe.send(('render', None)) return [pipe.recv() for pipe in self.parent_pipes] def _decode_obses(self, obs): result = {} for k in self.obs_keys: bufs = [b[k] for b in self.obs_bufs] o = [np.frombuffer(b.get_obj(), dtype=self.obs_dtypes[k]).reshape(self.obs_shapes[k]) for b in bufs] result[k] = np.array(o) return dict_to_obs(result) def _subproc_worker(pipe, parent_pipe, env_fn_wrapper, obs_bufs, obs_shapes, obs_dtypes, keys): """ Control a single environment instance using IPC and shared memory. """ def _write_obs(maybe_dict_obs): flatdict = obs_to_dict(maybe_dict_obs) for k in keys: dst = obs_bufs[k].get_obj() dst_np = np.frombuffer(dst, dtype=obs_dtypes[k]).reshape(obs_shapes[k]) # pylint: disable=W0212 np.copyto(dst_np, flatdict[k]) env = env_fn_wrapper.x() parent_pipe.close() try: while True: cmd, data = pipe.recv() if cmd == 'reset': pipe.send(_write_obs(env.reset())) elif cmd == 'step': obs, reward, done, info = env.step(data) if done: obs = env.reset() pipe.send((_write_obs(obs), reward, done, info)) elif cmd == 'render': pipe.send(env.render(mode='rgb_array')) elif cmd == 'close': pipe.send(None) break elif cmd == 'talk2Env': pipe.send(env.talk2Env(data)) else: raise RuntimeError('Got unrecognized cmd %s' % cmd) except KeyboardInterrupt: print('ShmemVecEnv worker: got KeyboardInterrupt') finally: env.close() ================================================ FILE: rl/networks/srnn_model.py ================================================ import torch.nn as nn from torch.autograd import Variable import torch import numpy as np import copy from rl.networks.network_utils import init class RNNBase(nn.Module): """ The class for RNN with done masks """ # edge: True -> edge RNN, False -> node RNN def __init__(self, args, edge): super(RNNBase, self).__init__() self.args = args # if this is an edge RNN if edge: self.gru = nn.GRU(args.human_human_edge_embedding_size, args.human_human_edge_rnn_size) # if this is a node RNN else: self.gru = nn.GRU(args.human_node_embedding_size*2, args.human_node_rnn_size) for name, param in self.gru.named_parameters(): if 'bias' in name: nn.init.constant_(param, 0) elif 'weight' in name: nn.init.orthogonal_(param) # x: [seq_len, nenv, 6 or 30 or 36, ?] # hxs: [1, nenv, human_num, ?] # masks: [1, nenv, 1] def _forward_gru(self, x, hxs, masks): # for acting model, input shape[0] == hidden state shape[0] if x.size(0) == hxs.size(0): # use env dimension as batch # [1, 12, 6, ?] -> [1, 12*6, ?] or [30, 6, 6, ?] -> [30, 6*6, ?] seq_len, nenv, agent_num, _ = x.size() x = x.view(seq_len, nenv*agent_num, -1) mask_agent_num = masks.size()[-1] hxs_times_masks = hxs * (masks.view(seq_len, nenv, mask_agent_num, 1)) hxs_times_masks = hxs_times_masks.view(seq_len, nenv*agent_num, -1) x, hxs = self.gru(x, hxs_times_masks) # we already unsqueezed the inputs in SRNN forward function x = x.view(seq_len, nenv, agent_num, -1) hxs = hxs.view(seq_len, nenv, agent_num, -1) # during update, input shape[0] * nsteps (30) = hidden state shape[0] else: # N: nenv, T: seq_len, agent_num: node num or edge num T, N, agent_num, _ = x.size() # x = x.view(T, N, agent_num, x.size(2)) # Same deal with masks masks = masks.view(T, N) # Let's figure out which steps in the sequence have a zero for any agent # We will always assume t=0 has a zero in it as that makes the logic cleaner # for the [29, num_env] boolean array, if any entry in the second axis (num_env) is True -> True # to make it [29, 1], then select the indices of True entries has_zeros = ((masks[1:] == 0.0) \ .any(dim=-1) .nonzero() .squeeze() .cpu()) # +1 to correct the masks[1:] if has_zeros.dim() == 0: # Deal with scalar has_zeros = [has_zeros.item() + 1] else: has_zeros = (has_zeros + 1).numpy().tolist() # add t=0 and t=T to the list has_zeros = [0] + has_zeros + [T] # hxs = hxs.unsqueeze(0) # hxs = hxs.view(hxs.size(0), hxs.size(1)*hxs.size(2), hxs.size(3)) outputs = [] for i in range(len(has_zeros) - 1): # We can now process steps that don't have any zeros in masks together! # This is much faster start_idx = has_zeros[i] end_idx = has_zeros[i + 1] # x and hxs have 4 dimensions, merge the 2nd and 3rd dimension x_in = x[start_idx:end_idx] x_in = x_in.view(x_in.size(0), x_in.size(1)*x_in.size(2), x_in.size(3)) hxs = hxs.view(hxs.size(0), N, agent_num, -1) hxs = hxs * (masks[start_idx].view(1, -1, 1, 1)) hxs = hxs.view(hxs.size(0), hxs.size(1) * hxs.size(2), hxs.size(3)) rnn_scores, hxs = self.gru(x_in, hxs) outputs.append(rnn_scores) # assert len(outputs) == T # x is a (T, N, -1) tensor x = torch.cat(outputs, dim=0) # flatten x = x.view(T, N, agent_num, -1) hxs = hxs.view(1, N, agent_num, -1) return x, hxs class HumanNodeRNN(RNNBase): ''' Class representing human Node RNNs in the st-graph ''' def __init__(self, args): ''' Initializer function params: args : Training arguments infer : Training or test time (True at test time) ''' super(HumanNodeRNN, self).__init__(args, edge=False) self.args = args # Store required sizes self.rnn_size = args.human_node_rnn_size self.output_size = args.human_node_output_size self.embedding_size = args.human_node_embedding_size self.input_size = args.human_node_input_size self.edge_rnn_size = args.human_human_edge_rnn_size # Linear layer to embed input self.encoder_linear = nn.Linear(self.input_size, self.embedding_size) # ReLU and Dropout layers self.relu = nn.ReLU() # Linear layer to embed edgeRNN hidden states self.edge_embed = nn.Linear(self.edge_rnn_size, self.embedding_size) # Linear layer to embed attention module output self.edge_attention_embed = nn.Linear(self.edge_rnn_size*2, self.embedding_size) # Output linear layer self.output_linear = nn.Linear(self.rnn_size, self.output_size) def forward(self, pos, h_temporal, h_spatial_other, h, masks): ''' Forward pass for the model params: pos : input position h_temporal : hidden state of the temporal edgeRNN corresponding to this node h_spatial_other : output of the attention module h : hidden state of the current nodeRNN c : cell state of the current nodeRNN ''' # Encode the input position encoded_input = self.encoder_linear(pos) encoded_input = self.relu(encoded_input) # Concat both the embeddings h_edges = torch.cat((h_temporal, h_spatial_other), -1) h_edges_embedded = self.relu(self.edge_attention_embed(h_edges)) concat_encoded = torch.cat((encoded_input, h_edges_embedded), -1) x, h_new = self._forward_gru(concat_encoded, h, masks) outputs = self.output_linear(x) return outputs, h_new class HumanHumanEdgeRNN(RNNBase): ''' Class representing the Human-Human Edge RNN in the s-t graph ''' def __init__(self, args): ''' Initializer function params: args : Training arguments infer : Training or test time (True at test time) ''' super(HumanHumanEdgeRNN, self).__init__(args, edge=True) self.args = args # Store required sizes self.rnn_size = args.human_human_edge_rnn_size self.embedding_size = args.human_human_edge_embedding_size self.input_size = args.human_human_edge_input_size # Linear layer to embed input self.encoder_linear = nn.Linear(self.input_size, self.embedding_size) self.relu = nn.ReLU() def forward(self, inp, h, masks): ''' Forward pass for the model params: inp : input edge features h : hidden state of the current edgeRNN c : cell state of the current edgeRNN ''' # Encode the input position encoded_input = self.encoder_linear(inp) encoded_input = self.relu(encoded_input) x, h_new = self._forward_gru(encoded_input, h, masks) return x, encoded_input, h_new class EdgeAttention(nn.Module): ''' Class representing the attention module ''' def __init__(self, args): ''' Initializer function params: args : Training arguments infer : Training or test time (True at test time) ''' super(EdgeAttention, self).__init__() self.args = args # Store required sizes self.human_human_edge_rnn_size = args.human_human_edge_rnn_size self.human_node_rnn_size = args.human_node_rnn_size self.attention_size = args.attention_size # Linear layer to embed temporal edgeRNN hidden state self.temporal_edge_layer=nn.ModuleList() self.spatial_edge_layer=nn.ModuleList() self.temporal_edge_layer.append(nn.Linear(self.human_human_edge_rnn_size, self.attention_size)) # Linear layer to embed spatial edgeRNN hidden states self.spatial_edge_layer.append(nn.Linear(self.human_human_edge_rnn_size, self.attention_size)) # number of agents who have spatial edges (complete graph: all 6 agents; incomplete graph: only the robot) self.agent_num = 1 self.num_attention_head = 1 def att_func(self, temporal_embed, spatial_embed, h_spatials): seq_len, nenv, num_edges, h_size = h_spatials.size() # [1, 12, 30, 256] in testing, [12, 30, 256] in training attn = temporal_embed * spatial_embed attn = torch.sum(attn, dim=3) # Variable length temperature = num_edges / np.sqrt(self.attention_size) attn = torch.mul(attn, temperature) # Softmax attn = attn.view(seq_len, nenv, self.agent_num, self.human_num) attn = torch.nn.functional.softmax(attn, dim=-1) # print(attn[0, 0, 0].cpu().numpy()) # Compute weighted value # weighted_value = torch.mv(torch.t(h_spatials), attn) # reshape h_spatials and attn # shape[0] = seq_len, shape[1] = num of spatial edges (6*5 = 30), shape[2] = 256 h_spatials = h_spatials.view(seq_len, nenv, self.agent_num, self.human_num, h_size) h_spatials = h_spatials.view(seq_len * nenv * self.agent_num, self.human_num, h_size).permute(0, 2, 1) # [seq_len*nenv*6, 5, 256] -> [seq_len*nenv*6, 256, 5] attn = attn.view(seq_len * nenv * self.agent_num, self.human_num).unsqueeze(-1) # [seq_len*nenv*6, 5, 1] weighted_value = torch.bmm(h_spatials, attn) # [seq_len*nenv*6, 256, 1] # reshape back weighted_value = weighted_value.squeeze(-1).view(seq_len, nenv, self.agent_num, h_size) # [seq_len, 12, 6 or 1, 256] return weighted_value, attn # h_temporal: [seq_len, nenv, 1, 256] # h_spatials: [seq_len, nenv, 5, 256] def forward(self, h_temporal, h_spatials): ''' Forward pass for the model params: h_temporal : Hidden state of the temporal edgeRNN h_spatials : Hidden states of all spatial edgeRNNs connected to the node. ''' # find the number of humans by the size of spatial edgeRNN hidden state self.human_num = h_spatials.size()[2] // self.agent_num weighted_value_list, attn_list=[],[] for i in range(self.num_attention_head): # Embed the temporal edgeRNN hidden state temporal_embed = self.temporal_edge_layer[i](h_temporal) # temporal_embed = temporal_embed.squeeze(0) # Embed the spatial edgeRNN hidden states spatial_embed = self.spatial_edge_layer[i](h_spatials) # Dot based attention try: temporal_embed = temporal_embed.repeat_interleave(self.human_num, dim=2) except RuntimeError: print('hello') weighted_value,attn=self.att_func(temporal_embed, spatial_embed, h_spatials) weighted_value_list.append(weighted_value) attn_list.append(attn) if self.num_attention_head > 1: return self.final_attn_linear(torch.cat(weighted_value_list, dim=-1)), attn_list else: return weighted_value_list[0], attn_list[0] class SRNN(nn.Module): """ Class for the DS-RNN model, see https://arxiv.org/abs/2011.04820 for details """ def __init__(self, obs_space_dict, args, infer=False): """ Initializer function params: args : Training arguments infer : Training or test time (True at test time) """ super(SRNN, self).__init__() self.infer = infer self.is_recurrent = True self.args=args self.human_num = obs_space_dict['spatial_edges'].shape[0] self.seq_length = args.seq_length self.nenv = args.num_processes self.nminibatch = args.num_mini_batch # Store required sizes self.human_node_rnn_size = args.human_node_rnn_size self.human_human_edge_rnn_size = args.human_human_edge_rnn_size self.output_size = args.human_node_output_size # Initialize the Node and Edge RNNs self.humanNodeRNN = HumanNodeRNN(args) spatial_args = copy.deepcopy(args) spatial_args.human_human_edge_input_size = obs_space_dict['spatial_edges'].shape[1] self.humanhumanEdgeRNN_spatial = HumanHumanEdgeRNN(spatial_args) self.humanhumanEdgeRNN_temporal = HumanHumanEdgeRNN(args) # Initialize attention module self.attn = EdgeAttention(args) init_ = lambda m: init(m, nn.init.orthogonal_, lambda x: nn.init. constant_(x, 0), np.sqrt(2)) num_inputs = hidden_size = self.output_size self.actor = nn.Sequential( init_(nn.Linear(num_inputs, hidden_size)), nn.Tanh(), init_(nn.Linear(hidden_size, hidden_size)), nn.Tanh()) self.critic = nn.Sequential( init_(nn.Linear(num_inputs, hidden_size)), nn.Tanh(), init_(nn.Linear(hidden_size, hidden_size)), nn.Tanh()) self.critic_linear = init_(nn.Linear(hidden_size, 1)) robot_size = 7 if args.env_type == 'crowd_sim' else 2 self.robot_linear = init_(nn.Linear(robot_size, 3)) self.human_node_final_linear=init_(nn.Linear(self.output_size,2)) self.temporal_edges = [0] self.spatial_edges = np.arange(1, self.human_num+1) # make sure the spatial edge embedding size is the same as temporal edge size self.spatial_linear = init_(nn.Linear(obs_space_dict['spatial_edges'].shape[1], 2)) def forward(self, inputs, rnn_hxs, masks, infer=False): if infer: # Test time/rollout trajectory time seq_length = 1 nenv = self.nenv else: # Training time seq_length = self.seq_length nenv = self.nenv // self.nminibatch robot_node = reshapeT(inputs['robot_node'], seq_length, nenv) temporal_edges = reshapeT(inputs['temporal_edges'], seq_length, nenv) spatial_edges = reshapeT(inputs['spatial_edges'], seq_length, nenv) hidden_states_node_RNNs = reshapeT(rnn_hxs['human_node_rnn'], 1, nenv) hidden_states_edge_RNNs = reshapeT(rnn_hxs['human_human_edge_rnn'], 1, nenv) masks = reshapeT(masks, seq_length, nenv) if self.args.no_cuda: all_hidden_states_edge_RNNs = Variable( torch.zeros(1, nenv, self.human_num + 1, rnn_hxs['human_human_edge_rnn'].size()[-1]).cpu()) else: all_hidden_states_edge_RNNs = Variable( torch.zeros(1, nenv, self.human_num + 1, rnn_hxs['human_human_edge_rnn'].size()[-1]).cuda()) # Do forward pass through temporaledgeRNN # todo: now edgeRNNs has 3 return values! hidden_temporal_start_end=hidden_states_edge_RNNs[:,:,self.temporal_edges,:] output_temporal, _, hidden_temporal = self.humanhumanEdgeRNN_temporal(temporal_edges, hidden_temporal_start_end, masks) # Update the hidden state and cell state all_hidden_states_edge_RNNs[:, :, self.temporal_edges,:] = hidden_temporal # Spatial Edges # spatial_edges = self.spatial_linear(spatial_edges) hidden_spatial_start_end=hidden_states_edge_RNNs[:,:,self.spatial_edges,:] # Do forward pass through spatialedgeRNN output_spatial, _, hidden_spatial = self.humanhumanEdgeRNN_spatial(spatial_edges, hidden_spatial_start_end, masks) # Update the hidden state and cell state all_hidden_states_edge_RNNs[:, :,self.spatial_edges,: ] = hidden_spatial # Do forward pass through attention module hidden_attn_weighted, _ = self.attn(output_temporal, output_spatial) # concatenate human node features with robot features nodes_current_selected = self.robot_linear(robot_node) # Do a forward pass through nodeRNN outputs, h_nodes \ = self.humanNodeRNN(nodes_current_selected, output_temporal, hidden_attn_weighted, hidden_states_node_RNNs, masks) # Update the hidden and cell states all_hidden_states_node_RNNs = h_nodes outputs_return = outputs rnn_hxs['human_node_rnn'] = all_hidden_states_node_RNNs rnn_hxs['human_human_edge_rnn'] = all_hidden_states_edge_RNNs # x is the output of the robot node and will be sent to actor and critic x = outputs_return[:, :, 0, :] hidden_critic = self.critic(x) hidden_actor = self.actor(x) for key in rnn_hxs: rnn_hxs[key] = rnn_hxs[key].squeeze(0) if infer: return self.critic_linear(hidden_critic).squeeze(0), hidden_actor.squeeze(0), rnn_hxs else: return self.critic_linear(hidden_critic).view(-1, 1), hidden_actor.view(-1, self.output_size), rnn_hxs def reshapeT(T, seq_length, nenv): shape = T.size()[1:] return T.unsqueeze(0).reshape((seq_length, nenv, *shape)) ================================================ FILE: rl/networks/storage.py ================================================ import torch from torch.utils.data.sampler import BatchSampler, SubsetRandomSampler def _flatten_helper(T, N, _tensor): if isinstance(_tensor, dict): for key in _tensor: _tensor[key] = _tensor[key].view(T * N, *(_tensor[key].size()[2:])) return _tensor else: return _tensor.view(T * N, *_tensor.size()[2:]) class RolloutStorage(object): """ The rollout buffer to store the agent's experience for PPO """ def __init__(self, num_steps, num_processes, obs_shape, action_space, human_node_rnn_size, human_human_edge_rnn_size): if isinstance(obs_shape, dict): self.obs = {} for key in obs_shape: self.obs[key] = torch.zeros(num_steps + 1, num_processes, *(obs_shape[key].shape)) self.human_num = obs_shape['spatial_edges'].shape[0] else: self.obs = torch.zeros(num_steps + 1, num_processes, *obs_shape) self.recurrent_hidden_states = {} # a dict of tuple(hidden state, cell state) node_num = 1 # todo: uncomment the next line for previous models! edge_num = self.human_num + 1 # edge_num = 2 self.recurrent_hidden_states['human_node_rnn'] = torch.zeros(num_steps + 1, num_processes, node_num, human_node_rnn_size) self.recurrent_hidden_states['human_human_edge_rnn'] = torch.zeros(num_steps + 1, num_processes, edge_num, human_human_edge_rnn_size) self.rewards = torch.zeros(num_steps, num_processes, 1) self.value_preds = torch.zeros(num_steps + 1, num_processes, 1) self.returns = torch.zeros(num_steps + 1, num_processes, 1) self.action_log_probs = torch.zeros(num_steps, num_processes, 1) if action_space.__class__.__name__ == 'Discrete': action_shape = 1 else: action_shape = action_space.shape[0] self.actions = torch.zeros(num_steps, num_processes, action_shape) if action_space.__class__.__name__ == 'Discrete': self.actions = self.actions.long() self.masks = torch.ones(num_steps + 1, num_processes, 1) # Masks that indicate whether it's a true terminal state # or time limit end state self.bad_masks = torch.ones(num_steps + 1, num_processes, 1) self.num_steps = num_steps self.step = 0 def to(self, device): for key in self.obs: self.obs[key] = self.obs[key].to(device) for key in self.recurrent_hidden_states: self.recurrent_hidden_states[key] = self.recurrent_hidden_states[key].to(device) self.rewards = self.rewards.to(device) self.value_preds = self.value_preds.to(device) self.returns = self.returns.to(device) self.action_log_probs = self.action_log_probs.to(device) self.actions = self.actions.to(device) self.masks = self.masks.to(device) self.bad_masks = self.bad_masks.to(device) def insert(self, obs, recurrent_hidden_states, actions, action_log_probs, value_preds, rewards, masks, bad_masks): for key in self.obs: self.obs[key][self.step + 1].copy_(obs[key]) for key in recurrent_hidden_states: self.recurrent_hidden_states[key][self.step + 1].copy_(recurrent_hidden_states[key]) self.actions[self.step].copy_(actions) self.action_log_probs[self.step].copy_(action_log_probs) self.value_preds[self.step].copy_(value_preds) self.rewards[self.step].copy_(rewards) self.masks[self.step + 1].copy_(masks) self.bad_masks[self.step + 1].copy_(bad_masks) self.step = (self.step + 1) % self.num_steps def after_update(self): for key in self.obs: self.obs[key][0].copy_(self.obs[key][-1]) for key in self.recurrent_hidden_states: self.recurrent_hidden_states[key][0].copy_(self.recurrent_hidden_states[key][-1]) self.masks[0].copy_(self.masks[-1]) self.bad_masks[0].copy_(self.bad_masks[-1]) def compute_returns(self, next_value, use_gae, gamma, gae_lambda, use_proper_time_limits=True): if use_proper_time_limits: if use_gae: self.value_preds[-1] = next_value gae = 0 for step in reversed(range(self.rewards.size(0))): delta = self.rewards[step] + gamma * self.value_preds[ step + 1] * self.masks[step + 1] - self.value_preds[step] gae = delta + gamma * gae_lambda * self.masks[step + 1] * gae gae = gae * self.bad_masks[step + 1] self.returns[step] = gae + self.value_preds[step] else: self.returns[-1] = next_value for step in reversed(range(self.rewards.size(0))): self.returns[step] = (self.returns[step + 1] * \ gamma * self.masks[step + 1] + self.rewards[step]) * self.bad_masks[step + 1] \ + (1 - self.bad_masks[step + 1]) * self.value_preds[step] else: if use_gae: self.value_preds[-1] = next_value gae = 0 for step in reversed(range(self.rewards.size(0))): delta = self.rewards[step] + gamma * self.value_preds[ step + 1] * self.masks[step + 1] - self.value_preds[step] gae = delta + gamma * gae_lambda * self.masks[step + 1] * gae self.returns[step] = gae + self.value_preds[step] else: self.returns[-1] = next_value for step in reversed(range(self.rewards.size(0))): self.returns[step] = self.returns[step + 1] * \ gamma * self.masks[step + 1] + self.rewards[step] def feed_forward_generator(self, advantages, num_mini_batch=None, mini_batch_size=None): num_steps, num_processes = self.rewards.size()[0:2] batch_size = num_processes * num_steps if mini_batch_size is None: assert batch_size >= num_mini_batch, ( "PPO requires the number of processes ({}) " "* number of steps ({}) = {} " "to be greater than or equal to the number of PPO mini batches ({})." "".format(num_processes, num_steps, num_processes * num_steps, num_mini_batch)) mini_batch_size = batch_size // num_mini_batch sampler = BatchSampler( SubsetRandomSampler(range(batch_size)), mini_batch_size, drop_last=True) for indices in sampler: obs_batch = {} for key in self.obs: obs_batch[key] = self.obs[key][:-1].view(-1, *self.obs[key].size()[2:])[indices] recurrent_hidden_states_batch = {} for key in self.recurrent_hidden_states: recurrent_hidden_states_batch[key] = self.recurrent_hidden_states[key][:-1].view( -1, self.recurrent_hidden_states[key].size(-1))[indices] actions_batch = self.actions.view(-1, self.actions.size(-1))[indices] value_preds_batch = self.value_preds[:-1].view(-1, 1)[indices] return_batch = self.returns[:-1].view(-1, 1)[indices] masks_batch = self.masks[:-1].view(-1, 1)[indices] old_action_log_probs_batch = self.action_log_probs.view(-1, 1)[indices] if advantages is None: adv_targ = None else: adv_targ = advantages.view(-1, 1)[indices] yield obs_batch, recurrent_hidden_states_batch, actions_batch, \ value_preds_batch, return_batch, masks_batch, old_action_log_probs_batch, adv_targ def recurrent_generator(self, advantages, num_mini_batch): num_processes = self.rewards.size(1) assert num_processes >= num_mini_batch, ( "PPO requires the number of processes ({}) " "to be greater than or equal to the number of " "PPO mini batches ({}).".format(num_processes, num_mini_batch)) num_envs_per_batch = num_processes // num_mini_batch perm = torch.randperm(num_processes) for start_ind in range(0, num_processes, num_envs_per_batch): obs_batch = {} for key in self.obs: obs_batch[key] = [] recurrent_hidden_states_batch = {} for key in self.recurrent_hidden_states: recurrent_hidden_states_batch[key] = [] actions_batch = [] value_preds_batch = [] return_batch = [] masks_batch = [] old_action_log_probs_batch = [] adv_targ = [] for offset in range(num_envs_per_batch): ind = perm[start_ind + offset] for key in self.obs: obs_batch[key].append(self.obs[key][:-1, ind]) for key in self.recurrent_hidden_states: recurrent_hidden_states_batch[key].append(self.recurrent_hidden_states[key][0:1, ind]) actions_batch.append(self.actions[:, ind]) value_preds_batch.append(self.value_preds[:-1, ind]) return_batch.append(self.returns[:-1, ind]) masks_batch.append(self.masks[:-1, ind]) old_action_log_probs_batch.append( self.action_log_probs[:, ind]) adv_targ.append(advantages[:, ind]) T, N = self.num_steps, num_envs_per_batch # These are all tensors of size (T, N, -1) actions_batch = torch.stack(actions_batch, 1) value_preds_batch = torch.stack(value_preds_batch, 1) return_batch = torch.stack(return_batch, 1) masks_batch = torch.stack(masks_batch, 1) old_action_log_probs_batch = torch.stack( old_action_log_probs_batch, 1) adv_targ = torch.stack(adv_targ, 1) for key in obs_batch: obs_batch[key] = torch.stack(obs_batch[key], 1) for key in recurrent_hidden_states_batch: temp = torch.stack(recurrent_hidden_states_batch[key], 1) recurrent_hidden_states_batch[key] = temp.view(N, *(temp.size()[2:])) # Flatten the (T, N, ...) tensors to (T * N, ...) obs_batch = _flatten_helper(T, N, obs_batch) actions_batch = _flatten_helper(T, N, actions_batch) value_preds_batch = _flatten_helper(T, N, value_preds_batch) return_batch = _flatten_helper(T, N, return_batch) masks_batch = _flatten_helper(T, N, masks_batch) old_action_log_probs_batch = _flatten_helper(T, N, \ old_action_log_probs_batch) adv_targ = _flatten_helper(T, N, adv_targ) yield obs_batch, recurrent_hidden_states_batch, actions_batch, \ value_preds_batch, return_batch, masks_batch, old_action_log_probs_batch, adv_targ ================================================ FILE: rl/ppo/__init__.py ================================================ from .ppo import PPO ================================================ FILE: rl/ppo/ppo.py ================================================ import torch import torch.nn as nn import torch.optim as optim class PPO(): """ Class for the PPO optimizer """ def __init__(self, actor_critic, clip_param, ppo_epoch, num_mini_batch, value_loss_coef, entropy_coef, lr=None, eps=None, max_grad_norm=None, use_clipped_value_loss=True): self.actor_critic = actor_critic self.clip_param = clip_param self.ppo_epoch = ppo_epoch self.num_mini_batch = num_mini_batch self.value_loss_coef = value_loss_coef self.entropy_coef = entropy_coef self.max_grad_norm = max_grad_norm self.use_clipped_value_loss = use_clipped_value_loss self.optimizer = optim.Adam(actor_critic.parameters(), lr=lr, eps=eps) def update(self, rollouts): advantages = rollouts.returns[:-1] - rollouts.value_preds[:-1] advantages = (advantages - advantages.mean()) / ( advantages.std() + 1e-5) value_loss_epoch = 0 action_loss_epoch = 0 dist_entropy_epoch = 0 for e in range(self.ppo_epoch): if self.actor_critic.is_recurrent: data_generator = rollouts.recurrent_generator( advantages, self.num_mini_batch) else: data_generator = rollouts.feed_forward_generator( advantages, self.num_mini_batch) for sample in data_generator: obs_batch, recurrent_hidden_states_batch, actions_batch, \ value_preds_batch, return_batch, masks_batch, old_action_log_probs_batch, \ adv_targ = sample # Reshape to do in a single forward pass for all steps values, action_log_probs, dist_entropy, _ = self.actor_critic.evaluate_actions( obs_batch, recurrent_hidden_states_batch, masks_batch, actions_batch) ratio = torch.exp(action_log_probs - old_action_log_probs_batch) surr1 = ratio * adv_targ surr2 = torch.clamp(ratio, 1.0 - self.clip_param, 1.0 + self.clip_param) * adv_targ action_loss = -torch.min(surr1, surr2).mean() if self.use_clipped_value_loss: value_pred_clipped = value_preds_batch + \ (values - value_preds_batch).clamp(-self.clip_param, self.clip_param) value_losses = (values - return_batch).pow(2) value_losses_clipped = ( value_pred_clipped - return_batch).pow(2) value_loss = 0.5 * torch.max(value_losses, value_losses_clipped).mean() else: value_loss = 0.5 * (return_batch - values).pow(2).mean() self.optimizer.zero_grad() total_loss=value_loss * self.value_loss_coef + action_loss - dist_entropy * self.entropy_coef total_loss.backward() nn.utils.clip_grad_norm_(self.actor_critic.parameters(), self.max_grad_norm) self.optimizer.step() value_loss_epoch += value_loss.item() action_loss_epoch += action_loss.item() dist_entropy_epoch += dist_entropy.item() num_updates = self.ppo_epoch * self.num_mini_batch value_loss_epoch /= num_updates action_loss_epoch /= num_updates dist_entropy_epoch /= num_updates return value_loss_epoch, action_loss_epoch, dist_entropy_epoch ================================================ FILE: rl/vec_env/__init__.py ================================================ from .vec_env import AlreadySteppingError, NotSteppingError, VecEnv, VecEnvWrapper, VecEnvObservationWrapper, CloudpickleWrapper from .dummy_vec_env import DummyVecEnv from .shmem_vec_env import ShmemVecEnv from .vec_frame_stack import VecFrameStack from .vec_normalize import VecNormalize from .vec_remove_dict_obs import VecExtractDictObs __all__ = ['AlreadySteppingError', 'NotSteppingError', 'VecEnv', 'VecEnvWrapper', 'VecEnvObservationWrapper', 'CloudpickleWrapper', 'DummyVecEnv', 'ShmemVecEnv', 'VecFrameStack', 'VecNormalize', 'VecExtractDictObs'] ================================================ FILE: rl/vec_env/dummy_vec_env.py ================================================ import numpy as np from .vec_env import VecEnv from .util import copy_obs_dict, dict_to_obs, obs_space_info import copy class DummyVecEnv(VecEnv): """ VecEnv that does runs multiple environments sequentially, that is, the step and reset commands are send to one environment at a time. Useful when debugging and when num_env == 1 (in the latter case, avoids communication overhead) """ def __init__(self, env_fns): """ Arguments: env_fns: iterable of callables functions that build environments """ self.envs = [fn() for fn in env_fns] env = self.envs[0] VecEnv.__init__(self, len(env_fns), env.observation_space, env.action_space) obs_space = env.observation_space self.keys, shapes, dtypes = obs_space_info(obs_space) self.buf_obs = { k: np.zeros((self.num_envs,) + tuple(shapes[k]), dtype=dtypes[k]) for k in self.keys } self.obs_list = [] self.buf_dones = np.zeros((self.num_envs,), dtype=np.bool) self.buf_rews = np.zeros((self.num_envs,), dtype=np.float32) self.buf_infos = [{} for _ in range(self.num_envs)] self.actions = None self.spec = self.envs[0].spec def step_async(self, actions): listify = True try: if len(actions) == self.num_envs: listify = False except TypeError: pass if not listify: self.actions = actions else: assert self.num_envs == 1, "actions {} is either not a list or has a wrong size - cannot match to {} environments".format(actions, self.num_envs) self.actions = [actions] def step_wait(self): for e in range(self.num_envs): action = self.actions[e] # if isinstance(self.envs[e].action_space, spaces.Discrete): # action = int(action) obs, self.buf_rews[e], self.buf_dones[e], self.buf_infos[e] = self.envs[e].step(action) if self.buf_dones[e]: obs = self.envs[e].reset() self._save_obs(e, obs) return (self._obs_from_buf(), np.copy(self.buf_rews), np.copy(self.buf_dones), copy.deepcopy(self.buf_infos)) def reset(self): for e in range(self.num_envs): obs = self.envs[e].reset() self._save_obs(e, obs) return self._obs_from_buf() def talk2Env_async(self, data): self.envs[0].env.talk2Env(data[0]) pass def talk2Env_wait(self): return [True] def _save_obs(self, e, obs): d={} for k in self.keys: if k is None: self.buf_obs[k][e] = obs else: d[k] = self.buf_obs[k][0] self.buf_obs[k][e] = obs[k] self.obs_list = [dict_to_obs(copy_obs_dict(d))] def _obs_from_buf(self): return dict_to_obs(copy_obs_dict(self.buf_obs)) def get_images(self): return [env.render(mode='rgb_array') for env in self.envs] def render(self, mode='human'): if self.num_envs == 1: return self.envs[0].render(mode=mode) else: return super().render(mode=mode) ================================================ FILE: rl/vec_env/envs.py ================================================ import os import gym import numpy as np from gym.spaces.box import Box from .vec_env import VecEnvWrapper import torch from .dummy_vec_env import DummyVecEnv from .shmem_vec_env import ShmemVecEnv from .vec_pretext_normalize import VecPretextNormalize def make_env(env_id, seed, rank): def _thunk(): env = gym.make(env_id) env.seed(seed + rank) if str(env.__class__.__name__).find('TimeLimit') >= 0: env = TimeLimitMask(env) return env return _thunk def make_vec_envs(env_name, seed, num_processes, gamma, device, randomCollect, config=None): envs = [ make_env(env_name, seed, i) for i in range(num_processes) ] if len(envs) > 1: envs = ShmemVecEnv(envs) if os.name == 'nt' else ShmemVecEnv(envs, context='fork') else: envs = DummyVecEnv(envs) if not randomCollect: # if it is not for random data collection phase if gamma is None: envs = VecPretextNormalize(envs, ob=False, ret=False, config=config) else: envs = VecPretextNormalize(envs, ob=False, gamma=gamma, config=config) envs = VecPyTorch(envs, device) return envs # Checks whether done was caused my timit limits or not class TimeLimitMask(gym.Wrapper): def step(self, action): obs, rew, done, info = self.env.step(action) if done and self.env._max_episode_steps == self.env._elapsed_steps: info['bad_transition'] = True return obs, rew, done, info def reset(self, **kwargs): return self.env.reset(**kwargs) class VecPyTorch(VecEnvWrapper): def __init__(self, venv, device): """Return only every `skip`-th frame""" super(VecPyTorch, self).__init__(venv) self.device = device # TODO: Fix data types def reset(self): obs = self.venv.reset() if isinstance(obs, dict): for key in obs: obs[key]=torch.from_numpy(obs[key]).to(self.device) else: obs = torch.from_numpy(obs).float().to(self.device) return obs def step_async(self, actions): if isinstance(actions, torch.LongTensor): # Squeeze the dimension for discrete actions actions = actions.squeeze(1) actions = actions.cpu().numpy() self.venv.step_async(actions) def step_wait(self): obs, reward, done, info = self.venv.step_wait() if isinstance(obs, dict): for key in obs: obs[key] = torch.from_numpy(obs[key]).to(self.device) else: obs = torch.from_numpy(obs).float().to(self.device) reward = torch.from_numpy(reward).unsqueeze(dim=1).float() return obs, reward, done, info ================================================ FILE: rl/vec_env/logger.py ================================================ import os import sys import shutil import os.path as osp import json import time import datetime import tempfile from collections import defaultdict from contextlib import contextmanager DEBUG = 10 INFO = 20 WARN = 30 ERROR = 40 DISABLED = 50 class KVWriter(object): def writekvs(self, kvs): raise NotImplementedError class SeqWriter(object): def writeseq(self, seq): raise NotImplementedError class HumanOutputFormat(KVWriter, SeqWriter): def __init__(self, filename_or_file): if isinstance(filename_or_file, str): self.file = open(filename_or_file, 'wt') self.own_file = True else: assert hasattr(filename_or_file, 'read'), 'expected file or str, got %s'%filename_or_file self.file = filename_or_file self.own_file = False def writekvs(self, kvs): # Create strings for printing key2str = {} for (key, val) in sorted(kvs.items()): if hasattr(val, '__float__'): valstr = '%-8.3g' % val else: valstr = str(val) key2str[self._truncate(key)] = self._truncate(valstr) # Find max widths if len(key2str) == 0: print('WARNING: tried to write empty key-value dict') return else: keywidth = max(map(len, key2str.keys())) valwidth = max(map(len, key2str.values())) # Write out the data dashes = '-' * (keywidth + valwidth + 7) lines = [dashes] for (key, val) in sorted(key2str.items(), key=lambda kv: kv[0].lower()): lines.append('| %s%s | %s%s |' % ( key, ' ' * (keywidth - len(key)), val, ' ' * (valwidth - len(val)), )) lines.append(dashes) self.file.write('\n'.join(lines) + '\n') # Flush the output to the file self.file.flush() def _truncate(self, s): maxlen = 30 return s[:maxlen-3] + '...' if len(s) > maxlen else s def writeseq(self, seq): seq = list(seq) for (i, elem) in enumerate(seq): self.file.write(elem) if i < len(seq) - 1: # add space unless this is the last one self.file.write(' ') self.file.write('\n') self.file.flush() def close(self): if self.own_file: self.file.close() class JSONOutputFormat(KVWriter): def __init__(self, filename): self.file = open(filename, 'wt') def writekvs(self, kvs): for k, v in sorted(kvs.items()): if hasattr(v, 'dtype'): kvs[k] = float(v) self.file.write(json.dumps(kvs) + '\n') self.file.flush() def close(self): self.file.close() class CSVOutputFormat(KVWriter): def __init__(self, filename): self.file = open(filename, 'w+t') self.keys = [] self.sep = ',' def writekvs(self, kvs): # Add our current row to the history extra_keys = list(kvs.keys() - self.keys) extra_keys.sort() if extra_keys: self.keys.extend(extra_keys) self.file.seek(0) lines = self.file.readlines() self.file.seek(0) for (i, k) in enumerate(self.keys): if i > 0: self.file.write(',') self.file.write(k) self.file.write('\n') for line in lines[1:]: self.file.write(line[:-1]) self.file.write(self.sep * len(extra_keys)) self.file.write('\n') for (i, k) in enumerate(self.keys): if i > 0: self.file.write(',') v = kvs.get(k) if v is not None: self.file.write(str(v)) self.file.write('\n') self.file.flush() def close(self): self.file.close() class TensorBoardOutputFormat(KVWriter): """ Dumps key/value pairs into TensorBoard's numeric format. """ def __init__(self, dir): os.makedirs(dir, exist_ok=True) self.dir = dir self.step = 1 prefix = 'events' path = osp.join(osp.abspath(dir), prefix) import tensorflow as tf from tensorflow.python import pywrap_tensorflow from tensorflow.core.util import event_pb2 from tensorflow.python.util import compat self.tf = tf self.event_pb2 = event_pb2 self.pywrap_tensorflow = pywrap_tensorflow self.writer = pywrap_tensorflow.EventsWriter(compat.as_bytes(path)) def writekvs(self, kvs): def summary_val(k, v): kwargs = {'tag': k, 'simple_value': float(v)} return self.tf.Summary.Value(**kwargs) summary = self.tf.Summary(value=[summary_val(k, v) for k, v in kvs.items()]) event = self.event_pb2.Event(wall_time=time.time(), summary=summary) event.step = self.step # is there any reason why you'd want to specify the step? self.writer.WriteEvent(event) self.writer.Flush() self.step += 1 def close(self): if self.writer: self.writer.Close() self.writer = None def make_output_format(format, ev_dir, log_suffix=''): os.makedirs(ev_dir, exist_ok=True) if format == 'stdout': return HumanOutputFormat(sys.stdout) elif format == 'log': return HumanOutputFormat(osp.join(ev_dir, 'log%s.txt' % log_suffix)) elif format == 'json': return JSONOutputFormat(osp.join(ev_dir, 'progress%s.json' % log_suffix)) elif format == 'csv': return CSVOutputFormat(osp.join(ev_dir, 'progress%s.csv' % log_suffix)) elif format == 'tensorboard': return TensorBoardOutputFormat(osp.join(ev_dir, 'tb%s' % log_suffix)) else: raise ValueError('Unknown format specified: %s' % (format,)) # ================================================================ # API # ================================================================ def logkv(key, val): """ Log a value of some diagnostic Call this once for each diagnostic quantity, each iteration If called many times, last value will be used. """ get_current().logkv(key, val) def logkv_mean(key, val): """ The same as logkv(), but if called many times, values averaged. """ get_current().logkv_mean(key, val) def logkvs(d): """ Log a dictionary of key-value pairs """ for (k, v) in d.items(): logkv(k, v) def dumpkvs(): """ Write all of the diagnostics from the current iteration """ return get_current().dumpkvs() def getkvs(): return get_current().name2val def log(*args, level=INFO): """ Write the sequence of args, with no separators, to the console and output files (if you've configured an output file). """ get_current().log(*args, level=level) def debug(*args): log(*args, level=DEBUG) def info(*args): log(*args, level=INFO) def warn(*args): log(*args, level=WARN) def error(*args): log(*args, level=ERROR) def set_level(level): """ Set logging threshold on current logger. """ get_current().set_level(level) def set_comm(comm): get_current().set_comm(comm) def get_dir(): """ Get directory that log files are being written to. will be None if there is no output directory (i.e., if you didn't call start) """ return get_current().get_dir() record_tabular = logkv dump_tabular = dumpkvs @contextmanager def profile_kv(scopename): logkey = 'wait_' + scopename tstart = time.time() try: yield finally: get_current().name2val[logkey] += time.time() - tstart def profile(n): """ Usage: @profile("my_func") def my_func(): code """ def decorator_with_name(func): def func_wrapper(*args, **kwargs): with profile_kv(n): return func(*args, **kwargs) return func_wrapper return decorator_with_name # ================================================================ # Backend # ================================================================ def get_current(): if Logger.CURRENT is None: _configure_default_logger() return Logger.CURRENT class Logger(object): DEFAULT = None # A logger with no output files. (See right below class definition) # So that you can still log to the terminal without setting up any output files CURRENT = None # Current logger being used by the free functions above def __init__(self, dir, output_formats, comm=None): self.name2val = defaultdict(float) # values this iteration self.name2cnt = defaultdict(int) self.level = INFO self.dir = dir self.output_formats = output_formats self.comm = comm # Logging API, forwarded # ---------------------------------------- def logkv(self, key, val): self.name2val[key] = val def logkv_mean(self, key, val): oldval, cnt = self.name2val[key], self.name2cnt[key] self.name2val[key] = oldval*cnt/(cnt+1) + val/(cnt+1) self.name2cnt[key] = cnt + 1 def dumpkvs(self): if self.comm is None: d = self.name2val else: from baselines.common import mpi_util d = mpi_util.mpi_weighted_mean(self.comm, {name : (val, self.name2cnt.get(name, 1)) for (name, val) in self.name2val.items()}) if self.comm.rank != 0: d['dummy'] = 1 # so we don't get a warning about empty dict out = d.copy() # Return the dict for unit testing purposes for fmt in self.output_formats: if isinstance(fmt, KVWriter): fmt.writekvs(d) self.name2val.clear() self.name2cnt.clear() return out def log(self, *args, level=INFO): if self.level <= level: self._do_log(args) # Configuration # ---------------------------------------- def set_level(self, level): self.level = level def set_comm(self, comm): self.comm = comm def get_dir(self): return self.dir def close(self): for fmt in self.output_formats: fmt.close() # Misc # ---------------------------------------- def _do_log(self, args): for fmt in self.output_formats: if isinstance(fmt, SeqWriter): fmt.writeseq(map(str, args)) def get_rank_without_mpi_import(): # check environment variables here instead of importing mpi4py # to avoid calling MPI_Init() when this module is imported for varname in ['PMI_RANK', 'OMPI_COMM_WORLD_RANK']: if varname in os.environ: return int(os.environ[varname]) return 0 def configure(dir=None, format_strs=None, comm=None, log_suffix=''): """ If comm is provided, average all numerical stats across that comm """ if dir is None: dir = os.getenv('OPENAI_LOGDIR') if dir is None: dir = osp.join(tempfile.gettempdir(), datetime.datetime.now().strftime("openai-%Y-%m-%d-%H-%M-%S-%f")) assert isinstance(dir, str) dir = os.path.expanduser(dir) os.makedirs(os.path.expanduser(dir), exist_ok=True) rank = get_rank_without_mpi_import() if rank > 0: log_suffix = log_suffix + "-rank%03i" % rank if format_strs is None: if rank == 0: format_strs = os.getenv('OPENAI_LOG_FORMAT', 'stdout,log,csv').split(',') else: format_strs = os.getenv('OPENAI_LOG_FORMAT_MPI', 'log').split(',') format_strs = filter(None, format_strs) output_formats = [make_output_format(f, dir, log_suffix) for f in format_strs] Logger.CURRENT = Logger(dir=dir, output_formats=output_formats, comm=comm) if output_formats: log('Logging to %s'%dir) def _configure_default_logger(): configure() Logger.DEFAULT = Logger.CURRENT def reset(): if Logger.CURRENT is not Logger.DEFAULT: Logger.CURRENT.close() Logger.CURRENT = Logger.DEFAULT log('Reset logger') @contextmanager def scoped_configure(dir=None, format_strs=None, comm=None): prevlogger = Logger.CURRENT configure(dir=dir, format_strs=format_strs, comm=comm) try: yield finally: Logger.CURRENT.close() Logger.CURRENT = prevlogger # ================================================================ def _demo(): info("hi") debug("shouldn't appear") set_level(DEBUG) debug("should appear") dir = "/tmp/testlogging" if os.path.exists(dir): shutil.rmtree(dir) configure(dir=dir) logkv("a", 3) logkv("b", 2.5) dumpkvs() logkv("b", -2.5) logkv("a", 5.5) dumpkvs() info("^^^ should see a = 5.5") logkv_mean("b", -22.5) logkv_mean("b", -44.4) logkv("a", 5.5) dumpkvs() info("^^^ should see b = -33.3") logkv("b", -2.5) dumpkvs() logkv("a", "longasslongasslongasslongasslongasslongassvalue") dumpkvs() # ================================================================ # Readers # ================================================================ def read_json(fname): import pandas ds = [] with open(fname, 'rt') as fh: for line in fh: ds.append(json.loads(line)) return pandas.DataFrame(ds) def read_csv(fname): import pandas return pandas.read_csv(fname, index_col=None, comment='#') def read_tb(path): """ path : a tensorboard file OR a directory, where we will find all TB files of the form events.* """ import pandas import numpy as np from glob import glob import tensorflow as tf if osp.isdir(path): fnames = glob(osp.join(path, "events.*")) elif osp.basename(path).startswith("events."): fnames = [path] else: raise NotImplementedError("Expected tensorboard file or directory containing them. Got %s"%path) tag2pairs = defaultdict(list) maxstep = 0 for fname in fnames: for summary in tf.train.summary_iterator(fname): if summary.step > 0: for v in summary.summary.value: pair = (summary.step, v.simple_value) tag2pairs[v.tag].append(pair) maxstep = max(summary.step, maxstep) data = np.empty((maxstep, len(tag2pairs))) data[:] = np.nan tags = sorted(tag2pairs.keys()) for (colidx,tag) in enumerate(tags): pairs = tag2pairs[tag] for (step, value) in pairs: data[step-1, colidx] = value return pandas.DataFrame(data, columns=tags) if __name__ == "__main__": _demo() ================================================ FILE: rl/vec_env/running_mean_std.py ================================================ #from typing import Tuple import numpy as np class RunningMeanStd(object): def __init__(self, epsilon = 1e-4, shape = ()): """ Calulates the running mean and std of a data stream https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Parallel_algorithm :param epsilon: helps with arithmetic issues :param shape: the shape of the data stream's output """ self.mean = np.zeros(shape, np.float64) self.var = np.ones(shape, np.float64) self.count = epsilon def update(self, arr): batch_mean = np.mean(arr, axis=0) batch_var = np.var(arr, axis=0) batch_count = arr.shape[0] self.update_from_moments(batch_mean, batch_var, batch_count) def update_from_moments(self, batch_mean, batch_var, batch_count): delta = batch_mean - self.mean tot_count = self.count + batch_count new_mean = self.mean + delta * batch_count / tot_count m_a = self.var * self.count m_b = batch_var * batch_count m_2 = m_a + m_b + np.square(delta) * self.count * batch_count / (self.count + batch_count) new_var = m_2 / (self.count + batch_count) new_count = batch_count + self.count self.mean = new_mean self.var = new_var self.count = new_count ================================================ FILE: rl/vec_env/shmem_vec_env.py ================================================ """ An interface for asynchronous vectorized environments. """ import multiprocessing as mp import numpy as np from .vec_env import VecEnv, CloudpickleWrapper, clear_mpi_env_vars import ctypes #from . import logger from .util import dict_to_obs, obs_space_info, obs_to_dict _NP_TO_CT = {np.float32: ctypes.c_float, np.int32: ctypes.c_int32, np.int8: ctypes.c_int8, np.uint8: ctypes.c_char, np.bool: ctypes.c_bool} class ShmemVecEnv(VecEnv): """ Optimized version of SubprocVecEnv that uses shared variables to communicate observations. """ def __init__(self, env_fns, spaces=None, context='spawn'): """ If you don't specify observation_space, we'll have to create a dummy environment to get it. """ ctx = mp.get_context(context) if spaces: observation_space, action_space = spaces else: print('Creating dummy env object to get spaces') dummy = env_fns[0]() observation_space, action_space = dummy.observation_space, dummy.action_space dummy.close() del dummy VecEnv.__init__(self, len(env_fns), observation_space, action_space) self.obs_keys, self.obs_shapes, self.obs_dtypes = obs_space_info(observation_space) self.obs_bufs = [ {k: ctx.Array(_NP_TO_CT[self.obs_dtypes[k].type], int(np.prod(self.obs_shapes[k]))) for k in self.obs_keys} for _ in env_fns] self.obs_list=[] self.parent_pipes = [] self.procs = [] with clear_mpi_env_vars(): for env_fn, obs_buf in zip(env_fns, self.obs_bufs): wrapped_fn = CloudpickleWrapper(env_fn) parent_pipe, child_pipe = ctx.Pipe() proc = ctx.Process(target=_subproc_worker, args=(child_pipe, parent_pipe, wrapped_fn, obs_buf, self.obs_shapes, self.obs_dtypes, self.obs_keys)) proc.daemon = True self.procs.append(proc) self.parent_pipes.append(parent_pipe) proc.start() child_pipe.close() self.waiting_step = False self.viewer = None def reset(self): if self.waiting_step: print('Called reset() while waiting for the step to complete') self.step_wait() for pipe in self.parent_pipes: pipe.send(('reset', None)) return self._decode_obses([pipe.recv() for pipe in self.parent_pipes]) def step_async(self, actions): assert len(actions) == len(self.parent_pipes) for pipe, act in zip(self.parent_pipes, actions): pipe.send(('step', act)) self.waiting_step = True def step_wait(self): outs = [pipe.recv() for pipe in self.parent_pipes] self.waiting_step = False obs, rews, dones, infos = zip(*outs) return self._decode_obses(obs), np.array(rews), np.array(dones), infos def talk2Env_async(self, data): assert len(data) == len(self.parent_pipes) for pipe, d in zip(self.parent_pipes, data): pipe.send(('talk2Env', d)) self.waiting_step = True def talk2Env_wait(self): outs = [pipe.recv() for pipe in self.parent_pipes] # pipe.recv() is a blocking call self.waiting_step = False return np.array(outs) def close_extras(self): if self.waiting_step: self.step_wait() for pipe in self.parent_pipes: pipe.send(('close', None)) for pipe in self.parent_pipes: pipe.recv() pipe.close() for proc in self.procs: proc.join() def get_images(self, mode='human'): for pipe in self.parent_pipes: pipe.send(('render', None)) return [pipe.recv() for pipe in self.parent_pipes] def _decode_obses(self, obs): result = {} self.obs_list=[{} for _ in range(len(self.obs_bufs))] for k in self.obs_keys: bufs = [b[k] for b in self.obs_bufs] o=[] for idx, b in enumerate(bufs): item=np.frombuffer(b.get_obj(), dtype=self.obs_dtypes[k]).reshape(self.obs_shapes[k]) o.append(item) self.obs_list[idx][k]=item result[k] = np.array(o) return dict_to_obs(result) def _subproc_worker(pipe, parent_pipe, env_fn_wrapper, obs_bufs, obs_shapes, obs_dtypes, keys): """ Control a single environment instance using IPC and shared memory. """ def _write_obs(maybe_dict_obs): flatdict = obs_to_dict(maybe_dict_obs) for k in keys: dst = obs_bufs[k].get_obj() dst_np = np.frombuffer(dst, dtype=obs_dtypes[k]).reshape(obs_shapes[k]) # pylint: disable=W0212 np.copyto(dst_np, flatdict[k]) env = env_fn_wrapper.x() parent_pipe.close() try: while True: cmd, data = pipe.recv() if cmd == 'reset': pipe.send(_write_obs(env.reset())) elif cmd == 'step': obs, reward, done, info = env.step(data) if done: obs = env.reset() pipe.send((_write_obs(obs), reward, done, info)) elif cmd == 'render': pipe.send(env.render(mode='rgb_array')) elif cmd == 'close': pipe.send(None) break elif cmd == 'talk2Env': pipe.send(env.talk2Env(data)) else: raise RuntimeError('Got unrecognized cmd %s' % cmd) except KeyboardInterrupt: print('ShmemVecEnv worker: got KeyboardInterrupt') finally: env.close() ================================================ FILE: rl/vec_env/tile_images.py ================================================ import numpy as np def tile_images(img_nhwc): """ Tile N images into one big PxQ image (P,Q) are chosen to be as close as possible, and if N is square, then P=Q. input: img_nhwc, list or array of images, ndim=4 once turned into array n = batch index, h = height, w = width, c = channel returns: bigim_HWc, ndarray with ndim=3 """ img_nhwc = np.asarray(img_nhwc) N, h, w, c = img_nhwc.shape H = int(np.ceil(np.sqrt(N))) W = int(np.ceil(float(N)/H)) img_nhwc = np.array(list(img_nhwc) + [img_nhwc[0]*0 for _ in range(N, H*W)]) img_HWhwc = img_nhwc.reshape(H, W, h, w, c) img_HhWwc = img_HWhwc.transpose(0, 2, 1, 3, 4) img_Hh_Ww_c = img_HhWwc.reshape(H*h, W*w, c) return img_Hh_Ww_c ================================================ FILE: rl/vec_env/util.py ================================================ """ Helpers for dealing with vectorized environments. """ from collections import OrderedDict import gym import numpy as np def copy_obs_dict(obs): """ Deep-copy an observation dict. """ return {k: np.copy(v) for k, v in obs.items()} def dict_to_obs(obs_dict): """ Convert an observation dict into a raw array if the original observation space was not a Dict space. """ if set(obs_dict.keys()) == {None}: return obs_dict[None] return obs_dict def obs_space_info(obs_space): """ Get dict-structured information about a gym.Space. Returns: A tuple (keys, shapes, dtypes): keys: a list of dict keys. shapes: a dict mapping keys to shapes. dtypes: a dict mapping keys to dtypes. """ if isinstance(obs_space, gym.spaces.Dict): assert isinstance(obs_space.spaces, OrderedDict) subspaces = obs_space.spaces elif isinstance(obs_space, gym.spaces.Tuple): assert isinstance(obs_space.spaces, tuple) subspaces = {i: obs_space.spaces[i] for i in range(len(obs_space.spaces))} else: subspaces = {None: obs_space} keys = [] shapes = {} dtypes = {} for key, box in subspaces.items(): keys.append(key) shapes[key] = box.shape dtypes[key] = box.dtype return keys, shapes, dtypes def obs_to_dict(obs): """ Convert an observation into a dict. """ if isinstance(obs, dict): return obs return {None: obs} ================================================ FILE: rl/vec_env/vec_env.py ================================================ import contextlib import os import abc from abc import abstractmethod import abc ABC = abc.ABCMeta('ABC', (), {}) #from envs.vec_env.tile_images import tile_images class AlreadySteppingError(Exception): """ Raised when an asynchronous step is running while step_async() is called again. """ def __init__(self): msg = 'already running an async step' Exception.__init__(self, msg) class NotSteppingError(Exception): """ Raised when an asynchronous step is not running but step_wait() is called. """ def __init__(self): msg = 'not running an async step' Exception.__init__(self, msg) class VecEnv(ABC): """ An abstract asynchronous, vectorized environment. Used to batch data from multiple copies of an environment, so that each observation becomes an batch of observations, and expected action is a batch of actions to be applied per-environment. """ closed = False viewer = None metadata = { 'render.modes': ['human', 'rgb_array'] } def __init__(self, num_envs, observation_space, action_space): self.num_envs = num_envs self.observation_space = observation_space self.action_space = action_space @abstractmethod def reset(self): """ Reset all the environments and return an array of observations, or a dict of observation arrays. If step_async is still doing work, that work will be cancelled and step_wait() should not be called until step_async() is invoked again. """ pass @abstractmethod def step_async(self, actions): """ Tell all the environments to start taking a step with the given actions. Call step_wait() to get the results of the step. You should not call this if a step_async run is already pending. """ pass @abstractmethod def step_wait(self): """ Wait for the step taken with step_async(). Returns (obs, rews, dones, infos): - obs: an array of observations, or a dict of arrays of observations. - rews: an array of rewards - dones: an array of "episode done" booleans - infos: a sequence of info objects """ pass @abstractmethod def talk2Env_async(self, data): pass @abstractmethod def talk2Env_wait(self): pass def close_extras(self): """ Clean up the extra resources, beyond what's in this base class. Only runs when not self.closed. """ pass def close(self): if self.closed: return if self.viewer is not None: self.viewer.close() self.close_extras() self.closed = True def step(self, actions): """ Step the environments synchronously. This is available for backwards compatibility. """ self.step_async(actions) return self.step_wait() def render(self, mode='human'): imgs = self.get_images() bigimg = tile_images(imgs) if mode == 'human': self.get_viewer().imshow(bigimg) return self.get_viewer().isopen elif mode == 'rgb_array': return bigimg else: raise NotImplementedError def talk2Env(self, data): self.talk2Env_async(data) return self.talk2Env_wait() def get_images(self): """ Return RGB images from each environment """ raise NotImplementedError @property def unwrapped(self): if isinstance(self, VecEnvWrapper): return self.venv.unwrapped else: return self def get_viewer(self): if self.viewer is None: from gym.envs.classic_control import rendering self.viewer = rendering.SimpleImageViewer() return self.viewer class VecEnvWrapper(VecEnv): """ An environment wrapper that applies to an entire batch of environments at once. """ def __init__(self, venv, observation_space=None, action_space=None): self.venv = venv super(VecEnvWrapper,self).__init__(num_envs=venv.num_envs, observation_space=observation_space or venv.observation_space, action_space=action_space or venv.action_space) def step_async(self, actions): self.venv.step_async(actions) def talk2Env_async(self, data): self.venv.talk2Env_async(data) @abstractmethod def talk2Env_wait(self): pass @abstractmethod def reset(self): pass @abstractmethod def step_wait(self): pass def close(self): return self.venv.close() def render(self, mode='human'): return self.venv.render(mode=mode) def get_images(self): return self.venv.get_images() def __getattr__(self, name): if name.startswith('_'): raise AttributeError("attempted to get missing private attribute '{}'".format(name)) return getattr(self.venv, name) class VecEnvObservationWrapper(VecEnvWrapper): @abstractmethod def process(self, obs): pass def reset(self): obs = self.venv.reset() return self.process(obs) def step_wait(self): obs, rews, dones, infos = self.venv.step_wait() return self.process(obs), rews, dones, infos class CloudpickleWrapper(object): """ Uses cloudpickle to serialize contents (otherwise multiprocessing tries to use pickle) """ def __init__(self, x): self.x = x def __getstate__(self): import cloudpickle return cloudpickle.dumps(self.x) def __setstate__(self, ob): import pickle self.x = pickle.loads(ob) @contextlib.contextmanager def clear_mpi_env_vars(): """ from mpi4py import MPI will call MPI_Init by default. If the child process has MPI environment variables, MPI will think that the child process is an MPI process just like the parent and do bad things such as hang. This context manager is a hacky way to clear those environment variables temporarily such as when we are starting multiprocessing Processes. """ removed_environment = {} for k, v in list(os.environ.items()): for prefix in ['OMPI_', 'PMI_']: if k.startswith(prefix): removed_environment[k] = v del os.environ[k] try: yield finally: os.environ.update(removed_environment) ================================================ FILE: rl/vec_env/vec_frame_stack.py ================================================ from .vec_env import VecEnvWrapper import numpy as np from gym import spaces class VecFrameStack(VecEnvWrapper): def __init__(self, venv, nstack): self.venv = venv self.nstack = nstack wos = venv.observation_space # wrapped ob space low = np.repeat(wos.low, self.nstack, axis=-1) high = np.repeat(wos.high, self.nstack, axis=-1) self.stackedobs = np.zeros((venv.num_envs,) + low.shape, low.dtype) observation_space = spaces.Box(low=low, high=high, dtype=venv.observation_space.dtype) VecEnvWrapper.__init__(self, venv, observation_space=observation_space) def step_wait(self): obs, rews, news, infos = self.venv.step_wait() self.stackedobs = np.roll(self.stackedobs, shift=-1, axis=-1) for (i, new) in enumerate(news): if new: self.stackedobs[i] = 0 self.stackedobs[..., -obs.shape[-1]:] = obs return self.stackedobs, rews, news, infos def reset(self): obs = self.venv.reset() self.stackedobs[...] = 0 self.stackedobs[..., -obs.shape[-1]:] = obs return self.stackedobs ================================================ FILE: rl/vec_env/vec_normalize.py ================================================ from . import VecEnvWrapper import numpy as np class VecNormalize(VecEnvWrapper): """ A vectorized wrapper that normalizes the observations and returns from an environment. """ def __init__(self, venv, ob=True, ret=True, clipob=10., cliprew=10., gamma=0.99, epsilon=1e-8, use_tf=False): VecEnvWrapper.__init__(self, venv) if use_tf: from baselines.common.running_mean_std import TfRunningMeanStd self.ob_rms = TfRunningMeanStd(shape=self.observation_space.shape, scope='ob_rms') if ob else None self.ret_rms = TfRunningMeanStd(shape=(), scope='ret_rms') if ret else None else: from baselines.common.running_mean_std import RunningMeanStd self.ob_rms = RunningMeanStd(shape=self.observation_space.shape) if ob else None self.ret_rms = RunningMeanStd(shape=()) if ret else None self.clipob = clipob self.cliprew = cliprew self.ret = np.zeros(self.num_envs) self.gamma = gamma self.epsilon = epsilon def step_wait(self): obs, rews, news, infos = self.venv.step_wait() self.ret = self.ret * self.gamma + rews obs = self._obfilt(obs) if self.ret_rms: self.ret_rms.update(self.ret) rews = np.clip(rews / np.sqrt(self.ret_rms.var + self.epsilon), -self.cliprew, self.cliprew) self.ret[news] = 0. return obs, rews, news, infos def _obfilt(self, obs): if self.ob_rms: self.ob_rms.update(obs) obs = np.clip((obs - self.ob_rms.mean) / np.sqrt(self.ob_rms.var + self.epsilon), -self.clipob, self.clipob) return obs else: return obs def reset(self): self.ret = np.zeros(self.num_envs) obs = self.venv.reset() return self._obfilt(obs) ================================================ FILE: rl/vec_env/vec_pretext_normalize.py ================================================ from . import VecEnvWrapper import numpy as np from .running_mean_std import RunningMeanStd import torch import os from collections import deque import copy import pickle from gst_updated.src.gumbel_social_transformer.temperature_scheduler import Temp_Scheduler from gst_updated.scripts.wrapper.crowd_nav_interface_parallel import CrowdNavPredInterfaceMultiEnv class VecPretextNormalize(VecEnvWrapper): """ A vectorized wrapper that processes the observations and rewards, used for GST predictors and returns from an environment. config: a Config object test: whether we are training or testing """ def __init__(self, venv, ob=False, ret=False, clipob=10., cliprew=10., gamma=0.99, epsilon=1e-8, config=None, test=False): VecEnvWrapper.__init__(self, venv) self.config = config self.device=torch.device(self.config.training.device) if test: self.num_envs = 1 else: self.num_envs = self.config.env.num_processes self.max_human_num = config.sim.human_num + config.sim.human_num_range self.ob_rms = RunningMeanStd(shape=self.observation_space.shape) if ob else None self.ret_rms = RunningMeanStd(shape=()) if ret else None self.clipob = clipob self.cliprew = cliprew self.ret = torch.zeros(self.num_envs).to(self.device) self.gamma = gamma self.epsilon = epsilon # load and configure the prediction model load_path = os.path.join(os.getcwd(), self.config.pred.model_dir) if not os.path.isdir(load_path): raise RuntimeError('The result directory was not found.') checkpoint_dir = os.path.join(load_path, 'checkpoint') with open(os.path.join(checkpoint_dir, 'args.pickle'), 'rb') as f: self.args = pickle.load(f) self.predictor = CrowdNavPredInterfaceMultiEnv(load_path=load_path, device=self.device, config = self.args, num_env = self.num_envs) temperature_scheduler = Temp_Scheduler(self.args.num_epochs, self.args.init_temp, self.args.init_temp, temp_min=0.03) self.tau = temperature_scheduler.decay_whole_process(epoch=100) # handle different prediction and control frequency self.pred_interval = int(self.config.data.pred_timestep//self.config.env.time_step) self.buffer_len = (self.args.obs_seq_len - 1) * self.pred_interval + 1 def talk2Env_async(self, data): self.venv.talk2Env_async(data) def talk2Env_wait(self): outs=self.venv.talk2Env_wait() return outs def step_wait(self): obs, rews, done, infos = self.venv.step_wait() # process the observations and reward obs, rews = self.process_obs_rew(obs, done, rews=rews) return obs, rews, done, infos def _obfilt(self, obs): if self.ob_rms and self.config.RLTrain: self.ob_rms.update(obs) obs = np.clip((obs - self.ob_rms.mean) / np.sqrt(self.ob_rms.var + self.epsilon), -self.clipob, self.clipob) return obs else: return obs def reset(self): # queue for inputs to the pred model # fill the queue with dummy values self.traj_buffer = deque(list(-torch.ones((self.buffer_len, self.num_envs, self.max_human_num, 2), device=self.device)*999), maxlen=self.buffer_len) # (n_env, num_peds, obs_seq_len, 2) self.mask_buffer = deque(list(torch.zeros((self.buffer_len, self.num_envs, self.max_human_num, 1), dtype=torch.bool, device=self.device)), maxlen=self.buffer_len) # (n_env, num_peds, obs_seq_len, 1) self.step_counter = 0 # for calculating the displacement of human positions self.last_pos = torch.zeros(self.num_envs, self.max_human_num, 2).to(self.device) obs = self.venv.reset() obs, _ = self.process_obs_rew(obs, np.zeros(self.num_envs)) return obs ''' 1. Process observations: Run inference on pred model with past obs as inputs, fill in the predicted trajectory in O['spatial_edges'] 2. Process rewards: Calculate reward for colliding with predicted future traj and add to the original reward, same as calc_reward() function in crowd_sim_pred.py except the data are torch tensors ''' def process_obs_rew(self, O, done, rews=0.): # O: robot_node: [nenv, 1, 7], spatial_edges: [nenv, observed_human_num, 2*(1+predict_steps)],temporal_edges: [nenv, 1, 2], # pos_mask: [nenv, max_human_num], pos_disp_mask: [nenv, max_human_num] # prepare inputs for pred_model # find humans' absolute positions human_pos = O['robot_node'][:, :, :2] + O['spatial_edges'][:, :, :2] # insert the new ob to deque self.traj_buffer.append(human_pos) self.mask_buffer.append(O['visible_masks'].unsqueeze(-1)) # [obs_seq_len, nenv, max_human_num, 2] -> [nenv, max_human_num, obs_seq_len, 2] in_traj = torch.stack(list(self.traj_buffer)).permute(1, 2, 0, 3) in_mask = torch.stack(list(self.mask_buffer)).permute(1, 2, 0, 3).float() # index select the input traj and input mask for GST in_traj = in_traj[:, :, ::self.pred_interval] in_mask = in_mask[:, :, ::self.pred_interval] # forward predictor model out_traj, out_mask = self.predictor.forward(input_traj=in_traj, input_binary_mask=in_mask) out_mask = out_mask.bool() # add penalties if the robot collides with predicted future pos of humans # deterministic reward, only uses mu_x, mu_y and a predefined radius # constant radius of each personal zone circle # [nenv, human_num, predict_steps] hr_dist_future = out_traj[:, :, :, :2] - O['robot_node'][:, :, :2].unsqueeze(1) # [nenv, human_num, predict_steps] collision_idx = torch.norm(hr_dist_future, dim=-1) < self.config.robot.radius + self.config.humans.radius # [1,1, predict_steps] # mask out invalid predictions # [nenv, human_num, predict_steps] AND [nenv, human_num, 1] collision_idx = torch.logical_and(collision_idx, out_mask) coefficients = 2. ** torch.arange(2, self.config.sim.predict_steps + 2, device=self.device).reshape( (1, 1, self.config.sim.predict_steps)) # 4, 8, 16, 32, 64 # [1, 1, predict_steps] collision_penalties = self.config.reward.collision_penalty / coefficients # [nenv, human_num, predict_steps] reward_future = collision_idx.to(torch.float)*collision_penalties # [nenv, human_num, predict_steps] -> [nenv, human_num*predict_steps] -> [nenv,] # keep the values & discard indices reward_future, _ = torch.min(reward_future.reshape(self.num_envs, -1), dim=1) # print(reward_future) # seems that rews is on cpu rews = rews + reward_future.reshape(self.num_envs, 1).cpu().numpy() # get observation back to env robot_pos = O['robot_node'][:, :, :2].unsqueeze(1) # convert from positions in world frame to robot frame out_traj[:, :, :, :2] = out_traj[:, :, :, :2] - robot_pos # only take mu_x and mu_y out_mask = out_mask.repeat(1, 1, self.config.sim.predict_steps * 2) new_spatial_edges = out_traj[:, :, :, :2].reshape(self.num_envs, self.max_human_num, -1) O['spatial_edges'][:, :, 2:][out_mask] = new_spatial_edges[out_mask] # sort all humans by distance to robot # [nenv, human_num] hr_dist_cur = torch.linalg.norm(O['spatial_edges'][:, :, :2], dim=-1) sorted_idx = torch.argsort(hr_dist_cur, dim=1) # sorted_idx = sorted_idx.unsqueeze(-1).repeat(1, 1, 2*(self.config.sim.predict_steps+1)) for i in range(self.num_envs): O['spatial_edges'][i] = O['spatial_edges'][i][sorted_idx[i]] obs={'robot_node':O['robot_node'], 'spatial_edges':O['spatial_edges'], 'temporal_edges':O['temporal_edges'], 'visible_masks':O['visible_masks'], 'detected_human_num': O['detected_human_num'], } self.last_pos = copy.deepcopy(human_pos) self.step_counter = self.step_counter + 1 return obs, rews ================================================ FILE: rl/vec_env/vec_remove_dict_obs.py ================================================ from .vec_env import VecEnvObservationWrapper class VecExtractDictObs(VecEnvObservationWrapper): def __init__(self, venv, key): self.key = key super().__init__(venv=venv, observation_space=venv.observation_space.spaces[self.key]) def process(self, obs): return obs[self.key] ================================================ FILE: test.py ================================================ import logging import argparse import os import sys from matplotlib import pyplot as plt import torch import torch.nn as nn from rl.networks.envs import make_vec_envs from rl.evaluation import evaluate from rl.networks.model import Policy from crowd_sim import * def main(): """ The main function for testing a trained model """ # the following parameters will be determined for each test run parser = argparse.ArgumentParser('Parse configuration file') # the model directory that we are testing parser.add_argument('--model_dir', type=str, default='trained_models/GST_predictor_rand') # render the environment or not parser.add_argument('--visualize', default=True, action='store_true') # if -1, it will run 500 different cases; if >=0, it will run the specified test case repeatedly parser.add_argument('--test_case', type=int, default=-1) # model weight file you want to test parser.add_argument('--test_model', type=str, default='41665.pt') # whether to save trajectories of episodes parser.add_argument('--render_traj', default=False, action='store_true') # whether to save slide show of episodes parser.add_argument('--save_slides', default=False, action='store_true') test_args = parser.parse_args() if test_args.save_slides: test_args.visualize = True from importlib import import_module model_dir_temp = test_args.model_dir if model_dir_temp.endswith('/'): model_dir_temp = model_dir_temp[:-1] # import arguments.py from saved directory # if not found, import from the default directory try: model_dir_string = model_dir_temp.replace('/', '.') + '.arguments' model_arguments = import_module(model_dir_string) get_args = getattr(model_arguments, 'get_args') except: print('Failed to get get_args function from ', test_args.model_dir, '/arguments.py') from arguments import get_args algo_args = get_args() # import config class from saved directory # if not found, import from the default directory try: model_dir_string = model_dir_temp.replace('/', '.') + '.configs.config' model_arguments = import_module(model_dir_string) Config = getattr(model_arguments, 'Config') except: print('Failed to get Config function from ', test_args.model_dir) from crowd_nav.configs.config import Config env_config = config = Config() # configure logging and device # print test result in log file log_file = os.path.join(test_args.model_dir,'test') if not os.path.exists(log_file): os.mkdir(log_file) if test_args.visualize: log_file = os.path.join(test_args.model_dir, 'test', 'test_visual.log') else: log_file = os.path.join(test_args.model_dir, 'test', 'test_' + test_args.test_model + '.log') file_handler = logging.FileHandler(log_file, mode='w') stdout_handler = logging.StreamHandler(sys.stdout) level = logging.INFO logging.basicConfig(level=level, handlers=[stdout_handler, file_handler], format='%(asctime)s, %(levelname)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S") logging.info('robot FOV %f', config.robot.FOV) logging.info('humans FOV %f', config.humans.FOV) torch.manual_seed(algo_args.seed) torch.cuda.manual_seed_all(algo_args.seed) if algo_args.cuda: if algo_args.cuda_deterministic: # reproducible but slower torch.backends.cudnn.benchmark = False torch.backends.cudnn.deterministic = True else: # not reproducible but faster torch.backends.cudnn.benchmark = True torch.backends.cudnn.deterministic = False torch.set_num_threads(1) device = torch.device("cuda" if algo_args.cuda else "cpu") logging.info('Create other envs with new settings') # set up visualization if test_args.visualize: fig, ax = plt.subplots(figsize=(7, 7)) ax.set_xlim(-6.5, 6.5) # 6 ax.set_ylim(-6.5, 6.5) ax.axes.xaxis.set_visible(False) ax.axes.yaxis.set_visible(False) # ax.set_xlabel('x(m)', fontsize=16) # ax.set_ylabel('y(m)', fontsize=16) plt.ion() plt.show() else: ax = None load_path=os.path.join(test_args.model_dir,'checkpoints', test_args.test_model) print(load_path) # create an environment env_name = algo_args.env_name eval_dir = os.path.join(test_args.model_dir,'eval') if not os.path.exists(eval_dir): os.mkdir(eval_dir) env_config.render_traj = test_args.render_traj env_config.save_slides = test_args.save_slides env_config.save_path = os.path.join(test_args.model_dir, 'social_eval', test_args.test_model[:-3]) envs = make_vec_envs(env_name, algo_args.seed, 1, algo_args.gamma, eval_dir, device, allow_early_resets=True, config=env_config, ax=ax, test_case=test_args.test_case, pretext_wrapper=config.env.use_wrapper) if config.robot.policy not in ['orca', 'social_force']: # load the policy weights actor_critic = Policy( envs.observation_space.spaces, envs.action_space, base_kwargs=algo_args, base=config.robot.policy) actor_critic.load_state_dict(torch.load(load_path, map_location=device)) actor_critic.base.nenv = 1 # allow the usage of multiple GPUs to increase the number of examples processed simultaneously nn.DataParallel(actor_critic).to(device) else: actor_critic = None test_size = config.env.test_size # call the evaluation function evaluate(actor_critic, envs, 1, device, test_size, logging, config, algo_args, test_args.visualize) if __name__ == '__main__': main() ================================================ FILE: train.py ================================================ import os import shutil import time from collections import deque import numpy as np import torch import torch.nn as nn import pandas as pd import matplotlib.pyplot as plt from rl import ppo from rl.networks import network_utils from arguments import get_args from rl.networks.envs import make_vec_envs from rl.networks.model import Policy from rl.networks.storage import RolloutStorage from crowd_nav.configs.config import Config from crowd_sim import * def main(): """ main function for training a robot policy network """ # read arguments algo_args = get_args() # create a directory for saving the logs and weights if not os.path.exists(algo_args.output_dir): os.makedirs(algo_args.output_dir) # if output_dir exists and overwrite = False elif not algo_args.overwrite: raise ValueError('output_dir already exists!') save_config_dir = os.path.join(algo_args.output_dir, 'configs') if not os.path.exists(save_config_dir): os.makedirs(save_config_dir) shutil.copy('crowd_nav/configs/config.py', save_config_dir) shutil.copy('crowd_nav/configs/__init__.py', save_config_dir) shutil.copy('arguments.py', algo_args.output_dir) env_config = config = Config() torch.manual_seed(algo_args.seed) torch.cuda.manual_seed_all(algo_args.seed) if algo_args.cuda: if algo_args.cuda_deterministic: # reproducible but slower torch.backends.cudnn.benchmark = False torch.backends.cudnn.deterministic = True else: # not reproducible but faster torch.backends.cudnn.benchmark = True torch.backends.cudnn.deterministic = False torch.set_num_threads(algo_args.num_threads) device = torch.device("cuda" if algo_args.cuda else "cpu") env_name = algo_args.env_name if config.sim.render: algo_args.num_processes = 1 algo_args.num_mini_batch = 1 # for visualization if config.sim.render: fig, ax = plt.subplots(figsize=(7, 7)) ax.set_xlim(-10, 10) ax.set_ylim(-10, 10) ax.set_xlabel('x(m)', fontsize=16) ax.set_ylabel('y(m)', fontsize=16) plt.ion() plt.show() else: ax = None # Create a wrapped, monitored VecEnv envs = make_vec_envs(env_name, algo_args.seed, algo_args.num_processes, algo_args.gamma, None, device, False, config=env_config, ax=ax, pretext_wrapper=config.env.use_wrapper) # create a policy network actor_critic = Policy( envs.observation_space.spaces, # pass the Dict into policy to parse envs.action_space, base_kwargs=algo_args, base=config.robot.policy) # storage buffer to store the agent's experience rollouts = RolloutStorage(algo_args.num_steps, algo_args.num_processes, envs.observation_space.spaces, envs.action_space, algo_args.human_node_rnn_size, algo_args.human_human_edge_rnn_size) # continue training from an existing model if resume = True if algo_args.resume: load_path = config.training.load_path actor_critic.load_state_dict(torch.load(load_path)) print("Loaded the following checkpoint:", load_path) # allow the usage of multiple GPUs to increase the number of examples processed simultaneously nn.DataParallel(actor_critic).to(device) # create the ppo optimizer agent = ppo.PPO( actor_critic, algo_args.clip_param, algo_args.ppo_epoch, algo_args.num_mini_batch, algo_args.value_loss_coef, algo_args.entropy_coef, lr=algo_args.lr, eps=algo_args.eps, max_grad_norm=algo_args.max_grad_norm) obs = envs.reset() if isinstance(obs, dict): for key in obs: rollouts.obs[key][0].copy_(obs[key]) else: rollouts.obs[0].copy_(obs) rollouts.to(device) episode_rewards = deque(maxlen=100) start = time.time() num_updates = int( algo_args.num_env_steps) // algo_args.num_steps // algo_args.num_processes # start the training loop for j in range(num_updates): # schedule learning rate if needed if algo_args.use_linear_lr_decay: network_utils.update_linear_schedule( agent.optimizer, j, num_updates, agent.optimizer.lr if algo_args.algo == "acktr" else algo_args.lr) # step the environment for a few times for step in range(algo_args.num_steps): # Sample actions with torch.no_grad(): rollouts_obs = {} for key in rollouts.obs: rollouts_obs[key] = rollouts.obs[key][step] rollouts_hidden_s = {} for key in rollouts.recurrent_hidden_states: rollouts_hidden_s[key] = rollouts.recurrent_hidden_states[key][step] value, action, action_log_prob, recurrent_hidden_states = actor_critic.act( rollouts_obs, rollouts_hidden_s, rollouts.masks[step]) # if we use real prediction, send predictions to env for rendering if env_name == 'CrowdSimPredRealGST-v0' and env_config.env.use_wrapper: # [nenv, max_human_num, 2*(pred_steps+1)] -> [nenv, max_human_num, 2*pred_steps] out_pred = rollouts_obs['spatial_edges'][:, :, 2:].to('cpu').numpy() # send manager action to all processes ack = envs.talk2Env(out_pred) assert all(ack) if config.sim.render: envs.render() # Obser reward and next obs obs, reward, done, infos = envs.step(action) for info in infos: if 'episode' in info.keys(): episode_rewards.append(info['episode']['r']) # If done then clean the history of observations. masks = torch.FloatTensor( [[0.0] if done_ else [1.0] for done_ in done]) bad_masks = torch.FloatTensor( [[0.0] if 'bad_transition' in info.keys() else [1.0] for info in infos]) rollouts.insert(obs, recurrent_hidden_states, action, action_log_prob, value, reward, masks, bad_masks) # store the stepped experience to buffer with torch.no_grad(): rollouts_obs = {} for key in rollouts.obs: rollouts_obs[key] = rollouts.obs[key][-1] rollouts_hidden_s = {} for key in rollouts.recurrent_hidden_states: rollouts_hidden_s[key] = rollouts.recurrent_hidden_states[key][-1] next_value = actor_critic.get_value( rollouts_obs, rollouts_hidden_s, rollouts.masks[-1]).detach() # compute advantage and gradient, and update the network parameters rollouts.compute_returns(next_value, algo_args.use_gae, algo_args.gamma, algo_args.gae_lambda, algo_args.use_proper_time_limits) value_loss, action_loss, dist_entropy = agent.update(rollouts) rollouts.after_update() # save the model for every interval-th episode or for the last epoch if (j % algo_args.save_interval == 0 or j == num_updates - 1) : save_path = os.path.join(algo_args.output_dir, 'checkpoints') if not os.path.exists(save_path): os.mkdir(save_path) torch.save(actor_critic.state_dict(), os.path.join(save_path, '%.5i'%j + ".pt")) if j % algo_args.log_interval == 0 and len(episode_rewards) > 1: total_num_steps = (j + 1) * algo_args.num_processes * algo_args.num_steps end = time.time() print( "Updates {}, num timesteps {}, FPS {} \n Last {} training episodes: mean/median reward " "{:.1f}/{:.1f}, min/max reward {:.1f}/{:.1f}\n" .format(j, total_num_steps, int(total_num_steps / (end - start)), len(episode_rewards), np.mean(episode_rewards), np.median(episode_rewards), np.min(episode_rewards), np.max(episode_rewards), dist_entropy, value_loss, action_loss)) df = pd.DataFrame({'misc/nupdates': [j], 'misc/total_timesteps': [total_num_steps], 'fps': int(total_num_steps / (end - start)), 'eprewmean': [np.mean(episode_rewards)], 'loss/policy_entropy': dist_entropy, 'loss/policy_loss': action_loss, 'loss/value_loss': value_loss}) if os.path.exists(os.path.join(algo_args.output_dir, 'progress.csv')) and j > 20: df.to_csv(os.path.join(algo_args.output_dir, 'progress.csv'), mode='a', header=False, index=False) else: df.to_csv(os.path.join(algo_args.output_dir, 'progress.csv'), mode='w', header=True, index=False) envs.close() if __name__ == '__main__': main()