Skip to content

Prevent shared-audio leakage across train/eval splits #10

Description

@s3d-i

Problem

Mapper v2.1 currently prevents window-level leakage by grouping windows from the same beatmap/chart into a single train/eval side, but it does not guarantee song/audio-level disjointness.

run_mapper_v2_1_phase_b_training() falls back to split_train_eval_dataset() when eval_index_path is not supplied. The split helper derives group identity via _map_identity_from_metadata(), which considers fields such as beatmap_path, map_path, beatmap_id, etc., but not audio_path / audio content identity.

This means different difficulties/charts that share the same source audio can be split across train and eval:

same audio/song
├── Easy.osu   -> train
├── Hard.osu   -> eval
└── Insane.osu -> train

For Mapper v2.1 this is especially problematic because include_full_song_context: true is used in the current training preset. The eval chart may therefore be unseen while its full audio has already appeared during training through another difficulty.

The current evaluation should consequently be interpreted as roughly unseen chart / potentially seen song, not a strict unseen song evaluation.

There is a second related gap when an explicit eval_index_path is supplied: train and eval datasets are constructed separately, with no overlap assertion that their beatmaps or audio identities are disjoint.

Relevant code

  • src/pulsefield_model/training/mapper_v2_1.py
  • src/pulsefield_model/training/common.py
    • split_train_eval_dataset()
    • _map_identity_index_groups()
    • _map_identity_from_metadata()
  • src/pulsefield_model/data/mapper_sparse_windows_v2_1.py
    • sample metadata already exposes audio_path
  • tests/training/test_common_split.py
    • currently tests whole-beatmap grouping only; fixtures give every beatmap a distinct audio path

Proposed behavior

Make the atomic split unit the underlying audio/song identity, so every chart/difficulty backed by the same audio is assigned entirely to either train or eval.

Preferred identity hierarchy:

  1. Stable audio content hash (e.g. SHA-256) stored in the dataset/index, if available.
  2. Canonicalized audio_path as a practical fallback.
  3. Only fall back to chart identity when no audio identity exists.

beatmap_set_id alone is weaker than audio identity: separate sets can theoretically contain identical audio, and a set may contain unusual layouts that should not define the actual leakage boundary.

Difficulty stratification can remain, but selection should operate on audio groups rather than individual beatmap groups.

Acceptance criteria

  • Default train/eval split has zero shared audio identities across train and eval.
  • All windows and all difficulties/charts associated with one audio identity stay on the same side.
  • Existing difficulty-stratified eval behavior is preserved as closely as possible at the audio-group level.
  • Add a regression test with at least two different beatmap_path values sharing the same audio_path; assert they cannot be split across train/eval.
  • Add a validation/assertion for explicit eval_index_path mode that detects shared audio identities between train and eval and fails loudly (or explicitly supports an opt-out if needed).
  • Training/eval reports record enough information to audit split integrity, ideally including unique train/eval audio counts and overlap count.

Why this matters

A strict audio-disjoint split is necessary if Mapper eval metrics are intended to measure generalization to genuinely unseen songs rather than only unseen chart annotations for songs the model has already heard.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions