Repository navigation
Conversation
|
More details about the two changes Notation (all sections)
MasterFor a submodel without group-level or additive terms,
Commit 1: one model matrixThe shared matrix is used if all of these conditions hold:
Otherwise the code is the same as on master. For predictions for new data, where Commit 2: one solve for all draws (Gaussian, identity link)
Let For the Gaussian family with the identity link, the IRLS pseudo-data equal The matrix on the left does not depend on Back-transform to the original scale: Commit 2 uses the commit 1 path for other families or links, for a column with zero variance ( Cost
In the forward search with small |
ffb7bbd to
3eebda0
Compare
Summary
This PR reduces overhead in projections for submodels without group-level or additive terms, and more for such submodels with the Gaussian family and the identity link.
varsel()for a Gaussian model with 50 observations and 200 predictor terms gets about 7 times faster. In a Bernoulli example,varsel()is about 2 times faster. Memory use is also reduced.This PR was assisted by Claude.
Overhead
divmin()callsfit_glm_ridge_callback()once per projected draw (or cluster). The draw-wise formulas differ only in the response, but each call builds the model frame and the model matrix again, andsubprd()builds the model matrix for new data once per draw.glm_ridge()(C++, with R callbacks for the pseudo-data), although the fit is one weighted ridge least-squares solve with the same left-hand side for all draws.In a profile of
varsel()for a Gaussian model with N = 50 and P = 200 (forward search, 20 clusters,nterms_max = 65, 400 draws for the evaluation),model.frame()took 43 % andmodel.matrix()took 40 % of the time.Changes
The PR has two commits, which can be reviewed in order. Each commit changes some computations that master does S times, so that they are done only once.
1. Share one model matrix across the draw-wise fits.
divmin()calls the newshared_model_matrix(), which builds the model matrix, the factor levels and the response names once.glm_ridge_callback_shared()gives them tofit_glm_ridge_callback()(new argumentprojpred_shared_x), which then takes only the response of the draw fromdata.subprd()calls the newsubprd_subfit_shared()for severalsubfitobjects with the same predictor terms. It builds one model matrix for the new data and computes the predictions of all draws with one matrix product.fit_glm_ridge_callback()andpredict.subfit()moves without change intoglm_model_matrix()andsubfit_newdata_x().shared_model_matrix()returnsNULL, anddivmin()uses the draw-wise code as before, if the right-hand sides differ, if a response is not a plain column ofdata, or if there are missing values. A non-defaultprojpred.glm_fitteror a customdiv_minimizeralso keeps the draw-wise code.2. Solve the Gaussian identity-link submodels for all draws at once.
glm_ridge_gauss_multi()standardizes the shared model matrix asglm_ridge()does, solves the normal equations with one right-hand side per draw, transforms the coefficients back to the original scale, and returns onesubfitobject per draw with the same elements asfit_glm_ridge_callback().divmin()calls it before the draw-wise loop, also for a single draw.NULL(draw-wise code) for other families and links, for a model matrix with a zero-variance column, for unusable observation weights, and if...contains a formal argument ofglm_ridge()other thanfamily,weightsorthresh(for examplepenaltyorbeta_init). It ignores the other arguments in..., asfit_glm_ridge_callback()does. Thusvarsel()andproj_linpred()use the same fit, and the test intest_varsel.Rthat compares them at.Machine$double.epspasses.All draw-wise
subfitobjects refer to one model matrix in memory instead of S copies.saveRDS()and parallel workers still write one copy per fit.Results
All rankings are identical to master. The largest absolute difference is 4.8e-10, in the test-set ELPD sums (about -1 800, so a relative difference of 3e-13). The ELPD summaries, the pointwise lppd and mu, the projected draws and
proj_linpred()differ by at most 6e-14. The checks used a Gaussian model with N = 50 and P = 200, a Gaussian model with factor and interaction terms, and a Bernoulli model (commit 1 only, because commit 2 is Gaussian only).varsel(), settings as abovecv_varsel(validate_search = FALSE)varsel(d_test = )A Bernoulli
varsel()with N = 80 takes 1.3 s on master and 0.6 s with commit 1.Peak memory (RSS) of one R process for a Gaussian reference model with P = 50 and 4 000 draws, commit 1:
project(), n = 50 000, 4 000 projected drawsvarsel(), n = 50 000, 4 000 projected drawsvarsel(), n = 10 000, 4 000 projected drawsIn 48 configurations (n from 100 to 50 000; 20 clusters, 400 or 4 000 projected draws;
varsel()andproject()), the peak memory and the run time were never higher than with master.Tests
The new
test_submodel_fit_shared.Rchecks that:glm_ridge_gauss_multi()gives the samesubfitobjects as the draw-wisefit_glm_ridge_callback()(coefficients to 1e-10;x,y,xlvlsand the element names identical), for a model with a factor and an interaction and with observation weights;glm_ridge_gauss_multi()returnsNULLfor a non-identity link and for a non-Gaussian family;shared_model_matrix()returnsNULLfor different right-hand sides, for a response that is not a plain column, and for a missing predictor value;subprd_subfit_shared()andsubprd()give the same predictions for new data as the draw-wisepredict.subfit();project()with a Gaussian reference model usesglm_ridge_gauss_multi()(counted withtrace()).All five tests fail on master (functions not found). The full suite passes with
NOT_CRAN=true: 258 tests, 55 857 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 #573
#573 adds the element
termstosubfitobjects and uses it inpredict.subfit(). This PR createssubfitobjects inglm_ridge_gauss_multi()and moves the model-matrix code ofpredict.subfit()intosubfit_newdata_x(). <After #573 is merged, I will merge master into this branch and addtermsin the new code paths.>