├── GraphLASSO_figure.png ├── README.md ├── data ├── CA1_Food1.csv ├── CA1_Food1 │ └── readme.md └── config │ └── para_CA1_Food1.yaml ├── eval_baseline_methods.py ├── lib ├── AMSGrad.py ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-39.pyc │ ├── metrics.cpython-39.pyc │ └── utils.cpython-39.pyc ├── metrics.py ├── metrics_test.py └── utils.py ├── model ├── __init__.py ├── __pycache__ │ └── __init__.cpython-39.pyc └── pytorch │ ├── __init__.py │ ├── __pycache__ │ ├── __init__.cpython-39.pyc │ ├── cell.cpython-39.pyc │ ├── loss.cpython-39.pyc │ ├── model.cpython-39.pyc │ └── supervisor.cpython-39.pyc │ ├── cell.py │ ├── loss.py │ ├── model.py │ └── supervisor.py ├── scripts ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-39.pyc │ └── generate_training_data.cpython-39.pyc ├── gen_adj_mx.py ├── generate_adj_lasso.py ├── generate_training_data.py └── generate_visualization_data.py └── train.py /GraphLASSO_figure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/GraphLASSO_figure.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sparsity exploitation via discovering graphical models in multi-variate time-series forecasting 2 | 3 | ![GraphLASSO](GraphLASSO_figure.png) 4 | 5 | https://arxiv.org/pdf/2306.17090.pdf 6 | 7 | Contributors: 8 | * Ngoc-Dung Do 9 | * Truong Son Hy (Correspondent / PI) 10 | -------------------------------------------------------------------------------- /data/CA1_Food1/readme.md: -------------------------------------------------------------------------------- 1 | Run scripts/generate_training_data.py to create training data -------------------------------------------------------------------------------- /data/config/para_CA1_Food1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | base_dir: data/model 3 | log_level: INFO 4 | data: 5 | batch_size: 32 6 | dataset_dir: data/CA1_Food1 7 | test_batch_size: 32 8 | val_batch_size: 32 9 | graph_pkl_filename: nope 10 | scaler: false 11 | 12 | 13 | 14 | model: 15 | model: dynamic_lasso 16 | cl_decay_steps: 2000 17 | filter_type: dual_random_walk 18 | horizon: 7 19 | input_dim: 1 20 | l1_decay: 0 21 | max_diffusion_step: 1 22 | num_nodes: 216 23 | num_rnn_layers: 1 24 | output_dim: 1 25 | rnn_units: 32 26 | seq_len: 14 27 | use_curriculum_learning: true 28 | dim_fc: 1776 29 | 30 | train: 31 | base_lr: 0.05 32 | dropout: 0.1 33 | epoch: 0 34 | epochs: 100 35 | epsilon: 1.0e-3 36 | global_step: 0 37 | lr_decay_ratio: 0.3 38 | max_grad_norm: 5 39 | max_to_keep: 100 40 | min_learning_rate: 2.0e-05 41 | optimizer: adam 42 | patience: 100 43 | steps: [20, 30, 40] 44 | test_every_n_epochs: 3 45 | knn_k: 3 # decrease 46 | epoch_use_regularization: 2000 47 | num_sample: 10 48 | -------------------------------------------------------------------------------- /eval_baseline_methods.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import numpy as np 3 | import pandas as pd 4 | import os 5 | from statsmodels.tsa.vector_ar.var_model import VAR 6 | from statsmodels.tsa.statespace.varmax import VARMAX 7 | #import os 8 | os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" 9 | #print(os.getcwd()) 10 | from lib import utils 11 | from lib.metrics import masked_rmse_np, masked_mape_np, masked_mae_np 12 | from lib.utils import StandardScaler 13 | 14 | 15 | def historical_average_predict(df, period=12 * 24 * 7, test_ratio=0.3, null_val=0.): 16 | """ 17 | Calculates the historical average of sensor reading. 18 | :param df: 19 | :param period: default 1 week. 20 | :param test_ratio: 21 | :param null_val: default 0. 22 | :return: 23 | """ 24 | n_sample, n_sensor = df.shape 25 | n_test = int(round(n_sample * test_ratio)) 26 | n_train = n_sample - n_test 27 | y_test = df[-n_test:] 28 | y_predict = pd.DataFrame.copy(y_test) 29 | 30 | for i in range(n_train, min(n_sample, n_train + period)): 31 | inds = [j for j in range(i % period, n_train, period)] 32 | historical = df.iloc[inds, :] 33 | y_predict.iloc[i - n_train, :] = historical[historical != null_val].mean() 34 | # Copy each period. 35 | for i in range(n_train + period, n_sample, period): 36 | size = min(period, n_sample - i) 37 | start = i - n_train 38 | y_predict.iloc[start:start + size, :] = y_predict.iloc[start - period: start + size - period, :].values 39 | return y_predict, y_test 40 | 41 | 42 | def static_predict(df, n_forward, test_ratio=0.3): 43 | """ 44 | Assumes $x^{t+1} = x^{t}$ 45 | :param df: 46 | :param n_forward: 47 | :param test_ratio: 48 | :return: 49 | """ 50 | test_num = int(round(df.shape[0] * test_ratio)) 51 | y_test = df[-test_num:] 52 | y_predict = df.shift(n_forward).iloc[-test_num:] 53 | return y_predict, y_test 54 | 55 | 56 | def predict(model,df, n_forwards=(1, 3), n_lags=4, n_noise=3, test_ratio=0.3): 57 | """ 58 | Multivariate time series forecasting using Vector Auto-Regressive Model. 59 | :param df: pandas.DataFrame, index: time, columns: sensor id, content: data. 60 | :param n_forwards: a tuple of horizons. 61 | :param n_lags: the order of the VAR model. 62 | :param test_ratio: 63 | :return: [list of prediction in different horizon], dt_test 64 | """ 65 | n_sample, n_output = df.shape 66 | n_test = int(round(n_sample * test_ratio)) 67 | n_train = n_sample - n_test 68 | df_train, df_test = df[:n_train], df[n_train:] 69 | 70 | print(df.shape) 71 | print("mean", df_test.values.mean(), "std: ", df_test.values.std()) 72 | print("mean", df_train.values.mean(), "std: ", df_train.values.std()) 73 | 74 | scaler = StandardScaler(mean=df_train.values.mean(), std=df_train.values.std()) 75 | data = scaler.transform(df_train.values) 76 | #data=df_train.copy() 77 | if model=="var": 78 | var_model = VAR(data) 79 | var_result = var_model.fit(n_lags) 80 | elif model=="varma": 81 | var_model=VARMAX(data,order=(n_lags,n_noise), trend ='t') 82 | var_result = var_model.fit() 83 | 84 | max_n_forwards = np.max(n_forwards) 85 | # Do forecasting. 86 | result = np.zeros(shape=(len(n_forwards), n_test, n_output)) 87 | start = n_train - n_lags - max_n_forwards + 1 88 | for input_ind in range(start, n_sample - n_lags): 89 | prediction = var_result.forecast(scaler.transform(df.values[input_ind: input_ind + n_lags]), max_n_forwards) 90 | #prediction[prediction<0]=0 91 | for i, n_forward in enumerate(n_forwards): 92 | result_ind = input_ind - n_train + n_lags + n_forward - 1 93 | if 0 <= result_ind < n_test: 94 | result[i, result_ind, :] = prediction[n_forward - 1, :] 95 | 96 | df_predicts = [] 97 | for i, n_forward in enumerate(n_forwards): 98 | df_predict = pd.DataFrame(scaler.inverse_transform(result[i]), index=df_test.index, columns=df_test.columns) 99 | #df_predict[df_predict < 0] = 0 100 | df_predicts.append(df_predict) 101 | 102 | return df_predicts, df_test 103 | 104 | 105 | # def eval_static(traffic_reading_df): 106 | # logger.info('Static') 107 | # horizons = [1, 3, 6, 12] 108 | # logger.info('\t'.join(['Model', 'Horizon', 'RMSE', 'MAPE', 'MAE'])) 109 | # for horizon in horizons: 110 | # y_predict, y_test = static_predict(traffic_reading_df, n_forward=horizon, test_ratio=0.2) 111 | # rmse = masked_rmse_np(preds=y_predict.values, labels=y_test, null_val=0) 112 | # mape = masked_mape_np(preds=y_predict.values, labels=y_test.values, null_val=0) 113 | # mae = masked_mae_np(preds=y_predict.values, labels=y_test.values, null_val=0) 114 | # line = 'Static\t%d\t%.2f\t%.2f\t%.2f' % (horizon, rmse, mape * 100, mae) 115 | # logger.info(line) 116 | 117 | 118 | # def eval_historical_average(traffic_reading_df, period): 119 | # y_predict, y_test = historical_average_predict(traffic_reading_df, period=period, test_ratio=0.2) 120 | # rmse = masked_rmse_np(preds=y_predict.values, labels=y_test.values, null_val=0) 121 | # mape = masked_mape_np(preds=y_predict.values, labels=y_test.values, null_val=0) 122 | # mae = masked_mae_np(preds=y_predict.values, labels=y_test.values, null_val=0) 123 | # logger.info('Historical Average') 124 | # logger.info('\t'.join(['Model', 'Horizon', 'RMSE', 'MAPE', 'MAE'])) 125 | # for horizon in [1, 3, 6, 12]: 126 | # line = 'HA\t%d\t%.2f\t%.2f\t%.2f' % (horizon, rmse, mape * 100, mae) 127 | # logger.info(line) 128 | 129 | 130 | def eval_var(model,traffic_reading_df, n_lags=7, n_noise=3,n_forwards=[1, 3, 6, 12]): 131 | 132 | y_predicts, y_test = predict(model,traffic_reading_df, n_forwards=n_forwards, n_lags=n_lags,n_noise=n_noise, 133 | test_ratio=0.2) 134 | logger.info('VAR (lag=%d)' % n_lags) 135 | logger.info('Model\tHorizon\tRMSE\tMAPE\tMAE') 136 | for i, horizon in enumerate(n_forwards): 137 | rmse = masked_rmse_np(preds=y_predicts[i].values, labels=y_test.values, null_val=0) 138 | mape = masked_mape_np(preds=y_predicts[i].values, labels=y_test.values, null_val=0) 139 | mae = masked_mae_np(preds=y_predicts[i].values, labels=y_test.values, null_val=0) 140 | line = 'VAR\t%d\t%.4f\t%.4f\t%.4f' % (horizon, rmse, mape * 100, mae) 141 | logger.info(line) 142 | 143 | 144 | def main(args): 145 | if args.reading_filename[-4:]=='.csv': 146 | reading_df = pd.read_csv(args.reading_filename) 147 | 148 | elif args.reading_filename[-3:]=='.h5': 149 | reading_df = pd.read_hdf(args.reading_filename) 150 | elif args.reading_filename[-4:] =='.txt': 151 | reading_df = pd.read_csv(args.reading_filename, header=None) 152 | reading_df=reading_df.iloc[:,:100] 153 | #print(traffic_reading_df.mean(),traffic_reading_df.std()) 154 | #eval_static(traffic_reading_df) 155 | #eval_historical_average(traffic_reading_df, period=7 * 24 * 12) 156 | if args.n_forwards =="weekly": 157 | n_fw=[1,3,7,14] 158 | elif args.n_forwards =="daily": 159 | n_fw=[1,3,6,12] 160 | model="var" 161 | if model=="varma": 162 | dif=reading_df.diff() 163 | reading_df = dif.iloc[1:,:] 164 | print("dataset: ", args.reading_filename) 165 | eval_var(model,reading_df, n_lags=args.n_lags, n_noise=2,n_forwards=n_fw) 166 | 167 | y_pred_ha, y_test_ha = historical_average_predict(reading_df,period = args.n_lags) 168 | 169 | print(masked_mae_np(y_pred_ha, y_test_ha)) 170 | 171 | 172 | if __name__ == '__main__': 173 | logger = utils.get_logger('data/model', 'Baseline') 174 | parser = argparse.ArgumentParser() 175 | parser.add_argument('--reading_filename', default="data/CA1_Food1.csv", type=str, 176 | help='Path to the Dataframe.') 177 | parser.add_argument('--n_forwards',default="daily", type=str, help="Number to forecast") # "daily" is minutes, "weekly" is days 178 | parser.add_argument('--n_lags',default=3, type=int, help="N_lags") 179 | parser.add_argument('--standarlize',default=True, type=bool, help="Normalization") 180 | args = parser.parse_args() 181 | main(args) 182 | -------------------------------------------------------------------------------- /lib/AMSGrad.py: -------------------------------------------------------------------------------- 1 | """AMSGrad for TensorFlow. 2 | From: https://github.com/taki0112/AMSGrad-Tensorflow 3 | """ 4 | 5 | from tensorflow.python.eager import context 6 | from tensorflow.python.framework import ops 7 | from tensorflow.python.ops import control_flow_ops 8 | from tensorflow.python.ops import math_ops 9 | from tensorflow.python.ops import resource_variable_ops 10 | from tensorflow.python.ops import state_ops 11 | from tensorflow.python.ops import variable_scope 12 | from tensorflow.python.training import optimizer 13 | 14 | 15 | class AMSGrad(optimizer.Optimizer): 16 | def __init__(self, learning_rate=0.01, beta1=0.9, beta2=0.99, epsilon=1e-8, use_locking=False, name="AMSGrad"): 17 | super(AMSGrad, self).__init__(use_locking, name) 18 | self._lr = learning_rate 19 | self._beta1 = beta1 20 | self._beta2 = beta2 21 | self._epsilon = epsilon 22 | 23 | self._lr_t = None 24 | self._beta1_t = None 25 | self._beta2_t = None 26 | self._epsilon_t = None 27 | 28 | self._beta1_power = None 29 | self._beta2_power = None 30 | 31 | def _create_slots(self, var_list): 32 | first_var = min(var_list, key=lambda x: x.name) 33 | 34 | create_new = self._beta1_power is None 35 | if not create_new and context.in_graph_mode(): 36 | create_new = (self._beta1_power.graph is not first_var.graph) 37 | 38 | if create_new: 39 | with ops.colocate_with(first_var): 40 | self._beta1_power = variable_scope.variable(self._beta1, name="beta1_power", trainable=False) 41 | self._beta2_power = variable_scope.variable(self._beta2, name="beta2_power", trainable=False) 42 | # Create slots for the first and second moments. 43 | for v in var_list: 44 | self._zeros_slot(v, "m", self._name) 45 | self._zeros_slot(v, "v", self._name) 46 | self._zeros_slot(v, "vhat", self._name) 47 | 48 | def _prepare(self): 49 | self._lr_t = ops.convert_to_tensor(self._lr) 50 | self._beta1_t = ops.convert_to_tensor(self._beta1) 51 | self._beta2_t = ops.convert_to_tensor(self._beta2) 52 | self._epsilon_t = ops.convert_to_tensor(self._epsilon) 53 | 54 | def _apply_dense(self, grad, var): 55 | beta1_power = math_ops.cast(self._beta1_power, var.dtype.base_dtype) 56 | beta2_power = math_ops.cast(self._beta2_power, var.dtype.base_dtype) 57 | lr_t = math_ops.cast(self._lr_t, var.dtype.base_dtype) 58 | beta1_t = math_ops.cast(self._beta1_t, var.dtype.base_dtype) 59 | beta2_t = math_ops.cast(self._beta2_t, var.dtype.base_dtype) 60 | epsilon_t = math_ops.cast(self._epsilon_t, var.dtype.base_dtype) 61 | 62 | lr = (lr_t * math_ops.sqrt(1 - beta2_power) / (1 - beta1_power)) 63 | 64 | # m_t = beta1 * m + (1 - beta1) * g_t 65 | m = self.get_slot(var, "m") 66 | m_scaled_g_values = grad * (1 - beta1_t) 67 | m_t = state_ops.assign(m, beta1_t * m + m_scaled_g_values, use_locking=self._use_locking) 68 | 69 | # v_t = beta2 * v + (1 - beta2) * (g_t * g_t) 70 | v = self.get_slot(var, "v") 71 | v_scaled_g_values = (grad * grad) * (1 - beta2_t) 72 | v_t = state_ops.assign(v, beta2_t * v + v_scaled_g_values, use_locking=self._use_locking) 73 | 74 | # amsgrad 75 | vhat = self.get_slot(var, "vhat") 76 | vhat_t = state_ops.assign(vhat, math_ops.maximum(v_t, vhat)) 77 | v_sqrt = math_ops.sqrt(vhat_t) 78 | 79 | var_update = state_ops.assign_sub(var, lr * m_t / (v_sqrt + epsilon_t), use_locking=self._use_locking) 80 | return control_flow_ops.group(*[var_update, m_t, v_t, vhat_t]) 81 | 82 | def _resource_apply_dense(self, grad, var): 83 | var = var.handle 84 | beta1_power = math_ops.cast(self._beta1_power, grad.dtype.base_dtype) 85 | beta2_power = math_ops.cast(self._beta2_power, grad.dtype.base_dtype) 86 | lr_t = math_ops.cast(self._lr_t, grad.dtype.base_dtype) 87 | beta1_t = math_ops.cast(self._beta1_t, grad.dtype.base_dtype) 88 | beta2_t = math_ops.cast(self._beta2_t, grad.dtype.base_dtype) 89 | epsilon_t = math_ops.cast(self._epsilon_t, grad.dtype.base_dtype) 90 | 91 | lr = (lr_t * math_ops.sqrt(1 - beta2_power) / (1 - beta1_power)) 92 | 93 | # m_t = beta1 * m + (1 - beta1) * g_t 94 | m = self.get_slot(var, "m").handle 95 | m_scaled_g_values = grad * (1 - beta1_t) 96 | m_t = state_ops.assign(m, beta1_t * m + m_scaled_g_values, use_locking=self._use_locking) 97 | 98 | # v_t = beta2 * v + (1 - beta2) * (g_t * g_t) 99 | v = self.get_slot(var, "v").handle 100 | v_scaled_g_values = (grad * grad) * (1 - beta2_t) 101 | v_t = state_ops.assign(v, beta2_t * v + v_scaled_g_values, use_locking=self._use_locking) 102 | 103 | # amsgrad 104 | vhat = self.get_slot(var, "vhat").handle 105 | vhat_t = state_ops.assign(vhat, math_ops.maximum(v_t, vhat)) 106 | v_sqrt = math_ops.sqrt(vhat_t) 107 | 108 | var_update = state_ops.assign_sub(var, lr * m_t / (v_sqrt + epsilon_t), use_locking=self._use_locking) 109 | return control_flow_ops.group(*[var_update, m_t, v_t, vhat_t]) 110 | 111 | def _apply_sparse_shared(self, grad, var, indices, scatter_add): 112 | beta1_power = math_ops.cast(self._beta1_power, var.dtype.base_dtype) 113 | beta2_power = math_ops.cast(self._beta2_power, var.dtype.base_dtype) 114 | lr_t = math_ops.cast(self._lr_t, var.dtype.base_dtype) 115 | beta1_t = math_ops.cast(self._beta1_t, var.dtype.base_dtype) 116 | beta2_t = math_ops.cast(self._beta2_t, var.dtype.base_dtype) 117 | epsilon_t = math_ops.cast(self._epsilon_t, var.dtype.base_dtype) 118 | 119 | lr = (lr_t * math_ops.sqrt(1 - beta2_power) / (1 - beta1_power)) 120 | 121 | # m_t = beta1 * m + (1 - beta1) * g_t 122 | m = self.get_slot(var, "m") 123 | m_scaled_g_values = grad * (1 - beta1_t) 124 | m_t = state_ops.assign(m, m * beta1_t, use_locking=self._use_locking) 125 | with ops.control_dependencies([m_t]): 126 | m_t = scatter_add(m, indices, m_scaled_g_values) 127 | 128 | # v_t = beta2 * v + (1 - beta2) * (g_t * g_t) 129 | v = self.get_slot(var, "v") 130 | v_scaled_g_values = (grad * grad) * (1 - beta2_t) 131 | v_t = state_ops.assign(v, v * beta2_t, use_locking=self._use_locking) 132 | with ops.control_dependencies([v_t]): 133 | v_t = scatter_add(v, indices, v_scaled_g_values) 134 | 135 | # amsgrad 136 | vhat = self.get_slot(var, "vhat") 137 | vhat_t = state_ops.assign(vhat, math_ops.maximum(v_t, vhat)) 138 | v_sqrt = math_ops.sqrt(vhat_t) 139 | var_update = state_ops.assign_sub(var, lr * m_t / (v_sqrt + epsilon_t), use_locking=self._use_locking) 140 | return control_flow_ops.group(*[var_update, m_t, v_t, vhat_t]) 141 | 142 | def _apply_sparse(self, grad, var): 143 | return self._apply_sparse_shared( 144 | grad.values, var, grad.indices, 145 | lambda x, i, v: state_ops.scatter_add( # pylint: disable=g-long-lambda 146 | x, i, v, use_locking=self._use_locking)) 147 | 148 | def _resource_scatter_add(self, x, i, v): 149 | with ops.control_dependencies( 150 | [resource_variable_ops.resource_scatter_add(x.handle, i, v)]): 151 | return x.value() 152 | 153 | def _resource_apply_sparse(self, grad, var, indices): 154 | return self._apply_sparse_shared( 155 | grad, var, indices, self._resource_scatter_add) 156 | 157 | def _finish(self, update_ops, name_scope): 158 | # Update the power accumulators. 159 | with ops.control_dependencies(update_ops): 160 | with ops.colocate_with(self._beta1_power): 161 | update_beta1 = self._beta1_power.assign( 162 | self._beta1_power * self._beta1_t, 163 | use_locking=self._use_locking) 164 | update_beta2 = self._beta2_power.assign( 165 | self._beta2_power * self._beta2_t, 166 | use_locking=self._use_locking) 167 | return control_flow_ops.group(*update_ops + [update_beta1, update_beta2], 168 | name=name_scope) 169 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/lib/__init__.py -------------------------------------------------------------------------------- /lib/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/lib/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /lib/__pycache__/metrics.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/lib/__pycache__/metrics.cpython-39.pyc -------------------------------------------------------------------------------- /lib/__pycache__/utils.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/lib/__pycache__/utils.cpython-39.pyc -------------------------------------------------------------------------------- /lib/metrics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensorflow as tf 3 | 4 | 5 | def masked_mse_tf(preds, labels, null_val=np.nan): 6 | """ 7 | Accuracy with masking. 8 | :param preds: 9 | :param labels: 10 | :param null_val: 11 | :return: 12 | """ 13 | if np.isnan(null_val): 14 | mask = ~tf.is_nan(labels) 15 | else: 16 | mask = tf.not_equal(labels, null_val) 17 | mask = tf.cast(mask, tf.float32) 18 | mask /= tf.reduce_mean(mask) 19 | mask = tf.where(tf.is_nan(mask), tf.zeros_like(mask), mask) 20 | loss = tf.square(tf.subtract(preds, labels)) 21 | loss = loss * mask 22 | loss = tf.where(tf.is_nan(loss), tf.zeros_like(loss), loss) 23 | return tf.reduce_mean(loss) 24 | 25 | 26 | def masked_mae_tf(preds, labels, null_val=np.nan): 27 | """ 28 | Accuracy with masking. 29 | :param preds: 30 | :param labels: 31 | :param null_val: 32 | :return: 33 | """ 34 | if np.isnan(null_val): 35 | mask = ~tf.is_nan(labels) 36 | else: 37 | mask = tf.not_equal(labels, null_val) 38 | mask = tf.cast(mask, tf.float32) 39 | mask /= tf.reduce_mean(mask) 40 | mask = tf.where(tf.is_nan(mask), tf.zeros_like(mask), mask) 41 | loss = tf.abs(tf.subtract(preds, labels)) 42 | loss = loss * mask 43 | loss = tf.where(tf.is_nan(loss), tf.zeros_like(loss), loss) 44 | return tf.reduce_mean(loss) 45 | 46 | 47 | def masked_rmse_tf(preds, labels, null_val=np.nan): 48 | """ 49 | Accuracy with masking. 50 | :param preds: 51 | :param labels: 52 | :param null_val: 53 | :return: 54 | """ 55 | return tf.sqrt(masked_mse_tf(preds=preds, labels=labels, null_val=null_val)) 56 | 57 | 58 | def masked_rmse_np(preds, labels, null_val=np.nan): 59 | return np.sqrt(masked_mse_np(preds=preds, labels=labels, null_val=null_val)) 60 | 61 | 62 | def masked_mse_np(preds, labels, null_val=np.nan): 63 | with np.errstate(divide='ignore', invalid='ignore'): 64 | if np.isnan(null_val): 65 | mask = ~np.isnan(labels) 66 | else: 67 | mask = np.not_equal(labels, null_val) 68 | mask = mask.astype('float32') 69 | mask /= np.mean(mask) 70 | rmse = np.square(np.subtract(preds, labels)).astype('float32') 71 | rmse = np.nan_to_num(rmse * mask) 72 | return np.mean(rmse) 73 | 74 | 75 | def masked_mae_np(preds, labels, null_val=np.nan): 76 | with np.errstate(divide='ignore', invalid='ignore'): 77 | if np.isnan(null_val): 78 | mask = ~np.isnan(labels) 79 | else: 80 | mask = np.not_equal(labels, null_val) 81 | mask = mask.astype('float32') 82 | mask /= np.mean(mask) 83 | mae = np.abs(np.subtract(preds, labels)).astype('float32') 84 | mae = np.nan_to_num(mae * mask) 85 | return np.mean(mae) 86 | 87 | 88 | def masked_mape_np(preds, labels, null_val=np.nan): 89 | with np.errstate(divide='ignore', invalid='ignore'): 90 | if np.isnan(null_val): 91 | mask = ~np.isnan(labels) 92 | else: 93 | mask = np.not_equal(labels, null_val) 94 | mask = mask.astype('float32') 95 | mask /= np.mean(mask) 96 | mape = np.abs(np.divide(np.subtract(preds, labels).astype('float32'), labels)) 97 | mape = np.nan_to_num(mask * mape) 98 | return np.mean(mape) 99 | 100 | 101 | # Builds loss function. 102 | def masked_mse_loss(scaler, null_val): 103 | def loss(preds, labels): 104 | if scaler: 105 | preds = scaler.inverse_transform(preds) 106 | labels = scaler.inverse_transform(labels) 107 | return masked_mse_tf(preds=preds, labels=labels, null_val=null_val) 108 | 109 | return loss 110 | 111 | 112 | def masked_rmse_loss(scaler, null_val): 113 | def loss(preds, labels): 114 | if scaler: 115 | preds = scaler.inverse_transform(preds) 116 | labels = scaler.inverse_transform(labels) 117 | return masked_rmse_tf(preds=preds, labels=labels, null_val=null_val) 118 | 119 | return loss 120 | 121 | 122 | def masked_mae_loss(scaler, null_val): 123 | def loss(preds, labels): 124 | if scaler: 125 | preds = scaler.inverse_transform(preds) 126 | labels = scaler.inverse_transform(labels) 127 | mae = masked_mae_tf(preds=preds, labels=labels, null_val=null_val) 128 | return mae 129 | 130 | return loss 131 | 132 | 133 | def calculate_metrics(df_pred, df_test, null_val): 134 | """ 135 | Calculate the MAE, MAPE, RMSE 136 | :param df_pred: 137 | :param df_test: 138 | :param null_val: 139 | :return: 140 | """ 141 | mape = masked_mape_np(preds=df_pred.as_matrix(), labels=df_test.as_matrix(), null_val=null_val) 142 | mae = masked_mae_np(preds=df_pred.as_matrix(), labels=df_test.as_matrix(), null_val=null_val) 143 | rmse = masked_rmse_np(preds=df_pred.as_matrix(), labels=df_test.as_matrix(), null_val=null_val) 144 | return mae, mape, rmse -------------------------------------------------------------------------------- /lib/metrics_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | import tensorflow as tf 5 | 6 | from lib import metrics 7 | 8 | 9 | class MyTestCase(unittest.TestCase): 10 | def test_masked_mape_np(self): 11 | preds = np.array([ 12 | [1, 2, 2], 13 | [3, 4, 5], 14 | ], dtype=np.float32) 15 | labels = np.array([ 16 | [1, 2, 2], 17 | [3, 4, 4] 18 | ], dtype=np.float32) 19 | mape = metrics.masked_mape_np(preds=preds, labels=labels) 20 | self.assertAlmostEqual(1 / 24.0, mape, delta=1e-5) 21 | 22 | def test_masked_mape_np2(self): 23 | preds = np.array([ 24 | [1, 2, 2], 25 | [3, 4, 5], 26 | ], dtype=np.float32) 27 | labels = np.array([ 28 | [1, 2, 2], 29 | [3, 4, 4] 30 | ], dtype=np.float32) 31 | mape = metrics.masked_mape_np(preds=preds, labels=labels, null_val=4) 32 | self.assertEqual(0., mape) 33 | 34 | def test_masked_mape_np_all_zero(self): 35 | preds = np.array([ 36 | [1, 2], 37 | [3, 4], 38 | ], dtype=np.float32) 39 | labels = np.array([ 40 | [0, 0], 41 | [0, 0] 42 | ], dtype=np.float32) 43 | mape = metrics.masked_mape_np(preds=preds, labels=labels, null_val=0) 44 | self.assertEqual(0., mape) 45 | 46 | def test_masked_mape_np_all_nan(self): 47 | preds = np.array([ 48 | [1, 2], 49 | [3, 4], 50 | ], dtype=np.float32) 51 | labels = np.array([ 52 | [np.nan, np.nan], 53 | [np.nan, np.nan] 54 | ], dtype=np.float32) 55 | mape = metrics.masked_mape_np(preds=preds, labels=labels) 56 | self.assertEqual(0., mape) 57 | 58 | def test_masked_mape_np_nan(self): 59 | preds = np.array([ 60 | [1, 2], 61 | [3, 4], 62 | ], dtype=np.float32) 63 | labels = np.array([ 64 | [np.nan, np.nan], 65 | [np.nan, 3] 66 | ], dtype=np.float32) 67 | mape = metrics.masked_mape_np(preds=preds, labels=labels) 68 | self.assertAlmostEqual(1 / 3., mape, delta=1e-5) 69 | 70 | def test_masked_rmse_np_vanilla(self): 71 | preds = np.array([ 72 | [1, 2], 73 | [3, 4], 74 | ], dtype=np.float32) 75 | labels = np.array([ 76 | [1, 4], 77 | [3, 4] 78 | ], dtype=np.float32) 79 | mape = metrics.masked_rmse_np(preds=preds, labels=labels, null_val=0) 80 | self.assertEqual(1., mape) 81 | 82 | def test_masked_rmse_np_nan(self): 83 | preds = np.array([ 84 | [1, 2], 85 | [3, 4], 86 | ], dtype=np.float32) 87 | labels = np.array([ 88 | [1, np.nan], 89 | [3, 4] 90 | ], dtype=np.float32) 91 | rmse = metrics.masked_rmse_np(preds=preds, labels=labels) 92 | self.assertEqual(0., rmse) 93 | 94 | def test_masked_rmse_np_all_zero(self): 95 | preds = np.array([ 96 | [1, 2], 97 | [3, 4], 98 | ], dtype=np.float32) 99 | labels = np.array([ 100 | [0, 0], 101 | [0, 0] 102 | ], dtype=np.float32) 103 | mape = metrics.masked_rmse_np(preds=preds, labels=labels, null_val=0) 104 | self.assertEqual(0., mape) 105 | 106 | def test_masked_rmse_np_missing(self): 107 | preds = np.array([ 108 | [1, 2], 109 | [3, 4], 110 | ], dtype=np.float32) 111 | labels = np.array([ 112 | [1, 0], 113 | [3, 4] 114 | ], dtype=np.float32) 115 | mape = metrics.masked_rmse_np(preds=preds, labels=labels, null_val=0) 116 | self.assertEqual(0., mape) 117 | 118 | def test_masked_rmse_np2(self): 119 | preds = np.array([ 120 | [1, 2], 121 | [3, 4], 122 | ], dtype=np.float32) 123 | labels = np.array([ 124 | [1, 0], 125 | [3, 3] 126 | ], dtype=np.float32) 127 | rmse = metrics.masked_rmse_np(preds=preds, labels=labels, null_val=0) 128 | self.assertAlmostEqual(np.sqrt(1 / 3.), rmse, delta=1e-5) 129 | 130 | 131 | class TFRMSETestCase(unittest.TestCase): 132 | def test_masked_mse_null(self): 133 | with tf.Session() as sess: 134 | preds = tf.constant(np.array([ 135 | [1, 2], 136 | [3, 4], 137 | ], dtype=np.float32)) 138 | labels = tf.constant(np.array([ 139 | [1, 0], 140 | [3, 3] 141 | ], dtype=np.float32)) 142 | rmse = metrics.masked_mse_tf(preds=preds, labels=labels, null_val=0) 143 | self.assertAlmostEqual(1 / 3.0, sess.run(rmse), delta=1e-5) 144 | 145 | def test_masked_mse_vanilla(self): 146 | with tf.Session() as sess: 147 | preds = tf.constant(np.array([ 148 | [1, 2], 149 | [3, 4], 150 | ], dtype=np.float32)) 151 | labels = tf.constant(np.array([ 152 | [1, 0], 153 | [3, 3] 154 | ], dtype=np.float32)) 155 | rmse = metrics.masked_mse_tf(preds=preds, labels=labels) 156 | self.assertAlmostEqual(1.25, sess.run(rmse), delta=1e-5) 157 | 158 | def test_masked_mse_all_zero(self): 159 | with tf.Session() as sess: 160 | preds = tf.constant(np.array([ 161 | [1, 2], 162 | [3, 4], 163 | ], dtype=np.float32)) 164 | labels = tf.constant(np.array([ 165 | [0, 0], 166 | [0, 0] 167 | ], dtype=np.float32)) 168 | rmse = metrics.masked_mse_tf(preds=preds, labels=labels, null_val=0) 169 | self.assertAlmostEqual(0., sess.run(rmse), delta=1e-5) 170 | 171 | def test_masked_mse_nan(self): 172 | with tf.Session() as sess: 173 | preds = tf.constant(np.array([ 174 | [1, 2], 175 | [3, 4], 176 | ], dtype=np.float32)) 177 | labels = tf.constant(np.array([ 178 | [1, 2], 179 | [3, np.nan] 180 | ], dtype=np.float32)) 181 | rmse = metrics.masked_mse_tf(preds=preds, labels=labels) 182 | self.assertAlmostEqual(0., sess.run(rmse), delta=1e-5) 183 | 184 | def test_masked_mse_all_nan(self): 185 | with tf.Session() as sess: 186 | preds = tf.constant(np.array([ 187 | [1, 2], 188 | [3, 4], 189 | ], dtype=np.float32)) 190 | labels = tf.constant(np.array([ 191 | [np.nan, np.nan], 192 | [np.nan, np.nan] 193 | ], dtype=np.float32)) 194 | rmse = metrics.masked_mse_tf(preds=preds, labels=labels, null_val=0) 195 | self.assertAlmostEqual(0., sess.run(rmse), delta=1e-5) 196 | 197 | if __name__ == '__main__': 198 | unittest.main() 199 | -------------------------------------------------------------------------------- /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 | import torch 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 | # print(type(data)) 63 | return (data - self.mean) / self.std 64 | 65 | def inverse_transform(self, data): 66 | if type(data) == torch.Tensor and type(self.mean) == np.ndarray: 67 | self.std = torch.from_numpy(self.std).to(data.device).type(data.dtype) 68 | self.mean = torch.from_numpy(self.mean).to(data.device).type(data.dtype) 69 | return (data * self.std) + self.mean 70 | def print_m(self): 71 | print(self.mean) 72 | print(self.std) 73 | 74 | def add_simple_summary(writer, names, values, global_step): 75 | """ 76 | Writes summary for a list of scalars. 77 | :param writer: 78 | :param names: 79 | :param values: 80 | :param global_step: 81 | :return: 82 | """ 83 | for name, value in zip(names, values): 84 | summary = tf.Summary() 85 | summary_value = summary.value.add() 86 | summary_value.simple_value = value 87 | summary_value.tag = name 88 | writer.add_summary(summary, global_step) 89 | 90 | 91 | def calculate_normalized_laplacian(adj): 92 | """ 93 | # L = D^-1/2 (D-A) D^-1/2 = I - D^-1/2 A D^-1/2 94 | # D = diag(A 1) 95 | :param adj: 96 | :return: 97 | """ 98 | adj = sp.coo_matrix(adj) 99 | d = np.array(adj.sum(1)) 100 | d_inv_sqrt = np.power(d, -0.5).flatten() 101 | d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0. 102 | d_mat_inv_sqrt = sp.diags(d_inv_sqrt) 103 | normalized_laplacian = sp.eye(adj.shape[0]) - adj.dot(d_mat_inv_sqrt).transpose().dot(d_mat_inv_sqrt).tocoo() 104 | return normalized_laplacian 105 | 106 | 107 | def calculate_random_walk_matrix(adj_mx): 108 | adj_mx = sp.coo_matrix(adj_mx) 109 | d = np.array(adj_mx.sum(1)) 110 | d_inv = np.power(d, -1).flatten() 111 | d_inv[np.isinf(d_inv)] = 0. 112 | d_mat_inv = sp.diags(d_inv) 113 | random_walk_mx = d_mat_inv.dot(adj_mx).tocoo() 114 | return random_walk_mx 115 | 116 | 117 | def calculate_reverse_random_walk_matrix(adj_mx): 118 | return calculate_random_walk_matrix(np.transpose(adj_mx)) 119 | 120 | 121 | def calculate_scaled_laplacian(adj_mx, lambda_max=2, undirected=True): 122 | if undirected: 123 | adj_mx = np.maximum.reduce([adj_mx, adj_mx.T]) 124 | L = calculate_normalized_laplacian(adj_mx) 125 | if lambda_max is None: 126 | lambda_max, _ = linalg.eigsh(L, 1, which='LM') 127 | lambda_max = lambda_max[0] 128 | L = sp.csr_matrix(L) 129 | M, _ = L.shape 130 | I = sp.identity(M, format='csr', dtype=L.dtype) 131 | L = (2 / lambda_max * L) - I 132 | return L.astype(np.float32) 133 | 134 | 135 | def config_logging(log_dir, log_filename='info.log', level=logging.INFO): 136 | # Add file handler and stdout handler 137 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 138 | # Create the log directory if necessary. 139 | try: 140 | os.makedirs(log_dir) 141 | except OSError: 142 | pass 143 | file_handler = logging.FileHandler(os.path.join(log_dir, log_filename)) 144 | file_handler.setFormatter(formatter) 145 | file_handler.setLevel(level=level) 146 | # Add console handler. 147 | console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 148 | console_handler = logging.StreamHandler(sys.stdout) 149 | console_handler.setFormatter(console_formatter) 150 | console_handler.setLevel(level=level) 151 | logging.basicConfig(handlers=[file_handler, console_handler], level=level) 152 | 153 | 154 | def get_logger(log_dir, name, log_filename='info.log', level=logging.INFO): 155 | logger = logging.getLogger(name) 156 | logger.setLevel(level) 157 | # Add file handler and stdout handler 158 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 159 | file_handler = logging.FileHandler(os.path.join(log_dir, log_filename)) 160 | file_handler.setFormatter(formatter) 161 | # Add console handler. 162 | console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 163 | console_handler = logging.StreamHandler(sys.stdout) 164 | console_handler.setFormatter(console_formatter) 165 | logger.addHandler(file_handler) 166 | logger.addHandler(console_handler) 167 | # Add google cloud log handler 168 | logger.info('Log directory: %s', log_dir) 169 | return logger 170 | 171 | 172 | def get_total_trainable_parameter_size(): 173 | """ 174 | Calculates the total number of trainable parameters in the current graph. 175 | :return: 176 | """ 177 | total_parameters = 0 178 | for variable in tf.trainable_variables(): 179 | # shape is an array of tf.Dimension 180 | total_parameters += np.product([x.value for x in variable.get_shape()]) 181 | return total_parameters 182 | 183 | 184 | def load_dataset(dataset_dir, batch_size, test_batch_size=None, **kwargs): 185 | data = {} 186 | for category in ['train', 'val', 'test']: 187 | cat_data = np.load(os.path.join(dataset_dir, category + '.npz'),allow_pickle=True) 188 | data['x_' + category] = cat_data['x'] 189 | data['y_' + category] = cat_data['y'] 190 | # print(data['x_train'][:5]) 191 | mean = data['x_train'][...,0].mean() 192 | std = data['x_train'][...,0].std() 193 | #mean=data['x_train'][:,:,:,0].mean(axis=(0,1)) 194 | #print(mean.shape) 195 | # std=data['x_train'][:,:,:,0].std(axis=(0,1)) 196 | scaler = StandardScaler(mean=mean, std=std) 197 | # print(mean) 198 | # print(data['x_train'][:,:,:,0].mean().shape) 199 | # print(data['x_train'][..., 0].std()) 200 | #print(data['scaler']) 201 | # Data format 202 | #print(1+ 'a') 203 | for category in ['train', 'val', 'test']: 204 | data['x_' + category][..., 0] = scaler.transform(data['x_' + category][..., 0]) 205 | data['y_' + category][..., 0] = scaler.transform(data['y_' + category][..., 0]) 206 | data['train_loader'] = DataLoader(data['x_train'], data['y_train'], batch_size, shuffle=False) 207 | data['val_loader'] = DataLoader(data['x_val'], data['y_val'], test_batch_size, shuffle=False) 208 | data['test_loader'] = DataLoader(data['x_test'], data['y_test'], test_batch_size, shuffle=False) 209 | data['scaler'] = scaler 210 | 211 | #print("ALo: ", data['train_loader'][0,0,:5,:5]) 212 | print("BLo: ", data['x_train'][0,0,:5,:5]) 213 | 214 | return data 215 | 216 | ''' 217 | def load_dataset_with_time(dataset_dir, batch_size, test_batch_size=None, **kwargs): 218 | data = {} 219 | for category in ['train', 'val', 'test']: 220 | cat_data = np.load(os.path.join(dataset_dir, category + '.npz')) 221 | data['x_' + category] = cat_data['x'] 222 | data['y_' + category] = cat_data['y'] 223 | data['time_' + category] = cat_data['time'] 224 | scaler = StandardScaler(mean=data['x_train'][..., 0].mean(), std=data['x_train'][..., 0].std()) 225 | # Data format 226 | for category in ['train', 'val', 'test']: 227 | data['x_' + category][..., 0] = scaler.transform(data['x_' + category][..., 0]) 228 | data['y_' + category][..., 0] = scaler.transform(data['y_' + category][..., 0]) 229 | data['train_loader'] = DataLoader(data['x_train'], data['y_train'], data['time_train'], batch_size, shuffle=True) 230 | data['val_loader'] = DataLoader(data['x_val'], data['y_val'], data['time_val'], test_batch_size, shuffle=False) 231 | data['test_loader'] = DataLoader(data['x_test'], data['y_test'], data['time_test'], test_batch_size, shuffle=False) 232 | data['scaler'] = scaler 233 | 234 | return data 235 | ''' 236 | 237 | def load_graph_data(pkl_filename): 238 | sensor_ids, sensor_id_to_ind, adj_mx = load_pickle(pkl_filename) 239 | return sensor_ids, sensor_id_to_ind, adj_mx 240 | 241 | 242 | def load_pickle(pickle_file): 243 | try: 244 | with open(pickle_file, 'rb') as f: 245 | pickle_data = pickle.load(f) 246 | except UnicodeDecodeError as e: 247 | with open(pickle_file, 'rb') as f: 248 | pickle_data = pickle.load(f, encoding='latin1') 249 | except Exception as e: 250 | print('Unable to load data ', pickle_file, ':', e) 251 | raise 252 | return pickle_data 253 | -------------------------------------------------------------------------------- /model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/model/__init__.py -------------------------------------------------------------------------------- /model/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/model/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /model/pytorch/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/model/pytorch/__init__.py -------------------------------------------------------------------------------- /model/pytorch/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/model/pytorch/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /model/pytorch/__pycache__/cell.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/model/pytorch/__pycache__/cell.cpython-39.pyc -------------------------------------------------------------------------------- /model/pytorch/__pycache__/loss.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/model/pytorch/__pycache__/loss.cpython-39.pyc -------------------------------------------------------------------------------- /model/pytorch/__pycache__/model.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/model/pytorch/__pycache__/model.cpython-39.pyc -------------------------------------------------------------------------------- /model/pytorch/__pycache__/supervisor.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/model/pytorch/__pycache__/supervisor.cpython-39.pyc -------------------------------------------------------------------------------- /model/pytorch/cell.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | from lib import utils 4 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 5 | 6 | class LayerParams: 7 | def __init__(self, rnn_network: torch.nn.Module, layer_type: str): 8 | self._rnn_network = rnn_network 9 | self._params_dict = {} 10 | self._biases_dict = {} 11 | self._type = layer_type 12 | 13 | def get_weights(self, shape): 14 | if shape not in self._params_dict: 15 | nn_param = torch.nn.Parameter(torch.empty(*shape, device=device)) 16 | torch.nn.init.xavier_normal_(nn_param) 17 | self._params_dict[shape] = nn_param 18 | self._rnn_network.register_parameter('{}_weight_{}'.format(self._type, str(shape)), 19 | nn_param) 20 | return self._params_dict[shape] 21 | 22 | def get_biases(self, length, bias_start=0.0): 23 | if length not in self._biases_dict: 24 | biases = torch.nn.Parameter(torch.empty(length, device=device)) 25 | torch.nn.init.constant_(biases, bias_start) 26 | self._biases_dict[length] = biases 27 | self._rnn_network.register_parameter('{}_biases_{}'.format(self._type, str(length)), 28 | biases) 29 | 30 | return self._biases_dict[length] 31 | 32 | 33 | class DCGRUCell(torch.nn.Module): 34 | def __init__(self, num_units, max_diffusion_step, num_nodes, nonlinearity='tanh', 35 | filter_type="laplacian", use_gc_for_ru=True): 36 | """ 37 | 38 | :param num_units: 39 | :param adj_mx: 40 | :param max_diffusion_step: 41 | :param num_nodes: 42 | :param nonlinearity: 43 | :param filter_type: "laplacian", "random_walk", "dual_random_walk". 44 | :param use_gc_for_ru: whether to use Graph convolution to calculate the reset and update gates. 45 | """ 46 | 47 | super().__init__() 48 | self._activation = torch.tanh if nonlinearity == 'tanh' else torch.relu 49 | # support other nonlinearities up here? 50 | self._num_nodes = num_nodes 51 | self._num_units = num_units 52 | self._max_diffusion_step = max_diffusion_step 53 | self._supports = [] 54 | self._use_gc_for_ru = use_gc_for_ru 55 | 56 | ''' 57 | Option: 58 | if filter_type == "laplacian": 59 | supports.append(utils.calculate_scaled_laplacian(adj_mx, lambda_max=None)) 60 | elif filter_type == "random_walk": 61 | supports.append(utils.calculate_random_walk_matrix(adj_mx).T) 62 | elif filter_type == "dual_random_walk": 63 | supports.append(utils.calculate_random_walk_matrix(adj_mx).T) 64 | supports.append(utils.calculate_random_walk_matrix(adj_mx.T).T) 65 | else: 66 | supports.append(utils.calculate_scaled_laplacian(adj_mx)) 67 | for support in supports: 68 | self._supports.append(self._build_sparse_matrix(support)) 69 | ''' 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 _calculate_random_walk_matrix(self, adj_mx): 84 | 85 | # tf.Print(adj_mx, [adj_mx], message="This is adj: ") 86 | 87 | adj_mx = adj_mx + torch.eye(int(adj_mx.shape[0])).to(device) 88 | d = torch.sum(adj_mx, 1) 89 | d_inv = 1. / d 90 | d_inv = torch.where(torch.isinf(d_inv), torch.zeros(d_inv.shape).to(device), d_inv) 91 | d_mat_inv = torch.diag(d_inv) 92 | random_walk_mx = torch.mm(d_mat_inv, adj_mx) 93 | return random_walk_mx 94 | 95 | def forward(self, inputs, hx, adj): 96 | """Gated recurrent unit (GRU) with Graph Convolution. 97 | :param inputs: (B, num_nodes * input_dim) 98 | :param hx: (B, num_nodes * rnn_units) 99 | 100 | :return 101 | - Output: A `2-D` tensor with shape `(B, num_nodes * rnn_units)`. 102 | """ 103 | adj_mx = self._calculate_random_walk_matrix(adj).t() 104 | output_size = 2 * self._num_units 105 | if self._use_gc_for_ru: 106 | fn = self._gconv 107 | else: 108 | fn = self._fc 109 | value = torch.sigmoid(fn(inputs, adj_mx, hx, output_size, bias_start=1.0)) 110 | value = torch.reshape(value, (-1, self._num_nodes, output_size)) 111 | r, u = torch.split(tensor=value, split_size_or_sections=self._num_units, dim=-1) 112 | r = torch.reshape(r, (-1, self._num_nodes * self._num_units)) 113 | u = torch.reshape(u, (-1, self._num_nodes * self._num_units)) 114 | 115 | c = self._gconv(inputs, adj_mx, r * hx, self._num_units) 116 | if self._activation is not None: 117 | c = self._activation(c) 118 | 119 | new_state = u * hx + (1.0 - u) * c 120 | return new_state 121 | 122 | @staticmethod 123 | def _concat(x, x_): 124 | x_ = x_.unsqueeze(0) 125 | return torch.cat([x, x_], dim=0) 126 | 127 | def _fc(self, inputs, state, output_size, bias_start=0.0): 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=-1) 132 | input_size = inputs_and_state.shape[-1] 133 | weights = self._fc_params.get_weights((input_size, output_size)) 134 | value = torch.sigmoid(torch.matmul(inputs_and_state, weights)) 135 | biases = self._fc_params.get_biases(output_size, bias_start) 136 | value += biases 137 | return value 138 | 139 | def _gconv(self, inputs, adj_mx, state, output_size, bias_start=0.0): 140 | # Reshape input and state to (batch_size, num_nodes, input_dim/state_dim) 141 | batch_size = inputs.shape[0] 142 | inputs = torch.reshape(inputs, (batch_size, self._num_nodes, -1)) 143 | state = torch.reshape(state, (batch_size, self._num_nodes, -1)) 144 | inputs_and_state = torch.cat([inputs, state], dim=2) 145 | input_size = inputs_and_state.size(2) 146 | 147 | x = inputs_and_state 148 | x0 = x.permute(1, 2, 0) # (num_nodes, total_arg_size, batch_size) 149 | x0 = torch.reshape(x0, shape=[self._num_nodes, input_size * batch_size]) 150 | x = torch.unsqueeze(x0, 0) 151 | 152 | if self._max_diffusion_step == 0: 153 | pass 154 | else: 155 | x1 = torch.mm(adj_mx, x0) 156 | x = self._concat(x, x1) 157 | 158 | for k in range(2, self._max_diffusion_step + 1): 159 | x2 = 2 * torch.mm(adj_mx, x1) - x0 160 | x = self._concat(x, x2) 161 | x1, x0 = x2, x1 162 | ''' 163 | Option: 164 | for support in self._supports: 165 | x1 = torch.sparse.mm(support, x0) 166 | x = self._concat(x, x1) 167 | 168 | for k in range(2, self._max_diffusion_step + 1): 169 | x2 = 2 * torch.sparse.mm(support, x1) - x0 170 | x = self._concat(x, x2) 171 | x1, x0 = x2, x1 172 | ''' 173 | num_matrices = self._max_diffusion_step + 1 # Adds for x itself. 174 | x = torch.reshape(x, shape=[num_matrices, self._num_nodes, input_size, batch_size]) 175 | x = x.permute(3, 1, 2, 0) # (batch_size, num_nodes, input_size, order) 176 | x = torch.reshape(x, shape=[batch_size * self._num_nodes, input_size * num_matrices]) 177 | 178 | weights = self._gconv_params.get_weights((input_size * num_matrices, output_size)) 179 | x = torch.matmul(x, weights) # (batch_size * self._num_nodes, output_size) 180 | 181 | biases = self._gconv_params.get_biases(output_size, bias_start) 182 | x += biases 183 | # Reshape res back to 2D: (batch_size, num_node, state_dim) -> (batch_size, num_node * state_dim) 184 | return torch.reshape(x, [batch_size, self._num_nodes * output_size]) 185 | 186 | def cell_count_parameters(model): 187 | return sum(p.numel() for p in model.parameters() if p.requires_grad) 188 | -------------------------------------------------------------------------------- /model/pytorch/loss.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | 4 | def masked_mae_loss(y_pred, y_true): 5 | mask = (y_true != 0).float() 6 | mask /= mask.mean() 7 | #print("y_pred:", y_pred.shape, y_true.shape) 8 | 9 | loss = torch.abs(y_pred - y_true) 10 | loss = loss * mask 11 | # trick for nans: https://discuss.pytorch.org/t/how-to-set-nan-in-tensor-to-0/3918/3 12 | loss[loss != loss] = 0 13 | return loss.mean() 14 | 15 | def masked_mape_loss(y_pred, y_true): 16 | mask = (y_true != 0).float() 17 | mask /= mask.mean() 18 | loss = torch.abs(torch.div(y_true - y_pred, y_true)) 19 | loss = loss * mask 20 | # trick for nans: https://discuss.pytorch.org/t/how-to-set-nan-in-tensor-to-0/3918/3 21 | loss[loss != loss] = 0 22 | return loss.mean() 23 | 24 | def masked_rmse_loss(y_pred, y_true): 25 | mask = (y_true != 0).float() 26 | mask /= mask.mean() 27 | loss = torch.pow(y_true - y_pred, 2) 28 | loss = loss * mask 29 | # trick for nans: https://discuss.pytorch.org/t/how-to-set-nan-in-tensor-to-0/3918/3 30 | loss[loss != loss] = 0 31 | return torch.sqrt(loss.mean()) 32 | 33 | def masked_mse_loss(y_pred, y_true): 34 | mask = (y_true != 0).float() 35 | mask /= mask.mean() 36 | loss = torch.pow(y_true - y_pred, 2) 37 | loss = loss * mask 38 | # trick for nans: https://discuss.pytorch.org/t/how-to-set-nan-in-tensor-to-0/3918/3 39 | loss[loss != loss] = 0 40 | return loss.mean() 41 | 42 | -------------------------------------------------------------------------------- /model/pytorch/model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | from torch.nn import functional as F 4 | from model.pytorch.cell import DCGRUCell 5 | import numpy as np 6 | import scipy.sparse as sp 7 | from sklearn.cluster import SpectralClustering 8 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 9 | 10 | def count_parameters(model): 11 | print("all para:", sum(p.numel() for p in model.parameters())) 12 | return sum(p.numel() for p in model.parameters() if p.requires_grad) 13 | 14 | def cosine_similarity_torch(x1, x2=None, eps=1e-8): 15 | x2 = x1 if x2 is None else x2 16 | w1 = x1.norm(p=2, dim=1, keepdim=True) 17 | w2 = w1 if x2 is x1 else x2.norm(p=2, dim=1, keepdim=True) 18 | return torch.mm(x1, x2.t()) / (w1 * w2.t()).clamp(min=eps) 19 | 20 | def sample_gumbel(shape, eps=1e-20): 21 | U = torch.rand(shape).to(device) 22 | return -torch.autograd.Variable(torch.log(-torch.log(U + eps) + eps)) 23 | 24 | def gumbel_softmax_sample(logits, temperature, eps=1e-10): 25 | sample = sample_gumbel(logits.size(), eps=eps) 26 | y = logits + sample 27 | return F.softmax(y / temperature, dim=-1) 28 | 29 | def gumbel_softmax(logits, temperature, hard=False, eps=1e-10): 30 | """Sample from the Gumbel-Softmax distribution and optionally discretize. 31 | Args: 32 | logits: [batch_size, n_class] unnormalized log-probs 33 | temperature: non-negative scalar 34 | hard: if True, take argmax, but differentiate w.r.t. soft sample y 35 | Returns: 36 | [batch_size, n_class] sample from the Gumbel-Softmax distribution. 37 | If hard=True, then the returned sample will be one-hot, otherwise it will 38 | be a probabilitiy distribution that sums to 1 across classes 39 | """ 40 | y_soft = gumbel_softmax_sample(logits, temperature=temperature, eps=eps) 41 | if hard: 42 | shape = logits.size() 43 | _, k = y_soft.data.max(-1) 44 | y_hard = torch.zeros(*shape).to(device) 45 | y_hard = y_hard.zero_().scatter_(-1, k.view(shape[:-1] + (1,)), 1.0) 46 | y = torch.autograd.Variable(y_hard - y_soft.data) + y_soft 47 | else: 48 | y = y_soft 49 | return y 50 | 51 | class Seq2SeqAttrs: 52 | def __init__(self, **model_kwargs): 53 | #self.adj_mx = adj_mx 54 | self.max_diffusion_step = int(model_kwargs.get('max_diffusion_step', 2)) 55 | self.cl_decay_steps = int(model_kwargs.get('cl_decay_steps', 1000)) 56 | self.filter_type = model_kwargs.get('filter_type', 'laplacian') 57 | self.num_nodes = int(model_kwargs.get('num_nodes', 1)) 58 | self.num_rnn_layers = int(model_kwargs.get('num_rnn_layers', 1)) 59 | self.rnn_units = int(model_kwargs.get('rnn_units')) 60 | self.hidden_state_size = self.num_nodes * self.rnn_units 61 | self.model = model_kwargs.get('model') 62 | 63 | class EncoderModel(nn.Module, Seq2SeqAttrs): 64 | def __init__(self, **model_kwargs): 65 | nn.Module.__init__(self) 66 | Seq2SeqAttrs.__init__(self, **model_kwargs) 67 | self.input_dim = int(model_kwargs.get('input_dim', 1)) 68 | self.seq_len = int(model_kwargs.get('seq_len')) # for the encoder 69 | self.dcgru_layers = nn.ModuleList( 70 | [DCGRUCell(self.rnn_units, 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, adj, hidden_state=None): 74 | """ 75 | Encoder forward pass. 76 | :param inputs: shape (batch_size, self.num_nodes * self.input_dim) 77 | :param hidden_state: (num_layers, batch_size, self.hidden_state_size) 78 | optional, zeros if not provided 79 | :return: output: # shape (batch_size, self.hidden_state_size) 80 | hidden_state # shape (num_layers, batch_size, self.hidden_state_size) 81 | (lower indices mean lower layers) 82 | """ 83 | batch_size, _ = inputs.size() 84 | if hidden_state is None: 85 | hidden_state = torch.zeros((self.num_rnn_layers, batch_size, self.hidden_state_size), 86 | device=device) 87 | hidden_states = [] 88 | output = inputs 89 | for layer_num, dcgru_layer in enumerate(self.dcgru_layers): 90 | next_hidden_state = dcgru_layer(output, hidden_state[layer_num], adj) 91 | hidden_states.append(next_hidden_state) 92 | output = next_hidden_state 93 | 94 | return output, torch.stack(hidden_states) # runs in O(num_layers) so not too slow 95 | 96 | 97 | class DecoderModel(nn.Module, Seq2SeqAttrs): 98 | def __init__(self, **model_kwargs): 99 | # super().__init__(is_training, adj_mx, **model_kwargs) 100 | nn.Module.__init__(self) 101 | Seq2SeqAttrs.__init__(self, **model_kwargs) 102 | self.output_dim = int(model_kwargs.get('output_dim', 1)) 103 | self.horizon = int(model_kwargs.get('horizon', 1)) # for the decoder 104 | self.projection_layer = nn.Linear(self.rnn_units, self.output_dim) 105 | self.dcgru_layers = nn.ModuleList( 106 | [DCGRUCell(self.rnn_units, self.max_diffusion_step, self.num_nodes, 107 | filter_type=self.filter_type) for _ in range(self.num_rnn_layers)]) 108 | 109 | def forward(self, inputs, adj, hidden_state=None): 110 | """ 111 | :param inputs: shape (batch_size, self.num_nodes * self.output_dim) 112 | :param hidden_state: (num_layers, batch_size, self.hidden_state_size) 113 | optional, zeros if not provided 114 | :return: output: # shape (batch_size, self.num_nodes * self.output_dim) 115 | hidden_state # shape (num_layers, batch_size, self.hidden_state_size) 116 | (lower indices mean lower layers) 117 | """ 118 | hidden_states = [] 119 | output = inputs 120 | for layer_num, dcgru_layer in enumerate(self.dcgru_layers): 121 | next_hidden_state = dcgru_layer(output, hidden_state[layer_num], adj) 122 | hidden_states.append(next_hidden_state) 123 | output = next_hidden_state 124 | 125 | projected = self.projection_layer(output.view(-1, self.rnn_units)) 126 | output = projected.view(-1, self.num_nodes * self.output_dim) 127 | 128 | return output, torch.stack(hidden_states) 129 | 130 | 131 | class GCRNModel(nn.Module, Seq2SeqAttrs): 132 | def __init__(self, temperature, logger, **model_kwargs): 133 | super().__init__() 134 | Seq2SeqAttrs.__init__(self, **model_kwargs) 135 | self.encoder_model = EncoderModel(**model_kwargs) 136 | self.decoder_model = DecoderModel(**model_kwargs) 137 | self.cl_decay_steps = int(model_kwargs.get('cl_decay_steps', 1000)) 138 | self.use_curriculum_learning = bool(model_kwargs.get('use_curriculum_learning', False)) 139 | print(self.use_curriculum_learning) 140 | self._logger = logger 141 | self.temperature = temperature 142 | # self.dim_fc = int(model_kwargs.get('dim_fc', False)) 143 | # self.embedding_dim = 40 144 | # self.conv1 = torch.nn.Conv1d(1, 8, 3, stride=3) # .to(device) 145 | # self.conv2 = torch.nn.Conv1d(8, 16, 3, stride=4) 146 | # self.conv3 = torch.nn.Conv1d(16, 64, 3, stride=7) # .to(device) 147 | # self.hidden_drop = torch.nn.Dropout(0.2) 148 | # self.fc = torch.nn.Linear(self.dim_fc, self.embedding_dim) 149 | # self.bn1 = torch.nn.BatchNorm1d(8) 150 | # self.bn2 = torch.nn.BatchNorm1d(16) 151 | # self.bn4 = torch.nn.BatchNorm1d(64) 152 | # self.bn3 = torch.nn.BatchNorm1d(self.embedding_dim) 153 | # self.fc_out = nn.Linear(self.embedding_dim * 2, self.embedding_dim) 154 | # self.fc_cat = nn.Linear(self.embedding_dim, 2) 155 | 156 | def encode_onehot(labels): 157 | classes = set(labels) 158 | classes_dict = {c: np.identity(len(classes))[i, :] for i, c in 159 | enumerate(classes)} 160 | labels_onehot = np.array(list(map(classes_dict.get, labels)), 161 | dtype=np.int32) 162 | return labels_onehot 163 | # Generate off-diagonal interaction graph 164 | off_diag = np.ones([self.num_nodes, self.num_nodes]) 165 | rel_rec = np.array(encode_onehot(np.where(off_diag)[0]), dtype=np.float32) 166 | rel_send = np.array(encode_onehot(np.where(off_diag)[1]), dtype=np.float32) 167 | self.rel_rec = torch.FloatTensor(rel_rec).to(device) 168 | self.rel_send = torch.FloatTensor(rel_send).to(device) 169 | 170 | 171 | def _compute_sampling_threshold(self, batches_seen): 172 | return self.cl_decay_steps / ( 173 | self.cl_decay_steps + np.exp(batches_seen / self.cl_decay_steps)) 174 | 175 | def encoder(self, inputs, adj): 176 | """ 177 | Encoder forward pass 178 | :param inputs: shape (seq_len, batch_size, num_sensor * input_dim) 179 | :return: encoder_hidden_state: (num_layers, batch_size, self.hidden_state_size) 180 | """ 181 | encoder_hidden_state = None 182 | for t in range(self.encoder_model.seq_len): 183 | _, encoder_hidden_state = self.encoder_model(inputs[t], adj, encoder_hidden_state) 184 | 185 | return encoder_hidden_state 186 | 187 | def decoder(self, encoder_hidden_state, adj, labels=None, batches_seen=None): 188 | """ 189 | Decoder forward pass 190 | :param encoder_hidden_state: (num_layers, batch_size, self.hidden_state_size) 191 | :param labels: (self.horizon, batch_size, self.num_nodes * self.output_dim) [optional, not exist for inference] 192 | :param batches_seen: global step [optional, not exist for inference] 193 | :return: output: (self.horizon, batch_size, self.num_nodes * self.output_dim) 194 | """ 195 | batch_size = encoder_hidden_state.size(1) 196 | go_symbol = torch.zeros((batch_size, self.num_nodes * self.decoder_model.output_dim), 197 | device=device) 198 | decoder_hidden_state = encoder_hidden_state 199 | decoder_input = go_symbol 200 | 201 | outputs = [] 202 | 203 | for t in range(self.decoder_model.horizon): 204 | decoder_output, decoder_hidden_state = self.decoder_model(decoder_input, adj, 205 | decoder_hidden_state) 206 | decoder_input = decoder_output 207 | outputs.append(decoder_output) 208 | if self.training and self.use_curriculum_learning: 209 | c = np.random.uniform(0, 1) 210 | if c < self._compute_sampling_threshold(batches_seen): 211 | decoder_input = labels[t] 212 | outputs = torch.stack(outputs) 213 | return outputs 214 | 215 | 216 | def forward(self, label, inputs, node_feas, lasso, temp, gumbel_soft, len_interval,labels=None, batches_seen=None): 217 | """ 218 | :param inputs: shape (seq_len, batch_size, num_sensor * input_dim) 219 | :param labels: shape (horizon, batch_size, num_sensor * output) 220 | :param batches_seen: batches seen till now 221 | :return: output: (self.horizon, batch_size, self.num_nodes * self.output_dim) 222 | """ 223 | batch_size= inputs.shape[1] 224 | # if self.model== 'end2end': 225 | # x = node_feas.transpose(1, 0).view(self.num_nodes, 1, -1) 226 | 227 | # x = self.conv1(x) 228 | # x = F.relu(x) 229 | # x = self.bn1(x) 230 | 231 | # x = self.conv2(x) 232 | # x = F.relu(x) 233 | # x = self.bn2(x) 234 | 235 | 236 | # # x = self.conv3(x) 237 | # # x = F.relu(x) 238 | # # x = self.bn4(x) 239 | 240 | 241 | # x = x.view(self.num_nodes, -1) 242 | # x = self.fc(x) 243 | # x = F.relu(x) 244 | # x = self.bn3(x) 245 | 246 | 247 | # receivers = torch.matmul(self.rel_rec, x) 248 | # senders = torch.matmul(self.rel_send, x) 249 | # x = torch.cat([senders, receivers], dim=1) 250 | # x = torch.relu(self.fc_out(x)) 251 | # x = self.fc_cat(x) 252 | 253 | # adj = gumbel_softmax(x, temperature=temp, hard=True) 254 | # adj = adj[:, 0].clone().reshape(self.num_nodes, -1) 255 | 256 | # mask = torch.eye(self.num_nodes, self.num_nodes).bool().to(device) 257 | # adj.masked_fill_(mask, 0) 258 | 259 | 260 | if self.model == "GCRN_full": 261 | adj = torch.ones(self.num_nodes).bool().to(device) 262 | elif self.model == "static_lasso": 263 | adj = gumbel_softmax(lasso, temperature=temp, hard=True) 264 | adj = adj[:].clone().to(torch.float32) 265 | 266 | 267 | elif self.model == "dynamic_lasso": 268 | i= int(batches_seen*batch_size/len_interval) 269 | if i< lasso.shape[0]-1: 270 | adj = gumbel_softmax(lasso[i:i+2], temperature=temp, hard=True).to(device) 271 | else: 272 | adj = gumbel_softmax(torch.stack((lasso[-1],lasso[-1]),), temperature=temp, hard=True).to(device) 273 | adj = adj[:].clone().to(torch.float32) 274 | 275 | if adj.ndim >2: 276 | encoder_hidden_state = 0.5* (self.encoder(inputs, adj[0]) + self.encoder(inputs, adj[1])) 277 | else: 278 | encoder_hidden_state = self.encoder(inputs, adj) 279 | 280 | self._logger.debug("Encoder complete, starting decoder") 281 | if adj.ndim >2: 282 | outputs = self.decoder(encoder_hidden_state, adj[1], labels, batches_seen=batches_seen) 283 | else: 284 | outputs = self.decoder(encoder_hidden_state, adj, labels, batches_seen=batches_seen) 285 | #outputs = self.decoder(encoder_hidden_state, adj, labels, batches_seen=batches_seen) 286 | self._logger.debug("Decoder complete") 287 | if batches_seen == 0: 288 | self._logger.info( 289 | "Total trainable parameters {}".format(count_parameters(self)) 290 | ) 291 | 292 | #clust_adj= self.clustering(adj, x) 293 | return outputs ,adj 294 | 295 | 296 | -------------------------------------------------------------------------------- /model/pytorch/supervisor.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from torch.utils.tensorboard import SummaryWriter 3 | import numpy as np 4 | from lib import utils 5 | from model.pytorch.model import GCRNModel 6 | 7 | from model.pytorch.loss import masked_mae_loss, masked_mape_loss, masked_rmse_loss, masked_mse_loss 8 | import pandas as pd 9 | import os 10 | import time 11 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 12 | 13 | 14 | class GCRNSupervisor: 15 | def __init__(self, save_adj_name, temperature, **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 | self.temperature = float(temperature) 21 | self.opt = self._train_kwargs.get('optimizer') 22 | self.max_grad_norm = self._train_kwargs.get('max_grad_norm', 1.) 23 | self.ANNEAL_RATE = 0.00003 24 | self.temp_min = 0.1 25 | self.save_adj_name = save_adj_name 26 | self.epoch_use_regularization = self._train_kwargs.get('epoch_use_regularization') 27 | self.num_sample = self._train_kwargs.get('num_sample') 28 | #self.lasso = self._data_kwargs.get('lasso') 29 | self.model = self._model_kwargs.get('model') 30 | 31 | self._log_dir = self._get_log_dir(kwargs) 32 | self._writer = SummaryWriter('runs/' + self._log_dir) 33 | log_level = self._kwargs.get('log_level', 'INFO') 34 | self._logger = utils.get_logger(self._log_dir, __name__, 'info.log', level=log_level) 35 | 36 | # data set 37 | self._data = utils.load_dataset(**self._data_kwargs) 38 | self.standard_scaler = self._data['scaler'] 39 | 40 | if self._data_kwargs['dataset_dir'] == 'data/CA1_Food1': 41 | df = pd.read_csv('./data/CA1_Food1.csv') 42 | elif self._data_kwargs['dataset_dir'] == 'data/Electric': 43 | df = pd.read_csv('./data/multivariate-time-series-data-master/electricity/electricity.txt', header=None) 44 | df= df.iloc[:,:100] 45 | elif self._data_kwargs['dataset_dir'] == 'data/solar': 46 | df = pd.read_csv('./data/multivariate-time-series-data-master/solar-energy/solar_AL.txt', header=None) 47 | elif self._data_kwargs['dataset_dir'] == 'data/solar1h': 48 | df = pd.read_csv('data\multivariate-time-series-data-master\solar-energy\solar1h.csv') 49 | elif self._data_kwargs['dataset_dir'] == 'data/CA_1': 50 | df= pd.read_csv('./data/CA_1.csv') 51 | elif self._data_kwargs['dataset_dir'] == 'data/WI_1': 52 | df= pd.read_csv('./data/WI_1.csv') 53 | elif self._data_kwargs['dataset_dir'] == 'data/TX_1': 54 | 55 | df= pd.read_csv('./data/TX_1.csv') 56 | elif self._data_kwargs['dataset_dir'] == 'data/CA_2': 57 | 58 | df= pd.read_csv('./data/CA_2.csv') 59 | elif self._data_kwargs['dataset_dir'] == 'data/TX1_Food1': 60 | df = pd.read_csv('./data/TX1_Food1.csv') 61 | elif self._data_kwargs['dataset_dir'] == 'data/WI1_Food1': 62 | df = pd.read_csv('./data/WI1_Food1.csv') 63 | 64 | num_samples = df.shape[0] 65 | num_train = round(num_samples * 0.7) 66 | df = df.iloc[:num_train].values 67 | print("mean:", df.mean(), "std:", df.std()) 68 | scaler = utils.StandardScaler(mean=df.mean(), std=df.std()) 69 | 70 | train_feas = scaler.transform(df) 71 | print(train_feas.shape) 72 | self._train_feas = torch.Tensor(train_feas).to(device) 73 | #print(self._train_feas.shape) 74 | 75 | # k = self._train_kwargs.get('knn_k') 76 | # knn_metric = 'cosine' 77 | # from sklearn.neighbors import kneighbors_graph 78 | # g = kneighbors_graph(train_feas.T, k, metric=knn_metric) 79 | # g = np.array(g.todense(), dtype=np.float32) 80 | # self.adj_mx = torch.Tensor(g).to(device) 81 | 82 | if self.model=='static_lasso': 83 | tmp=np.load("./" + self._data_kwargs['dataset_dir'] + "/static.npy" , allow_pickle=True) 84 | self._lasso= torch.from_numpy(tmp.item()['Theta']).to(device) 85 | self._len_interval = 0 86 | elif self.model == 'dynamic_lasso': 87 | tmp=np.load("./" + self._data_kwargs['dataset_dir'] + "/dynamic.npy", allow_pickle=True ) 88 | self._len_interval=tmp.item()['len_interval'] 89 | self._lasso= torch.from_numpy(tmp.item()['Theta']).to(device) 90 | elif self.model =="end2end": 91 | self._lasso = 0 92 | self._len_interval = 0 93 | #print([np.sum(g[i]) for i in range(len(g[0]))]) 94 | self.train_len = train_feas.shape[0] 95 | self.num_nodes = int(self._model_kwargs.get('num_nodes', 1)) 96 | self.input_dim = int(self._model_kwargs.get('input_dim', 1)) 97 | self.seq_len = int(self._model_kwargs.get('seq_len')) # for the encoder 98 | self.output_dim = int(self._model_kwargs.get('output_dim', 1)) 99 | self.use_curriculum_learning = bool( 100 | self._model_kwargs.get('use_curriculum_learning', False)) 101 | self.horizon = int(self._model_kwargs.get('horizon', 1)) # for the decoder 102 | 103 | print(self._model_kwargs) 104 | GCRN_model = GCRNModel(self.temperature, self._logger, **self._model_kwargs) 105 | self.GCRN_model = GCRN_model.cuda() if torch.cuda.is_available() else GCRN_model 106 | self._logger.info("Model created") 107 | self._epoch_num = self._train_kwargs.get('epoch', 0) 108 | if self._epoch_num > 0: 109 | self.load_model() 110 | 111 | @staticmethod 112 | def _get_log_dir(kwargs): 113 | log_dir = kwargs['train'].get('log_dir') 114 | if log_dir is None: 115 | batch_size = kwargs['data'].get('batch_size') 116 | learning_rate = kwargs['train'].get('base_lr') 117 | max_diffusion_step = kwargs['model'].get('max_diffusion_step') 118 | num_rnn_layers = kwargs['model'].get('num_rnn_layers') 119 | rnn_units = kwargs['model'].get('rnn_units') 120 | structure = '-'.join( 121 | ['%d' % rnn_units for _ in range(num_rnn_layers)]) 122 | horizon = kwargs['model'].get('horizon') 123 | filter_type = kwargs['model'].get('filter_type') 124 | filter_type_abbr = 'L' 125 | if filter_type == 'random_walk': 126 | filter_type_abbr = 'R' 127 | elif filter_type == 'dual_random_walk': 128 | filter_type_abbr = 'DR' 129 | run_id = 'GCRN_%s_%d_h_%d_%s_lr_%g_bs_%d_%s/' % ( 130 | filter_type_abbr, max_diffusion_step, horizon, 131 | structure, learning_rate, batch_size, 132 | time.strftime('%m%d%H%M%S')) 133 | base_dir = kwargs.get('base_dir') 134 | log_dir = os.path.join(base_dir, run_id) 135 | if not os.path.exists(log_dir): 136 | os.makedirs(log_dir) 137 | return log_dir 138 | 139 | def save_model(self, epoch): 140 | if not os.path.exists('models/'): 141 | os.makedirs('models/') 142 | 143 | config = dict(self._kwargs) 144 | config['model_state_dict'] = self.GCRN_model.state_dict() 145 | config['epoch'] = epoch 146 | torch.save(config, 'models/epo%d.tar' % epoch) 147 | self._logger.info("Saved model at {}".format(epoch)) 148 | return 'models/epo%d.tar' % epoch 149 | 150 | def load_model(self): 151 | self._setup_graph() 152 | assert os.path.exists('models/epo%d.tar' % self._epoch_num), 'Weights at epoch %d not found' % self._epoch_num 153 | checkpoint = torch.load('models/epo%d.tar' % self._epoch_num, map_location='cpu') 154 | self.GCRN_model.load_state_dict(checkpoint['model_state_dict']) 155 | self._logger.info("Loaded model at {}".format(self._epoch_num)) 156 | 157 | def _setup_graph(self): 158 | with torch.no_grad(): 159 | self.GCRN_model = self.GCRN_model.eval() 160 | 161 | val_iterator = self._data['val_loader'].get_iterator() 162 | 163 | for _, (x, y) in enumerate(val_iterator): 164 | x, y = self._prepare_data(x, y) 165 | output = self.GCRN_model(x, self._train_feas) 166 | break 167 | 168 | def train(self, **kwargs): 169 | kwargs.update(self._train_kwargs) 170 | return self._train(**kwargs) 171 | 172 | def evaluate(self,label, dataset='val', batches_seen= 42, gumbel_soft=True): 173 | """ 174 | Computes mean L1Loss 175 | :return: mean L1Loss 176 | """ 177 | with torch.no_grad(): 178 | self.GCRN_model = self.GCRN_model.eval() 179 | 180 | val_iterator = self._data['{}_loader'.format(dataset)].get_iterator() 181 | losses = [] 182 | mapes = [] 183 | #rmses = [] 184 | mses = [] 185 | temp = self.temperature 186 | 187 | l_3 = [] 188 | m_3 = [] 189 | r_3 = [] 190 | l_6 = [] 191 | m_6 = [] 192 | r_6 = [] 193 | l_12 = [] 194 | m_12 = [] 195 | r_12 = [] 196 | 197 | for batch_idx, (x, y) in enumerate(val_iterator): 198 | x, y = self._prepare_data(x, y) 199 | 200 | output, mid_output = self.GCRN_model(label, x, self._train_feas,self._lasso, temp, gumbel_soft, self._len_interval, batches_seen= batches_seen) 201 | 202 | if label == 'without_regularization': 203 | loss = self._compute_loss(y, output) 204 | y_true = self.standard_scaler.inverse_transform(y) 205 | y_pred = self.standard_scaler.inverse_transform(output) 206 | #print("-7 series: ", y[0,0,:], y_true[0,0,:]) 207 | mapes.append(masked_mape_loss(y_pred, y_true).item()) 208 | mses.append(masked_mse_loss(y_pred, y_true).item()) 209 | #rmses.append(masked_rmse_loss(y_pred, y_true).item()) 210 | losses.append(loss.item()) 211 | 212 | 213 | # Followed the DCRNN TensorFlow Implementation 214 | l_3.append(masked_mae_loss(y_pred[2:3], y_true[2:3]).item()) 215 | m_3.append(masked_mape_loss(y_pred[2:3], y_true[2:3]).item()) 216 | r_3.append(masked_mse_loss(y_pred[2:3], y_true[2:3]).item()) 217 | l_6.append(masked_mae_loss(y_pred[5:6], y_true[5:6]).item()) 218 | m_6.append(masked_mape_loss(y_pred[5:6], y_true[5:6]).item()) 219 | r_6.append(masked_mse_loss(y_pred[5:6], y_true[5:6]).item()) 220 | l_12.append(masked_mae_loss(y_pred[11:12], y_true[11:12]).item()) 221 | m_12.append(masked_mape_loss(y_pred[11:12], y_true[11:12]).item()) 222 | r_12.append(masked_mse_loss(y_pred[11:12], y_true[11:12]).item()) 223 | 224 | #print("diff:", torch.abs(y_pred[0,0,:]-y_true[0,0,:]).mean()) 225 | 226 | 227 | else: 228 | loss_1 = self._compute_loss(y, output) 229 | 230 | pred = torch.sigmoid(mid_output.view(mid_output.shape[0] * mid_output.shape[1])) 231 | true_label = self.adj_mx.view(mid_output.shape[0] * mid_output.shape[1]).to(device) 232 | compute_loss = torch.nn.BCELoss() 233 | loss_g = compute_loss(pred, true_label) 234 | loss = loss_1 + loss_g 235 | # option 236 | # loss = loss_1 + 10*loss_g 237 | losses.append((loss_1.item()+loss_g.item())) 238 | 239 | y_true = self.standard_scaler.inverse_transform(y) 240 | y_pred = self.standard_scaler.inverse_transform(output) 241 | mapes.append(masked_mape_loss(y_pred, y_true).item()) 242 | #rmses.append(masked_rmse_loss(y_pred, y_true).item()) 243 | mses.append(masked_mse_loss(y_pred, y_true).item()) 244 | 245 | # Followed the DCRNN TensorFlow Implementation 246 | l_3.append(masked_mae_loss(y_pred[2:3], y_true[2:3]).item()) 247 | m_3.append(masked_mape_loss(y_pred[2:3], y_true[2:3]).item()) 248 | r_3.append(masked_mse_loss(y_pred[2:3], y_true[2:3]).item()) 249 | l_6.append(masked_mae_loss(y_pred[5:6], y_true[5:6]).item()) 250 | m_6.append(masked_mape_loss(y_pred[5:6], y_true[5:6]).item()) 251 | r_6.append(masked_mse_loss(y_pred[5:6], y_true[5:6]).item()) 252 | l_12.append(masked_mae_loss(y_pred[11:12], y_true[11:12]).item()) 253 | m_12.append(masked_mape_loss(y_pred[11:12], y_true[11:12]).item()) 254 | r_12.append(masked_mse_loss(y_pred[11:12], y_true[11:12]).item()) 255 | 256 | # if batch_idx % 40 == 1: 257 | # print(losses) 258 | # temp = np.maximum(temp * np.exp(-self.ANNEAL_RATE * batch_idx), self.temp_min) 259 | mean_loss = np.mean(losses) 260 | 261 | 262 | mean_mape = np.mean(mapes) 263 | mean_rmse = np.sqrt(np.mean(mses)) 264 | # mean_rmse = np.mean(rmses) #another option 265 | 266 | if dataset == 'test': 267 | print(losses[-1]) 268 | # Followed the DCRNN PyTorch Implementation 269 | message = 'Test: mae: {:.4f}, mape: {:.4f}, rmse: {:.4f}'.format(mean_loss, mean_mape, mean_rmse) 270 | self._logger.info(message) 271 | 272 | # Followed the DCRNN TensorFlow Implementation 273 | message = 'Horizon 15mins: mae: {:.4f}, mape: {:.4f}, rmse: {:.4f}'.format(np.mean(l_3), np.mean(m_3), 274 | np.sqrt(np.mean(r_3))) 275 | self._logger.info(message) 276 | message = 'Horizon 30mins: mae: {:.4f}, mape: {:.4f}, rmse: {:.4f}'.format(np.mean(l_6), np.mean(m_6), 277 | np.sqrt(np.mean(r_6))) 278 | self._logger.info(message) 279 | message = 'Horizon 60mins: mae: {:.4f}, mape: {:.4f}, rmse: {:.4f}'.format(np.mean(l_12), np.mean(m_12), 280 | np.sqrt(np.mean(r_12))) 281 | self._logger.info(message) 282 | 283 | self._writer.add_scalar('{} loss'.format(dataset), mean_loss, batches_seen) 284 | if label == 'without_regularization': 285 | return mean_loss, mean_mape, mean_rmse 286 | else: 287 | return mean_loss 288 | 289 | 290 | 291 | def _train(self, base_lr, 292 | steps, patience=200, epochs=100, lr_decay_ratio=0.1, log_every=1, save_model=0, 293 | test_every_n_epochs=10, epsilon=1e-8, **kwargs): 294 | min_val_loss = float('inf') 295 | wait = 0 296 | if self.opt == 'adam': 297 | optimizer = torch.optim.Adam(self.GCRN_model.parameters(), lr=base_lr, eps=epsilon) 298 | 299 | elif self.opt == 'sgd': 300 | optimizer = torch.optim.SGD(self.GCRN_model.parameters(), lr=base_lr) 301 | else: 302 | optimizer = torch.optim.Adam(self.GCRN_model.parameters(), lr=base_lr, eps=epsilon) 303 | 304 | lr_scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=steps, gamma=float(lr_decay_ratio)) 305 | 306 | self._logger.info('Start training ...') 307 | 308 | 309 | 310 | # this will fail if model is loaded with a changed batch_size 311 | num_batches = self._data['train_loader'].num_batch 312 | self._logger.info("num_batches:{}".format(num_batches)) 313 | 314 | batches_seen = num_batches * self._epoch_num 315 | 316 | for epoch_num in range(self._epoch_num, epochs): 317 | print("Num of epoch:",epoch_num) 318 | self.GCRN_model = self.GCRN_model.train() 319 | train_iterator = self._data['train_loader'].get_iterator() 320 | losses = [] 321 | start_time = time.time() 322 | temp = self.temperature 323 | gumbel_soft = True 324 | 325 | if epoch_num < self.epoch_use_regularization: 326 | label = 'without_regularization' 327 | else: 328 | label = 'without_regularization' 329 | 330 | for batch_idx, (x, y) in enumerate(train_iterator): 331 | optimizer.zero_grad() 332 | x, y = self._prepare_data(x, y) 333 | output, adj= self.GCRN_model(label, x, self._train_feas, self._lasso, temp, gumbel_soft, self._len_interval, y, batches_seen) 334 | if (epoch_num % epochs) == epochs - 1: 335 | output, mid_output = self.GCRN_model(label, x, self._train_feas, self._lasso, temp, gumbel_soft, self._len_interval ,y, batches_seen) 336 | 337 | if batches_seen == 0: 338 | if self.opt == 'adam': 339 | optimizer = torch.optim.Adam(self.GCRN_model.parameters(), lr=base_lr, eps=epsilon) 340 | 341 | elif self.opt == 'sgd': 342 | optimizer = torch.optim.SGD(self.GCRN_model.parameters(), lr=base_lr) 343 | else: 344 | optimizer = torch.optim.Adam(self.GCRN_model.parameters(), lr=base_lr, eps=epsilon) 345 | 346 | self.GCRN_model.to(device) 347 | 348 | if label == 'without_regularization': # or label == 'predictor': 349 | #print(type(y)) 350 | #print(type(output)) 351 | loss = self._compute_loss(y, output) 352 | losses.append(loss.item()) 353 | else: 354 | loss_1 = self._compute_loss(y, output) 355 | pred = mid_output.view(mid_output.shape[0] * mid_output.shape[1]) 356 | true_label = self.adj_mx.view(mid_output.shape[0] * mid_output.shape[1]).to(device) 357 | compute_loss = torch.nn.BCELoss() 358 | loss_g = compute_loss(pred, true_label) 359 | loss = loss_1 + loss_g 360 | # option 361 | # loss = loss_1 + 10*loss_g 362 | losses.append((loss_1.item()+loss_g.item())) 363 | 364 | self._logger.debug(loss.item()) 365 | batches_seen += 1 366 | loss.backward() 367 | 368 | # gradient clipping - this does it in place 369 | torch.nn.utils.clip_grad_norm_(self.GCRN_model.parameters(), self.max_grad_norm) 370 | # print(sum(p.numel() for p in self.GCRN_model.parameters())) 371 | optimizer.step() 372 | self._logger.info("epoch complete") 373 | lr_scheduler.step() 374 | self._logger.info("evaluating now!") 375 | end_time = time.time() 376 | 377 | if label == 'without_regularization': 378 | val_loss, val_mape, val_rmse = self.evaluate(label, dataset='val', batches_seen=batches_seen, gumbel_soft=gumbel_soft) 379 | end_time2 = time.time() 380 | self._writer.add_scalar('training loss', 381 | np.mean(losses), 382 | batches_seen) 383 | 384 | if (epoch_num % log_every) == log_every - 1: 385 | message = 'Epoch [{}/{}] ({}) train_mae: {:.4f}, val_mae: {:.4f}, val_mape: {:.4f}, val_rmse: {:.4f}, lr: {:.6f}, ' \ 386 | '{:.1f}s, {:.1f}s'.format(epoch_num, epochs, batches_seen, 387 | np.mean(losses), val_loss, val_mape, val_rmse, 388 | lr_scheduler.get_lr()[0], 389 | (end_time - start_time), (end_time2 - start_time)) 390 | self._logger.info(message) 391 | 392 | if (epoch_num % test_every_n_epochs) == test_every_n_epochs - 1 or epoch_num == 0: 393 | test_loss, test_mape, test_rmse = self.evaluate(label, dataset='test', batches_seen=batches_seen, gumbel_soft=gumbel_soft) 394 | message = 'Epoch [{}/{}] ({}) train_mae: {:.4f}, test_mae: {:.4f}, test_mape: {:.4f}, test_rmse: {:.4f}, lr: {:.6f}, ' \ 395 | '{:.1f}s, {:.1f}s'.format(epoch_num, epochs, batches_seen, 396 | np.mean(losses), test_loss, test_mape, test_rmse, 397 | lr_scheduler.get_lr()[0], 398 | (end_time - start_time), (end_time2 - start_time)) 399 | self._logger.info(message) 400 | else: 401 | val_loss = self.evaluate(label, dataset='val', batches_seen=batches_seen, gumbel_soft=gumbel_soft) 402 | 403 | end_time2 = time.time() 404 | 405 | self._writer.add_scalar('training loss', np.mean(losses), batches_seen) 406 | 407 | if (epoch_num % log_every) == log_every - 1: 408 | message = 'Epoch [{}/{}] ({}) train_mae: {:.4f}, val_mae: {:.4f}'.format(epoch_num, epochs, 409 | batches_seen, 410 | np.mean(losses), val_loss) 411 | self._logger.info(message) 412 | if (epoch_num % test_every_n_epochs) == test_every_n_epochs - 1: 413 | test_loss = self.evaluate(label, dataset='test', batches_seen=batches_seen, gumbel_soft=gumbel_soft) 414 | message = 'Epoch [{}/{}] ({}) train_mae: {:.4f}, test_mae: {:.4f}, lr: {:.6f}, ' \ 415 | '{:.1f}s, {:.1f}s'.format(epoch_num, epochs, batches_seen, 416 | np.mean(losses), test_loss, lr_scheduler.get_lr()[0], 417 | (end_time - start_time), (end_time2 - start_time)) 418 | self._logger.info(message) 419 | 420 | if val_loss < min_val_loss: 421 | wait = 0 422 | if save_model: 423 | model_file_name = self.save_model(epoch_num) 424 | self._logger.info( 425 | 'Val loss decrease from {:.4f} to {:.4f}, ' 426 | 'saving to {}'.format(min_val_loss, val_loss, model_file_name)) 427 | min_val_loss = val_loss 428 | 429 | elif val_loss >= min_val_loss: 430 | wait += 1 431 | if wait == patience: 432 | self._logger.warning('Early stopping at epoch: %d' % epoch_num) 433 | break 434 | 435 | def _prepare_data(self, x, y): 436 | x, y = self._get_x_y(x, y) 437 | x, y = self._get_x_y_in_correct_dims(x, y) 438 | return x.to(device), y.to(device) 439 | 440 | def _get_x_y(self, x, y): 441 | """ 442 | :param x: shape (batch_size, seq_len, num_sensor, input_dim) 443 | :param y: shape (batch_size, horizon, num_sensor, input_dim) 444 | :returns x shape (seq_len, batch_size, num_sensor, input_dim) 445 | y shape (horizon, batch_size, num_sensor, input_dim) 446 | """ 447 | 448 | x=np.vstack(x).astype(np.float).reshape(x.shape) 449 | y=np.vstack(y).astype(np.float).reshape(y.shape) 450 | 451 | #print(x.shape) 452 | 453 | x = torch.from_numpy(x).float() 454 | y = torch.from_numpy(y).float() 455 | self._logger.debug("X: {}".format(x.size())) 456 | self._logger.debug("y: {}".format(y.size())) 457 | 458 | 459 | x = x.permute(1, 0, 2, 3) 460 | y = y.permute(1, 0, 2, 3) 461 | #print(x.size()) 462 | # print(x[0,0,0,:]) 463 | # print(y[0,0,0,:]) 464 | return x, y 465 | 466 | def _get_x_y_in_correct_dims(self, x, y): 467 | """ 468 | :param x: shape (seq_len, batch_size, num_sensor, input_dim) 469 | :param y: shape (horizon, batch_size, num_sensor, input_dim) 470 | :return: x: shape (seq_len, batch_size, num_sensor * input_dim) 471 | y: shape (horizon, batch_size, num_sensor * output_dim) 472 | """ 473 | batch_size = x.size(1) 474 | 475 | #print(self.seq_len, batch_size, self.num_nodes * self.input_dim) 476 | #print(x.size()) 477 | #x = x.view(self.seq_len, batch_size,-1) 478 | x = x.reshape(self.seq_len,batch_size,-1) 479 | #y = y[..., :self.output_dim].view(self.horizon, batch_size,-1) 480 | y = y[..., :self.output_dim].reshape((self.horizon,batch_size,-1)) 481 | #print(x.size()) 482 | # print(x[0,0,:]) 483 | # print(x.size()) 484 | 485 | return x, y 486 | 487 | def _compute_loss(self, y_true, y_predicted): 488 | #print(y_true) 489 | y_true = self.standard_scaler.inverse_transform(y_true) 490 | #print("inverrt?: ", y_true) 491 | #print(y_predicted) 492 | y_predicted = self.standard_scaler.inverse_transform(y_predicted) 493 | #if np.rand() 494 | # print(y_true) 495 | 496 | return masked_mae_loss(y_predicted, y_true) 497 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/scripts/__init__.py -------------------------------------------------------------------------------- /scripts/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/scripts/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /scripts/__pycache__/generate_training_data.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HySonLab/GraphLASSO/84eb0fadae2fd2dfec7f16190e8a2fc130f23a8e/scripts/__pycache__/generate_training_data.cpython-39.pyc -------------------------------------------------------------------------------- /scripts/gen_adj_mx.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | 5 | import argparse 6 | import numpy as np 7 | import pandas as pd 8 | import pickle 9 | 10 | 11 | def get_adjacency_matrix(distance_df, sensor_ids, normalized_k=0.1): 12 | """ 13 | 14 | :param distance_df: data frame with three columns: [from, to, distance]. 15 | :param sensor_ids: list of sensor ids. 16 | :param normalized_k: entries that become lower than normalized_k after normalization are set to zero for sparsity. 17 | :return: 18 | """ 19 | num_sensors = len(sensor_ids) 20 | dist_mx = np.zeros((num_sensors, num_sensors), dtype=np.float32) 21 | dist_mx[:] = np.inf 22 | # Builds sensor id to index map. 23 | sensor_id_to_ind = {} 24 | for i, sensor_id in enumerate(sensor_ids): 25 | sensor_id_to_ind[sensor_id] = i 26 | 27 | # Fills cells in the matrix with distances. 28 | for row in distance_df.values: 29 | if row[0] not in sensor_id_to_ind or row[1] not in sensor_id_to_ind: 30 | continue 31 | dist_mx[sensor_id_to_ind[row[0]], sensor_id_to_ind[row[1]]] = row[2] 32 | 33 | # Calculates the standard deviation as theta. 34 | distances = dist_mx[~np.isinf(dist_mx)].flatten() 35 | std = distances.std() 36 | adj_mx = np.exp(-np.square(dist_mx / std)) 37 | # Make the adjacent matrix symmetric by taking the max. 38 | # adj_mx = np.maximum.reduce([adj_mx, adj_mx.T]) 39 | 40 | # Sets entries that lower than a threshold, i.e., k, to zero for sparsity. 41 | adj_mx[adj_mx < normalized_k] = 0 42 | return sensor_ids, sensor_id_to_ind, adj_mx 43 | 44 | 45 | if __name__ == '__main__': 46 | parser = argparse.ArgumentParser() 47 | parser.add_argument('--sensor_ids_filename', type=str, default='data/sensor_graph/graph_sensor_ids.txt', 48 | help='File containing sensor ids separated by comma.') 49 | parser.add_argument('--distances_filename', type=str, default='data/sensor_graph/distances_la_2012.csv', 50 | help='CSV file containing sensor distances with three columns: [from, to, distance].') 51 | parser.add_argument('--normalized_k', type=float, default=0.1, 52 | help='Entries that become lower than normalized_k after normalization are set to zero for sparsity.') 53 | parser.add_argument('--output_pkl_filename', type=str, default='data/sensor_graph/adj_mat.pkl', 54 | help='Path of the output file.') 55 | args = parser.parse_args() 56 | 57 | with open(args.sensor_ids_filename) as f: 58 | sensor_ids = f.read().strip().split(',') 59 | distance_df = pd.read_csv(args.distances_filename, dtype={'from': 'str', 'to': 'str'}) 60 | _, sensor_id_to_ind, adj_mx = get_adjacency_matrix(distance_df, sensor_ids) 61 | # Save to pickle file. 62 | with open(args.output_pkl_filename, 'wb') as f: 63 | pickle.dump([sensor_ids, sensor_id_to_ind, adj_mx], f, protocol=2) 64 | -------------------------------------------------------------------------------- /scripts/generate_adj_lasso.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import argparse 4 | import os 5 | 6 | from sklearn.covariance import GraphicalLasso 7 | from regain.covariance import TimeGraphicalLasso 8 | 9 | from sklearn.preprocessing import MinMaxScaler, StandardScaler 10 | 11 | from gglasso.solver.admm_solver import ADMM_MGL 12 | from gglasso.helper.data_generation import time_varying_power_network, sample_covariance_matrix, generate_precision_matrix 13 | from gglasso.helper.experiment_helper import lambda_grid, discovery_rate, error 14 | from gglasso.helper.utils import get_K_identity 15 | from gglasso.helper.experiment_helper import plot_evolution, plot_deviation, surface_plot, single_heatmap_animation 16 | from gglasso.helper.model_selection import aic, ebic 17 | from gglasso.problem import glasso_problem 18 | import matplotlib.pyplot as plt 19 | import networkx as nx 20 | 21 | 22 | # L = int(p/M) 23 | 24 | # reg = 'FGL' 25 | 26 | # Sigma, Theta = time_varying_power_network(p, K, M=5, scale = False, seed = 2340) 27 | # S, sample = sample_covariance_matrix(Sigma, N) 28 | 29 | # results = {} 30 | # results['truth'] = {'Theta' : Theta} 31 | 32 | # # anim = single_heatmap_animation(Theta) 33 | 34 | # l1 = 0.1 35 | # l2 = 0.05 36 | # tmp = sample.transpose(1,0,2).reshape(p,-1).T 37 | 38 | 39 | # Omega_0 = get_K_identity(K,p) 40 | 41 | # sol, info = ADMM_MGL(S, l1, l2, reg, Omega_0, rho = 1, max_iter = 1000, \ 42 | # tol = 1e-10, rtol = 1e-10, verbose = False, measure = True) 43 | # #ltgl = ltgl.fit(X = tmp, y = np.repeat(np.arange(K),N)) 44 | 45 | # results['ADMM'] = {'Theta' : sol['Theta']} 46 | 47 | 48 | # diff= results['ADMM']['Theta'] 49 | 50 | # create time stamp in first dimension 51 | 52 | # Sigma, Theta = generate_precision_matrix(p=20, M=1, style='erdos', prob=0.1, seed=1234) 53 | 54 | # S, sample = sample_covariance_matrix(Sigma, N) 55 | 56 | # P = glasso_problem(S, N, reg_params={'lambda1': 0.05} ,latent = False, do_scaling = False) 57 | # print(P) 58 | 59 | # lambda1_range = np.logspace(0, -3, 30) 60 | # modelselect_params = {'lambda1_range': lambda1_range} 61 | 62 | # # P.model_selection(modelselect_params = modelselect_params, method = 'eBIC', gamma = 0.1) 63 | 64 | # # regularization parameters are set to the best ones found during model selection 65 | 66 | # P.solve() 67 | # print(P.reg_params) 68 | 69 | # sol = P.solution.precision_ 70 | #res[] 71 | 72 | def generate_lasso(args): 73 | if args.df_filename[-3:] =='.h5': 74 | df = pd.read_hdf(args.df_filename) 75 | elif args.df_filename[-4:] =='.csv': 76 | df = pd.read_csv(args.df_filename) 77 | len_data = df.shape[0] 78 | 79 | K= args.len_interval 80 | mode = args.mode 81 | modify_cov= args.cov_modify 82 | 83 | eps = 0.001 84 | 85 | if mode =='static': 86 | 87 | t_test = round(len_data * 0.9) 88 | t_train = round(len_data * 0.8) 89 | 90 | elif mode=='dynamic': 91 | 92 | t_train = int(round(len_data * 0.8)/K) *K 93 | t_test = round(len_data * 0.9) 94 | 95 | print("len train:", t_train) 96 | print("len test:", len_data -t_train) 97 | train =df.iloc[:t_train,:] 98 | val =df.iloc[t_train: t_test,:] 99 | test=df.iloc[t_test: ,:] 100 | 101 | #S= np.array(train.cov(numeric_only=True)) 102 | p= train.shape[1] 103 | train_scaled = StandardScaler().fit_transform(train) 104 | 105 | if mode == "static": 106 | S= np.array(np.cov(train_scaled.T)) 107 | if modify_cov == True: 108 | S+= eps*np.eye(p,p) 109 | 110 | l1 = args.Lambda 111 | P = glasso_problem(S, t_train, reg_params = {'lambda1': l1}, latent = False, do_scaling = args.scaling) 112 | P.solve(tol = 1e-10, rtol = 1e-10,) 113 | 114 | 115 | sol = P.solution.precision_ 116 | #P.solution.calc_adjacency(t = 1e-4) 117 | 118 | result = sol 119 | 120 | 121 | elif mode =="dynamic": 122 | number_intervals = int(t_train/K) 123 | Omega_0 = get_K_identity(number_intervals,p) 124 | S=Omega_0.copy() 125 | 126 | for i in range(number_intervals): 127 | 128 | S[i,:,:] = np.array(train.iloc[K*i:K*(i+1),:].cov()) 129 | if modify_cov == True: 130 | S[i,:,:]+= eps*np.eye(p,p) 131 | 132 | l1 = args.Lambda 133 | l2 = args.Beta 134 | 135 | sol, info = ADMM_MGL(S, l1, l2, reg = 'FGL', Omega_0=Omega_0, rho = 1, max_iter = 1000, \ 136 | tol = 1e-5, rtol = 1e-5, verbose = False, measure = True) 137 | 138 | 139 | result= sol['Theta'] 140 | 141 | return result 142 | 143 | 144 | def main(args): 145 | print("Generating training data") 146 | T = generate_lasso(args) 147 | save= {'len_interval': args.len_interval, 'Theta': T} 148 | np.save(os.path.join(args.output_dir, "%s.npy" % args.mode), save, allow_pickle=True) 149 | print("Tau") 150 | 151 | if __name__ == "__main__": 152 | parser = argparse.ArgumentParser() 153 | parser.add_argument( 154 | "--output_dir", type=str, default="data/CA1_Food1", help="Output directory." 155 | ) 156 | parser.add_argument( 157 | "--df_filename", 158 | type=str, 159 | default="data/CA1_Food1.csv", 160 | help="Raw data readings.", 161 | ) 162 | parser.add_argument( 163 | "--mode", type=str, default= 'static', help="unique or time-varying graphical lasso." 164 | ) 165 | parser.add_argument( 166 | "--cov_modify", type=bool, default= True, help="add epsilon to diagonal of covariance matrix." 167 | ) 168 | 169 | parser.add_argument( 170 | "--Lambda", type=float, default= 0.02, help="Lambda coefficent Lasso norm." 171 | ) 172 | parser.add_argument( 173 | "--Beta", type=float, default= 0.05, help="Beta coefficent time-varying constraint." 174 | ) 175 | parser.add_argument( 176 | "--len_interval", type=int, default= 180, help="Length intervals in time-varying cases." 177 | ) 178 | parser.add_argument( 179 | "--scaling", type=bool, default= True, help="Scaling for equaly treating." 180 | ) 181 | 182 | args = parser.parse_args() 183 | main(args) 184 | 185 | 186 | -------------------------------------------------------------------------------- /scripts/generate_training_data.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | 6 | import argparse 7 | import numpy as np 8 | import os 9 | import pandas as pd 10 | 11 | 12 | def generate_graph_seq2seq_io_data( 13 | df, x_offsets, y_offsets, add_time_in_day=True, add_day_in_week=True, scaler=None, csv= True, has_index=False 14 | ): 15 | """ 16 | Generate samples from 17 | :param df: 18 | :param x_offsets: 19 | :param y_offsets: 20 | :param add_time_in_day: 21 | :param add_day_in_week: 22 | :param scaler: 23 | :return: 24 | # x: (epoch_size, input_length, num_nodes, input_dim) 25 | # y: (epoch_size, output_length, num_nodes, output_dim) 26 | """ 27 | if has_index: 28 | df=df.iloc[:,1:] 29 | 30 | num_samples, num_nodes = df.shape 31 | data = np.expand_dims(df.values, axis=-1) 32 | 33 | data_list = [data] 34 | 35 | 36 | 37 | if csv: 38 | print("shape:", data.shape) 39 | # time_ind=list(df.iloc[:,0].apply(lambda x: int(x[2:]))) 40 | # time_in_day = np.tile(time_ind, [1, num_nodes, 1]).transpose((2, 1, 0)) 41 | # print(time_in_day.shape) 42 | # data_list.append(time_in_day) 43 | else: 44 | if add_time_in_day: 45 | time_ind = (df.index.values - df.index.values.astype("datetime64[D]")) / np.timedelta64(1, "D") 46 | time_in_day = np.tile(time_ind, [1, num_nodes, 1]).transpose((2, 1, 0)) 47 | data_list.append(time_in_day) 48 | if add_day_in_week: 49 | day_in_week = np.zeros(shape=(num_samples, num_nodes, 7)) 50 | day_in_week[np.arange(num_samples), :, df.index.dayofweek] = 1 51 | data_list.append(day_in_week) 52 | 53 | data = np.concatenate(data_list, axis=-1) 54 | 55 | 56 | # epoch_len = num_samples + min(x_offsets) - max(y_offsets) 57 | x, y = [], [] 58 | # t is the index of the last observation. 59 | min_t = abs(min(x_offsets)) 60 | max_t = abs(num_samples - abs(max(y_offsets))) # Exclusive 61 | for t in range(min_t, max_t): 62 | x_t = data[t + x_offsets, ...] 63 | y_t = data[t + y_offsets, ...] 64 | x.append(x_t) 65 | y.append(y_t) 66 | x = np.stack(x, axis=0) 67 | y = np.stack(y, axis=0) 68 | # print(x.shape) 69 | # print(x_t[0,...]) 70 | # print(y_t[0,...]) 71 | 72 | return x, y 73 | 74 | 75 | def generate_train_val_test(args): 76 | if args.df_filename[-3:] =='.h5': 77 | df = pd.read_hdf(args.df_filename) 78 | elif args.df_filename[-4:] =='.csv': 79 | df = pd.read_csv(args.df_filename) 80 | 81 | if "dept_CA1.csv" in args.df_filename: 82 | index=True 83 | else: 84 | index=False 85 | # 0 is the latest observed sample. 86 | input_length=args.input_len 87 | output_length=args.output_len 88 | 89 | x_offsets = np.sort( 90 | # np.concatenate(([-week_size + 1, -day_size + 1], np.arange(-11, 1, 1))) 91 | # here to change the input and output length 92 | np.concatenate((np.arange(-input_length+1, 1, 1),)) 93 | ) 94 | # Predict the next one hour 95 | y_offsets = np.sort(np.arange(1,output_length+1, 1)) 96 | # x: (num_samples, input_length, num_nodes, input_dim) 97 | # y: (num_samples, output_length, num_nodes, output_dim) 98 | len_data = df.shape[0] 99 | t_test = round(len_data * 0.9) 100 | t_train = round(len_data * 0.7) 101 | train =df.iloc[:t_train,:] 102 | val =df.iloc[t_train: t_test,:] 103 | test=df.iloc[t_test: ,:] 104 | 105 | x_train, y_train = generate_graph_seq2seq_io_data(train,x_offsets=x_offsets, 106 | y_offsets=y_offsets, 107 | add_time_in_day=True, 108 | add_day_in_week=False, 109 | has_index=index) 110 | x_val, y_val = generate_graph_seq2seq_io_data(val,x_offsets=x_offsets, 111 | y_offsets=y_offsets, 112 | add_time_in_day=False, 113 | add_day_in_week=False, 114 | has_index=index) 115 | x_test, y_test = generate_graph_seq2seq_io_data(test,x_offsets=x_offsets, 116 | y_offsets=y_offsets, 117 | add_time_in_day=False, 118 | add_day_in_week=False, 119 | has_index=index) 120 | 121 | 122 | # x, y = generate_graph_seq2seq_io_data( 123 | # df, 124 | # x_offsets=x_offsets, 125 | # y_offsets=y_offsets, 126 | # add_time_in_day=True, 127 | # add_day_in_week=False, 128 | # has_index=index 129 | # ) 130 | 131 | # print("x shape: ", x.shape, ", y shape: ", y.shape) 132 | # # Write the data into npz file. 133 | # # num_test = 6831, using the last 6831 examples as testing. 134 | # # for the rest: 7/8 is used for training, and 1/8 is used for validation. 135 | 136 | # # train 137 | # x_train, y_train = x[:num_train], y[:num_train] 138 | # # val 139 | # x_val, y_val = ( 140 | # x[num_train: num_train + num_val], 141 | # y[num_train: num_train + num_val], 142 | # ) 143 | # # test 144 | # x_test, y_test = x[-num_test:], y[-num_test:] 145 | 146 | for cat in ["train", "val", "test"]: 147 | _x, _y = locals()["x_" + cat], locals()["y_" + cat] 148 | print(cat, "x: ", _x.shape, "y:", _y.shape) 149 | np.savez_compressed( 150 | os.path.join(args.output_dir, "%s.npz" % cat), 151 | x=_x, 152 | y=_y, 153 | x_offsets=x_offsets.reshape(list(x_offsets.shape) + [1]), 154 | y_offsets=y_offsets.reshape(list(y_offsets.shape) + [1]), 155 | ) 156 | 157 | 158 | def main(args): 159 | print("Generating training data") 160 | generate_train_val_test(args) 161 | 162 | 163 | if __name__ == "__main__": 164 | parser = argparse.ArgumentParser() 165 | parser.add_argument( 166 | "--output_dir", type=str, default="data/CA1_Food1", help="Output directory." 167 | ) 168 | parser.add_argument( 169 | "--df_filename", 170 | type=str, 171 | default="data/CA1_Food1.csv", 172 | help="Raw traffic readings.", 173 | ) 174 | parser.add_argument( 175 | "--output_len", type=int, default= 7, help="Output len." 176 | ) 177 | parser.add_argument( 178 | "--input_len", type=int, default= 14, help="Input len." 179 | ) 180 | args = parser.parse_args() 181 | main(args) 182 | -------------------------------------------------------------------------------- /scripts/generate_visualization_data.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | 6 | import argparse 7 | import numpy as np 8 | import os 9 | import pandas as pd 10 | 11 | 12 | def generate_graph_seq2seq_io_data( 13 | df, x_offsets, y_offsets, add_time_in_day=True, add_day_in_week=False, scaler=None 14 | ): 15 | """ 16 | Generate samples from 17 | :param df: 18 | :param x_offsets: 19 | :param y_offsets: 20 | :param add_time_in_day: 21 | :param add_day_in_week: 22 | :param scaler: 23 | :return: 24 | # x: (epoch_size, input_length, num_nodes, input_dim) 25 | # y: (epoch_size, output_length, num_nodes, output_dim) 26 | """ 27 | 28 | num_samples, num_nodes = df.shape 29 | data = np.expand_dims(df.values, axis=-1) 30 | data_list = [data] 31 | if add_time_in_day: 32 | time_ind = (df.index.values - df.index.values.astype("datetime64[D]")) / np.timedelta64(1, "D") 33 | time_in_day = np.tile(time_ind, [1, num_nodes, 1]).transpose((2, 1, 0)) 34 | data_list.append(time_in_day) 35 | if add_day_in_week: 36 | day_in_week = np.zeros(shape=(num_samples, num_nodes, 7)) 37 | day_in_week[np.arange(num_samples), :, df.index.dayofweek] = 1 38 | data_list.append(day_in_week) 39 | 40 | data = np.concatenate(data_list, axis=-1) 41 | index = np.array(df.index) 42 | # epoch_len = num_samples + min(x_offsets) - max(y_offsets) 43 | x, y = [], [] 44 | indexes = [] 45 | # t is the index of the last observation. 46 | min_t = abs(min(x_offsets)) 47 | max_t = abs(num_samples - abs(max(y_offsets))) # Exclusive 48 | for t in range(min_t, max_t, 12): 49 | x_t = data[t + x_offsets, ...] 50 | y_t = data[t + y_offsets, ...] 51 | x.append(x_t) 52 | y.append(y_t) 53 | ind = index[t + y_offsets] 54 | indexes.append(ind) 55 | x = np.stack(x, axis=0) 56 | y = np.stack(y, axis=0) 57 | indexes = np.stack(indexes, axis=0) 58 | return x, y, indexes 59 | 60 | 61 | def generate_train_val_test(args): 62 | df = pd.read_hdf(args.traffic_df_filename) 63 | # 0 is the latest observed sample. 64 | x_offsets = np.sort( 65 | # np.concatenate(([-week_size + 1, -day_size + 1], np.arange(-11, 1, 1))) 66 | np.concatenate((np.arange(-11, 1, 1),)) 67 | ) 68 | # Predict the next one hour 69 | y_offsets = np.sort(np.arange(1, 13, 1)) 70 | # x: (num_samples, input_length, num_nodes, input_dim) 71 | # y: (num_samples, output_length, num_nodes, output_dim) 72 | x, y, index = generate_graph_seq2seq_io_data( 73 | df, 74 | x_offsets=x_offsets, 75 | y_offsets=y_offsets, 76 | add_time_in_day=True, 77 | add_day_in_week=False, 78 | ) 79 | time = df.index 80 | 81 | print("x shape: ", x.shape, ", y shape: ", y.shape) 82 | # Write the data into npz file. 83 | # num_test = 6831, using the last 6831 examples as testing. 84 | # for the rest: 7/8 is used for training, and 1/8 is used for validation. 85 | num_samples = time.shape[0] 86 | num_test = round(num_samples * 0.2) 87 | num_train = round(num_samples * 0.7) 88 | num_val = num_samples - num_test - num_train 89 | 90 | # train 91 | x_train, y_train, time_train = x[:num_train], y[:num_train], index[:num_train] 92 | # val 93 | x_val, y_val, time_val = ( 94 | x[num_train: num_train + num_val], 95 | y[num_train: num_train + num_val], 96 | index[num_train: num_train + num_val], 97 | ) 98 | # test 99 | x_test, y_test, time_test = x[-num_test:], y[-num_test:], index[-num_test:] 100 | 101 | for cat in ["train", "val", "test"]: 102 | _x, _y, _time = locals()["x_" + cat], locals()["y_" + cat], locals()["time_" + cat] 103 | print(cat, "x: ", _x.shape, "y:", _y.shape) 104 | np.savez_compressed( 105 | os.path.join(args.output_dir, "%s.npz" % cat), 106 | x=_x, 107 | y=_y, 108 | time = _time, 109 | x_offsets=x_offsets.reshape(list(x_offsets.shape) + [1]), 110 | y_offsets=y_offsets.reshape(list(y_offsets.shape) + [1]), 111 | time_offsets=y_offsets.reshape(list(y_offsets.shape) + [1]), 112 | ) 113 | 114 | 115 | def main(args): 116 | print("Generating training data") 117 | generate_train_val_test(args) 118 | 119 | 120 | if __name__ == "__main__": 121 | parser = argparse.ArgumentParser() 122 | parser.add_argument( 123 | "--output_dir", type=str, default="../data/la_time", help="Output directory." 124 | ) 125 | parser.add_argument( 126 | "--traffic_df_filename", 127 | type=str, 128 | default="../data/metr-la.h5", 129 | help="Raw traffic readings.", 130 | ) 131 | args = parser.parse_args() 132 | main(args) 133 | -------------------------------------------------------------------------------- /train.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 | from model.pytorch.supervisor import GCRNSupervisor 8 | from lib.utils import load_graph_data 9 | 10 | def main(args): 11 | with open(args.config_filename) as f: 12 | supervisor_config = yaml.full_load(f) 13 | save_adj_name = args.config_filename[11:-5] 14 | supervisor = GCRNSupervisor(save_adj_name, temperature=args.temperature, **supervisor_config) 15 | supervisor.train() 16 | print("") 17 | 18 | if __name__ == '__main__': 19 | parser = argparse.ArgumentParser() 20 | parser.add_argument('--config_filename', default='data/config/para_CA1_Food1.yaml', type=str, 21 | help='Configuration filename for restoring the model.') 22 | parser.add_argument('--use_cpu_only', default=False, type=bool, help='Set to true to only use cpu.') 23 | parser.add_argument('--temperature', default=0.5, type=float, help='temperature value for gumbel-softmax.') 24 | args = parser.parse_args() 25 | main(args) --------------------------------------------------------------------------------