Blend out-of-fold predictions only over members that predicted each row - #57
Blend out-of-fold predictions only over members that predicted each row#57fus3r wants to merge 1 commit into
Conversation
821feb2 to
7458864
Compare
|
Hi, just following up on this PR. Is there anything you'd like me to add or change before review? |
When max_num_rows is set, each ensemble member subsamples different rows, so in predict_oof_proba a member only has out-of-fold predictions for its own rows and the rest of its output stays zero. fit() averaged all members together for the calibration input, zeros and all, so rows summed to k / n_estimators instead of 1 and the calibrator was fit on vectors that aren't valid probabilities. It only happens with max_num_rows. predict_oof_proba now records which rows each member predicted (oof_valid_mask_). fit() drops rows no member predicted and averages each remaining row over just the members that predicted it. Without max_num_rows every member predicts every row, so nothing changes. enable_nnls can't be combined with max_num_rows, so the NNLS blend never sees this and is left as is.
7458864 to
0f7c689
Compare
|
Thank you for the contribution and apologies for the delay in reviewing. |
|
Thanks for the update, and no worries about the delay. The hold-out approach sounds like the cleaner way to handle it. Happy to try the fix when it lands! |
|
Sorry for the long delay, but the hold-out set fix is now merged (PR #85). Let us know if you have any more issues. Thank you for your patience. |
Fixes #55.
The bug
When
max_num_rowsis set, each ensemble member subsamples a different set of rows. So inpredict_oof_probaa member only produces out-of-fold predictions for its own rows, and the rest of its output stays zero.fit()builds the calibration input by averaging over all members, zeros included. That makes rows sum tok / n_estimators(k = how many members actually predicted the row) instead of 1, so the calibrator is fit on vectors that aren't valid probabilities. You only hit it withmax_num_rows, which is the large-data case.The single-split path made it worse: it kept only member 0's validation indices and sliced every member with them. The multi-fold path averaged over all N rows, including ones a member never sampled.
enable_nnlscan't be combined withmax_num_rows(the constructor rejects it), so the NNLS blend never runs into this and I left it as is.Repro on main
Random weights, PyTorch, no checkpoint needed:
On
mainmany of those sums come out as 0.333 or 0.667. With the fix they are all 1.0.Fix
predict_oof_probanow stores which rows each member predicted (oof_valid_mask_).fit()drops rows that no member predicted, and for the rest it averages each row over just the members that predicted it. Withoutmax_num_rowsevery member predicts every row, the mask is all True, and the result is unchanged.Tests
Two tests added: one checks the mask matches each member's predicted rows under subsampling, the other checks the calibration input equals the per-row mean over the predicting members (with a never-predicted row dropped and the labels realigned).
pytest tabfm/src/is green (74 tests).