Commit Graph

23 Commits

Author SHA1 Message Date
Mohamad Fazeli 0626a12352
README edits: subheadings under Results, fix Xeon column missing from throughput table, fix markdown bullet break in training paragraph, flatten note italics, de-AI-tell wording 2026-08-15 17:03:06 +03:30
Mohamad Fazeli 37c85d0186
fix layout of README.md 2026-08-14 11:57:35 +03:30
Mohamad Fazeli e37d052435
Correct the 940MX trf figure to a 9-run median
1,106 words/s, not the 1,158 from the first 3-run sample. Two independent 3-run
samples disagreed by 3.4%, so the larger sample settles it.
2026-08-13 17:52:15 +03:30
Mohamad Fazeli b26b194d9f
Publish the remaining three packages and link them
fa_ent_news_sm, fa_dep_news_md and fa_core_news_md are now on the Hub, each with
a generated card carrying accuracy and measured throughput.

The md wheels in packages/ were stale 0.1.0 artifacts from an earlier manual run,
even though project.yml passes --version 3.8.0. Rebuilt at 3.8.0 so they match
their published siblings before upload.
2026-08-13 17:34:41 +03:30
Mohamad Fazeli f1a95d92c3
Link each package to its Hugging Face page
Add the lg packages to the table, which had never been listed, with measured
wheel sizes rather than the rounded ones. fa_ent_news_sm, fa_dep_news_md and
fa_core_news_md are built by project.yml but not on the Hub, so they stay
unlinked and are called out as unpublished. All nine links verified to return
200.

Move the cu126 torch requirement out of both READMEs into MODELS.md 9.
2026-08-13 17:17:57 +03:30
Mohamad Fazeli e7871a29a1
Cut the throughput notes to measurements
State the numbers and the cu126 requirement; drop the narration around them.
2026-08-13 17:12:50 +03:30
Mohamad Fazeli 4135142e72
Fill in trf throughput on the 940MX
The 940MX does run trf, at 1,158 words/s with batch 32 inside 2 GB, 6.2x its host
CPU. The earlier claim that current PyTorch wheels cannot target sm_50 was only
half right: Maxwell kernels were dropped from the cu128 and cu129 builds starting
torch 2.8, which is what `pip install torch` now resolves to, but the cu126 build
of 2.7.1 still ships sm_50 and works.

.venv-trf-gpu pins torch==2.7.1+cu126 for this and stays separate from .venv,
whose cupy runs on nvidia-* 12.9 wheels that torch would downgrade to 12.6.

Drop the editorial sentence from the generated model card; the table states it.
2026-08-13 17:11:25 +03:30
Mohamad Fazeli 6fa8aa70b8
Lead the README tables with trf instead of sm
The Hazm and en_core_web_sm comparison now cites fa_core_news_trf, which is the
strongest tier and the only one to pass the hazm+ParsBERT DEP_LAS reference.
Drop the package-size row from that table: it compared a 608 MB transformer
against a 7 MB rule-based toolkit, which says nothing about accuracy.

Add trf to the package list and to the per-label NER table, alongside lg, which
had never been added. Its largest per-label gains are PER +9.25 and DAT +11.69
over lg. Drop the gold-count column there and trim the surrounding notes.
2026-08-13 16:52:37 +03:30
Mohamad Fazeli 5da9dd1524
Add fa_core_news_trf tier on a shared fine-tuned ParsBERT
Trains tagger, morphologizer, trainable_lemmatizer, parser and ner against one
fine-tuned HooshvareLab/bert-base-parsbert-uncased through TransformerListener,
rather than the sm/md/lg split where ner is trained separately and sourced in.
Fine-tuning a 162M-parameter encoder twice would double GPU cost, ship two
encoders in one wheel, and collide on the `transformer` component name.

A shared encoder needs one corpus carrying both annotation layers, so
merge_joint_corpus.py fuses them. corpus/perdt-ner/ came from the same
--merge-subtokens CoNLL-U as corpus/merged/ with the same --n-sents, so the
DocBins are token-for-token identical; the script asserts that per document and
copies doc.ents by token index. Char offsets do not work here because the two
converters differ in trailing whitespace, which pushes char_span off the token
grid and returns None.

Trained on a Colab T4 in 1h58m, 3000 steps, no early stop. Test scores against
lg: DEP_LAS 90.79 (+4.19), ENTS_F 82.89 (+6.95, almost all recall), TAG_ACC
97.62 (+1.07). DEP_LAS passes the hazm+ParsBERT reference of 89.34, which no CPU
tier reached. LEMMA_ACC 97.31 and SENTS_F 97.35 regress against lg; the likely
cause of the latter is strided_spans leaving only 32 tokens of overlap.

