Spaces:
Running on Zero
Running on Zero
| """Drummer-disjoint re-partitioning for the robustness experiment (Plan D). | |
| The official E-GMD split shares every drummer across train/val/test, so its | |
| metrics measure *in-distribution* performance. To test generalization to an | |
| unseen player, this re-partitions the already-featurized rows so that the | |
| held-out drummers form the test split and never appear in train/val. | |
| Torch-free (pandas only) — safe to import without triggering the lazy torch | |
| imports in ``drum_dynamics/__init__``. | |
| """ | |
| from __future__ import annotations | |
| from typing import Iterable | |
| import numpy as np | |
| import pandas as pd | |
| def build_drummer_holdout(frames: Iterable[pd.DataFrame], holdout_drummers, | |
| val_frac: float = 0.1, seed: int = 42): | |
| """Re-partition featurized frames into a drummer-disjoint split. | |
| Parameters | |
| ---------- | |
| frames : iterable of DataFrames | |
| The original (train/val/test) featurized frames to pool. Each must | |
| carry ``file_id``, ``drummer`` and ``genre`` columns. | |
| holdout_drummers : set-like | |
| Drummer ids that become the *test* split. Must not be the unique | |
| source of any genre, or that genre would vanish from training. | |
| val_frac : float | |
| Fraction of the remaining *files* (per genre) to route to validation. | |
| seed : int | |
| Seed for the deterministic per-genre file shuffle. | |
| Returns | |
| ------- | |
| (train_df, val_df, test_df) : tuple of DataFrames | |
| A partition of the pooled rows. Splitting is by ``file_id`` so no file | |
| leaks across splits; per-genre stratification keeps every genre present | |
| in train (a genre's single file is never sent to val). | |
| """ | |
| df = pd.concat(list(frames), ignore_index=True) | |
| hold = set(holdout_drummers) | |
| test = df[df["drummer"].isin(hold)].copy() | |
| rest = df[~df["drummer"].isin(hold)].copy() | |
| files = rest[["file_id", "genre"]].drop_duplicates("file_id") | |
| rng = np.random.RandomState(seed) | |
| val_ids: set = set() | |
| for _, sub in files.groupby("genre"): | |
| ids = sub["file_id"].to_numpy() | |
| ids = ids[rng.permutation(len(ids))] # deterministic shuffle | |
| # keep >=1 file per genre in train: only split when >1 file exists | |
| n_val = int(round(len(ids) * val_frac)) if len(ids) > 1 else 0 | |
| n_val = min(n_val, len(ids) - 1) # never empty a genre from train | |
| val_ids.update(ids[:n_val].tolist()) | |
| is_val = rest["file_id"].isin(val_ids) | |
| val = rest[is_val].copy() | |
| train = rest[~is_val].copy() | |
| return train, val, test | |