Repository navigation
Conversation
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR removes formula and model-matrix work that the forward search and the projections repeat with the same result.
varsel()with forward search for a Gaussian model with 50 observations and 200 predictor terms gets about 1.5 times faster.cv_varsel(validate_search = TRUE)runs the forward search once per fold, so the faster candidate selection matters most there. All results are identical to master.This PR was assisted by Claude.
Overhead
In a profile of
varsel()for a Gaussian model with N = 50 and P = 200 (forward search, 20 clusters,nterms_max = 65), with #574 applied,update.formula()took 32 % of the time andextract_terms_response()took 22 %. On master the same work happens, but the per-draw model matrices take a larger share of the time.Changes
The PR has three commits. Each commit removes some work that master repeats with the same result.
1. Fewer
update()calls and term extractions.validate_response_formula()splits a formula with acbind()response into one formula per projected draw. It calledupdate()once per draw.update()rewrites the right-hand side in the same way for each response, so the function now callsupdate()once and replaces the response in copies of the result.select_possible_terms_size()calledextract_terms_response()for the already chosen terms once per candidate. The result is the same for all candidates, so the function now computes it once per search step.2. Training-data predictions from the stored model matrices.
mu_fun()(ininit_refmodel()) predicts the training data in every projection and built the training-data model matrix again each time, although eachsubfitobject stores it inx. With the defaultproj_predfun,mu_fun()now computes these predictions from the stored matrices with the newsubprd_fitted(). The previous code runs for a customproj_predfun, for the augmented-data projection, for fits of other classes, and if a stored matrix has fewer rows than the data (for example, aftermodel.frame()removed rows with missing values).3. Candidate selection from precomputed term labels.
select_possible_terms_size()parsed the formula of every candidate in every step. If no search term has a smooth, group-level or offset term, the size of a candidate submodel is the number of distinct term labels plus one for the intercept. The newsearch_term_info()computes the term labels of the search terms once per search, andsearch_forward()passes them toselect_possible_terms_size()(new argumentterm_info). A candidate with one new term label is then returned as that label without parsing; other candidates go through the previous code. For 30 search terms and 11 chosen terms, one call takes 0.9 ms instead of 18 ms.Additional details:
In each step of the forward search,
select_possible_terms_size()takes the chosen terms and all search terms and returns the candidates that make the submodel exactly one size larger. Master parses formulas for every candidate:For$T$ search terms, one step parses two formulas per search term and one more per kept candidate, so between $2T$ and $3T$ formulas. A forward search with $K$ steps multiplies this by $K$ , and
cv_varsel(validate_search = TRUE)repeats the search for every fold.If no search term has a smooth, group-level or offset term, and every search term has an intercept, the size of a submodel is simple to compute. Let$L(x)$ be the set of term labels of search term $x$ , with the variables of an interaction in sorted order, so that
x2:x1andx1:x2are the same label. Thenwhere the$1$ counts the intercept. The new $L(x)$ once per search, and for a search term with one label also its output string. Each step then needs only set operations:
search_term_info()computesCompound search terms such as
x1 + x2 + x1:x2still go through the formula code for their output string, but their size check uses the labels. If a search term has a smooth, group-level or offset term,search_term_info()returnsNULL, and the whole step runs as in master.test_select_terms.Rcompares both paths along random forward searches. For 30 search terms and 11 chosen terms, one step takes 0.9 ms instead of 18 ms.Results
All results are identical to master (difference 0, identical rankings), also for a model with a factor, an interaction and a
poly()term.varsel(), settings as above, 400 evaluation drawscv_varsel(validate_search = FALSE)varsel(d_test = )varsel(), N = 80These timings were measured before commit 3, which makes the search faster still. For large n the gain is smaller, because the projections take most of the time:
varsel(), n = 10 000, 400 evaluation drawsvarsel(), n = 1 000, 20 evaluation clustersTests
The new
test_select_terms.Rcomparesselect_possible_terms_size()with and withoutterm_infoalong random forward searches for three sets of search terms (compound terms with interactions, a factor,poly()andI()terms; apoly()term written without a space; 15 numeric terms). It also checks thatsearch_term_info()returnsNULLfor smooth and group-level terms.The new
test_formula_overhead.Rchecks that:validate_response_formula()gives the same formulas as oneupdate()per response, for seven formula types (interactions, a factor, an offset, group-level terms, a smooth term, intercept only);subprd_fitted()gives the same training-data predictions assubprd()for a model with a factor and an interaction and for intercept-only submodels, and returnsNULLif a stored model matrix has fewer rows than the data.The tests of
search_term_info()andsubprd_fitted()fail on master (functions not found); thevalidate_response_formula()test also passes on master, because the change keeps the results. The full suite passes withNOT_CRAN=true: 257 tests, 56 061 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).