Correct the held-out slice scale, and hold out half of each mode - #552
Merged
Conversation
fishidaho
added this pull request to stack #553
September 12, 2026 00:58
aarmey
force-pushed
the
fix/bicv-holdout-scaling
branch
from
September 12, 2026 01:17
de71f7a to
3a68802
Compare
PARAFAC2 writes slice k as `P_k (B diag(a_k)) C^T` with `P_k` orthonormal, so the reconstruction's norm is `||(B diag(a_k)) C^T||` regardless of how many cells the slice holds: all of a slice's energy sits in `a_k`. BiCV fit `a_k` against the training cells and then reused it unchanged for the held-out cells, and since a slice's energy grows like sqrt(n_k) that overstated the predicted block by sqrt(n_train_k / n_test_k) -- a factor of two at the old default split. The symptom was a metric that got worse as the model got better. On noiseless, exactly-rank-4 data fit at the true rank, the training block scored 0.9996 while the held-out block scored -0.034: the fit had recovered the structure exactly and the score still called it worse than predicting the mean. On the IBD cohort the curve was negative at every rank and fell monotonically as rank rose, putting its argmax at rank 1 and firing the "rank at the edge of tested ranks" warning. The error was purely one of magnitude -- a single global rescale of the prediction recovered R^2 = 0.970. `_holdout_scale` corrects each condition by sqrt(n_test_k / n_train_k), which needs only the cell counts and is exact when the two halves of a condition have comparable per-cell energy -- true here, since the split is random within each condition. Three candidates were measured (this one, re-fitting `a_k` per held-out slice from the train genes, and unconstrained least-squares cell loadings); all three fixed the bug and none scored above zero on pure noise, so the narrowest was taken. Both held-out fractions now default to one half, following Owen and Perry (https://arxiv.org/abs/0908.2062), who report that "in simulated examples we find that a method leaving out half the rows and half the columns performs well". That is also the one split at which the old score happened to be unbiased, so it is the setting under which older numbers remain comparable, and it recovers the true rank in every simulated scenario tested -- which the correction alone did not at 0.2. Measured after the change: noiseless rank-4 data at its own rank scores 0.97-0.99 at every fraction (was -0.034); the spread across fractions 0.1-0.7 falls from 3.75 to 0.02; argmax lands on the true rank for (4, noise 0.3), (6, noise 0.3) and (4, noise 1.0); pure noise still scores negative. Row-order invariance from the alignment fix underneath is undisturbed, agreeing to 8.9e-16 between contiguous and fully interleaved conditions. The dense reference in the tests derives the same factor independently from the counts, so it still pins the streaming algebra rather than the old behaviour. The tutorial's "peaks" claim is softened to describe the plateau the curve actually forms, since past the plateau neighbouring ranks differ only in the fourth decimal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VJRB1RMUAb1id11XZ6KWS
aarmey
force-pushed
the
fix/bicv-holdout-scaling
branch
from
September 12, 2026 01:20
3a68802 to
38ddb41
Compare
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.
PARAFAC2 models slice
kasX_k ≈ P_k (B diag(a_k)) Cᵗ, whereP_khas orthonormal columns (P_kᵗ P_k = I). BecauseP_kis orthonormal irrespective of how many cells the slice holds, the reconstruction's normis independent of
n_k. All slice energy lives ina_k.a_kis fitted againstn_train_kcells, then applied unchanged to then_test_kheld-out cells. Since real slice energy grows likesqrt(n_k), the held-out block's magnitude is overstated bysqrt(n_train_k / n_test_k)— 2× at the defaultheld_out_cell_frac=0.2.Noiseless, exactly-rank-4 data, fit at the true rank 4:
Train Block R2X(in-sample)BiCV R2X(held-out)The direction is right; only the magnitude is wrong. And the factor tracks the cell-count ratio exactly — noiseless rank-4 data, varying only
held_out_cell_frac:held_out_fracsqrt(ratio)The score is correct only at
held_out_cell_frac = 0.5, where the ratio is 1.The fix
Simple 6 line fix to multiply each condition's held-out loadings by
sqrt(n_test_k / n_train_k). Keeps the orthonormal convention and corrects the known factor. Assumes per-cell energy is homogeneous within a condition which should be true under BiCV's random split.It is also important to note that both held-out fractions now default to one half which follows the [Owen and Perry] (https://arxiv.org/abs/0908.2062) paper this whole method is inspired by. They reported even in their abstract that "in simulated examples we find that a method leaving out half the rows and half the columns performs well". That is also the one split at which the old score happened to be unbiased, so it is the setting under which older numbers remain comparable, and it recovers the true rank in every simulated scenario tested -- which the correction alone did not at 0.2.
Measured after the change: noiseless rank-4 data at its own rank scores 0.97-0.99 at every fraction (was -0.034); the spread across fractions 0.1-0.7 falls from 3.75 to 0.02; argmax lands on the true rank for (4, noise 0.3), (6, noise 0.3) and (4, noise 1.0); pure noise still scores negative. Row-order invariance from the alignment fix underneath is undisturbed, agreeing to 8.9e-16 between contiguous and fully interleaved conditions.