WiggleGPT / task_finetuning /prepare_data.py
edeneldith's picture
Add task-specific WiggleGPT checkpoints and results
5b39202 verified
Raw History Blame Contribute Delete
5.88 kB
"""Download public benchmark parquets and preserve exact normalized task splits.
This script performs no model work. Public SST-2 test labels are withheld (-1);
the validation split is therefore the labelled held-out evaluation split.
"""
from __future__ import annotations
import hashlib
import json
import time
import urllib.parse
import urllib.request
from collections import Counter
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timezone
from pathlib import Path
import pyarrow as pa
import pyarrow.parquet as pq
ROOT = Path(__file__).resolve().parent / "data"
SOURCES = [("sst2", "stanfordnlp/sst2", "default"), ("race_middle", "ehovy/race", "middle")]
def digest(path: Path) -> str:
value = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
value.update(block)
return value.hexdigest()
def fetch(url: str, path: Path) -> None:
if path.exists():
return
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_suffix(path.suffix + ".partial")
for attempt in range(4):
try:
request = urllib.request.Request(url, headers={"User-Agent": "WiggleGPT-benchmark-preparation/1.0"})
with urllib.request.urlopen(request, timeout=90) as response, temporary.open("wb") as stream:
while block := response.read(1024 * 1024):
stream.write(block)
temporary.replace(path)
return
except Exception:
if attempt == 3:
raise
time.sleep(2 ** attempt)
def normalize(task: str, row: dict, index: int, split: str) -> dict:
if task == "sst2":
return {"example_id": f"sst2/{split}/{row.get('idx', index)}", "text": row["sentence"], "label": int(row["label"])}
answer = row["answer"]
label = "ABCD".index(answer) if isinstance(answer, str) else int(answer)
options = list(row["options"])
if len(options) != 4 or not 0 <= label < 4:
raise ValueError(f"Invalid RACE record {index}")
return {"example_id": str(row.get("example_id", f"race_middle/{split}/{index}")),
"article": row["article"], "question": row["question"], "options": options, "label": label}
def prepare_source(spec: tuple[str, str, str]) -> dict:
task, dataset, config = spec
metadata_url = "https://datasets-server.huggingface.co/parquet?" + urllib.parse.urlencode({"dataset": dataset})
metadata_path = ROOT / "source_parquets" / task / "parquet_metadata.json"
fetch(metadata_url, metadata_path)
metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
if metadata.get("partial"):
raise RuntimeError(f"Partial parquet conversion for {dataset}")
files = [entry for entry in metadata["parquet_files"] if entry["config"] == config]
result = {"dataset": dataset, "config": config, "metadata_url": metadata_url, "splits": {}}
for split in ("train", "validation", "test"):
split_files = sorted([entry for entry in files if entry["split"] == split], key=lambda entry: entry["filename"])
if not split_files:
raise RuntimeError(f"Missing {dataset} {config} {split}")
destination = ROOT / f"{task}_{split}.jsonl"
temporary = destination.with_suffix(".jsonl.partial")
count, labels, source_files = 0, Counter(), []
with temporary.open("w", encoding="utf-8", newline="\n") as output:
for ordinal, entry in enumerate(split_files):
parquet_path = ROOT / "source_parquets" / task / split / f"{ordinal:03d}_{entry['filename']}"
fetch(entry["url"], parquet_path)
if parquet_path.stat().st_size != entry["size"]:
raise RuntimeError(f"Size mismatch for {parquet_path}")
source_files.append({"url": entry["url"], "path": str(parquet_path), "bytes": parquet_path.stat().st_size, "sha256": digest(parquet_path)})
for batch in pq.ParquetFile(parquet_path).iter_batches(batch_size=1024):
for row in batch.to_pylist():
item = normalize(task, row, count, split)
output.write(json.dumps(item, ensure_ascii=False) + "\n")
labels[str(item["label"])] += 1
count += 1
temporary.replace(destination)
result["splits"][split] = {"path": str(destination), "rows": count, "sha256": digest(destination), "label_counts": dict(sorted(labels.items())), "labelled": all(int(label) >= 0 for label in labels), "source_parquets": source_files}
print(f"{task} {split}: {count:,} rows; labels={dict(labels)}", flush=True)
return result
def main() -> None:
ROOT.mkdir(parents=True, exist_ok=True)
pa.set_cpu_count(2)
pa.set_io_thread_count(2)
with ThreadPoolExecutor(max_workers=2) as pool:
prepared = list(pool.map(prepare_source, SOURCES))
manifest = {"prepared_at_utc": datetime.now(timezone.utc).isoformat(), "tasks": {spec[0]: result for spec, result in zip(SOURCES, prepared)},
"split_policy": {"sst2": "Train on official train; validation is the only public labelled holdout. Official test labels are hidden (-1).",
"race_middle": "Train on official middle/train; select using middle/validation; evaluate once on middle/test."},
"normalization": "Exact input strings retained; sentiment labels are 0=negative,1=positive; RACE A/B/C/D mapped to integer 0/1/2/3; no examples generated or reassigned."}
manifest_path = ROOT / "dataset_manifest.json"
manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f"Manifest: {manifest_path}", flush=True)
if __name__ == "__main__":
main()