Download scripts/build_notebook.py from Ericu950/Stoicheia-code: direct link, hf CLI and curl.
- Browser
- Download file 10.1 kB
-
https://huggingface.co/Ericu950/Stoicheia-code/resolve/main/scripts/build_notebook.py
- Command line
-
hf download hf://Ericu950/Stoicheia-code/scripts/build_notebook.py
-
curl -L -o build_notebook.py https://huggingface.co/Ericu950/Stoicheia-code/resolve/main/scripts/build_notebook.py
10.1 kB
| #!/usr/bin/env python3 | |
| """Build the Stoicheia demo notebook (Colab-ready).""" | |
| import json, pathlib | |
| ORG = "Ericu950" | |
| def md(*lines): return {"cell_type": "markdown", "metadata": {}, "source": [l + "\n" for l in lines]} | |
| def code(*lines): return {"cell_type": "code", "metadata": {}, "execution_count": None, | |
| "outputs": [], "source": [l + "\n" for l in lines]} | |
| cells = [ | |
| md("# Stoicheia — a character-level model for Ancient Greek", | |
| "", | |
| "Stoicheia is a 405M-parameter character-level masked-diffusion encoder for Ancient Greek.", | |
| "Its input is factored into five aligned planes — letters, word/sentence boundaries,", | |
| "diacritics, capitalization, punctuation — and **any of them can be set to *unknown* at", | |
| "inference**. One model therefore reads an edited text, bare *scriptio continua*, and a", | |
| "lacuna of unknown length, changing nothing but its input.", | |
| "", | |
| "This notebook runs the whole release end to end on a free Colab GPU (CPU works too, slower):", | |
| "", | |
| "1. restore a lacuna whose width is *not known* in advance", | |
| "2. pick the restoration model that has provably **never read** your document", | |
| "3. tag and parse a verse of Homer", | |
| "4. macronize and scan a line of verse", | |
| "5. score the macronizer against a hand-annotated benchmark", | |
| "", | |
| "Every model and dataset used below is public."), | |
| code("%pip install -q --upgrade transformers huggingface_hub safetensors torch datasets"), | |
| md("## 1. Restoring a lacuna of unknown width", | |
| "", | |
| "The hard case in epigraphy and papyrology is a break whose extent is uncertain, in text that", | |
| "carries no accents and no word division. Write `[N±M]` and the model scores every width in", | |
| "`N-M … N+M` by its own confidence, restoring the letters, the accents and the word boundaries", | |
| "together."), | |
| code("import sys, torch", | |
| "from transformers import AutoModel", | |
| "from huggingface_hub import snapshot_download", | |
| "", | |
| f'REPO = "{ORG}/Stoicheia-doc_clean" # zero exposure to inscriptions or papyri', | |
| 'local = snapshot_download(REPO, allow_patterns=["*.py", "*.json"])', | |
| "sys.path.insert(0, local)", | |
| "", | |
| "model = AutoModel.from_pretrained(REPO, trust_remote_code=True).eval()", | |
| "from processing_char_bert import CharBertProcessor", | |
| "proc = CharBertProcessor()", | |
| "", | |
| "# John 1:1 as it would reach us on a damaged, unaccented, unspaced witness", | |
| 'damaged = "εναρχηηνο[5±3]καιολογοςηνπροστονθεον"', | |
| "best, width, candidates = proc.restore_elastic(model, damaged, mask_dia_boundary=True)", | |
| 'print("restored :", best)', | |
| 'print("width :", width, "characters")', | |
| 'for c in candidates[:5]:', | |
| ' print(" ", c)'), | |
| md("## 2. The model that has never read your document", | |
| "", | |
| "A single fixed train/test split makes a model useless for exactly the documents an editor", | |
| "cares about. Ten restoration checkpoints are released instead, one per held-out final digit", | |
| "of the PHI/TM identifier: whatever inscription or papyrus you are working on, one of the ten", | |
| "has provably never seen it during fine-tuning, and its backbone never saw a documentary text", | |
| "at all. A reading proposed by *that* model cannot be a memory of the edition you are checking."), | |
| code("def model_that_never_read(document_id: str) -> str:", | |
| ' """Pick the released checkpoint whose held-out digit matches this document."""', | |
| " digit = str(document_id).strip()[-1]", | |
| f' return f"{ORG}/Stoicheia-restoration-test{{digit}}"', | |
| "", | |
| 'for phi in ["PHI 12345", "PHI 293", "TM 8100"]:', | |
| ' print(f"{phi:12s} -> {model_that_never_read(phi)}")', | |
| "", | |
| "# use it exactly like the backbone above", | |
| 'REPO = model_that_never_read("PHI 293")', | |
| 'local = snapshot_download(REPO, allow_patterns=["*.py", "*.json"])', | |
| "sys.path.insert(0, local)", | |
| "restorer = AutoModel.from_pretrained(REPO, trust_remote_code=True).eval()", | |
| "", | |
| '# "[7]" is a lacuna of known width; the decree formula is deliberately incomplete', | |
| 'text = "αγαθηιτυχηιεδοξεντ[7]βουληικαιτωιδημωι"', | |
| "best, width, _ = proc.restore_elastic(restorer, text, mask_dia_boundary=True)", | |
| 'print("\\nrestored:", best)'), | |
| md("## 3. Tagging and parsing", | |
| "", | |
| "Four heads on one shared backbone — factored XPOS, an edit-script lemmatizer, a UPOS", | |
| "auxiliary and a biaffine dependency parser — all from a single forward pass."), | |
| code("from huggingface_hub import snapshot_download", | |
| f'REPO = "{ORG}/Stoicheia-tagger-parser"', | |
| 'local = snapshot_download(REPO, allow_patterns=["*.json", "*.txt", "*.py", "*.model"])', | |
| "sys.path.insert(0, local)", | |
| "from processing_char_bert_joint import CharBertJointProcessor", | |
| "", | |
| "parser_model = AutoModel.from_pretrained(REPO, trust_remote_code=True).eval()", | |
| "jproc = CharBertJointProcessor.from_pretrained(local)", | |
| "", | |
| 'words = "μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος".split()', | |
| "batch = jproc([words])", | |
| "with torch.no_grad():", | |
| " out = parser_model(**batch)", | |
| "rows = jproc.decode(out, batch, ud=True)", | |
| "", | |
| "sent = rows[0] if rows and not isinstance(rows[0], dict) else rows", | |
| "hdr = ('id', 'form', 'lemma', 'upos', 'head', 'deprel')", | |
| "print('%3s %-12s%-12s%-8s%4s %s' % hdr)", | |
| "for i, w in enumerate(sent, 1):", | |
| " print('%3d %-12s%-12s%-8s%4s %s' % (i, w['form'], w['lemma'], w['upos'], w['head'], w['deprel']))"), | |
| md("## 4. Vowel length and metre", | |
| "", | |
| "Greek orthography never marks vowel length: α, ι and υ — the *dichrona* — are ambiguous.", | |
| "Recovering it (*macronization*) is lexical knowledge, and it is the prerequisite for scanning", | |
| "verse. `Stoicheia-meter` does both at once; `Stoicheia-macronizer` does vowel length alone,", | |
| "slightly better."), | |
| code(f'REPO = "{ORG}/Stoicheia-meter"', | |
| 'local = snapshot_download(REPO, allow_patterns=["*.json", "*.txt", "*.py", "*.model"])', | |
| "sys.path.insert(0, local)", | |
| "from processing_char_bert_meter import CharBertMeterProcessor", | |
| "", | |
| "meter_model = AutoModel.from_pretrained(REPO, trust_remote_code=True).eval()", | |
| "mproc = CharBertMeterProcessor()", | |
| "", | |
| 'line = "ἄνδρα μοι ἔννεπε, μοῦσα, πολύτροπον, ὃς μάλα πολλὰ"', | |
| "batch = mproc(line)", | |
| "with torch.no_grad():", | |
| ' out = meter_model(**{k: v for k, v in batch.items() if not k.startswith("_")})', | |
| 'print("macronized:", mproc.decode_macronization(out, batch)) # _ long, ^ short', | |
| 'print("scanned :", mproc.decode_scansion(out, batch)) # [heavy] {light}'), | |
| md("## 5. Scoring against the benchmark", | |
| "", | |
| "*Norma Syllabarum Graecarum* is a hand-annotated benchmark of macronization and", | |
| "syllabification. Here we score the dedicated macronizer on its test split — every ambiguous", | |
| "α/ι/υ position, compared against the gold mark."), | |
| code("import json, re", | |
| "from huggingface_hub import hf_hub_download", | |
| "", | |
| f'REPO = "{ORG}/Stoicheia-macronizer"', | |
| 'local = snapshot_download(REPO, allow_patterns=["*.json", "*.txt", "*.py", "*.model"])', | |
| "sys.path.insert(0, local)", | |
| "mac_model = AutoModel.from_pretrained(REPO, trust_remote_code=True).eval()", | |
| "", | |
| f'path = hf_hub_download("{ORG}/norma", "data/test.jsonl", repo_type="dataset")', | |
| 'rows = [json.loads(l) for l in open(path, encoding="utf-8")]', | |
| 'rows = [r for r in rows if r["task"] == "macronize"][:120] # raise for the full set', | |
| "", | |
| 'MARKS = re.compile(r"[_^]")', | |
| "n = correct = 0", | |
| "for r in rows:", | |
| ' gold = r["text"]', | |
| ' raw = MARKS.sub("", gold)', | |
| " batch = mproc(raw)", | |
| " with torch.no_grad():", | |
| ' out = mac_model(**{k: v for k, v in batch.items() if not k.startswith("_")})', | |
| " pred = mproc.decode_macronization(out, batch)", | |
| " for g, p in zip(gold, pred):", | |
| " pass", | |
| " # compare mark-by-mark at the positions the gold marks", | |
| " gi = pi = 0", | |
| " while gi < len(gold) and pi < len(pred):", | |
| ' if gold[gi] in "_^" and pred[pi] in "_^":', | |
| " n += 1; correct += (gold[gi] == pred[pi]); gi += 1; pi += 1", | |
| ' elif gold[gi] in "_^":', | |
| " n += 1; gi += 1", | |
| ' elif pred[pi] in "_^":', | |
| " pi += 1", | |
| " else:", | |
| " gi += 1; pi += 1", | |
| 'print(f"macronization accuracy on {len(rows)} lines: {100*correct/max(n,1):.2f}% ({n} scored positions)")'), | |
| md("---", | |
| "", | |
| "**Everything in the release**", | |
| "", | |
| "| | |", | |
| "|---|---|", | |
| "| 11 pretrained backbones | ten rotated literary folds + one documentary-clean |", | |
| "| 10 restoration checkpoints | one per held-out PHI/TM digit |", | |
| "| tagger-parser, meter, macronizer | fine-tuned from the documentary-clean backbone |", | |
| "| 5 datasets | pretraining corpus, synthetic augmentation, inscriptions, meter silver, benchmark |", | |
| "", | |
| "Training and evaluation code, including the split pipeline that produces the decontamination", | |
| "guarantee, is in the accompanying code repository."), | |
| ] | |
| nb = {"cells": cells, | |
| "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, | |
| "language_info": {"name": "python"}, | |
| "colab": {"provenance": [], "toc_visible": True}, | |
| "accelerator": "GPU"}, | |
| "nbformat": 4, "nbformat_minor": 0} | |
| out = pathlib.Path("/tmp/stoicheia_hf/Stoicheia_demo.ipynb") | |
| out.write_text(json.dumps(nb, ensure_ascii=False, indent=1), encoding="utf-8") | |
| print("wrote", out, out.stat().st_size // 1024, "KB,", len(cells), "cells") | |