Download meter/encode.py from Ericu950/Stoicheia-code: direct link, hf CLI and curl.
- Browser
- Download file 6.17 kB
-
https://huggingface.co/Ericu950/Stoicheia-code/resolve/main/meter/encode.py
- Command line
-
hf download hf://Ericu950/Stoicheia-code/meter/encode.py
-
curl -L -o encode.py https://huggingface.co/Ericu950/Stoicheia-code/resolve/main/meter/encode.py
6.17 kB
| """Encode the macron-data corpora into letter-plane npz stores. | |
| python -m meter.encode [--out $METER_DATA/encoded] [--src $MACRONIZER_SRC] [--norma-source hf] | |
| Sources (all under --src/data, except Norma -- see --norma-source): | |
| macron TSVs plain \t marked (verse silver + OGA prose silver) | |
| dev.txt marked lines (763 Aristophanic verses, macron dev set) | |
| scanner/corpus_v3.tsv work \t meter \t bracketed verse | |
| Anything whose letter stream overlaps the Norma benchmark (both tasks, dev+test) or | |
| dev.txt is EXCLUDED from training stores: a record is dropped if any 20-letter | |
| shingle of an eval line occurs in it (shorter eval lines: exact letter-stream match). | |
| Slightly over-eager by design — dropping a few extra silver lines is free, leakage | |
| is not. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import os | |
| import sys | |
| import unicodedata | |
| from pathlib import Path | |
| from meter.backbone import ALPHABET # noqa: F401 (puts STOICHEIA_ROOT on sys.path) | |
| from meter.dataset import encode_macron_line, encode_scan_line, save_records | |
| from meter.marks import parse_macron_line, parse_scan_line | |
| from meter.norma_data import add_norma_source_arg, load_norma | |
| MACRON_TSVS = ["hypotactic", "drama_ia6", "drama_ia6_tet", "anthology", | |
| "nonnus_quintus", "babrius_chol", "theocritus_doric", | |
| "theocritus_other", "sweep1_hex", "sweep1_ia6", "sweep1_chol", | |
| "sweep1_eleg", "oga_0", "oga_1", "oga_2", "oga_3"] | |
| SHINGLE = 20 | |
| def letters_of(rec) -> str: | |
| return "".join(ALPHABET[c] for c in rec.chars) | |
| ALPHABET_SET = set(ALPHABET) | {"ς", "ϲ"} | |
| def _letters_only(plain: str) -> str: | |
| return "".join(ch for ch in unicodedata.normalize("NFD", plain).lower() | |
| if ch in ALPHABET_SET) | |
| def eval_letter_streams(src: Path, norma_source: str = "hf"): | |
| """Letter streams of every eval line (Norma macronize+syllabify -- dev+test both, | |
| so the exclusion screen covers everything Norma could ever score us against -- | |
| plus dev.txt).""" | |
| streams = [] | |
| norma = load_norma(norma_source) | |
| for d in norma["dev"] + norma["test"]: | |
| parsed = (parse_scan_line(d["text"]) if d["task"] == "syllabify" | |
| else parse_macron_line(d["text"])) | |
| if parsed is not None: | |
| streams.append(_letters_only(parsed[0])) | |
| for line in open(src / "data/dev.txt", encoding="utf-8"): | |
| streams.append(_letters_only(parse_macron_line(line.rstrip("\n"))[0])) | |
| return [s for s in streams if s] | |
| def build_screen(streams): | |
| shingles, exact = set(), set() | |
| for s in streams: | |
| s = s.replace("ς", "σ").replace("ϲ", "σ") | |
| if len(s) >= SHINGLE: | |
| for i in range(len(s) - SHINGLE + 1): | |
| shingles.add(s[i:i + SHINGLE]) | |
| else: | |
| exact.add(s) | |
| return shingles, exact | |
| def is_contaminated(letters: str, shingles, exact) -> bool: | |
| if letters in exact: | |
| return True | |
| for i in range(len(letters) - SHINGLE + 1): | |
| if letters[i:i + SHINGLE] in shingles: | |
| return True | |
| return False | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--src", default=os.path.expandvars(os.environ.get( | |
| "MACRONIZER_SRC", "$MACRONIZER_SRC"))) | |
| ap.add_argument("--out", default=None) | |
| add_norma_source_arg(ap) | |
| a = ap.parse_args() | |
| src = Path(a.src) | |
| out = Path(a.out or os.path.join(os.environ["METER_DATA"], "encoded")) | |
| out.mkdir(parents=True, exist_ok=True) | |
| print("building eval exclusion screen ...", flush=True) | |
| shingles, exact = build_screen(eval_letter_streams(src, a.norma_source)) | |
| print(f" {len(shingles):,} shingles, {len(exact)} exact keys", flush=True) | |
| stats = {} | |
| def finish(name, kept, dropped, excluded, works=None): | |
| save_records(out / f"{name}.npz", kept, works) | |
| n_mac = sum(int((r.y_mac != -100).sum()) for r in kept) | |
| stats[name] = dict(records=len(kept), dropped=dropped, excluded=excluded, | |
| letters=sum(len(r) for r in kept), mac_labels=n_mac) | |
| print(f" {name}: kept={len(kept):,} dropped={dropped:,} " | |
| f"excluded={excluded:,} mac_labels={n_mac:,}", flush=True) | |
| # ---- macron TSVs (train) | |
| for name in MACRON_TSVS: | |
| path = src / "data" / f"{name}.tsv" | |
| if not path.exists(): | |
| print(f" {name}: MISSING, skipped", flush=True) | |
| continue | |
| kept, dropped, excluded = [], 0, 0 | |
| for line in open(path, encoding="utf-8"): | |
| parts = line.rstrip("\n").split("\t") | |
| if len(parts) < 2: | |
| dropped += 1 | |
| continue | |
| rec = encode_macron_line(parts[1]) | |
| if rec is None: | |
| dropped += 1 | |
| continue | |
| if is_contaminated(letters_of(rec), shingles, exact): | |
| excluded += 1 | |
| continue | |
| kept.append(rec) | |
| finish(name, kept, dropped, excluded) | |
| # ---- macron dev (no exclusion screen — it IS an eval set) | |
| kept, dropped = [], 0 | |
| for line in open(src / "data/dev.txt", encoding="utf-8"): | |
| rec = encode_macron_line(line.rstrip("\n")) | |
| if rec is None: | |
| dropped += 1 | |
| continue | |
| kept.append(rec) | |
| finish("dev_aristophanes", kept, dropped, 0) | |
| # ---- scanner corpus (train/dev/test split by work happens at load time) | |
| kept, works, dropped, excluded = [], [], 0, 0 | |
| for line in open(src / "data/scanner/corpus_v3.tsv", encoding="utf-8"): | |
| parts = line.rstrip("\n").split("\t") | |
| if len(parts) != 3 or parts[0] == "?": | |
| dropped += 1 | |
| continue | |
| rec = encode_scan_line(parts[2]) | |
| if rec is None: | |
| dropped += 1 | |
| continue | |
| if is_contaminated(letters_of(rec), shingles, exact): | |
| excluded += 1 | |
| continue | |
| kept.append(rec) | |
| works.append(parts[0]) | |
| finish("scan_corpus", kept, dropped, excluded, works) | |
| (out / "stats.json").write_text(json.dumps(stats, indent=1)) | |
| print("done:", out, flush=True) | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |