#!/usr/bin/env python
# coding: utf-8
# ## Online Low-Rank Tensor Learning (Online-LRTL)
#
# This notebook shows how to implement the following paper:
#
#
#
# Rose Yu, Dehua Cheng, Yan Liu (2015). Accelerated Online Low-Rank Tensor Learning for Multivariate Spatio-Temporal Streams. ICML 2015. [PDF]
#
#
#
# The authors provide Matlab code, if you have any interest, please download at [http://roseyu.com/Materials/accelerate_online_low_rank_tensor.zip](http://roseyu.com/Materials/accelerate_online_low_rank_tensor.zip).
# ### Necessarity
# In[ ]:
import numpy as np
def ten2mat(tensor, mode):
return np.reshape(np.moveaxis(tensor, mode, 0), (tensor.shape[mode], -1), order = 'F')
def mat2ten(mat, dim, mode):
index = list()
index.append(mode)
for i in range(dim.shape[0]):
if i != mode:
index.append(i)
return np.moveaxis(np.reshape(mat, list(dim[index]), order = 'F'), 0, mode)
# ### Kernel
# **1) How to initialize paramters?**
# In[ ]:
def mapTensorKInit(W, nRank):
dim = W.shape
U = []
for i in range(len(dim)):
u, _, _ = np.linalg.svds(ten2mat(W, i), min(nRank, dim[i]))
U.append(u)
if nRank ~= dim[0]:
W = np.einsum('pqm, pr, qs, mt -> rst', W, U[0], U[1], U[2])
W = np.einsum('rst, pr, qs, mt -> pqm', W, U[0], U[1], U[2])
return W, U
def solve_init(X, Y, nRank):
"""Get the initial estimation from the first batch."""
P, _ = Y[0].shape
Q, M = X[0].shape
nSample = len(Y)
W_mat = []
W_mat.append(np.zeros((P, Q * M)))
W_mat.append(np.zeros((Q, P * M)))
W_mat.append(np.zeros((M, P * Q)))
Sigma_X = np.zeros((Q, Q, M))
Sigma_Y = np.zeros((P, Q, M))
for m in range(M):
X_m = np.zeros((Q, nSample))
Y_m = np.zeros((P, nSample))
for n in range(nSample):
X_m[:, n] = X[n][:, m]
Y_m[:, n] = Y[n][:, m]
S_X = np.linalg.inv(X_m @ X_m.T + 1e-5 * np.eye(Q)) # Q x Q
S_Y = Y_m @ X_m.T # P x Q
W_est = S_Y @ S_X # P x Q
W_mat[0][:, (m - 1) * Q : m * Q] = W_est # P x MQ
W_mat[1][:, (m - 1) * P : m * P] = W_est.T # Q x MP
W_mat[2][m, :] = W_est.T.reshape(P * Q) # M x PQ
Sigma_X[:, :, m] = S_X
Sigma_Y[:, :, m] = S_Y
W = mat2ten(W_mat[0], np.array([P, Q, M]), 0) # P x Q x M
W, U = mapTensorKInit(W, nRank, [1, 2, 3])
W = W.data
D_L = U
D_R = 0
return W, Sigma_X, Sigma_Y, D_L, D_R
# **2) How to update parameters?**
# In[ ]:
def mapTensorKGreedy(W, nRank):
dim = W.shape
U = []
W0 = np.zeros(dim)
for i in range(len(dim)):
u, s, v = np.linalg.svds(ten2mat(T, i), min(nRank, dim[i]))
U.append(u)
W0 += mat2ten(u @ np.diag(s) @ v.T, np.array(dim), i)
W = W0 / 3
return W, U
def ALTO(W, U):
D_L = []
dim = W.shape
nRank = U[0].shape[1]
for i in range(len(dim)):
tmp = np.random.randn(U[i].shape)
tmp = tmp - U[i] @ U[i].T @ tmp
tmp = tmp / np.linalg.norm(tmp)
D_L.append(np.append(U[i], tmp.reshape([dim[i], 1]), axis = 1))
T = np.einsum('pqm, pr, qs, mt -> rst', W, D_L[0], D_L[1], D_L[2]) # R+1 x R+1 x R+1
T, U = mapTensorKGreedy(T, nRank)
W_out = np.einsum('rst, pr, qs, mt -> pqm', T, D_L[0], D_L[1], D_L[2])
for i in range(len(dim)):
D_L[i] = D_L[i] @ U[i]
return W_out, D_L
def solve_update(W, X, Y, Sigma_X, Sigma_Y, D_L, D_R, nRank, nIter):
nSample = len(Y)
Q, _ = X[0].shape
P, M = Y[0].shape
Sigma_X_out = np.zeros((Q, Q, M))
Sigma_Y_out = np.zeros((P, Q, M))
Delta = np.zeros((P, Q, M))
for m in range(M): # solve for each variable seperately
X_m = np.zeros((Q, nSample))
Y_m = np.zeros((P, nSample))
for n in range(nSample):
X_m[:, n] = X[n][:, m]
Y_m[:, n] = Y[n][:, m]
# update Sigma_X and Sigma_Y
B = Sigma_X[:, :, m] @ X_m # Q x nSample
E = B @ np.linalg.lstsq(np.eye(nSample) + X_m.T @ B, B.T)[0]
Delta[:, :, m] = 1 / nIter @ (Y_m @ B.T - Simga_Y[:, :, m] @ E
- Y_m @ X_m.T @ E + Sigma_Y[:, :, m] @ Sigma_X[:, :, m])
Sigma_X_out[:, :, m] = Sigma_X[:, :, m] - E
Sigma_Y_out[:, :, m] = Sigma_Y[:, :, m] + Y_m @ X_m.T
W_out, D_L = ALTO(nIter * Delta, D_L)
return W_out, Sigma_X_out, Sigma_Y_out, D_L, D_R
# **3) How to build the kernel online low-rank tensor learning?**
# In[ ]:
def online_tensor_learning(X, Y, At, nInit, batch_size, nRank):
"""
Main routine for Online Low-Rank Tensor Learning (Online_LRTL)
Refer to "Accelerated Online Low-Rank Tensor Learning for Multivariate Spatio-Temporal Streams"
Rose Yu, Dehua Cheng, Yan Liu (ICML 2015)
Input:
- X: predictor, list of length 1 x T, each of size Q x M (array)
- Y: response, list of length 1 x T, each of size P x M (array)
- At:
- if matrix, Laplacian matrix
- if tensor: ground truth tensor, for synthetic evaluation
- nInit: size of the initial batch
- batch_size: size of mini-batch
- nRank: upper bound of the rank
Output:
- W: model tensor: P x Q x M
- obj: objective function value
"""
nSample = len(X)
nBatch = int((nSample - nInit) / batch_size)
objs = np.zeros(nBatch)
W, Sigma_X, Sigma_Y, D_L, D_R = solve_init(X[:nInit], Y[:nInit], nRank)
for nIter in range(nBatch):
if nIter == nBatch:
X_new = X[nInit + (nBatch - 1) * batch_size - 1 :]
Y_new = Y[nInit + (nBatch - 1) * batch_size - 1 :]
else:
X_new = X[nInit + (nIter - 1) * batch_size - 1 : nIter * batch_size - 1]
Y_new = Y[nInit + (nIter - 1) * batch_size - 1 : nIter * batch_size - 1]
W, Sigma_X, Sigma_Y, D_L, D_R = solve_update(W, X_new, Y_new, Sigma_X, Sigma_Y, D_L, D_R, nRank, nIter)
if len(At.shape) == 3:
objs[nIter] = np.linalg.norm(W - At) / np.linalg.norm(At)
else:
X_test = X[nInit + nIter * batch_size - 1 :]
Y_test = Y[nInit + nIter * batch_size - 1 :]
tmp = 0
for s in range(nSample):
for m in range(M):
tmp += np.linalg.norm(Y_test[s][:, m] - W[:, :, m] @ X_test[s][:, m]) ** 2
objs[nIter] = np.sqrt(tmp / (nSample * P * M))
print('Batch: {}'.format(nIter))
print('Objective function: {:.6}'.format(objs[nIter]))
print()
return W, objs
# ### Example
# I have no idea for providing simple example currently, but I will try my best to give some!