import pickle import torch import numpy as np import pandas as pd import scipy.sparse as sp from scipy.sparse import linalg class DataLoader(object): def __init__(self, xs, ys, batch_size, pad_with_last_sample=True, shuffle=False): """ :param xs: :param ys: :param batch_size: :param pad_with_last_sample: pad with the last sample to make number of samples divisible to batch_size. """ self.batch_size = batch_size self.current_ind = 0 if pad_with_last_sample: num_padding = (batch_size - (len(xs) % batch_size)) % batch_size x_padding = np.repeat(xs[-1:], num_padding, axis=0) y_padding = np.repeat(ys[-1:], num_padding, axis=0) xs = np.concatenate([xs, x_padding], axis=0) ys = np.concatenate([ys, y_padding], axis=0) self.size = len(xs) self.num_batch = int(self.size // self.batch_size) if shuffle: permutation = np.random.permutation(self.size) xs, ys = xs[permutation], ys[permutation] self.xs = xs self.ys = ys def get_iterator(self): self.current_ind = 0 def _wrapper(): while self.current_ind < self.num_batch: start_ind = self.batch_size * self.current_ind end_ind = min(self.size, self.batch_size * (self.current_ind + 1)) x_i = self.xs[start_ind: end_ind, ...] y_i = self.ys[start_ind: end_ind, ...] yield (x_i, y_i) self.current_ind += 1 return _wrapper() class StandardScaler(): def __init__(self, mean, std): self.mean = mean self.std = std def transform(self, data): return (data - self.mean) / self.std def inverse_transform(self, data): return (data * self.std) + self.mean def getTimestamp(data): num_samples, num_nodes = data.shape time_ind = (data.index.values - data.index.values.astype("datetime64[D]")) / np.timedelta64(1, "D") time_in_day = np.tile(time_ind, [num_nodes,1]).transpose((1, 0)) return time_in_day def getDayTimestamp(data): # 288 timeslots each day for dataset has 5 minutes time interval. df = pd.DataFrame({'timestamp':data.index.values}) df['weekdaytime'] = df['timestamp'].dt.weekday * 288 + (df['timestamp'].dt.hour * 60 + df['timestamp'].dt.minute)//5 df['weekdaytime'] = df['weekdaytime'] / df['weekdaytime'].max() num_samples, num_nodes = data.shape time_ind = df['weekdaytime'].values time_ind_node = np.tile(time_ind, [num_nodes,1]).transpose((1, 0)) return time_ind_node def getDayTimestamp_(start, end, freq, num_nodes): # 288 timeslots each day for dataset has 5 minutes time interval. df = pd.DataFrame({'timestamp':pd.date_range(start=start, end=end, freq=freq)}) df['weekdaytime'] = df['timestamp'].dt.weekday * 288 + (df['timestamp'].dt.hour * 60 + df['timestamp'].dt.minute)//5 df['weekdaytime'] = df['weekdaytime'] / df['weekdaytime'].max() time_ind = df['weekdaytime'].values time_ind_node = np.tile(time_ind, [num_nodes, 1]).transpose((1, 0)) return time_ind_node def masked_mse(preds, labels, null_val=1e-3): if np.isnan(null_val): mask = ~torch.isnan(labels) else: mask = (labels > null_val) mask = mask.float() mask /= torch.mean((mask)) mask = torch.where(torch.isnan(mask), torch.zeros_like(mask), mask) loss = (preds-labels)**2 loss = loss * mask loss = torch.where(torch.isnan(loss), torch.zeros_like(loss), loss) return torch.mean(loss) def masked_rmse(preds, labels, null_val=1e-3): return torch.sqrt(masked_mse(preds=preds, labels=labels, null_val=null_val)) def masked_mae(preds, labels, null_val=1e-3): if np.isnan(null_val): mask = ~torch.isnan(labels) else: mask = (labels > null_val) mask = mask.float() mask /= torch.mean((mask)) mask = torch.where(torch.isnan(mask), torch.zeros_like(mask), mask) loss = torch.abs(preds-labels) loss = loss * mask loss = torch.where(torch.isnan(loss), torch.zeros_like(loss), loss) return torch.mean(loss) def masked_mape(preds, labels, null_val=1e-3): if np.isnan(null_val): mask = ~torch.isnan(labels) else: mask = (labels > null_val) mask = mask.float() mask /= torch.mean((mask)) mask = torch.where(torch.isnan(mask), torch.zeros_like(mask), mask) loss = torch.abs(preds-labels)/labels loss = loss * mask loss = torch.where(torch.isnan(loss), torch.zeros_like(loss), loss) return torch.mean(loss) # DCRNN def masked_mae_loss(y_pred, y_true): mask = (y_true != 0).float() mask /= mask.mean() loss = torch.abs(y_pred - y_true) loss = loss * mask # trick for nans: https://discuss.pytorch.org/t/how-to-set-nan-in-tensor-to-0/3918/3 loss[loss != loss] = 0 return loss.mean() def masked_mape_loss(y_pred, y_true): mask = (y_true != 0).float() mask /= mask.mean() loss = torch.abs(torch.div(y_true - y_pred, y_true)) loss = loss * mask # trick for nans: https://discuss.pytorch.org/t/how-to-set-nan-in-tensor-to-0/3918/3 loss[loss != loss] = 0 return loss.mean() def masked_rmse_loss(y_pred, y_true): mask = (y_true != 0).float() mask /= mask.mean() loss = torch.pow(y_true - y_pred, 2) loss = loss * mask # trick for nans: https://discuss.pytorch.org/t/how-to-set-nan-in-tensor-to-0/3918/3 loss[loss != loss] = 0 return torch.sqrt(loss.mean()) def masked_mse_loss(y_pred, y_true): mask = (y_true != 0).float() mask /= mask.mean() loss = torch.pow(y_true - y_pred, 2) loss = loss * mask # trick for nans: https://discuss.pytorch.org/t/how-to-set-nan-in-tensor-to-0/3918/3 loss[loss != loss] = 0 return loss.mean() def load_pickle(pickle_file): try: with open(pickle_file, 'rb') as f: pickle_data = pickle.load(f) except UnicodeDecodeError as e: with open(pickle_file, 'rb') as f: pickle_data = pickle.load(f, encoding='latin1') except Exception as e: print('Unable to load data ', pickle_file, ':', e) raise return pickle_data def sym_adj(adj): """Symmetrically normalize adjacency matrix.""" adj = sp.coo_matrix(adj) rowsum = np.array(adj.sum(1)) d_inv_sqrt = np.power(rowsum, -0.5).flatten() d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0. d_mat_inv_sqrt = sp.diags(d_inv_sqrt) return adj.dot(d_mat_inv_sqrt).transpose().dot(d_mat_inv_sqrt).astype(np.float32).todense() def asym_adj(adj): adj = sp.coo_matrix(adj) rowsum = np.array(adj.sum(1)).flatten() d_inv = np.power(rowsum, -1).flatten() d_inv[np.isinf(d_inv)] = 0. d_mat = sp.diags(d_inv) return d_mat.dot(adj).astype(np.float32).todense() def calculate_normalized_laplacian(adj): """ # L = D^-1/2 (D-A) D^-1/2 = I - D^-1/2 A D^-1/2 # D = diag(A 1) :param adj: :return: """ adj = sp.coo_matrix(adj) d = np.array(adj.sum(1)) d_inv_sqrt = np.power(d, -0.5).flatten() d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0. d_mat_inv_sqrt = sp.diags(d_inv_sqrt) normalized_laplacian = sp.eye(adj.shape[0]) - adj.dot(d_mat_inv_sqrt).transpose().dot(d_mat_inv_sqrt).tocoo() return normalized_laplacian def calculate_random_walk_matrix(adj_mx): adj_mx = sp.coo_matrix(adj_mx) d = np.array(adj_mx.sum(1)) d_inv = np.power(d, -1).flatten() d_inv[np.isinf(d_inv)] = 0. d_mat_inv = sp.diags(d_inv) random_walk_mx = d_mat_inv.dot(adj_mx).tocoo() return random_walk_mx def calculate_reverse_random_walk_matrix(adj_mx): return calculate_random_walk_matrix(np.transpose(adj_mx)) def calculate_scaled_laplacian(adj_mx, lambda_max=2, undirected=True): if undirected: adj_mx = np.maximum.reduce([adj_mx, adj_mx.T]) L = calculate_normalized_laplacian(adj_mx) if lambda_max is None: lambda_max, _ = linalg.eigsh(L, 1, which='LM') lambda_max = lambda_max[0] L = sp.csr_matrix(L) M, _ = L.shape I = sp.identity(M, format='csr', dtype=L.dtype) L = (2 / lambda_max * L) - I return L.astype(np.float32) def load_adj(pkl_filename, adjtype): if "PEMS0" in pkl_filename or "D7" in pkl_filename: adj_mx = load_pickle(pkl_filename) else: sensor_ids, sensor_id_to_ind, adj_mx = load_pickle(pkl_filename) if adjtype == "scalap": adj = [calculate_scaled_laplacian(adj_mx)] elif adjtype == "normlap": adj = [calculate_normalized_laplacian(adj_mx).astype(np.float32).todense()] elif adjtype == "symadj": adj = [sym_adj(adj_mx)] elif adjtype == "transition": adj = [asym_adj(adj_mx)] elif adjtype == "doubletransition": adj = [asym_adj(adj_mx), asym_adj(np.transpose(adj_mx))] elif adjtype == "identity": adj = [np.diag(np.ones(adj_mx.shape[0])).astype(np.float32)] else: error = 0 assert error, "adj type not defined" return adj def print_params(model): # print trainable params param_count = 0 print('Trainable parameter list:') for name, param in model.named_parameters(): if param.requires_grad: print(name, param.shape, param.numel()) param_count += param.numel() print(f'\n In total: {param_count} trainable parameters. \n') return