| 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): |
| |
| 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): |
| |
| 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) |
|
|
| |
| 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 |
| |
| 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 |
| |
| 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 |
| |
| 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 |
| |
| 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): |
| |
| 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 |