Embedded Laplace, solver 3: the Newton iteration converges to saddle points and converges slowly
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
调研方向
The Newton loop is run_newton_loop(), with flip_direction() and the Wolfe line search that starts from the Barzilai-Borwein step; the saddle-point check and the stopping rule are in the same path. Run the R script in the issue (issue_laplace_solver3.R), which uses laplace_latent_solve_tol() with solver 3 for example 1 (saddle point) and example 2 (iteration count). Done means example 1 reaches a maximum from every start or fails with a named cause, and example 2 needs about 10 iterations at each tolerance.
由索引模型根据 Issue 内容生成。
描述
Description
Solver 3 (LU) is the only solver of the embedded Laplace that accepts a log-likelihood that is not concave, that is, when the negative Hessian W of the log-likelihood is indefinite at some Newton iterate. I used it for a nonlinear joint model with 4 latent variables per group. Its log-likelihood is not concave at the mode for about half of the groups. The Newton iteration of solver 3 has two problems there. The examples below are small; both problems occur with CmdStan 2.40.0 and with develop (43bc5684a3).
Notation: f(theta) = log p(y | theta) + log N(theta | 0, K) is the objective of the Newton iteration. At a maximum, K^-1 + W is positive definite.
-
The Newton iteration can converge to a saddle point of
f. Inrun_newton_loop(),flip_direction()reverses the Newton direction only when it is not an ascent direction, and nothing checks thatK^-1 + Wis positive definite at the result. A Newton step can be an ascent step in total while it moves to a saddle point along a direction in whichfis convex. At the saddle point the gradient is zero, and the stopping rule accepts the point. The Laplace approximation is not defined there:laplace_latent_solve*()andlaplace_latent*_rng()fail, andlaplace_marginal*()returns NaN or fails. In example 1, the starts(+-0.01, 3)go to the saddle point(0, 0)in one step. In the joint model, 41 of 1000 posterior draws failed when the Newton iteration started at 0, and 163 of 1000 when it started at the posterior draw of the latent variables. In the failing draw that I traced, one group converged to a saddle point (eigenvalues of-Hessian of f: 104.4, 1.27, 0.85, -1.6). With #, the error message says thatdet(I + Sigma * W)is not positive at the final iterate, but the solve still fails. That check finds only an odd number of negative eigenvalues. -
The convergence is linear, not quadratic. The line search starts from the Barzilai-Borwein step size, and the strong Wolfe curvature condition with
c2 = 0.9accepts every stepalphawith|1 - alpha| <= 0.9along a Newton direction of a nearly quadratic objective. In example 2 (4 latent variables, unimodal posterior), a trace of the iteration shows thatalpha = 1is never used: 26 of the 37 iterations at tolerance 1e-6 usealpha = 1.855. The error then shrinks by the factor|1 - alpha| = 0.855per iteration. The solve needs 37, 50 and 65 iterations at tolerance 1e-6, 1.49e-8 (default) and 1e-10. As an experiment, I started the line search atalpha = 1(the Wolfe conditions unchanged): the same solve took 10 iterations at each of the three tolerances and ended at the same mode. Also, the stopping rule checks only the change of the objective. With linear convergence it stops while|K * grad f|is still 0.093 (tolerance 1e-6) or 0.012 (default tolerance).
Possible changes:
- Item 1: check that
K^-1 + Wis positive definite during the iteration, for example with a Cholesky factorization of the symmetricI + L_K^T W L_K(K = L_K L_K^T). Where it is not, use a modified Newton step (Levenberg-Marquardt damping, or a modified Hessian) so that directions of negative curvature are ascent directions. At the end, a failed check could move the iterate along a direction of negative curvature and restart, or give an error that names the cause. - Item 2: try
alpha = 1first for a Newton direction, and use the Barzilai-Borwein step only whenalpha = 1fails the line-search conditions. Stop on a gradient-based criterion as well, for example the Newton decrementgrad f^T (K^-1 + W)^-1 grad f.
Example
The R script below (cmdstanr; the Stan programs are inline) runs both examples.
- Example 1 (item 1): 2 latent variables,
K = [4 1; 1 1].theta[1]has two Cauchy observations,y = (-3, 3),s = 0.3, andtheta[2]has one Gaussian observation,z = 0,sz = 0.1.fhas maxima at(+-2.9385, +-0.0097)and a saddle point at(0, 0).laplace_latent_solve_tol()with solver 3, tolerance 1e-10, from 5 start points. - Example 2 (item 2): the longitudinal part of the joint model for one group: 12 observations
y ~ normal(m, sigma * m)withm(t) = bsld * (phi * exp(-d * t) + (1 - phi) * exp(g * t)), and 4 latent variables on the log or logit scale of(bsld, d, g, phi)with diagonalK. BFGS from 50 start points finds one mode.laplace_latent_solve_tol()with solver 3 from one start point. The script finds the smallestmax_num_stepsthat gives no "max number of iterations" warning.
R script (cmdstanr)
# issue_laplace_solver3.R
#
# Reproduction for a stan-dev/math issue on the Newton iteration of the
# embedded Laplace with solver 3 (LU), the solver that accepts a
# log-likelihood that is not concave. Self-contained (the Stan programs are
# inline).
#
# 1. The Newton iteration converges to a saddle point of
# log p(theta | y), where the Laplace approximation is not defined.
# 2. The iteration needs many steps: the line search starts from the
# Barzilai-Borwein step and accepts steps up to about 1.9 times the
# Newton step, so the convergence is linear, not quadratic.
# Shown by the smallest max_num_steps that gives no
# "max number of iterations" warning.
#
# Rscript tests/issue_laplace_solver3.R
# CMDSTAN_PATH=~/proj/cmdstan STAN_MATH=~/proj/stan_math_laplace_mean/ \
# Rscript tests/issue_laplace_solver3.R
library(cmdstanr)
library(posterior)
if (nzchar(Sys.getenv("CMDSTAN_PATH"))) {
set_cmdstan_path(Sys.getenv("CMDSTAN_PATH"))
}
cpp <- list()
if (nzchar(Sys.getenv("STAN_MATH"))) {
cpp$MATH <- Sys.getenv("STAN_MATH")
}
compile <- function(code) {
cmdstan_model(write_stan_file(code), cpp_options = cpp,
force_recompile = length(cpp) > 0)
}
cat(sprintf("CmdStan %s at %s, Stan Math %s\n", cmdstan_version(),
cmdstan_path(), if (is.null(cpp$MATH)) "of the CmdStan" else cpp$MATH))
# One fixed_param draw; returns the values of `vars` (NA if the draw
# failed) and the output lines.
run_once <- function(mod, data, vars) {
out <- capture.output(
fit <- mod$sample(data = data, fixed_param = TRUE, chains = 1,
iter_sampling = 1, refresh = 0, show_messages = TRUE,
sig_figs = 12),
type = "output")
vals <- tryCatch(as.numeric(as_draws_matrix(fit$draws(vars))),
error = function(e) rep(NA_real_, 2))
list(vals = vals, out = out)
}
exception_of <- function(out) {
unique(sub(".*Exception: (.*?) \\(in '.*", "\\1",
grep("Exception", out, value = TRUE), perl = TRUE))
}
# ── 1. Saddle point ────────────────────────────────────────────────────────
saddle_code <- "
functions {
real ll(vector theta, vector y, real s, real z, real sz) {
return cauchy_lpdf(y | theta[1], s) + normal_lpdf(z | theta[2], sz);
}
matrix k_fun(matrix K) {
return K;
}
}
data {
vector[2] y;
real<lower=0> s;
real z;
real<lower=0> sz;
matrix[2, 2] K;
vector[2] theta0;
}
generated quantities {
vector[2] mode;
{
tuple(vector[2], matrix[2, 2]) lap = laplace_latent_solve_tol(
ll, (y, s, z, sz), 2, k_fun, (K,), (theta0, 1e-10, 100, 3, 100, 0));
mode = lap.1;
}
}
"
y <- c(-3, 3); s <- 0.3; z <- 0; sz <- 0.1
K <- matrix(c(4, 1, 1, 1), 2)
log_f1 <- function(th) {
sum(dcauchy(y, th[1], s, log = TRUE)) + dnorm(z, th[2], sz, log = TRUE) -
0.5 * sum(th * solve(K, th))
}
cat("\n1. Saddle point. Stationary points of log p(theta | y):\n")
for (p in list(c(0, 0), c(2.9385, 0.0097), c(-2.9385, -0.0097))) {
q <- optim(p, function(t) sum(numDeriv::grad(log_f1, t)^2), method = "BFGS",
control = list(reltol = 1e-20))$par
cat(sprintf(" (%7.4f, %7.4f): eigenvalues of -Hessian %s\n", q[1], q[2],
paste(sprintf("%.3f", eigen(-numDeriv::hessian(log_f1, q),
symmetric = TRUE)$values),
collapse = " ")))
}
mod1 <- compile(saddle_code)
for (start in list(c(0.3, 3), c(0.05, 3), c(0.01, 3), c(-0.01, 3),
c(-0.05, 3))) {
r <- run_once(mod1, list(y = y, s = s, z = z, sz = sz, K = K,
theta0 = start), "mode")
ex <- exception_of(r$out)
cat(sprintf(" start (%5.2f, %4.1f): %s\n", start[1], start[2],
if (length(ex)) paste("exception:", ex)
else sprintf("mode (%.4f, %.4f)", r$vals[1], r$vals[2])))
}
# ── 2. Number of Newton iterations ─────────────────────────────────────────
tumor_code <- "
functions {
real ll(vector theta, vector y, vector t, vector mu, real sigma) {
real bsld = mu[1] * exp(theta[1]);
real d = mu[2] * exp(theta[2]);
real g = mu[3] * exp(theta[3]);
real phi = inv_logit(logit(mu[4]) + theta[4]);
vector[num_elements(y)] m
= bsld * (phi * exp(-d * t) + (1 - phi) * exp(g * t));
return normal_lpdf(y | m, sigma * m);
}
matrix k_fun(vector omega) {
return diag_matrix(square(omega));
}
}
data {
int<lower=1> n;
vector[n] y;
vector[n] t;
vector[4] mu;
vector<lower=0>[4] omega;
real<lower=0> sigma;
vector[4] theta0;
real<lower=0> tol;
int<lower=1> max_steps;
}
generated quantities {
vector[4] mode;
{
tuple(vector[4], matrix[4, 4]) lap = laplace_latent_solve_tol(
ll, (y, t, mu, sigma), 4, k_fun, (omega,),
(theta0, tol, max_steps, 3, 100, 0));
mode = lap.1;
}
}
"
data2 <- list(n = 12L,
y = c(201.04, 78.30, 48.01, 44.39, 52.91, 50.49, 48.63, 67.29,
52.21, 72.43, 102.43, 105.06),
t = seq(0, 693, by = 63),
mu = c(117.4, 9.121e-3, 1.913e-3, 0.3781),
omega = c(0.4925, 0.9217, 1.120, 1.299), sigma = 0.09826,
theta0 = c(0.3636, -0.3385, 1.9794, -1.9322))
mod2 <- compile(tumor_code)
hits_limit <- function(tol, max_steps) {
r <- run_once(mod2, c(data2, list(tol = tol, max_steps = as.integer(max_steps))),
"mode")
any(grepl("max number of iterations", r$out))
}
cat("\n2. Newton iterations, 4 latent variables (smallest max_num_steps without the\n",
" 'max number of iterations' warning):\n")
for (tol in c(1e-6, 1.49012e-8, 1e-10)) {
lo <- 1L
hi <- 200L
while (lo < hi) {
mid <- (lo + hi) %/% 2L
if (hits_limit(tol, mid)) lo <- mid + 1L else hi <- mid
}
cat(sprintf(" tol %-11g max_num_steps %d\n", tol, lo))
}
Expected Output
Current output (CmdStan 2.40.0):
1. Saddle point. Stationary points of log p(theta | y):
( 0.0000, 0.0000): eigenvalues of -Hessian 101.334 -0.099
( 2.9385, 0.0097): eigenvalues of -Hessian 101.335 19.879
(-2.9385, -0.0097): eigenvalues of -Hessian 101.335 19.879
start ( 0.30, 3.0): mode (2.9385, 0.0097)
start ( 0.05, 3.0): mode (2.9385, 0.0097)
start ( 0.01, 3.0): exception: cholesky_decompose: Matrix m is not positive definite
start (-0.01, 3.0): exception: cholesky_decompose: Matrix m is not positive definite
start (-0.05, 3.0): mode (-2.9385, -0.0097)
2. Newton iterations, 4 latent variables (smallest max_num_steps without the
'max number of iterations' warning):
tol 1e-06 max_num_steps 37
tol 1.49012e-08 max_num_steps 50
tol 1e-10 max_num_steps 65
A trace of example 1 (debug prints in run_newton_loop()) for the start (0.01, 3): the first step (alpha = 1, directional derivative 912, so no flip) goes to (1.8e-6, 5.9e-9), the saddle point; the next step changes the objective by 1.2e-13, and the iteration stops.
Expected:
- Example 1: a maximum from every start point, or, if the iteration cannot leave the saddle point, an error that names the cause.
- Example 2: quadratic convergence near the mode, that is, about 10 iterations at each tolerance, as in the experiment with
alpha = 1.
Current Version:
v5.4.0 (CmdStan 2.40.0). The same code is in develop at 43bc5684a3.
- 主要语言
- C++
- 星标
- 845
- 派生
- 225
- 平均合并
- 3 天 9 小时
- 30 天内合并 PR
- 12
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
stan-dev/math 的其他 Issue
-
难度 3/5 1-2 天 新手友好度 54/100
维护者通常 1 天内回复
-
难度 4/5 3-5 天 新手友好度 54/100
维护者通常 1 天内回复
-
难度 5/5 一周以上 新手友好度 35/100
维护者通常 1 天内回复
-
numerics performance
难度 5/5 一周以上 新手友好度 35/100
stan-dev/math#3481 · 1 个 reaction ·
维护者通常 1 天内回复
-
`eigendecompose_sym`, `eigenvalues_sym` and `eigenvectors_sym` do not compile for complex matrices of `fvar<var>` or `fvar<fvar<double>>`可能重新可做 关联的 PR 已关闭且未合并。 未关闭bug
难度 3/5 1-2 天 新手友好度 55/100
维护者通常 1 天内回复
相似的 Issue
-
难度 1/5 1 小时以内 新手友好度 72/100
rhalbersma/xstd-ints#395 ·
维护者通常 1 天内回复
-
[Bug] Root filesystem scope (/) incorrectly rejects all descendant paths可能已有人在做 @mugenkyou 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 72/100
neutralinojs/neutralino.js#304 · 1 条评论 ·
-
CLI: enforce absolute ADB device paths in file and directory commands可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
FuSa
难度 2/5 1-3 小时 新手友好度 76/100
tenstorrent/tt-umd#3851 ·
维护者通常 3 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 73/100
mattgodbolt/zindex#67 ·
维护者通常 1 天内回复