Spaces:
Running
Running
Download src/miniwebgen/split.py from sparklabuofu/MiniWeb-demo: direct link, hf CLI and curl.
- Browser
- Download file 6.27 kB
-
https://huggingface.co/spaces/sparklabuofu/MiniWeb-demo/resolve/main/src/miniwebgen/split.py
- Command line
-
hf download hf://spaces/sparklabuofu/MiniWeb-demo/src/miniwebgen/split.py
-
curl -L -o split.py https://huggingface.co/spaces/sparklabuofu/MiniWeb-demo/resolve/main/src/miniwebgen/split.py
6.27 kB
| """Train/test SITE split — decided once, before anything is generated, and persisted | |
| (data/datagen/site_split.json, tracked). Every later stage only touches train sites. | |
| Greedy, deterministic (seeded) set cover over a test budget (~20% of sites): | |
| * every slice/target macro gets >= `min_human_test` test sites where humans | |
| recorded it (so transfer is measured on real annotated tasks there); | |
| * every macro supported on >= 4 sites (data/macro_locations.yaml) gets >= 1 test site; | |
| * no pick may leave a macro with fewer than max(3, 60%) of its sites in train. | |
| Leftover budget is filled with sites that have human tasks (evaluable), seeded order. | |
| """ | |
| from __future__ import annotations | |
| from helpers.paths import SITES_DIR | |
| import hashlib | |
| import json | |
| import math | |
| import time | |
| from collections import defaultdict | |
| from miniwebgen import config | |
| def all_sites(): | |
| return sorted(p.parent.name for p in SITES_DIR.glob("*/site.json")) | |
| def support_map(): | |
| """{macro: set(site)} from data/macro_locations.yaml (canonical keys).""" | |
| from miniweb.annotation.macro_locations import MACRO_LOCATIONS | |
| from miniweb.annotation.macros import canon | |
| out = defaultdict(set) | |
| for site, macros in MACRO_LOCATIONS.items(): | |
| for m in macros or {}: | |
| out[canon(m)].add(site) | |
| return out | |
| def human_map(): | |
| """{macro: set(site)} — sites where a human recording performs the macro. The site is | |
| read from the URLs of the span's actions (multi-site tasks name several sites).""" | |
| import re | |
| from miniweb.annotation.macro_browser import index, task_detail | |
| from miniweb.annotation.macros import canon | |
| out = defaultdict(set) | |
| details = {} | |
| for macro, occs in index()["macros"].items(): | |
| for o in occs: | |
| if not o.get("span"): | |
| continue | |
| if o["key"] not in details: | |
| a, t = o["key"].split("/", 1) | |
| details[o["key"]] = task_detail(a, t) or {"actions": []} | |
| lo, hi = o["span"] | |
| for act in details[o["key"]]["actions"][lo - 1:hi]: | |
| m = re.search(r"/sites/([^/?#]+)", act.get("url") or "") | |
| if m: | |
| out[canon(macro)].add(m.group(1)) | |
| return out | |
| def _h(seed, s): | |
| return hashlib.sha256(f"{seed}:{s}".encode()).hexdigest() | |
| def build_split(seed=7, test_fraction=0.2, target_macros=config.SLICE_MACROS, min_human_test=3, | |
| sites=None, support=None, human=None): | |
| sites = sorted(sites or all_sites()) | |
| support = {m: set(s) & set(sites) for m, s in (support if support is not None else support_map()).items()} | |
| human = {m: set(s) & set(sites) for m, s in (human if human is not None else human_map()).items()} | |
| budget = max(1, round(test_fraction * len(sites))) | |
| need = {} # (kind, macro) -> (required count, candidate sites) | |
| for m in target_macros: | |
| need[("human", m)] = (min_human_test, human.get(m, set()) & support.get(m, set()) or human.get(m, set())) | |
| for m, s in support.items(): | |
| if len(s) >= 4: | |
| need[("support", m)] = (1, s) | |
| def train_floor(m): | |
| n = len(support.get(m, ())) | |
| return max(3, math.ceil(0.6 * n)) if n else 0 | |
| test = [] | |
| def ok(site): | |
| for m, s in support.items(): | |
| if site in s and len(s - set(test) - {site}) < min(train_floor(m), len(s)): | |
| return False | |
| return True | |
| def deficit(key): | |
| req, cands = need[key] | |
| return max(0, req - len(set(test) & cands)) | |
| order = sorted(sites, key=lambda s: _h(seed, s)) | |
| while len(test) < budget: | |
| best, best_score = None, 0.0 | |
| for s in order: | |
| if s in test or not ok(s): | |
| continue | |
| score = sum((3.0 if k[0] == "human" else 1.0) for k in need if deficit(k) and s in need[k][1]) | |
| if score > best_score: | |
| best, best_score = s, score | |
| if best is None: | |
| break | |
| test.append(best) | |
| humans_any = set().union(*human.values()) if human else set() | |
| for s in sorted(order, key=lambda s: (s not in humans_any, _h(seed, s))): | |
| if len(test) >= budget: | |
| break | |
| if s not in test and ok(s): | |
| test.append(s) | |
| train = [s for s in sites if s not in test] | |
| coverage = {} | |
| for m in sorted(set(support) | set(human)): | |
| coverage[m] = {"train_sites": len(support.get(m, set()) - set(test)), | |
| "test_sites": len(support.get(m, set()) & set(test)), | |
| "test_sites_with_human_tasks": len(human.get(m, set()) & set(test))} | |
| unmet = {f"{k[0]}:{k[1]}": deficit(k) for k in need if deficit(k)} | |
| return {"version": 1, "created": time.strftime("%Y-%m-%d"), "seed": seed, "test_fraction": test_fraction, | |
| "method": __doc__.strip().splitlines()[0], "target_macros": list(target_macros), | |
| "min_human_test": min_human_test, "train": train, "test": sorted(test), | |
| "target_detail": {m: {"train_sites": sorted(support.get(m, set()) - set(test)), | |
| "test_sites": sorted(support.get(m, set()) & set(test)), | |
| "test_sites_with_human_tasks": sorted(human.get(m, set()) & set(test))} | |
| for m in target_macros}, | |
| "unmet_requirements": unmet, "coverage": coverage} | |
| def load_split(): | |
| if not config.SPLIT_PATH.exists(): | |
| raise FileNotFoundError(f"{config.SPLIT_PATH} missing — run `python -m miniwebgen split` first") | |
| return json.loads(config.SPLIT_PATH.read_text()) | |
| def ensure_split(force=False, **kw): | |
| """Load the persisted split; build it only when missing (or --force).""" | |
| if config.SPLIT_PATH.exists() and not force: | |
| return load_split() | |
| split = build_split(**kw) | |
| config.SPLIT_PATH.parent.mkdir(parents=True, exist_ok=True) | |
| config.SPLIT_PATH.write_text(json.dumps(split, indent=1)) | |
| return split | |
| def train_sites(): | |
| return list(load_split()["train"]) | |
| def assert_train(site): | |
| """Guard used by every stage that touches a site.""" | |
| if site in set(load_split()["test"]): | |
| raise ValueError(f"{site} is a TEST site — the pipeline only touches train sites") | |