Instructions to use kaan84/sinhala-print-trocr with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use kaan84/sinhala-print-trocr with Transformers:
# Use a pipeline as a high-level helper # Warning: Pipeline type "image-to-text" is no longer supported in transformers v5. # You must load the model directly (see below) or downgrade to v4.x with: # 'pip install "transformers<5.0.0' from transformers import pipeline pipe = pipeline("image-to-text", model="kaan84/sinhala-print-trocr")# Load model directly from transformers import AutoTokenizer, AutoModelForMultimodalLM tokenizer = AutoTokenizer.from_pretrained("kaan84/sinhala-print-trocr") model = AutoModelForMultimodalLM.from_pretrained("kaan84/sinhala-print-trocr", device_map="auto") - Notebooks
- Google Colab
- Kaggle
sinhala-print-trocr
TrOCR fine-tune for printed Sinhala text lines, with a repaired decoder tokenizer that preserves Zero Width Joiner.
Use this repo. The predecessor,
kaan84/sinhala-print-trocr-pilot,
is frozen and contains a tokenizer defect; it is kept only as the published record
of that finding.
| this repo | predecessor | |
|---|---|---|
| name | sinhala-print-trocr |
sinhala-print-trocr-pilot |
| role | current model | frozen "before" state |
| tokenizer | ZWJ preserved | deletes ZWJ |
Scope
Input is a single line image (~2600×128). This is not a page or document recogniser — see Real documents for what page-level use requires.
What changed
The predecessor's BertNormalizer ran with clean_text=True, deleting all Unicode
Cf characters including U+200D ZWJ — the character that forms Sinhala conjuncts
such as ශ්රී. Two changes were needed, and the order matters:
- Vocabulary first. WordPiece maps an entire word to
[UNK]if any character is missing from the vocabulary. Patching the normalizer alone would have made output strictly worse. Five tokens were injected directly intomodel.vocab(32,000 → 32,005).add_tokens()does not work here: it creates standaloneAddedTokens that bypass WordPiece, so the decoder inserts spaces around them. - Then
clean_text=False.
New embedding rows were warm-started (##x copies the row for x; ZWJ takes the
matrix mean), then fine-tuned.
tok = AutoTokenizer.from_pretrained("kaan84/sinhala-print-trocr")
s = "ශ්\u200Dරී"
tok.decode(tok.encode(s, add_special_tokens=False)) == s # True
Results
Controlled ablation: identical base checkpoint, data, seed (42), epochs and effective batch size. The tokenizer is the only difference. Held-out 5% split, 349 lines.
| pilot (as published) | control (retrained, old tokenizer) | this model | |
|---|---|---|---|
| char-CER (codepoint) | 0.1820 | 0.1709 | 0.1690 |
| g-CER (grapheme cluster) | 0.2045 | 0.1886 | 0.1852 |
| emitted any ZWJ | 0/20 (impossible) | 0/20 (impossible) | 2/20 |
| conjunct recall (exact) | 0/19 | 0/26 | 0/26 |
Compare against control, not against the pilot — the pilot-to-fixed gap includes extra training and would overstate the change.
Reading these numbers honestly
- The capability was restored and is used. The control arm cannot emit ZWJ at all; this model does, on 2 of 20 conjunct-bearing eval lines. That asymmetry is structural, not statistical.
- Recognition did not improve. 0.1709 → 0.1690 char-CER is noise.
- Exact conjunct recall is still 0. Two reasons, both measured: ZWJ is 0.118% of training graphemes, giving almost no signal for a freshly-initialised embedding; and at ~17% CER the surrounding words are usually wrong anyway, so an exact grapheme match rarely has the chance to occur.
The fix removes a hard ceiling. It does not improve accuracy at current error rates.
Metric note
g-CER uses grapheme clusters from
grapheme-kit. Report it alongside
codepoint CER, never instead of it: grapheme-CER runs higher when errors corrupt a
cluster from inside (a stripped ZWJ) and lower when a whole multi-codepoint
grapheme is misread. A number that falls when you switch units is a change of ruler,
not a better model.
Limitations
- Synthetic training and evaluation. Same generator, same five fonts. These numbers largely measure memorisation of one renderer.
- Line-level only. Pages must be segmented first.
- Conjunct placement is unsolved. The model can emit ZWJ; it rarely places it correctly.
- Training data contains stray joiners. Two of the five injected tokens
(
' ','##‼') arose from ZWJ adjacent to spaces and punctuation in the corpus. They are unreachable in practice (BertPreTokenizersplits on whitespace) but indicate upstream noise. - One source row had null text and was filtered.
Real documents
avishadilhara/sinhala-ocr-lk-acts-1010 contains full page scans (~1650×2450 to
2480×3509, 1,200–2,500 characters per page), while this model reads single lines.
Pages must be segmented before recognition.
Preliminary, one page from the test split with Tesseract layout analysis: 37 lines detected, char-CER 0.4657. Page-level ground truth means concatenated line predictions conflate recognition errors with reading-order errors. A full 202-page benchmark is pending.
Next steps
- Reduce baseline CER — at ~17% on single-font synthetic data, it caps everything downstream.
- Test the sparsity hypothesis by oversampling the 315 conjunct-bearing lines.
- Full 202-page real-document benchmark.
Related work
- Velayuthan & Sarveswaran (2025), Egalitarian Language Representation in Language Models: It All Begins with Tokenizers, COLING 2025.
- Nisfer et al. (2026), grapheme-kit: Grapheme-Level Metrics and Text Processing for Multilingual NLP.
The defect fixed here is in the normalizer, not the pre-tokenizer.
BertPreTokenizer preserves ZWJ and yields one pre-token per conjunct, so the
pre-tokenization compression ceiling described in the COLING paper does not apply to
this tokenizer.
- Downloads last month
- 24
Model tree for kaan84/sinhala-print-trocr
Base model
Ransaka/TrOCR-Sinhala