├── lib ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-36.pyc │ └── __init__.cpython-37.pyc ├── metrics.py └── utils_HIAM.py ├── models ├── __init__.py ├── __pycache__ │ ├── Net.cpython-36.pyc │ ├── rgcn.cpython-36.pyc │ ├── DO_Net.cpython-36.pyc │ ├── OD_Net.cpython-36.pyc │ ├── GGRUCell.cpython-36.pyc │ └── __init__.cpython-36.pyc ├── DualInfoTransformer.py ├── rgcn.py ├── GGRUCell.py ├── DO_Net.py ├── OD_Net.py └── Net.py ├── SOTA ├── DCRNN │ ├── lib │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── utils.cpython-36.pyc │ │ │ ├── __init__.cpython-36.pyc │ │ │ └── metrics.cpython-36.pyc │ │ ├── metrics.py │ │ └── utils.py │ ├── model │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ └── dcrnn_supervisor.cpython-36.pyc │ │ ├── dcrnn_cell.py │ │ ├── dcrnn_model.py │ │ └── dcrnn_supervisor.py │ ├── script.sh │ ├── data │ │ └── config │ │ │ ├── dcrnn_do_sh_96.yaml │ │ │ ├── dcrnn_od_hz_96.yaml │ │ │ ├── dcrnn_od_sh_96.yaml │ │ │ └── dcrnn_do_hz_96.yaml │ └── dcrnn_train_pytorch.py └── Graph-WaveNet-master │ ├── metrics.py │ ├── scripts.sh │ ├── engine1.py │ ├── util1.py │ ├── model1.py │ └── train1.py ├── data ├── prediction_formula.png ├── config │ ├── train_hz_dim26_units96_h4c512.yaml │ ├── train_sh_dim76_units96_h4c512.yaml │ ├── eval_hz_dim26_units96_h4c512.yaml │ └── eval_sh_dim76_units96_h4c512.yaml └── README.md ├── .idea ├── misc.xml ├── vcs.xml ├── inspectionProfiles │ ├── profiles_settings.xml │ └── Project_Default.xml ├── .gitignore ├── modules.xml └── HIAM.iml ├── README.md ├── evaluate.py └── train.py /lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SOTA/DCRNN/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SOTA/DCRNN/model/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/prediction_formula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/data/prediction_formula.png -------------------------------------------------------------------------------- /models/__pycache__/Net.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/models/__pycache__/Net.cpython-36.pyc -------------------------------------------------------------------------------- /models/__pycache__/rgcn.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/models/__pycache__/rgcn.cpython-36.pyc -------------------------------------------------------------------------------- /lib/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/lib/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /lib/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/lib/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /models/__pycache__/DO_Net.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/models/__pycache__/DO_Net.cpython-36.pyc -------------------------------------------------------------------------------- /models/__pycache__/OD_Net.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/models/__pycache__/OD_Net.cpython-36.pyc -------------------------------------------------------------------------------- /models/__pycache__/GGRUCell.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/models/__pycache__/GGRUCell.cpython-36.pyc -------------------------------------------------------------------------------- /models/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/models/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /SOTA/DCRNN/lib/__pycache__/utils.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/SOTA/DCRNN/lib/__pycache__/utils.cpython-36.pyc -------------------------------------------------------------------------------- /SOTA/DCRNN/lib/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/SOTA/DCRNN/lib/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /SOTA/DCRNN/lib/__pycache__/metrics.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/SOTA/DCRNN/lib/__pycache__/metrics.cpython-36.pyc -------------------------------------------------------------------------------- /SOTA/DCRNN/model/__pycache__/dcrnn_supervisor.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HCPLab-SYSU/HIAM/HEAD/SOTA/DCRNN/model/__pycache__/dcrnn_supervisor.cpython-36.pyc -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /../../../../:\PycharmProjects\HIAM\.idea/dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/HIAM.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /SOTA/DCRNN/script.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/bash 2 | 3 | 4 | ### hangzhou 5 | ## od 6 | CUDA_VISIBLE_DEVICES=1 OMP_NUM_THREADS=1 python dcrnn_train_pytorch.py \ 7 | --config 8 | ## do 9 | CUDA_VISIBLE_DEVICES=5 OMP_NUM_THREADS=1 python dcrnn_train_pytorch.py \ 10 | --config data/config/dcrnn_do_hz_96.yaml 11 | 12 | ### shanghai 13 | ## od 14 | CUDA_VISIBLE_DEVICES=0 OMP_NUM_THREADS=1 python dcrnn_train_pytorch.py \ 15 | --config data/model/dcrnn_od_sh.yaml 16 | ## do 17 | CUDA_VISIBLE_DEVICES=5 OMP_NUM_THREADS=1 python dcrnn_train_pytorch.py \ 18 | --config data/config/dcrnn_do_sh_96.yaml -------------------------------------------------------------------------------- /lib/metrics.py: -------------------------------------------------------------------------------- 1 | # part of this code are copied from DCRNN 2 | import numpy as np 3 | 4 | def masked_rmse_np(preds, labels): 5 | return np.sqrt(masked_mse_np(preds=preds, labels=labels)) 6 | 7 | 8 | def masked_mse_np(preds, labels): 9 | rmse = np.square(np.subtract(preds, labels)).astype('float32') 10 | rmse = np.nan_to_num(rmse) 11 | return np.mean(rmse) 12 | 13 | 14 | def masked_mae_np(preds, labels): 15 | mae = np.abs(np.subtract(preds, labels)).astype('float32') 16 | mae = np.nan_to_num(mae) 17 | return np.mean(mae) 18 | 19 | 20 | def masked_mape_np(preds, labels): 21 | mape_net = np.divide(np.sum(np.abs(np.subtract(preds, labels)).astype('float32')), np.sum(labels)) 22 | mape_net = np.nan_to_num(mape_net) 23 | return mape_net 24 | -------------------------------------------------------------------------------- /SOTA/DCRNN/lib/metrics.py: -------------------------------------------------------------------------------- 1 | # part of this code are copied from DCRNN 2 | import numpy as np 3 | 4 | def masked_rmse_np(preds, labels): 5 | return np.sqrt(masked_mse_np(preds=preds, labels=labels)) 6 | 7 | 8 | def masked_mse_np(preds, labels): 9 | rmse = np.square(np.subtract(preds, labels)).astype('float32') 10 | rmse = np.nan_to_num(rmse) 11 | return np.mean(rmse) 12 | 13 | 14 | def masked_mae_np(preds, labels): 15 | mae = np.abs(np.subtract(preds, labels)).astype('float32') 16 | mae = np.nan_to_num(mae) 17 | return np.mean(mae) 18 | 19 | 20 | def masked_mape_np(preds, labels): 21 | mape_net = np.divide(np.sum(np.abs(np.subtract(preds, labels)).astype('float32')), np.sum(labels)) 22 | mape_net = np.nan_to_num(mape_net) 23 | return mape_net -------------------------------------------------------------------------------- /SOTA/Graph-WaveNet-master/metrics.py: -------------------------------------------------------------------------------- 1 | # part of this code are copied from DCRNN 2 | import numpy as np 3 | 4 | def masked_rmse_np(preds, labels): 5 | return np.sqrt(masked_mse_np(preds=preds, labels=labels)) 6 | 7 | 8 | def masked_mse_np(preds, labels): 9 | rmse = np.square(np.subtract(preds, labels)).astype('float32') 10 | rmse = np.nan_to_num(rmse) 11 | return np.mean(rmse) 12 | 13 | 14 | def masked_mae_np(preds, labels): 15 | mae = np.abs(np.subtract(preds, labels)).astype('float32') 16 | mae = np.nan_to_num(mae) 17 | return np.mean(mae) 18 | 19 | 20 | def masked_mape_np(preds, labels): 21 | mape_net = np.divide(np.sum(np.abs(np.subtract(preds, labels)).astype('float32')), np.sum(labels)) 22 | mape_net = np.nan_to_num(mape_net) 23 | return mape_net -------------------------------------------------------------------------------- /SOTA/DCRNN/data/config/dcrnn_do_sh_96.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | base_dir: data/checkpoint/sh 3 | log_level: INFO 4 | data: 5 | batch_size: 8 6 | dataset_dir: data/shanghai/DO/DO_76 7 | test_batch_size: 8 8 | val_batch_size: 8 9 | graph_pkl_filename: data/shanghai/graph_sh_conn.pkl 10 | ds_type: do 11 | 12 | model: 13 | cl_decay_steps: 200 14 | filter_type: dual_random_walk 15 | horizon: 4 16 | input_dim: 76 17 | l1_decay: 0 18 | max_diffusion_step: 2 19 | num_nodes: 288 20 | num_rnn_layers: 2 21 | output_dim: 76 22 | rnn_units: 96 23 | seq_len: 4 24 | use_curriculum_learning: true 25 | 26 | train: 27 | base_lr: 0.001 28 | dropout: 0 29 | epoch: 0 30 | epochs: 300 31 | epsilon: 1.0e-4 32 | global_step: 0 33 | lr_decay_ratio: 0.2 34 | max_grad_norm: 5 35 | max_to_keep: 100 36 | min_learning_rate: 1.0e-12 37 | optimizer: adam 38 | patience: 100 39 | steps: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280] 40 | test_every_n_epochs: 1 41 | -------------------------------------------------------------------------------- /SOTA/DCRNN/data/config/dcrnn_od_hz_96.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | base_dir: data/checkpoint/hz 3 | log_level: INFO 4 | data: 5 | batch_size: 32 6 | dataset_dir: data/hangzhou/OD/OD_26 7 | test_batch_size: 32 8 | val_batch_size: 32 9 | graph_pkl_filename: data/hangzhou/graph_hz_conn.pkl 10 | ds_type: od 11 | 12 | model: 13 | cl_decay_steps: 200 14 | filter_type: dual_random_walk 15 | horizon: 4 16 | input_dim: 26 17 | l1_decay: 0 18 | max_diffusion_step: 2 19 | num_nodes: 80 20 | num_rnn_layers: 2 21 | output_dim: 26 22 | rnn_units: 96 23 | seq_len: 4 24 | use_curriculum_learning: true 25 | 26 | train: 27 | base_lr: 0.001 28 | dropout: 0 29 | epoch: 0 30 | epochs: 300 31 | epsilon: 1.0e-4 32 | global_step: 0 33 | lr_decay_ratio: 0.5 34 | max_grad_norm: 5 35 | max_to_keep: 100 36 | min_learning_rate: 1.0e-12 37 | optimizer: adam 38 | patience: 100 39 | steps: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280] 40 | test_every_n_epochs: 1 41 | -------------------------------------------------------------------------------- /SOTA/DCRNN/data/config/dcrnn_od_sh_96.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | base_dir: data/checkpoint/sh 3 | log_level: INFO 4 | data: 5 | batch_size: 8 6 | dataset_dir: data/shanghai/OD/OD_76 7 | test_batch_size: 8 8 | val_batch_size: 8 9 | graph_pkl_filename: data/shanghai/graph_sh_conn.pkl 10 | ds_type: od 11 | 12 | model: 13 | cl_decay_steps: 200 14 | filter_type: dual_random_walk 15 | horizon: 4 16 | input_dim: 76 17 | l1_decay: 0 18 | max_diffusion_step: 2 19 | num_nodes: 288 20 | num_rnn_layers: 2 21 | output_dim: 76 22 | rnn_units: 96 23 | seq_len: 4 24 | use_curriculum_learning: true 25 | 26 | train: 27 | base_lr: 0.001 28 | dropout: 0 29 | epoch: 0 30 | epochs: 300 31 | epsilon: 1.0e-4 32 | global_step: 0 33 | lr_decay_ratio: 0.2 34 | max_grad_norm: 5 35 | max_to_keep: 100 36 | min_learning_rate: 1.0e-12 37 | optimizer: adam 38 | patience: 100 39 | steps: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280] 40 | test_every_n_epochs: 1 41 | -------------------------------------------------------------------------------- /SOTA/DCRNN/data/config/dcrnn_do_hz_96.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | base_dir: data/checkpoint/hz_do 3 | log_level: INFO 4 | data: 5 | batch_size: 32 6 | dataset_dir: data/hangzhou/DO/DO_26 7 | test_batch_size: 32 8 | val_batch_size: 32 9 | graph_pkl_filename: data/hangzhou/graph_hz_conn.pkl 10 | ds_type: do 11 | 12 | model: 13 | cl_decay_steps: 200 14 | filter_type: dual_random_walk 15 | horizon: 4 16 | input_dim: 26 17 | l1_decay: 0 18 | max_diffusion_step: 2 19 | num_nodes: 80 20 | num_rnn_layers: 2 21 | output_dim: 26 22 | rnn_units: 96 23 | seq_len: 4 24 | use_curriculum_learning: true 25 | 26 | train: 27 | base_lr: 0.001 28 | dropout: 0 29 | epoch: 0 30 | epochs: 300 31 | epsilon: 1.0e-4 32 | global_step: 0 33 | lr_decay_ratio: 0.5 34 | max_grad_norm: 5 35 | max_to_keep: 100 36 | min_learning_rate: 1.0e-12 37 | optimizer: adam 38 | patience: 100 39 | steps: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280] 40 | test_every_n_epochs: 1 41 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | -------------------------------------------------------------------------------- /data/config/train_hz_dim26_units96_h4c512.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | base_dir: data/checkpoint/hz 3 | log_level: INFO 4 | data: 5 | batch_size: 32 6 | test_batch_size: 32 7 | dataset_dir: data/hangzhou/OD/ 8 | do_dataset_dir: data/hangzhou/DO/ 9 | graph_pkl_filename: [ data/hangzhou/graph_hz_conn.pkl] 10 | 11 | model: 12 | cl_decay_steps: 200 13 | horizon: 4 14 | input_dim: 26 15 | output_dim: 26 16 | output_type: fc 17 | num_nodes: 80 18 | num_rnn_layers: 2 19 | rnn_units: 96 20 | seq_len: 4 21 | head: 4 22 | channel: 512 23 | l1_decay: 0 24 | use_curriculum_learning: true 25 | dropout_type: none 26 | dropout_prop: 0.05 27 | use_input: true 28 | num_relations: 1 29 | num_bases: 1 30 | K: 2 31 | norm: True 32 | global_fusion: false 33 | 34 | 35 | train: 36 | base_lr: 0.001 37 | epoch: 0 38 | epochs: 300 39 | epsilon: 1.0e-4 40 | global_step: 0 41 | lr_decay_ratio: 0.5 42 | max_grad_norm: 5 43 | min_learning_rate: 1.0e-12 44 | optimizer: adam 45 | patience: 100 46 | steps: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280] 47 | test_every_n_epochs: 1 48 | save_every_n_epochs: 1 49 | -------------------------------------------------------------------------------- /data/config/train_sh_dim76_units96_h4c512.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | base_dir: data/checkpoint/sh 3 | log_level: INFO 4 | data: 5 | batch_size: 8 6 | test_batch_size: 8 7 | dataset_dir: data/shanghai/OD/ 8 | do_dataset_dir: data/shanghai/DO/ 9 | graph_pkl_filename: [ data/shanghai/graph_sh_conn.pkl] 10 | 11 | model: 12 | cl_decay_steps: 200 13 | horizon: 4 14 | input_dim: 76 15 | output_dim: 76 16 | output_type: fc 17 | num_nodes: 288 18 | num_rnn_layers: 2 19 | rnn_units: 96 20 | seq_len: 4 21 | head: 4 22 | channel: 512 23 | l1_decay: 0 24 | use_curriculum_learning: true 25 | dropout_type: none 26 | dropout_prop: 0.05 27 | use_input: true 28 | num_relations: 1 29 | num_bases: 1 30 | K: 2 31 | norm: True 32 | global_fusion: false 33 | 34 | 35 | train: 36 | base_lr: 0.001 37 | epoch: 0 38 | epochs: 300 39 | epsilon: 1.0e-4 40 | global_step: 0 41 | lr_decay_ratio: 0.2 42 | max_grad_norm: 5 43 | min_learning_rate: 1.0e-12 44 | optimizer: adam 45 | patience: 100 46 | steps: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280] 47 | test_every_n_epochs: 1 48 | save_every_n_epochs: 1 49 | -------------------------------------------------------------------------------- /data/config/eval_hz_dim26_units96_h4c512.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | base_dir: data/checkpoint/hz 3 | log_level: INFO 4 | data: 5 | batch_size: 32 6 | test_batch_size: 32 7 | dataset_dir: data/hangzhou/OD/ 8 | do_dataset_dir: data/hangzhou/DO/ 9 | graph_pkl_filename: [ data/hangzhou/graph_hz_conn.pkl] 10 | 11 | model: 12 | cl_decay_steps: 200 13 | horizon: 4 14 | input_dim: 26 15 | output_dim: 26 16 | output_type: fc 17 | num_nodes: 80 18 | num_rnn_layers: 2 19 | rnn_units: 96 20 | seq_len: 4 21 | head: 4 22 | channel: 512 23 | l1_decay: 0 24 | use_curriculum_learning: true 25 | dropout_type: none 26 | dropout_prop: 0.05 27 | use_input: true 28 | num_relations: 1 29 | num_bases: 1 30 | K: 2 31 | norm: True 32 | global_fusion: false 33 | save_path: data/checkpoint/hz_dim26_units96_h4c512.pt 34 | 35 | train: 36 | base_lr: 0.001 37 | epoch: 0 38 | epochs: 300 39 | epsilon: 1.0e-4 40 | global_step: 0 41 | lr_decay_ratio: 0.5 42 | max_grad_norm: 5 43 | min_learning_rate: 1.0e-12 44 | optimizer: adam 45 | patience: 100 46 | steps: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280] 47 | test_every_n_epochs: 1 48 | save_every_n_epochs: 1 49 | -------------------------------------------------------------------------------- /data/config/eval_sh_dim76_units96_h4c512.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | base_dir: data/checkpoint/sh 3 | log_level: INFO 4 | data: 5 | batch_size: 8 6 | test_batch_size: 8 7 | dataset_dir: data/shanghai/OD/ 8 | do_dataset_dir: data/shanghai/DO/ 9 | graph_pkl_filename: [ data/shanghai/graph_sh_conn.pkl] 10 | 11 | model: 12 | cl_decay_steps: 200 13 | horizon: 4 14 | input_dim: 76 15 | output_dim: 76 16 | output_type: fc 17 | num_nodes: 288 18 | num_rnn_layers: 2 19 | rnn_units: 96 20 | seq_len: 4 21 | head: 4 22 | channel: 512 23 | l1_decay: 0 24 | use_curriculum_learning: true 25 | dropout_type: none 26 | dropout_prop: 0.05 27 | use_input: true 28 | num_relations: 1 29 | num_bases: 1 30 | K: 2 31 | norm: True 32 | global_fusion: false 33 | save_path: data/checkpoint/sh_dim76_units96_h4c512.pt 34 | 35 | 36 | train: 37 | base_lr: 0.001 38 | epoch: 0 39 | epochs: 300 40 | epsilon: 1.0e-4 41 | global_step: 0 42 | lr_decay_ratio: 0.2 43 | max_grad_norm: 5 44 | min_learning_rate: 1.0e-12 45 | optimizer: adam 46 | patience: 100 47 | steps: [60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280] 48 | test_every_n_epochs: 1 49 | save_every_n_epochs: 1 50 | -------------------------------------------------------------------------------- /SOTA/DCRNN/dcrnn_train_pytorch.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | 5 | import argparse 6 | import yaml 7 | import pickle 8 | import numpy as np 9 | 10 | from lib.utils import load_graph_data 11 | from model.dcrnn_supervisor import DCRNNSupervisor 12 | 13 | def load_pickle(pickle_file): 14 | try: 15 | with open(pickle_file, 'rb') as f: 16 | pickle_data = pickle.load(f) 17 | except UnicodeDecodeError as e: 18 | with open(pickle_file, 'rb') as f: 19 | pickle_data = pickle.load(f, encoding='latin1') 20 | except Exception as e: 21 | print('Unable to load data ', pickle_file, ':', e) 22 | raise 23 | return pickle_data 24 | 25 | def main(args): 26 | with open(args.config_filename) as f: 27 | supervisor_config = yaml.load(f) 28 | 29 | graph_pkl_filename = supervisor_config['data'].get('graph_pkl_filename') 30 | # sensor_ids, sensor_id_to_ind, adj_mx = load_graph_data(graph_pkl_filename) 31 | adj_mx = load_pickle(graph_pkl_filename).astype(np.float32) 32 | 33 | for i in range(len(adj_mx)): # 构建邻接矩阵 34 | adj_mx[i, i] = 0 35 | 36 | supervisor = DCRNNSupervisor(adj_mx=adj_mx, **supervisor_config) 37 | 38 | supervisor.train() 39 | 40 | 41 | if __name__ == '__main__': 42 | parser = argparse.ArgumentParser() 43 | parser.add_argument('--config_filename', default=None, type=str, 44 | help='Configuration filename for restoring the model.') 45 | parser.add_argument('--use_cpu_only', default=False, type=bool, help='Set to true to only use cpu.') 46 | args = parser.parse_args() 47 | main(args) 48 | -------------------------------------------------------------------------------- /SOTA/Graph-WaveNet-master/scripts.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/bash 2 | 3 | ### hangzhou OD 4 | CUDA_VISIBLE_DEVICES=7 OMP_NUM_THREADS=1 python train1.py \ 5 | --data data/hangzhou/OD/OD_26 \ 6 | --adjdata data/hangzhou/graph_hz_conn.pkl \ 7 | --exp_base "data" \ 8 | --runs "hz_od" \ 9 | --dropout 0.001 \ 10 | --epochs 400 \ 11 | --learning_rate 0.008 \ 12 | --nhid 96 \ 13 | --out_dim 26 \ 14 | --in_dim 26 \ 15 | --seq_length 4 \ 16 | --num_nodes 80 \ 17 | --gcn_bool \ 18 | --addaptadj \ 19 | --train_tyep "od" \ 20 | --randomadj 21 | 22 | ### hangzhou DO 23 | CUDA_VISIBLE_DEVICES=0 OMP_NUM_THREADS=1 python train1.py \ 24 | --data data/hangzhou/DO/DO_26 \ 25 | --adjdata data/hangzhou/graph_hz_conn.pkl \ 26 | --exp_base "data" \ 27 | --runs "hz_do" \ 28 | --dropout 0.001 \ 29 | --epochs 400 \ 30 | --learning_rate 0.008 \ 31 | --nhid 96 \ 32 | --out_dim 26 \ 33 | --in_dim 26 \ 34 | --seq_length 4 \ 35 | --num_nodes 80 \ 36 | --gcn_bool \ 37 | --addaptadj \ 38 | --train_type "do" \ 39 | --randomadj 40 | 41 | 42 | ###shanghai OD 43 | CUDA_VISIBLE_DEVICES=5 OMP_NUM_THREADS=1 python train1.py \ 44 | --data data/shanghai/OD/OD_76 \ 45 | --adjdata data/shanghai/graph_sh_conn.pkl \ 46 | --exp_base "data" \ 47 | --runs "sh" \ 48 | --dropout 0.001 \ 49 | --epochs 300 \ 50 | --learning_rate 0.01 \ 51 | --nhid 96 \ 52 | --in_dim 76 \ 53 | --out_dim 76 \ 54 | --num_nodes 288 \ 55 | --seq_length 4 \ 56 | --gcn_bool \ 57 | --addaptadj \ 58 | --randomadj 59 | 60 | ###shanghai DO 61 | CUDA_VISIBLE_DEVICES=1 OMP_NUM_THREADS=1 python train1.py \ 62 | --data data/shanghai/DO/DO_76 \ 63 | --adjdata data/shanghai/graph_sh_conn.pkl \ 64 | --exp_base "data" \ 65 | --runs "sh_do" \ 66 | --dropout 0.001 \ 67 | --epochs 300 \ 68 | --learning_rate 0.01 \ 69 | --nhid 96 \ 70 | --in_dim 76 \ 71 | --out_dim 76 \ 72 | --num_nodes 288 \ 73 | --seq_length 4 \ 74 | --gcn_bool \ 75 | --addaptadj \ 76 | --train_type "do" \ 77 | --randomadj 78 | 79 | tar --exclude='./data' -zcvf /backup/filename.tgz . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Online Metro Origin-Destination Prediction via Heterogeneous Information Aggregation 2 | This is a PyTorch implementation of **Online Metro Origin-Destination Prediction via Heterogeneous Information Aggregation**. 3 | 4 | With high trip efficiencies and cheap ticket charges, metro has recently become a popular travel mode for urban residents. Due to its significant applications, metro ridership prediction has recently attracted extensive attention in both academic and industrial communities. However, most of conventional works were merely proposed for station-level prediction, i.e., forecasting the inflow and outflow of each metro station. Such information of inflow/outflow ridership is too coarse to reflect the mobility of passengers. 5 | 6 | We propose a novel **Heterogeneous Information Aggregation Machine** to facilitate the online metro origin-destination prediction. To the best of our knowledge, our **HIAM** is the first deep learning based approach that fully aggregates heterogeneous information to jointly forecast the future OD ridership and DO ridership. 7 | 8 | If you use this code for your research, please cite our papers. 9 | ``` 10 | @article{liu2022online, 11 | title={Online Metro Origin-Destination Prediction via Heterogeneous Information Aggregation}, 12 | author={Liu, Lingbo and Zhu, Yuying and Li, Guanbin and Wu, Ziyi and Bai, Lei and Lin, Liang}, 13 | journal={IEEE Transactions on Pattern Analysis and Machine Intelligence}, 14 | year={2022}, 15 | publisher={IEEE} 16 | } 17 | ``` 18 | 19 | ### Requirements 20 | - python3 21 | - numpy 22 | - yaml 23 | - pytorch 24 | - torch_geometric 25 | ### Extract dataset 26 | Please download the dataset and extract it to `data` folder. 27 | - [Dropbox link](https://www.dropbox.com/sh/4pgk4uez7g200fg/AACHN6wMhjq_v0R2ZZ8ZeI6ma?dl=0) 28 | - [Baidu Netdisk,password:q6e0 ](https://pan.baidu.com/s/1PHN8SNT3jTroX0sTWHsrXw) 29 | 30 | ## Train 31 | - SHMOD 32 | ``` 33 | python train.py --config data/config/train_sh_dim76_units96_h4c512.yaml 34 | ``` 35 | 36 | - HZMOD 37 | ``` 38 | python train.py --config data/config/train_hz_dim26_units96_h4c512.yaml 39 | ``` 40 | ## Test 41 | First of all, download the trained model and extract it to the path:`data/checkpoint`. 42 | 43 | - SHMOD 44 | ``` 45 | python evaluate.py --config data/checkpoint/eval_sh_dim76_units96_h4c512.yaml 46 | ``` 47 | - HZMOD 48 | ``` 49 | python evaluate.py --config data/checkpoint/eval_hz_dim26_units96_h4c512.yaml 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # SHMOD & HZMOD Dataset 2 | In this work, we focus on the metro ridership of 5:15 - 23:30 and utilize the metro OD/DO distribution of the previous four time intervals (15minutes x 4 = 60minutes) to predict the metro OD/DO distribution of future four time intervals (15minutes x 4 = 60minutes). 3 | 4 | Specifically, the data of input sequence consists of incomplete OD matrix(IOD), unfinished order vector(U), and DO matrix(DO) and our online origin-destination prediction could be denoted as follows. 5 | 6 | ​ ![image](https://github.com/GillianZhu/HIAM/blob/master/data/prediction_formula.png) 7 | 8 | ​ where i=1,2,...,n and j=1,2,...,m. n=4, m=4 9 | 10 | We release two datasets named SHMOD and HZMOD, respectively. Each dataset is divided into a training set, a validation set and a testing set, and also contains corresponding metro graph information. For each set, we release four `pkl` files, three for metro OD ridership distribution data, and one for metro DO ridership distribution data. 11 | 12 | ### 1. Metro OD&DO Matrix 13 | 14 | - train.pkl: the OD training set. It is a `dict` that consists of 5 `ndarray`: 15 | 16 | ``` 17 | (1) finished: the incomplete OD matrix of the previous four time intervals. Its shape is [T, n, N, D]. 18 | (2) unfinished: the unfinished order vector of the previous four time intervals. Its shape is [T, n, N, 1]. 19 | (3) y: the complete OD matrix of the next four time intervals. Its shape is [T, n, N, D]. 20 | (4) xtime: the timestamps of x. Its shape is [T, n]. 21 | (5) ytime: the timestamps of y. Its shape is [T, m]. 22 | 23 | T = the number of time slices 24 | N = the number of metro stations, i.e, 80/288 (HZMOD/SHMOD) in our work 25 | n = the length of the input sequence, i.e, 4 time intervals in our work 26 | m = the length of the output sequence, i.e, 4 time intervals in our work 27 | D = the data dimension of each station, i.e, 26/76 (HZMOD/SHMOD) in our work 28 | ``` 29 | 30 | - train_history_long.pkl: the long-term historical information of the training set. It is a `dict` that consists of 1 `ndarray`: 31 | 32 | ``` 33 | (1) history: the long-term destination distribution matrix of the previous four time intervals. Its shape is [T, n, N, D]. 34 | ``` 35 | 36 | Note that val_history_long.pkl and test_history_long.pkl are also built based on the long-term historical data on the training set. 37 | 38 | - train_history_short.pkl: the short-term historical information of the training set. It is a `dict` that consists of 1 `ndarray`: 39 | 40 | ``` 41 | (1) history: the short-term destination distribution matrix of the previous four time intervals. Its shape is [T, n, N, D]. 42 | ``` 43 | 44 | - train_do.pkl: the DO training set. It is a `dict` that consists of 4 `ndarray`: 45 | 46 | ``` 47 | (1) do_x: the DO matrix of the previous four time intervals. Its shape is [T, n, N, D]. 48 | (2) do_y: the DO matrix of the next four time intervals. Its shape is [T, m, N, D]. 49 | (3) xtime: the timestamps of x. Its shape is [T, n]. 50 | (4) ytime: the timestamps of y. Its shape is [T, m]. 51 | ``` 52 | 53 | Besides, each pkl file has its counterparts on testing set and validation set with similar structure. 54 | 55 | ### 2. Graph Information 56 | 57 | - graph_conn.pkl: the physical topology information of metro system. 58 | -------------------------------------------------------------------------------- /SOTA/Graph-WaveNet-master/engine1.py: -------------------------------------------------------------------------------- 1 | import torch.optim as optim 2 | from model1 import * 3 | from torch.optim.lr_scheduler import MultiStepLR 4 | import torch.nn as nn 5 | import torch 6 | 7 | class StepLR2(MultiStepLR): 8 | """StepLR with min_lr""" 9 | 10 | def __init__(self, 11 | optimizer, 12 | milestones, 13 | gamma=0.1, 14 | last_epoch=-1, 15 | min_lr=2.0e-6): 16 | """ 17 | 18 | :optimizer: TODO 19 | :milestones: TODO 20 | :gamma: TODO 21 | :last_epoch: TODO 22 | :min_lr: TODO 23 | 24 | """ 25 | self.optimizer = optimizer 26 | self.milestones = milestones 27 | self.gamma = gamma 28 | self.last_epoch = last_epoch 29 | self.min_lr = min_lr 30 | super(StepLR2, self).__init__(optimizer, milestones, gamma) 31 | 32 | def get_lr(self): 33 | lr_candidate = super(StepLR2, self).get_lr() 34 | if isinstance(lr_candidate, list): 35 | for i in range(len(lr_candidate)): 36 | lr_candidate[i] = max(self.min_lr, lr_candidate[i]) 37 | 38 | else: 39 | lr_candidate = max(self.min_lr, lr_candidate) 40 | 41 | return lr_candidate 42 | 43 | class trainer(): 44 | def __init__(self, scaler, in_dim, seq_length, num_nodes, out_dim, nhid , dropout, lrate, wdecay, device, supports, gcn_bool, addaptadj, aptinit): 45 | self.model = gwnet( 46 | device, 47 | num_nodes, 48 | dropout, 49 | supports=supports, 50 | gcn_bool=gcn_bool, 51 | addaptadj=addaptadj, 52 | aptinit=aptinit, 53 | in_dim=in_dim, # input feature 54 | out_dim=out_dim, # output feature dim 55 | residual_channels=nhid, 56 | dilation_channels=nhid, 57 | skip_channels=nhid * 8, # TODO: skip channels ? 58 | end_channels=nhid * 16, 59 | blocks=4, 60 | ) 61 | self.model.to(device) 62 | self.optimizer = optim.Adam(self.model.parameters(), lr=lrate, weight_decay=wdecay) 63 | self.scheduler = MultiStepLR(optimizer=self.optimizer, milestones=[200, 350], gamma=0.5) 64 | self.loss = nn.L1Loss(reduction='mean') 65 | self.scaler = scaler 66 | self.clip = 5 67 | self.device =device 68 | 69 | def train(self, input, real_val): 70 | self.model.train() 71 | self.optimizer.zero_grad() 72 | input = input.to(self.device) 73 | real_val = real_val.to(self.device) 74 | output = self.model(input) 75 | real = real_val 76 | predict = self.scaler.inverse_transform(output) 77 | loss = self.loss(predict, real) 78 | loss.backward() 79 | if self.clip is not None: 80 | torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.clip) 81 | self.optimizer.step() 82 | self.scheduler.step() 83 | 84 | return loss.item(), predict, real 85 | 86 | def eval(self, input, real_val): 87 | self.model.eval() 88 | input = nn.functional.pad(input,(1,0,0,0)).to(self.device) 89 | output = self.model(input) 90 | output = output.transpose(1,3) 91 | real = real_val 92 | predict = self.scaler.inverse_transform(output) 93 | loss = self.loss(predict, real,self.device, 0.0) 94 | return loss.item(), predict, real -------------------------------------------------------------------------------- /models/DualInfoTransformer.py: -------------------------------------------------------------------------------- 1 | from torch_geometric import nn as gnn 2 | from torch import nn 3 | import torch 4 | import math 5 | import sys 6 | import os 7 | import copy 8 | from torch.nn import functional as F 9 | sys.path.insert(0, os.path.abspath('..')) 10 | 11 | 12 | class DualInfoTransformer(nn.Module): 13 | def __init__(self, h=4, d_nodes=288, d_channel=512, d_model=96): 14 | "Take in model size and number of heads." 15 | super(DualInfoTransformer, self).__init__() 16 | assert d_model % h == 0 17 | 18 | self.d_nodes = d_nodes 19 | self.d_model = d_model 20 | self.d_channel = d_channel 21 | self.d_k = d_channel // h 22 | self.h = h 23 | 24 | self.od_linears = self.clones( 25 | nn.Sequential( 26 | nn.Conv1d(in_channels=d_model, out_channels=d_channel, kernel_size=1), 27 | nn.PReLU(d_channel), 28 | nn.Conv1d(in_channels=d_channel, out_channels=d_channel, kernel_size=1), 29 | nn.PReLU(d_channel)), 3) 30 | self.do_linears = self.clones( 31 | nn.Sequential( 32 | nn.Conv1d(in_channels=d_model, out_channels=d_channel, kernel_size=1), 33 | nn.PReLU(d_channel), 34 | nn.Conv1d(in_channels=d_channel, out_channels=d_channel, kernel_size=1), 35 | nn.PReLU(d_channel)), 3) 36 | self.od_conv = nn.Sequential( 37 | nn.Conv1d(in_channels=d_channel, out_channels=d_channel, kernel_size=1), 38 | nn.PReLU(d_channel), 39 | nn.Conv1d(in_channels=d_channel, out_channels=d_model, kernel_size=1), 40 | nn.PReLU(d_model) 41 | ) 42 | self.do_conv = nn.Sequential( 43 | nn.Conv1d(in_channels=d_channel, out_channels=d_channel, kernel_size=1), 44 | nn.PReLU(d_channel), 45 | nn.Conv1d(in_channels=d_channel, out_channels=d_model, kernel_size=1), 46 | nn.PReLU(d_model) 47 | ) 48 | 49 | def clones(self, module, N): 50 | "Produce N identical layers." 51 | return nn.ModuleList([copy.deepcopy(module) for _ in range(N)]) 52 | 53 | def attention(self, query, key, value): 54 | "Compute 'Scaled Dot Product Attention'" 55 | d_k = query.size(-1) # batch, heads, num_nodes, num_units / heads 56 | scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k) # batch, heads, num_nodes, num_nodes 57 | p_attn = F.softmax(scores, dim=-1) # batch, heads, num_nodes, num_nodes 58 | return torch.matmul(p_attn, value) # batch, heads, num_nodes, num_units / heads 59 | 60 | def MultiHeadedAttention(self, hid_od, hid_do): 61 | hid_od = hid_od.view(-1, self.d_model, self.d_nodes) 62 | hid_do = hid_do.view(-1, self.d_model, self.d_nodes) 63 | nbatches = hid_od.size(0) 64 | 65 | # 1) Do all the linear projections in batch from d_model => h x d_k 66 | odquery, odkey, odvalue = \ 67 | [l(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2) 68 | for l, x in zip(self.od_linears, (hid_od, hid_od, hid_od))] # batch, heads, num_nodes, num_units / heads 69 | doquery, dokey, dovalue = \ 70 | [l(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2) 71 | for l, x in zip(self.do_linears, (hid_do, hid_do, hid_do))] 72 | 73 | # 2) Apply attention on all the projected vectors in batch. 74 | attn_od = self.od_conv( 75 | self.attention(query=odquery, 76 | key=dokey, 77 | value=dovalue).transpose(-2, -1).contiguous().view(-1, self.d_channel, self.d_nodes)) # batch, heads, 1, num_units / heads 78 | attn_do = self.do_conv( 79 | self.attention(query=doquery, 80 | key=odkey, 81 | value=odvalue).transpose(-2, -1).contiguous().view(-1, self.d_channel, self.d_nodes)) 82 | return attn_od.view(-1, self.d_model), attn_do.view(-1, self.d_model) 83 | 84 | def forward(self, hidden_states_od, hidden_states_do): 85 | return self.MultiHeadedAttention(hidden_states_od, hidden_states_do) 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /models/rgcn.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from torch.nn import Parameter as Param 3 | from torch_geometric.nn.conv import MessagePassing 4 | 5 | from torch_geometric.nn.inits import uniform 6 | from torch.nn.init import xavier_normal_, calculate_gain 7 | 8 | 9 | class RGCNConv(MessagePassing): 10 | r"""The relational graph convolutional operator from the `"Modeling 11 | Relational Data with Graph Convolutional Networks" 12 | `_ paper 13 | 14 | .. math:: 15 | \mathbf{x}^{\prime}_i = \mathbf{\Theta}_0 \cdot \mathbf{x}_i + 16 | \sum_{r \in \mathcal{R}} \sum_{j \in \mathcal{N}_r(i)} 17 | \frac{1}{|\mathcal{N}_r(i)|} \mathbf{\Theta}_r \cdot \mathbf{x}_j, 18 | 19 | where :math:`\mathcal{R}` denotes the set of relations, *i.e.* edge types. 20 | Edge type needs to be a one-dimensional :obj:`torch.long` tensor which 21 | stores a relation identifier 22 | :math:`\in \{ 0, \ldots, |\mathcal{R}| - 1\}` for each edge. 23 | 24 | Args: 25 | in_channels (int): Size of each input sample. 26 | out_channels (int): Size of each output sample. 27 | num_relations (int): Number of relations. 28 | num_bases (int): Number of bases used for basis-decomposition. 29 | bias (bool, optional): If set to :obj:`False`, the layer will not learn 30 | an additive bias. (default: :obj:`True`) 31 | **kwargs (optional): Additional arguments of 32 | :class:`torch_geometric.nn.conv.MessagePassing`. 33 | """ 34 | 35 | def __init__(self, 36 | in_channels, 37 | out_channels, 38 | num_relations, 39 | num_bases, 40 | bias=True, 41 | **kwargs): 42 | super(RGCNConv, self).__init__(aggr='add', **kwargs) 43 | 44 | self.in_channels = in_channels 45 | self.out_channels = out_channels 46 | self.num_relations = num_relations 47 | self.num_bases = num_bases 48 | 49 | self.basis = Param(torch.Tensor(num_bases, in_channels, out_channels)) 50 | self.att = Param(torch.Tensor(num_relations, num_bases)) 51 | self.root = Param(torch.Tensor(in_channels, out_channels)) 52 | 53 | if bias: 54 | self.bias = Param(torch.Tensor(out_channels)) 55 | else: 56 | self.register_parameter('bias', None) 57 | 58 | self.reset_parameters() 59 | 60 | def reset_parameters(self): 61 | # size = self.num_bases * self.in_channels 62 | # uniform(size, self.basis) 63 | # uniform(size, self.att) 64 | # uniform(size, self.root) 65 | # xavier_normal_(self.basis, gain=calculate_gain('relu')) 66 | # xavier_normal_(self.root, gain=calculate_gain('relu')) 67 | torch.nn.init.xavier_uniform_(self.basis) 68 | torch.nn.init.xavier_uniform_(self.att) 69 | torch.nn.init.xavier_uniform_(self.root) 70 | 71 | def forward(self, x, edge_index, edge_attr, edge_norm=None): 72 | """""" 73 | # print("forward:", x.shape, edge_attr.shape) 74 | return self.propagate( 75 | edge_index, x=x, edge_attr=edge_attr, edge_norm=edge_norm) 76 | 77 | def message(self, x_j, edge_index_j, edge_attr, edge_norm): 78 | w = torch.matmul(self.att, self.basis.view(self.num_bases, -1)) 79 | w = w.to(x_j.device) # 加cuda 80 | w = w.view(self.num_relations, self.in_channels, self.out_channels) # (num_relations, in, out) 81 | out = torch.einsum('bi,rio->bro', x_j, w) # x_j: (batch, in) w:(num_relations, in, out) out:(batch, num_relations, out) 先转置w再相乘 82 | # print("message1:", x_j.size(), w.size(), out.size()) 83 | out = (out * edge_attr.unsqueeze(2)).sum(dim=1) 84 | # print("message2:", out.size(), edge_attr.unsqueeze(2).size()) 85 | # out = torch.bmm(x_j.unsqueeze(1), w).squeeze(-2) 86 | 87 | return out if edge_norm is None else out * edge_norm.view(-1, 1) 88 | 89 | def update(self, aggr_out, x): 90 | if x is None: 91 | out = aggr_out + self.root 92 | else: 93 | out = aggr_out + torch.matmul(x, self.root) 94 | 95 | if self.bias is not None: 96 | out = out + self.bias 97 | 98 | # print("update:", out.size()) 99 | return out 100 | 101 | def __repr__(self): 102 | return '{}({}, {}, num_relations={})'.format( 103 | self.__class__.__name__, self.in_channels, self.out_channels, 104 | self.num_relations) 105 | -------------------------------------------------------------------------------- /models/GGRUCell.py: -------------------------------------------------------------------------------- 1 | from torch_geometric import nn as gnn 2 | from torch import nn 3 | from torch.nn import functional as F 4 | from torch.nn import init, Parameter 5 | import torch 6 | import random 7 | import math 8 | import sys 9 | import os 10 | import copy 11 | # from lib.utils_unfinished import softmax 12 | from torch.nn import functional as F 13 | sys.path.insert(0, os.path.abspath('..')) 14 | 15 | from models.rgcn import RGCNConv 16 | 17 | 18 | def zoneout(prev_h, next_h, rate, training=True): 19 | """TODO: Docstring for zoneout. 20 | 21 | :prev_h: TODO 22 | :next_h: TODO 23 | 24 | :p: when p = 1, all new elements should be droped 25 | when p = 0, all new elements should be maintained 26 | 27 | :returns: TODO 28 | 29 | """ 30 | from torch.nn.functional import dropout 31 | if training: 32 | # bernoulli: draw a value 1. 33 | # p = 1 -> d = 1 -> return prev_h 34 | # p = 0 -> d = 0 -> return next_h 35 | # d = torch.zeros_like(next_h).bernoulli_(p) 36 | # return (1 - d) * next_h + d * prev_h 37 | next_h = (1 - rate) * dropout(next_h - prev_h, rate) + prev_h 38 | else: 39 | next_h = rate * prev_h + (1 - rate) * next_h 40 | 41 | return next_h 42 | 43 | 44 | class KStepRGCN(nn.Module): 45 | """docstring for KStepRGCN""" 46 | 47 | def __init__( 48 | self, 49 | in_channels, 50 | out_channels, 51 | num_relations, 52 | num_bases, 53 | K, 54 | bias, 55 | ): 56 | super(KStepRGCN, self).__init__() 57 | self.in_channels = in_channels 58 | self.out_channels = out_channels 59 | self.num_relations = num_relations 60 | self.num_bases = num_bases 61 | self.K = K 62 | self.rgcn_layers = nn.ModuleList([ 63 | RGCNConv(in_channels, 64 | out_channels, 65 | num_relations, 66 | num_bases, 67 | bias) 68 | ] + [ 69 | RGCNConv(out_channels, 70 | out_channels, 71 | num_relations, 72 | num_bases, 73 | bias) for _ in range(self.K - 1) 74 | ]) 75 | self.mediate_activation = nn.PReLU() 76 | 77 | def forward(self, x, edge_index, edge_attr): 78 | for i in range(self.K): 79 | x = self.rgcn_layers[i](x=x, 80 | edge_index=edge_index, 81 | edge_attr=edge_attr, 82 | edge_norm=None) 83 | # not final layer, add relu 84 | if i != self.K - 1: 85 | #x = torch.relu(x) 86 | x = self.mediate_activation(x) 87 | return x 88 | 89 | 90 | class GGRUCell(nn.Module): 91 | """Docstring for GGRUCell. """ 92 | 93 | def __init__(self, 94 | in_channels, 95 | out_channels, 96 | dropout_type=None, 97 | dropout_prob=0.0, 98 | num_relations=3, 99 | num_bases=3, 100 | K=1, 101 | num_nodes=80, 102 | global_fusion=False): 103 | """TODO: to be defined1. """ 104 | super(GGRUCell, self).__init__() 105 | self.num_chunks = 3 106 | self.in_channels = in_channels 107 | self.out_channels = out_channels 108 | self.num_relations = num_relations 109 | self.num_bases = num_bases 110 | self.num_nodes = num_nodes 111 | self.global_fusion = global_fusion 112 | self.cheb_i = KStepRGCN(in_channels, 113 | out_channels * self.num_chunks, 114 | num_relations=num_relations, 115 | num_bases=num_bases, 116 | K=K, 117 | bias=False) 118 | self.cheb_h = KStepRGCN(out_channels, 119 | out_channels * self.num_chunks, 120 | num_relations=num_relations, 121 | num_bases=num_bases, 122 | K=K, 123 | bias=False) 124 | 125 | self.bias_i = Parameter(torch.Tensor(self.out_channels)) 126 | self.bias_r = Parameter(torch.Tensor(self.out_channels)) 127 | self.bias_n = Parameter(torch.Tensor(self.out_channels)) 128 | self.dropout_prob = dropout_prob 129 | self.dropout_type = dropout_type 130 | 131 | self.reset_parameters() 132 | 133 | def reset_parameters(self): 134 | init.ones_(self.bias_i) 135 | init.ones_(self.bias_r) 136 | init.ones_(self.bias_n) 137 | 138 | if self.global_fusion is True: 139 | init.ones_(self.bias_i_g) 140 | init.ones_(self.bias_r_g) 141 | init.ones_(self.bias_n_g) 142 | 143 | def forward(self, inputs, edge_index, edge_attr, hidden=None): 144 | """TODO: Docstring for forward. 145 | 146 | :inputs: TODO 147 | :hidden: TODO 148 | :returns: TODO 149 | 150 | """ 151 | if hidden is None: 152 | hidden = torch.zeros(inputs.size(0), 153 | self.out_channels, 154 | dtype=inputs.dtype, 155 | device=inputs.device) 156 | gi = self.cheb_i(inputs, edge_index=edge_index, edge_attr=edge_attr) 157 | gh = self.cheb_h(hidden, edge_index=edge_index, edge_attr=edge_attr) 158 | # print("cheb_i:", gi.shape) 159 | # print("cheb_h:", gh.shape) 160 | i_r, i_i, i_n = gi.chunk(3, 1) 161 | h_r, h_i, h_n = gh.chunk(3, 1) 162 | 163 | resetgate = torch.sigmoid(i_r + h_r + self.bias_r) 164 | inputgate = torch.sigmoid(i_i + h_i + self.bias_i) 165 | newgate = torch.tanh(i_n + resetgate * h_n + self.bias_n) 166 | next_hidden = (1 - inputgate) * newgate + inputgate * hidden 167 | 168 | output = next_hidden 169 | 170 | if self.dropout_type == 'zoneout': 171 | next_hidden = zoneout(prev_h=hidden, 172 | next_h=next_hidden, 173 | rate=self.dropout_prob, 174 | training=self.training) 175 | 176 | elif self.dropout_type == 'dropout': 177 | next_hidden = F.dropout(next_hidden, 178 | self.dropout_prob, 179 | self.training) 180 | 181 | return output, next_hidden -------------------------------------------------------------------------------- /models/DO_Net.py: -------------------------------------------------------------------------------- 1 | from torch_geometric import nn as gnn 2 | from torch import nn 3 | import torch 4 | import sys 5 | import os 6 | sys.path.insert(0, os.path.abspath('..')) 7 | 8 | from models.GGRUCell import GGRUCell 9 | 10 | class DONet(torch.nn.Module): 11 | 12 | def __init__(self, cfg, logger): 13 | super(DONet, self).__init__() 14 | self.logger = logger 15 | self.cfg = cfg 16 | 17 | self.num_nodes = cfg['model']['num_nodes'] 18 | self.num_output_dim = cfg['model']['output_dim'] 19 | self.num_units = cfg['model']['rnn_units'] 20 | self.num_finished_input_dim = cfg['model']['input_dim'] 21 | self.num_rnn_layers = cfg['model']['num_rnn_layers'] 22 | self.seq_len = cfg['model']['seq_len'] 23 | self.horizon = cfg['model']['horizon'] 24 | self.num_relations = cfg['model'].get('num_relations', 1) 25 | self.K = cfg['model'].get('K', 2) 26 | self.num_bases = cfg['model'].get('num_bases', 1) 27 | 28 | self.use_curriculum_learning = self.cfg['model'][ 29 | 'use_curriculum_learning'] 30 | self.cl_decay_steps = torch.FloatTensor( 31 | data=[self.cfg['model']['cl_decay_steps']]) 32 | self.dropout_type = cfg['model'].get('dropout_type', None) 33 | self.dropout_prob = cfg['model'].get('dropout_prob', 0.0) 34 | self.use_input = cfg['model'].get('use_input', True) 35 | 36 | self.global_fusion = cfg['model'].get('global_fusion', False) 37 | 38 | self.encoder_first_cells = GGRUCell(self.num_finished_input_dim, 39 | self.num_units, 40 | self.dropout_type, 41 | self.dropout_prob, 42 | self.num_relations, 43 | num_bases=self.num_bases, 44 | K=self.K, 45 | num_nodes=self.num_nodes, 46 | global_fusion=self.global_fusion) 47 | self.encoder_second_cells = nn.ModuleList([GGRUCell(self.num_units, 48 | self.num_units, 49 | self.dropout_type, 50 | self.dropout_prob, 51 | self.num_relations, 52 | num_bases=self.num_bases, 53 | K=self.K, 54 | num_nodes=self.num_nodes, 55 | global_fusion=self.global_fusion) 56 | for _ in range(self.num_rnn_layers - 1)]) 57 | 58 | self.decoder_first_cells = GGRUCell(self.num_finished_input_dim, 59 | self.num_units, 60 | self.dropout_type, 61 | self.dropout_prob, 62 | self.num_relations, 63 | num_bases=self.num_bases, 64 | K=self.K, 65 | num_nodes=self.num_nodes, 66 | global_fusion=self.global_fusion) 67 | 68 | self.decoder_second_cells = nn.ModuleList([GGRUCell(self.num_units, 69 | self.num_units, 70 | self.dropout_type, 71 | self.dropout_prob, 72 | self.num_relations, 73 | self.K, 74 | num_nodes=self.num_nodes, 75 | global_fusion=self.global_fusion) 76 | for _ in range(self.num_rnn_layers - 1)]) 77 | self.output_type = cfg['model'].get('output_type', 'fc') 78 | if self.output_type == 'fc': 79 | self.output_layer = nn.Linear(self.num_units, self.num_output_dim) 80 | 81 | 82 | def encoder_first_layer(self, 83 | batch, 84 | enc_first_hidden, 85 | edge_index, 86 | edge_attr=None): 87 | enc_first_out, enc_first_hidden = self.encoder_first_cells(inputs=batch.x_do, 88 | edge_index=edge_index, 89 | edge_attr=edge_attr, 90 | hidden=enc_first_hidden) 91 | return enc_first_out, enc_first_hidden 92 | 93 | def encoder_second_layer(self, 94 | index, 95 | encoder_first_out, 96 | enc_second_hidden, 97 | edge_index, 98 | edge_attr): 99 | enc_second_out, enc_second_hidden = self.encoder_second_cells[index](inputs=encoder_first_out, 100 | hidden=enc_second_hidden, 101 | edge_index=edge_index, 102 | edge_attr=edge_attr) 103 | return enc_second_out, enc_second_hidden 104 | 105 | def decoder_first_layer(self, 106 | decoder_input, 107 | dec_first_hidden, 108 | edge_index, 109 | edge_attr=None): 110 | dec_first_out, dec_first_hidden = self.decoder_first_cells(inputs=decoder_input, 111 | hidden=dec_first_hidden, 112 | edge_index=edge_index, 113 | edge_attr=edge_attr) 114 | 115 | return dec_first_out, dec_first_hidden 116 | 117 | def decoder_second_layer(self, 118 | index, 119 | decoder_first_out, 120 | dec_second_hidden, 121 | edge_index, 122 | edge_attr=None): 123 | dec_second_out, dec_second_hidden = self.decoder_second_cells[index](inputs=decoder_first_out, 124 | hidden=dec_second_hidden, 125 | edge_index=edge_index, 126 | edge_attr=edge_attr) 127 | return dec_second_out, dec_second_hidden 128 | -------------------------------------------------------------------------------- /SOTA/DCRNN/model/dcrnn_cell.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | 4 | from lib import utils_HIAM 5 | 6 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 7 | 8 | 9 | class LayerParams: 10 | def __init__(self, rnn_network: torch.nn.Module, layer_type: str): 11 | self._rnn_network = rnn_network 12 | self._params_dict = {} 13 | self._biases_dict = {} 14 | self._type = layer_type 15 | 16 | def get_weights(self, shape): 17 | if shape not in self._params_dict: 18 | nn_param = torch.nn.Parameter(torch.empty(*shape, device=device)) 19 | torch.nn.init.xavier_normal_(nn_param) 20 | self._params_dict[shape] = nn_param 21 | self._rnn_network.register_parameter('{}_weight_{}'.format(self._type, str(shape)), 22 | nn_param) 23 | return self._params_dict[shape] 24 | 25 | def get_biases(self, length, bias_start=0.0): 26 | if length not in self._biases_dict: 27 | biases = torch.nn.Parameter(torch.empty(length, device=device)) 28 | torch.nn.init.constant_(biases, bias_start) 29 | self._biases_dict[length] = biases 30 | self._rnn_network.register_parameter('{}_biases_{}'.format(self._type, str(length)), 31 | biases) 32 | 33 | return self._biases_dict[length] 34 | 35 | 36 | class DCGRUCell(torch.nn.Module): 37 | def __init__(self, num_units, adj_mx, max_diffusion_step, num_nodes, nonlinearity='tanh', 38 | filter_type="laplacian", use_gc_for_ru=True): 39 | """ 40 | 41 | :param num_units: 42 | :param adj_mx: 43 | :param max_diffusion_step: 44 | :param num_nodes: 45 | :param nonlinearity: 46 | :param filter_type: "laplacian", "random_walk", "dual_random_walk". 47 | :param use_gc_for_ru: whether to use Graph convolution to calculate the reset and update gates. 48 | """ 49 | 50 | super().__init__() 51 | self._activation = torch.tanh if nonlinearity == 'tanh' else torch.relu 52 | # support other nonlinearities up here? 53 | self._num_nodes = num_nodes 54 | self._num_units = num_units 55 | self._max_diffusion_step = max_diffusion_step 56 | self._supports = [] 57 | self._use_gc_for_ru = use_gc_for_ru 58 | supports = [] 59 | if filter_type == "laplacian": 60 | supports.append(utils_HIAM.calculate_scaled_laplacian(adj_mx, lambda_max=None)) 61 | elif filter_type == "random_walk": 62 | supports.append(utils_HIAM.calculate_random_walk_matrix(adj_mx).T) 63 | elif filter_type == "dual_random_walk": 64 | supports.append(utils_HIAM.calculate_random_walk_matrix(adj_mx).T) 65 | supports.append(utils_HIAM.calculate_random_walk_matrix(adj_mx.T).T) 66 | else: 67 | supports.append(utils_HIAM.calculate_scaled_laplacian(adj_mx)) 68 | for support in supports: 69 | self._supports.append(self._build_sparse_matrix(support)) 70 | 71 | self._fc_params = LayerParams(self, 'fc') 72 | self._gconv_params = LayerParams(self, 'gconv') 73 | 74 | @staticmethod 75 | def _build_sparse_matrix(L): 76 | L = L.tocoo() 77 | indices = np.column_stack((L.row, L.col)) 78 | # this is to ensure row-major ordering to equal torch.sparse.sparse_reorder(L) 79 | indices = indices[np.lexsort((indices[:, 0], indices[:, 1]))] 80 | L = torch.sparse_coo_tensor(indices.T, L.data, L.shape, device=device) 81 | return L 82 | 83 | def forward(self, inputs, hx): 84 | """Gated recurrent unit (GRU) with Graph Convolution. 85 | :param inputs: (B, num_nodes * input_dim) 86 | :param hx: (B, num_nodes * rnn_units) 87 | 88 | :return 89 | - Output: A `2-D` tensor with shape `(B, num_nodes * rnn_units)`. 90 | """ 91 | output_size = 2 * self._num_units 92 | if self._use_gc_for_ru: 93 | fn = self._gconv 94 | else: 95 | fn = self._fc 96 | value = torch.sigmoid(fn(inputs, hx, output_size, bias_start=1.0)) 97 | value = torch.reshape(value, (-1, self._num_nodes, output_size)) 98 | r, u = torch.split(tensor=value, split_size_or_sections=self._num_units, dim=-1) 99 | r = torch.reshape(r, (-1, self._num_nodes * self._num_units)) 100 | u = torch.reshape(u, (-1, self._num_nodes * self._num_units)) 101 | 102 | c = self._gconv(inputs, r * hx, self._num_units) 103 | if self._activation is not None: 104 | c = self._activation(c) 105 | 106 | new_state = u * hx + (1.0 - u) * c 107 | return new_state 108 | 109 | @staticmethod 110 | def _concat(x, x_): 111 | x_ = x_.unsqueeze(0) 112 | return torch.cat([x, x_], dim=0) 113 | 114 | def _fc(self, inputs, state, output_size, bias_start=0.0): 115 | batch_size = inputs.shape[0] 116 | inputs = torch.reshape(inputs, (batch_size * self._num_nodes, -1)) 117 | state = torch.reshape(state, (batch_size * self._num_nodes, -1)) 118 | inputs_and_state = torch.cat([inputs, state], dim=-1) 119 | input_size = inputs_and_state.shape[-1] 120 | weights = self._fc_params.get_weights((input_size, output_size)) 121 | value = torch.sigmoid(torch.matmul(inputs_and_state, weights)) 122 | biases = self._fc_params.get_biases(output_size, bias_start) 123 | value += biases 124 | return value 125 | 126 | def _gconv(self, inputs, state, output_size, bias_start=0.0): 127 | # Reshape input and state to (batch_size, num_nodes, input_dim/state_dim) 128 | batch_size = inputs.shape[0] 129 | inputs = torch.reshape(inputs, (batch_size, self._num_nodes, -1)) 130 | state = torch.reshape(state, (batch_size, self._num_nodes, -1)) 131 | inputs_and_state = torch.cat([inputs, state], dim=2) 132 | input_size = inputs_and_state.size(2) 133 | 134 | x = inputs_and_state 135 | x0 = x.permute(1, 2, 0) # (num_nodes, total_arg_size, batch_size) 136 | x0 = torch.reshape(x0, shape=[self._num_nodes, input_size * batch_size]) 137 | x = torch.unsqueeze(x0, 0) 138 | 139 | if self._max_diffusion_step == 0: 140 | pass 141 | else: 142 | for support in self._supports: 143 | x1 = torch.sparse.mm(support, x0) 144 | x = self._concat(x, x1) 145 | 146 | for k in range(2, self._max_diffusion_step + 1): 147 | x2 = 2 * torch.sparse.mm(support, x1) - x0 148 | x = self._concat(x, x2) 149 | x1, x0 = x2, x1 150 | 151 | num_matrices = len(self._supports) * self._max_diffusion_step + 1 # Adds for x itself. 152 | x = torch.reshape(x, shape=[num_matrices, self._num_nodes, input_size, batch_size]) 153 | x = x.permute(3, 1, 2, 0) # (batch_size, num_nodes, input_size, order) 154 | x = torch.reshape(x, shape=[batch_size * self._num_nodes, input_size * num_matrices]) 155 | 156 | weights = self._gconv_params.get_weights((input_size * num_matrices, output_size)) 157 | x = torch.matmul(x, weights) # (batch_size * self._num_nodes, output_size) 158 | 159 | biases = self._gconv_params.get_biases(output_size, bias_start) 160 | x += biases 161 | # Reshape res back to 2D: (batch_size, num_node, state_dim) -> (batch_size, num_node * state_dim) 162 | return torch.reshape(x, [batch_size, self._num_nodes * output_size]) 163 | -------------------------------------------------------------------------------- /SOTA/DCRNN/model/dcrnn_model.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import torch.nn as nn 4 | 5 | from .dcrnn_cell import DCGRUCell 6 | 7 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 8 | 9 | 10 | def count_parameters(model): 11 | return sum(p.numel() for p in model.parameters() if p.requires_grad) 12 | 13 | 14 | class Seq2SeqAttrs: 15 | def __init__(self, adj_mx, **model_kwargs): 16 | self.adj_mx = adj_mx 17 | self.max_diffusion_step = int(model_kwargs.get('max_diffusion_step', 2)) 18 | self.cl_decay_steps = int(model_kwargs.get('cl_decay_steps', 1000)) 19 | self.filter_type = model_kwargs.get('filter_type', 'laplacian') 20 | self.num_nodes = int(model_kwargs.get('num_nodes', 1)) 21 | self.num_rnn_layers = int(model_kwargs.get('num_rnn_layers', 1)) 22 | self.rnn_units = int(model_kwargs.get('rnn_units')) 23 | self.hidden_state_size = self.num_nodes * self.rnn_units 24 | 25 | 26 | class EncoderModel(nn.Module, Seq2SeqAttrs): 27 | def __init__(self, adj_mx, **model_kwargs): 28 | nn.Module.__init__(self) 29 | Seq2SeqAttrs.__init__(self, adj_mx, **model_kwargs) 30 | self.input_dim = int(model_kwargs.get('input_dim', 1)) 31 | self.seq_len = int(model_kwargs.get('seq_len')) # for the encoder 32 | self.dcgru_layers = nn.ModuleList( 33 | [DCGRUCell(self.rnn_units, adj_mx, self.max_diffusion_step, self.num_nodes, 34 | filter_type=self.filter_type) for _ in range(self.num_rnn_layers)]) 35 | 36 | def forward(self, inputs, hidden_state=None): 37 | """ 38 | Encoder forward pass. 39 | 40 | :param inputs: shape (batch_size, self.num_nodes * self.input_dim) 41 | :param hidden_state: (num_layers, batch_size, self.hidden_state_size) 42 | optional, zeros if not provided 43 | :return: output: # shape (batch_size, self.hidden_state_size) 44 | hidden_state # shape (num_layers, batch_size, self.hidden_state_size) 45 | (lower indices mean lower layers) 46 | """ 47 | batch_size, _ = inputs.size() 48 | if hidden_state is None: 49 | hidden_state = torch.zeros((self.num_rnn_layers, batch_size, self.hidden_state_size), 50 | device=device) 51 | hidden_states = [] 52 | output = inputs 53 | for layer_num, dcgru_layer in enumerate(self.dcgru_layers): 54 | next_hidden_state = dcgru_layer(output, hidden_state[layer_num]) 55 | hidden_states.append(next_hidden_state) 56 | output = next_hidden_state 57 | 58 | return output, torch.stack(hidden_states) # runs in O(num_layers) so not too slow 59 | 60 | 61 | class DecoderModel(nn.Module, Seq2SeqAttrs): 62 | def __init__(self, adj_mx, **model_kwargs): 63 | # super().__init__(is_training, adj_mx, **model_kwargs) 64 | nn.Module.__init__(self) 65 | Seq2SeqAttrs.__init__(self, adj_mx, **model_kwargs) 66 | self.output_dim = int(model_kwargs.get('output_dim', 1)) 67 | self.horizon = int(model_kwargs.get('horizon', 1)) # for the decoder 68 | self.projection_layer = nn.Linear(self.rnn_units, self.output_dim) 69 | self.dcgru_layers = nn.ModuleList( 70 | [DCGRUCell(self.rnn_units, adj_mx, self.max_diffusion_step, self.num_nodes, 71 | filter_type=self.filter_type) for _ in range(self.num_rnn_layers)]) 72 | 73 | def forward(self, inputs, hidden_state=None): 74 | """ 75 | Decoder forward pass. 76 | 77 | :param inputs: shape (batch_size, self.num_nodes * self.output_dim) 78 | :param hidden_state: (num_layers, batch_size, self.hidden_state_size) 79 | optional, zeros if not provided 80 | :return: output: # shape (batch_size, self.num_nodes * self.output_dim) 81 | hidden_state # shape (num_layers, batch_size, self.hidden_state_size) 82 | (lower indices mean lower layers) 83 | """ 84 | hidden_states = [] 85 | output = inputs 86 | for layer_num, dcgru_layer in enumerate(self.dcgru_layers): 87 | next_hidden_state = dcgru_layer(output, hidden_state[layer_num]) 88 | hidden_states.append(next_hidden_state) 89 | output = next_hidden_state 90 | 91 | projected = self.projection_layer(output.view(-1, self.rnn_units)) 92 | output = projected.view(-1, self.num_nodes * self.output_dim) 93 | 94 | return output, torch.stack(hidden_states) 95 | 96 | 97 | class DCRNNModel(nn.Module, Seq2SeqAttrs): 98 | def __init__(self, adj_mx, logger, **model_kwargs): 99 | super().__init__() 100 | Seq2SeqAttrs.__init__(self, adj_mx, **model_kwargs) 101 | self.encoder_model = EncoderModel(adj_mx, **model_kwargs) 102 | self.decoder_model = DecoderModel(adj_mx, **model_kwargs) 103 | self.cl_decay_steps = int(model_kwargs.get('cl_decay_steps', 1000)) 104 | self.use_curriculum_learning = bool(model_kwargs.get('use_curriculum_learning', False)) 105 | self._logger = logger 106 | 107 | def _compute_sampling_threshold(self, batches_seen): 108 | return self.cl_decay_steps / ( 109 | self.cl_decay_steps + np.exp(batches_seen / self.cl_decay_steps)) 110 | 111 | def encoder(self, inputs): 112 | """ 113 | encoder forward pass on t time steps 114 | :param inputs: shape (seq_len, batch_size, num_sensor * input_dim) 115 | :return: encoder_hidden_state: (num_layers, batch_size, self.hidden_state_size) 116 | """ 117 | encoder_hidden_state = None 118 | for t in range(self.encoder_model.seq_len): 119 | _, encoder_hidden_state = self.encoder_model(inputs[t], encoder_hidden_state) 120 | 121 | return encoder_hidden_state 122 | 123 | def decoder(self, encoder_hidden_state, labels=None, batches_seen=None): 124 | """ 125 | Decoder forward pass 126 | :param encoder_hidden_state: (num_layers, batch_size, self.hidden_state_size) 127 | :param labels: (self.horizon, batch_size, self.num_nodes * self.output_dim) [optional, not exist for inference] 128 | :param batches_seen: global step [optional, not exist for inference] 129 | :return: output: (self.horizon, batch_size, self.num_nodes * self.output_dim) 130 | """ 131 | batch_size = encoder_hidden_state.size(1) 132 | go_symbol = torch.zeros((batch_size, self.num_nodes * self.decoder_model.output_dim), 133 | device=device) 134 | decoder_hidden_state = encoder_hidden_state 135 | decoder_input = go_symbol 136 | 137 | outputs = [] 138 | 139 | for t in range(self.decoder_model.horizon): 140 | decoder_output, decoder_hidden_state = self.decoder_model(decoder_input, 141 | decoder_hidden_state) 142 | decoder_input = decoder_output 143 | outputs.append(decoder_output) 144 | if self.training and self.use_curriculum_learning: 145 | c = np.random.uniform(0, 1) 146 | if c < self._compute_sampling_threshold(batches_seen): 147 | decoder_input = labels[t] 148 | outputs = torch.stack(outputs) 149 | return outputs 150 | 151 | def forward(self, inputs, labels=None, batches_seen=None): 152 | """ 153 | seq2seq forward pass 154 | :param inputs: shape (seq_len, batch_size, num_sensor * input_dim) 155 | :param labels: shape (horizon, batch_size, num_sensor * output) 156 | :param batches_seen: batches seen till now 157 | :return: output: (self.horizon, batch_size, self.num_nodes * self.output_dim) 158 | """ 159 | encoder_hidden_state = self.encoder(inputs) 160 | self._logger.debug("Encoder complete, starting decoder") 161 | outputs = self.decoder(encoder_hidden_state, labels, batches_seen=batches_seen) 162 | self._logger.debug("Decoder complete") 163 | if batches_seen == 0: 164 | self._logger.info( 165 | "Total trainable parameters {}".format(count_parameters(self)) 166 | ) 167 | return outputs 168 | -------------------------------------------------------------------------------- /SOTA/DCRNN/lib/utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import numpy as np 3 | import os 4 | import pickle 5 | import scipy.sparse as sp 6 | import sys 7 | # import tensorflow as tf 8 | 9 | from scipy.sparse import linalg 10 | 11 | 12 | class DataLoader(object): 13 | def __init__(self, xs, ys, batch_size, pad_with_last_sample=True, shuffle=False): 14 | """ 15 | 16 | :param xs: 17 | :param ys: 18 | :param batch_size: 19 | :param pad_with_last_sample: pad with the last sample to make number of samples divisible to batch_size. 20 | """ 21 | self.batch_size = batch_size 22 | self.current_ind = 0 23 | if pad_with_last_sample: 24 | num_padding = (batch_size - (len(xs) % batch_size)) % batch_size 25 | x_padding = np.repeat(xs[-1:], num_padding, axis=0) 26 | y_padding = np.repeat(ys[-1:], num_padding, axis=0) 27 | xs = np.concatenate([xs, x_padding], axis=0) 28 | ys = np.concatenate([ys, y_padding], axis=0) 29 | self.size = len(xs) 30 | self.num_batch = int(self.size // self.batch_size) 31 | if shuffle: 32 | permutation = np.random.permutation(self.size) 33 | xs, ys = xs[permutation], ys[permutation] 34 | self.xs = xs 35 | self.ys = ys 36 | 37 | def get_iterator(self): 38 | self.current_ind = 0 39 | 40 | def _wrapper(): 41 | while self.current_ind < self.num_batch: 42 | start_ind = self.batch_size * self.current_ind 43 | end_ind = min(self.size, self.batch_size * (self.current_ind + 1)) 44 | x_i = self.xs[start_ind: end_ind, ...] 45 | y_i = self.ys[start_ind: end_ind, ...] 46 | yield (x_i, y_i) 47 | self.current_ind += 1 48 | 49 | return _wrapper() 50 | 51 | 52 | class StandardScaler: 53 | """ 54 | Standard the input 55 | """ 56 | 57 | def __init__(self, mean, std): 58 | self.mean = mean 59 | self.std = std 60 | 61 | def transform(self, data): 62 | return (data - self.mean) / self.std * 1.0 63 | 64 | def inverse_transform(self, data): 65 | return (data * self.std) + self.mean 66 | 67 | def calculate_normalized_laplacian(adj): 68 | """ 69 | # L = D^-1/2 (D-A) D^-1/2 = I - D^-1/2 A D^-1/2 70 | # D = diag(A 1) 71 | :param adj: 72 | :return: 73 | """ 74 | adj = sp.coo_matrix(adj) 75 | d = np.array(adj.sum(1)) 76 | d_inv_sqrt = np.power(d, -0.5).flatten() 77 | d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0. 78 | d_mat_inv_sqrt = sp.diags(d_inv_sqrt) 79 | normalized_laplacian = sp.eye(adj.shape[0]) - adj.dot(d_mat_inv_sqrt).transpose().dot(d_mat_inv_sqrt).tocoo() 80 | return normalized_laplacian 81 | 82 | 83 | def calculate_random_walk_matrix(adj_mx): 84 | adj_mx = sp.coo_matrix(adj_mx) 85 | d = np.array(adj_mx.sum(1)) 86 | d_inv = np.power(d, -1).flatten() 87 | d_inv[np.isinf(d_inv)] = 0. 88 | d_mat_inv = sp.diags(d_inv) 89 | random_walk_mx = d_mat_inv.dot(adj_mx).tocoo() 90 | return random_walk_mx 91 | 92 | 93 | def calculate_reverse_random_walk_matrix(adj_mx): 94 | return calculate_random_walk_matrix(np.transpose(adj_mx)) 95 | 96 | 97 | def calculate_scaled_laplacian(adj_mx, lambda_max=2, undirected=True): 98 | if undirected: 99 | adj_mx = np.maximum.reduce([adj_mx, adj_mx.T]) 100 | L = calculate_normalized_laplacian(adj_mx) 101 | if lambda_max is None: 102 | lambda_max, _ = linalg.eigsh(L, 1, which='LM') 103 | lambda_max = lambda_max[0] 104 | L = sp.csr_matrix(L) 105 | M, _ = L.shape 106 | I = sp.identity(M, format='csr', dtype=L.dtype) 107 | L = (2 / lambda_max * L) - I 108 | return L.astype(np.float32) 109 | 110 | 111 | def config_logging(log_dir, log_filename='info.log', level=logging.INFO): 112 | # Add file handler and stdout handler 113 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 114 | # Create the log directory if necessary. 115 | try: 116 | os.makedirs(log_dir) 117 | except OSError: 118 | pass 119 | file_handler = logging.FileHandler(os.path.join(log_dir, log_filename)) 120 | file_handler.setFormatter(formatter) 121 | file_handler.setLevel(level=level) 122 | # Add console handler. 123 | console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 124 | console_handler = logging.StreamHandler(sys.stdout) 125 | console_handler.setFormatter(console_formatter) 126 | console_handler.setLevel(level=level) 127 | logging.basicConfig(handlers=[file_handler, console_handler], level=level) 128 | 129 | 130 | def get_logger(log_dir, name, log_filename='info.log', level=logging.INFO): 131 | logger = logging.getLogger(name) 132 | logger.setLevel(level) 133 | # Add file handler and stdout handler 134 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 135 | file_handler = logging.FileHandler(os.path.join(log_dir, log_filename)) 136 | file_handler.setFormatter(formatter) 137 | # Add console handler. 138 | console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 139 | console_handler = logging.StreamHandler(sys.stdout) 140 | console_handler.setFormatter(console_formatter) 141 | logger.addHandler(file_handler) 142 | logger.addHandler(console_handler) 143 | # Add google cloud log handler 144 | logger.info('Log directory: %s', log_dir) 145 | return logger 146 | 147 | 148 | def get_total_trainable_parameter_size(): 149 | """ 150 | Calculates the total number of trainable parameters in the current graph. 151 | :return: 152 | """ 153 | total_parameters = 0 154 | for variable in tf.trainable_variables(): 155 | # shape is an array of tf.Dimension 156 | total_parameters += np.product([x.value for x in variable.get_shape()]) 157 | return total_parameters 158 | 159 | # return data 160 | def load_dataset(dataset_dir, 161 | batch_size, 162 | test_batch_size=None, 163 | ds_type = "", 164 | scaler_axis=(0, 165 | 1, 166 | 2, 167 | 3), 168 | **kwargs): 169 | data = {} 170 | if ds_type == "od": 171 | for category in ['train', 'val', 'test']: 172 | cat_data = load_pickle(os.path.join(dataset_dir, category + '.pkl')) 173 | data['x_' + category] = cat_data['finished'] 174 | data['y_' + category] = cat_data['y'] 175 | elif ds_type == "do": 176 | for category in ['train', 'val', 'test']: 177 | cat_data = load_pickle(os.path.join(dataset_dir, category + '_do.pkl')) 178 | data['x_' + category] = cat_data['do_x'] / 1.0 179 | data['y_' + category] = cat_data['do_y'] / 1.0 180 | 181 | scaler = StandardScaler(mean=data['x_train'].mean(axis=scaler_axis), 182 | std=data['x_train'].std(axis=scaler_axis)) 183 | # Data format 184 | for category in ['train', 'val', 'test']: 185 | data['x_' + category] = scaler.transform(data['x_' + category]) 186 | data['y_' + category] = scaler.transform(data['y_' + category]) 187 | data['train_loader'] = DataLoader(data['x_train'], 188 | data['y_train'], 189 | batch_size, 190 | shuffle=True) 191 | data['val_loader'] = DataLoader(data['x_val'], 192 | data['y_val'], 193 | test_batch_size, 194 | shuffle=False) 195 | data['test_loader'] = DataLoader(data['x_test'], 196 | data['y_test'], 197 | test_batch_size, 198 | shuffle=False) 199 | data['scaler'] = scaler 200 | return data 201 | 202 | def load_graph_data(pkl_filename): 203 | sensor_ids, sensor_id_to_ind, adj_mx = load_pickle(pkl_filename) 204 | return sensor_ids, sensor_id_to_ind, adj_mx 205 | 206 | 207 | def load_pickle(pickle_file): 208 | try: 209 | with open(pickle_file, 'rb') as f: 210 | pickle_data = pickle.load(f) 211 | except UnicodeDecodeError as e: 212 | with open(pickle_file, 'rb') as f: 213 | pickle_data = pickle.load(f, encoding='latin1') 214 | except Exception as e: 215 | print('Unable to load data ', pickle_file, ':', e) 216 | raise 217 | return pickle_data 218 | -------------------------------------------------------------------------------- /SOTA/Graph-WaveNet-master/util1.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import numpy as np 3 | import os 4 | import scipy.sparse as sp 5 | import torch 6 | from scipy.sparse import linalg 7 | import logging 8 | import sys 9 | 10 | 11 | class DataLoader(object): 12 | def __init__(self, xs, ys, batch_size, pad_with_last_sample=True): 13 | """ 14 | :param xs: 15 | :param ys: 16 | :param batch_size: 17 | :param pad_with_last_sample: pad with the last sample to make number of samples divisible to batch_size. 18 | """ 19 | self.batch_size = batch_size 20 | self.current_ind = 0 21 | if pad_with_last_sample: 22 | num_padding = (batch_size - (len(xs) % batch_size)) % batch_size 23 | x_padding = np.repeat(xs[-1:], num_padding, axis=0) 24 | y_padding = np.repeat(ys[-1:], num_padding, axis=0) 25 | xs = np.concatenate([xs, x_padding], axis=0) 26 | ys = np.concatenate([ys, y_padding], axis=0) 27 | self.size = len(xs) 28 | self.num_batch = int(self.size // self.batch_size) 29 | self.xs = xs 30 | self.ys = ys 31 | 32 | def shuffle(self): 33 | permutation = np.random.permutation(self.size) 34 | xs, ys = self.xs[permutation], self.ys[permutation] 35 | self.xs = xs 36 | self.ys = ys 37 | 38 | def get_iterator(self): 39 | self.current_ind = 0 40 | 41 | def _wrapper(): 42 | while self.current_ind < self.num_batch: 43 | start_ind = self.batch_size * self.current_ind 44 | end_ind = min(self.size, self.batch_size * (self.current_ind + 1)) 45 | x_i = self.xs[start_ind: end_ind, ...] 46 | y_i = self.ys[start_ind: end_ind, ...] 47 | yield (x_i, y_i) 48 | self.current_ind += 1 49 | 50 | return _wrapper() 51 | 52 | 53 | class StandardScaler(): 54 | """ 55 | Standard the input 56 | """ 57 | 58 | def __init__(self, mean, std): 59 | self.mean = mean 60 | self.std = std 61 | 62 | def transform(self, data): 63 | return (data - self.mean) / self.std 64 | 65 | def inverse_transform(self, data): 66 | return (data * self.std) + self.mean 67 | 68 | 69 | def sym_adj(adj): 70 | """Symmetrically normalize adjacency matrix.""" 71 | adj = sp.coo_matrix(adj) 72 | rowsum = np.array(adj.sum(1)) 73 | d_inv_sqrt = np.power(rowsum, -0.5).flatten() 74 | d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0. 75 | d_mat_inv_sqrt = sp.diags(d_inv_sqrt) 76 | return adj.dot(d_mat_inv_sqrt).transpose().dot(d_mat_inv_sqrt).astype(np.float32).todense() 77 | 78 | 79 | def asym_adj(adj): 80 | adj = adj.astype(float) 81 | adj = sp.coo_matrix(adj) 82 | rowsum = np.array(adj.sum(1)).flatten() 83 | d_inv = np.power(rowsum, -1).flatten() 84 | d_inv[np.isinf(d_inv)] = 0. 85 | d_mat= sp.diags(d_inv) 86 | return d_mat.dot(adj).astype(np.float32).todense() 87 | 88 | 89 | def calculate_normalized_laplacian(adj): 90 | """ 91 | # L = D^-1/2 (D-A) D^-1/2 = I - D^-1/2 A D^-1/2 92 | # D = diag(A 1) 93 | :param adj: 94 | :return: 95 | """ 96 | adj = sp.coo_matrix(adj) 97 | d = np.array(adj.sum(1)) 98 | d_inv_sqrt = np.power(d, -0.5).flatten() 99 | d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0. 100 | d_mat_inv_sqrt = sp.diags(d_inv_sqrt) 101 | normalized_laplacian = sp.eye(adj.shape[0]) - adj.dot(d_mat_inv_sqrt).transpose().dot(d_mat_inv_sqrt).tocoo() 102 | return normalized_laplacian 103 | 104 | 105 | def calculate_scaled_laplacian(adj_mx, lambda_max=2, undirected=True): 106 | if undirected: 107 | adj_mx = np.maximum.reduce([adj_mx, adj_mx.T]) 108 | L = calculate_normalized_laplacian(adj_mx) 109 | if lambda_max is None: 110 | lambda_max, _ = linalg.eigsh(L, 1, which='LM') 111 | lambda_max = lambda_max[0] 112 | L = sp.csr_matrix(L) 113 | M, _ = L.shape 114 | I = sp.identity(M, format='csr', dtype=L.dtype) 115 | L = (2 / lambda_max * L) - I 116 | return L.astype(np.float32).todense() 117 | 118 | 119 | def load_pickle(pickle_file): 120 | try: 121 | with open(pickle_file, 'rb') as f: 122 | pickle_data = pickle.load(f) 123 | except UnicodeDecodeError as e: 124 | with open(pickle_file, 'rb') as f: 125 | pickle_data = pickle.load(f, encoding='latin1') 126 | except Exception as e: 127 | print('Unable to load data ', pickle_file, ':', e) 128 | raise 129 | return pickle_data 130 | 131 | 132 | def load_adj(pkl_filename, adjtype): 133 | # sensor_ids, sensor_id_to_ind, adj_mx = load_pickle(pkl_filename) 134 | adj_mx = load_pickle(pkl_filename) 135 | if adjtype == "scalap": 136 | adj = [calculate_scaled_laplacian(adj_mx)] 137 | elif adjtype == "normlap": 138 | adj = [calculate_normalized_laplacian(adj_mx).astype(np.float32).todense()] 139 | elif adjtype == "symnadj": 140 | adj = [sym_adj(adj_mx)] 141 | elif adjtype == "transition": 142 | adj = [asym_adj(adj_mx)] 143 | elif adjtype == "doubletransition": 144 | adj = [asym_adj(adj_mx), asym_adj(np.transpose(adj_mx))] 145 | elif adjtype == "identity": 146 | adj = [np.diag(np.ones(adj_mx.shape[0])).astype(np.float32)] 147 | else: 148 | error = 0 149 | assert error, "adj type not defined" 150 | # return sensor_ids, sensor_id_to_ind, adj 151 | return adj 152 | 153 | 154 | def load_dataset(dataset_dir, batch_size, valid_batch_size= None, test_batch_size=None): 155 | data = {} 156 | for category in ['train', 'val', 'test']: 157 | # cat_data = np.load(os.path.join(dataset_dir, category + '.npz')) 158 | # data['x_' + category] = cat_data['x'] 159 | # data['y_' + category] = cat_data['y'] 160 | cat_data = load_pickle(os.path.join(dataset_dir, category + '.pkl')) 161 | data['x_' + category] = cat_data['finished'] / 1.0 162 | data['y_' + category] = cat_data['y'] / 1.0 163 | scaler = StandardScaler(mean=data['x_train'][..., 0].mean(), std=data['x_train'][..., 0].std()) 164 | # Data format 165 | for category in ['train', 'val', 'test']: 166 | data['x_' + category][..., 0] = scaler.transform(data['x_' + category][..., 0]) 167 | data['train_loader'] = DataLoader(data['x_train'], data['y_train'], batch_size) 168 | data['val_loader'] = DataLoader(data['x_val'], data['y_val'], valid_batch_size) 169 | data['test_loader'] = DataLoader(data['x_test'], data['y_test'], test_batch_size) 170 | data['scaler'] = scaler 171 | return data 172 | 173 | def load_dataset_do(dataset_dir, batch_size, valid_batch_size= None, test_batch_size=None): 174 | data = {} 175 | for category in ['train', 'val', 'test']: 176 | # cat_data = np.load(os.path.join(dataset_dir, category + '.npz')) 177 | # data['x_' + category] = cat_data['x'] 178 | # data['y_' + category] = cat_data['y'] 179 | cat_data = load_pickle(os.path.join(dataset_dir, category + '_do.pkl')) 180 | data['x_' + category] = cat_data['do_x'] / 1.0 181 | data['y_' + category] = cat_data['do_y'] / 1.0 182 | scaler = StandardScaler(mean=data['x_train'][..., 0].mean(), std=data['x_train'][..., 0].std()) 183 | # Data format 184 | for category in ['train', 'val', 'test']: 185 | data['x_' + category][..., 0] = scaler.transform(data['x_' + category][..., 0]) 186 | data['train_loader'] = DataLoader(data['x_train'], data['y_train'], batch_size) 187 | data['val_loader'] = DataLoader(data['x_val'], data['y_val'], valid_batch_size) 188 | data['test_loader'] = DataLoader(data['x_test'], data['y_test'], test_batch_size) 189 | data['scaler'] = scaler 190 | return data 191 | 192 | def get_logger(log_dir, 193 | name, 194 | log_filename='info.log', 195 | level=logging.INFO, 196 | write_to_file=True): 197 | logger = logging.getLogger(name) 198 | logger.setLevel(level) 199 | logger.propagate = False 200 | # Add file handler and stdout handler 201 | formatter = logging.Formatter( 202 | '%(asctime)s - %(name)s - %(levelname)s - %(message)s') 203 | file_handler = logging.FileHandler(os.path.join(log_dir, log_filename)) 204 | file_handler.setFormatter(formatter) 205 | # Add console handler. 206 | console_formatter = logging.Formatter( 207 | '%(asctime)s - %(levelname)s - %(message)s') 208 | console_handler = logging.StreamHandler(sys.stdout) 209 | console_handler.setFormatter(console_formatter) 210 | if write_to_file is True: 211 | logger.addHandler(file_handler) 212 | logger.addHandler(console_handler) 213 | # Add google cloud log handler 214 | logger.info('Log directory: %s', log_dir) 215 | return logger 216 | 217 | 218 | def setup_tensorboard_writer(base_dir, comment, **kargs): 219 | from torch.utils.tensorboard import SummaryWriter 220 | tf_file_name = '_'.join(k + str(v) for k, v in kargs.items()) 221 | tf_file_path = os.path.join(base_dir, tf_file_name) + comment 222 | return SummaryWriter( 223 | log_dir=tf_file_path 224 | ), tf_file_path 225 | -------------------------------------------------------------------------------- /SOTA/Graph-WaveNet-master/model1.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | from torch.autograd import Variable 5 | import sys 6 | 7 | 8 | class nconv(nn.Module): 9 | def __init__(self): 10 | super(nconv,self).__init__() 11 | 12 | def forward(self,x, A): 13 | x = torch.einsum('ncvl,vw->ncwl',(x,A)) 14 | return x.contiguous() 15 | 16 | class linear(nn.Module): 17 | def __init__(self,c_in,c_out): 18 | super(linear,self).__init__() 19 | self.mlp = torch.nn.Conv2d(c_in, c_out, kernel_size=(1, 1), padding=(0,0), stride=(1,1), bias=True) 20 | 21 | def forward(self,x): 22 | return self.mlp(x) 23 | 24 | class gcn(nn.Module): 25 | def __init__(self,c_in,c_out,dropout,support_len=3,order=2): 26 | super(gcn,self).__init__() 27 | self.nconv = nconv() 28 | c_in = (order*support_len+1)*c_in 29 | self.mlp = linear(c_in,c_out) 30 | self.dropout = dropout 31 | self.order = order 32 | 33 | def forward(self,x,support): 34 | out = [x] 35 | for a in support: 36 | x1 = self.nconv(x,a) 37 | out.append(x1) 38 | for k in range(2, self.order + 1): 39 | x2 = self.nconv(x1,a) 40 | out.append(x2) 41 | x1 = x2 42 | 43 | h = torch.cat(out,dim=1) 44 | h = self.mlp(h) 45 | h = F.dropout(h, self.dropout, training=self.training) 46 | return h 47 | 48 | 49 | class gwnet(nn.Module): 50 | def __init__(self, device, num_nodes, dropout=0.3, supports=None, gcn_bool=True, addaptadj=True, aptinit=None, 51 | in_dim=2,out_dim=12,residual_channels=32,dilation_channels=32,skip_channels=256,end_channels=512, 52 | kernel_size=2,blocks=4,layers=2): 53 | """ 54 | args: 55 | - out_dim: output node feature 56 | - in_dim: input node feature 57 | - skip_channels: hidden node feature 58 | - residual_channels: 59 | - end_channels: 60 | - layers: 61 | - kernel_size: 62 | returns: 63 | - prediction: [bn, feat_dim, node_num, seq] 64 | """ 65 | super(gwnet, self).__init__() 66 | self.device = device 67 | self.dropout = dropout 68 | self.blocks = blocks 69 | self.layers = layers 70 | self.gcn_bool = gcn_bool 71 | self.addaptadj = addaptadj 72 | 73 | self.filter_convs = nn.ModuleList() 74 | self.gate_convs = nn.ModuleList() 75 | self.residual_convs = nn.ModuleList() 76 | self.skip_convs = nn.ModuleList() 77 | self.pads = nn.ModuleList() 78 | self.bn = nn.ModuleList() 79 | self.gconv = nn.ModuleList() 80 | 81 | self.start_conv = nn.Conv2d(in_channels=in_dim, 82 | out_channels=residual_channels, 83 | kernel_size=(1,1)) 84 | self.supports = supports 85 | 86 | receptive_field = 1 87 | 88 | self.supports_len = 0 89 | if supports is not None: 90 | self.supports_len += len(supports) 91 | 92 | if gcn_bool and addaptadj: 93 | if aptinit is None: 94 | if supports is None: 95 | self.supports = [] 96 | self.nodevec1 = nn.Parameter(torch.randn(num_nodes, 10).to(device), requires_grad=True).to(device) 97 | self.nodevec2 = nn.Parameter(torch.randn(10, num_nodes).to(device), requires_grad=True).to(device) 98 | self.supports_len +=1 99 | else: 100 | if supports is None: 101 | self.supports = [] 102 | m, p, n = torch.svd(aptinit) 103 | initemb1 = torch.mm(m[:, :10], torch.diag(p[:10] ** 0.5)) 104 | initemb2 = torch.mm(torch.diag(p[:10] ** 0.5), n[:, :10].t()) 105 | self.nodevec1 = nn.Parameter(initemb1, requires_grad=True).to(device) 106 | self.nodevec2 = nn.Parameter(initemb2, requires_grad=True).to(device) 107 | self.supports_len += 1 108 | 109 | 110 | for b in range(blocks): 111 | additional_scope = kernel_size - 1 112 | new_dilation = 1 113 | for i in range(layers): 114 | # dilated convolutions 115 | pad_size = new_dilation * (kernel_size - 1) 116 | self.pads.append( 117 | nn.ZeroPad2d((pad_size, 0, 0, 0)) 118 | ) 119 | 120 | self.filter_convs.append(nn.Conv2d(in_channels=residual_channels, 121 | out_channels=dilation_channels, 122 | kernel_size=(1,kernel_size),dilation=new_dilation)) 123 | 124 | self.gate_convs.append(nn.Conv1d(in_channels=residual_channels, 125 | out_channels=dilation_channels, 126 | kernel_size=(1, kernel_size), dilation=new_dilation)) 127 | 128 | # 1x1 convolution for residual connection 129 | self.residual_convs.append(nn.Conv1d(in_channels=dilation_channels, 130 | out_channels=residual_channels, 131 | kernel_size=(1, 1))) 132 | 133 | # 1x1 convolution for skip connection 134 | self.skip_convs.append(nn.Conv1d(in_channels=dilation_channels, 135 | out_channels=skip_channels, 136 | kernel_size=(1, 1))) 137 | self.bn.append(nn.BatchNorm2d(residual_channels)) 138 | new_dilation *=2 139 | receptive_field += additional_scope 140 | additional_scope *= 2 141 | if self.gcn_bool: 142 | self.gconv.append(gcn(dilation_channels,residual_channels,dropout,support_len=self.supports_len)) 143 | 144 | 145 | 146 | self.end_conv_1 = nn.Conv2d(in_channels=skip_channels, 147 | out_channels=end_channels, 148 | kernel_size=(1,1), 149 | bias=True) 150 | 151 | self.end_conv_2 = nn.Conv2d(in_channels=end_channels, 152 | out_channels=out_dim, 153 | kernel_size=(1,1), 154 | bias=True) 155 | 156 | self.receptive_field = receptive_field 157 | 158 | 159 | 160 | def forward(self, input): 161 | in_len = input.size(3) 162 | input = input.transpose(1, 3) # [bn, in_dim, node_num, seq] 163 | x = input # x.shape (32,4,80,31) 164 | x = self.start_conv(x) # x.shape 32, 32, 80, 31 165 | skip = 0 166 | 167 | # calculate the current adaptive adj matrix once per iteration 168 | new_supports = None 169 | if self.gcn_bool and self.addaptadj and self.supports is not None: 170 | adp = F.softmax(F.relu(torch.mm(self.nodevec1, self.nodevec2)), dim=1) 171 | new_supports = self.supports + [adp] 172 | 173 | # WaveNet layers 174 | for i in range(self.blocks * self.layers): 175 | 176 | # |----------------------------------------| *residual* 177 | # | | 178 | # | |-- conv -- tanh --| | 179 | # -> dilate -|----| * ----|-- 1x1 -- + --> *input* 180 | # |-- conv -- sigm --| | 181 | # 1x1 182 | # | 183 | # ---------------------------------------> + -------------> *skip* 184 | 185 | #(dilation, init_dilation) = self.dilations[i] 186 | 187 | #residual = dilation_func(x, dilation, init_dilation, i) 188 | residual = x 189 | # dilated convolution 190 | # train with paddings 191 | filter = self.filter_convs[i](self.pads[i](residual)) 192 | filter = torch.tanh(filter) 193 | gate = self.gate_convs[i](self.pads[i](residual)) 194 | gate = torch.sigmoid(gate) 195 | 196 | x = filter * gate 197 | # parametrized skip connection 198 | 199 | s = x 200 | s = self.skip_convs[i](s) 201 | try: 202 | skip = skip[:, :, :, -s.size(3):] 203 | except: 204 | skip = 0 205 | skip = s + skip 206 | 207 | 208 | if self.gcn_bool and self.supports is not None: 209 | if self.addaptadj: 210 | x = self.gconv[i](x, new_supports) 211 | else: 212 | x = self.gconv[i](x,self.supports) 213 | else: 214 | x = self.residual_convs[i](x) 215 | 216 | x = x + residual[:, :, :, -x.size(3):] 217 | 218 | x = self.bn[i](x) 219 | x = F.relu(skip) 220 | # x = x.transpose(1, 3) 221 | x = F.relu(self.end_conv_1(x)) 222 | x = self.end_conv_2(x) 223 | return x.transpose(1, 3) # [bn, seq, node_num, feat] 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /SOTA/Graph-WaveNet-master/train1.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | import argparse 4 | import time 5 | import util1 as util 6 | import metrics 7 | # import matplotlib.pyplot as plt 8 | from engine1 import trainer 9 | import os 10 | 11 | parser = argparse.ArgumentParser() 12 | parser.add_argument('--data',type=str,default='data/hangzhou/OD/OD_26',help='data path') 13 | parser.add_argument('--adjdata',type=str,default='data/hangzhou/graph_hz_conn.pkl',help='adj data path') 14 | parser.add_argument('--adjtype',type=str,default='symnadj',help='adj type') 15 | parser.add_argument('--gcn_bool',action='store_true',help='whether to add graph convolution layer') 16 | parser.add_argument('--aptonly',action='store_true',help='whether only adaptive adj') 17 | parser.add_argument('--addaptadj',action='store_true',help='whether add adaptive adj') 18 | parser.add_argument('--randomadj',action='store_true',help='whether random initialize adaptive adj') 19 | parser.add_argument('--seq_length',type=int,default=4,help='') 20 | parser.add_argument('--nhid',type=int,default=32,help='') 21 | parser.add_argument('--in_dim',type=int,default=26,help='inputs dimension') 22 | parser.add_argument('--num_nodes',type=int,default=80,help='number of nodes') 23 | parser.add_argument('--out_dim',type=int,default=26,help='') 24 | parser.add_argument('--batch_size',type=int,default=32,help='batch size') 25 | parser.add_argument('--learning_rate',type=float,default=0.0005,help='learning rate') 26 | parser.add_argument('--dropout',type=float,default=0.1,help='dropout rate') 27 | parser.add_argument('--weight_decay',type=float,default=1e-5,help='weight decay rate') 28 | parser.add_argument('--epochs',type=int,default=400,help='') 29 | parser.add_argument('--print_every',type=int,default=1,help='') 30 | parser.add_argument('--save',type=str,default='data/checkpoint',help='save path') 31 | parser.add_argument('--expid',type=int,default=1,help='experiment id') 32 | parser.add_argument('--seed',type=int,default=777,help='random seed') 33 | 34 | parser.add_argument('--exp_base',type=str,default="data",help='log_base_dir') 35 | parser.add_argument('--runs',type=str,default="debug",help='log runs name') 36 | 37 | parser.add_argument('--train_type',type=str,default="od",help='od training or do training') 38 | 39 | args = parser.parse_args() 40 | 41 | def evaluate(scaler, dataloader, device, engine, logger,type, cat_list): 42 | results = {} 43 | y_preds = [] 44 | gt = [] 45 | for iter, (x, y) in enumerate(dataloader[type + '_loader'].get_iterator()): 46 | test_x = torch.tensor(x, dtype=torch.float, device=device) 47 | # testx = testx.transpose(1,3) 48 | with torch.no_grad(): 49 | preds = engine.model(test_x) # .transpose(1,3) 50 | # preds = preds.transpose(1,3) 51 | test_y = torch.tensor(y, dtype=torch.float, device=device) 52 | y_preds.append(preds.detach().cpu().numpy()) 53 | gt.append(test_y.detach().cpu().numpy()) 54 | 55 | res = [] 56 | for category in cat_list: 57 | y_preds = np.concatenate(y_preds, axis=0) # concat in batch_size dim. 58 | gt = np.concatenate(gt, axis=0) 59 | mae_list = [] 60 | mape_net_list = [] 61 | rmse_list = [] 62 | mae_sum = 0 63 | 64 | mape_net_sum = 0 65 | rmse_sum = 0 66 | horizon = 4 67 | for horizon_i in range(horizon): 68 | y_pred = scaler.inverse_transform( 69 | y_preds[:, horizon_i, :, :]) 70 | y_pred[y_pred < 0] = 0 71 | y_truth = gt[:, horizon_i, :, :] 72 | mae = metrics.masked_mae_np(y_pred, y_truth) 73 | mape_net = metrics.masked_mape_np(y_pred, y_truth) 74 | rmse = metrics.masked_rmse_np(y_pred, y_truth) 75 | mae_sum += mae 76 | mape_net_sum += mape_net 77 | rmse_sum += rmse 78 | mae_list.append(mae) 79 | 80 | mape_net_list.append(mape_net) 81 | rmse_list.append(rmse) 82 | 83 | msg = "Horizon {:02d}, MAE: {:.2f}, RMSE: {:.2f}, MAPE_net: {:.4f}" 84 | if type=='test': 85 | logger.info(msg.format(horizon_i + 1, mae, rmse, mape_net)) 86 | res.append( 87 | { 88 | "MAE": mae, 89 | "RMSE": rmse, 90 | "MAPE_net": mape_net, 91 | } 92 | ) 93 | results['MAE_' + category] = mae_sum / horizon 94 | results['RMSE_' + category] = rmse_sum / horizon 95 | results['MAPE_net_' + category] = mape_net_sum / horizon 96 | 97 | if type=='test': 98 | logger.info('Evaluation_{}_End:'.format(type)) 99 | return results, res 100 | else: 101 | return results 102 | 103 | def main(): 104 | # set seed 105 | torch.manual_seed(args.seed) 106 | np.random.seed(args.seed) 107 | 108 | tb_writer, log_dir = util.setup_tensorboard_writer(os.path.join(args.exp_base, args.runs), comment="", 109 | epochs=args.epochs, lr = args.learning_rate, bn=args.batch_size, nhid=args.nhid, gcn="t" if args.gcn_bool else 'f') 110 | 111 | # load data 112 | logger = util.get_logger(log_dir, __name__, 'info.log', level='INFO') 113 | args.log_dir = log_dir 114 | # device = torch.device(args.device) 115 | device = torch.device( 116 | 'cuda') if torch.cuda.is_available() else torch.device('cpu') 117 | # sensor_ids, sensor_id_to_ind, adj_mx = util.load_adj(args.adjdata,args.adjtype) 118 | adj_mx = util.load_adj(args.adjdata, args.adjtype) 119 | if args.train_type == "od": 120 | dataloader = util.load_dataset(args.data, args.batch_size, args.batch_size, args.batch_size) 121 | else: 122 | dataloader = util.load_dataset_do(args.data, args.batch_size, args.batch_size, args.batch_size) 123 | scaler = dataloader['scaler'] 124 | supports = [torch.tensor(i).to(device) for i in adj_mx] 125 | 126 | print(args) 127 | 128 | if args.randomadj: 129 | adjinit = None 130 | else: 131 | adjinit = supports[0] 132 | 133 | if args.aptonly: 134 | supports = None 135 | 136 | engine = trainer(scaler, args.in_dim, args.seq_length, args.num_nodes, args.out_dim, args.nhid, 137 | args.dropout, args.learning_rate, args.weight_decay, device, 138 | supports, args.gcn_bool, args.addaptadj,adjinit) 139 | 140 | 141 | print("start training...",flush=True) 142 | his_loss =[] 143 | val_time = [] 144 | train_time = [] 145 | 146 | 147 | best_epoch = 0 148 | best_val_net_mape = 1e6 149 | update = {} 150 | for category in ['od', 'do']: 151 | update['val_steady_count_'+category] = 0 152 | update['last_val_mae_'+category] = 1e6 153 | update['last_val_mape_net_'+category] = 1e6 154 | 155 | global_step = 0 156 | for i in range(1,args.epochs+1): 157 | #if i % 10 == 0: 158 | #lr = max(0.000002,args.learning_rate * (0.1 ** (i // 10))) 159 | #for g in engine.optimizer.param_groups: 160 | #g['lr'] = lr 161 | train_loss = [] 162 | t1 = time.time() 163 | dataloader['train_loader'].shuffle() 164 | for iter, (x, y) in enumerate(dataloader['train_loader'].get_iterator()): 165 | trainx = torch.tensor(x, dtype=torch.float, device=device) 166 | trainy = torch.tensor(y, dtype=torch.float, device=device) 167 | 168 | loss, predicts, gt = engine.train(trainx, trainy) 169 | train_loss.append(loss) 170 | tb_writer.add_scalar('train/loss', loss, global_step) 171 | 172 | global_step += 1 173 | 174 | if i % args.print_every == 0: 175 | logger.info(('Epoch:{}').format(i)) 176 | val_result = evaluate(scaler, dataloader, device, engine, logger, 'val', [args.train_type]) 177 | 178 | test_res, res = evaluate(scaler, dataloader, device, engine, logger, 'test', [args.train_type]) 179 | for k, v in test_res.items(): 180 | tb_writer.add_scalar(f'metric_test/{k}', v, i) 181 | 182 | val_category = [args.train_type] 183 | for k, v in val_result.items(): 184 | tb_writer.add_scalar(f'metric_val/{k}', v, i) 185 | for category in val_category: 186 | logger.info('{}:'.format(category)) 187 | logger.info(('val_mae:{}, val_mape_net:{}').format( 188 | val_result['MAE_' + category], 189 | val_result['MAPE_net_' + category])) 190 | if val_result['MAPE_net_' + category] < best_val_net_mape: 191 | best_val_net_mape = val_result['MAPE_net_' + category] 192 | metrics_strs = ['MAE', 'RMSE', 'MAPE_net'] 193 | best_epoch = i 194 | with open(os.path.join(args.log_dir, 'a_res.csv'), 'w') as f: 195 | f.write(f"{args.log_dir}_{best_epoch}\n") 196 | for met in metrics_strs: 197 | f.write(',\n'.join([str(e[met]) for e in res] + [',\n'])) 198 | torch.save(engine.model.state_dict(), os.path.join(args.log_dir, "best_model.pth")) 199 | if update['last_val_mae_' + category] > val_result['MAE_' + category]: 200 | logger.info('val_mae decreased from {} to {}'.format( 201 | update['last_val_mae_' + category], 202 | val_result['MAE_' + category])) 203 | update['last_val_mae_' + category] = val_result['MAE_' + category] 204 | 205 | if update['last_val_mape_net_' + category] > val_result['MAPE_net_' + category]: 206 | logger.info('val_mape_net decreased from {} to {}'.format( 207 | update['last_val_mape_net_' + category], 208 | val_result['MAPE_net_' + category])) 209 | update['last_val_mape_net_' + category] = val_result['MAPE_net_' + category] 210 | 211 | torch.save(engine.model.state_dict(), os.path.join(args.log_dir, "epoch_"+str(i)+".pth")) 212 | print("Average Training Time: {:.4f} secs/epoch".format(np.mean(train_time))) 213 | print("Average Inference Time: {:.4f} secs".format(np.mean(val_time))) 214 | 215 | print("Training finished") 216 | 217 | 218 | if __name__ == "__main__": 219 | t1 = time.time() 220 | main() 221 | t2 = time.time() 222 | print("Total time spent: {:.4f}".format(t2-t1)) 223 | -------------------------------------------------------------------------------- /models/OD_Net.py: -------------------------------------------------------------------------------- 1 | from torch_geometric import nn as gnn 2 | from torch import nn 3 | from torch.nn import functional as F 4 | from torch.nn import init, Parameter 5 | import torch 6 | import random 7 | import math 8 | import sys 9 | import os 10 | import copy 11 | # from lib.utils_unfinished import softmax 12 | from torch.nn import functional as F 13 | sys.path.insert(0, os.path.abspath('..')) 14 | 15 | from models.GGRUCell import GGRUCell 16 | 17 | class ODNet(torch.nn.Module): 18 | 19 | def __init__(self, cfg, logger): 20 | super(ODNet, self).__init__() 21 | self.logger = logger 22 | self.cfg = cfg 23 | 24 | self.num_nodes = cfg['model']['num_nodes'] 25 | self.num_output_dim = cfg['model']['output_dim'] 26 | self.num_units = cfg['model']['rnn_units'] 27 | self.num_finished_input_dim = cfg['model']['input_dim'] 28 | self.num_unfinished_input_dim = cfg['model']['input_dim'] 29 | self.num_rnn_layers = cfg['model']['num_rnn_layers'] 30 | self.seq_len = cfg['model']['seq_len'] 31 | self.horizon = cfg['model']['horizon'] 32 | self.num_relations = cfg['model'].get('num_relations', 1) 33 | self.K = cfg['model'].get('K', 2) 34 | self.num_bases = cfg['model'].get('num_bases', 1) 35 | 36 | self.dropout_type = cfg['model'].get('dropout_type', None) 37 | self.dropout_prob = cfg['model'].get('dropout_prob', 0.0) 38 | 39 | self.global_fusion = cfg['model'].get('global_fusion', False) 40 | 41 | self.encoder_first_finished_cells = GGRUCell(self.num_finished_input_dim, 42 | self.num_units, 43 | self.dropout_type, 44 | self.dropout_prob, 45 | self.num_relations, 46 | num_bases=self.num_bases, 47 | K=self.K, 48 | num_nodes=self.num_nodes, 49 | global_fusion=self.global_fusion) 50 | self.encoder_first_unfinished_cells = GGRUCell(self.num_unfinished_input_dim, 51 | self.num_units, 52 | self.dropout_type, 53 | self.dropout_prob, 54 | self.num_relations, 55 | num_bases=self.num_bases, 56 | K=self.K, 57 | num_nodes=self.num_nodes, 58 | global_fusion=self.global_fusion) 59 | self.encoder_first_short_his_cells = GGRUCell(self.num_unfinished_input_dim, 60 | self.num_units, 61 | self.dropout_type, 62 | self.dropout_prob, 63 | self.num_relations, 64 | num_bases=self.num_bases, 65 | K=self.K, 66 | num_nodes=self.num_nodes, 67 | global_fusion=self.global_fusion) 68 | 69 | self.unfinished_output_layer = nn.Conv1d(in_channels=self.num_units*2, 70 | out_channels=self.num_units, 71 | kernel_size=1) 72 | self.unfinished_hidden_layer = nn.Conv1d(in_channels=self.num_units*2, 73 | out_channels=self.num_units, 74 | kernel_size=1) 75 | 76 | self.encoder_second_cells = nn.ModuleList([GGRUCell(self.num_units, 77 | self.num_units, 78 | self.dropout_type, 79 | self.dropout_prob, 80 | self.num_relations, 81 | num_bases=self.num_bases, 82 | K=self.K, 83 | num_nodes=self.num_nodes, 84 | global_fusion=self.global_fusion) 85 | for _ in range(self.num_rnn_layers - 1)]) 86 | self.decoder_first_cells = GGRUCell(self.num_finished_input_dim, 87 | self.num_units, 88 | self.dropout_type, 89 | self.dropout_prob, 90 | self.num_relations, 91 | num_bases=self.num_bases, 92 | K=self.K, 93 | num_nodes=self.num_nodes, 94 | global_fusion=self.global_fusion) 95 | self.decoder_second_cells = nn.ModuleList([GGRUCell(self.num_units, 96 | self.num_units, 97 | self.dropout_type, 98 | self.dropout_prob, 99 | self.num_relations, 100 | self.K, 101 | num_nodes=self.num_nodes, 102 | global_fusion=self.global_fusion) 103 | for _ in range(self.num_rnn_layers - 1)]) 104 | 105 | self.output_type = cfg['model'].get('output_type', 'fc') 106 | if self.output_type == 'fc': 107 | self.output_layer = nn.Linear(self.num_units, self.num_output_dim) 108 | 109 | def encoder_first_layer(self, 110 | batch, 111 | finished_hidden, 112 | long_his_hidden, 113 | short_his_hidden, 114 | edge_index, 115 | edge_attr=None): 116 | finished_out, finished_hidden = self.encoder_first_finished_cells(inputs=batch.x_od, 117 | edge_index=edge_index, 118 | edge_attr=edge_attr, 119 | hidden=finished_hidden) 120 | enc_first_hidden = finished_hidden 121 | enc_first_out = finished_out 122 | 123 | long_his_out, long_his_hidden = self.encoder_first_unfinished_cells(inputs=batch.history, 124 | edge_index=edge_index, 125 | edge_attr=edge_attr, 126 | hidden=long_his_hidden) 127 | 128 | short_his_out, short_his_hidden = self.encoder_first_short_his_cells(inputs=batch.yesterday, 129 | edge_index=edge_index, 130 | edge_attr=edge_attr, 131 | hidden=short_his_hidden) 132 | 133 | hidden_fusion = torch.cat([long_his_hidden, short_his_hidden], -1).view(-1, self.num_units * 2, self.num_nodes) 134 | 135 | long_his_weight = torch.sigmoid(self.unfinished_hidden_layer(hidden_fusion)).view(-1, self.num_units) 136 | short_his_weight = torch.sigmoid(self.unfinished_output_layer(hidden_fusion)).view(-1, self.num_units) 137 | 138 | unfinished_hidden = long_his_weight * long_his_hidden + short_his_weight * short_his_hidden 139 | unfinished_out = long_his_weight * long_his_out + short_his_weight * short_his_out 140 | 141 | enc_first_out = finished_out + unfinished_out 142 | enc_first_hidden = enc_first_hidden + unfinished_hidden 143 | 144 | return enc_first_out, finished_hidden, long_his_hidden, short_his_hidden, enc_first_hidden 145 | 146 | def encoder_second_layer(self, 147 | index, 148 | first_out, 149 | enc_second_hidden, 150 | edge_index, 151 | edge_attr): 152 | enc_second_out, enc_second_hidden = self.encoder_second_cells[index](inputs=first_out, 153 | hidden=enc_second_hidden, 154 | edge_index=edge_index, 155 | edge_attr=edge_attr) 156 | return enc_second_out, enc_second_hidden 157 | 158 | def decoder_first_layer(self, 159 | decoder_input, 160 | dec_first_hidden, 161 | edge_index, 162 | edge_attr=None): 163 | dec_first_out, dec_first_hidden = self.decoder_first_cells(inputs=decoder_input, 164 | hidden=dec_first_hidden, 165 | edge_index=edge_index, 166 | edge_attr=edge_attr) 167 | return dec_first_out, dec_first_hidden 168 | 169 | def decoder_second_layer(self, 170 | index, 171 | decoder_first_out, 172 | dec_second_hidden, 173 | edge_index, 174 | edge_attr=None): 175 | dec_second_out, dec_second_hidden = self.decoder_second_cells[index](inputs=decoder_first_out, 176 | hidden=dec_second_hidden, 177 | edge_index=edge_index, 178 | edge_attr=edge_attr) 179 | return dec_second_out, dec_second_hidden 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /evaluate.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | import random 3 | import argparse 4 | import time 5 | import yaml 6 | import numpy as np 7 | import torch 8 | import os 9 | 10 | from torch import nn 11 | from torch.optim.lr_scheduler import MultiStepLR 12 | from torch.nn.init import xavier_uniform_ 13 | from lib import utils_HIAM as utils 14 | from lib.utils_HIAM import collate_wrapper 15 | from lib import metrics 16 | from models.Net import Net 17 | try: 18 | from yaml import CLoader as Loader, CDumper as Dumper 19 | except ImportError: 20 | from yaml import Loader, Dumper 21 | seed = 1234 22 | random.seed(seed) 23 | np.random.seed(seed) 24 | torch.manual_seed(seed) 25 | parser = argparse.ArgumentParser() 26 | parser.add_argument('--config_filename', 27 | default=None, 28 | type=str, 29 | help='Configuration filename for restoring the model.') 30 | args = parser.parse_args() 31 | 32 | def read_cfg_file(filename): 33 | with open(filename, 'r') as ymlfile: 34 | cfg = yaml.load(ymlfile, Loader=Loader) 35 | return cfg 36 | 37 | def run_model(model, data_iterator, edge_index, edge_attr, device, seq_len, horizon, output_dim): 38 | """ 39 | return a list of (horizon_i, batch_size, num_nodes, output_dim) 40 | """ 41 | # while evaluation, we need model.eval and torch.no_grad 42 | model.eval() 43 | y_od_pred_list = [] 44 | y_do_pred_list = [] 45 | for _, (x_od, y_od, x_do, y_do, unfinished, history, yesterday, xtime, ytime) in enumerate(data_iterator): 46 | y_od = y_od[..., :output_dim] 47 | y_do = y_do[..., :output_dim] 48 | sequences, sequences_y, y_od, y_do = collate_wrapper(x_od=x_od, y_od=y_od, x_do=x_do, y_do=y_do, unfinished=unfinished, history=history, yesterday=yesterday, 49 | edge_index=edge_index, 50 | edge_attr=edge_attr, 51 | device=device, 52 | seq_len=seq_len, 53 | horizon=horizon) 54 | # (T, N, num_nodes, num_out_channels) 55 | with torch.no_grad(): 56 | y_od_pred, y_do_pred = model(sequences, sequences_y) 57 | if y_od_pred is not None: 58 | y_od_pred_list.append(y_od_pred.cpu().numpy()) 59 | if y_do_pred is not None: 60 | y_do_pred_list.append(y_do_pred.cpu().numpy()) 61 | return y_od_pred_list, y_do_pred_list 62 | 63 | 64 | def evaluate(model, 65 | dataset, 66 | dataset_type, 67 | edge_index, 68 | edge_attr, 69 | device, 70 | seq_Len, 71 | horizon, 72 | output_dim, 73 | logger, 74 | detail=True, 75 | cfg=None, 76 | format_result=False): 77 | if detail: 78 | logger.info('Evaluation_{}_Begin:'.format(dataset_type)) 79 | 80 | y_od_preds, y_do_preds = run_model( 81 | model, 82 | data_iterator=dataset['{}_loader'.format(dataset_type)].get_iterator(), 83 | edge_index=edge_index, 84 | edge_attr=edge_attr, 85 | device=device, 86 | seq_len=seq_Len, 87 | horizon=horizon, 88 | output_dim=output_dim) 89 | 90 | evaluate_category = [] 91 | if len(y_od_preds) > 0: 92 | evaluate_category.append("od") 93 | if len(y_do_preds) > 0: 94 | evaluate_category.append("do") 95 | results = {} 96 | for category in evaluate_category: 97 | if category == 'od': 98 | y_preds = y_od_preds 99 | scaler = dataset['scaler'] 100 | gt = dataset['y_{}'.format(dataset_type)] 101 | else: 102 | y_preds = y_do_preds 103 | scaler = dataset['do_scaler'] 104 | # scaler = dataset['scaler'] 105 | gt = dataset['do_y_{}'.format(dataset_type)] 106 | y_preds = np.concatenate(y_preds, axis=0) # concat in batch_size dim. 107 | mae_list = [] 108 | mape_net_list = [] 109 | rmse_list = [] 110 | mae_sum = 0 111 | 112 | mape_net_sum = 0 113 | rmse_sum = 0 114 | # horizon = dataset['y_{}'.format(dataset_type)].shape[1] 115 | logger.info("{}:".format(category)) 116 | horizon = cfg['model']['horizon'] 117 | for horizon_i in range(horizon): 118 | y_truth = scaler.inverse_transform( 119 | gt[:, horizon_i, :, :output_dim]) 120 | 121 | y_pred = scaler.inverse_transform( 122 | y_preds[:y_truth.shape[0], horizon_i, :, :output_dim]) 123 | y_pred[y_pred < 0] = 0 124 | mae = metrics.masked_mae_np(y_pred, y_truth) 125 | mape_net = metrics.masked_mape_np(y_pred, y_truth) 126 | rmse = metrics.masked_rmse_np(y_pred, y_truth) 127 | mae_sum += mae 128 | mape_net_sum += mape_net 129 | rmse_sum += rmse 130 | mae_list.append(mae) 131 | 132 | mape_net_list.append(mape_net) 133 | rmse_list.append(rmse) 134 | 135 | msg = "Horizon {:02d}, MAE: {:.2f}, RMSE: {:.2f}, MAPE_net: {:.4f}" 136 | if detail: 137 | logger.info(msg.format(horizon_i + 1, mae, rmse, mape_net)) 138 | results['MAE_' + category] = mae_sum / horizon 139 | results['RMSE_' + category] = rmse_sum / horizon 140 | results['MAPE_net_' + category] = mape_net_sum / horizon 141 | if detail: 142 | logger.info('Evaluation_{}_End:'.format(dataset_type)) 143 | if format_result: 144 | for i in range(len(mae_list)): 145 | print('{:.2f}'.format(mae_list[i])) 146 | print('{:.2f}'.format(rmse_list[i])) 147 | print('{:.2f}%'.format(mape_net_list[i] * 100)) 148 | print() 149 | else: 150 | # return mae_sum / horizon, rmse_sum / horizon, mape_sta_sum / horizon, mape_pair_sum / horizon, mape_net_sum/ horizon, mape_distribution_sum / horizon 151 | return results 152 | 153 | class StepLR2(MultiStepLR): 154 | """StepLR with min_lr""" 155 | def __init__(self, 156 | optimizer, 157 | milestones, 158 | gamma=0.1, 159 | last_epoch=-1, 160 | min_lr=2.0e-6): 161 | self.optimizer = optimizer 162 | self.milestones = milestones 163 | self.gamma = gamma 164 | self.last_epoch = last_epoch 165 | self.min_lr = min_lr 166 | super(StepLR2, self).__init__(optimizer, milestones, gamma) 167 | 168 | def get_lr(self): 169 | lr_candidate = super(StepLR2, self).get_lr() 170 | if isinstance(lr_candidate, list): 171 | for i in range(len(lr_candidate)): 172 | lr_candidate[i] = max(self.min_lr, lr_candidate[i]) 173 | 174 | else: 175 | lr_candidate = max(self.min_lr, lr_candidate) 176 | 177 | return lr_candidate 178 | 179 | def _get_log_dir(kwargs): 180 | log_dir = kwargs['train'].get('log_dir') 181 | if log_dir is None: 182 | batch_size = kwargs['data'].get('batch_size') 183 | learning_rate = kwargs['train'].get('base_lr') 184 | num_rnn_layers = kwargs['model'].get('num_rnn_layers') 185 | rnn_units = kwargs['model'].get('rnn_units') 186 | structure = '-'.join(['%d' % rnn_units for _ in range(num_rnn_layers)]) 187 | 188 | # name of dir for saving log 189 | run_id = 'HIAM_%s_lr%g_bs%d_%s/' % ( 190 | structure, 191 | learning_rate, 192 | batch_size, 193 | time.strftime('%m%d%H%M%S')) 194 | base_dir = kwargs.get('base_dir') 195 | log_dir = os.path.join(base_dir, run_id) 196 | if not os.path.exists(log_dir): 197 | os.makedirs(log_dir) 198 | return log_dir 199 | 200 | 201 | def init_weights(m): 202 | classname = m.__class__.__name__ # 2 203 | if classname.find('Conv') != -1 and classname.find('RGCN') == -1: 204 | xavier_uniform_(m.weight.data) 205 | if type(m) == nn.Linear: 206 | xavier_uniform_(m.weight.data) 207 | #xavier_uniform_(m.bias.data) 208 | 209 | def toDevice(datalist, device): 210 | for i in range(len(datalist)): 211 | datalist[i] = datalist[i].to(device) 212 | return datalist 213 | 214 | def main(args): 215 | cfg = read_cfg_file(args.config_filename) 216 | log_dir = _get_log_dir(cfg) 217 | log_level = cfg.get('log_level', 'INFO') 218 | 219 | logger = utils.get_logger(log_dir, __name__, 'info.log', level=log_level) 220 | 221 | device = torch.device( 222 | 'cuda') if torch.cuda.is_available() else torch.device('cpu') 223 | # all edge_index in same dataset is same 224 | # edge_index = adjacency_to_edge_index(adj_mx) # alreay added self-loop 225 | logger.info(cfg) 226 | batch_size = cfg['data']['batch_size'] 227 | seq_len = cfg['model']['seq_len'] 228 | horizon = cfg['model']['horizon'] 229 | # edge_index = utils.load_pickle(cfg['data']['edge_index_pkl_filename']) 230 | 231 | adj_mx_list = [] 232 | graph_pkl_filename = cfg['data']['graph_pkl_filename'] 233 | 234 | if not isinstance(graph_pkl_filename, list): 235 | graph_pkl_filename = [graph_pkl_filename] 236 | 237 | src = [] 238 | dst = [] 239 | for g in graph_pkl_filename: 240 | adj_mx = utils.load_graph_data(g) 241 | for i in range(len(adj_mx)): # 构建邻接矩阵 242 | adj_mx[i, i] = 0 243 | adj_mx_list.append(adj_mx) 244 | 245 | adj_mx = np.stack(adj_mx_list, axis=-1) 246 | print("adj_mx:", adj_mx.shape) 247 | if cfg['model'].get('norm', False): 248 | print('row normalization') 249 | adj_mx = adj_mx / (adj_mx.sum(axis=0) + 1e-18) # 归一化 250 | src, dst = adj_mx.sum(axis=-1).nonzero() 251 | print("src, dst:", src.shape, dst.shape) 252 | edge_index = torch.tensor([src, dst], dtype=torch.long, device=device) 253 | edge_attr = torch.tensor(adj_mx[adj_mx.sum(axis=-1) != 0], 254 | dtype=torch.float, 255 | device=device) 256 | print("train, edge:", edge_index.shape, edge_attr.shape) 257 | output_dim = cfg['model']['output_dim'] 258 | for i in range(adj_mx.shape[-1]): 259 | logger.info(adj_mx[..., i]) 260 | 261 | dataset = utils.load_dataset(**cfg['data'], scaler_axis=(0, 1, 2, 3)) 262 | for k, v in dataset.items(): 263 | if hasattr(v, 'shape'): 264 | logger.info((k, v.shape)) 265 | 266 | model = Net(cfg, logger).to(device) 267 | state = torch.load(cfg['model']['save_path']) 268 | model.load_state_dict(state, strict=False) 269 | evaluate(model=model, 270 | dataset=dataset, 271 | dataset_type='test', 272 | edge_index=edge_index, 273 | edge_attr=edge_attr, 274 | device=device, 275 | seq_Len=seq_len, 276 | horizon=horizon, 277 | output_dim=output_dim, 278 | logger=logger, 279 | cfg=cfg) 280 | 281 | if __name__ == "__main__": 282 | main(args) 283 | -------------------------------------------------------------------------------- /SOTA/DCRNN/model/dcrnn_supervisor.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | import numpy as np 5 | import torch 6 | # from torch.utils.tensorboard import SummaryWriter 7 | 8 | from lib import utils_HIAM, metrics 9 | from .dcrnn_model import DCRNNModel 10 | 11 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 12 | 13 | 14 | class DCRNNSupervisor: 15 | def __init__(self, adj_mx, **kwargs): 16 | self._kwargs = kwargs 17 | self._data_kwargs = kwargs.get('data') 18 | self._model_kwargs = kwargs.get('model') 19 | self._train_kwargs = kwargs.get('train') 20 | 21 | self.max_grad_norm = self._train_kwargs.get('max_grad_norm', 1.) 22 | 23 | # logging. 24 | self._log_dir = self._get_log_dir(kwargs) 25 | # self._writer = SummaryWriter('runs/' + self._log_dir) 26 | 27 | log_level = self._kwargs.get('log_level', 'INFO') 28 | self._logger = utils_HIAM.get_logger(self._log_dir, __name__, 'info.log', level=log_level) 29 | 30 | # data set 31 | self._data = utils_HIAM.load_dataset(**self._data_kwargs) 32 | self.standard_scaler = self._data['scaler'] 33 | 34 | self.num_nodes = int(self._model_kwargs.get('num_nodes', 1)) 35 | self.input_dim = int(self._model_kwargs.get('input_dim', 1)) 36 | self.seq_len = int(self._model_kwargs.get('seq_len')) # for the encoder 37 | self.output_dim = int(self._model_kwargs.get('output_dim', 1)) 38 | self.use_curriculum_learning = bool( 39 | self._model_kwargs.get('use_curriculum_learning', False)) 40 | self.horizon = int(self._model_kwargs.get('horizon', 1)) # for the decoder 41 | 42 | # setup model 43 | dcrnn_model = DCRNNModel(adj_mx, self._logger, **self._model_kwargs) 44 | self.dcrnn_model = dcrnn_model.cuda() if torch.cuda.is_available() else dcrnn_model 45 | self._logger.info("Model created") 46 | 47 | self._epoch_num = self._train_kwargs.get('epoch', 0) 48 | if self._epoch_num > 0: 49 | self.load_model() 50 | 51 | @staticmethod 52 | def _get_log_dir(kwargs): 53 | log_dir = kwargs['train'].get('log_dir') 54 | if log_dir is None: 55 | batch_size = kwargs['data'].get('batch_size') 56 | learning_rate = kwargs['train'].get('base_lr') 57 | max_diffusion_step = kwargs['model'].get('max_diffusion_step') 58 | num_rnn_layers = kwargs['model'].get('num_rnn_layers') 59 | rnn_units = kwargs['model'].get('rnn_units') 60 | structure = '-'.join( 61 | ['%d' % rnn_units for _ in range(num_rnn_layers)]) 62 | horizon = kwargs['model'].get('horizon') 63 | filter_type = kwargs['model'].get('filter_type') 64 | filter_type_abbr = 'L' 65 | if filter_type == 'random_walk': 66 | filter_type_abbr = 'R' 67 | elif filter_type == 'dual_random_walk': 68 | filter_type_abbr = 'DR' 69 | run_id = 'dcrnn_%s_%d_h_%d_%s_lr_%g_bs_%d_%s/' % ( 70 | filter_type_abbr, max_diffusion_step, horizon, 71 | structure, learning_rate, batch_size, 72 | time.strftime('%m%d%H%M%S')) 73 | base_dir = kwargs.get('base_dir') 74 | log_dir = os.path.join(base_dir, run_id) 75 | if not os.path.exists(log_dir): 76 | os.makedirs(log_dir) 77 | return log_dir 78 | 79 | def save_model(self, epoch): 80 | if not os.path.exists('models/'): 81 | os.makedirs('models/') 82 | 83 | config = dict(self._kwargs) 84 | config['model_state_dict'] = self.dcrnn_model.state_dict() 85 | config['epoch'] = epoch 86 | torch.save(config, 'models/epo%d.pt' % epoch) 87 | self._logger.info("Saved model at {}".format(epoch)) 88 | return 'models/epo%d.pt' % epoch 89 | 90 | def load_model(self): 91 | self._setup_graph() 92 | assert os.path.exists('models/epo%d.pt' % self._epoch_num), 'Weights at epoch %d not found' % self._epoch_num 93 | checkpoint = torch.load('models/epo%d.pt' % self._epoch_num, map_location='cpu') 94 | self.dcrnn_model.load_state_dict(checkpoint['model_state_dict']) 95 | self._logger.info("Loaded model at {}".format(self._epoch_num)) 96 | 97 | def _setup_graph(self): 98 | with torch.no_grad(): 99 | self.dcrnn_model = self.dcrnn_model.eval() 100 | 101 | val_iterator = self._data['val_loader'].get_iterator() 102 | 103 | for _, (x, y) in enumerate(val_iterator): 104 | x, y = self._prepare_data(x, y) 105 | output = self.dcrnn_model(x) 106 | 107 | output = output.view(self.horizon, -1, self.num_nodes, self.output_dim).transpose(0, 1) 108 | y = y.view(self.horizon, -1, self.num_nodes, self.output_dim).transpose(0, 1) 109 | break 110 | 111 | def train(self, **kwargs): 112 | kwargs.update(self._train_kwargs) 113 | return self._train(**kwargs) 114 | 115 | def evaluate(self, dataset='val', batches_seen=0): 116 | """ 117 | Computes mean L1Loss 118 | :return: mean L1Loss 119 | """ 120 | with torch.no_grad(): 121 | self.dcrnn_model = self.dcrnn_model.eval() 122 | 123 | val_iterator = self._data['{}_loader'.format(dataset)].get_iterator() 124 | # losses = [] 125 | mae_sum = 0 126 | mape_sum = 0 127 | 128 | y_preds = [] 129 | y_truths = [] 130 | 131 | for _, (x, y) in enumerate(val_iterator): 132 | x, y = self._prepare_data(x, y) 133 | output = self.dcrnn_model(x) 134 | 135 | output = output.view(self.horizon, -1, self.num_nodes, self.output_dim).transpose(0, 1) 136 | y = y.view(self.horizon, -1, self.num_nodes, self.output_dim).transpose(0, 1) 137 | y_preds.append(output.cpu()) 138 | y_truths.append(y.cpu()) 139 | 140 | y_preds = np.concatenate(y_preds, axis=0) 141 | y_truths = np.concatenate(y_truths, axis=0) 142 | 143 | scaler = self._data['scaler'] 144 | for horizon_i in range(self.horizon): 145 | y_truth = scaler.inverse_transform(y_truths[:, horizon_i, :, :]) 146 | y_pred = scaler.inverse_transform(y_preds[:, horizon_i, :, :]) 147 | 148 | mae = metrics.masked_mae_np(y_pred, y_truth) 149 | rmse = metrics.masked_rmse_np(y_pred, y_truth) 150 | mape_net = metrics.masked_mape_np(y_pred, y_truth) 151 | 152 | mae_sum += mae 153 | mape_sum += mape_net 154 | 155 | if dataset == 'test': 156 | self._logger.info( 157 | "Horizon {:02d}, MAE: {:.2f}, RMSE: {:.2f}, MAPE_net: {:.4f}".format( 158 | horizon_i + 1, mae, rmse, mape_net) 159 | ) 160 | mean_mae = mae_sum / self.horizon * 1.0 161 | mean_mape = mape_sum / self.horizon * 1.0 162 | return mean_mae, mean_mape 163 | 164 | def _train(self, base_lr, 165 | steps, patience=50, epochs=100, lr_decay_ratio=0.1, log_every=1, save_model=1, 166 | test_every_n_epochs=10, epsilon=1e-8, **kwargs): 167 | # steps is used in learning rate - will see if need to use it? 168 | min_val_loss = float('inf') 169 | min_val_mape_loss = float('inf') 170 | wait = 0 171 | optimizer = torch.optim.Adam(self.dcrnn_model.parameters(), lr=base_lr, eps=epsilon) 172 | 173 | lr_scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=steps, 174 | gamma=lr_decay_ratio) 175 | 176 | self._logger.info('Start training ...') 177 | 178 | # this will fail if model is loaded with a changed batch_size 179 | num_batches = self._data['train_loader'].num_batch 180 | self._logger.info("num_batches:{}".format(num_batches)) 181 | 182 | batches_seen = num_batches * self._epoch_num 183 | 184 | for epoch_num in range(self._epoch_num, epochs): 185 | 186 | self.dcrnn_model = self.dcrnn_model.train() 187 | 188 | train_iterator = self._data['train_loader'].get_iterator() 189 | losses = [] 190 | 191 | start_time = time.time() 192 | 193 | for _, (x, y) in enumerate(train_iterator): 194 | optimizer.zero_grad() 195 | 196 | x, y = self._prepare_data(x, y) 197 | 198 | output = self.dcrnn_model(x, y, batches_seen) 199 | 200 | if batches_seen == 0: 201 | # this is a workaround to accommodate dynamically registered parameters in DCGRUCell 202 | optimizer = torch.optim.Adam(self.dcrnn_model.parameters(), lr=base_lr, eps=epsilon) 203 | 204 | output = output.view(self.horizon, -1, self.num_nodes, self.output_dim).transpose(0, 1) 205 | y = y.view(self.horizon, -1, self.num_nodes, self.output_dim).transpose(0, 1) 206 | loss = self._compute_loss(y, output) 207 | 208 | self._logger.debug(loss.item()) 209 | 210 | losses.append(loss.item()) 211 | 212 | batches_seen += 1 213 | loss.backward() 214 | 215 | # gradient clipping - this does it in place 216 | torch.nn.utils.clip_grad_norm_(self.dcrnn_model.parameters(), self.max_grad_norm) 217 | 218 | optimizer.step() 219 | self._logger.info("epoch complete") 220 | lr_scheduler.step() 221 | self._logger.info("evaluating now!") 222 | 223 | val_loss, val_mape_loss = self.evaluate(dataset='val', batches_seen=batches_seen) 224 | 225 | end_time = time.time() 226 | 227 | message = 'Epoch [{}/{}] ({}) train_mae: {:.4f}, val_mae: {:.4f}, val_mape: {:.4f}, lr: {:.6f}, ' \ 228 | '{:.1f}s'.format(epoch_num, epochs, batches_seen, 229 | np.mean(losses), val_loss, val_mape_loss, lr_scheduler.get_lr()[0], 230 | (end_time - start_time)) 231 | self._logger.info(message) 232 | 233 | if val_loss < min_val_loss: 234 | wait = 0 235 | if save_model: 236 | model_file_name = self.save_model(epoch_num) 237 | self._logger.info( 238 | 'Val MAE decrease from {:.4f} to {:.4f}, ' 239 | 'saving to {}'.format(min_val_loss, val_loss, model_file_name)) 240 | min_val_loss = val_loss 241 | 242 | if val_mape_loss < min_val_mape_loss: 243 | wait = 0 244 | if save_model: 245 | model_file_name = self.save_model(epoch_num) 246 | self._logger.info( 247 | 'Val MAPE decrease from {:.4f} to {:.4f}, ' 248 | 'saving to {}'.format(min_val_mape_loss, val_mape_loss, model_file_name)) 249 | min_val_mape_loss = val_mape_loss 250 | 251 | _, _ = self.evaluate(dataset='test', batches_seen=batches_seen) 252 | 253 | if val_loss >= min_val_loss: 254 | wait += 1 255 | if wait == patience: 256 | self._logger.warning('Early stopping at epoch: %d' % epoch_num) 257 | break 258 | 259 | def _prepare_data(self, x, y): 260 | x, y = self._get_x_y(x, y) 261 | x, y = self._get_x_y_in_correct_dims(x, y) 262 | return x.to(device), y.to(device) 263 | 264 | def _get_x_y(self, x, y): 265 | """ 266 | :param x: shape (batch_size, seq_len, num_sensor, input_dim) 267 | :param y: shape (batch_size, horizon, num_sensor, input_dim) 268 | :returns x shape (seq_len, batch_size, num_sensor, input_dim) 269 | y shape (horizon, batch_size, num_sensor, input_dim) 270 | """ 271 | x = torch.from_numpy(x).float() 272 | y = torch.from_numpy(y).float() 273 | self._logger.debug("X: {}".format(x.size())) 274 | self._logger.debug("y: {}".format(y.size())) 275 | x = x.permute(1, 0, 2, 3) 276 | y = y.permute(1, 0, 2, 3) 277 | return x, y 278 | 279 | def _get_x_y_in_correct_dims(self, x, y): 280 | """ 281 | :param x: shape (seq_len, batch_size, num_sensor, input_dim) 282 | :param y: shape (horizon, batch_size, num_sensor, input_dim) 283 | :return: x: shape (seq_len, batch_size, num_sensor * input_dim) 284 | y: shape (horizon, batch_size, num_sensor * output_dim) 285 | """ 286 | batch_size = x.size(1) 287 | x = x.view(self.seq_len, batch_size, self.num_nodes * self.input_dim) 288 | y = y[..., :self.output_dim].view(self.horizon, batch_size, 289 | self.num_nodes * self.output_dim) 290 | return x, y 291 | 292 | def _compute_loss(self, y_true, y_predicted): 293 | y_true = self.standard_scaler.inverse_transform(y_true) 294 | y_predicted = self.standard_scaler.inverse_transform(y_predicted) 295 | loss = torch.nn.L1Loss(reduction='mean') 296 | return loss(y_predicted, y_true) 297 | -------------------------------------------------------------------------------- /models/Net.py: -------------------------------------------------------------------------------- 1 | from torch_geometric import nn as gnn 2 | from torch import nn 3 | import torch 4 | import random 5 | import math 6 | import sys 7 | import os 8 | sys.path.insert(0, os.path.abspath('..')) 9 | 10 | from models.OD_Net import ODNet 11 | from models.DO_Net import DONet 12 | from models.DualInfoTransformer import DualInfoTransformer 13 | 14 | 15 | class Net(torch.nn.Module): 16 | 17 | def __init__(self, cfg, logger): 18 | super(Net, self).__init__() 19 | self.logger = logger 20 | self.cfg = cfg 21 | 22 | self.OD = ODNet(cfg, logger) 23 | self.DO = DONet(cfg, logger) 24 | 25 | self.num_nodes = cfg['model']['num_nodes'] 26 | self.num_output_dim = cfg['model']['output_dim'] 27 | self.num_units = cfg['model']['rnn_units'] 28 | self.num_finished_input_dim = cfg['model']['input_dim'] 29 | self.num_unfinished_input_dim = cfg['model']['input_dim'] 30 | self.num_rnn_layers = cfg['model']['num_rnn_layers'] 31 | 32 | self.seq_len = cfg['model']['seq_len'] 33 | self.horizon = cfg['model']['horizon'] 34 | self.head = cfg['model'].get('head', 4) 35 | self.d_channel = cfg['model'].get('channel', 512) 36 | 37 | self.use_curriculum_learning = self.cfg['model']['use_curriculum_learning'] 38 | self.cl_decay_steps = torch.FloatTensor(data=[self.cfg['model']['cl_decay_steps']]) 39 | self.use_input = cfg['model'].get('use_input', True) 40 | 41 | self.mediate_activation = nn.PReLU(self.num_units) 42 | 43 | self.global_step = 0 44 | 45 | self.encoder_first_interact = DualInfoTransformer( 46 | h=self.head, 47 | d_nodes=self.num_nodes, 48 | d_model=self.num_units, 49 | d_channel=self.d_channel) 50 | self.decoder_first_interact = DualInfoTransformer( 51 | h=self.head, 52 | d_nodes=self.num_nodes, 53 | d_model=self.num_units, 54 | d_channel=self.d_channel) 55 | self.encoder_second_interact = nn.ModuleList([DualInfoTransformer( 56 | h=self.head, 57 | d_nodes=self.num_nodes, 58 | d_model=self.num_units, 59 | d_channel=self.d_channel) 60 | for _ in range(self.num_rnn_layers - 1)]) 61 | self.decoder_second_interact = nn.ModuleList([DualInfoTransformer( 62 | h=self.head, 63 | d_nodes=self.num_nodes, 64 | d_model=self.num_units, 65 | d_channel=self.d_channel) 66 | for _ in range(self.num_rnn_layers - 1)]) 67 | 68 | 69 | @staticmethod 70 | def inverse_sigmoid_scheduler_sampling(step, k): 71 | """TODO: Docstring for linear_scheduler_sampling. 72 | :returns: TODO 73 | 74 | """ 75 | try: 76 | return k / (k + math.exp(step / k)) 77 | except OverflowError: 78 | return float('inf') 79 | 80 | def encoder_od_do(self, 81 | sequences, 82 | edge_index, 83 | edge_attr=None): 84 | """ 85 | Encodes input into hidden state on one branch for T steps. 86 | 87 | Return: hidden state on one branch.""" 88 | enc_hiddens_od = [None] * self.num_rnn_layers 89 | enc_hiddens_do = [None] * self.num_rnn_layers 90 | 91 | finished_hidden_od = None 92 | long_his_hidden_od = None 93 | short_his_hidden_od = None 94 | 95 | for t, batch in enumerate(sequences): 96 | encoder_first_out_od, finished_hidden_od, \ 97 | long_his_hidden_od, short_his_hidden_od, \ 98 | enc_first_hidden_od = self.OD.encoder_first_layer(batch, 99 | finished_hidden_od, 100 | long_his_hidden_od, 101 | short_his_hidden_od, 102 | edge_index, 103 | edge_attr) 104 | 105 | enc_first_out_do, enc_first_hidden_do = self.DO.encoder_first_layer(batch, 106 | enc_hiddens_do[0], 107 | edge_index, 108 | edge_attr) 109 | 110 | enc_first_interact_info_od, enc_first_interact_info_do = self.encoder_first_interact( 111 | enc_first_hidden_od, 112 | enc_first_hidden_do) 113 | 114 | enc_hiddens_od[0] = enc_first_hidden_od + enc_first_interact_info_od 115 | enc_hiddens_do[0] = enc_first_hidden_do + enc_first_interact_info_do 116 | 117 | enc_mid_out_od = encoder_first_out_od + enc_first_interact_info_od 118 | enc_mid_out_do = enc_first_out_do + enc_first_interact_info_do 119 | 120 | for index in range(self.num_rnn_layers - 1): 121 | enc_mid_out_od = self.mediate_activation(enc_mid_out_od) 122 | enc_mid_out_do = self.mediate_activation(enc_mid_out_do) 123 | 124 | enc_mid_out_od, enc_mid_hidden_od = self.OD.encoder_second_layer(index, 125 | enc_mid_out_od, 126 | enc_hiddens_od[index + 1], 127 | edge_index, 128 | edge_attr) 129 | enc_mid_out_do, enc_mid_hidden_do = self.DO.encoder_second_layer(index, 130 | enc_mid_out_do, 131 | enc_hiddens_do[index + 1], 132 | edge_index, 133 | edge_attr) 134 | 135 | enc_mid_interact_info_od, enc_mid_interact_info_do = self.encoder_second_interact[index]( 136 | enc_mid_hidden_od, 137 | enc_mid_hidden_do) 138 | 139 | enc_hiddens_od[index + 1] = enc_mid_hidden_od + enc_mid_interact_info_od 140 | enc_hiddens_do[index + 1] = enc_mid_hidden_do + enc_mid_interact_info_do 141 | 142 | return enc_hiddens_od, enc_hiddens_do 143 | 144 | def scheduled_sampling(self, 145 | out, 146 | label, 147 | GO): 148 | if self.training and self.use_curriculum_learning: 149 | c = random.uniform(0, 1) 150 | T = self.inverse_sigmoid_scheduler_sampling( 151 | self.global_step, 152 | self.cl_decay_steps) 153 | use_truth_sequence = True if c < T else False 154 | else: 155 | use_truth_sequence = False 156 | 157 | if use_truth_sequence: 158 | # Feed the prev label as the next input 159 | decoder_input = label 160 | else: 161 | # detach from history as input 162 | decoder_input = out.detach().view(-1, self.num_output_dim) 163 | if not self.use_input: 164 | decoder_input = GO.detach() 165 | 166 | return decoder_input 167 | 168 | def decoder_od_do(self, 169 | sequences, 170 | enc_hiddens_od, 171 | enc_hiddens_do, 172 | edge_index, 173 | edge_attr=None): 174 | predictions_od = [] 175 | predictions_do = [] 176 | 177 | GO_od = torch.zeros(enc_hiddens_od[0].size()[0], 178 | self.num_output_dim, 179 | dtype=enc_hiddens_od[0].dtype, 180 | device=enc_hiddens_od[0].device) 181 | GO_do = torch.zeros(enc_hiddens_do[0].size()[0], 182 | self.num_output_dim, 183 | dtype=enc_hiddens_do[0].dtype, 184 | device=enc_hiddens_do[0].device) 185 | 186 | dec_input_od = GO_od 187 | dec_hiddens_od = enc_hiddens_od 188 | 189 | dec_input_do = GO_do 190 | dec_hiddens_do = enc_hiddens_do 191 | 192 | for t in range(self.horizon): 193 | dec_first_out_od, dec_first_hidden_od = self.OD.decoder_first_layer(dec_input_od, 194 | dec_hiddens_od[0], 195 | edge_index, 196 | edge_attr) 197 | 198 | dec_first_out_do, dec_first_hidden_do = self.DO.decoder_first_layer(dec_input_do, 199 | dec_hiddens_do[0], 200 | edge_index, 201 | edge_attr) 202 | 203 | dec_first_interact_info_od, dec_first_interact_info_do = self.decoder_first_interact( 204 | dec_first_hidden_od, 205 | dec_first_hidden_do) 206 | 207 | dec_hiddens_od[0] = dec_first_hidden_od + dec_first_interact_info_od 208 | dec_hiddens_do[0] = dec_first_hidden_do + dec_first_interact_info_do 209 | dec_mid_out_od = dec_first_out_od + dec_first_interact_info_od 210 | dec_mid_out_do = dec_first_out_do + dec_first_interact_info_do 211 | 212 | for index in range(self.num_rnn_layers - 1): 213 | dec_mid_out_od = self.mediate_activation(dec_mid_out_od) 214 | dec_mid_out_do = self.mediate_activation(dec_mid_out_do) 215 | dec_mid_out_od, dec_mid_hidden_od = self.OD.decoder_second_layer(index, 216 | dec_mid_out_od, 217 | dec_hiddens_od[index + 1], 218 | edge_index, 219 | edge_attr) 220 | dec_mid_out_do, dec_mid_hidden_do = self.DO.decoder_second_layer(index, 221 | dec_mid_out_do, 222 | dec_hiddens_do[index + 1], 223 | edge_index, 224 | edge_attr) 225 | 226 | dec_second_interact_info_od, dec_second_interact_info_do = self.decoder_second_interact[index]( 227 | dec_mid_hidden_od, 228 | dec_mid_hidden_do) 229 | 230 | dec_hiddens_od[index + 1] = dec_mid_hidden_od + dec_second_interact_info_od 231 | dec_hiddens_do[index + 1] = dec_mid_hidden_do + dec_second_interact_info_do 232 | dec_mid_out_od = dec_mid_out_od + dec_second_interact_info_od 233 | dec_mid_out_do = dec_mid_out_do + dec_second_interact_info_do 234 | 235 | dec_mid_out_od = dec_mid_out_od.reshape(-1, self.num_units) 236 | dec_mid_out_do = dec_mid_out_do.reshape(-1, self.num_units) 237 | 238 | dec_mid_out_od = self.OD.output_layer(dec_mid_out_od).view(-1, self.num_nodes, self.num_output_dim) 239 | dec_mid_out_do = self.DO.output_layer(dec_mid_out_do).view(-1, self.num_nodes, self.num_output_dim) 240 | 241 | predictions_od.append(dec_mid_out_od) 242 | predictions_do.append(dec_mid_out_do) 243 | 244 | dec_input_od = self.scheduled_sampling(dec_mid_out_od, sequences[t].y_od, GO_od) 245 | dec_input_do = self.scheduled_sampling(dec_mid_out_do, sequences[t].y_do, GO_do) 246 | 247 | if self.training: 248 | self.global_step += 1 249 | 250 | return torch.stack(predictions_od).transpose(0, 1), torch.stack(predictions_do).transpose(0, 1) 251 | 252 | def forward(self, sequences, sequences_y): 253 | edge_index = sequences[0].edge_index.detach() 254 | edge_attr = sequences[0].edge_attr.detach() 255 | 256 | enc_hiddens_od, enc_hiddens_do = self.encoder_od_do(sequences, 257 | edge_index=edge_index, 258 | edge_attr=edge_attr) 259 | predictions_od, predictions_do = self.decoder_od_do(sequences_y, 260 | enc_hiddens_od, 261 | enc_hiddens_do, 262 | edge_index=edge_index, 263 | edge_attr=edge_attr) 264 | 265 | return predictions_od, predictions_do 266 | 267 | 268 | -------------------------------------------------------------------------------- /lib/utils_HIAM.py: -------------------------------------------------------------------------------- 1 | # part of this code are copied from DCRNN 2 | import logging 3 | import os 4 | import pickle 5 | import sys 6 | import numpy as np 7 | import torch 8 | from torch_geometric.data import Batch, Data 9 | # from sklearn.externals import joblib 10 | 11 | class DataLoader(object): 12 | 13 | def __init__(self, 14 | x_od, 15 | y_od, 16 | unfinished, 17 | history, 18 | yesterday, 19 | x_do, 20 | y_do, 21 | xtime, 22 | ytime, 23 | batch_size, 24 | pad_with_last_sample=True, 25 | shuffle=False): 26 | """ 27 | 28 | :param xs: 29 | :param ys: 30 | :param batch_size: 31 | :param pad_with_last_sample: pad with the last sample to make number of samples divisible to batch_size. 32 | """ 33 | self.batch_size = batch_size 34 | self.current_ind = 0 35 | if pad_with_last_sample: 36 | num_padding = (batch_size - (len(x_od) % batch_size)) % batch_size 37 | x_od_padding = np.repeat(x_od[-1:], num_padding, axis=0) 38 | x_do_padding = np.repeat(x_do[-1:], num_padding, axis=0) 39 | xtime_padding = np.repeat(xtime[-1:], num_padding, axis=0) 40 | y_od_padding = np.repeat(y_od[-1:], num_padding, axis=0) 41 | y_do_padding = np.repeat(y_do[-1:], num_padding, axis=0) 42 | ytime_padding = np.repeat(ytime[-1:], num_padding, axis=0) 43 | 44 | x_od = np.concatenate([x_od, x_od_padding], axis=0) 45 | x_do = np.concatenate([x_do, x_do_padding], axis=0) 46 | xtime = np.concatenate([xtime, xtime_padding], axis=0) 47 | y_od = np.concatenate([y_od, y_od_padding], axis=0) 48 | y_do = np.concatenate([y_do, y_do_padding], axis=0) 49 | ytime = np.concatenate([ytime, ytime_padding], axis=0) 50 | 51 | unfi_num_padding = (batch_size - (len(unfinished) % batch_size)) % batch_size 52 | unfinished_padding = np.repeat(unfinished[-1:], unfi_num_padding, axis=0) 53 | unfinished = np.concatenate([unfinished, unfinished_padding], axis=0) 54 | 55 | history_num_padding = (batch_size - (len(history) % batch_size)) % batch_size 56 | history_padding = np.repeat(history[-1:], history_num_padding, axis=0) 57 | history = np.concatenate([history, history_padding], axis=0) 58 | 59 | yesterday_num_padding = (batch_size - (len(yesterday) % batch_size)) % batch_size 60 | yesterday_padding = np.repeat(yesterday[-1:], yesterday_num_padding, axis=0) 61 | yesterday = np.concatenate([yesterday, yesterday_padding], axis=0) 62 | self.size = len(x_od) 63 | self.num_batch = int(self.size // self.batch_size) 64 | # if shuffle: 65 | # permutation = np.random.permutation(self.size) 66 | # x_do, y_do = x_do[permutation], y_do[permutation] 67 | # x_od, y_od = x_od[permutation], y_od[permutation] 68 | # xtime, ytime = xtime[permutation], ytime[permutation] 69 | # unfinished = unfinished[permutation] 70 | # history = history[permutation] 71 | # yesterday = yesterday[permutation] 72 | self.x_do = x_do 73 | self.y_do = y_do 74 | self.x_od = x_od 75 | self.y_od = y_od 76 | self.unfinished = unfinished 77 | self.history = history 78 | self.yesterday = yesterday 79 | self.xtime = xtime 80 | self.ytime = ytime 81 | 82 | def shuffle(self): 83 | permutation = np.random.permutation(self.size) 84 | x_do, y_do = self.x_do[permutation], self.y_do[permutation] 85 | x_od, y_od = self.x_od[permutation], self.y_od[permutation] 86 | xtime, ytime = self.xtime[permutation], self.ytime[permutation] 87 | unfinished = self.unfinished[permutation] 88 | history = self.history[permutation] 89 | yesterday = self.yesterday[permutation] 90 | self.x_do = x_do 91 | self.y_do = y_do 92 | self.x_od = x_od 93 | self.y_od = y_od 94 | self.unfinished = unfinished 95 | self.history = history 96 | self.yesterday = yesterday 97 | self.xtime = xtime 98 | self.ytime = ytime 99 | 100 | def get_iterator(self): 101 | self.current_ind = 0 102 | 103 | def _wrapper(): 104 | while self.current_ind < self.num_batch: 105 | start_ind = self.batch_size * self.current_ind 106 | end_ind = min(self.size, 107 | self.batch_size * (self.current_ind + 1)) 108 | x_do_i = self.x_do[start_ind:end_ind, ...] 109 | y_do_i = self.y_do[start_ind:end_ind, ...] 110 | x_od_i = self.x_od[start_ind:end_ind, ...] 111 | y_od_i = self.y_od[start_ind:end_ind, ...] 112 | unfinished_i = self.unfinished[start_ind:end_ind, ...] 113 | history_i = self.history[start_ind:end_ind, ...] 114 | yesterday_i = self.yesterday[start_ind:end_ind, ...] 115 | xtime_i = self.xtime[start_ind:end_ind, ...] 116 | ytime_i = self.ytime[start_ind:end_ind, ...] 117 | yield (x_od_i, y_od_i, x_do_i, y_do_i, unfinished_i, history_i, yesterday_i, xtime_i, ytime_i) # 注意顺序 118 | self.current_ind += 1 119 | return _wrapper() 120 | 121 | 122 | class StandardScaler_Torch: 123 | """ 124 | Standard the input 125 | """ 126 | 127 | def __init__(self, mean, std, device): 128 | self.mean = torch.tensor(data=mean, dtype=torch.float, device=device) 129 | self.std = torch.tensor(data=std, dtype=torch.float, device=device) 130 | 131 | def transform(self, data): 132 | return (data - self.mean) / self.std 133 | 134 | def inverse_transform(self, data): 135 | return (data * self.std) + self.mean 136 | 137 | 138 | class StandardScaler: 139 | """ 140 | Standard the input 141 | """ 142 | 143 | def __init__(self, mean, std): 144 | self.mean = mean 145 | self.std = std 146 | 147 | def transform(self, data): 148 | return (data - self.mean) / self.std 149 | 150 | def inverse_transform(self, data): 151 | return (data * self.std) + self.mean 152 | 153 | 154 | 155 | def config_logging(log_dir, log_filename='info.log', level=logging.INFO): 156 | # Add file handler and stdout handler 157 | formatter = logging.Formatter( 158 | '%(asctime)s - %(name)s - %(levelname)s - %(message)s') 159 | # Create the log directory if necessary. 160 | try: 161 | os.makedirs(log_dir) 162 | except OSError: 163 | pass 164 | file_handler = logging.FileHandler(os.path.join(log_dir, log_filename)) 165 | file_handler.setFormatter(formatter) 166 | file_handler.setLevel(level=level) 167 | # Add console handler. 168 | console_formatter = logging.Formatter( 169 | '%(asctime)s - %(levelname)s - %(message)s') 170 | console_handler = logging.StreamHandler(sys.stdout) 171 | console_handler.setFormatter(console_formatter) 172 | console_handler.setLevel(level=level) 173 | logging.basicConfig(handlers=[file_handler, console_handler], level=level) 174 | 175 | 176 | def get_logger(log_dir, 177 | name, 178 | log_filename='info.log', 179 | level=logging.INFO, 180 | write_to_file=True): 181 | logger = logging.getLogger(name) 182 | logger.setLevel(level) 183 | logger.propagate = False 184 | # Add file handler and stdout handler 185 | formatter = logging.Formatter( 186 | '%(asctime)s - %(name)s - %(levelname)s - %(message)s') 187 | file_handler = logging.FileHandler(os.path.join(log_dir, log_filename)) 188 | file_handler.setFormatter(formatter) 189 | # Add console handler. 190 | console_formatter = logging.Formatter( 191 | '%(asctime)s - %(levelname)s - %(message)s') 192 | console_handler = logging.StreamHandler(sys.stdout) 193 | console_handler.setFormatter(console_formatter) 194 | if write_to_file is True: 195 | logger.addHandler(file_handler) 196 | logger.addHandler(console_handler) 197 | # Add google cloud log handler 198 | logger.info('Log directory: %s', log_dir) 199 | return logger 200 | 201 | 202 | def load_dataset(dataset_dir, 203 | do_dataset_dir, 204 | batch_size, 205 | test_batch_size=None, 206 | scaler_axis=(0, 207 | 1, 208 | 2, 209 | 3), 210 | **kwargs): 211 | data = {} 212 | 213 | for category in ['train', 'val', 'test']: 214 | cat_data = load_pickle(os.path.join(dataset_dir, category + '.pkl')) 215 | history_data = load_pickle(os.path.join(dataset_dir, category + '_history_long.pkl')) 216 | yesterday_data = load_pickle(os.path.join(dataset_dir, category + '_history_short.pkl')) 217 | do_data = load_pickle(os.path.join(do_dataset_dir, category + '_do.pkl')) 218 | data['x_' + category] = cat_data['finished'] 219 | data['unfinished_' + category] = cat_data['unfinished'] 220 | data['xtime_' + category] = cat_data['xtime'] 221 | data['y_' + category] = cat_data['y'] 222 | data['ytime_' + category] = cat_data['ytime'] 223 | data['do_x_'+ category] = do_data['do_x'] 224 | data['do_y_' + category] = do_data['do_y'] 225 | 226 | his_sum = np.sum(history_data['history'], axis=-1) 227 | history_distribution = np.nan_to_num(np.divide(history_data['history'], np.expand_dims(his_sum, axis=-1))) 228 | data['history_' + category] = np.multiply(history_distribution, cat_data['unfinished']) 229 | 230 | yesterday_sum = np.sum(yesterday_data['history'], axis=-1) 231 | yesterday_distribution = np.nan_to_num(np.divide(yesterday_data['history'], np.expand_dims(yesterday_sum, axis=-1))) 232 | data['yesterday_' + category] = np.multiply(yesterday_distribution, cat_data['unfinished']) 233 | 234 | 235 | scaler = StandardScaler(mean=data['x_train'].mean(axis=scaler_axis), 236 | std=data['x_train'].std(axis=scaler_axis)) 237 | do_scaler = StandardScaler(mean=data['do_x_train'].mean(axis=scaler_axis), 238 | std=data['do_x_train'].std(axis=scaler_axis)) 239 | # Data format 240 | for category in ['train', 'val', 'test']: 241 | data['x_' + category] = scaler.transform(data['x_' + category]) 242 | data['y_' + category] = scaler.transform(data['y_' + category]) 243 | data['do_x_' + category] = do_scaler.transform(data['do_x_' + category]) 244 | data['do_y_' + category] = do_scaler.transform(data['do_y_' + category]) 245 | data['unfinished_' + category] = scaler.transform(data['unfinished_' + category]) 246 | data['history_' + category] = scaler.transform(data['history_' + category]) 247 | data['yesterday_' + category] = scaler.transform(data['yesterday_' + category]) 248 | 249 | data['train_loader'] = DataLoader(data['x_train'], 250 | data['y_train'], 251 | data['unfinished_train'], 252 | data['history_train'], 253 | data['yesterday_train'], 254 | data['do_x_train'], 255 | data['do_y_train'], 256 | data['xtime_train'], 257 | data['ytime_train'], 258 | batch_size, 259 | shuffle=True) 260 | data['val_loader'] = DataLoader(data['x_val'], 261 | data['y_val'], 262 | data['unfinished_val'], 263 | data['history_val'], 264 | data['yesterday_val'], 265 | data['do_x_val'], 266 | data['do_y_val'], 267 | data['xtime_val'], 268 | data['ytime_val'], 269 | test_batch_size, 270 | shuffle=False) 271 | 272 | data['test_loader'] = DataLoader(data['x_test'], 273 | data['y_test'], 274 | data['unfinished_test'], 275 | data['history_test'], 276 | data['yesterday_test'], 277 | data['do_x_test'], 278 | data['do_y_test'], 279 | data['xtime_test'], 280 | data['ytime_test'], 281 | test_batch_size, 282 | shuffle=False) 283 | data['scaler'] = scaler 284 | data['do_scaler'] = do_scaler 285 | 286 | return data 287 | 288 | def load_graph_data(pkl_filename): 289 | adj_mx = load_pickle(pkl_filename) 290 | return adj_mx.astype(np.float32) 291 | 292 | 293 | def load_pickle(pickle_file): 294 | try: 295 | with open(pickle_file, 'rb') as f: 296 | pickle_data = pickle.load(f) 297 | except UnicodeDecodeError as e: 298 | with open(pickle_file, 'rb') as f: 299 | pickle_data = pickle.load(f, encoding='latin1') 300 | except Exception as e: 301 | print('Unable to load data ', pickle_file, ':', e) 302 | raise 303 | return pickle_data 304 | 305 | 306 | class SimpleBatch(list): 307 | 308 | def to(self, device): 309 | for ele in self: 310 | ele.to(device) 311 | return self 312 | 313 | 314 | def collate_wrapper(x_od, y_od, x_do, y_do, unfinished, history, yesterday, edge_index, edge_attr, seq_len, horizon, device, return_y=True): 315 | x_od = torch.tensor(x_od, dtype=torch.float, device=device) 316 | y_od = torch.tensor(y_od, dtype=torch.float, device=device) 317 | x_od = x_od.transpose(dim0=1, dim1=0) # (T, N, num_nodes, num_features) 318 | y_od_T_first = y_od.transpose(dim0=1, dim1=0) # (T, N, num_nodes, num_features) 319 | 320 | x_do = torch.tensor(x_do, dtype=torch.float, device=device) 321 | y_do = torch.tensor(y_do, dtype=torch.float, device=device) 322 | x_do = x_do.transpose(dim0=1, dim1=0) # (T, N, num_nodes, num_features) 323 | y_do_T_first = y_do.transpose(dim0=1, dim1=0) # (T, N, num_nodes, num_features) 324 | 325 | unfinished = torch.tensor(unfinished, dtype=torch.float, device=device) 326 | unfinished = unfinished.transpose(dim0=1, dim1=0) 327 | 328 | history = torch.tensor(history, dtype=torch.float, device=device) 329 | history = history.transpose(dim0=1, dim1=0) 330 | 331 | yesterday = torch.tensor(yesterday, dtype=torch.float, device=device) 332 | yesterday = yesterday.transpose(dim0=1, dim1=0) 333 | # do not tranpose y_truth 334 | T = x_od.size()[0] 335 | H = y_od_T_first.size()[0] 336 | N = x_od.size()[1] 337 | # generate batched sequence. 338 | sequences = [] 339 | sequences_y = [] 340 | for t in range(T - seq_len, T): 341 | cur_batch_x_od = x_od[t] 342 | cur_batch_x_do = x_do[t] 343 | cur_batch_unfinished = unfinished[t] 344 | cur_batch_history = history[t] 345 | cur_batch_yesterday = yesterday[t] 346 | 347 | batch = Batch.from_data_list([ 348 | Data(x_do=cur_batch_x_do[i], 349 | x_od=cur_batch_x_od[i], 350 | unfinished=cur_batch_unfinished[i], 351 | history=cur_batch_history[i], 352 | yesterday=cur_batch_yesterday[i], 353 | edge_index=edge_index, 354 | edge_attr=edge_attr) for i in range(N) 355 | ]) 356 | sequences.append(batch) 357 | 358 | for t in range(H - horizon, H): 359 | cur_batch_y_od = y_od_T_first[t] 360 | cur_batch_y_do = y_do_T_first[t] 361 | 362 | batch_y = Batch.from_data_list([ 363 | Data(y_do=cur_batch_y_do[i], 364 | y_od=cur_batch_y_od[i]) for i in range(N) 365 | ]) 366 | sequences_y.append(batch_y) 367 | if return_y: 368 | return SimpleBatch(sequences), SimpleBatch(sequences_y), y_od, y_do 369 | else: 370 | return SimpleBatch(sequences), SimpleBatch(sequences_y) 371 | 372 | 373 | def collate_wrapper_multi_branches(x_numpy, y_numpy, edge_index_list, device): 374 | sequences_multi_branches = [] 375 | for edge_index in edge_index_list: 376 | sequences, y = collate_wrapper(x_numpy, y_numpy, edge_index, device, return_y=True) 377 | sequences_multi_branches.append(sequences) 378 | 379 | return sequences_multi_branches, y 380 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | import random 3 | import argparse 4 | import time 5 | import yaml 6 | import numpy as np 7 | import torch 8 | import os 9 | 10 | from torch import nn 11 | from torch.nn.utils import clip_grad_norm_ 12 | from torch import optim 13 | from torch.optim.lr_scheduler import MultiStepLR 14 | from torch.nn.init import xavier_uniform_ 15 | from lib import utils_HIAM as utils 16 | from lib import metrics 17 | from lib.utils_HIAM import collate_wrapper 18 | from models.Net import Net 19 | try: 20 | from yaml import CLoader as Loader, CDumper as Dumper 21 | except ImportError: 22 | from yaml import Loader, Dumper 23 | 24 | seed = 1234 25 | random.seed(seed) 26 | np.random.seed(seed) 27 | torch.manual_seed(seed) 28 | parser = argparse.ArgumentParser() 29 | parser.add_argument('--config_filename', 30 | default=None, 31 | type=str, 32 | help='Configuration filename for restoring the model.') 33 | args = parser.parse_args() 34 | 35 | 36 | def read_cfg_file(filename): 37 | with open(filename, 'r') as ymlfile: 38 | cfg = yaml.load(ymlfile, Loader=Loader) 39 | return cfg 40 | 41 | 42 | def run_model(model, data_iterator, edge_index, edge_attr, device, seq_len, horizon, output_dim): 43 | """ 44 | return a list of (horizon_i, batch_size, num_nodes, output_dim) 45 | """ 46 | # while evaluation, we need model.eval and torch.no_grad 47 | model.eval() 48 | y_od_pred_list = [] 49 | y_do_pred_list = [] 50 | for _, (x_od, y_od, x_do, y_do, unfinished, history, yesterday, xtime, ytime) in enumerate(data_iterator): 51 | y_od = y_od[..., :output_dim] 52 | y_do = y_do[..., :output_dim] 53 | sequences, sequences_y, y_od, y_do = collate_wrapper(x_od=x_od, y_od=y_od, x_do=x_do, y_do=y_do, unfinished=unfinished, history=history, yesterday=yesterday, 54 | edge_index=edge_index, 55 | edge_attr=edge_attr, 56 | device=device, 57 | seq_len=seq_len, 58 | horizon=horizon) 59 | # (T, N, num_nodes, num_out_channels) 60 | with torch.no_grad(): 61 | y_od_pred, y_do_pred = model(sequences, sequences_y) 62 | if y_od_pred is not None: 63 | y_od_pred_list.append(y_od_pred.detach().cpu().numpy()) 64 | if y_do_pred is not None: 65 | y_do_pred_list.append(y_do_pred.detach().cpu().numpy()) 66 | return y_od_pred_list, y_do_pred_list 67 | 68 | 69 | def evaluate(model, 70 | dataset, 71 | dataset_type, 72 | edge_index, 73 | edge_attr, 74 | device, 75 | seq_Len, 76 | horizon, 77 | output_dim, 78 | logger, 79 | detail=True, 80 | cfg=None, 81 | format_result=False): 82 | if detail: 83 | logger.info('Evaluation_{}_Begin:'.format(dataset_type)) 84 | 85 | y_od_preds, y_do_preds = run_model( 86 | model, 87 | data_iterator=dataset['{}_loader'.format(dataset_type)].get_iterator(), 88 | edge_index=edge_index, 89 | edge_attr=edge_attr, 90 | device=device, 91 | seq_len=seq_Len, 92 | horizon=horizon, 93 | output_dim=output_dim) 94 | 95 | evaluate_category = [] 96 | if len(y_od_preds) > 0: 97 | evaluate_category.append("od") 98 | if len(y_do_preds) > 0: 99 | evaluate_category.append("do") 100 | results = {} 101 | for category in evaluate_category: 102 | if category == 'od': 103 | y_preds = y_od_preds 104 | scaler = dataset['scaler'] 105 | gt = dataset['y_{}'.format(dataset_type)] 106 | else: 107 | y_preds = y_do_preds 108 | scaler = dataset['do_scaler'] 109 | # scaler = dataset['scaler'] 110 | gt = dataset['do_y_{}'.format(dataset_type)] 111 | y_preds = np.concatenate(y_preds, axis=0) # concat in batch_size dim. 112 | mae_list = [] 113 | mape_net_list = [] 114 | rmse_list = [] 115 | mae_sum = 0 116 | 117 | mape_net_sum = 0 118 | rmse_sum = 0 119 | logger.info("{}:".format(category)) 120 | horizon = cfg['model']['horizon'] 121 | for horizon_i in range(horizon): 122 | y_truth = scaler.inverse_transform( 123 | gt[:, horizon_i, :, :output_dim]) 124 | 125 | y_pred = scaler.inverse_transform( 126 | y_preds[:y_truth.shape[0], horizon_i, :, :output_dim]) 127 | y_pred[y_pred < 0] = 0 128 | mae = metrics.masked_mae_np(y_pred, y_truth) 129 | mape_net = metrics.masked_mape_np(y_pred, y_truth) 130 | rmse = metrics.masked_rmse_np(y_pred, y_truth) 131 | mae_sum += mae 132 | mape_net_sum += mape_net 133 | rmse_sum += rmse 134 | mae_list.append(mae) 135 | 136 | mape_net_list.append(mape_net) 137 | rmse_list.append(rmse) 138 | 139 | msg = "Horizon {:02d}, MAE: {:.2f}, RMSE: {:.2f}, MAPE_net: {:.4f}" 140 | if detail: 141 | logger.info(msg.format(horizon_i + 1, mae, rmse, mape_net)) 142 | results['MAE_' + category] = mae_sum / horizon 143 | results['RMSE_' + category] = rmse_sum / horizon 144 | results['MAPE_net_' + category] = mape_net_sum / horizon 145 | if detail: 146 | logger.info('Evaluation_{}_End:'.format(dataset_type)) 147 | if format_result: 148 | for i in range(len(mae_list)): 149 | print('{:.2f}'.format(mae_list[i])) 150 | print('{:.2f}'.format(rmse_list[i])) 151 | print('{:.2f}%'.format(mape_net_list[i] * 100)) 152 | print() 153 | else: 154 | return results 155 | 156 | 157 | class StepLR2(MultiStepLR): 158 | """StepLR with min_lr""" 159 | def __init__(self, 160 | optimizer, 161 | milestones, 162 | gamma=0.1, 163 | last_epoch=-1, 164 | min_lr=2.0e-6): 165 | 166 | self.optimizer = optimizer 167 | self.milestones = milestones 168 | self.gamma = gamma 169 | self.last_epoch = last_epoch 170 | self.min_lr = min_lr 171 | super(StepLR2, self).__init__(optimizer, milestones, gamma) 172 | 173 | def get_lr(self): 174 | lr_candidate = super(StepLR2, self).get_lr() 175 | if isinstance(lr_candidate, list): 176 | for i in range(len(lr_candidate)): 177 | lr_candidate[i] = max(self.min_lr, lr_candidate[i]) 178 | 179 | else: 180 | lr_candidate = max(self.min_lr, lr_candidate) 181 | 182 | return lr_candidate 183 | 184 | 185 | def _get_log_dir(kwargs): 186 | log_dir = kwargs['train'].get('log_dir') 187 | if log_dir is None: 188 | batch_size = kwargs['data'].get('batch_size') 189 | learning_rate = kwargs['train'].get('base_lr') 190 | num_rnn_layers = kwargs['model'].get('num_rnn_layers') 191 | rnn_units = kwargs['model'].get('rnn_units') 192 | structure = '-'.join(['%d' % rnn_units for _ in range(num_rnn_layers)]) 193 | 194 | # 保存的文件夹名称 195 | run_id = 'HIAM_%s_lr%g_bs%d_%s/' % ( 196 | structure, 197 | learning_rate, 198 | batch_size, 199 | time.strftime('%m%d%H%M%S')) 200 | base_dir = kwargs.get('base_dir') 201 | log_dir = os.path.join(base_dir, run_id) 202 | if not os.path.exists(log_dir): 203 | os.makedirs(log_dir) 204 | return log_dir 205 | 206 | 207 | def init_weights(m): 208 | classname = m.__class__.__name__ # 2 209 | if classname.find('Conv') != -1 and classname.find('RGCN') == -1: 210 | xavier_uniform_(m.weight.data) 211 | if type(m) == nn.Linear: 212 | xavier_uniform_(m.weight.data) 213 | #xavier_uniform_(m.bias.data) 214 | 215 | 216 | def main(args): 217 | cfg = read_cfg_file(args.config_filename) 218 | log_dir = _get_log_dir(cfg) 219 | log_level = cfg.get('log_level', 'INFO') 220 | 221 | logger = utils.get_logger(log_dir, __name__, 'info.log', level=log_level) 222 | 223 | device = torch.device( 224 | 'cuda') if torch.cuda.is_available() else torch.device('cpu') 225 | # all edge_index in same dataset is same 226 | logger.info(cfg) 227 | batch_size = cfg['data']['batch_size'] 228 | seq_len = cfg['model']['seq_len'] 229 | horizon = cfg['model']['horizon'] 230 | 231 | adj_mx_list = [] 232 | graph_pkl_filename = cfg['data']['graph_pkl_filename'] 233 | 234 | if not isinstance(graph_pkl_filename, list): 235 | graph_pkl_filename = [graph_pkl_filename] 236 | 237 | src = [] 238 | dst = [] 239 | for g in graph_pkl_filename: 240 | adj_mx = utils.load_graph_data(g) 241 | for i in range(len(adj_mx)): # 构建邻接矩阵 242 | adj_mx[i, i] = 0 243 | adj_mx_list.append(adj_mx) 244 | 245 | adj_mx = np.stack(adj_mx_list, axis=-1) 246 | print("adj_mx:", adj_mx.shape) 247 | if cfg['model'].get('norm', False): 248 | print('row normalization') 249 | adj_mx = adj_mx / (adj_mx.sum(axis=0) + 1e-18) # 归一化 250 | src, dst = adj_mx.sum(axis=-1).nonzero() 251 | print("src, dst:", src.shape, dst.shape) 252 | edge_index = torch.tensor([src, dst], dtype=torch.long, device=device) 253 | edge_attr = torch.tensor(adj_mx[adj_mx.sum(axis=-1) != 0], 254 | dtype=torch.float, 255 | device=device) 256 | print("train, edge:", edge_index.shape, edge_attr.shape) 257 | output_dim = cfg['model']['output_dim'] 258 | for i in range(adj_mx.shape[-1]): 259 | logger.info(adj_mx[..., i]) 260 | 261 | ## load dataset 262 | dataset = utils.load_dataset(**cfg['data'], scaler_axis=(0, 1, 2, 3)) 263 | for k, v in dataset.items(): 264 | if hasattr(v, 'shape'): 265 | logger.info((k, v.shape)) 266 | 267 | scaler_od = dataset['scaler'] 268 | scaler_od_torch = utils.StandardScaler_Torch(scaler_od.mean, 269 | scaler_od.std, 270 | device=device) 271 | logger.info('scaler_od.mean:{}, scaler_od.std:{}'.format(scaler_od.mean, 272 | scaler_od.std)) 273 | scaler_do = dataset['do_scaler'] 274 | scaler_do_torch = utils.StandardScaler_Torch(scaler_do.mean, 275 | scaler_do.std, 276 | device=device) 277 | logger.info('scaler_do.mean:{}, scaler_do.std:{}'.format(scaler_do.mean, 278 | scaler_do.std)) 279 | ## define model 280 | model = Net(cfg, logger).to(device) 281 | model.apply(init_weights) 282 | criterion = nn.L1Loss(reduction='mean') 283 | optimizer = optim.Adam(model.parameters(), 284 | lr=cfg['train']['base_lr'], 285 | eps=cfg['train']['epsilon']) 286 | scheduler = StepLR2(optimizer=optimizer, 287 | milestones=cfg['train']['steps'], 288 | gamma=cfg['train']['lr_decay_ratio'], 289 | min_lr=cfg['train']['min_learning_rate']) 290 | 291 | max_grad_norm = cfg['train']['max_grad_norm'] 292 | train_patience = cfg['train']['patience'] 293 | 294 | update = {} 295 | for category in ['od', 'do']: 296 | update['val_steady_count_'+category] = 0 297 | update['last_val_mae_'+category] = 1e6 298 | update['last_val_mape_net_'+category] = 1e6 299 | 300 | horizon = cfg['model']['horizon'] 301 | 302 | for epoch in range(cfg['train']['epochs']): 303 | total_loss = 0 304 | i = 0 305 | begin_time = time.perf_counter() 306 | dataset['train_loader'].shuffle() 307 | train_iterator = dataset['train_loader'].get_iterator() 308 | model.train() 309 | for _, (x_od, y_od, x_do, y_do, unfinished, history, yesterday, xtime, ytime) in enumerate(train_iterator): 310 | optimizer.zero_grad() 311 | y_od = y_od[:, :horizon, :, :output_dim] 312 | y_do = y_do[:, :horizon, :, :output_dim] 313 | sequences, sequences_y, y_od, y_do = collate_wrapper( 314 | x_od=x_od, y_od=y_od, x_do=x_do, y_do=y_do, 315 | unfinished=unfinished, history=history, yesterday=yesterday, 316 | edge_index=edge_index, edge_attr=edge_attr, 317 | device=device, seq_len=seq_len, horizon=horizon) # generate batch data 318 | 319 | y_od_pred, y_do_pred = model(sequences, sequences_y) 320 | 321 | # OD loss 322 | y_od_pred = scaler_od_torch.inverse_transform(y_od_pred) # *std+mean 323 | y_od = scaler_od_torch.inverse_transform(y_od) 324 | loss_od = criterion(y_od_pred, y_od) 325 | 326 | # DO loss 327 | y_do_pred = scaler_do_torch.inverse_transform(y_do_pred) # *std+mean 328 | y_do = scaler_do_torch.inverse_transform(y_do) 329 | loss_do = criterion(y_do_pred, y_do) 330 | 331 | loss = loss_od + loss_do 332 | total_loss += loss.item() 333 | loss.backward() 334 | 335 | clip_grad_norm_(model.parameters(), max_grad_norm) 336 | optimizer.step() 337 | 338 | i += 1 339 | 340 | # evaluation on validation set 341 | val_result = evaluate(model=model, 342 | dataset=dataset, 343 | dataset_type='val', 344 | edge_index=edge_index, 345 | edge_attr=edge_attr, 346 | device=device, 347 | seq_Len=seq_len, 348 | horizon=horizon, 349 | output_dim=output_dim, 350 | logger=logger, 351 | detail=False, 352 | cfg=cfg) 353 | 354 | time_elapsed = time.perf_counter() - begin_time 355 | 356 | logger.info(('Epoch:{}, total_loss:{}').format(epoch, total_loss / i)) 357 | val_category = ['od', 'do'] 358 | for category in val_category: 359 | logger.info('{}:'.format(category)) 360 | logger.info(('val_mae:{}, val_mape_net:{}' 361 | 'r_loss={:.2f},lr={}, time_elapsed:{}').format( 362 | val_result['MAE_'+category], 363 | val_result['MAPE_net_' + category], 364 | 0, 365 | str(scheduler.get_lr()), 366 | time_elapsed)) 367 | if update['last_val_mae_'+category] > val_result['MAE_'+category]: 368 | logger.info('val_mae decreased from {} to {}'.format( 369 | update['last_val_mae_' + category], 370 | val_result['MAE_' + category])) 371 | update['last_val_mae_' + category] = val_result['MAE_' + category] 372 | update['val_steady_count_' + category] = 0 373 | else: 374 | update['val_steady_count_' + category] += 1 375 | 376 | if update['last_val_mape_net_'+category] > val_result['MAPE_net_'+category]: 377 | logger.info('val_mape_net decreased from {} to {}'.format( 378 | update['last_val_mape_net_' + category], 379 | val_result['MAPE_net_' + category])) 380 | update['last_val_mape_net_' + category] = val_result['MAPE_net_'+category] 381 | 382 | # after per epoch, run evaluation on test dataset 383 | if (epoch + 1) % cfg['train']['test_every_n_epochs'] == 0: 384 | evaluate(model=model, 385 | dataset=dataset, 386 | dataset_type='test', 387 | edge_index=edge_index, 388 | edge_attr=edge_attr, 389 | device=device, 390 | seq_Len=seq_len, 391 | horizon=horizon, 392 | output_dim=output_dim, 393 | logger=logger, 394 | cfg=cfg) 395 | 396 | if (epoch + 1) % cfg['train']['save_every_n_epochs'] == 0: 397 | save_dir = log_dir 398 | if not os.path.exists(save_dir): 399 | os.mkdir(save_dir) 400 | config_path = os.path.join(save_dir, 401 | 'config-{}.yaml'.format(epoch + 1)) 402 | epoch_path = os.path.join(save_dir, 403 | 'epoch-{}.pt'.format(epoch + 1)) 404 | torch.save(model.state_dict(), epoch_path) 405 | with open(config_path, 'w') as f: 406 | from copy import deepcopy 407 | save_cfg = deepcopy(cfg) 408 | save_cfg['model']['save_path'] = epoch_path 409 | f.write(yaml.dump(save_cfg, Dumper=Dumper)) 410 | 411 | if train_patience <= update['val_steady_count_od'] or train_patience <= update['val_steady_count_do']: 412 | logger.info('early stopping.') 413 | break 414 | scheduler.step() 415 | 416 | 417 | if __name__ == "__main__": 418 | main(args) 419 | --------------------------------------------------------------------------------