| tulpa_em_laplace | R Documentation |
Generic EM engine. Model packages provide callbacks for the E-step
(latent variable posterior) and the M-step encoding (how to assemble
submodel data from weights). Each M-step submodel block is fit
independently via tulpa_laplace(); the engine reads family and
offset per block so heterogeneous-family mixtures (e.g. a binomial
zero submodel + poisson positive submodel for a hurdle model) work
without engine changes.
tulpa_em_laplace(
e_step,
m_step_encode,
spatial = NULL,
re_list = list(),
max_iter = 50L,
tol = 1e-04,
damping = 0.3,
correction = c("auto", "mi", "gibbs", "none"),
n_imputations = 20L,
n_gibbs = 10L,
draw_z = NULL,
m_step_extra = NULL,
beta_prior = NULL,
verbose = TRUE,
...
)
e_step |
Callback: |
m_step_encode |
Callback:
|
spatial, re_list |
Not consumed at the driver level – latent structure
is per-submodel, set as a |
max_iter |
Maximum EM iterations. |
tol |
Convergence tolerance on max relative parameter change. |
damping |
EM damping factor in |
correction |
Post-EM correction. |
n_imputations |
Number of MI draws (default |
n_gibbs |
Length of the Gibbs chain (default |
draw_z |
Optional function |
m_step_extra |
Optional |
beta_prior |
Optional Gaussian prior on the fixed effects, applied to
every block fit via |
verbose |
Print per-iteration progress. |
... |
Forwarded to |
This is an engine block, not a front door: model packages call it
programmatically, so its tuning knobs (max_iter, tol, damping)
sit in the signature rather than in a control list.
A list with:
fits – named list of tulpa_laplace() results, one per block.
weights – final E-step weights.
n_iter – number of EM iterations actually run.
converged – logical.
history – data.frame(iter, delta) of max relative parameter
change per iteration.
correction – the resolved correction mode ("none", "mi",
or "gibbs").
pooled – present when correction %in% c("mi", "gibbs").
Named list of pooled per-submodel summaries from rubins_pool().
draws – present when correction %in% c("mi", "gibbs").
List of per-draw fits with beta / se attached.
weights, not in yThe M-step maximizes the expected complete-data log-likelihood. For a
Bernoulli latent z_i carrying E-step posterior weight w_i that is
Q = \sum_i [\, w_i \log p_i + (1 - w_i) \log (1 - p_i) \,],
a weighted Bernoulli log-likelihood, which carries no binomial
coefficient. Encode it as two rows per unit – y = 1 at weight w_i and
y = 0 at weight 1 - w_i:
list(y = rep(c(1, 0), each = n),
X = rbind(X, X),
weights = c(w, 1 - w),
family = "binomial")
A fractional y on a binomial block is refused, and is not the same
objective: it asks for the exact binomial density at a non-integer
response, whose normalizer lchoose(n, y) is not zero there and depends on
w. On the weighted encoding the block's log_marginal is the Laplace
marginal of Q, an EM objective that increases across iterations.
# Zero-inflated Poisson via EM: the E-step scores the posterior probability
# that each zero is non-structural. Those are soft labels, so the occupancy
# arm is the weighted Bernoulli above -- two rows per unit -- and the
# abundance arm weights each count by the same w.
set.seed(1)
n <- 200
z <- rbinom(n, 1, 0.7)
y <- rpois(n, 4) * z
X <- cbind(1, rnorm(n))
e_step <- function(fits, ...) {
if (!length(fits)) return(list(weights = pmax(as.numeric(y > 0), 0.5)))
psi <- plogis(drop(X %*% fits$occ$mode))
lam <- exp(drop(X %*% fits$abund$mode))
w <- ifelse(y > 0, 1, psi * exp(-lam) / (psi * exp(-lam) + (1 - psi)))
list(weights = w)
}
m_step_encode <- function(weights, ...) {
list(
occ = list(y = rep(c(1, 0), each = n), X = rbind(X, X),
weights = c(weights, 1 - weights), family = "binomial"),
abund = list(y = y, X = X, family = "poisson", weights = weights)
)
}
res <- tulpa_em_laplace(e_step, m_step_encode, verbose = FALSE)
res$converged
plogis(res$fits$occ$mode[1]) # P(non-structural), truth 0.7
exp(res$fits$abund$mode[1]) # abundance mean, truth 4
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.