max_steps and learn_rate.total_steps are held equal on purpose. They are
independent knobs, and a patience stop under a longer total_steps ends training
at a high learning rate, discarding the annealing tail.

finalize_pipeline.py now reads the encoder name out of the trained config
instead of hardcoding it, tracks per-encoder licences, and writes a
redistribution warning into meta.json when the encoder states none. ParsBERT
states none, so that wheel is not redistributable; roberta-fa-zwnj-base
(Apache-2.0) is a one-line change to `name`.

Add benchmark_throughput.py, which times nlp.pipe alone. The words/s from
`spacy benchmark accuracy` includes the Scorer's per-token alignment, which is
why MODELS.md 7 reported ent_lg as faster than sm despite an identical ner
architecture. Remeasured every tier on CPU and the 940MX; a background rsync
halved every figure, so the final numbers are 9-run medians on an idle machine.

Add make_model_card.py, which composes the Hub card from meta.json and the
throughput records. The card spacy package writes has no frontmatter, so the Hub
cannot index the model by language, and no install line or usage.
2026-08-13 16:34:55 +03:30
Mohamad Fazeli c3cb02d9c3
README: dedicated NER section, drop AI-tell formatting
- New ## Named entity recognition section: labels, silver-provenance,
  per-label breakdown table, package options. Moved out of ## Results
  (which now just points to it) and out of ## Caveats (silver-label and
  thin-label bullets folded into the new section's prose instead of
  repeating them).
- Added fa_ent_news_sm to the package table (built, was undocumented).
- Design decision 4 no longer repeats the NER transfer methodology now
  that it has a dedicated home.
- Dropped the warning-emoji decoration on the comparability note.
2026-08-12 13:49:05 +03:30
Mohamad Fazeli 8e42c38ed6
Ship the md tier: floret vectors, docs, and HF publish fixes
- project.yml: new md workflow (vectors-md, train-dep-md, train-ner-md,
  finalize-dep-md, assemble-core-md, evaluate-md, finalize-meta-md,
  compare-md, package-md, smoke-md), same corpus/architecture as sm plus
  the fa_floret static vector table (50k rows x 300d, 400k Persian
  documents).
- configs/fa_dep_news_md.cfg, configs/fa_ner_md.cfg: byte-identical to the
  sm configs except include_static_vectors, isolating what the vectors buy.
- scripts/unpack_vectors.py: extracts a floret wheel's vectors-only
  pipeline into a directory --paths.vectors can point at.
- scripts/finalize_pipeline.py: --size now accepts md (floret source +
  vectors note), plus lg/trf (used by the pending Colab notebook on
  colab-lg-trf-training; lg's vectors note is generated from the trained
  model's actual vector table shape since that tier is still being
  iterated on).
- scripts/compare_tiers.py: sm vs md metrics diff.
- docs/MODELS.md, README.md, README.fa.md: md tier results, fa_ent_news_md
  package row, and the analysis of why floret helps NER recall.
- docs/CONTRIBUTING-GUIDE.md, README.md, README.fa.md: fixed every
  documented pip install URL. spacy huggingface-hub push names the
  uploaded wheel '<name>-any-py3-none-any.whl'; 'any' is not a valid PEP
  440 version, so current pip rejects it. Re-uploaded a correctly
  versioned copy of every published wheel (fa_core_news_sm, fa_dep_news_sm,
  fa_ent_news_md) to the Hub and repointed the docs at that filename.
2026-08-12 13:15:55 +03:30
Kiyarash Fazeli d5fadb07f0
Update README.md 2026-08-11 13:52:50 +03:30
Kiyarash Fazeli 621be65956
Add benefits section for spacy-persian
Added section highlighting the benefits of spacy-persian.
2026-08-11 11:56:21 +03:30
Kiyarash Fazeli 178c9ffa8e
Fix formatting in README for Persian spaCy pipelines 2026-08-10 10:51:39 +03:30
Kiyarash Fazeli 6b4a97c838
Update README.md 2026-08-10 10:50:38 +03:30
Kiyarash Fazeli 8c82010550
Modify example output in README.md
Updated example output to show only the first two tokens.
2026-08-10 10:48:38 +03:30
Kiyarash Fazeli b20380f97f
Update README for Persian spaCy pipelines
Improved README clarity and added installation instructions for Persian spaCy pipelines.
2026-08-10 10:46:34 +03:30
Mohamad Fazeli 32b4ae57ef
Add a brief Farsi README
Encyclopedic Persian summary: packages, install, metrics table, silver-NER
caveat, build pointers. Linked from the English README.
2026-08-02 19:16:49 +03:30
Mohamad Fazeli 628578744d
update README.md 2026-08-01 19:45:51 +03:30
Mohamad Fazeli 0689be63ff
README: Farsi in title, results first, drop roadmap and licensing sections
- Title now 'Persian (Farsi) pipelines for spaCy'
- Section order: intro, Results, Install, Caveats, Build, Design decisions,
  Why not hazm, More -- numbers before any provenance argument
