Download vanta/data/dynamic_dataset.py from rjattt/echofocus: direct link, hf CLI and curl.
- Browser
- Download file 3.3 kB
-
https://huggingface.co/spaces/rjattt/echofocus/resolve/main/vanta/data/dynamic_dataset.py
- Command line
-
hf download hf://spaces/rjattt/echofocus/vanta/data/dynamic_dataset.py
-
curl -L -o dynamic_dataset.py https://huggingface.co/spaces/rjattt/echofocus/resolve/main/vanta/data/dynamic_dataset.py
3.3 kB
| """On-the-fly mixing dataset — the fix for the memorization problem. | |
| The old pipeline froze 20k mixtures to disk (build_dataset.py) and trained on | |
| that same fixed set every epoch, so the model memorized it (train SI-SDR climbed | |
| while val stayed pinned at ~+1 dB). This dataset instead calls the synthesis | |
| engine *live*: every __getitem__ produces a brand-new random mixture, so the | |
| model never sees the same example twice and has to learn to separate voices | |
| rather than memorize files. | |
| Only the *training* set should be dynamic. Keep validation on a fixed manifest | |
| (VantaDataset) so val numbers are comparable across runs. | |
| Freshness across epochs: | |
| We reseed the per-process mixer from torch.initial_seed(), which PyTorch | |
| varies per (epoch, worker). With num_workers=0 the mixer is created once and | |
| its RNG simply keeps advancing across epochs — either way, no epoch ever | |
| repeats the previous epoch's mixtures. | |
| """ | |
| from __future__ import annotations | |
| import torch | |
| from torch.utils.data import Dataset, get_worker_info | |
| from vanta.data.indexer import NoiseIndex, RirIndex, SpeakerIndex | |
| from vanta.data.synthesize import MixConfig, Mixer | |
| class DynamicMixDataset(Dataset): | |
| def __init__( | |
| self, | |
| mix_cfg: MixConfig, | |
| speakers: SpeakerIndex, | |
| noise: NoiseIndex | None, | |
| rirs: RirIndex | None, | |
| epoch_size: int, | |
| base_seed: int = 0, | |
| ): | |
| self.mix_cfg = mix_cfg | |
| self.speakers = speakers | |
| self.noise = noise | |
| self.rirs = rirs | |
| self.epoch_size = int(epoch_size) | |
| self.base_seed = int(base_seed) | |
| # Lazily built per process so each DataLoader worker gets its own RNG. | |
| self._mixer: Mixer | None = None | |
| def __len__(self) -> int: | |
| # Nominal length = how many fresh mixtures make up one "epoch". Purely a | |
| # cadence knob (when to validate / checkpoint); the data is unbounded. | |
| return self.epoch_size | |
| def _mixer_for_process(self) -> Mixer: | |
| if self._mixer is None: | |
| info = get_worker_info() | |
| if info is None: | |
| # Main process (num_workers=0): seed once; the RNG advances | |
| # across epochs because this object persists, so data stays fresh. | |
| seed = self.base_seed | |
| else: | |
| # Worker process: torch.initial_seed() is set by PyTorch per | |
| # (epoch, worker), so reseeding from it gives fresh data every | |
| # epoch even when workers are torn down and recreated. | |
| seed = torch.initial_seed() % (2**31) | |
| self._mixer = Mixer( | |
| self.mix_cfg, | |
| speakers=self.speakers, | |
| noise=self.noise, | |
| rirs=self.rirs, | |
| seed=seed, | |
| ) | |
| return self._mixer | |
| def __getitem__(self, idx: int) -> dict: | |
| # idx is ignored — every call is a new random draw. | |
| r = self._mixer_for_process().mix() | |
| return { | |
| "mixture": torch.from_numpy(r.mixture), | |
| "target": torch.from_numpy(r.target), | |
| "enrollment": torch.from_numpy(r.enrollment), | |
| "target_speaker": r.meta["target_speaker"], | |
| } | |