Fix 1/N floor in effective-rank collapse score after mean-centering - #8997
Conversation
…ering _effective_rank_score mean-centers the embedding matrix, which caps the attainable rank at min(N-1, D), but normalized by min(N, D). That imposed a hard 1/N floor on the score whenever N <= D: isotropic embeddings at N=4, D=768 scored 0.2503 and two maximally-spread points scored exactly 0.50, where the correct answer is ~0. _per_class_rank is affected more, since per-class N is smaller and the floor is therefore larger. The fix is a no-op for N > D, where both expressions equal D. Also updates test_formula_matches_manual, which mirrored the old normalization inline. Its fixture is N=20 > D=16, so it passes either way, but it would otherwise encode the old formula. Signed-off-by: rubenuni1009 <183279777+rubenG1009@users.noreply.github.com>
📝 WalkthroughWalkthroughThe effective-rank collapse score now normalizes by Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/metrics/test_embedding_collapse.py`:
- Around line 231-251: Replace the inline comments in
test_effective_rank_no_collapse_when_n_leq_d,
test_effective_rank_two_distinct_samples_report_no_collapse, and
test_effective_rank_unchanged_when_n_gt_d with Google-style method docstrings
describing the test inputs, expected score behavior, return value, and any
raised exceptions as applicable. Preserve the existing test logic and
assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: de1aab56-425f-4a98-9a56-cfe1a5dd5bd2
📒 Files selected for processing (2)
monai/metrics/embedding_collapse.pytests/metrics/test_embedding_collapse.py
vikashg
left a comment
There was a problem hiding this comment.
Mathematically correct fix. Mean-centering introduces one linear dependency (rows sum to 0), so rank(centered) \u2264 min(N-1, D) \u2014 normalizing by min(N, D) was imposing a spurious 1/N floor. The tests clearly demonstrate the bug (0.25 and 0.50 for isotropic inputs) and the fix. No-op for N > D confirmed. LGTM.
vikashg
left a comment
There was a problem hiding this comment.
Mathematically correct fix. Mean-centering introduces one linear dependency (rows sum to 0), so rank(centered) \u2264 min(N-1, D) \u2014 normalizing by min(N, D) was imposing a spurious 1/N floor. The tests clearly demonstrate the bug (0.25 and 0.50 for isotropic inputs) and the fix. No-op for N > D confirmed. LGTM.
Fixes #8996
Description
_effective_rank_scoremean-centers the embedding matrix (L290), which caps its attainable rank atmin(N-1, D), but normalized the effective rank bymin(N, D)(L298).Since the score is
1 - eff_rank/max_rank, that imposed a hard floor of1/NwheneverN <= D: isotropic embeddings at N=4, D=768 scored 0.2503, and two maximally-spread points scored exactly 0.50 ("50% collapsed"), where the correct answer is ~0. The metric could not report "no collapse" in precisely the regime it was designed for._per_class_rankis affected more, since per-class N is smaller and the floor is therefore larger.The observed floor matches
1 - (N-1)/N = 1/Nexactly.Why
min(N-1, D)Mean-centering forces the rows to sum to zero, which is a linear dependency: if you know N-1 rows, the last one is determined. So
rank(centered) <= min(N-1, D). Normalizing by the attainable maximum removes the artifact.The fix is a no-op for
N > D, wheremin(N-1, D)andmin(N, D)both equalD. Verified: a 1024x768 matrix scores 0.1234 before and after.Tests
Three tests added to
TestEffectiveRankScore, all RED ondevand GREEN after the fix (except the third, which is a regression guard and passes both ways):test_effective_rank_no_collapse_when_n_leq_d- 4x768 isotropic: 0.2503 -> ~0.0004test_effective_rank_two_distinct_samples_report_no_collapse- 2x768: 0.5 -> 0.0test_effective_rank_unchanged_when_n_gt_d- 1024x768 unchanged at 0.1234 (proves no regression)Full suite: 48 passed, 2 skipped (sklearn not installed locally).
Note on the modified test
test_formula_matches_manualmirrored the old normalization inline (min(20, 16)). Its fixture is N=20 > D=16, so it passes either way, but it would otherwise encode the old formula. Updated tomin(20 - 1, 16)- flagging it explicitly rather than changing an assertion silently.Types of changes
./runtests.sh -f -u --net --coverage../runtests.sh --quick --unittests --disttests.