Spaces:
Running on Zero
Running on Zero
File size: 6,127 Bytes
875e4af | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | """
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()
|