Same M-step plumbing as tulpa_em_laplace(): every iteration calls
m_step_encode(weights, ...) to assemble per-submodel blocks and
fits each block via tulpa_laplace(). The difference is the E-step:
instead of computing closed-form weights, an e_step_sample
callback returns n_mc weight draws per iteration. Each draw is
run through the M-step independently and the resulting parameter
estimates are pooled via rubins_pool().
Convergence criterion is the max relative change in pooled M-step
parameter estimates between iterations. To increase Monte-Carlo
accuracy as iterations progress (Booth-Hobert ascent-based MCEM),
set n_mc_growth > 1.
This is an engine block, not a front door: model packages call it
programmatically, so its tuning knobs (max_iter, tol, n_mc)
sit in the signature rather than in a control list.
Usage
tulpa_em_mc(
e_step_sample,
m_step_encode,
n_mc = 10L,
n_mc_growth = 1,
n_mc_max = 200L,
max_iter = 30L,
tol = 0.001,
verbose = TRUE,
...
)Arguments
- e_step_sample
Function
function(fits, n_mc, ...) -> list. Must return a list of lengthn_mc, each element a weights object of the same shapem_step_encodeconsumes (typically a numeric vector of lengthn_obs, or a matrixn_obs x Kfor K-class latent variables). On the first iterationfitsislist()– the callback should return draws from the prior.- m_step_encode
Function
function(weights, ...) -> list of blocks. Identical to the contract used bytulpa_em_laplace(): each block is a list with required fieldsy,X,family, and optionaln_trials,offset,phi,re_list,spatial,weights. See?tulpa_em_laplacefor the full spec.- n_mc
Initial number of Monte-Carlo draws per iteration (default
10L).- n_mc_growth
Multiplicative growth of
n_mcper iteration (default1.0= constant). Set> 1for ascent-based MCEM that ramps up MC accuracy near convergence.- n_mc_max
Cap on
n_mc(default200L).- max_iter
Maximum EM iterations (default
30L).- tol
Convergence tolerance on max relative change in pooled parameter estimates (default
1e-3). Looser thantulpa_em_laplacedefault because Monte-Carlo noise floors the achievable precision.- verbose
Print per-iteration progress (default
TRUE).- ...
Forwarded to
e_step_sampleandm_step_encode.
Value
A list with:
pooled– named list of pooled per-submodel summaries (mean,se,V_within,V_between,V_total); seerubins_pool().fits– list of per-MC-draw fit lists from the final iteration, each indexed by submodel name.n_iter– iterations actually run.n_mc_final–n_mcvalue used in the last iteration.converged– logical.history–data.frame(iter, n_mc, delta).
Tier
Inherits the tier of the inner M-step (Laplace => Tier 2). The
Monte-Carlo E-step itself is exact in the limit n_mc -> infinity,
so as a full pipeline MCEM is asymptotically Tier 1 if
e_step_sample is exact.
References
Wei, G. C. G., & Tanner, M. A. (1990). A Monte Carlo implementation of the EM algorithm and the poor man's data augmentation algorithms. Journal of the American Statistical Association, 85(411), 699-704.
Booth, J. G., & Hobert, J. P. (1999). Maximizing generalized linear mixed model likelihoods with an automated Monte Carlo EM algorithm. JRSS B, 61(1), 265-285.
See also
tulpa_em_laplace() for the closed-form-weights variant,
rubins_pool() for the pooling rule.
Examples
# \donttest{
# Two-component Gaussian mixture with unit variances: the latent class z is
# sampled in the E-step, and each class mean is an intercept-only Gaussian
# block in the M-step.
set.seed(1)
z <- rbinom(100, 1, 0.4)
y <- ifelse(z == 0, rnorm(100, -1), rnorm(100, 2))
e_step_sample <- function(fits, n_mc, ...) {
p1 <- if (length(fits) == 0L) {
ifelse(y > median(y), 0.8, 0.2)
} else {
mu0 <- fits[[1]]$comp0$mode[1]
mu1 <- fits[[1]]$comp1$mode[1]
l0 <- dnorm(y, mu0, log = TRUE) + log(0.6)
l1 <- dnorm(y, mu1, log = TRUE) + log(0.4)
1 / (1 + exp(l0 - l1))
}
lapply(seq_len(n_mc), function(s) rbinom(length(y), 1, p1))
}
m_step_encode <- function(weights, ...) {
list(comp0 = list(y = y[weights == 0], X = matrix(1, sum(weights == 0), 1),
family = "gaussian"),
comp1 = list(y = y[weights == 1], X = matrix(1, sum(weights == 1), 1),
family = "gaussian"))
}
fit <- tulpa_em_mc(e_step_sample, m_step_encode, n_mc = 5L, max_iter = 5L,
tol = 5e-3, verbose = FALSE)
sapply(fit$pooled, function(p) p$mean)
fit$history
# }