Spaces:
Running on Zero
Running on Zero
| """ | |
| Create a reproducible, stratified development subset of | |
| pipecat-ai/smart-turn-data-v3.2-train, WITHOUT downloading/materializing | |
| the full 41GB dataset. | |
| Status: this script is complete and ready to run, but has NOT been executed | |
| in this sandbox — `datasets`/`huggingface_hub` aren't installed here and | |
| `bash` has no route to huggingface.co (confirmed 403 host_not_allowed via | |
| curl; see docs/INITIAL_ANALYSIS.md and experiments/EXPERIMENTS.md). Run this | |
| in an environment with network access and `pip install datasets`. | |
| Usage: | |
| python scripts/create_dev_subset.py \ | |
| --target-n 10000 \ | |
| --seed 42 \ | |
| --max-scan 200000 \ | |
| --out data/processed/dev_subset_manifest.jsonl | |
| Methodology (also written into the output manifest's header for | |
| reproducibility): | |
| 1. Stream the dataset (`streaming=True`) so we never download the full | |
| 41GB just to build a 10k-example subset. | |
| 2. Compute per-clip `duration_sec` and `duration_bucket` from the audio | |
| array's length/sample rate as each streamed record is visited (cheap — | |
| doesn't require decoding beyond what `datasets` already does to expose | |
| the array). | |
| 3. Apply stratified reservoir sampling (src/turn_detector/data.py) across | |
| endpoint_bool x language x dataset(source) x synthetic x midfiller x | |
| endfiller x duration_bucket, using a two-pass approach over the first | |
| `--max-scan` streamed records (pass 1: count stratum sizes; pass 2: | |
| per-stratum reservoir sampling). This means `--max-scan` records are | |
| scanned twice; the dataset itself is never materialized beyond that. | |
| 4. The chosen `target_n` records' `id` values (not the audio itself) are | |
| written to the manifest, plus the exact sampling parameters, so the | |
| subset is exactly reproducible from the manifest + original dataset. | |
| 5. The test set (pipecat-ai/smart-turn-data-v3.2-test) is never touched | |
| by this script — it exists only to build a TRAIN-side dev subset. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) | |
| from turn_detector.data import ( | |
| TRAIN_DATASET_ID, load_hf_dataset, stratified_reservoir_sample, | |
| duration_bucket, DevSubsetManifest, DatasetAccessError, | |
| ) | |
| def record_to_stratum_record(record: dict) -> dict: | |
| """Extract only the lightweight metadata fields needed for | |
| stratification + the id needed to re-fetch the audio later — never | |
| keeps the decoded audio array in memory longer than needed to compute | |
| duration. | |
| """ | |
| audio = record.get("audio") | |
| duration_sec = None | |
| if isinstance(audio, dict) and "array" in audio and "sampling_rate" in audio: | |
| duration_sec = len(audio["array"]) / audio["sampling_rate"] | |
| return { | |
| "id": record.get("id"), | |
| "endpoint_bool": record.get("endpoint_bool"), | |
| "language": record.get("language"), | |
| "dataset": record.get("dataset"), | |
| "synthetic": record.get("synthetic"), | |
| "midfiller": record.get("midfiller"), | |
| "endfiller": record.get("endfiller"), | |
| "duration_sec": duration_sec, | |
| } | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--target-n", type=int, default=10_000) | |
| ap.add_argument("--seed", type=int, default=42) | |
| ap.add_argument( | |
| "--max-scan", type=int, default=None, | |
| help="Cap on records scanned per pass for stratum-size counting " | |
| "and reservoir filling. Default: scan the whole stream (safest for " | |
| "correctness, slowest for a 270k-row dataset). Set e.g. 200000 to " | |
| "bound runtime at some accuracy cost in stratum proportion " | |
| "accuracy for very rare strata.", | |
| ) | |
| ap.add_argument("--out", type=Path, default=Path("data/processed/dev_subset_manifest.jsonl")) | |
| args = ap.parse_args() | |
| try: | |
| ds = load_hf_dataset(TRAIN_DATASET_ID, split="train", streaming=True) | |
| except DatasetAccessError as e: | |
| print(f"ERROR: could not load dataset: {e}", file=sys.stderr) | |
| print( | |
| "This script cannot run without network access to Hugging Face. " | |
| "See docs/INITIAL_ANALYSIS.md for the current sandbox limitation.", | |
| file=sys.stderr, | |
| ) | |
| sys.exit(1) | |
| # Two lightweight passes over the (streamed) dataset. Streaming | |
| # datasets support repeated iteration by re-creating the iterator; we | |
| # rely on `stratified_reservoir_sample`'s two-`for record in records` | |
| # loops, so we materialize just the lightweight metadata (not audio) | |
| # into a list first — this is a deliberate, documented tradeoff: it | |
| # means `--max-scan` records of *metadata only* are held in memory | |
| # (small), while full audio arrays are never retained beyond a single | |
| # record's processing. | |
| print(f"Scanning up to {args.max_scan or 'ALL'} records for stratification metadata...") | |
| stratum_records = [] | |
| for i, record in enumerate(ds): | |
| stratum_records.append(record_to_stratum_record(record)) | |
| if args.max_scan is not None and i + 1 >= args.max_scan: | |
| break | |
| if (i + 1) % 10_000 == 0: | |
| print(f" scanned {i + 1} records...") | |
| print(f"Scanned {len(stratum_records)} records. Running stratified sampling...") | |
| sample = stratified_reservoir_sample( | |
| stratum_records, target_n=args.target_n, seed=args.seed, max_scan=None, | |
| ) | |
| manifest = DevSubsetManifest( | |
| source_dataset_id=TRAIN_DATASET_ID, | |
| source_split="train", | |
| target_n=args.target_n, | |
| actual_n=len(sample), | |
| seed=args.seed, | |
| stratify_columns=("endpoint_bool", "language", "dataset", "synthetic", "midfiller", "endfiller", "duration_bucket"), | |
| max_scan=args.max_scan, | |
| ) | |
| args.out.parent.mkdir(parents=True, exist_ok=True) | |
| with open(args.out, "w") as f: | |
| f.write(json.dumps({"_manifest": manifest.to_dict()}) + "\n") | |
| for r in sample: | |
| f.write(json.dumps(r) + "\n") | |
| print(f"Wrote {len(sample)} records + manifest header to {args.out}") | |
| if __name__ == "__main__": | |
| main() | |