Repository navigation
Conversation
|
More details with equations The three subsections follow the three commits of the PR, so each one can be pasted as a PR comment. 1. Ranking the candidates from cross-productsFor a Gaussian submodel with the identity link, The fitted values are $\hat{y}s = \bar{y}s + X{c,J} \beta_s$, with $\bar{y}s$ the weighted mean of column $s$ of $M$. With the Cholesky factor $G{JJ} + \Lambda{JJ} = R^\top R$ and The part of In one step, all candidates share the columns After the search, 2. Evaluation in the LOO CV with validated search
The first identity follows from $\sum_i w_i (M_{is} - \bar{y}s)(\hat{y}{is} - \bar{y}s) = \beta_s^\top C{Js}$ and This applies only in the LOO CV with validated search. 3. All one-column candidates at onceMost candidates add one column Expanding where all terms for all candidates 4. No submodel fits along the ranking in the validated search when the evaluation refitsAfter the ranking, So |
Summary
For Gaussian submodels with the identity link, this PR computes the forward search and the evaluation in the LOO CV with validated search from cross-products of the model matrix. The cost per candidate submodel then does not depend on the number of observations.
varsel()with forward search for a Gaussian model with 50 observations and 200 predictor terms gets about 6 times faster.cv_varsel(validate_search = TRUE)for a Gaussian model with 40 observations and 15 predictor terms gets about 11 times faster. The rankings are identical to master.This PR was assisted by Claude.
Overhead
A forward search projects the reference model onto every candidate submodel in every step: about P ×
nterms_maxprojections, each with one fit per projected draw.cv_varsel(validate_search = TRUE)repeats the search for each left-out observation and then evaluates every submodel along that observation's path. For a Gaussian submodel with the identity link, each fit is a weighted ridge least-squares problem. The search needs only the solution and the part of the cross-entropy that differs between candidates; the evaluation also needs the projected dispersion and the predictions. All of these follow from cross-products of the model matrix, which projpred can compute once per search or evaluation.Changes
The PR has five commits that change the code, numbered below. One more commit, between commits 3 and 4, only adds the PR number to
NEWS.md.1. Ranking the candidates from cross-products.
search_forward()first calls the newsearch_forward_gauss(). For the columns J of a candidate and draw s,glm_ridge()solves (G[J, J] + Λ) β_s = C[J, s], with G = Xc' W Xc, C = Xc' W M and Λ the penalty on the original scale.search_forward_gauss()ranks the candidates by −Σ_s w_s β_s' (G[J, J] + 2Λ) β_s, which is the part ofce_gauss()that differs between candidates.select_possible_terms_size(), so compound search terms such as"x1 + x2 + x1:x2"work as insearch_forward().proj_to_submodl()computes the submodel fits along the ranking, whichsearch_forward()stores as before.search_forward_gauss()returnsNULL, andsearch_forward()continues as before, in these cases:projpred.glm_fitter;glm_ridge()other thanthreshin the divergence-minimizer arguments;projpred.search_gauss_crossprod = FALSEturns this off. I documented it with the other internal global options.2. Evaluation in the LOO CV with validated search. The new file
R/gauss_crossprod.Rcollects the condition check (gauss_crossprod_regul()), the cross-products (gauss_crossprod_design()) and the newperf_eval_gauss().perf_eval()callsperf_eval_gauss()ifrefit_prj = TRUEand the projected draws are reweighted for one left-out observation. For each submodel size along the path,perf_eval_gauss()computes the coefficients, the projected dispersion (dis_gauss()), the cross-entropy (ce_gauss()) and the prediction for the left-out observation. The header of the file gives the identities. The submodels along a path are nested, so the Cholesky factor grows block by block. Other evaluations do not change. Sovarsel()andproj_linpred()keep using the same code for their test-data summaries, which a test compares at machine precision.3. All one-column candidates at once. Most candidates add one model-matrix column. For these, the block Cholesky step reduces to scalars, and
search_forward_gauss()computes the criterion for all of them with a few matrix products instead of one factorization per candidate.4. No submodel fits along the ranking in the validated search when the evaluation refits. After the ranking,
search_forward_gauss()computes the submodel fits along the ranking withproj_to_submodl(), becausesearch_forward()stores them insearch_path$outdmins. Incv_varsel(validate_search = TRUE)withrefit_prj = TRUE, the evaluation of each fold projects again and does not use these fits. In a profile of this case, they took 23 % of the time..select(),search_forward()andsearch_forward_gauss()get the new argumentreturn_outdmins = TRUE. WithFALSE,search_forward_gauss()does not compute the fits and returnsoutdmins = NULL. The draw-wise search insearch_forward()gets the fits during the search, so it ignores the argument.loo_varsel()andkfold_varsel()setreturn_outdmins = !refit_prjfor the search of each fold. All other calls keep the default, sovarsel(),project()andrefit_prj = FALSEwork as before.5. The latent projection. The latent projection projects onto Gaussian submodels with the identity link on the latent scale, with the same submodel fitter and the same cross-entropy as the traditional Gaussian projection. Commits 1 to 4 excluded it; commit 5 removes this exclusion. The evaluation passes the latent-scale predictions and dispersions to
weighted_summary_means()with the latent family, as the draw-wise evaluation does, so the performance on the original response scale does not change. For a latent projection of a Bernoulli model with 100 observations and 60 predictor terms (varsel(), 20 search and 20 evaluation clusters,nterms_max = 25, one workstation), the time goes from 45.1 s to 3.9 s with an identical ranking.Results
The rankings are identical to master in all checks:
poly()term, with forward and L1 search, 1, 5 or 20 clusters,cv_varsel(validate_search = FALSE)andproject();cv_varsel(validate_search = TRUE).Outside the validated LOO evaluation, all numbers are identical. In the validated LOO evaluation, the summaries differ from master by at most 7e-14 (relative), because the dispersion, the cross-entropy and the predictions come from other but equivalent computations. When two candidates have almost the same criterion, the two computations could select different candidates. This did not happen in the checks.
varsel(), forward search, 20 clusters,nterms_max = 65, 400 evaluation drawscv_varsel(validate_search = FALSE)varsel(d_test = )The two evaluations reuse the search, so they do not change. The benchmark (CSC Roihu, one node) also gives identical results (difference 0) for a model with a factor and an interaction and for a Bernoulli model.
cv_varsel(validate_search = TRUE), n = 40, P = 15 (with a factor and an interaction),nloo = 40, 5 clusters, 50 evaluation drawsThese times are medians of 3 runs on one Linux workstation.
Tests
The new
test_search_gauss.R(about 8 seconds, no Stan fits) checks that:varsel()gives the same rankings, performances and stored submodel fits with the option on and off (1 and 5 clusters);cv_varsel(validate_search = TRUE)gives the same results with the option on and off (tolerance 1e-10);perf_eval_gauss()is used once per left-out observation;search_forward_gauss()applies to a model with compound search terms and returnsNULLfor a factor in an interaction term;search_forward_gauss(return_outdmins = FALSE)gives the same ranking and no submodel fits;nloo = 5, the search of each of the 5 left-out observations runs withreturn_outdmins = FALSE.The tests that call
search_forward_gauss()orperf_eval_gauss()fail on master, where these functions do not exist; the last two tests also fail without commit 4. The full suite passes withNOT_CRAN=true: 260 tests, 55 823 expectations, 0 failures, the same 4 warnings as master. Tested with this change applied to master 73cf0ee (projpred 2.11.0.9000), R 4.6.1, brms 2.23.0, rstanarm 2.32.2, cmdstanr 0.9.0.9001, CmdStan 2.39.0, on CSC Roihu (Linux, AMD EPYC).Interaction with other PRs
search_forward_gauss()can pass the precomputed term labels toselect_possible_terms_size(). I will add this one-line change after Avoid repeated formula updates, term extraction and training-data model matrices #578 is merged.poly()terms in the LOO CV with validated search. After Keep the data-dependent bases #573 is merged, I will add apoly()term to the validated-LOO test.