|
|
|
|
| import numpy as np
|
| import torch
|
| import os
|
|
|
|
|
| def concat_fun(inputs, axis=-1):
|
| if len(inputs) == 1:
|
| return inputs[0]
|
| else:
|
| return torch.cat(inputs, dim=axis)
|
|
|
|
|
| def slice_arrays(arrays, start=None, stop=None):
|
| """Slice an array or list of arrays.
|
|
|
| This takes an array-like, or a list of
|
| array-likes, and outputs:
|
| - arrays[start:stop] if `arrays` is an array-like
|
| - [x[start:stop] for x in arrays] if `arrays` is a list
|
|
|
| Can also work on list/array of indices: `slice_arrays(x, indices)`
|
|
|
| Arguments:
|
| arrays: Single array or list of arrays.
|
| start: can be an integer index (start index)
|
| or a list/array of indices
|
| stop: integer (stop index); should be None if
|
| `start` was a list.
|
|
|
| Returns:
|
| A slice of the array(s).
|
|
|
| Raises:
|
| ValueError: If the value of start is a list and stop is not None.
|
| """
|
|
|
| if arrays is None:
|
| return [None]
|
|
|
| if isinstance(arrays, np.ndarray):
|
| arrays = [arrays]
|
|
|
| if isinstance(start, list) and stop is not None:
|
| raise ValueError('The stop argument has to be None if the value of start '
|
| 'is a list.')
|
| elif isinstance(arrays, list):
|
| if hasattr(start, '__len__'):
|
|
|
| if hasattr(start, 'shape'):
|
| start = start.tolist()
|
| return [None if x is None else x[start] for x in arrays]
|
| else:
|
| if len(arrays) == 1:
|
| return arrays[0][start:stop]
|
| return [None if x is None else x[start:stop] for x in arrays]
|
| else:
|
| if hasattr(start, '__len__'):
|
| if hasattr(start, 'shape'):
|
| start = start.tolist()
|
| return arrays[start]
|
| elif hasattr(start, '__getitem__'):
|
| return arrays[start:stop]
|
| else:
|
| return [None]
|
|
|
|
|
| def save_model(model, model_dir, epoch=None):
|
| if model_dir is None:
|
| return
|
| if not os.path.exists(model_dir):
|
| os.makedirs(model_dir)
|
| epoch = str(epoch) if epoch else ''
|
| file_name = os.path.join(model_dir, epoch + '_dhfm.pt')
|
| with open(file_name, 'wb') as f:
|
| torch.save(model, f)
|
|
|
|
|
| def load_model(model_dir, epoch=None):
|
| if not model_dir:
|
| return
|
| epoch = str(epoch) if epoch else ''
|
| file_name = os.path.join(model_dir, epoch + '_dhfm.pt')
|
| if not os.path.exists(model_dir):
|
| os.makedirs(model_dir)
|
| if not os.path.exists(file_name):
|
| return
|
| with open(file_name, 'rb') as f:
|
| model = torch.load(f)
|
| return model
|
|
|
| def masked_MAPE(v, v_, axis=None):
|
| '''
|
| Mean absolute percentage error.
|
| :param v: np.ndarray or int, ground truth.
|
| :param v_: np.ndarray or int, prediction.
|
| :param axis: axis to do calculation.
|
| :return: int, MAPE averages on all elements of input.
|
| '''
|
| mask = (v == 0)
|
| percentage = np.abs(v_ - v) / np.abs(v)
|
| if np.any(mask):
|
| masked_array = np.ma.masked_array(percentage, mask=mask)
|
| result = masked_array.mean(axis=axis)
|
| if isinstance(result, np.ma.MaskedArray):
|
| return result.filled(np.nan)
|
| else:
|
| return result
|
| return np.mean(percentage, axis).astype(np.float64)
|
|
|
| """
|
| original
|
| def MAPE(v, v_, axis=None):
|
| '''
|
| Mean absolute percentage error.
|
| :param v: np.ndarray or int, ground truth.
|
| :param v_: np.ndarray or int, prediction.
|
| :param axis: axis to do calculation.
|
| :return: int, MAPE averages on all elements of input.
|
| '''
|
| mape = (np.abs(v_ - v) / np.abs(v)+1e-5).astype(np.float64)
|
| mape = np.where(mape > 5, 5, mape)
|
| return np.mean(mape, axis)
|
|
|
| """
|
|
|
| def MAPE(v, v_, axis=None):
|
| '''
|
| Mean absolute percentage error.
|
| :param v: np.ndarray or int, ground truth.
|
| :param v_: np.ndarray or int, prediction.
|
| :param axis: axis to do calculation.
|
| :return: float, MAPE averages on all elements of input.
|
| '''
|
| mape = (np.abs(v_ - v) / (np.abs(v) + 1e-5)).astype(np.float64)
|
| mape = np.where(mape > 5, 5, mape)
|
| return np.mean(mape, axis)
|
|
|
|
|
|
|
|
|
|
|
| def smape(P, A):
|
| nz = np.where(A > 0)
|
| Pz = P[nz]
|
| Az = A[nz]
|
|
|
| return np.mean(2 * np.abs(Az - Pz) / (np.abs(Az) + np.abs(Pz)))
|
|
|
|
|
| def R2(y, y_hat, axis=None, eps=1e-12):
|
| """
|
| R^2 score for arrays shaped like [count, time_step, node] (or compatible).
|
| axis=None -> global scalar R2 over all elements.
|
| axis can be int or tuple of ints: reduce over those axes, keeping the others.
|
| """
|
| y = np.asarray(y, dtype=np.float64)
|
| y_hat = np.asarray(y_hat, dtype=np.float64)
|
|
|
|
|
| ss_res = np.sum((y - y_hat) ** 2, axis=axis)
|
|
|
|
|
| y_mean = np.mean(y, axis=axis, keepdims=True)
|
| ss_tot = np.sum((y - y_mean) ** 2, axis=axis)
|
|
|
|
|
| denom = ss_tot + eps
|
| r2 = 1.0 - (ss_res / denom)
|
|
|
|
|
|
|
| if np.isscalar(ss_tot):
|
| if ss_tot < eps:
|
| return np.nan
|
| return float(r2)
|
|
|
| r2 = np.where(ss_tot < eps, np.nan, r2)
|
| return r2.astype(np.float64)
|
|
|
| def RSE(v, v_, axis=None, eps=1e-12):
|
| '''
|
| Relative squared error (rooted):
|
| sqrt( sum((v_ - v)^2) / sum((v - mean(v))^2) )
|
| :param v: np.ndarray or int, ground truth.
|
| :param v_: np.ndarray or int, prediction.
|
| :param axis: axis to do calculation.
|
| :return: float, RSE on all elements of input (or reduced by axis).
|
| '''
|
| v = np.asarray(v, dtype=np.float64)
|
| v_ = np.asarray(v_, dtype=np.float64)
|
|
|
| v_mean = np.mean(v, axis=axis, keepdims=True)
|
| num = np.sum((v_ - v) ** 2, axis=axis)
|
| denom = np.sum((v - v_mean) ** 2, axis=axis)
|
| return np.sqrt(num / (denom + eps)).astype(np.float64)
|
|
|
| def RMSE(v, v_, axis=None):
|
| '''
|
| Mean squared error.
|
| :param v: np.ndarray or int, ground truth.
|
| :param v_: np.ndarray or int, prediction.
|
| :param axis: axis to do calculation.
|
| :return: int, RMSE averages on all elements of input.
|
| '''
|
| return np.sqrt(np.mean((v_ - v) ** 2, axis)).astype(np.float64)
|
|
|
|
|
| def MAE(v, v_, axis=None):
|
| '''
|
| Mean absolute error.
|
| :param v: np.ndarray or int, ground truth.
|
| :param v_: np.ndarray or int, prediction.
|
| :param axis: axis to do calculation.
|
| :return: int, MAE averages on all elements of input.
|
| '''
|
| return np.mean(np.abs(v_ - v), axis).astype(np.float64)
|
|
|
|
|
| def evaluate(y, y_hat, by_step=False, by_node=False):
|
| '''
|
| :param y: array in shape of [count, time_step, node].
|
| :param y_hat: in same shape with y.
|
| :param by_step: evaluate by time_step dim.
|
| :param by_node: evaluate by node dim.
|
| :return: array of mape, mae and rmse.
|
| '''
|
| if not by_step and not by_node:
|
| return MAPE(y, y_hat), MAE(y, y_hat), RMSE(y, y_hat), R2(y, y_hat), RSE(y, y_hat)
|
| if by_step and by_node:
|
| return MAPE(y, y_hat, axis=0), MAE(y, y_hat, axis=0), RMSE(y, y_hat, axis=0), R2(y, y_hat, axis=0)
|
| if by_step:
|
| return MAPE(y, y_hat, axis=(0, 2)), MAE(y, y_hat, axis=(0, 2)), RMSE(y, y_hat, axis=(0, 2)), R2(y, y_hat, axis=(0, 2))
|
| if by_node:
|
| return MAPE(y, y_hat, axis=(0, 1)), MAE(y, y_hat, axis=(0, 1)), RMSE(y, y_hat, axis=(0, 1)), R2(y, y_hat, axis=(0, 1))
|
|
|
|
|
| def save_model_ts(model, path, epoch):
|
| if not os.path.exists(path):
|
| os.makedirs(path)
|
| filename = 'epoch_{}.pth'.format(epoch)
|
| f = os.path.join(path, filename)
|
|
|
| torch.save(model.state_dict(), f)
|
|
|
| def load_model_ts(model, path, epoch):
|
| """Load state dict into an existing model instance"""
|
| filename = 'epoch_{}.pth'.format(epoch)
|
| f = os.path.join(path, filename)
|
| model.load_state_dict(torch.load(f))
|
| return model
|
|
|