BTMF
Bayesian Temporal Matrix Factorization (BTMF)
import numpy as np
from numpy.random import normal as normrnd
from numpy.linalg import inv as inv
from numpy.linalg import solve as solve
from numpy.linalg import cholesky as cholesky_lower
from numpy.linalg import cholesky as cholesky_upper
from scipy.linalg import solve_triangular as solve_ut
from scipy.stats import wishart
from scipy.stats import invwishart
import matplotlib.pyplot as plt
%matplotlib inline
def kr_prod(a, b):
return np.einsum('ir, jr -> ijr', a, b).reshape(a.shape[0] * b.shape[0], -1)
def mvnrnd_pre(mu, Lambda):
src = normrnd(size = (mu.shape[0],))
return solve_ut(cholesky_upper(Lambda, overwrite_a = True, check_finite = False),
src, lower = False, check_finite = False, overwrite_b = True) + mu
def cov_mat(mat, mat_bar):
mat = mat - mat_bar
return mat.T @ mat
def sample_spatial_factor(tau_sparse_mat, tau_ind, W, X, tau, beta0 = 1, vargin = 0):
N, R = W.shape
W_bar = np.mean(W, axis = 0)
temp = N / (N + beta0)
var_W_hyper = inv(np.eye(R) + cov_mat(W, W_bar) + temp * beta0 * np.outer(W_bar, W_bar))
var_Lambda_hyper = wishart.rvs(df = dim1 + rank, scale = var_W_hyper)
var_mu_hyper = mvnrnd_pre(temp * W_bar, (dim1 + beta0) * var_Lambda_hyper)
if N * R ** 2 > 1e+8:
vargin = 1
if vargin == 0:
var1 = X.T
var2 = kr_prod(var1, var1)
var3 = (var2 @ tau_ind.T).reshape([R, R, N]) + var_Lambda_hyper[:, :, None]
var4 = var1 @ tau_sparse_mat.T + (var_Lambda_hyper @ var_mu_hyper)[:, None]
for i in range(N):
W[i, :] = mvnrnd_pre(solve(var3[:, :, i], var4[:, i]), var3[:, :, i])
elif vargin == 1:
for i in range(N):
pos0 = np.where(sparse_mat[i, :] != 0)
Xt = X[pos0[0], :]
var_mu = tau[i] * Xt.T @ sparse_mat[i, pos0[0]] + var_Lambda_hyper @ var_mu_hyper
var_Lambda = tau[i] * Xt.T @ Xt + var_Lambda_hyper
W[i, :] = mvnrnd_pre(solve(var_Lambda, var_mu), var_Lambda)
return W
def sample_temporal_factor(tau_sparse_mat, tau_ind, time_lags, W, X, A, Lambda_x):
T, R = X.shape
tmax = max(time_lags)
tmin = min(time_lags)
d = time_lags.shape[0]
A0 = np.dstack([A] * d)
for k in range(d):
A0[k * R : (k + 1) * R, :, k] = 0
mat0 = Lambda_x @ A.T
mat1 = np.einsum('kij, jt -> kit', A.reshape([d, R, R]), Lambda_x)
mat2 = np.einsum('kit, kjt -> ij', mat1, A.reshape([d, R, R]))
var1 = W.T
var2 = kr_prod(var1, var1)
var3 = (var2 @ tau_ind).reshape([R, R, T]) + Lambda_x[:, :, None]
var4 = var1 @ tau_sparse_mat
for t in range(T):
Mt = np.zeros((R, R))
Nt = np.zeros(R)
Qt = mat0 @ X[t - time_lags, :].reshape(R * d)
index = list(range(0, d))
if t >= T - tmax and t < T - tmin:
index = list(np.where(t + time_lags < T))[0]
elif t < tmax:
Qt = np.zeros(R)
index = list(np.where(t + time_lags >= tmax))[0]
if t < T - tmin:
Mt = mat2.copy()
temp = np.zeros((R * d, len(index)))
n = 0
for k in index:
temp[:, n] = X[t + time_lags[k] - time_lags, :].reshape(R * d)
n += 1
temp0 = X[t + time_lags[index], :].T - np.einsum('ijk, ik -> jk', A0[:, :, index], temp)
Nt = np.einsum('kij, jk -> i', mat1[index, :, :], temp0)
var3[:, :, t] = var3[:, :, t] + Mt
if t < tmax:
var3[:, :, t] = var3[:, :, t] - Lambda_x + np.eye(R)
X[t, :] = mvnrnd_pre(solve(var3[:, :, t], var4[:, t] + Nt + Qt), var3[:, :, t])
return X
def mnrnd(M, U, V):
dim1, dim2 = M.shape
X0 = np.random.randn(dim1, dim2)
P = cholesky_lower(U)
Q = cholesky_lower(V)
return M + P @ X0 @ Q.T
def sample_var_coefficient(X, time_lags):
T, R = X.shape
d = time_lags.shape[0]
tmax = max(time_lags)
Z_mat = X[tmax : T, :]
Q_mat = np.zeros((T - tmax, R * d))
for k in range(d):
Q_mat[:, k * R : (k + 1) * R] = X[tmax - time_lags[k] : T - time_lags[k], :]
var_Psi0 = np.eye(R * d) + Q_mat.T @ Q_mat
var_Psi = inv(var_Psi0)
var_M = var_Psi @ Q_mat.T @ Z_mat
var_S = np.eye(R) + Z_mat.T @ Z_mat - var_M.T @ var_Psi0 @ var_M
Sigma = invwishart.rvs(df = R + T - tmax, scale = var_S)
return mnrnd(var_M, var_Psi, Sigma), Sigma
def sample_precision_tau(sparse_mat, mat_hat, ind):
var_alpha = 1e-6 + 0.5 * np.sum(ind, axis = 1)
var_beta = 1e-6 + 0.5 * np.sum(((sparse_mat - mat_hat) ** 2) * ind, axis = 1)
return np.random.gamma(var_alpha, 1 / var_beta)
def sample_precision_scalar_tau(sparse_mat, mat_hat, ind):
var_alpha = 1e-6 + 0.5 * np.sum(ind)
var_beta = 1e-6 + 0.5 * np.sum(((sparse_mat - mat_hat) ** 2) * ind)
return np.random.gamma(var_alpha, 1 / var_beta)
def compute_mape(var, var_hat):
return np.sum(np.abs(var - var_hat) / var) / var.shape[0]
def compute_rmse(var, var_hat):
return np.sqrt(np.sum((var - var_hat) ** 2) / var.shape[0])
By using these functions, we have a Bayesian temporal matrix factorization algorithm.
def BTMF(dense_mat, sparse_mat, init, R, time_lags, burn_iter, gibbs_iter, option = "factor"):
"""Bayesian Temporal Matrix Factorization, BTMF."""
N, T = sparse_mat.shape
d = time_lags.shape[0]
W = init["W"]
X = init["X"]
if np.isnan(sparse_mat).any() == False:
ind = sparse_mat != 0
pos_obs = np.where(ind)
pos_test = np.where((dense_mat != 0) & (sparse_mat == 0))
elif np.isnan(sparse_mat).any() == True:
pos_test = np.where((dense_mat != 0) & (np.isnan(sparse_mat)))
ind = ~np.isnan(sparse_mat)
pos_obs = np.where(ind)
sparse_mat[np.isnan(sparse_mat)] = 0
dense_test = dense_mat[pos_test]
del dense_mat
tau = np.ones(N)
W_plus = np.zeros((N, R))
X_plus = np.zeros((T, R))
A_plus = np.zeros((R * d, R))
temp_hat = np.zeros(len(pos_test[0]))
show_iter = 200
mat_hat_plus = np.zeros((N, T))
for it in range(burn_iter + gibbs_iter):
tau_ind = tau[:, None] * ind
tau_sparse_mat = tau[:, None] * sparse_mat
W = sample_spatial_factor(tau_sparse_mat, tau_ind, W, X, tau)
A, Sigma = sample_var_coefficient(X, time_lags)
X = sample_temporal_factor(tau_sparse_mat, tau_ind, time_lags, W, X, A, inv(Sigma))
mat_hat = W @ X.T
if option == "factor":
tau = sample_precision_tau(sparse_mat, mat_hat, ind)
elif option == "pca":
tau = sample_precision_scalar_tau(sparse_mat, mat_hat, ind)
tau = tau * np.ones(N)
temp_hat += mat_hat[pos_test]
if (it + 1) % show_iter == 0 and it < burn_iter:
temp_hat = temp_hat / show_iter
print('Iter: {}'.format(it + 1))
print('MAPE: {:.6}'.format(compute_mape(dense_test, temp_hat)))
print('RMSE: {:.6}'.format(compute_rmse(dense_test, temp_hat)))
temp_hat = np.zeros(len(pos_test[0]))
print()
if it + 1 > burn_iter:
W_plus += W
X_plus += X
A_plus += A
mat_hat_plus += mat_hat
mat_hat = mat_hat_plus / gibbs_iter
W = W_plus / gibbs_iter
X = X_plus / gibbs_iter
A = A_plus / gibbs_iter
print('Imputation MAPE: {:.6}'.format(compute_mape(dense_test, mat_hat[:, : dim2][pos_test])))
print('Imputation RMSE: {:.6}'.format(compute_rmse(dense_test, mat_hat[:, : dim2][pos_test])))
print()
mat_hat[mat_hat < 0] = 0
return mat_hat, W, X, A
In Numpy, the .npz
file format is appropriate for compressing the numpy
file. How to save one single numpy
array to a compressed file?
import numpy as np
# save to .npz file
np.savez_compressed('data.npz', data)
One can also load a .npz
file in numpy
:
# load dic of arrays
dict_data = np.load('data.npz')
# extract the first array
data = dict_data['arr_0']