Repository navigation
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #412 +/- ##
==========================================
- Coverage 92.48% 92.47% -0.01%
==========================================
Files 31 31
Lines 3139 3191 +52
==========================================
+ Hits 2903 2951 +48
- Misses 236 240 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
|
This is how benchmark results would change (along with a 95% confidence interval in relative change) if 07446db is merged into master:
|
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.
Fixes #119.
This PR was assisted by Claude.
Summary
stacking_weights()optimized the weights withstats::constrOptim(), a log-barrier method with an inner BFGS. It is slow for many models, as #119 reports (more than 10 minutes for 10 000 models). It also often stops far from the optimum:constrOptim()reports convergence after 2 or 3 outer iterations, and the barrier term keeps the weights away from zero. loo gave no warning.This PR finds the weights with an active-set Newton method that uses only base R and matrixStats.
@yao-yl @jgabry, the stacking weights change for many inputs, so I would like your review.
How far master was from the optimum
For stacking with fixed predictive densities, a bound shows how far any weights are from the optimum. With
p_i = sum_k w_k exp(lpd_ik)andg_k = mean_i exp(lpd_ik) / p_i, Jensen's inequality gives that the log score ofwis at mostN * log(max_k g_k)below its maximum. At the optimum,g_k = 1for every model with positive weight andg_k <= 1for the others (Lindsay, 1983). The same bound appears for log-optimal portfolios (Cover, 1984), which is the same optimization problem.I simulated pointwise log predictive densities for N observations and K normal models, 50 data sets per cell, and computed this bound for the master weights:
The gap is in units of the log score. A weight difference near 1 means that master put the weight on a different model than the optimum. Larger iteration limits for
constrOptim()reduce the gap but do not remove it.The old code could also give a non-zero weight to a clearly worse model. The optimization starts from equal weights, and when it stops early, models whose optimal weight is zero keep part of their starting weight. With 10 models, master gave more than 0.01 weight to a model with optimal weight zero in 54 of 100 simulated data sets for N = 100, and in 70 of 100 for N = 1000. In 27 and 57 of these, that model's elpd was more than 2 SE below the best model, and the total weight on such models was up to 0.89. With 3 or 5 models this happened in 2 of 400 data sets.
Method
Stacking with fixed predictive densities is the maximum likelihood estimate of mixture proportions with fixed components.
stacking_weights()now calls the internalstacking_weights_active_set(), a variant of the constrained Newton method (CNM) of Wang (2007):g_kfor all models. Stop whenmax_k log(g_k)is at most1e-8, so the log score is at mostN * 1e-8below its maximum. Otherwise add the model with the largestg_kand go to step 2.CNM adds all local maxima of the gradient function at each iteration and solves the quadratic approximation with a least-squares formulation. This implementation adds one model per iteration and solves the KKT system directly, which is enough here because the optimal weights are usually sparse. Each check over all models costs one pass over the
N x Kmatrix.Over 1800 simulated data sets (N from 20 to 1000, K from 2 to 50), the largest gap bound with this PR is 9.3e-7. Two other approaches were not good enough. A softmax parametrization with
optim(method = "L-BFGS-B")is fast but stalls with gaps from 0.1 to 3, because the gradient of a weight is proportional to the weight. EM for mixture weights (Cover's multiplicative update), also with SQUAREM acceleration, reaches the optimum but took more than 60 s for 1000 models.?loo_model_weightsdescribes the method in Details and cites Wang (2007) and Lindsay (1983).References:
API change
optim_methodandoptim_controlofstacking_weights()andloo_model_weights()no longer have an effect. Their defaults are nowNULL. If a user supplies them, a warning says that they are deprecated and ignored. They can be removed in a later release.The stacking weights change for many inputs, also with 3 models, because the new weights are at the optimum. For the 3-model test data in
test_model_weighting.R, the weights change from 0.203658, 0.796342, 3.6e-8 (gap bound 3.8e-4) to 0.203608, 0.796392, 0 (gap bound 2.4e-13). Please add a NEWS entry; I suggest a text below.exp_diff_over_exp()inR/helpers.Rcomputed the gradient of the old objective and has no caller after this change. This PR removes it and its tests.Tests
tests/testthat/test_model_weighting.R:stacking_weights gives expected resultcompared with values fromconstrOptim(). It now checks the gap bound and compares with an EM solution of the same data.N * 1e-8and sparse weights.-Infentries), a duplicated model, and identical models.stacking_weights()andloo_model_weights(), and the same weights with and without the deprecated arguments.With the master
R/loo_model_weights.R, the new and changed tests give 10 failed expectations. With this PR,devtools::test()withNOT_CRAN=truepasses all 1269 tests and gives no warnings (R 4.6.1, testthat 3.3.2, matrixStats 1.5.0, Linux x86_64).