SpikF-GO / utils /utils.py
bakhshaliyev's picture
Upload folder using huggingface_hub
d88ab6d verified
Raw
History Blame Contribute Delete
8.39 kB
# -*- coding:utf-8 -*-
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__'):
# hdf5 datasets only support list objects as indices
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) # mask the dividing-zero as invalid
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) # clip extreme values
return np.mean(mape, axis)
#def MAPE(true, pred):
# return np.mean(np.abs((pred - true) / (true+1e-5)))
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)
# residual sum of squares
ss_res = np.sum((y - y_hat) ** 2, axis=axis)
# total sum of squares around mean of y along the same reduction axis
y_mean = np.mean(y, axis=axis, keepdims=True)
ss_tot = np.sum((y - y_mean) ** 2, axis=axis)
# Avoid division by zero (constant targets)
denom = ss_tot + eps
r2 = 1.0 - (ss_res / denom)
# If ss_tot is truly ~0, R2 is not well-defined; mark as nan
# (Optional) If you want 0.0 instead, replace np.nan with 0.0
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)
# Save state_dict instead of the entire model
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