Uzbek NER: exact spans for ORG / NAME / GEO (Latin and Cyrillic)
Named entity recognition for Uzbek text, built for brand monitoring. The model finds organizations, person names and locations and returns exact character offsets into the original string.
It works on both Uzbek scripts — Latin (Toshkentda) and Cyrillic
(Тошкентда) — and on mixed text, where the two are combined in one message.
No transliteration is needed on your side.
| Classes | ORG (organizations, brands, media), NAME (people), GEO (places) |
| Scripts | Uzbek Latin, Uzbek Cyrillic, mixed |
| Architecture | biaffine span decoder on top of mmBERT-base (308M parameters) |
| Input length | any — long texts are split into overlapping 512-token windows |
| License | Apache-2.0 |
Quick start
pip install torch transformers safetensors huggingface_hub
import sys
from huggingface_hub import snapshot_download
path = snapshot_download("Slenser0/uzbek-ner-mmbert-span")
sys.path.insert(0, path)
from uzner import UzbekNER
ner = UzbekNER.from_pretrained(path) # picks CUDA, then Apple MPS, then CPU
ner.predict("Toshkentda Oqtepa Lavash filiali ochildi, Kun.uz xabar berdi.")
[{'label': 'GEO', 'start': 0, 'end': 10, 'text': 'Toshkentda', 'score': 0.997},
{'label': 'ORG', 'start': 11, 'end': 24, 'text': 'Oqtepa Lavash', 'score': 0.9985},
{'label': 'ORG', 'start': 42, 'end': 48, 'text': 'Kun.uz', 'score': 0.9994}]
Pass a list of strings to process a batch: ner.predict([text1, text2, ...])
returns one list of entities per text. From the command line:
python uzner.py "Toshkentda Oqtepa Lavash filiali ochildi."
Examples — Latin, Cyrillic, mixed
These are real outputs of this model.
Latin script
Samsung Oʻzbekistonga yangi smartfonlarini olib keldi, dedi Aziz Karimov.
ORG [ 0, 7) Samsung
GEO [ 8,21) Oʻzbekistonga
NAME [60,72) Aziz Karimov
Cyrillic script
Тошкентдаги Artel заводида Азиз Каримов янги линияни ишга туширди.
GEO [ 0,11) Тошкентдаги
ORG [12,17) Artel
NAME [27,39) Азиз Каримов
Наманганда Акмалжон Каримов Ўзбекистон темир йўллари компаниясида ишлайди.
GEO [ 0,10) Наманганда
NAME [11,27) Акмалжон Каримов
ORG [28,65) Ўзбекистон темир йўллари компаниясида
Mixed script in one message
Centrum Air Samarqand aeroportida рейсни икки соатга кечиктирди.
ORG [ 0,11) Centrum Air
GEO [12,33) Samarqand aeroportida
Note on span boundaries
The model follows the annotation convention of its training data: an attached
Uzbek suffix is part of the entity (Toshkentda, Oʻzbekistonga, Наманганда),
while a separately written function word is not (KFC da → KFC). Outer
quotes, brackets, #, @ and trailing punctuation are excluded; inner
apostrophes, dots and hyphens stay (Fargʻona, Kun.uz, Coca-Cola).
Entities are flat — no nesting.
If you need the bare name without the suffix, strip it downstream; the offsets always point at the exact substring of your input.
Quality
The metric is strict exact-span micro-F1: an entity counts only if the class and both character boundaries match exactly.
Numbers on a held-out validation set of 1,500 documents (7,698 gold spans). They were measured on a twin model with the same architecture and recipe, trained without the validation split, so they are not inflated by memorization. This released checkpoint additionally saw the validation split during training.
| Script | Documents | F1 |
|---|---|---|
| Uzbek Latin | 951 | 0.9058 |
| Uzbek Cyrillic | 390 | 0.8803 |
| Mixed | 159 | 0.9184 |
| Class | Precision | Recall | F1 |
|---|---|---|---|
| ORG | 0.8775 | 0.8620 | 0.8697 |
| NAME | 0.9265 | 0.9237 | 0.9251 |
| GEO | 0.9174 | 0.9029 | 0.9101 |
| micro | 0.9065 | 0.8950 | 0.9007 |
How it works
Instead of tagging tokens one by one (BIO), the model scores whole candidate spans. Every word n-gram of up to 8 words becomes a candidate. Its first and last subword tokens are projected to 256 dimensions each and combined by a biaffine form, plus a learned span-width embedding:
logits_k = [h_start ; 1]ᵀ · U_k · [h_end ; 1] + width_k k ∈ {O, ORG, NAME, GEO}
Overlapping candidates are resolved greedily by score, and the threshold is
0.35 by default (ner.predict(text, threshold=...)). Span scoring was chosen
after an error analysis of a BIO baseline: in 64% of its boundary false positives
only the end of the span was off — the model misjudged how many words to include.
Training data: 14,500 annotated Uzbek documents (13,000 train + 1,500 validation). Every Cyrillic document was also added as a Latin transliteration with remapped offsets, 18,732 documents in total. Three epochs, boundary smoothing ε = 0.1, learning rate 3e-5 for the encoder and 1e-3 for the head, seed 42. The dataset itself is not redistributed here.
Speed
uzner.py processed 300 validation documents of mixed length (5 to 9,216
characters) in 4.7 s on an Apple M4 Pro GPU. On an NVIDIA A100 the same model
served through HTTP handles about 96 documents per second, with a 20 ms median
latency for a single short request.
Limitations
- Three classes only: products, events, dates and other types are not labeled.
- Cyrillic text is a little harder than Latin (0.8803 vs 0.9058 F1).
- Names that never appeared in training are harder than familiar ones.
- Quality on text sources unlike the training data is not measured.
- Offsets include attached suffixes by design, see above.
Files
| File | |
|---|---|
model.safetensors |
weights: encoder and span head |
config.json |
ModernBERT encoder config |
span_config.json |
window size, stride, hidden size |
tokenizer.json, tokenizer_config.json |
mmBERT tokenizer |
uzner.py |
self-contained inference, no other files needed |
uzner.py reproduces the original inference code exactly: on 300 validation
documents it returned the same spans with the same scores.
License and attribution
Apache License 2.0, see LICENSE and NOTICE. The encoder is fine-tuned from
jhu-clsp/mmBERT-base
(MIT License, arXiv:2509.06888).
На русском
Распознавание именованных сущностей для узбекского языка: организации, имена людей и места с точными символьными координатами в исходной строке.
Работает и на латинице, и на кириллице, а также на смешанном тексте, где обе письменности встречаются в одном сообщении. Транслитерировать текст заранее не нужно.
import sys
from huggingface_hub import snapshot_download
path = snapshot_download("Slenser0/uzbek-ner-mmbert-span")
sys.path.insert(0, path)
from uzner import UzbekNER
ner = UzbekNER.from_pretrained(path)
ner.predict("Тошкентдаги Artel заводида Азиз Каримов янги линияни ишга туширди.")
# GEO «Тошкентдаги», ORG «Artel», NAME «Азиз Каримов»
Граница сущности. Приклеенный узбекский аффикс входит в сущность
(Toshkentda, Наманганда), отдельно написанное служебное слово — нет
(KFC da → KFC). Так устроена разметка обучающих данных.
Качество — строгий exact-span micro-F1, засчитывается только полное совпадение класса и обеих границ. На отложенной валидации из 1500 документов: micro 0.9007; латиница 0.9058, кириллица 0.8803, смешанный текст 0.9184.
Числа сняты на модели-близнеце того же рецепта, обученной без валидационной выборки. Этот выложенный чекпоинт видел валидацию при обучении.
Лицензия Apache-2.0.
- Downloads last month
- 25
Model tree for Slenser0/uzbek-ner-mmbert-span
Base model
jhu-clsp/mmBERT-base