This vignette fits a skew-normal
location model to body-mass index (BMI) from the Australian Institute of
Sport (ais) dataset. We use the
adlaplaceExample backend for the observation density and
adlaplace for Laplace approximation, inner optimization,
and outer profiling.
The sn package provides the original ais
data; if it is not installed we build a small stand-in so the vignette
still knits.
if (requireNamespace("sn", quietly = TRUE)) {
data("ais", package = "sn")
hist(ais$BMI)
} else {
ais <- data.frame(
sex = rep(c("male", "female"), 5),
sport = rep(1:5, each = 2),
BMI = rnorm(10)
)
}We specify a skew-normal response for BMI with an
intercept, fixed effect of sex, and an independent random
effect for sport. Observation shards are split across 100
groups for parallel derivative evaluation.
model_stuff <- adlaplace::model_data(
adlaplaceExample::skewnormal(BMI) ~
adlaplace::intercept(init = 10, parscale = 10) +
sex + adlaplace::iid(sport),
data = ais,
verbose = TRUE
)## Model term adlaplaceExample::sk... parsed from formula
## Model term adlaplace::intercept... parsed from formula
## Model term sex... parsed from formula
## Model term adlaplace::iid(sport... parsed from formula
## variables:
## [1] "BMI" "sex" "sport"
## data has 202 rows
## Model term adlaplaceExample::sk... parsed from formula
## Model term adlaplace::intercept... parsed from formula
## Model term sex... parsed from formula
## Model term adlaplace::iid(sport... parsed from formula
ad_fun() composes the observation, parameter, and
random-effect shards into a single callable object.
log_lik_laplace() solves the inner problem for random
effects and returns the profile log-likelihood, gradient, and fitted
parameters at the initial outer vector.
ad_fun <- adlaplace::ad_fun(
model_stuff,
config = config,
num_threads = config$num_threads
)
res <- adlaplace::log_lik_laplace(
x = model_stuff$data$info$parameters$init,
ad_fun = ad_fun,
config = config,
deriv = TRUE
)## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 1688878.228723 85870.385201 Continuing - TR expand 2.000000 1 Intersect TR bound
## 2 1527402.149188 75622.182521 Continuing - TR expand 4.000000 1 Intersect TR bound
## 3 1265708.921210 55313.902431 Continuing - TR expand 8.000000 1 Intersect TR bound
## 4 983541.561603 16433.724581 Continuing - TR expand 16.000000 1 Intersect TR bound
## 5 953188.216184 0.338769 Continuing 16.000000 6 Reached tolerance
## 6 953188.216171 0.000006 Continuing 16.000000 6 Reached tolerance
## 7 953188.216171 0.000000 Continuing 16.000000 6 Reached tolerance
##
## Iteration has terminated
## 7 953188.216171 0.000000 Success
## [1] 10.000000 0.000000 -2.302585 0.100000 -3.912023
## [1] 953221
## [1] -1.370136e+05 -7.840007e+04 -1.067354e+06 -1.143206e+01 -8.397178e+05
The outer objective profiles out random effects via a cache. We evaluate the objective and gradient once at the start, then run a short L-BFGS-B fit.
cache <- new.env(parent = emptyenv())
cache$gamma <- res$opt$solution
x0 <- model_stuff$data$info$parameters$init
adlaplace::outer_fn(x = x0, cache = cache, config = config, ad_fun = ad_fun)## [1] 953221
## [1] -1.370136e+05 -7.840007e+04 -1.067354e+06 -1.143206e+01 -8.397178e+05
outer_fit <- stats::optim(
par = x0,
fn = adlaplace::outer_fn,
gr = adlaplace::outer_gr,
lower = model_stuff$data$info$parameters$lower,
upper = model_stuff$data$info$parameters$upper,
method = "L-BFGS-B",
control = list(
maxit = 10,
parscale = model_stuff$data$info$parameters$parscale,
trace = 3,
REPORT = 1
),
config = config,
ad_fun = ad_fun,
cache = cache
)## N = 5, M = 5 machine precision = 2.22045e-16
## At X0, 0 variables are exactly at the bounds
## At iterate 0 f= 9.5322e+05 |proj g|= 1.3701e+06
## At iterate 1 f = 88432 |proj g|= 2.2605e+05
## At iterate 2 f = 53785 |proj g|= 1.5368e+05
## At iterate 3 f = 18431 |proj g|= 40307
## At iterate 4 f = 13382 |proj g|= 24186
## At iterate 5 f = 10108 |proj g|= 18531
## At iterate 6 f = 5034.7 |proj g|= 9368.8
## At iterate 7 f = 2436.2 |proj g|= 4399.6
## At iterate 8 f = 1342.5 |proj g|= 2159.7
## At iterate 9 f = 830.06 |proj g|= 1023.4
## At iterate 10 f = 612 |proj g|= 472.62
## At iterate 11 f = 527.28 |proj g|= 200.51
## final value 527.279450
## stopped after 11 iterations
## [1] 23.060589934 0.230926775 0.693340643 -0.002664903 -3.539238360
## [1] 527.2795
adlaplace::format_parameters(
parameters = outer_fit$par,gamma = cache$gamma,
info=ad_fun@info)$parameters## term model label transform mle
## 1 intercept intercept intercept FALSE 23.060589934
## 2 sex linear sex_linear FALSE 0.230926775
## 3 BMI skewnormal BMI_skewnormal_omega TRUE 2.000386963
## 4 BMI skewnormal BMI_skewnormal_alpha FALSE -0.002664903
## 5 sport iid sport_iid TRUE 0.029035433
After refitting at the outer MLE, we store updated fixed effects,
variance components, and random-effect modes, then overlay the fitted
skew-normal density on the BMI histogram (when sn is
available).
res <- adlaplace::log_lik_laplace(
x = outer_fit$par,
ad_fun = ad_fun,
config = modifyList(config, list(verbose = FALSE)),
deriv = TRUE
)## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 501.055226 0.000072 Continuing 1.000000 2 Reached tolerance
## 2 501.055226 0.000000 Continuing 1.000000 2 Reached tolerance
##
## Iteration has terminated
## 2 501.055226 0.000000 Success
## [1] 10.8998003 -15.7241785 -200.5131343 17.0562747 -0.6898023
model_stuff$data$info$gamma$mode <- res$opt$solution
model_stuff$data$info$beta$mle <- res$parameters[seq(1, nrow(model_stuff$data$info$beta))]
model_stuff$data$info$theta$mle <- res$parameters[-(1:nrow(model_stuff$data$info$beta))]
model_stuff$data$info$theta[
model_stuff$data$info$theta$transform, "mle"
] <-
exp(model_stuff$data$info$theta[model_stuff$data$info$theta$transform, "mle"])hist(ais$BMI, prob = TRUE, breaks = 31)
x_seq <- seq(par("usr")[1], par("usr")[2], len = 1001)
if (requireNamespace("sn", quietly = TRUE)) {
lines(x_seq,
sn::dsn(x_seq,
xi = model_stuff$data$info$beta$mle[1],
omega = model_stuff$data$info$theta$mle[1],
alpha = model_stuff$data$info$theta$mle[2]
),
col = "red"
)
}Finally we scan the fourth outer parameter and compare the analytic
gradient to finite differences, along with inner-mode and
Hessian-determinant sensitivities. This requires the suggested
abind package.
if (!requireNamespace("abind", quietly = TRUE)) {
stop("Install suggested package 'abind' to run derivative checks.")
}
x <- cache$last_par_fn # outer_fit$par
Dpar <- 4
Ngrid <- 13L
par_grid <- matrix(x, nrow = Ngrid, ncol = length(x), byrow = TRUE)
Sx <- 5 * seq(-1, 1, len = Ngrid) + x[Dpar]
SxD <- Sx[-1] - diff(Sx) / 2
par_grid[, Dpar] <- Sx
res <- lapply(
split(par_grid, row(par_grid)),
adlaplace::log_lik_laplace,
ad_fun = ad_fun,
config = config,
deriv = TRUE,
gamma = cache$gamma
)## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 3180.472717 1.593348 Continuing 1.000000 3 Reached tolerance
## 2 3180.471734 0.000012 Continuing 1.000000 3 Reached tolerance
## 3 3180.471734 0.000012 Continuing - TR contract 0.500000 3 Reached tolerance
## 4 3180.471734 0.000012 Continuing - TR contract 0.250000 3 Reached tolerance
## 5 3180.471734 0.000012 Continuing - TR contract 0.125000 3 Reached tolerance
## 6 3180.471734 0.000012 Continuing - TR contract 0.062500 3 Reached tolerance
## 7 3180.471734 0.000012 Continuing - TR contract 0.031250 3 Reached tolerance
## 8 3180.471734 0.000012 Continuing - TR contract 0.015625 3 Reached tolerance
## 9 3180.471734 0.000012 Continuing - TR contract 0.007812 3 Reached tolerance
## 10 3180.471734 0.000012 Continuing - TR contract 0.003906 3 Reached tolerance
##
## iter f nrm_gr status radCG iter CG result
## 11 3180.471734 0.000012 Continuing - TR contract 0.001953 3 Reached tolerance
## 12 3180.471734 0.000012 Continuing - TR contract 0.000977 3 Reached tolerance
## 13 3180.471734 0.000012 Continuing - TR contract 0.000488 3 Reached tolerance
## 14 3180.471734 0.000012 Continuing - TR contract 0.000244 3 Reached tolerance
## 15 3180.471734 0.000012 Continuing - TR contract 0.000122 3 Reached tolerance
## 16 3180.471734 0.000012 Continuing - TR contract 0.000061 3 Reached tolerance
## 17 3180.471734 0.000012 Continuing - TR contract 0.000031 3 Reached tolerance
## 18 3180.471734 0.000012 Continuing - TR contract 0.000015 3 Reached tolerance
## 19 3180.471734 0.000012 Continuing - TR contract 0.000008 3 Reached tolerance
## 20 3180.471734 0.000012 Continuing - TR contract 0.000004 3 Reached tolerance
##
## iter f nrm_gr status radCG iter CG result
## 21 3180.471734 0.000012 Continuing - TR contract 0.000002 3 Reached tolerance
## 22 3180.471734 0.000012 Continuing - TR contract 0.000001 3 Reached tolerance
## 23 3180.471734 0.000012 Continuing - TR contract 0.000000 3 Reached tolerance
## 24 3180.471734 0.000012 Continuing - TR contract 0.000000 3 Reached tolerance
## 25 3180.471734 0.000012 Continuing - TR contract 0.000000 3 Reached tolerance
## 26 3180.471734 0.000012 Continuing - TR contract 0.000000 3 Reached tolerance
## 27 3180.471734 0.000012 Continuing - TR contract 0.000000 3 Reached tolerance
## 28 3180.471734 0.000012 Continuing - TR contract 0.000000 3 Reached tolerance
## 29 3180.471734 0.000012 Continuing - TR contract 0.000000 3 Reached tolerance
##
## Iteration has terminated
## 29 3180.471734 0.000012Radius of trust region is less than stop.trust.radius
##
## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 2400.869969 0.513170 Continuing 1.000000 3 Reached tolerance
## 2 2400.869865 0.000049 Continuing 1.000000 2 Reached tolerance
## 3 2400.869865 0.000000 Continuing 1.000000 3 Reached tolerance
##
## Iteration has terminated
## 3 2400.869865 0.000000 Success
##
## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 1741.045751 0.125032 Continuing 1.000000 3 Reached tolerance
## 2 1741.045744 0.000006 Continuing 1.000000 2 Reached tolerance
## 3 1741.045744 0.000006 Continuing - TR contract 0.500000 3 Reached tolerance
## 4 1741.045744 0.000006 Continuing - TR contract 0.250000 3 Reached tolerance
## 5 1741.045744 0.000006 Continuing - TR contract 0.125000 3 Reached tolerance
## 6 1741.045744 0.000006 Continuing - TR contract 0.062500 3 Reached tolerance
## 7 1741.045744 0.000006 Continuing - TR contract 0.031250 3 Reached tolerance
## 8 1741.045744 0.000006 Continuing - TR contract 0.015625 3 Reached tolerance
## 9 1741.045744 0.000006 Continuing - TR contract 0.007812 3 Reached tolerance
## 10 1741.045744 0.000006 Continuing - TR contract 0.003906 3 Reached tolerance
##
## iter f nrm_gr status radCG iter CG result
## 11 1741.045744 0.000006 Continuing - TR contract 0.001953 3 Reached tolerance
## 12 1741.045744 0.000006 Continuing - TR contract 0.000977 3 Reached tolerance
## 13 1741.045744 0.000006 Continuing - TR contract 0.000488 3 Reached tolerance
## 14 1741.045744 0.000006 Continuing - TR contract 0.000244 3 Reached tolerance
## 15 1741.045744 0.000006 Continuing - TR contract 0.000122 3 Reached tolerance
## 16 1741.045744 0.000006 Continuing - TR contract 0.000061 3 Reached tolerance
## 17 1741.045744 0.000006 Continuing - TR contract 0.000031 3 Reached tolerance
## 18 1741.045744 0.000006 Continuing - TR contract 0.000015 3 Reached tolerance
## 19 1741.045744 0.000006 Continuing - TR contract 0.000008 3 Reached tolerance
## 20 1741.045744 0.000006 Continuing - TR contract 0.000004 3 Reached tolerance
##
## iter f nrm_gr status radCG iter CG result
## 21 1741.045744 0.000006 Continuing - TR contract 0.000002 3 Reached tolerance
## 22 1741.045744 0.000006 Continuing - TR contract 0.000001 3 Reached tolerance
## 23 1741.045744 0.000006 Continuing - TR contract 0.000000 3 Reached tolerance
## 24 1741.045744 0.000006 Continuing - TR contract 0.000000 3 Reached tolerance
## 25 1741.045744 0.000006 Continuing - TR contract 0.000000 3 Reached tolerance
## 26 1741.045744 0.000006 Continuing - TR contract 0.000000 3 Reached tolerance
## 27 1741.045744 0.000006 Continuing - TR contract 0.000000 3 Reached tolerance
## 28 1741.045744 0.000006 Continuing - TR contract 0.000000 3 Reached tolerance
## 29 1741.045744 0.000006 Continuing - TR contract 0.000000 3 Reached tolerance
##
## Iteration has terminated
## 29 1741.045744 0.000006Radius of trust region is less than stop.trust.radius
##
## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 1211.124332 0.022028 Continuing 1.000000 2 Reached tolerance
## 2 1211.124332 0.000002 Continuing 1.000000 2 Reached tolerance
##
## Iteration has terminated
## 2 1211.124332 0.000002 Success
##
## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 820.004988 0.002208 Continuing 1.000000 2 Reached tolerance
## 2 820.004988 0.000000 Continuing 1.000000 2 Reached tolerance
##
## Iteration has terminated
## 2 820.004988 0.000000 Success
##
## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 577.250360 0.000107 Continuing 1.000000 2 Reached tolerance
## 2 577.250360 0.000000 Continuing 1.000000 2 Reached tolerance
##
## Iteration has terminated
## 2 577.250360 0.000000 Success
##
## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 501.055226 0.000000 Continuing - TR contract 0.500000 2 Reached tolerance
##
## Iteration has terminated
## 1 501.055226 0.000000 Success
##
## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 591.640043 0.000189 Continuing 1.000000 2 Reached tolerance
## 2 591.640043 0.000000 Continuing 1.000000 2 Reached tolerance
##
## Iteration has terminated
## 2 591.640043 0.000000 Success
##
## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 807.825434 0.002720 Continuing 1.000000 2 Reached tolerance
## 2 807.825434 0.000000 Continuing 1.000000 2 Reached tolerance
##
## Iteration has terminated
## 2 807.825434 0.000000 Success
##
## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 1138.703495 0.022864 Continuing 1.000000 2 Reached tolerance
## 2 1138.703495 0.000001 Continuing 1.000000 2 Reached tolerance
##
## Iteration has terminated
## 2 1138.703495 0.000001 Success
##
## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 1578.686473 0.093941 Continuing 1.000000 3 Reached tolerance
## 2 1578.686469 0.000007 Continuing 1.000000 2 Reached tolerance
## 3 1578.686469 0.000000 Continuing 1.000000 3 Reached tolerance
##
## Iteration has terminated
## 3 1578.686469 0.000000 Success
##
## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 2121.079324 0.332690 Continuing 1.000000 3 Reached tolerance
## 2 2121.079281 0.000001 Continuing 1.000000 3 Reached tolerance
##
## Iteration has terminated
## 2 2121.079281 0.000001 Success
##
## Beginning optimization
##
## iter f nrm_gr status radCG iter CG result
## 1 2757.898501 0.943724 Continuing 1.000000 3 Reached tolerance
## 2 2757.898167 0.000007 Continuing 1.000000 3 Reached tolerance
## 3 2757.898167 0.000007 Continuing - TR contract 0.500000 3 Reached tolerance
## 4 2757.898167 0.000007 Continuing - TR contract 0.250000 3 Reached tolerance
## 5 2757.898167 0.000007 Continuing - TR contract 0.125000 3 Reached tolerance
## 6 2757.898167 0.000007 Continuing - TR contract 0.062500 3 Reached tolerance
## 7 2757.898167 0.000007 Continuing - TR contract 0.031250 3 Reached tolerance
## 8 2757.898167 0.000007 Continuing - TR contract 0.015625 3 Reached tolerance
## 9 2757.898167 0.000007 Continuing - TR contract 0.007812 3 Reached tolerance
## 10 2757.898167 0.000007 Continuing - TR contract 0.003906 3 Reached tolerance
##
## iter f nrm_gr status radCG iter CG result
## 11 2757.898167 0.000007 Continuing - TR contract 0.001953 3 Reached tolerance
## 12 2757.898167 0.000007 Continuing - TR contract 0.000977 3 Reached tolerance
## 13 2757.898167 0.000007 Continuing - TR contract 0.000488 3 Reached tolerance
## 14 2757.898167 0.000007 Continuing - TR contract 0.000244 3 Reached tolerance
## 15 2757.898167 0.000007 Continuing - TR contract 0.000122 3 Reached tolerance
## 16 2757.898167 0.000007 Continuing - TR contract 0.000061 3 Reached tolerance
## 17 2757.898167 0.000007 Continuing - TR contract 0.000031 3 Reached tolerance
## 18 2757.898167 0.000007 Continuing - TR contract 0.000015 3 Reached tolerance
## 19 2757.898167 0.000007 Continuing - TR contract 0.000008 3 Reached tolerance
## 20 2757.898167 0.000007 Continuing - TR contract 0.000004 3 Reached tolerance
##
## iter f nrm_gr status radCG iter CG result
## 21 2757.898167 0.000007 Continuing - TR contract 0.000002 3 Reached tolerance
## 22 2757.898167 0.000007 Continuing - TR contract 0.000001 3 Reached tolerance
## 23 2757.898167 0.000007 Continuing - TR contract 0.000000 3 Reached tolerance
## 24 2757.898167 0.000007 Continuing - TR contract 0.000000 3 Reached tolerance
## 25 2757.898167 0.000007 Continuing - TR contract 0.000000 3 Reached tolerance
## 26 2757.898167 0.000007 Continuing - TR contract 0.000000 3 Reached tolerance
## 27 2757.898167 0.000007 Continuing - TR contract 0.000000 3 Reached tolerance
## 28 2757.898167 0.000007 Continuing - TR contract 0.000000 3 Reached tolerance
## 29 2757.898167 0.000007 Continuing - TR contract 0.000000 3 Reached tolerance
##
## Iteration has terminated
## 29 2757.898167 0.000007Radius of trust region is less than stop.trust.radius
SnegLik <- vapply(res, `[[`, numeric(1), "neg_log_lik")
Sdet <- vapply(res, function(r) r$extra$hessian$half_log_det, numeric(1))
grad_mat <- do.call(rbind, lapply(res, `[[`, "grad"))
extra_df <- do.call(abind::abind, c(lapply(res, `[[`, "deriv"), along = 3))
dU <- do.call(
abind::abind,
c(lapply(res, function(r) as.matrix(r$extra$dU)), along = 3)
)
u_hat <- do.call(rbind, lapply(res, function(r) r$opt$solution))
par(mfrow = c(3, 2), mar = c(2, 2, 2, 0), mgp = c(1, 0.5, 0))
plot(Sx, SnegLik)
plot(Sx, grad_mat[, Dpar], type = "l")
points(SxD, diff(SnegLik) / diff(Sx))
Du <- 1L
plot(Sx, u_hat[, Du])
plot(Sx, dU[Du, Dpar, ])
lines(SxD, diff(u_hat[, Du]) / diff(Sx))
plot(Sx, Sdet)
plot(Sx, extra_df[Dpar, "d_det", ])
lines(SxD, diff(Sdet) / diff(Sx))