/ Модель

Характеристики

Контекст
8 192 токена
Параметры
69,7 млн
Лицензия
MIT
Архитектура
modernbert · dense
Базовая модель
mmBERT-small-en-az
Библиотека
transformers
Опубликована
16 мар 2026

pii-ner-azerbaijani-v3

Разметка токеновТекст → label4 профессии

Компактная модель LocalDoc на ModernBERT для поиска и классификации персональных данных в азербайджанском и английском тексте.

2 346скачиваний за 30 дней0лайковвеса обновлены16 мар 2026

Бенчмарки

Как модель справилась с задачами пользователей ailist и что о ней публикуют открытые лидерборды

Публичные оценки

Из открытых лидербордов. Сравнивайте результаты одного теста при одинаковых условиях — отсутствие оценки не значит, что модель хуже.

В подключённых источниках результатов для этой модели пока нет.

Что умеет модель

Описание составлено по карточке модели и метаданным Hugging Face

LocalDoc разработала модель распознавания персональных данных в азербайджанском тексте на основе компактного энкодера ModernBERT. Она выполняет NER и выделяет 15 типов сущностей, включая имена, фамилии, адреса, электронную почту, номера телефонов, паспортные и налоговые номера. Модель содержит 69,7 млн параметров, принимает текст и возвращает метки токенов; контекст — до 8192 токенов. В README указано, что для инференса текст нужно предварительно перевести в нижний регистр; модель обучена на азербайджанских и английских данных с синтетическими примерами, транслитерациями и сложными отрицательными примерами.

Модель предназначена для обнаружения и последующей защиты персональных данных в сообщениях, документах и других текстах. README приводит F1 0,9974 на оценочном наборе и заявляет более высокую скорость по сравнению с XLM-RoBERTa, однако качество на реальных данных может отличаться. Ограничения включают слабую надёжность для языков помимо азербайджанского и английского, возможное неверное распознавание названий районов как имён и проблемы с телефонными номерами через дефис. Лицензия MIT разрешает коммерческое использование и распространение с соблюдением её условий.

Сценарии применения

  • поиск имён и фамилий в азербайджанских документах
  • обнаружение телефонов, адресов и электронной почты в сообщениях
  • выявление паспортных и налоговых номеров перед публикацией текста
  • подготовка текстов к обезличиванию и защите персональных данных

Ограничения

Перед инференсом текст требуется перевести в нижний регистр; синтетические обучающие данные могут не охватывать все реальные шаблоны PII. На других языках качество низкое, названия районов могут ошибочно распознаваться как имена, а телефонные номера с дефисами могут разбиваться токенизатором.

Для каких профессий подходит4

Разделы каталога профессий, в задачах которых используется эта модель

Все профессии

Карточка модели

README от автора на Hugging Face, как он его опубликовал

Открыть на Hugging Face

PII NER Azerbaijani v3

A high-accuracy Named Entity Recognition model for detecting Personally Identifiable Information (PII) in Azerbaijani text. Built on LocalDoc/mmBERT-small-en-az (ModernBERT architecture), this model is 4x smaller and faster than XLM-RoBERTa while achieving higher accuracy.

Key Features

  • F1 = 0.9974 — all 15 entity types above 0.99
  • 69M parameters — 4x smaller than XLM-RoBERTa (278M)
  • 3-4x faster inference — ModernBERT architecture with Flash Attention 2
  • Transliteration-robust — works with both Şərifova and Sherifova
  • Hard negative trained — distinguishes "bakı küləyi" (weather) from "bakıda yaşayır" (address)
  • Lowercase input — model is trained on lowercased text for case-insensitive detection

Model Details

MetricValue
Base ModelLocalDoc/mmBERT-small-en-az
ArchitectureModernBERT (22 layers, hidden=384)
Parameters69M
Model Size (fp32)0.26 GB
Max Sequence Length8,192 tokens
Training DataLocalDoc/pii_ner_azerbaijani_extended (530K rows)
Training Epochs5 (best at epoch 5)
LicenseMIT

Performance

Overall Metrics

MetricThis Model (69M)XLM-RoBERTa v2 (278M)
F10.99740.9746
Precision0.99670.9760
Recall0.99820.9732
False Positives (hard neg)14

Per-Entity F1 Scores

EntityF1EntityF1
GIVENNAME0.9974PASSPORTNUM0.9996
SURNAME0.9980TAXNUM0.9994
EMAIL0.9978TELEPHONENUM0.9993
DATE0.9936TIME0.9993
AGE0.9965CREDITCARDNUMBER0.9948
CITY0.9967STREET0.9926
IDCARDNUM0.9985BUILDINGNUM0.9976
ZIPCODE0.9978

Training Progress

