├── DGCRNN.ipynb ├── LICENSE ├── README.md ├── custom_model ├── __pycache__ │ ├── attention_layer.cpython-37.pyc │ ├── dgcn.cpython-35.pyc │ ├── dgcn.cpython-37.pyc │ ├── graph.cpython-37.pyc │ ├── layers_keras.cpython-35.pyc │ ├── layers_keras.cpython-37.pyc │ ├── math_utils.cpython-37.pyc │ ├── model.cpython-37.pyc │ └── model_keras.cpython-37.pyc ├── layers_keras.py ├── math_utils.py └── model_keras.py ├── model_interpretation.ipynb ├── pretrained ├── amsnetf.h5 ├── cc2-k4.h5 ├── cc5-k9.h5 ├── cl2-k4.h5 └── cl5-k9.h5 └── utils_vis.py /DGCRNN.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from __future__ import print_function\n", 10 | "import os\n", 11 | "import tensorflow as tf\n", 12 | "import keras\n", 13 | "\n", 14 | "config = tf.ConfigProto( device_count = {'GPU': 1 , 'CPU': 56} ) \n", 15 | "sess = tf.Session(config=config) \n", 16 | "keras.backend.set_session(sess)\n", 17 | "\n", 18 | "from custom_model.layers_keras import *\n", 19 | "from custom_model.model_keras import *\n", 20 | "from custom_model.math_utils import *\n", 21 | "from keras import metrics\n", 22 | "\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "%matplotlib inline\n", 25 | "import h5py\n", 26 | "import numpy as np\n", 27 | "\n", 28 | "import warnings\n", 29 | "warnings.filterwarnings(\"ignore\")" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "dt = 2\n", 39 | "OBS = 30//dt-1\n", 40 | "PRED = 20//dt" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "Data = h5py.File('Datasets/RRot_cc2_20.h5', 'r')\n", 50 | "x_train = np.array(Data['Speed_obs_train'])\n", 51 | "y_train = np.array(Data['Speed_pred_train'])\n", 52 | "e_train = np.array(Data['E_train'])\n", 53 | "x_test = np.array(Data['Speed_obs_test'])\n", 54 | "y_test = np.array(Data['Speed_pred_test'])\n", 55 | "e_test = np.array(Data['E_test'])\n", 56 | "\n", 57 | "x_size = x_train.shape[1:]\n", 58 | "y_size = y_train.shape[1:]\n", 59 | "\n", 60 | "print('x_train shape:', x_train.shape)\n", 61 | "print('y_train shape:', y_train.shape)\n", 62 | "print('e_train shape:', e_train.shape)\n", 63 | "print(x_train.shape[0], 'train samples')\n", 64 | "print(x_test.shape[0], 'test samples')\n", 65 | "print(y_train.shape[1:], 'output size')\n", 66 | "\n", 67 | "x_train = x_train[:,-OBS:]\n", 68 | "x_test = x_test[:,-OBS:]\n", 69 | "y_train = y_train[:,:PRED]\n", 70 | "e_train =e_train[:,:PRED]\n", 71 | "y_test = y_test[:,:PRED]\n", 72 | "e_test =e_test[:,:PRED]\n", 73 | "#'''\n", 74 | "X_train = z_score(x_train, np.mean(x_train), np.std(x_train))\n", 75 | "X_test = z_score(x_test, np.mean(x_test), np.std(x_test))\n", 76 | "Y_train = z_score(y_train, np.mean(y_train), np.std(y_train))\n", 77 | "Y_test = z_score(x_test, np.mean(y_test), np.std(y_test))\n", 78 | "E_train = z_score(e_train, np.mean(e_train), np.std(e_train))\n", 79 | "E_test = z_score(e_test, np.mean(e_test), np.std(e_test))\n", 80 | "#'''\n", 81 | "print(x_train.shape, y_train.shape)" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": null, 87 | "metadata": {}, 88 | "outputs": [], 89 | "source": [ 90 | "model = create_embed_model(obs_timesteps=OBS, pred_timesteps=PRED, nb_nodes=35, k=1)\n", 91 | "model.summary()" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": null, 97 | "metadata": {}, 98 | "outputs": [], 99 | "source": [ 100 | "# load pretrained models, optional\n", 101 | "file=h5py.File('pretrained/cc2-k4.h5','r')\n", 102 | "weight = []\n", 103 | "for i in range(len(file.keys())):\n", 104 | " weight.append(file['weight'+str(i)][:])\n", 105 | "model.set_weights(weight)" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "metadata": { 112 | "scrolled": true 113 | }, 114 | "outputs": [], 115 | "source": [ 116 | "# Let's train the model\n", 117 | "opt = keras.optimizers.Adam(lr=0.001, decay=1e-3)\n", 118 | "\n", 119 | "model.compile(loss = rmse,\n", 120 | " optimizer=opt,\n", 121 | " metrics=['mae', 'mape'])\n", 122 | "\n", 123 | "callbacks = [EarlyStopping(monitor='val_loss', patience=5),\n", 124 | " ModelCheckpoint(filepath='\\pretrained\\test.h5', save_weights_only=True, monitor='val_loss', save_best_only=True),\n", 125 | " ScheduledSampling(k=4)\n", 126 | " ]\n", 127 | "\n", 128 | "history = model.fit([X_train,E_train], Y_train,\n", 129 | " epochs=256,\n", 130 | " batch_size=128,\n", 131 | " callbacks=callbacks,\n", 132 | " #validation_data = ([x_val,e_val], y_val), optional\n", 133 | " shuffle='batch',\n", 134 | " validation_split = 0.2\n", 135 | " )" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": null, 141 | "metadata": {}, 142 | "outputs": [], 143 | "source": [ 144 | "y = model.predict([X_test,E_test])\n", 145 | "y = z_inverse(y, np.mean(x_test), np.std(x_test))\n", 146 | "\n", 147 | "for i in range(1,PRED+1): \n", 148 | " print(MAE(y_test[:,:i]*120, y[:,:i]*120), ' ',100*MAPE(y_test[:,:i]*120, y[:,:i]*120), ' ',RMSE(y_test[:,:i]*120, y[:,:i]*120))\n", 149 | "print('##########')\n", 150 | "for i in range(PRED):\n", 151 | " print(MAE(y_test[:,i]*120, y[:,i]*120), ' ',100*MAPE(y_test[:,i]*120, y[:,i]*120), ' ',RMSE(y_test[:,i]*120, y[:,i]*120))" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": null, 157 | "metadata": {}, 158 | "outputs": [], 159 | "source": [ 160 | "#save weights, in this way to avoid tensorflow version conflict\n", 161 | "file = h5py.File('pretrained/cl5-k9.h5','w')\n", 162 | "weight = model.get_weights()\n", 163 | "for i in range(len(weight)):\n", 164 | " file.create_dataset('weight'+str(i),data=weight[i])\n", 165 | "file.close()" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "metadata": {}, 172 | "outputs": [], 173 | "source": [ 174 | "# compare predictions and groundtruth\n", 175 | "day = 2\n", 176 | "j=5 # prediction steps\n", 177 | "start = 150*(day-1)\n", 178 | "end = start+150\n", 179 | "gt = [y_test[i] for i in range(start,end,PRED)]\n", 180 | "pred = [y[i][:j] for i in range(start,end,j)]\n", 181 | "\n", 182 | "ground_truth = np.concatenate(gt, axis=0)\n", 183 | "prediction = np.concatenate(pred, axis=0)\n", 184 | "\n", 185 | "\n", 186 | "plt.figure(figsize = (20,7))\n", 187 | "plt.imshow(ground_truth.squeeze().transpose(),aspect = 'auto')\n", 188 | "plt.title('matrix')\n", 189 | "plt.colorbar()\n", 190 | "\n", 191 | "plt.figure(figsize = (20,7))\n", 192 | "plt.imshow(prediction.squeeze().transpose(),aspect = 'auto')\n", 193 | "plt.title('matrix')\n", 194 | "plt.colorbar()" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": null, 200 | "metadata": {}, 201 | "outputs": [], 202 | "source": [ 203 | "# save figs if necessary\n", 204 | "from utils_vis import *\n", 205 | "plot_figure(prediction, title1 = '10min prediction of DGCN on RotCC2 (k=0)', title2 = ' ', nb = 208, figtitle='k0.PNG', time=150, dt=2/60, color='autumn')" 206 | ] 207 | } 208 | ], 209 | "metadata": { 210 | "kernelspec": { 211 | "display_name": "Python 3", 212 | "language": "python", 213 | "name": "python3" 214 | }, 215 | "language_info": { 216 | "codemirror_mode": { 217 | "name": "ipython", 218 | "version": 3 219 | }, 220 | "file_extension": ".py", 221 | "mimetype": "text/x-python", 222 | "name": "python", 223 | "nbconvert_exporter": "python", 224 | "pygments_lexer": "ipython3", 225 | "version": "3.7.3" 226 | } 227 | }, 228 | "nbformat": 4, 229 | "nbformat_minor": 2 230 | } 231 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 RomainLITUD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multistep Traffic Forecasting by Dynamic Graph Convolution: Interpretations of Real-Time Spatial Correlations 2 | 3 | ## ONLINE DEMO 4 | 5 | We have an online Streamlit App to show how the extended version of this model works. Please go to [this page](https://nltrafficpredictiondemo-ekpqd7e8sdue92ewyttuuw.streamlit.app/) for the interactive demo. The related code is available [here](https://github.com/RomainLITUD/NL_Traffic_Prediction_Demo). This demo uses PyTorch and will always be active. 6 | 7 | ## !UPDATE! 8 | This repository uses a very old version of Tensorflow (1.14.0) and Keras (2.3.1). To make the code easier to use, we extend the dynamic graph convolutional module to uncertainty-aware multiple traffic quantities prediction and demand estimation. The new source code converts the layers in this paper to the newest version of PyTorch. An interactive online web application is also provided to give predictions and model interpretations. Please go to [this new page](https://github.com/RomainLITUD/uncertainty-aware-traffic-speed-flow-demand-prediction) for more details. This old repository will not be updated. 9 | 10 | ------------------------------------------------- 11 | ------------------------------------------------- 12 | ------------------------------------------------- 13 | 14 | ## Old description 15 | 16 | This is the open source Keras code of the proposed Dynamic Graph Convolutional Networks (DGCN), a multistep network-level traffic condition forecasting model that can capture and explicitly give understandable spatial correlations among road links. 17 | 18 | An example of the dataset used in the article (RotCC2) can be downloaded here: https://drive.google.com/file/d/1UCWmA-vLp3LSu1IFSiwdVMXSvdfsVFf9/view?usp=sharing. 19 | 20 | For more datasets please visit DittLab-TUD: https://dittlab.tudelft.nl/, or our online traffic dynamics visualization website: http://dittlab-apps.tudelft.nl/apps/app-ndw/home.jsp, or directly send an email to one of the author: G.Li-5@tudelft.nl 21 | 22 | The meta-description of the dataset is as follows. `x_train` is the observed speed, `e_train` is the input labels for scheduled sampling, `y_train` is labeld to be predicted, the same for test set: 23 | ```bash 24 | x_train = Data['Speed_obs_train'] 25 | y_train = Data['Speed_pred_train'] 26 | e_train = Data['E_train'] 27 | x_test = Data['Speed_obs_test'] 28 | y_test = Data['Speed_pred_test'] 29 | e_test = Data['E_test'] 30 | ``` 31 | 32 | To reproduce the results in the paper, please put the corresponding datasets in the "Datasets" file. A command-line parsed `.py` file will be added before 1st March. 33 | 34 | ![archi](https://user-images.githubusercontent.com/48381256/98677777-cd67e180-235d-11eb-9fd6-4aaaefc790f1.PNG) 35 | 36 | 37 | ## File strucuture 38 | ```bash 39 | . 40 | |-custom_models 41 | |-layers_keras.py # custom keras layers and DGCN RNN cell 42 | |-model_keras.py # construct DGCN model 43 | |-math_utils.py # mathematical tools 44 | |-pretrained # pre-trained models to reproduce the results in the paper 45 | |-DGCRNN.ipynb # train/test the model, visualize predictions 46 | |-model_interpretation.ipynb # interpret dynamic spatial correlations 47 | |-utils_vis.py # visulization tools 48 | ``` 49 | 50 | ## Requirements 51 | * scipy 0.19.0 52 | * numpy 1.12.1 53 | * h5py 54 | * statsmodels 55 | * tensorflow 1.14.0 or 1.15.0 56 | * keras 2.3.1 or 2.2.5 57 | * networkx 2.5.0 (for tracking attention distribution in a complex graph) 58 | 59 | \* for early versions of tensorflow and keras the modelcheckpoint may fail. 60 | 61 | -------------------------------------------------------------------------------- /custom_model/__pycache__/attention_layer.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/custom_model/__pycache__/attention_layer.cpython-37.pyc -------------------------------------------------------------------------------- /custom_model/__pycache__/dgcn.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/custom_model/__pycache__/dgcn.cpython-35.pyc -------------------------------------------------------------------------------- /custom_model/__pycache__/dgcn.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/custom_model/__pycache__/dgcn.cpython-37.pyc -------------------------------------------------------------------------------- /custom_model/__pycache__/graph.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/custom_model/__pycache__/graph.cpython-37.pyc -------------------------------------------------------------------------------- /custom_model/__pycache__/layers_keras.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/custom_model/__pycache__/layers_keras.cpython-35.pyc -------------------------------------------------------------------------------- /custom_model/__pycache__/layers_keras.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/custom_model/__pycache__/layers_keras.cpython-37.pyc -------------------------------------------------------------------------------- /custom_model/__pycache__/math_utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/custom_model/__pycache__/math_utils.cpython-37.pyc -------------------------------------------------------------------------------- /custom_model/__pycache__/model.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/custom_model/__pycache__/model.cpython-37.pyc -------------------------------------------------------------------------------- /custom_model/__pycache__/model_keras.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/custom_model/__pycache__/model_keras.cpython-37.pyc -------------------------------------------------------------------------------- /custom_model/layers_keras.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from custom_model.math_utils import * 3 | import random 4 | import numpy as np 5 | 6 | from keras import activations, initializers, constraints, regularizers 7 | import keras.backend as K 8 | from keras.layers import Layer, Dense, LeakyReLU, Dropout, Reshape, Flatten 9 | from keras.callbacks import Callback, EarlyStopping, ModelCheckpoint 10 | ############################ 11 | 12 | dim = 208 # number of nodes 13 | # construct adjacency matrix and scaled Laplacian for a circle graph with dim nodes 14 | A = adjacency_matrix(dim) # for ROTnet 15 | #Ad, Au, A = directed_adj() # for AMSnet 16 | scaled_laplacian = normalized_laplacian(A) 17 | 18 | ############################ 19 | class LocallyConnectedGC(Layer): 20 | """ 21 | Locally-connected graph convolutional layer 22 | Arguments: 23 | k: receptive field, k-hop neighbors 24 | scaled_lap: scaled Laplacian matrix 25 | """ 26 | 27 | def __init__(self, k, 28 | scaled_lap=scaled_laplacian, 29 | activation=None, 30 | use_bias=True, 31 | kernel_initializer='glorot_uniform', 32 | bias_initializer='zeros', 33 | kernel_regularizer=None, 34 | bias_regularizer=None, 35 | activity_regularizer=None, 36 | kernel_constraint=None, 37 | bias_constraint=None, 38 | **kwargs): 39 | if 'input_shape' not in kwargs and 'input_dim' in kwargs: 40 | kwargs['input_shape'] = (kwargs.pop('input_dim'),) 41 | super(LocallyConnectedGC, self).__init__(**kwargs) 42 | 43 | self.k = k 44 | self.scaled_lap = scaled_lap 45 | self.activation = activations.get(activation) 46 | self.use_bias = use_bias 47 | self.kernel_initializer = initializers.get(kernel_initializer) 48 | self.bias_initializer = initializers.get(bias_initializer) 49 | self.kernel_regularizer = regularizers.get(kernel_regularizer) 50 | self.bias_regularizer = regularizers.get(bias_regularizer) 51 | self.activity_regularizer = regularizers.get(activity_regularizer) 52 | self.kernel_constraint = constraints.get(kernel_constraint) 53 | self.bias_constraint = constraints.get(bias_constraint) 54 | self.supports_masking = True 55 | self.supports = [] 56 | # k-hop adjacency matrix 57 | S = K.constant(K.to_dense(calculate_adjacency_k(self.scaled_lap, self.k))) 58 | self.supports.append(S) 59 | 60 | def compute_output_shape(self, input_shapes): 61 | return input_shapes 62 | 63 | def build(self, input_shapes): 64 | nb_nodes = input_shapes[1] 65 | feature_dim = input_shapes[-1] 66 | 67 | self.kernel = self.add_weight(shape=(nb_nodes, nb_nodes), 68 | initializer=self.kernel_initializer, 69 | name='kernel', 70 | regularizer=self.kernel_regularizer, 71 | constraint=self.kernel_constraint) 72 | 73 | if self.use_bias: 74 | self.bias = self.add_weight(shape=(feature_dim,), 75 | initializer=self.bias_initializer, 76 | name='bias', 77 | regularizer=self.bias_regularizer, 78 | constraint=self.bias_constraint) 79 | else: 80 | self.bias = None 81 | self.built = True 82 | 83 | def call(self, inputs, mask=None): 84 | features = inputs 85 | W = self.supports[0]*self.kernel 86 | X = K.dot(K.permute_dimensions(features, (0,2,1)), W) 87 | outputs = K.permute_dimensions(X, (0,2,1)) 88 | if self.use_bias: 89 | outputs += self.bias 90 | return self.activation(outputs) 91 | 92 | ###################################### 93 | class Linear(Layer): 94 | """ 95 | Node-wise dense layer wih given output dimension 96 | Arguments: 97 | units: dimension of output 98 | """ 99 | 100 | def __init__(self, units, 101 | activation=None, 102 | use_bias=True, 103 | kernel_initializer='glorot_uniform', 104 | bias_initializer='zeros', 105 | kernel_regularizer=None, 106 | bias_regularizer=None, 107 | activity_regularizer=None, 108 | kernel_constraint=None, 109 | bias_constraint=None, 110 | **kwargs): 111 | if 'input_shape' not in kwargs and 'input_dim' in kwargs: 112 | kwargs['input_shape'] = (kwargs.pop('input_dim'),) 113 | super(Linear, self).__init__(**kwargs) 114 | self.units = units 115 | self.activation = activations.get(activation) 116 | self.use_bias = use_bias 117 | self.kernel_initializer = initializers.get(kernel_initializer) 118 | self.bias_initializer = initializers.get(bias_initializer) 119 | self.kernel_regularizer = regularizers.get(kernel_regularizer) 120 | self.bias_regularizer = regularizers.get(bias_regularizer) 121 | self.activity_regularizer = regularizers.get(activity_regularizer) 122 | self.kernel_constraint = constraints.get(kernel_constraint) 123 | self.bias_constraint = constraints.get(bias_constraint) 124 | self.supports_masking = True 125 | 126 | def compute_output_shape(self, input_shapes): 127 | output_shape = (None, input_shapes[1], self.units) 128 | return output_shape 129 | 130 | def build(self, input_shapes): 131 | input_dim = input_shapes[-1] 132 | self.kernel_lin = self.add_weight(shape=(input_dim, self.units), 133 | initializer=self.kernel_initializer, 134 | name='kernel_lin', 135 | regularizer=self.kernel_regularizer, 136 | constraint=self.kernel_constraint) 137 | 138 | if self.use_bias: 139 | self.bias = self.add_weight(shape=(self.units,), 140 | initializer=self.bias_initializer, 141 | name='bias', 142 | regularizer=self.bias_regularizer, 143 | constraint=self.bias_constraint) 144 | else: 145 | self.bias = None 146 | self.built = True 147 | 148 | def call(self, inputs, mask=None): 149 | outputs = K.dot(inputs, self.kernel_lin) 150 | if self.use_bias: 151 | outputs += self.bias 152 | return self.activation(outputs) 153 | 154 | ############################ 155 | class Scheduled(Layer): 156 | """ 157 | Scheduled sampling layer, input = [ground_truth, previous_prediction] 158 | Arguments: 159 | coin: probability to use previous prediction, defaut = 0 160 | Methods: 161 | reset_coin: to change the probability 162 | """ 163 | 164 | def __init__(self, coin=0., 165 | **kwargs): 166 | if 'input_shape' not in kwargs and 'input_dim' in kwargs: 167 | kwargs['input_shape'] = (kwargs.pop('input_dim'),) 168 | super(Scheduled, self).__init__(**kwargs) 169 | self.coin = coin 170 | 171 | def compute_output_shape(self, input_shapes): 172 | return input_shapes[-1] 173 | 174 | def build(self, input_shapes): 175 | self.built = True 176 | 177 | def reset_coin(self, s): 178 | self.coin = s 179 | 180 | def call(self, inputs, mask=None): 181 | rand = random.uniform(0, 1) 182 | if rand > self.coin: 183 | outputs = K.permute_dimensions(inputs[0], (0,2,1)) 184 | else: 185 | outputs = inputs[1] 186 | return outputs 187 | 188 | def get_config(self): 189 | config = {'coin': self.coin} 190 | 191 | base_config = super(Scheduled, self).get_config() 192 | return dict(list(base_config.items()) + list(config.items())) 193 | 194 | ################################## 195 | class DynamicGCf(Layer): 196 | """ 197 | Dynamic graph convolutional module, Eq-(5)/(DGC) in the paper 198 | Arguments: 199 | k: receptive field 200 | units: output dimension 201 | normalization: use softmax normalization or not, defaut = None 202 | attn_heads=1, 203 | attn_heads_reduction='concat', # {'concat', 'average'} 204 | scaled_lap = scaled_laplacian, 205 | """ 206 | 207 | def __init__(self, 208 | k, 209 | units, 210 | normalization=False, 211 | attn_heads=1, 212 | attn_heads_reduction='concat', # {'concat', 'average'} 213 | scaled_lap = scaled_laplacian, 214 | activation=None, 215 | use_bias=True, 216 | kernel_initializer='glorot_uniform', 217 | bias_initializer='zeros', 218 | kernel_regularizer=None, 219 | bias_regularizer=None, 220 | activity_regularizer=None, 221 | kernel_constraint=None, 222 | bias_constraint=None, 223 | **kwargs): 224 | if attn_heads_reduction not in {'concat', 'average'}: 225 | raise ValueError('Possbile reduction methods: concat, average') 226 | 227 | self.k = k 228 | self.units = units 229 | self.normalization = normalization 230 | self.attn_heads = attn_heads # Number of attention heads (K in the paper) 231 | self.attn_heads_reduction = attn_heads_reduction # Eq. 5 and 6 in the paper 232 | self.scaled_lap = scaled_lap 233 | self.activation = activations.get(activation) # Eq. 4 in the paper 234 | self.use_bias = use_bias 235 | 236 | self.kernel_initializer = initializers.get(kernel_initializer) 237 | self.bias_initializer = initializers.get(bias_initializer) 238 | 239 | self.kernel_regularizer = regularizers.get(kernel_regularizer) 240 | self.bias_regularizer = regularizers.get(bias_regularizer) 241 | self.activity_regularizer = regularizers.get(activity_regularizer) 242 | 243 | self.kernel_constraint = constraints.get(kernel_constraint) 244 | self.bias_constraint = constraints.get(bias_constraint) 245 | self.supports_masking = False 246 | 247 | # Populated by build() 248 | self.kernels = [] # Layer kernels for attention heads 249 | self.parameters = [] 250 | self.fc = [] 251 | self.biases = [] # Layer biases for attention heads 252 | 253 | self.supports = [] 254 | s = K.constant(K.to_dense(calculate_adjacency_k(self.scaled_lap, self.k))) 255 | self.supports.append(s) 256 | 257 | super(DynamicGCf, self).__init__(**kwargs) 258 | 259 | def build(self, input_shape): 260 | F = input_shape[-1] 261 | N = input_shape[-2] 262 | 263 | # Initialize weights for each attention head 264 | for head in range(self.attn_heads): 265 | # Layer kernel 266 | kernel = self.add_weight(shape=(F, N), 267 | initializer=self.kernel_initializer, 268 | regularizer=self.kernel_regularizer, 269 | constraint=self.kernel_constraint, 270 | name='kernel_{}'.format(head)) 271 | 272 | self.kernels.append(kernel) 273 | # Layer parameters 274 | parameter = self.add_weight(shape=(N, N), 275 | initializer=self.kernel_initializer, 276 | regularizer=self.kernel_regularizer, 277 | constraint=self.kernel_constraint, 278 | name='parameter_{}'.format(head)) 279 | self.parameters.append(parameter) 280 | 281 | # # Layer bias 282 | if self.use_bias: 283 | bias1 = self.add_weight(shape=(N, ), 284 | initializer=self.bias_initializer, 285 | regularizer=self.bias_regularizer, 286 | constraint=self.bias_constraint, 287 | name='bias1_{}'.format(head)) 288 | 289 | bias2 = self.add_weight(shape=(self.units, ), 290 | initializer=self.bias_initializer, 291 | regularizer=self.bias_regularizer, 292 | constraint=self.bias_constraint, 293 | name='bias2_{}'.format(head)) 294 | self.biases.append([bias1, bias2]) 295 | 296 | self.built = True 297 | 298 | def call(self, inputs): 299 | X = inputs # Node features (N x F) 300 | speed = inputs[:,:,:1] 301 | A = self.supports[0] # Adjacency matrix (N x N) 302 | 303 | outputs = [] 304 | for head in range(self.attn_heads): 305 | kernel = self.kernels[head]#[1] # W in the paper (F x N) 306 | W = self.parameters[head] 307 | bias_N = self.biases[head][0] 308 | bias_units = self.biases[head][1] 309 | 310 | feature = K.dot(K.permute_dimensions(X, (0,2,1)), A*W) 311 | dense = K.dot(K.permute_dimensions(feature, (0,2,1)), kernel) 312 | #dense = K.dot(dense, kernel) 313 | if self.use_bias: 314 | dense = K.bias_add(dense, bias_N) 315 | 316 | if self.normalization: 317 | mask = dense + -10e15 * (1.0 - A) 318 | mask = K.softmax(mask) 319 | else: 320 | mask = dense*A 321 | 322 | # Linear combination with neighbors' features 323 | #node_features = K.batch_dot(mask, feature) 324 | node_features = K.batch_dot(mask, speed) # (N x F) 325 | 326 | if self.use_bias: 327 | node_features = K.bias_add(node_features, bias_units) 328 | 329 | # Add output of attention head to final output 330 | outputs.append(node_features) 331 | 332 | # Aggregate the heads' output according to the reduction method 333 | if self.attn_heads_reduction == 'concat': 334 | output = K.concatenate(outputs) # (N x KF') 335 | else: 336 | output = K.mean(K.stack(outputs), axis=1) # N x F') 337 | 338 | output = self.activation(output) 339 | return output 340 | 341 | # for model interpretation 342 | #return K.concatenate([output, mask], axis=-1) 343 | 344 | def compute_output_shape(self, input_shapes): 345 | if self.attn_heads_reduction == 'concat': 346 | output_shape = (input_shapes[0], input_shapes[1], self.units*self.attn_heads) 347 | 348 | # for model interpretation 349 | #output_shape = (input_shapes[0], input_shapes[1], self.units*self.attn_heads + input_shapes[1]) 350 | else: 351 | output_shape = (input_shapes[0], input_shapes[1], self.units) 352 | return output_shape 353 | 354 | ########################### 355 | class DGCRNNCell(Layer): 356 | """ 357 | RNN cell with GRU structure and spatial attention gates 358 | Eq-(DGGRU) in the paper 359 | Arguments: 360 | k: receptive field - 1 361 | dgc_mode: spatial attention module, 362 | {'dgc','gan','lc'} 363 | """ 364 | 365 | def __init__(self, k, 366 | dgc_mode='hybrid', 367 | activation='tanh', 368 | recurrent_activation='hard_sigmoid', 369 | **kwargs): 370 | if 'input_shape' not in kwargs and 'input_dim' in kwargs: 371 | kwargs['input_shape'] = (kwargs.pop('input_dim'),) 372 | super(DGCRNNCell,self).__init__(**kwargs) 373 | self.k = k 374 | self.dgc_mode = dgc_mode 375 | self.state_size = dim 376 | self.activation = activation 377 | self.recurrent_activation = recurrent_activation 378 | self.supports_masking = True 379 | 380 | def build(self, input_shape): 381 | #input_shape = (bs, nb_nodes) 382 | shapes = (input_shape[0], input_shape[1], 2) 383 | inner = (input_shape[0], input_shape[1], 2*self.k+1) 384 | shapes_1 = (input_shape[0], input_shape[1], 1) 385 | 386 | if self.dgc_mode == 'hybrid': 387 | self.dgc_r = LocallyConnectedGC(2)#, depthwise=False 388 | self.dgc_r.build(shapes) 389 | wr_1 = self.dgc_r.trainable_weights 390 | 391 | self.lin_r = Linear(1, activation=self.recurrent_activation) 392 | self.lin_r.build(shapes) 393 | wr_2 = self.lin_r.trainable_weights 394 | 395 | self.dgc_u = LocallyConnectedGC(2)#, depthwise=False 396 | self.dgc_u.build(shapes) 397 | wu_1 = self.dgc_u.trainable_weights 398 | 399 | self.lin_u = Linear(1, activation=self.recurrent_activation) 400 | self.lin_u.build(shapes) 401 | wu_2 = self.lin_u.trainable_weights 402 | 403 | self.dgc_c = LocallyConnectedGC(2)#, depthwise=False 404 | self.dgc_c.build(shapes) 405 | wc_1 = self.dgc_c.trainable_weights 406 | 407 | self.lin_c = Linear(1, activation=self.activation) 408 | self.lin_c.build(shapes) 409 | wc_2 = self.lin_c.trainable_weights 410 | 411 | self.core = DynamicGCf(k=self.k, units=1, normalization=True, activation = None) 412 | self.core.build(shapes) 413 | w_1 = self.core.trainable_weights 414 | 415 | 416 | w = wr_1+wr_2+wu_1+wu_2+wc_1+wc_2+w_1 417 | 418 | 419 | self._trainable_weights = w 420 | self.built = True 421 | 422 | def call(self, inputs, states): 423 | 424 | feature_ru1 = K.concatenate([K.expand_dims(inputs), K.expand_dims(states[0])]) #(bs, nb_nodes, 2) 425 | p = self.core(feature_ru1) 426 | feature_ru = K.concatenate([p, K.expand_dims(states[0])]) 427 | 428 | r = self.dgc_r(feature_ru) #(bs, nb_nodes, 2) 429 | r = self.lin_r(r) #(bs, nb_nodes, 1) 430 | 431 | u = self.dgc_u(feature_ru) #(bs, nb_nodes, 2) 432 | u = self.lin_u(u) #(bs, nb_nodes, 1) 433 | 434 | s = r*K.expand_dims(states[0]) #(bs, nb_nodes, 1) 435 | feature_c = K.concatenate([K.expand_dims(inputs), s]) #(bs, nb_nodes, 2) 436 | 437 | c = self.dgc_c(feature_c) #(bs, nb_nodes, 2) 438 | c = self.lin_c(c) #(bs, nb_nodes, 1) 439 | 440 | H = u*K.expand_dims(states[0]) + (1-u)*c 441 | 442 | return K.squeeze(p, axis=-1), [K.squeeze(H, axis=-1)] 443 | 444 | # for model interpretation, use the following code instead: 445 | ''' 446 | feature_ru1 = K.concatenate([K.expand_dims(inputs), K.expand_dims(states[0])]) #(bs, nb_nodes, 2) 447 | p = self.core(feature_ru1) 448 | feature_ru = K.concatenate([p[...,:1], K.expand_dims(states[0])]) 449 | 450 | r = self.dgc_r(feature_ru) #(bs, nb_nodes, 2) 451 | r = self.lin_r(r) #(bs, nb_nodes, 1) 452 | 453 | u = self.dgc_u(feature_ru) #(bs, nb_nodes, 2) 454 | u = self.lin_u(u) #(bs, nb_nodes, 1) 455 | 456 | s = r*K.expand_dims(states[0]) #(bs, nb_nodes, 1) 457 | feature_c = K.concatenate([K.expand_dims(inputs), s]) #(bs, nb_nodes, 2) 458 | 459 | c = self.dgc_c(feature_c) #(bs, nb_nodes, 2) 460 | c = self.lin_c(c) #(bs, nb_nodes, 1) 461 | 462 | H = u*K.expand_dims(states[0]) + (1-u)*c 463 | return p, [K.squeeze(H, axis=-1)] 464 | '' 465 | ################################ 466 | class ScheduledSampling(Callback): 467 | ''' 468 | Custom Callbacks to use teacher forcing and schedule sampling 469 | strategy in training. 470 | see `https://arxiv.org/pdf/1506.03099.pdf` 471 | # Argument: 472 | k: Interger, the coefficient to control convergence speed 473 | model: the keras model to apply 474 | ''' 475 | 476 | def __init__(self, k, **kwargs): 477 | self.k = k 478 | super(ScheduledSampling, self).__init__(**kwargs) 479 | 480 | def on_train_epoch_begin(self, epoch, logs=None): 481 | prob_use_gt = self.k / (self.k+math.exp(epoch/self.k)) 482 | for layer in self.model.layers: 483 | if ('reset_coin' in dir(layer)): 484 | layer.reset_coin(prob_use_gt) 485 | 486 | def on_test_batch_begin(self, batch, logs=None): 487 | for layer in self.model.layers: 488 | if ('reset_coin' in dir(layer)): 489 | layer.reset_coin(0.) 490 | def on_epoch_end(self, epoch, logs=None): 491 | for layer in self.model.layers: 492 | if ('reset_coin' in dir(layer)): 493 | layer.reset_coin(0.) 494 | -------------------------------------------------------------------------------- /custom_model/math_utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy.sparse as sp 3 | import keras.backend as K 4 | import networkx as nx 5 | 6 | def directed_adj(): 7 | acc = [-1,36,49,61,79,105,116,151,162,200] 8 | 9 | base = np.identity(201,dtype=bool) 10 | 11 | for i in range(0,201): 12 | if i not in acc: 13 | base[i][i+1]=True 14 | 15 | base[36][37]=True 16 | base[36][80]=True 17 | base[49][50]=True 18 | base[116][50]=True 19 | base[105][106]=True 20 | base[105][117]=True 21 | base[61][62]=True 22 | base[61][152]=True 23 | base[162][163]=True 24 | base[151][163]=True 25 | base[200][0]=True 26 | base[79][0]=True 27 | 28 | both = np.logical_or(base, base.transpose()) 29 | 30 | return base.astype(int), base.transpose().astype(int), both.astype(int) 31 | 32 | def adjacency_matrix(n): 33 | S = np.zeros((n,n)) 34 | for i in range(len(S)): 35 | if i==0: 36 | S[0][1] =1 37 | S[0][n-1] = 1 38 | elif i==(n-1): 39 | S[n-1][n-2] = 1 40 | S[n-1][0] = 1 41 | else: 42 | S[i][i-1]=1 43 | S[i][i+1]=1 44 | return S 45 | 46 | def normalized_laplacian(adj): 47 | D = 1/adj.sum(axis=1) 48 | D = np.diag(D) 49 | return adj+D 50 | 51 | def calculate_normalized_laplacian(adj): 52 | adj = sp.coo_matrix(adj) 53 | d = np.array(adj.sum(axis = 1)) 54 | d_inv_sqrt = np.power(d, -0.5).flatten() 55 | d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0. 56 | d_mat_inv_sqrt = sp.diags(d_inv_sqrt) 57 | normalized_laplacian = sp.eye(adj.shape[0]) - adj.dot(d_mat_inv_sqrt).transpose().dot(d_mat_inv_sqrt).tocoo() 58 | return normalized_laplacian 59 | 60 | def calculate_adjacency_k(L, k): 61 | dim = L.shape[0] 62 | if k > 0: 63 | output = L 64 | for i in range(k-1): 65 | output = np.matmul(output,L) 66 | out = np.sign(output) 67 | elif k == 0: 68 | out = np.eye(dim) 69 | return out 70 | 71 | def A_k(L, k): 72 | output = L 73 | for i in range(k-1): 74 | output = K.dot(output,L) 75 | return np.sign(output) 76 | 77 | def compute_threshold(batch, k=4): 78 | """ 79 | Computes the sampling probability for scheduled sampling using inverse sigmoid. 80 | :param global_step: 81 | :param k: 82 | :return: 83 | """ 84 | return K.cast(k / (k + K.exp(batch / k)), 'float32') 85 | 86 | 87 | def rmse(y_true, y_pred): 88 | return K.sqrt(K.mean(K.square(y_pred - y_true), axis=-1)) 89 | 90 | def RMSE(y_true, y_pred): 91 | return np.sqrt(np.mean(np.square(y_pred - y_true))) 92 | 93 | def MAPE(y_true, y_pred): 94 | return np.mean(np.abs((y_pred - y_true)/y_true)) 95 | def MAE(y_true, y_pred): 96 | return np.mean(np.abs(y_pred - y_true)) 97 | 98 | ############################################ 99 | def Speed_k_correlation(ground_truth, weights, k): 100 | 101 | nb_samples = ground_truth.shape[0] 102 | time_steps = ground_truth.shape[1] 103 | N = ground_truth.shape[2] 104 | 105 | speed_vector = [i for i in range(115, -1, -5)] 106 | print(speed_vector) 107 | counts = [] 108 | f_value = np.zeros(len(speed_vector)) 109 | importances = np.zeros((len(speed_vector), 2*k+1)) 110 | statistics = [] 111 | for i in range(len(speed_vector)): 112 | statistics.append([]) 113 | 114 | for i in range(len(speed_vector)): 115 | ground_truth[ground_truth >= speed_vector[i]] = -i-1 116 | counts.append(np.count_nonzero(ground_truth == -i-1)) 117 | counts = np.array(counts) 118 | print(counts) 119 | 120 | W_head = weights[...,:k] 121 | W_end = weights[...,-k:] 122 | kernels = np.concatenate([W_end, weights, W_head], axis=-1) 123 | 124 | mask = np.array([i for i in range(-k,k+1)]) 125 | 126 | for i in range(nb_samples-1): 127 | for j in range(time_steps): 128 | for l in range(N): 129 | ref = ground_truth[i+1][j][l] 130 | f = 0. 131 | for p in range(-k,k+1): 132 | f += mask[p+k]*kernels[i][j][l][l+p+k] 133 | importances[int(-ref-1)][p+k] += kernels[i][j][l][l+p+k] 134 | #f += mask[p+k]*kernels[i][j][l][l+p] 135 | #importances[int(-ref-1)][p+k] += kernels[i][j][l][l+p] 136 | f_value[int(-ref-1)] += f 137 | statistics[int(-ref-1)].append(f) 138 | 139 | f_out = f_value/counts 140 | 141 | importance = importances.transpose() 142 | sp = [] 143 | for i in range(2*k+1): 144 | sp.append(importance[i]/counts) 145 | 146 | return f_out, np.array(sp), statistics 147 | 148 | def STcorrelation(ground_truth, weights, k): 149 | 150 | nb_samples = ground_truth.shape[0] 151 | time_steps = ground_truth.shape[1] 152 | N = ground_truth.shape[2] 153 | 154 | speed_vector = [i for i in range(115, -1, -5)] 155 | print(speed_vector) 156 | 157 | counts = np.zeros((N, len(speed_vector))) 158 | f_value = np.zeros((N, len(speed_vector))) 159 | J = np.zeros((N, len(speed_vector), 2*k+1)) 160 | 161 | for i in range(len(speed_vector)): 162 | ground_truth[ground_truth >= speed_vector[i]] = -i-1 163 | 164 | W_head = weights[...,:k] 165 | W_end = weights[...,-k:] 166 | kernels = np.concatenate([W_end, weights, W_head], axis=-1) 167 | print(kernels.shape) 168 | 169 | mask = np.array([i for i in range(-k,k+1)]) 170 | 171 | for i in range(nb_samples-1): 172 | for j in range(time_steps): 173 | filters = kernels[i][j] 174 | for l in range(N): 175 | ref = ground_truth[i+1][j][l] 176 | counts[l][int(-ref-1)] += 1 177 | f = 0 178 | for p in range(-k,k+1): 179 | f += mask[p+k]*filters[l][l+p+k] 180 | J[l][int(-ref-1)][p+k] += filters[l][l+p+k] 181 | f_value[l][int(-ref-1)] += f 182 | 183 | f_out = f_value/counts 184 | sp = [] 185 | for i in range(2*k+1): 186 | sp.append(J[...,i]/counts) 187 | st_out = np.array(sp) 188 | print(st_out.shape) 189 | 190 | return f_out, st_out 191 | 192 | def Compare(ground_truth, weights, k): 193 | 194 | nb_samples = ground_truth.shape[0] 195 | N = ground_truth.shape[1] 196 | 197 | f_value = np.zeros((nb_samples, N)) 198 | 199 | W_head = weights[...,:k] 200 | W_end = weights[...,-k:] 201 | kernels = np.concatenate([W_end, weights, W_head], axis=-1) 202 | print(kernels.shape) 203 | 204 | mask = np.array([i for i in range(-k,k+1)]) 205 | 206 | for i in range(nb_samples): 207 | filters = kernels[i] 208 | for l in range(N): 209 | f1 = 0 210 | for p in range(-k,k+1): 211 | f1 += mask[p+k]*filters[l][l+p+k] 212 | f_value[i][l] = f1 213 | print(f_value.shape) 214 | 215 | return f_value 216 | 217 | def Track(data, mask, Ad, Au, A, k, node): 218 | 219 | nb_samples, time_steps, N = data.shape 220 | 221 | G = nx.from_numpy_matrix(A) 222 | dis = dict(nx.all_pairs_shortest_path_length(G)) 223 | 224 | Ak = LA.matrix_power(A, k) 225 | 226 | lg = np.count_nonzero(mask[0][0][node]) 227 | print(lg, 'nodes') 228 | 229 | speed = np.zeros((nb_samples, time_steps, lg)) 230 | weights = np.zeros((nb_samples, time_steps, lg)) 231 | 232 | index = [] 233 | 234 | 235 | for i in range(N): 236 | if Ak[node][i] != 0: 237 | index.append(i) 238 | 239 | print(index) 240 | 241 | for i in range(nb_samples): 242 | for j in range(time_steps): 243 | m = mask[i][j][node] #N 244 | raw = data[i][j] 245 | 246 | v = [] 247 | w = [] 248 | 249 | 250 | for r in range(N): 251 | if m[r] != 0: 252 | v.append(raw[r]) 253 | w.append(m[r]) 254 | 255 | speed[i][j] = np.array(v) 256 | weights[i][j] = np.array(w) 257 | 258 | return speed, weights 259 | 260 | 261 | def z_score(x, mean, std): 262 | return (x - mean) / std 263 | 264 | 265 | def z_inverse(x, mean, std): 266 | return x * std + mean 267 | -------------------------------------------------------------------------------- /custom_model/model_keras.py: -------------------------------------------------------------------------------- 1 | from custom_model.layers_keras import * 2 | from custom_model.math_utils import * 3 | 4 | import numpy as np 5 | import tensorflow as tf 6 | import math 7 | from collections import defaultdict 8 | 9 | from keras import Model 10 | from keras.layers import Input, Dense, GRU, TimeDistributed, Lambda, Concatenate, RNN, dot, LeakyReLU 11 | import keras.backend as K 12 | from keras.callbacks import Callback, EarlyStopping, ModelCheckpoint 13 | import keras 14 | 15 | def unstack(x): 16 | x = tf.unstack(x, axis=1) 17 | return x 18 | 19 | def stack(x): 20 | y = K.stack(x, axis=1) 21 | return K.squeeze(y, axis=2) 22 | 23 | ####################### 24 | def create_embed_model(obs_timesteps=10, pred_timesteps=3, nb_nodes=208, k=1, dgc_mode='hybrid', inner_act=None): 25 | encoder = RNN(DGCRNNCell(k,dgc_mode=dgc_mode), return_state=True) 26 | decoder = RNN(DGCRNNCell(k,dgc_mode=dgc_mode), return_sequences=True, return_state=True) 27 | 28 | unstack_k = Lambda(unstack) 29 | choice = Scheduled() 30 | 31 | input_obs = Input(shape=(obs_timesteps, nb_nodes, 1)) 32 | input_gt = Input(shape=(pred_timesteps, nb_nodes, 1)) #(None, T, N, 1) 33 | encoder_inputs = Lambda(lambda x: K.squeeze(x, axis = -1))(input_obs) # (None, T, N) 34 | 35 | encoder_outputs, state_h = encoder(encoder_inputs) 36 | 37 | unstacked = unstack_k(input_gt) #[(None, N, 1) x T] list 38 | 39 | initial = unstacked[0] #(None, N, 1) 40 | decoder_inputs = Lambda(lambda x: K.permute_dimensions(x, (0,2,1)))(initial) #(None, 1, N) 41 | decoder_outputs_new, state_h_new = decoder(decoder_inputs, initial_state=state_h) 42 | state_h = state_h_new 43 | prediction = [] 44 | decoded_results = decoder_outputs_new 45 | prediction.append(decoded_results) 46 | 47 | if pred_timesteps > 1: 48 | for i in range(1,pred_timesteps): 49 | decoder_inputs = choice([prediction[-1], unstacked[i]])#(None, 208, 1) 50 | decoder_inputs = Lambda(lambda x: K.permute_dimensions(x, (0,2,1)))(decoder_inputs)#(None, 1, 208) 51 | decoder_outputs_new, state_h_new = decoder(decoder_inputs, initial_state=state_h) 52 | state_h = state_h_new 53 | decoded_results = decoder_outputs_new 54 | prediction.append(decoded_results) 55 | 56 | outputs = Lambda(stack)(prediction) 57 | model = Model([input_obs, input_gt], outputs) 58 | 59 | return model 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /model_interpretation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from __future__ import print_function\n", 10 | "import os\n", 11 | "import keras\n", 12 | "import tensorflow as tf\n", 13 | "\n", 14 | "config = tf.ConfigProto( device_count = {'GPU': 1 , 'CPU': 56} ) \n", 15 | "sess = tf.Session(config=config) \n", 16 | "keras.backend.set_session(sess)\n", 17 | "\n", 18 | "from custom_model.layers_keras import *\n", 19 | "from custom_model.model_keras import *\n", 20 | "from custom_model.math_utils import *\n", 21 | "from utils_vis import *\n", 22 | "from keras import metrics\n", 23 | "\n", 24 | "import matplotlib.pyplot as plt\n", 25 | "%matplotlib inline\n", 26 | "import h5py\n", 27 | "import numpy as np" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "dt = 5\n", 37 | "horizon = 30\n", 38 | "OBS = 60//dt-1\n", 39 | "PRED = horizon//dt\n", 40 | "\n", 41 | "model_name = 'dfn'\n", 42 | "\n", 43 | "Data = h5py.File('Datasets/RRot_cc5_30.h5', 'r')\n", 44 | "x_train = np.array(Data['Speed_obs_train'])\n", 45 | "y_train = np.array(Data['Speed_pred_train'])\n", 46 | "e_train = np.array(Data['E_train'])\n", 47 | "x_test = np.array(Data['Speed_obs_test'])\n", 48 | "y_test = np.array(Data['Speed_pred_test'])\n", 49 | "e_test = np.array(Data['E_test'])\n", 50 | "\n", 51 | "x_train = x_train[:,-OBS:]\n", 52 | "y_train = y_train[:,:PRED]\n", 53 | "e_train =e_train[:,:PRED]\n", 54 | "y_test = y_test[:,:PRED]\n", 55 | "e_test =e_test[:,:PRED]\n", 56 | "\n", 57 | "X_train = z_score(x_train, np.mean(x_train), np.std(x_train))\n", 58 | "X_test = z_score(x_test, np.mean(x_test), np.std(x_test))\n", 59 | "Y_train = z_score(y_train, np.mean(y_train), np.std(y_train))\n", 60 | "Y_test = z_score(x_test, np.mean(y_test), np.std(y_test))\n", 61 | "E_train = z_score(e_train, np.mean(e_train), np.std(e_train))\n", 62 | "E_test = z_score(e_test, np.mean(e_test), np.std(e_test))" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "#create model\n", 72 | "encoder = RNN(DGCRNNCell(k=9,dgc_mode='hybrid'), return_state=True)\n", 73 | "decoder = RNN(DGCRNNCell(k=9,dgc_mode='hybrid'), return_sequences=True, return_state=True)\n", 74 | "\n", 75 | "unstack_k = Lambda(unstack)\n", 76 | "choice = Scheduled()\n", 77 | " \n", 78 | "input_obs = Input(shape=(OBS, 208, 1)) \n", 79 | "input_gt = Input(shape=(PRED, 208, 1)) #(None, 4, 208, 1)\n", 80 | "encoder_inputs = Lambda(lambda x: K.squeeze(x, axis = -1))(input_obs) # (None, 29, 208)\n", 81 | " \n", 82 | "encoder_outputs, state_h = encoder(encoder_inputs)\n", 83 | "unstacked = unstack_k(input_gt) #[(None, 208, 1) x 4] list\n", 84 | " \n", 85 | "initial = unstacked[0] #(None, 208, 1)\n", 86 | "decoder_inputs = Lambda(lambda x: K.permute_dimensions(x, (0,2,1)))(initial) #(None, 1, 208)\n", 87 | "decoder_outputs_new, state_h_new = decoder(decoder_inputs, initial_state=state_h)\n", 88 | "state_h = state_h_new\n", 89 | "feature = []\n", 90 | "feature.append(decoder_outputs_new)\n", 91 | "if PRED > 1: \n", 92 | " for i in range(1,PRED):\n", 93 | " decoder_inputs = unstacked[i]#(None, 208, 1)\n", 94 | " decoder_inputs = Lambda(lambda x: K.permute_dimensions(x, (0,2,1)))(decoder_inputs)#(None, 1, 208)\n", 95 | " decoder_outputs_new, state_h_new = decoder(decoder_inputs, initial_state=state_h)\n", 96 | " state_h = state_h_new\n", 97 | " feature.append(decoder_outputs_new)\n", 98 | " \n", 99 | "outputs = Lambda(stack)(feature)\n", 100 | "model = Model([input_obs, input_gt], outputs)" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "metadata": {}, 107 | "outputs": [], 108 | "source": [ 109 | "#load pretrained models\n", 110 | "filename = 'pretrained/cc2-k4.h5'\n", 111 | "file=h5py.File(filename,'r')\n", 112 | "weight = []\n", 113 | "for i in range(len(file.keys())):\n", 114 | " weight.append(file['weight'+str(i)][:])\n", 115 | "model.set_weights(weight)" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "#prediction\n", 125 | "y = model.predict([X_test,E_test])\n", 126 | "y = z_inverse(y, np.mean(y_test), np.std(y_test))" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": null, 132 | "metadata": {}, 133 | "outputs": [], 134 | "source": [ 135 | "# extract dynamic kernels from outputs\n", 136 | "W = y[...,1:]\n", 137 | "# choose each step or all steps in the decoder\n", 138 | "step = 0\n", 139 | "W = W[:,[step]]\n", 140 | "W.shape" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": null, 146 | "metadata": {}, 147 | "outputs": [], 148 | "source": [ 149 | "f_avr,J_avr,C = Speed_k_correlation(ground_truth=120*y_train[:,[step]], weights=W, k=4)\n", 150 | "\n", 151 | "dynamicf = Compare(ground_truth=120*y_train[:,[step]].squeeze(), weights=W.squeeze(), k=4)" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": null, 157 | "metadata": {}, 158 | "outputs": [], 159 | "source": [ 160 | "plt.figure(figsize = (13,5))\n", 161 | "labels = [i for i in range(0, 125, 5)]\n", 162 | "#y_lab = [-4.5, -3.5,-2.5,-1.5,-0.5,0.5,1.5,2.5,3.5, 4.5]\n", 163 | "y_lab = np.arange(-9.5,10.5,1)*200\n", 164 | "plt.xlabel(\"v(km/h)\",fontsize=22)\n", 165 | "plt.xlim(0,120)\n", 166 | "plt.ylim(-9.5*200,9.5*200)\n", 167 | "plt.tick_params(axis='both', which='major', labelsize=18)\n", 168 | "plt.xticks(np.arange(0, 125, 10))\n", 169 | "plt.yticks(np.arange(-1800, 1801, 400))\n", 170 | "plt.ylabel(\"Directional distance (m)\",fontsize=22)\n", 171 | "plt.pcolormesh(labels, y_lab, np.flip(J_avr,axis=1), cmap = 'jet')\n", 172 | "plt.title('Average spatial attention vs speed on RotCC5', fontsize=20)\n", 173 | "plt.colorbar()\n", 174 | "plt.clim(0,0.2)\n", 175 | "#plt.show()\n", 176 | "plt.savefig('Figs/hm-cc5.PNG',dpi=600)" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "metadata": {}, 183 | "outputs": [], 184 | "source": [ 185 | "head = 260\n", 186 | "end = head+150\n", 187 | "s = 208\n", 188 | "gt = 120*y_train[:,[step]].squeeze()\n", 189 | "\n", 190 | "plt.figure(figsize = (15,8))\n", 191 | "y_lab = [0.2*i for i in range(s)]\n", 192 | "x_lab = [14+3/60*i for i in range(150)]\n", 193 | "#y, x = np.mgrid[slice(0, (s+1)*0.2, 0.2), slice(14-4/60., 14+150*2/60., 2/60)]\n", 194 | "plt.xlabel(\"time (PM)\",fontsize=22)\n", 195 | "plt.xlim(14,19)\n", 196 | "plt.ylim(0,(s-0.2)*0.2)\n", 197 | "plt.tick_params(axis='both', which='major', labelsize=18)\n", 198 | "plt.ylabel(\"position (km)\",fontsize=22)\n", 199 | "#plt.pcolormesh(x_lab, y_lab, 120*y_train[:,1][head:end].transpose(), cmap = 'autumn')\n", 200 | "plt.pcolormesh(x_lab, y_lab, dynamicf[head:end].transpose()/4, cmap = 'coolwarm')\n", 201 | "#levels1 = MaxNLocator(nbins=120).tick_values(-3, 3)\n", 202 | "#plt.contourf(x[:-1, :-1] + 0.2/2.,\n", 203 | " #y[:-1, :-1] + 2/120., [head:end].transpose(), levels=levels1,\n", 204 | " #cmap='RdYlBu_r')\n", 205 | "#plt.title('Speed evolution on 13-11-2018 (RotCC2)', fontsize=20)\n", 206 | "plt.title('Attention coefficient evolution on 13-11-2018 (RotCC2)', fontsize=20)\n", 207 | "plt.colorbar()\n", 208 | "#plt.clim(0,1)\n", 209 | "#plt.show()\n", 210 | "plt.savefig('Figs/attention.PNG',dpi=600)" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": null, 216 | "metadata": {}, 217 | "outputs": [], 218 | "source": [] 219 | } 220 | ], 221 | "metadata": { 222 | "kernelspec": { 223 | "display_name": "Python 3", 224 | "language": "python", 225 | "name": "python3" 226 | }, 227 | "language_info": { 228 | "codemirror_mode": { 229 | "name": "ipython", 230 | "version": 3 231 | }, 232 | "file_extension": ".py", 233 | "mimetype": "text/x-python", 234 | "name": "python", 235 | "nbconvert_exporter": "python", 236 | "pygments_lexer": "ipython3", 237 | "version": "3.7.3" 238 | } 239 | }, 240 | "nbformat": 4, 241 | "nbformat_minor": 2 242 | } 243 | -------------------------------------------------------------------------------- /pretrained/amsnetf.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/pretrained/amsnetf.h5 -------------------------------------------------------------------------------- /pretrained/cc2-k4.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/pretrained/cc2-k4.h5 -------------------------------------------------------------------------------- /pretrained/cc5-k9.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/pretrained/cc5-k9.h5 -------------------------------------------------------------------------------- /pretrained/cl2-k4.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/pretrained/cl2-k4.h5 -------------------------------------------------------------------------------- /pretrained/cl5-k9.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RomainLITUD/Multistep-Traffic-Forecasting-by-Dynamic-Graph-Convolution/b18a469a55c5266f0de5855f3bfb7fb792303f23/pretrained/cl5-k9.h5 -------------------------------------------------------------------------------- /utils_vis.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from matplotlib.colors import BoundaryNorm 3 | from matplotlib.ticker import MaxNLocator 4 | import numpy as np 5 | import datetime 6 | 7 | def plot_figure(X, title1, title2, figtitle, nb=208,time=60, dt=2, color='rainbow_r'): 8 | # make these smaller to increase the resolution 9 | dx, dy = dt, 0.2 10 | 11 | # generate 2 2d grids for the x & y bounds 12 | y, x = np.mgrid[slice(0, nb*dy, dy), slice(14, 19.01, dx)] 13 | 14 | z = 120*X.transpose() 15 | 16 | # x and y are bounds, so z should be the value *inside* those bounds. 17 | # Therefore, remove the last value from the z array. 18 | #z = z[:-1, :-1] 19 | levels = MaxNLocator(nbins=120).tick_values(0, 120) 20 | levels1 = MaxNLocator(nbins=12).tick_values(0, 120) 21 | 22 | 23 | # pick the desired colormap, sensible levels, and define a normalization 24 | # instance which takes data values and translates those into levels. 25 | cmap = plt.get_cmap(color)#('jet_r') 26 | #norm = BoundaryNorm(levels, ncolors=cmap.N, clip=True) 27 | 28 | #fig, (ax0, ax1) = plt.subplots(nrows=2,figsize=(12, 12)) 29 | fig, ax0 = plt.subplots(nrows=1,figsize=(15, 7)) 30 | 31 | im = ax0.pcolormesh(x, y, z, cmap=cmap)#, norm=norm) 32 | cbar0 = fig.colorbar(im, ax=ax0) 33 | ax0.set_title(title1, fontsize=25) 34 | ax0.set_xlabel('time (PM)', fontsize=20) 35 | ax0.set_ylabel('distance (km)', fontsize=20) 36 | cbar0.set_label('km/h', fontsize=20) 37 | # contours are *point* based plots, so convert our bound into point 38 | # centers 39 | #cf = ax1.contourf(x[:-1, :-1] + dx/2., 40 | #y[:-1, :-1] + dy/2., z, levels=levels1, 41 | #cmap=cmap) 42 | #cbar1=fig.colorbar(cf, ax=ax1) 43 | #ax1.set_title(title2, fontsize=20) 44 | #ax1.set_xlabel('time (min)', fontsize=15) 45 | #ax1.set_ylabel('distance (km)', fontsize=15) 46 | #cbar1.set_label('km/h', fontsize=15) 47 | # adjust spacing between subplots so `ax1` title and `ax0` tick labels 48 | # don't overlap 49 | ax0.tick_params(axis='both', which='major', labelsize=15) 50 | #ax1.tick_params(axis='both', which='major', labelsize=12) 51 | fig.tight_layout() 52 | 53 | plt.savefig('Figs/'+figtitle, dpi=600) 54 | plt.show() 55 | 56 | 57 | def plotter(history): 58 | plt.figure(figsize=(12, 6)) 59 | 60 | plt.subplot(211) 61 | plt.title('training and validation loss') 62 | plt.plot(history.history['loss'], label='training loss') 63 | plt.plot(history.history['val_loss'], label='validation loss') 64 | plt.legend() 65 | 66 | plt.tight_layout() 67 | 68 | plt.show() --------------------------------------------------------------------------------