fix(train): derive train/val split boundaries from one expression - #868
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughChangesDataset split pipeline
Possibly related PRs
🚥 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 |
Merge Protections🟢 Merge protection satisfied — ready to merge. Show 1 satisfied protection🟢 Require approval from approved reviewers listAll pull requests must have at least one approving review from a member of the approved reviewers list before merging.
|
|
The quality checks have failed. Please run |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@src/speculators/train/data.py`:
- Around line 212-230: Validate the split argument in the initializer before
calculating or selecting the slice, accepting only "train" and "val" and raising
a clear ValueError for any other value. Update the conditional assigning start
and stop so it relies on this validated input and preserves the existing
complementary partition behavior.
In `@tests/unit/train/test_split_boundaries.py`:
- Around line 30-36: Update the _split helper’s split parameter annotation to
use the same Literal["train", "val"] type as ArrowDataset.split, preserving the
existing train and validation call behavior.
🪄 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 Plus
Run ID: 1146c2fb-e3b7-4ce6-8910-4ca78564771d
📒 Files selected for processing (3)
src/speculators/train/data.pysrc/speculators/train/dataloader.pytests/unit/train/test_split_boundaries.py
9cc70d5 to
1f40845
Compare
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
tests/unit/train/test_split_boundaries.py (1)
36-47: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd tiny-dataset boundary coverage.
All parameterized datasets are large enough to keep the selected partitions non-empty. Add cases such as
n=1orn=2and document whether an empty partition should raise or be supported.🤖 Prompt for 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. In `@tests/unit/train/test_split_boundaries.py` around lines 36 - 47, Extend test_splits_are_exactly_complementary with tiny dataset sizes such as n=1 and n=2, and explicitly assert the intended behavior when the ratio produces an empty train or validation partition. Document through the test expectations whether empty partitions are supported or should raise, while preserving the existing complementary split assertions for valid non-empty cases.Source: Path instructions
🤖 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 `@src/speculators/train/data.py`:
- Around line 227-232: Define the empty-partition behavior in the split logic
around split_idx, start, stop, and self.data.select: either reject empty
selected ranges with a clear validation error or explicitly support them
consistently. Update src/speculators/train/data.py lines 227-232 accordingly,
and add small-dataset boundary cases in
tests/unit/train/test_split_boundaries.py lines 36-47 asserting the chosen
contract.
---
Nitpick comments:
In `@tests/unit/train/test_split_boundaries.py`:
- Around line 36-47: Extend test_splits_are_exactly_complementary with tiny
dataset sizes such as n=1 and n=2, and explicitly assert the intended behavior
when the ratio produces an empty train or validation partition. Document through
the test expectations whether empty partitions are supported or should raise,
while preserving the existing complementary split assertions for valid non-empty
cases.
🪄 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 Plus
Run ID: d67e30d1-b831-41cb-ba12-a5af73146ec8
📒 Files selected for processing (4)
src/speculators/train/data.pysrc/speculators/train/dataloader.pytests/unit/train/test_data.pytests/unit/train/test_split_boundaries.py
`ArrowDataset` encoded the split in the sign of `split_ratio`: the caller passed `train_data_ratio` for train and `train_data_ratio - 1.0` for val, and the val branch reconstructed the boundary as `int(n * (1.0 + split_ratio))`. That round trip is not exact in binary floating point, so the two branches could disagree on where the split falls. At `train_data_ratio=0.2` over 12501 rows, train ended at `int(12501 * 0.2) == 2500` while val started at `int(12501 * (1.0 + (0.2 - 1.0))) == int(12501 * 0.19999999999999996) == 2499`. Row 2499 therefore appeared in *both* splits, and the val split was one row larger than requested. The trainer's default 0.9 happens to agree, so the bug was latent, but any ratio can hit it and the failure is silent -- train/val leakage with no error. Take the split side explicitly (`train_ratio` plus `split="train"|"val"`) and compute the boundary once with a single expression, so the two splits are exactly complementary by construction. Also reject out-of-range ratios and a val split requested at `train_ratio=1.0`, which would silently be empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R1mQaSLBM1djDWD6herKZw Signed-off-by: Eldar Kurtic <8884008+eldarkurtic@users.noreply.github.com>
1f40845 to
c2efa18
Compare
## Summary - The `ArrowDataset` split logic computed the train boundary as `int(n * ratio)` and the val boundary as `int(n * (ratio - 1.0))`. These two expressions disagree in floating point at certain ratios — e.g. at `ratio=0.2` with `n=12501`: `int(12501 * 0.2) == 2500` but `int(12501 * -0.8) → int(12501 * 0.2000…3) == 2501` — putting **one row in both splits** (silent train/val leakage). - The fix replaces the `split_ratio: float` parameter with `train_ratio: float` + `split: Literal["train", "val"]`. Both splits derive their boundary from the single expression `int(len(data) * train_ratio)`, guaranteeing exact complementarity. Input validation rejects out-of-range ratios and val splits at `train_ratio=1.0`. ## Test plan - [x] 34 unit tests (`test_split_boundaries.py`): exact complementarity across 10 ratios (including 0.2 which was broken), no row in both splits, union covers full dataset, default takes whole dataset, rejects out-of-range, rejects val at ratio 1.0 - [x] Verified the 0.2 ratio case produces off-by-one on unfixed code, exact complement on fixed code 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Eldar Kurtic <8884008+eldarkurtic@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Roderick-Wu <rowu@redhat.com>
Summary
The
ArrowDatasetsplit logic computed the train boundary asint(n * ratio)and the val boundary asint(n * (ratio - 1.0)). These two expressions disagree in floating point at certain ratios — e.g. atratio=0.2withn=12501:int(12501 * 0.2) == 2500butint(12501 * -0.8) → int(12501 * 0.2000…3) == 2501— putting one row in both splits (silent train/val leakage).The fix replaces the
split_ratio: floatparameter withtrain_ratio: float+split: Literal["train", "val"]. Both splits derive their boundary from the single expressionint(len(data) * train_ratio), guaranteeing exact complementarity. Input validation rejects out-of-range ratios and val splits attrain_ratio=1.0.Test plan
test_split_boundaries.py): exact complementarity across 10 ratios (including 0.2 which was broken), no row in both splits, union covers full dataset, default takes whole dataset, rejects out-of-range, rejects val at ratio 1.0🤖 Generated with Claude Code