This note derives the objective
that marginalApprox = "laplace" optimises, states its exact
gradient, and records how each block of that gradient was verified
against central finite differences. ("laplace_fast" and
"laplace_direct" are accepted as names for the same
backend; they were once two implementations, and the alternating one was
withdrawn after it proved slower for the same answer.) It also records a
defect that stood in the objective for some time, what it looked like
from the outside, and why the diagnosis that first suggested itself was
wrong. It is written for someone changing this code, not for someone
using the package.
This note describes the mathematics behind the current
bigIRT marginalApprox = "laplace_direct"
approach for binary 4PL IRT. It focuses on:
The goal is to describe the method actually implemented in
bigIRT, not an idealized future version.
For person \(i\), item \(j\), and binary response \(y_{ij} \in \{0,1\}\),
\[ \Pr(Y_{ij} = 1 \mid \theta_i, \psi_j) = c_j + (d_j - c_j)\,\sigma(\eta_{ij}), \]
where
\[ \eta_{ij} = a_j^\top \theta_i - b_j, \qquad \sigma(x) = \frac{1}{1 + e^{-x}}. \]
Here:
The conditional log-likelihood contribution is
\[ \ell_{ij}(\theta_i,\psi_j) = y_{ij}\log p_{ij} + (1-y_{ij})\log(1-p_{ij}), \]
with
\[ p_{ij} = c_j + (d_j-c_j)\sigma(\eta_{ij}). \]
bigIRTbigIRT uses unconstrained raw parameters and transforms
them into constrained item parameters:
\[ a_{jk} = \operatorname{softplus}(\alpha_{jk}) = \log(1+e^{\alpha_{jk}}), \]
\[ c_j = \tfrac12 \sigma(\gamma_j), \]
\[ d_j = \tfrac12 \sigma(\delta_j) + \tfrac12. \]
So the constrained ranges are:
This is a deliberate identification and stability choice, not a universal requirement of 4PL IRT.
The latent ability vector is modeled as multivariate normal:
\[ \theta_i \sim \mathcal{N}(\mu, \Sigma), \]
with precision matrix
\[ Q = \Sigma^{-1}. \]
In the current laplace_direct path:
The ideal marginal log-likelihood is
\[ \log L(\psi) = \sum_{i=1}^N \log \int \exp\left( \sum_{j \in \mathcal{J}_i} \ell_{ij}(\theta_i,\psi_j) \right) \phi(\theta_i;\mu,\Sigma)\,d\theta_i. \]
This is a \(K\)-dimensional integral for each person. For multidimensional 4PL IRT, direct integration becomes expensive quickly.
Laplace approximation replaces each person integral by a local Gaussian approximation around that person’s posterior mode. The computational burden is shifted from high-dimensional quadrature to repeated:
That tradeoff is often attractive in large-data IRT.
For fixed global parameters \(\psi\), define the person-specific log posterior
\[ f_i(\theta_i;\psi) = \sum_{j \in \mathcal{J}_i} \ell_{ij}(\theta_i,\psi_j) - \frac12(\theta_i-\mu)^\top Q(\theta_i-\mu) + \frac12 \log|Q| - \frac{K}{2}\log(2\pi). \]
The mode is
\[ \hat\theta_i(\psi) = \arg\max_{\theta_i} f_i(\theta_i;\psi). \]
The negative Hessian at the mode is
\[ H_i(\psi) = -\nabla_{\theta_i}^2 f_i(\hat\theta_i(\psi);\psi). \]
The Laplace approximation to the person integral is
\[ \log \int e^{f_i(\theta_i;\psi)}\,d\theta_i \approx f_i(\hat\theta_i;\psi) + \frac{K}{2}\log(2\pi) - \frac12 \log|H_i(\psi)|. \]
bigIRTThe current laplace_direct target is
\[ \tilde L(\psi) = \sum_{i=1}^N \left[ f_i(\hat\theta_i(\psi);\psi) + \frac{K}{2}\log(2\pi) - \frac12 \log|H_i(\psi)| \right] + \log p(\psi), \]
where \(\log p(\psi)\) collects optional priors and hyperpriors on:
Operationally, each evaluation of \(\tilde L(\psi)\) does:
The value used by laplace_direct is a direct Laplace
value at the current parameter vector:
So the value is not a frozen surrogate.
The exact derivative would be
\[ \frac{d}{d\psi}\tilde L(\psi), \]
which includes implicit derivatives through:
That full derivative is substantially harder to derive and implement.
Instead, laplace_direct uses a frozen-posterior
gradient:
This is not the exact gradient of the direct Laplace objective, but it is a structured approximation rather than an unrelated proxy.
For fixed \(\hat\theta_i\) and \(H_i\), the item-side gradient is approximated by
\[ \frac{\partial \tilde L}{\partial \psi_j} \approx \sum_i \left[ \frac{\partial \ell_{ij}(\hat\theta_i,\psi_j)}{\partial \psi_j} - \frac12 \operatorname{tr}\left(H_i^{-1}\frac{\partial H_i}{\partial \psi_j}\right) \right] + \frac{\partial \log p(\psi)}{\partial \psi_j}. \]
The two key pieces are:
\[ - \frac12 \operatorname{tr}\left(H_i^{-1}\frac{\partial H_i}{\partial \psi_j}\right). \]
This correction accounts for how the local curvature of the person posterior changes with the item parameters.
At the mode, envelope-theorem logic helps: for the joint log-posterior term, the derivative through \(\hat\theta_i(\psi)\) is less problematic than it first appears because the first derivative with respect to \(\theta_i\) vanishes at the optimum.
The remaining mismatch is mainly tied to the Hessian term and the fact that the direct gradient is not fully differentiated through the inner solve.
If the latent mean \(\mu\) is estimated, then conditional on the resolved posterior modes the frozen-posterior gradient is straightforward.
The prior contribution involves
\[ -\frac12(\hat\theta_i - \mu)^\top Q(\hat\theta_i-\mu), \]
so the approximate gradient with respect to \(\mu\) is
\[ \frac{\partial \tilde L}{\partial \mu} \approx \sum_i Q(\hat\theta_i - \mu) + \frac{\partial \log p(\mu)}{\partial \mu}. \]
This is implemented in the direct path.
When latent correlations are estimated, the same approximation principle is used:
This requires:
The prior normalizing constant matters here because once the latent correlation matrix is estimated, it is no longer a removable constant.
The main attraction of laplace_direct is that it avoids
the explicit outer EM-style loop. There is:
Relative to a two-step Laplace EM method, that has practical advantages.
One optimizer evaluation in the current implementation consists of:
The outer optimizer is a quasi-Newton method applied to this expensive approximate-gradient target.
The direct Laplace approximation does not remove the ordinary identification issues of MIRT. One still needs to identify:
For confirmatory MIRT, the most sensible strategy is usually:
This is a good match for the current laplace_direct
architecture because estimating full covariance structure is much more
delicate than estimating correlations with fixed marginal scales.
Even though the gradient is not exact, the approach can work well empirically for several reasons.
The objective value is recomputed using the current person modes and Hessians rather than a stale surrogate.
At the person mode, the first-order condition removes part of the implicit-derivative burden.
Nearby optimizer iterates usually imply nearby person posterior modes, so the inner Newton solves can become quite cheap.
When each person has enough information, the posterior becomes more concentrated and the Laplace approximation becomes more accurate.
Update, September 2026. The gradient is the exact total derivative of the direct Laplace objective, to about 1e-7 against central differences with the person modes re-solved at tolerance 1e-13, for
pl2, 3 and 4 and for one, two and three dimensions. See the Resolution section, and the update note before “Measured behaviour of the frozen-posterior gradient” for the curvature change that this depended on.
The main limitation is still that the gradient is not the exact derivative of the direct Laplace objective.
Guessing and slipping parameters are often weakly identified and especially sensitive to how latent tails are represented. Approximation error can distort them more than it distorts \(a\) or \(b\).
The Laplace target depends on the assumed latent distribution. Mis-specification of latent scales or correlations can feed back into item estimates.
The direct path simplifies outer-loop structure but makes each optimizer evaluation expensive.
Repeat:
Differentiate through
\[ \psi \mapsto \hat\theta_i(\psi), \qquad \psi \mapsto H_i(\psi), \]
using the implicit function theorem and exact derivative identities.
Approximate the marginal integral directly using adaptive quadrature.
Replace each person posterior with a tractable approximating family and optimize an ELBO.
Treat person abilities as ordinary parameters rather than integrating them out.
laplace_direct is attractiveIt is especially attractive when:
Another method may be preferable when:
The current bigIRT laplace_direct method
can be summarized as follows:
Its main strength is that the value stays close to the intended direct Laplace target.
Its main weakness is that the gradient is only approximate.
In many large-data settings that is a useful compromise, but it should be understood as an approximation strategy rather than an exact optimizer for the direct Laplace objective.
Update, September 2026 – read with the Resolution section below. The “Resolution” section near the end of this note already retracts the under-weighted-adjoint reading that follows: the adjoint was correct, and the finite differences that appeared to condemn it were taken of the wrong function. Two things have changed since that was written.
The 3PL and 4PL gradient was still wrong, for a different reason. Every gradient check in this note and in the test suite ran
pl = 2, where the observed Hessian and Fisher information are the same number, so none of them could see it. The mode adjoint applies the implicit function theorem and needs the inverse of the actual Hessian; the code inverted Fisher information. For the 3PL and 4PL the assembled item gradient missed the true total derivative by 1e-3 to 4e-2 relative, at one, two and three dimensions. The Laplace curvature is now the observed Hessian and the gradient matches at 1e-7. Fisher information is still used for the Newton step, where its positive definiteness is what makes the mode solve robust.
laplaceAdjointScaleandlaplaceLogdetScaleno longer exist. They were inert in the shipped path – the backend hardcodes both to 1.0 where the direct path calls the item block objective from C++ – and fits at 0, 1, 8.4 and 20 were bit-identical, so they were removed. The weight studies below cannot be reproduced throughfitIRT, and it is not established whether they were produced by a code state in which the knob was live.
The approximation argued for above was checked against central finite differences, re-solving the person modes at every perturbed parameter value so that the comparison is against the total derivative rather than the frozen partial. Step sizes of 1e-3, 1e-4 and 1e-5 agree to seven significant figures, so what follows is not finite-difference noise.
Splitting the analytic item gradient into its two halves, using the
laplaceAdjointScale argument to switch the mode-adjoint
term off:
The section above argues that the omitted implicit derivative is small because the first derivative with respect to \(\theta_i\) vanishes at the mode. That is correct for the conditional log-likelihood term and false for the log-determinant term: \(\partial(-\tfrac12\log|H_i|)/\partial\theta_i\) does not vanish at the mode. The residual described there as “mainly tied to the Hessian term” is not a small leftover; it is about nine times the correction that is actually retained.
An initial four-seed check suggested that correcting the weight degrades recovery outright. That was over-read from too narrow a design, and on two counts. Discrimination RMSE was the arbiter, but with tightly clustered generating discriminations the prior biases them, so that statistic mixes bias with variance – and intercept RMSE moved the opposite way, which the same check recorded and passed over.
A paired factorial study settles it: 2PL/3PL/4PL, 150 and 450 responses per item, one and three dimensions, weights 0, 1, 3, 8.4 and 20, 25 replications, 1,500 fits. Within a replication every weight sees the identical data set – the seed keys on the generating factors and excludes the weight – so contrasts are paired rather than differences of independent means. Held-out log loss is the primary outcome because it needs no reference parameters; item-curve RMSE is reported because it is invariant to how a fit divides a curve between discrimination and intercept; per-parameter bias and the ratio \(\mathrm{sd}(\hat a)/\mathrm{sd}(a)\) are reported separately rather than folded into one number.
| weight | held-out loss | curve RMSE | sd ratio | median s | strict |
|---|---|---|---|---|---|
| 0 | .58766 | .07173 | 1.144 | 3.3 | .23 |
| 1 (shipped) | .58718 | .06828 | 1.059 | 3.2 | .28 |
| 3 | .58699 | .07093 | 0.977 | 3.3 | .15 |
| 8.4 (near-correct) | .58734 | .07777 | 0.897 | 4.2 | .18 |
| 20 | .58848 | .09521 | 0.628 | 4.5 | .02 |
Three things follow.
The correction earns its place: weight 0 is among the worst on every outcome, so the term the design note originally omitted altogether is doing real work.
Full correction is not better. Weight 8.4, which makes the gradient 0.99 correlated with the true derivative, is not best on any outcome and is beaten by both 1 and 3 on held-out loss. Weight 20 is clearly worse throughout and reaches strict convergence in 2 per cent of fits against 28 per cent at the shipped weight.
The best weight depends on the model, and no single value wins. Weight 3 improves held-out loss for the 2PL (\(t=-6.3\)) and the 4PL (\(t=-4.6\)) and gives almost exactly calibrated discrimination spread; weight 1 is best for the 3PL, where raising the weight hurts sharply (\(t=17.9\) at weight 20). Curve RMSE favours the low weights nearly everywhere.
Against all of this, the effects are small. Held-out loss varies between .562 and .612 across the design cells, while the entire span across weights is about .0015. The gradient defect is large as a defect and second-order in its consequences, which is why the shipped default is kept: it is never badly wrong, it is best for the 3PL, it is the fastest, and it reaches strict convergence most often. The reason to record all this is not that the weight should be retuned but that the item step should stop being described as optimising the Laplace marginal likelihood. It optimises a damped surrogate, and the convergence diagnostics report convergence of that surrogate.
Two studies answer this, and both refuted the explanation I expected.
The obvious suspect was that the Laplace approximation is poor when a person has answered few items, so that its log-determinant is misestimated and the correct gradient faithfully optimises a biased objective. Holding responses per item at 300 and varying responses per person from 10 to 160 (450 fits, paired seeds) falsifies it. The near-correct weight does best at the lowest exposure – paired \(t=-8.2\) at ten responses per person, winning 97 per cent of comparisons – and the ordering does not reverse as exposure rises. If approximation quality were the mechanism, the sign would have gone the other way.
For the 3PL, the weight that gives essentially unbiased and correctly dispersed discriminations is weight 3 (bias \(-0.006\), \(\mathrm{sd}\) ratio \(0.993\)), and it has worse curve RMSE and held-out loss than the shipped weight 1, whose estimates are biased (+0.088) and over-dispersed (1.078). Unbiased but noisier loses to biased but shrunk on squared-error criteria.
Where the near-correct weight loses tracks identification: 3PL \(t=9.1\), 4PL in one dimension \(t=3.4\), 2PL \(t\approx0\), and 4PL in three dimensions it actually wins. The 3PL is the least identified of the three and gains most from shrinkage.
If the under-weighted adjoint is doing the work of a regulariser, the
consistent way to get the same effect is to damp the log-determinant in
the objective and in both of its gradient contributions – one coherent
objective \(f -
\lambda\tfrac12\log|H|\), with \(\lambda\) a documented dial. That is what
laplaceLogdetScale does, and it does not work.
Crossing the two weights over the 2PL and 3PL (400 fits, paired seeds), every cell is worse than the shipped pairing on both held-out loss and curve RMSE:
| \(\lambda\) | A bias (2PL) | sd ratio (2PL) | curve RMSE (3PL) |
|---|---|---|---|
| 0 | +2.14 | 2.89 | .2107 |
| 0.25 | +1.04 | 2.17 | .1317 |
| 0.5 | +0.51 | 1.66 | .0880 |
| 1 (shipped) | +0.00 | 1.08 | .0628 |
Damping the log-determinant does not add shrinkage, it removes it,
and discrimination runs away. The reason is that \(\lambda\) moves the explicit curvature term
\(-\tfrac12\operatorname{tr}(H^{-1}\partial
H/\partial\psi)\) along with the implicit one, and it is the
explicit term that holds discrimination down. Only the implicit
correction benefits from being under-weighted, which is exactly what
laplaceAdjointScale controls. The existing parameterisation
is the right one.
The numerics should stay as they are: no configuration tested beats the shipped pairing, and the alternatives that would make the optimiser self-consistent are measurably worse. Nor can consistency be recovered by rescaling, because a total derivative with one of its terms scaled is not in general the gradient of any closed-form objective.
What should change is the description. The item step optimises neither the Laplace marginal likelihood nor any objective whose gradient it computes: it is a mode-adjusted quasi-Laplace estimator whose implicit correction is deliberately under-weighted, and whose default weight is where recovery is best rather than where the derivative is right. Convergence diagnostics report the norm of the gradient that is actually followed, which is the honest thing for them to report, but it is not the gradient of the reported objective and should not be described as though it were.
Everything above treats the mode-adjoint correction as under-weighted by about a factor of eight, on the evidence of central finite differences. That evidence was sound and the inference from it was wrong, because the finite differences were taken of the wrong function.
Decomposing the implicit term into its two factors and testing each separately against finite differences:
Both factors exact, yet their product – assembled by hand, and matching the backend’s own adjoint to five decimals – came out four to nineteen times smaller than the finite difference of the objective. A product of two exact factors cannot be wrong, so the objective had to be.
It was. The per-subject objective in the C++ kernel is
where loglik accumulates the data log-likelihood only.
The Laplace approximation to the marginal is \(\ell(\hat heta) + \log p(\hat heta)
+ frac{K}{2}\log 2\pi -
frac12\log|H|\), so the ability prior evaluated at the mode –
\(- frac12(\hat heta-\mu)^ op
Q(\hat heta-\mu)\) – is missing.
That omission is not a constant offset, because \(\hat heta\) moves with the item parameters. More importantly it is the term that makes \(\partial f/\partial heta\) vanish at the mode. Without it the envelope theorem does not apply to the reported objective, its true total derivative carries an extra contribution, and an analytic gradient that is correct for the complete objective disagrees with finite differences of the incomplete one. The disagreement scales with \(d\hat heta/d\psi\), which is why it correlated 0.99 with the adjoint and looked exactly like a missing constant factor.
With the quadratic added to the objective, the analytic gradient matches the finite difference of the total derivative at correlation 1.000000 and maximum absolute difference 8e-08, and the implicit half matches at ratio 1.00 with standard deviation 0.00.
laplaceAdjointScale was removed in
September 2026: it was inert in the shipped path, so the weight is fixed
at 1 and is no longer settable.)What was genuinely wrong is the reported objective value, which omitted a term and was therefore not the Laplace marginal likelihood it was compared against. Fixing it leaves the estimates essentially unchanged – the value enters the outer convergence tests and the item line search, not the gradient – but it makes the reported objective the quantity the gradient belongs to.
bigIRTlaplace_direct is attractive