EpochLossF1PrecisionRecall
10.01590.98390.97940.9889
20.00990.98770.98480.9908
30.00530.99490.99310.9967
40.00380.99720.99640.9980
50.00410.99740.99670.9982

Recognized Entities

GIVENNAME          — First name (e.g., "Əli", "Aysel")
SURNAME            — Last name (e.g., "Həsənov", "Məmmədova")
EMAIL              — Email address
TELEPHONENUM       — Phone number
DATE               — Date in various formats
TIME               — Time
AGE                — Age
IDCARDNUM          — ID card / FIN number
PASSPORTNUM        — Passport number
TAXNUM             — Tax identification number
CREDITCARDNUMBER   — Credit card number
CITY               — City name (as address, not adjective)
STREET             — Street name
BUILDINGNUM        — Building number
ZIPCODE            — ZIP/postal code

Usage

Quick Start

import torch
from transformers import AutoModelForTokenClassification, AutoTokenizer


class AzerbaijaniPiiNer:
    def __init__(self, model_name="LocalDoc/pii-ner-azerbaijani-v3"):
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForTokenClassification.from_pretrained(model_name)
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        self.model.to(self.device).eval()

        self.id2label = self.model.config.id2label

    def predict(self, text: str) -> list[dict]:
        """
        Detect PII entities in text.
        Input is lowercased for the model, but original casing is preserved in output.
        """
        original_text = text
        text_lower = text.lower()

        inputs = self.tokenizer(
            text_lower,
            return_tensors="pt",
            return_offsets_mapping=True,
            return_special_tokens_mask=True,
            truncation=True,
            max_length=512,
        )

        offsets = inputs.pop("offset_mapping")[0]
        special_mask = inputs.pop("special_tokens_mask")[0]
        inputs = {k: v.to(self.device) for k, v in inputs.items()}

        with torch.no_grad():
            logits = self.model(**inputs).logits

        predictions = torch.argmax(logits, dim=-1)[0].cpu()

        # Extract entities
        entities = []
        current = None

        for pred_id, offset, is_special in zip(predictions, offsets, special_mask):
            if is_special:
                if current:
                    entities.append(current)
                    current = None
                continue

            label = self.id2label[pred_id.item()]
            cs, ce = offset[0].item(), offset[1].item()

            if label.startswith("B-"):
                if current:
                    entities.append(current)
                current = {"label": label[2:], "start": cs, "end": ce}
            elif label.startswith("I-") and current and label[2:] == current["label"]:
                current["end"] = ce
            else:
                if current:
                    entities.append(current)
                    current = None

        if current:
            entities.append(current)

        # Map back to ORIGINAL text (preserve original casing)
        for ent in entities:
            raw = original_text[ent["start"]:ent["end"]]
            ent["value"] = raw.strip()
            if raw != raw.strip():
                offset = len(raw) - len(raw.lstrip())
                ent["start"] += offset
                ent["end"] = ent["start"] + len(ent["value"])

        return entities

    def anonymize(self, text: str, replacement: str = "***") -> str:
        """Replace all PII entities with a placeholder."""
        entities = self.predict(text)
        entities.sort(key=lambda x: x["start"], reverse=True)

        result = text
        for ent in entities:
            result = result[:ent["start"]] + replacement + result[ent["end"]:]

        return result

    def highlight(self, text: str) -> str:
        """Return text with entities marked: [LABEL: value]."""
        entities = self.predict(text)
        entities.sort(key=lambda x: x["start"], reverse=True)

        result = text
        for ent in entities:
            result = (
                result[:ent["start"]]
                + f"[{ent['label']}: {ent['value']}]"
                + result[ent["end"]:]
            )

        return result


# --- Example ---
if __name__ == "__main__":
    ner = AzerbaijaniPiiNer()

    examples = [
        # Original Azerbaijani
        "Hörmətli Əhməd Süleymanlı, 05.03.1987 tarixli müraciətiniz qəbul edildi. Əlaqə: 055-234-67-89.",

        # Transliterated (informal)
        "Hormetli Ehmed Suleymanlı, 05.03.1987 tarixli muracietiniz qebul edildi. Elaqe: 055-234-67-89.",

        # Mixed context with hard negatives
        "Bakı küləyi güclüdür, amma Əli Bakıda Nizami küçəsi 42-də yaşayır.",

        # Complex document
        "Müştəri: Gülarə Məmmədli, 67 yaş. Pasport: AZE 1234567. Email: gulare@mail.az. Tel: 012-456-78-90.",

        # English-Azerbaijani mix
        "Dear customer Əli Həsənli, your order shipped to Bakı, 28 May küçəsi 12. Contact: ali@company.com.",
    ]

    for text in examples:
        print(f"\nInput:     {text}")
        print(f"Highlight: {ner.highlight(text)}")
        print(f"Anonymize: {ner.anonymize(text)}")
        for ent in ner.predict(text):
            print(f"  {ent['label']:20s} → \"{ent['value']}\" ({ent['start']}:{ent['end']})")

