Skip to content

Add PhayaThaiBERT POS tagger (ONNX and Transformers) - #1550

Merged
wannaphong merged 13 commits into
PyThaiNLP:devfrom
wiriyabot:add-phayathaibert-pos-onnx
Nov 5, 2026
Merged

wannaphong merged 13 commits into
PyThaiNLP:devfrom
wiriyabot:add-phayathaibert-pos-onnx

Conversation

@wiriyabot

Copy link
Copy Markdown

What do these changes do

Adds nlp-chula/phayathaibert-thai-pos-tagger (UD Thai-TUD, UPOS) to:

  • pos_tag(words, engine="phayathaibert"): an ONNX export, no PyTorch needed (extra phayathaibert_onnx)
  • pos_tag_transformers(sentence, engine="phayathaibert", corpus="tud"): the original model

What was wrong

No engine offered this model.

How this fixes it

  • Tags each word by its first subword, as in training
  • Tags whitespace-only words PUNCT
  • Splits input into chunks of at most 415 tokens: accuracy drops sharply after that, in PyTorch too
  • UD Thai-TUD test accuracy: 91.21% (PyTorch: 91.16%)
  • Core tests use a fake model, so CI covers the code without onnxruntime

Fixes #1536

Your checklist for this pull request

  • Passed code styles and structures
  • Passed code linting checks and unit test

@coveralls

coveralls commented Oct 7, 2026 •

Copy link
Copy Markdown

Coverage Status

coverage: 74.57% (+0.2%) from 74.329% — wiriyabot:add-phayathaibert-pos-onnx into PyThaiNLP:dev

@bact bact added the enhancement enhance functionalities label Oct 7, 2026
@wannaphong

Copy link
Copy Markdown
Member

@wiriyabot Thank you for pull request! Can you sent this pull request to dev branch?

Add the nlp-chula/phayathaibert-thai-pos-tagger model, fine-tuned on
the TUD treebank, as a Universal POS tagger:

- pos_tag(engine="phayathaibert") runs an ONNX export of the model
  with ONNX Runtime and the tokenizers library, without PyTorch. The
  model is downloaded from the Hugging Face Hub, pinned to a commit.
  Whitespace-only words are tagged PUNCT, and input longer than the
  model limit is tagged in chunks of whole words.
- pos_tag_transformers(engine="phayathaibert", corpus="tud") runs the
  original model with Transformers. The per-corpus engine tables are
  merged into one lookup.

Add the phayathaibert_onnx optional dependency group, tests, and API
documentation.

Refs PyThaiNLP#1536
The published tokenizer.json truncates at 510 tokens, so long input
was cut before chunking and every word past the cut was tagged X.
Turn off the tokenizer's built-in truncation and padding so chunking
by whole words takes effect.

- Tag words made only of whitespace or format characters (such as
  the zero-width space) as PUNCT without passing them to the model.
- Require onnxruntime>=1.12.0 (opset 17) and tokenizers>=0.19.0
  (the Metaspace format in tokenizer.json).
- Pin the default revision only for the default repository.
- Guard the lazily created tagger with a lock.
- Name huggingface-hub in the import check.
- List the phayathaibert_onnx extra in the installation notes.
- Test tags past the first 510 tokens, an over-long word, and format
  characters.
- Use D213 docstrings and typed fields in phayathaibert_onnx, and add
  an __init__ docstring (D107).
- Test PhayaThaiBERTTagger, tag() and the new pos_tag and
  pos_tag_transformers branches with a fake model, so CI covers the
  new code without onnxruntime, tokenizers or transformers.
- Fake every dependency inside patch.dict(sys.modules), so no real
  module (such as numpy) is imported and then unloaded by the test.
- Merge the _chunk_spans tests into one table-driven test.
The model accepts 512 positions, but its accuracy drops sharply from
position 415 on, in the original PyTorch model as well as the ONNX
export. With chunks of up to 510 tokens, tagging the whole UD Thai-TUD
test set as one input gave 80.26% accuracy; with 415 it gives 90.08%
(91.21% sentence by sentence).

The long-input test now checks that every repeat of a sentence gets
the same tags as the sentence alone, which fails with the old limit.
Only README.md changed on the Hugging Face repository; model.onnx, tokenizer.json and config.json are the same files.
- Check dependencies with importlib.util.find_spec instead of unused
  imports (pylint W0611).
- Use a one-line module docstring (pydocstyle D212); the model
  description moves to the class docstring.
- Mark the unused corpus parameter of tag() for SonarCloud (S1172).
- Tests: end a docstring with a period (D415), keep one assertion
  argument order (S3415), and use assertLessEqual (S5906).
CI runs mypy on tests too (mypy 1.19.1, Python 3.9), which rejects calls to the untyped fake model helpers.
Fixes pylint W0404 (reimport) reported by Codacy.
- Write zero-width and special space characters in tests as \u escapes,
  so they are visible and editors cannot strip them (pylint E2515).
- Rename a loop variable that shadowed the module-level tag() (W0621).
- Correct the length comment: the model runs on up to 510 tokens.
- Build the truncated word_ids list by unpacking, not concatenation.
- Annotate the shared WORDS test attribute as ClassVar.
@wiriyabot
wiriyabot changed the base branch from main to dev October 7, 2026 14:14
@wiriyabot
wiriyabot force-pushed the add-phayathaibert-pos-onnx branch from 9cd311c to 9a5da21 Compare October 7, 2026 14:15
@wiriyabot

Copy link
Copy Markdown
Author

@wannaphong Thanks! Rebased onto dev and retargeted the PR. I also fixed the two new ruff findings (RUF005, RUF012).

@bact
bact requested a review from wannaphong October 7, 2026 14:23
@bact bact added this to the 6.0 milestone Oct 8, 2026
@wannaphong
wannaphong merged commit 2b0a1aa into PyThaiNLP:dev Nov 5, 2026
1 of 2 checks passed
@wannaphong

Copy link
Copy Markdown
Member

Thank you!

@sonarqubecloud

sonarqubecloud Bot commented Nov 5, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement enhance functionalities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add POS tagger from ThaiTrees

4 participants