- Install points at published HF wheels instead of local packages/*.whl
  paths, verified against a clean venv
- Dropped ## Roadmap (mirrored in gitignored TODO.md) and
  ## Licensing drove most decisions here (superseded by docs/MODELS.md)
- Trimmed provenance/build-trivia prose; kept every score, the hazm
  comparison, and the four design decisions verbatim
2026-08-01 11:20:06 +03:30
Mohamad Fazeli 518340a840 Ship fa_core_news_sm: PerDT carries its own NER layer
UD_Persian-PerDT ships entity annotations in not-to-release/Dadegan with NER
tag/ that nothing in this project had looked at: 29,107 sentences, 484,312
tokens, 15,833 entities, under the treebank's own CC BY-SA 4.0. That removes the
reason core was withheld. The previous commit split dep from ent because the only
redistributable Persian NER corpus known then was ParsTwiNER, a Twitter corpus
scoring 67.22 F against 85-98 for the UD components, and hiding that genre and
quality gap behind one package name was not acceptable. A NER layer from the same
corpus has none of those problems: one genre, one tokenization, one licence, one
provenance chain.

Two shipping packages, both from PerDT alone:

  fa_dep_news_sm   7.5 MB  TAG 95.96  LEMMA 97.91  LAS 85.15
  fa_core_news_sm   13 MB  the above plus ENTS_P/R/F 77.67 / 66.87 / 71.87

Per label: LOC 80.24, DAT 74.45, MON 73.68, ORG 68.77, TIM 66.67, PER 65.29,
PCT 57.14. PER scoring below LOC on comparable data is the silver labels showing:
PerDT starts 6.24% of PER spans with a title against ParsTwiNER's 1.41%, so the
boundaries are less regular than the count suggests. MON, TIM and PCT rest on 4
to 11 test entities and are indicative only.

The labels are silver, from Beheshti-NER (Taher et al. 2020) with manual
corrections per the treebank README. meta.json notes say so. A human-annotated
test set is the outstanding work, tracked in ../ner_dataset.

Alignment: the NER files use the original Dadegan tokenization, matching the
released UD tokenization in only 57 to 62% of sentences (dropped copulas and
auxiliaries, one honorific corrupted to a comma). scripts/transfer_perdt_ner.py
realigns with difflib at 99.86% train / 99.74% dev / 99.51% test; spans whose
tokens do not all map contiguously are dropped rather than guessed.

Also fixed a silent metadata corruption. Folding both benchmark reports over one
key set gave core tag_acc 0.00, because the NER corpus has no gold tags so its
report carries tag_acc: 0.0, which overwrote the real 95.96; sents_f was wrong
the same way. finalize_pipeline.py now takes --ud-metrics and --ner-metrics
separately, each restricted to the keys its corpus can evidence. Its variant
guards work both directions now: a dep pipeline may not contain ner, a core one
must.

ParsTwiNER moves out of the shipping repo to a future mixed-genre package, with
the ablation that justifies it: prose-trained NER scores 45.72 F on tweets,
tweet-trained scores 55.59 on prose, and mixing gives 72.11 / 66.49, so roughly
20 F of cross-genre robustness for under 1 F on prose.

Adds LICENSE recording the split: MIT for the code, CC BY-SA 4.0 for the trained
pipelines as Adapted Material, with the attribution, modification notice and
warranty disclaimer that CC BY-SA 4.0 section 3 requires. Verified the treebank's
LICENSE.txt is unmodified CC BY-SA 4.0 with no carve-out for not-to-release/.

Author metadata filled in; both packages rebuilt and installed from their wheels.
2026-08-01 00:59:16 +03:30
Mohamad Fazeli 41d5a46d96 Split the pipeline into fa_dep_news_sm + fa_ent_news_sm, drop core
spaCy's naming scheme encodes contents: dep = tagger+parser+lemmatizer,
ent = NER only, core = both. Shipping a single fa_core_news_sm implied the NER
was held to the same standard as the rest of the pipeline. It is not, and the
numbers are not close:

  UD components (PerDT, edited prose):  TAG 95.96  LEMMA 97.91  LAS 85.15
  ner           (ParsTwiNER, tweets):   ENTS_F 67.22

One package name and one version number would paper over that. So the treebank
components ship as fa_dep_news_sm (CC BY-SA 4.0, 7.5 MB) and NER ships as an
opt-in fa_ent_news_sm (MIT, 5.6 MB). Users now choose the weak component
deliberately instead of inheriting it.

Counting ParsTwiNER settles what "weak" means. It is not a small corpus --
232,917 tokens and 16,250 entities, the same order as the restricted ARMAN and
PEYMA. But the labels are skewed: PER 6258, LOC 5478, ORG 2694, NAT 939,
EVE 482, POG 399. The two starved labels are exactly the two scoring worst
(EVE 30.0, POG 41.2). Head labels suffer genre mismatch, tail labels suffer data
starvation -- two problems needing two different fixes. Recorded in README and
docs/MODELS.md.

fa_core_news_sm is reserved, not abandoned. ../ner_dataset/PLAN.md targets
prose-genre NER data that must beat ParsTwiNER on a human-annotated test set
before the name gets used. Nothing technical blocks the merge: the ner component
already embeds its own tok2vec instead of a Tok2VecListener, so

    dep.add_pipe("ner", source=spacy.load("fa_ent_news_sm"))

reassembles a core-equivalent pipeline at runtime -- verified, not assumed.

Mechanics:
- scripts/assemble_core.py -> scripts/finalize_pipeline.py, now variant-aware
  (dep|ent) and refusing to publish a dep pipeline containing an ner component,
  so the split cannot silently regress.
- published meta.json["performance"] now comes from a strict whitelist. It was
  inheriting raw *_loss values and a bogus tag_micro_f: 0.0 from training meta.
- project.yml split into two independent workflows, `all` and `ner`; all 16
  commands dry-run clean.
- configs/fa_core_news_sm.cfg -> configs/fa_dep_news_sm.cfg.
- smoke_test.py degrades gracefully on pipelines lacking DEP/MORPH/ner.

Also folded into ../ner_dataset/PLAN.md: PerDT's XPOS encodes animacy on proper
nouns (N_ANM 6752, N_IANM 12682). Animate PROPN is a strong free prior for PER,
shrinking the annotation task to splitting inanimate PROPN into LOC/ORG, and
giving a gold-grounded cross-check that beats the model's self-reported
confidence for routing items to human review.

Committed unsigned: the OpenPGP smartcard holding 05E227BF4D6736DE is not
present (gpg: selecting card failed: No such device).
2026-07-31 11:08:49 +03:30
Mohamad Fazeli 92fc1c3002 fa_core_news_sm: Persian spaCy pipeline from UD_Persian-PerDT + ParsTwiNER
No trained Persian pipeline exists for spaCy: spacy.load("fa_core_news_sm") has
never worked. This builds one from openly-licensed data so the result can actually
be redistributed.

Test scores (held-out splits): TAG 95.96, POS 96.24, MORPH 96.29, LEMMA 97.91,
UAS 89.69, LAS 85.15, ENTS_F 67.22. ~9,250 words/s, 13 MB wheel. 1h27m on 4 CPU
cores, no GPU.

Corpus choices, with evidence:
- UD_Persian-PerDT (CC BY-SA 4.0) over Seraji: 3.7x more tokens (452k vs 121k) and
  Seraji has no PROPN tag at all. hazm's own spaCy parser used PerDT too.
- ParsTwiNER (MIT) for NER. ARMAN/PEYMA/NSURL are research-only; spaCy never
  shipped the 2018 Persian models precisely because of corpus licensing. Cost: NER
  is trained on tweets, so it is the weak component.
- --merge-subtokens: measured token F 0.9887 vs 0.9823 split. Without it, 1.5% of
  gold token boundaries are unreachable by the tokenizer we ship.
- morphologizer + trainable_lemmatizer instead of the English attribute_ruler +
  rule lemmatizer, because UD gives gold UPOS/FEATS/lemmas to train and measure on.

The ner component embeds its own tok2vec rather than using a Tok2VecListener, so it
can be sourced into the core pipeline after being trained on a separate corpus.

Also documents an upstream bug: spacy/lang/fa/syntax_iterators.py matches ClearNLP
labels (dobj, pobj, nsubjpass, attr, dative) that do not exist in UD, so
doc.noun_chunks returns bare head nouns (1.31 vs 2.77 tokens/chunk). Patch and
tests in docs/upstream/fa-noun-chunks.md.
2026-07-29 20:52:13 +03:30