Expected Output

Input:     Hörmətli Əhməd Süleymanlı, 05.03.1987 tarixli müraciətiniz qəbul edildi. Əlaqə: 055-234-67-89.
Highlight: Hörmətli [GIVENNAME: Əhməd] [SURNAME: Süleymanlı], [DATE: 05.03.1987] tarixli müraciətiniz qəbul edildi. Əlaqə: [TELEPHONENUM: 055-234-67-89].
Anonymize: Hörmətli *** ***, *** tarixli müraciətiniz qəbul edildi. Əlaqə: ***.
  GIVENNAME            → "Əhməd" (9:14)
  SURNAME              → "Süleymanlı" (15:25)
  DATE                 → "05.03.1987" (27:37)
  TELEPHONENUM         → "055-234-67-89" (82:95)

Pipeline Usage

from transformers import pipeline

ner_pipeline = pipeline(
    "token-classification",
    model="LocalDoc/pii-ner-azerbaijani-v3",
    aggregation_strategy="simple",
)

# Important: lowercase the input
text = "Əhməd Həsənov Bakıda yaşayır, telefonu 055-123-45-67."
results = ner_pipeline(text.lower())

for entity in results:
    print(f"{entity['entity_group']:20s} → \"{entity['word']}\" (score: {entity['score']:.4f})")

Training Details

Dataset

Trained on LocalDoc/pii_ner_azerbaijani_extended (530K rows):

  • Template-based data (~481K) — original + 3 transliteration strategies
  • LLM-generated PII (~25K) — natural sentences in diverse contexts
  • LLM-generated hard negatives (~15K) — trap words that look like PII
  • LLM-generated mixed (~10K) — real PII + traps in the same sentence

Why Hard Negatives Matter

Without hard negatives, the model marks every city name as PII:

  • ❌ "bakı küləyi güclüdür" → CITY: bakı (wrong — it's about weather)
  • ❌ "nərgiz çiçəkləri açılır" → GIVENNAME: nərgiz (wrong — it's a flower)

With hard negatives, the model learns context:

  • ✅ "bakı küləyi güclüdür" → no PII (weather context)
  • ✅ "əhməd bakıda yaşayır" → GIVENNAME: əhməd, CITY: bakı (address context)

Configuration

  • Optimizer: AdamW
  • Learning Rate: 3e-5 with cosine schedule
  • Warmup: 10%
  • Batch Size: 64
  • Weight Decay: 0.01
  • Max Length: 512
  • Early Stopping: patience=3 on F1
  • Preprocessing: all text lowercased before tokenization

Limitations

  • Lowercase input required — always call .lower() before inference
  • Synthetic training data — may not cover all real-world PII patterns
  • Phone numbers with dashes — in chat-style text, numbers like 055-987-65-43 may split. This is a known tokenizer limitation.
  • Azerbaijani and English only — other languages will produce poor results
  • District names — names like "Nəsimi" may be misidentified as personal names

Comparison with Previous Versions

v3 (this)v2 (XLM-RoBERTa)v1 (XLM-RoBERTa)
BasemmBERT-smallXLM-RoBERTaXLM-RoBERTa
Parameters69M278M278M
F10.99740.97460.9629
Hard neg FP14not tested
Transliterationyesnono
Speed3-4x faster1x1x

Citation

@misc{pii-ner-azerbaijani-v3,
  title={PII NER Azerbaijani v3},
  author={LocalDoc},
  year={2025},
  publisher={Hugging Face},
  url={https://huggingface.co/LocalDoc/pii-ner-azerbaijani-v3}
}

CC BY 4.0 License — What It Allows

The Creative Commons Attribution 4.0 International (CC BY 4.0) license allows:

✅ You Can:

  • Use the model for any purpose, including commercial use.
  • Share it — copy and redistribute in any medium or format.
  • Adapt it — remix, transform, and build upon it for any purpose, even commercially.

📝 You Must:

  • Give appropriate credit — Attribute the original creator.
  • Not imply endorsement — Do not suggest the original author endorses your use.

❌ You Cannot:

  • Apply legal terms or technological measures that restrict others from doing anything the license permits.

For more information, refer to the CC BY 4.0 license.

Contact

For questions or issues, contact LocalDoc at [v.resad.89@gmail.com].

Как запустить

На своей GPU в ailist. Цена — по текущим предложениям провайдеров

GPU в ailist

Универсальный рантайм · transformers · token-classification

Можно запустить

Подбираем GPU…

Класс архитектуры
ModernBertForTokenClassification
Ревизия весов
8b193cb

0 моделей