Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| Seeded train/dev/test split for SenseCatch. | |
| Provides ONE fixed train/dev/test split so every tuning decision | |
| (model choice, hyperparameters, ensemble weights, gate threshold, | |
| calibration) is made on DEV, and the IMDB official TEST set is touched | |
| only once, for final reporting. | |
| - IMDB official split: train/ (25k) and test/ (25k) are kept separate. | |
| - DEV is carved from IMDB train/ (seeded, stratified), 10% by default. | |
| - A manifest (sizes + a hash of each split's file ids) is saved for | |
| reproducibility and as a no-leakage proof artifact. | |
| Usage: | |
| python src/training/data_split.py # writes/print the manifest | |
| from data_split import get_split, load_texts | |
| """ | |
| import os | |
| import json | |
| import random | |
| import hashlib | |
| PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| IMDB_DIR = os.path.join(PROJECT_ROOT, "datasets", "aclImdb") | |
| MANIFEST_PATH = os.path.join(PROJECT_ROOT, "artifacts", "data_split_manifest.json") | |
| SEED = 42 | |
| DEV_FRACTION = 0.10 | |
| def _list_imdb(split): | |
| """Return [(relpath, label)] for an IMDB split ('train'/'test'); label 1=pos, 0=neg.""" | |
| items = [] | |
| for sentiment, label in (("pos", 1), ("neg", 0)): | |
| folder = os.path.join(IMDB_DIR, split, sentiment) | |
| for fn in sorted(os.listdir(folder)): | |
| items.append((os.path.join(split, sentiment, fn), label)) | |
| return items | |
| def get_split(dev_fraction=DEV_FRACTION, seed=SEED): | |
| """Deterministic train/dev/test split. | |
| Returns {'train': [(relpath, label)], 'dev': [...], 'test': [...]}. | |
| DEV is a stratified random sample of IMDB-train; the remainder of | |
| train is 'train'; IMDB-test is 'test' (held out until final eval). | |
| """ | |
| rng = random.Random(seed) | |
| train_all = _list_imdb("train") | |
| test = _list_imdb("test") | |
| pos = [x for x in train_all if x[1] == 1] | |
| neg = [x for x in train_all if x[1] == 0] | |
| rng.shuffle(pos) | |
| rng.shuffle(neg) | |
| n_dev_pos = int(len(pos) * dev_fraction) | |
| n_dev_neg = int(len(neg) * dev_fraction) | |
| dev = pos[:n_dev_pos] + neg[:n_dev_neg] | |
| train = pos[n_dev_pos:] + neg[n_dev_neg:] | |
| rng.shuffle(train) | |
| rng.shuffle(dev) | |
| return {"train": train, "dev": dev, "test": test} | |
| def load_texts(items): | |
| """Given [(relpath, label)], read files -> (texts, labels).""" | |
| texts, labels = [], [] | |
| for rel, lab in items: | |
| with open(os.path.join(IMDB_DIR, rel), encoding="utf-8") as f: | |
| texts.append(f.read()) | |
| labels.append(lab) | |
| return texts, labels | |
| def save_manifest(path=MANIFEST_PATH): | |
| """Write a manifest: per-split sizes + SHA-256 of each split's sorted file ids.""" | |
| sp = get_split() | |
| manifest = {"seed": SEED, "dev_fraction": DEV_FRACTION, "splits": {}} | |
| for name, items in sp.items(): | |
| ids = sorted(rel for rel, _ in items) | |
| h = hashlib.sha256("\n".join(ids).encode()).hexdigest() | |
| n_pos = sum(1 for _, l in items if l == 1) | |
| manifest["splits"][name] = { | |
| "n": len(items), | |
| "n_pos": n_pos, | |
| "n_neg": len(items) - n_pos, | |
| "ids_sha256": h, | |
| } | |
| os.makedirs(os.path.dirname(path), exist_ok=True) | |
| with open(path, "w") as f: | |
| json.dump(manifest, f, indent=2) | |
| return manifest | |
| if __name__ == "__main__": | |
| print(json.dumps(save_manifest(), indent=2)) | |