171 lines
6.6 KiB
R
171 lines
6.6 KiB
R
source('../tensor_predictors/matpow.R')
|
|
source('../tensor_predictors/multi_assign.R')
|
|
source('../tensor_predictors/approx_kronecker.R')
|
|
|
|
log.likelihood <- function(par, X, Fy, Delta.inv, da, db) {
|
|
alpha <- matrix(par[1:prod(da)], da[1L])
|
|
beta <- matrix(par[(prod(da) + 1):length(par)], db[1L])
|
|
error <- X - tcrossprod(Fy, kronecker(alpha, beta))
|
|
sum(error * (error %*% Delta.inv))
|
|
}
|
|
|
|
tensor_predictor <- function(X, Fy, p, t, k = 1L, r = 1L, d1 = 1L, d2 = 1L,
|
|
method = "KPIR_LS",
|
|
eps1 = 1e-2, eps2 = 1e-2, maxit = 10L) {
|
|
# Validate method using unexact matching.
|
|
methods <- list(KPIR_LS = "KPIR_LS", KPIR_MLE = "KPIR_MLE",
|
|
KPFC1 = "KPFC1", KPFC2 = "KPFC2", KPFC3 = "KPFC3")
|
|
method <- methods[[toupper(method), exact = FALSE]]
|
|
if (is.null(method)) {
|
|
stop("Unable to determine method.")
|
|
}
|
|
|
|
if (method %in% c("KPIR_LS", "KPIR_MLE")) {
|
|
## Step 1:
|
|
# OLS estimate of the model `X = F_y B + epsilon`.
|
|
B <- t(solve(crossprod(Fy), crossprod(Fy, X)))
|
|
|
|
# Estimate alpha, beta as nearest kronecker approximation.
|
|
c(alpha, beta) %<-% approx.kronecker(B, c(t, r), c(p, k))
|
|
|
|
if (method == "KPIR_LS") {
|
|
# Estimate Delta.
|
|
B <- kronecker(alpha, beta)
|
|
rank <- if (ncol(Fy) == 1) 1L else qr(Fy)$rank
|
|
Delta <- crossprod(X - tcrossprod(Fy, B)) / (nrow(X) - rank)
|
|
|
|
} else { # KPIR_MLE
|
|
# Estimate initial Delta.
|
|
B <- kronecker(alpha, beta)
|
|
Delta <- crossprod(X - tcrossprod(Fy, B)) / nrow(X)
|
|
|
|
for (. in 1:maxit) {
|
|
# Optimize log-likelihood for alpha, beta with fixed Delta.
|
|
opt <- optim(c(alpha, beta), log.likelihood, gr = NULL,
|
|
X, Fy, matpow(Delta, -1), c(t, r), c(p, k))
|
|
# Store previous alpha, beta and Delta (for break consition).
|
|
Delta.last <- Delta
|
|
B.last <- B
|
|
# Extract optimized alpha, beta.
|
|
alpha <- matrix(opt$par[1:(t * r)], t, r)
|
|
beta <- matrix(opt$par[(t * r + 1):length(opt$par)], p, k)
|
|
# Calc new Delta with likelihood optimized alpha, beta.
|
|
B <- kronecker(alpha, beta)
|
|
Delta <- crossprod(X - tcrossprod(Fy, B)) / nrow(X)
|
|
# Check break condition 1.
|
|
if (norm(Delta - Delta.last, 'F') < eps1 * norm(Delta, 'F')) {
|
|
# Check break condition 2.
|
|
if (norm(B - B.last, 'F') < eps2 * norm(B, 'F')) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Construct basis from alpha and beta.
|
|
Gamma_1 <- if(d1 > 1L) La.svd(alpha, d1, 0L)$u
|
|
else alpha / norm(alpha, 'F')
|
|
Gamma_2 <- if(d2 > 1L) La.svd(beta, d2, 0L)$u
|
|
else beta / norm(beta, 'F')
|
|
Gamma <- kronecker(Gamma_1, Gamma_2)
|
|
} else if (method %in% c("KPFC1", "KPFC2", "KPFC3")) {
|
|
## Step 1:
|
|
# OLS extimate of the model `X = F_y B + epsilon`.
|
|
B <- t(solve(crossprod(Fy), crossprod(Fy, X)))
|
|
|
|
## Step 2:
|
|
# Estimate Delta_mle.
|
|
P_Fy <- Fy %*% solve(crossprod(Fy), t(Fy))
|
|
Q_Fy <- diag(nrow(P_Fy)) - P_Fy
|
|
Delta_fit <- crossprod(X, P_Fy %*% X) / nrow(X)
|
|
Delta_res <- crossprod(X, Q_Fy %*% X) / nrow(X)
|
|
# Compute Delta_mle using equation (7).
|
|
D <- matpow(Delta_res, -0.5)
|
|
Delta <- with(La.svd(D %*% Delta_fit %*% D), {
|
|
K <- diag(c(rep(0, d1 * d2), d[-(1:(d1 * d2))]))
|
|
D <- matpow(Delta_res, 0.5)
|
|
Delta_res + (D %*% u %*% tcrossprod(K, u) %*% D)
|
|
})
|
|
|
|
## Step 3:
|
|
# Set Gamma to be the first `d = d1 * d2` eigenvectors of (25).
|
|
D <- matpow(Delta, -0.5)
|
|
Gamma <- with(La.svd(D %*% Delta_fit %*% D, d1 * d2, 0L), {
|
|
La.svd(matpow(Delta, 0.5) %*% u[, 1:(d1 * d2)])$u
|
|
})
|
|
|
|
if (method == "KPFC1") {
|
|
# Compute lower_gamma using (26).
|
|
D <- crossprod(Gamma, matpow(Delta, -1))
|
|
lower_gamma <- solve(D %*% Gamma, D %*% B)
|
|
|
|
## Step 4a:
|
|
# Calc MLE estimate of B.
|
|
B <- Gamma %*% lower_gamma
|
|
# Using the VLP approx. for a kronecker product factorization.
|
|
c(alpha, beta) %<-% approx.kronecker(B, c(t, r), c(p, k))
|
|
|
|
# Construct basis from alpha and beta.
|
|
Gamma_1 <- if(d1 > 1L) La.svd(alpha, d1, 0L)$u
|
|
else alpha / norm(alpha, 'F')
|
|
Gamma_2 <- if(d2 > 1L) La.svd(beta, d2, 0L)$u
|
|
else beta / norm(beta, 'F')
|
|
Gamma <- kronecker(Gamma_1, Gamma_2)
|
|
|
|
} else { # KPFC2, KPFC3
|
|
## Step 4b:
|
|
# Estimate Gamma's as nearest kronecker approximation of Gamma.
|
|
c(Gamma_1, Gamma_2) %<-% approx.kronecker(Gamma, c(t, d1), c(p, d2))
|
|
Gamma <- kronecker(Gamma_1, Gamma_2)
|
|
# Compute lower_gamma using (26).
|
|
D <- crossprod(Gamma, matpow(Delta, -1))
|
|
lower_gamma <- solve(D %*% Gamma, D %*% B)
|
|
|
|
if (prod(dim(lower_gamma)) == 1) {
|
|
# If lower_gamma is a scalar, then alpha, beta is only scaled.
|
|
# (shortcut)
|
|
lg1 <- lg2 <- sqrt(abs(as.vector(lower_gamma)))
|
|
alpha <- lg1 * Gamma_1
|
|
beta <- lg2 * Gamma_2
|
|
} else if (method == "KPFC2") {
|
|
## Step 5c:
|
|
c(alpha, beta) %<-% approx.kronecker(Gamma %*% lower_gamma,
|
|
c(t, r), c(p, k))
|
|
} else { # KPFC3
|
|
## Step 5d:
|
|
c(lg1, lg2) %<-% approx.kronecker(lower_gamma,
|
|
c(d1, r), c(d2, k))
|
|
alpha <- Gamma_1 %*% lg1
|
|
beta <- Gamma_2 %*% lg2
|
|
}
|
|
}
|
|
}
|
|
|
|
return(structure(
|
|
list(alpha = alpha,
|
|
beta = beta,
|
|
Gamma = Gamma,
|
|
Gamma_1 = Gamma_1, Gamma_2 = Gamma_2,
|
|
Delta = Delta),
|
|
class = c("tensor_predictor", method)
|
|
))
|
|
}
|
|
|
|
#' TODO: Write this properly!
|
|
reduce <- function(object, data, use = 'Gamma') {
|
|
if (use == 'Gamma') {
|
|
projection <- object$Gamma
|
|
} else if (use == 'alpha_beta') {
|
|
projection <- kronecker(object$alpha, object$beta)
|
|
} else {
|
|
stop("Unkown 'use' parameter value.")
|
|
}
|
|
|
|
# ensure alignement of multiple calls.
|
|
if (projection[1] < 0) {
|
|
projection <- -projection
|
|
}
|
|
|
|
return(data %*% projection)
|
|
}
|