Repository navigation
Prevent overlapping ERP epoch windows in chronological within-session CV - #1235
lindicaphxag-tech wants to merge 48 commits into
Conversation
|
I am not super supportive of this design, like, I feel that we can have the same thing just using the native split or implement some time split CV with stratification. The PR is also touch many extra sensitive areas of moabb. |
|
Thanks for the design feedback. I checked the native alternatives against issue #280: I agree the cross-cutting integration needs a clear scope boundary. Would you prefer this issue to cover only run-held-out evaluation (which leaves single-run recordings unresolved), or is the opt-in single-run time/purge protocol in scope? I am keeping the PR as draft until that design question is resolved. I also fixed the two failures from the first full CI run: MNE offsets event samples when concatenating runs, so the regression now compares timing within each run; and the cross-run purge test now uses valid non-overlapping epochs with an explicit cross-run overlap candidate. I moved metadata assertions back into their original test. On the new head |
|
Update on the new head: I merged current |
|
CI update for head |
|
I followed up on the stratified-time-split suggestion in commit |
|
CI has advanced on current head |
|
I’ve made the draft ready for review. The stratified-time suggestion is implemented with dynamic-programming boundary selection while keeping each test fold contiguous and purging exact sample overlap. The complete upstream matrix is green now: Ubuntu Python 3.11–3.14, macOS/Windows Python 3.12, docs, and the braindecode-dev job; pre-commit, link, changelog, and Codecov checks also pass. Event timing is opt-in, so the default paradigm metadata schema is unchanged. Please review the updated scope and let me know if you want a narrower integration. |
|
Can you do some visual, ala scikit learn, to represent exactly what this split is? I have the feeling that we are making a mistake here, but I may be wrong. |
|
Follow-up to the requested visualization: the review prompted a material correction to the scientific scope, which I have now made in the PR itself rather than hiding it in a comment. The 5×30 matrix correctly describes To avoid overclaiming, I updated the PR title/summary, API figure explanation, and Current new head: |
|
I took your concern about touching sensitive evaluation/paradigm code seriously and prepared an actual narrow alternative, rather than adding another general CI/status message. Two-file, internal fork draft (NOT a second upstream PR): on top of current I also found a scalability issue in the earlier Important unchanged scientific limit: independently purged raw epoch windows can still share information after continuous IIR filtering. This alternative makes no claim to fix fold-unaware preprocessing or the original issue end-to-end without later integration. It is intentionally a small, opt-in raw-index primitive. Scope decision for maintainers: Would you prefer (A) such a two-file standalone |
|
A concrete narrower implementation is available for your scope review: fork-only Draft PR #6 (not a competing upstream submission). Since the earlier version, I corrected a scientifically meaningful recording-identity issue: identical |
…r overflowing intervals in upstream review PR
…R, including translation and overflow
… two Python versions
…ding bot-head drift
… epoch overlap purging
Summary
Addresses the direct shared-raw-sample failure described in #280: an opt-in chronological within-session CV splitter that removes overlapping epoch extraction intervals from train/test folds. It does not guarantee post-filter signal independence.
Problem
Random trial-wise CV can put epochs that share samples from the same continuous recording into train and test. With a 500-sample epoch and 100-sample SOA, trial-disjoint folds are not signal-disjoint.
Approach
PurgedEpochKFold, a sklearn-compatible group-aware splitter;(subject, session, run)identity so reused run names cannot cause false cross-session/subject purges;event_sampleandepoch_n_samplesonly on demand, preserving the default paradigm metadata schema;WithinSessionEvaluation(..., cv_class=PurgedEpochKFold)is selected;n_purged_overlapandpurge_fractionas per-fold protocol diagnostics.Exact large-integer interval correctness (current head)
The original sample parser passed integer event/sample-window coordinates through
float64; above2**53that can collapse distinct integer sample indices and invalidate exact half-open overlap decisions.fb067c4preserves trueint64sample identity, refuses ambiguous large floating inputs, and prevents overflow when constructing interval stops.d0100fcadds exact translation-invariance, boundary and fail-closed regressions; pre-commit formatting45a35dcis the current source head.Independent exact-source native CI: Python 3.11/3.13 focused PurgedEpochKFold tests both SUCCESS. That workflow checks out the immutable
45a35dcSHA and runs the actualmoabb/tests/test_splits.py -k purged_epochtests, rather than duplicating the parser in a toy implementation. The official full test matrix on the current head runs separately; focused green tests do not replace full upstream acceptance.Reused run-label correctness
Run labels like
"0"are routinely reused across subjects or sessions. The original splitter grouped solely byrun, which could place independent recordings on the same sample axis and incorrectly remove training epochs that merely have overlapping numeric sample indices from different raw files. Source fixdfa052aencodes the available(subject, session, run)tuple without changing the three-column groups shape or default metadata. Regressioncfca859verifies independent sessions, subjects and refusal of missing recording identity. Current code is bot-formatted Head3c76a2c. Independent immutable-SHA validation passed both Python 3.11 and 3.13 on the realmoabb/tests/test_splits.py -k purged_epochregression subset. The upstream full cross-platform matrix and maintainer Review are separate gates.Minimal usage
No manual chronology labels are required; the evaluation requests event timing from the processed epoch stream.
Regression design
The synthetic witness uses 100-sample SOA and 500-sample epochs:
StratifiedKFoldhas train/test interval overlap;PurgedEpochKFoldhas zero same-run overlap;BaseParadigm.get_data()keeps its existingsubject/session/runmetadata by default and only adds timing when requested.Scope / limitation
Important preprocessing caveat discovered in review:
BaseParadigm.make_process_pipelines()applies the standard continuous-Raw IIR band-pass before cutting epochs. A zero-phase IIR filter has nonlocal temporal response, so disjoint extraction intervals do not prove that the resulting filtered signals contain no cross-boundary contribution. This PR prevents direct reuse of overlapping raw sample-index windows; it does not claim strict signal independence or eliminate all possible temporal leakage. A stricter protocol requires fold-aware preprocessing or independently justified transformation-specific support. A new synthetic regression (moabb/tests/test_purged_preprocessing_boundary.py) demonstrates this counterexample explicitly and preserves the narrower contract.The v1 contract assumes a fixed event-locked epoch window within an evaluated partition. The interval is represented on the processed epoch sample grid. It intentionally does not infer chronology from DataFrame row order and does not claim to model arbitrary time-warping post-processing. If a chronological partition cannot provide class support in every fold, the splitter fails closed rather than interleaving time blocks.
The review suggestion to use time-split CV with stratification is addressed by selecting near-equal contiguous boundaries with dynamic programming; temporal order remains intact and overlap is purged exactly. The previous full upstream matrix was green; the newly formatted exact-integer head has its own current checks and independent Python 3.11/3.13 source-pinned regression proof. Completion of the latest full matrix and maintainer review are still required. The four runtime files carry timing metadata only for this opt-in evaluation path; default metadata remains unchanged. This PR is ready for review.