/ Модель

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

Лицензия
CC BY-SA 4.0
Архитектура
extractor · dense
Базовая модель
gliner2.5-multi-v1
Библиотека
gliner2
Опубликована
4 окт 2026

gliner2-arabic-saudi

Разметка токеновТекст → label

Модель на базе GLiNER2 для извлечения сущностей и структурированных данных, классификации и поиска отношений в арабских текстах, особенно саудовских.

38скачиваний за 30 дней0лайковвеса обновлены7 окт 2026

Бенчмарки

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

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

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

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

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

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

GLiNER2 Arabic — Saudi round 2 — это дообученная модель извлечения информации на базе мультиязычной fastino/gliner2.5-multi-v1. В карточке указана архитектура GLiNER2 с одним энкодером mDeBERTa и четырьмя специализированными головами; размер — 287 млн параметров. Модель рассчитана прежде всего на арабский текст, включая саудовский диалект, и поддерживает извлечение сущностей, классификацию, формирование структурированных записей и извлечение отношений по заданным схемам.

Модель подходит для поиска персон, организаций, мест, дат, денежных сумм, процентов, времени, телефонов и идентификаторов, а также для определения тональности, сарказма и диалекта. Извлечение записей носит экстрактивный характер: значения должны присутствовать в исходном тексте. Для извлечения сущностей рекомендуется передавать названия меток на арабском и английском языках в указанном карточкой формате. Перед обработкой следует нормализовать арабский текст, удаляя диакритические знаки и татвиль, иначе токенизация может давать некорректные смещения.

На небольшом вручную составленном бенчмарке саудовских текстов авторы сообщают F1 0,802 при запросе десяти типов сущностей одновременно; результаты на отложенных наборах различаются в зависимости от состава меток. Карточка отмечает ошибки на границах сущностей с арабскими приставками, а также недостаточную надёжность некоторых полей персональных данных на новых текстах поддержки. Модель опубликована под лицензией CC BY-SA 4.0.

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

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

Ограничения

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

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

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

Открыть на Hugging Face

GLiNER2 Arabic — Saudi round 2

One mDeBERTa encoder, four heads, tuned for Saudi text. This is the second round of Elafnawaf/gliner2-arabic-multitask, a GLiNER2 boundary checkpoint (287M parameters) built on fastino/gliner2.5-multi-v1. Round 1 beat the base model on its held-out split and then lost to it on realistic Saudi sentences: asked for eight types at once it returned no dates, amounts or percentages. Round 2 fixes that in the training data and is measured on a hand-written Saudi benchmark.

What changed from round 1

  • Negatives only for labels a corpus annotates. WikiANN/IAHLT sentences no longer carry the 14 labels they never annotate as explicit negatives; the label subset is sampled per example and shuffled at train time.
  • Real numeric mentions. Regex-labelled dates, Hijri dates, money, percentages, times, phones and IDs on SANAD news and Gulf tweets; 9k generated Saudi sentences (half in dialect) that put those types beside people, organisations and places; ShamNER spoken Levantine for clean entity boundaries.
  • Type-balanced replay (person/org/place from 76 % to 50 % of mentions) and class-balanced sentiment, sarcasm and dialect with ArSarcasm v2, IADD and Gulf tweets.
  • Started from a 50 % weight average of round 1 and the base model, then one epoch (129k examples, fp32).

Ask for every type you need in one request. Splitting the label list, which helped round 1, now costs 0.04 F1.

Results

Saudi benchmark (42 sentences, 20 in Saudi dialect, 179 mentions, mention-level F1, all 10 types asked at once)

modelF1PRdialectMSA
fastino/gliner2.5-multi-v1 (base)0.7830.7900.7770.7280.829
Elafnawaf/gliner2-arabic-multitask (round 1)0.6500.7560.5700.6840.615
soup α=0.5 of round 1 and base0.8020.8360.7710.7450.852
this model (round 2)0.8950.9130.8770.8500.935
typenbaseround 1round 2
شخص / person120.5930.6400.833
منظمة / organization390.7840.8270.923
مكان / place390.7540.6400.694
تاريخ / date350.7940.4580.928
وقت / time90.6000.0001.000
مبلغ مالي / money250.9600.7501.000
نسبة مئوية / percentage90.9470.5001.000
رقم جوال / mobile number60.8000.9091.000
رقم الهوية الوطنية / national id number30.5001.0001.000
مطار / airport21.0001.0001.000

Conventions of the benchmark: person without title, organisation without a generic شركة, numeric dates without يوم, weekday names with it, clock times without الساعة, amounts with their currency word. Seven of round 2's 37 remaining errors are preposition clitics (بجدة returned for جدة), the convention WikiANN and IAHLT follow.

Held-out split (data/gliner test, 1,500 sentences of WikiANN, IAHLT, Wojood and synthetic PII)

queryround 1round 2
all 17 labels0.6940.586
only the labels each sentence's corpus annotates0.6690.667

The 17-label number falls because WikiANN and IAHLT never annotated dates, ordinals or cardinals, so a model that now finds them is charged a false positive for each one (423 of round 2's extra false positives). On the labels the corpora annotate the two rounds tie.

Other heads (round-2 dev mix, 600 examples each; the start checkpoint is the 50 % soup)

headstartround 2
structure, field F10.6450.867
relations, triple F1 (REDFM)0.3810.391
topic / الموضوع accuracy0.879 / 0.9810.966 / 1.000
sentiment / المشاعر accuracy0.734 / 0.6810.766 / 0.714
speech act / نوع الخطاب accuracy0.641 / 0.6570.688 / 0.642
dialect / اللهجة accuracy0.155 / 0.1720.333 / 0.356

Known limitation: the dialect task over-predicts خليجي (80 % of its predictions on the dev mix), because Gulf sources outweigh the others in this round's mix. Treat dialect as a coarse signal.

Quick start

pip install "gliner2[local]"
from gliner2.auto import AutoExtractor

# AutoExtractor reads the architecture field in config.json and picks the right
# class. Plain GLiNER2.from_pretrained is span-only and refuses this checkpoint.
model = AutoExtractor.from_pretrained("Elafnawaf/gliner2-arabic-saudi")

Normalise first — this is not optional

GLiNER's word splitter is \w+(?:[-_]\w+)*|\S, and Python's \w does not match Arabic diacritics. A diacritised word shatters into one token per character and every span offset after it is meaningless. Tatweel (ـ) fails the other way: it does match \w, so it survives glued to a real word. Strip both before inference:

import re, unicodedata

_DIACRITICS = re.compile(r"[\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06ED]")
_INVISIBLE  = re.compile(r"[\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]")

def normalise(text: str) -> str:
    text = unicodedata.normalize("NFC", text)
    text = _DIACRITICS.sub("", text)       # حركات
    text = text.replace("\u0640", "")      # tatweel ـ
    text = _INVISIBLE.sub("", text)        # ZWJ / RLM / BOM ...
    return text

1 · Entities

text = normalise("أعلنت شركة أرامكو السعودية في الظهران يوم 5 مارس 2024 عن أرباح بلغت "
                 "121 مليار دولار، ويمكن التواصل عبر الرقم 0501234567.")

model.extract_entities(text, ["منظمة / organization", "مكان / place",
                              "مبلغ مالي / money", "رقم جوال / mobile number"])
# {'entities': {'منظمة / organization': ['أرامكو السعودية'],
#               'مكان / place': ['الظهران'],
#               'مبلغ مالي / money': ['121 مليار دولار'],
#               'رقم جوال / mobile number': ['0501234567']}}

# character offsets and confidence, e.g. for redaction
model.extract_entities(text, ["منظمة / organization"],
                       include_spans=True, include_confidence=True)
# {'entities': {'منظمة / organization': [
#     {'text': 'أرامكو السعودية', 'confidence': 0.866, 'start': 11, 'end': 26}]}}

The entity head was trained with bilingual label names of the form عربي / english (full list below). Use those exact strings for the best precision; plain منظمة or organization also work.

2 · Classification

review = normalise("الخدمة كانت ممتازة والتوصيل وصل قبل الموعد، شكراً لكم")

model.classify_text(review, {
    "المشاعر":    ["إيجابي", "سلبي", "محايد"],
    "نوع الخطاب": ["سؤال", "طلب", "شكوى", "تعبير", "إعلان", "أخرى"],
})
# {'المشاعر': 'إيجابي', 'نوع الخطاب': 'تعبير'}

# the same task, asked in English, with confidence
model.classify_text(review, {"sentiment": ["positive", "negative", "neutral"]},
                    include_confidence=True)
# {'sentiment': {'label': 'positive', 'confidence': 0.998}}

model.classify_text(normalise("والله الجو اليوم يجنن، بس الزحمة تقتل"),
                    {"اللهجة": ["خليجي", "مصري", "شامي", "مغاربي", "فصحى"]})
# {'اللهجة': 'فصحى'}

3 · Structured records (JSON)

news = normalise("تأسست شركة الاتصالات السعودية عام 1998 ومقرها الرياض، "
                 "ويرأسها المهندس عليان الوتيد.")

model.extract_json(news, {"شركة": ["الاسم", "المقر", "سنة التأسيس", "الرئيس"]})
# {'شركة': [{'الاسم': ['الاتصالات السعودية'], 'المقر': ['الرياض'],
#            'سنة التأسيس': ['1998'], 'الرئيس': ['عليان الوتيد']}]}

Field values are lists (a field may occur more than once) and extraction is extractive: every value should be a substring of the input. A value that is not is a hallucination — log it. Richer schemas from the gliner2 API (ChoiceField for a classification inside a record, list-valued fields, several records of the same name under an occurrence_policy) are supported and were part of training.

4 · Relations

model.extract_relations(news, ["المقر", "تاريخ التأسيس", "المهنة"])
# {'relation_extraction': {'المقر': [['شركة الاتصالات السعودية', 'الرياض']],
#                          'تاريخ التأسيس': [['شركة الاتصالات السعودية', '1998']],
#                          'المهنة': []}}

model.extract_relations(news, ["headquarters location", "inception"])
# {'relation_extraction': {'headquarters location': [['شركة الاتصالات السعودية', 'الرياض']],
#                          'inception': [['شركة الاتصالات السعودية', '1998']]}}

Everything at once

from gliner2 import Schema

schema = (Schema()
          .entities(["شخص / person", "منظمة / organization", "مكان / place"])
          .classification("المشاعر", ["إيجابي", "سلبي", "محايد"])
          .structure("شركة").field("الاسم").field("المقر")
          .relations(["المقر"]))
model.extract(news, schema)
# {'entities': {'شخص / person': ['عليان الوتيد'],
#               'منظمة / organization': ['شركة الاتصالات السعودية'],
#               'مكان / place': ['الرياض']},
#  'المشاعر': 'محايد',
#  'شركة': [{'الاسم': ['شركة الاتصالات السعودية'], 'المقر': ['الرياض']}],
#  'relation_extraction': {'المقر': [['شركة الاتصالات السعودية', 'الرياض']]}}

5 · Multi-label classification

One task, several true labels. Ask with multi_label=True through the schema builder:

from gliner2 import Schema

ticket = normalise("تواصل معنا العميل سعود الدوسري، رقم الهوية 1098765432، عبر الجوال 0551234567 "
                   "بخصوص فاتورة بقيمة 450 ريالاً صادرة بتاريخ 3 مايو 2024 من فرع الرياض.")
TYPES = ["شخص / person", "منظمة / organization", "مكان / place", "تاريخ / date",
         "مبلغ مالي / money", "رقم جوال / mobile number", "رقم الهوية الوطنية / national id number"]

model.extract(ticket, Schema().classification("أنواع الكيانات", TYPES, multi_label=True))
# {'أنواع الكيانات': ['شخص / person', 'مكان / place', 'تاريخ / date',
#                    'رقم جوال / mobile number', 'رقم الهوية الوطنية / national id number']}

On held-out data this returns the exact label set 77% of the time (per-label F1 0.94). It is a cheap first pass before running the entity head on a large corpus.

6 · Records with choice fields and list fields

A choices=[...] field is a classification inside a record: its value comes from the list, not from the text. dtype="list" lets a field hold several values. Together they give you an extracted record plus a decision in one call, which is the shape a PDPL redaction log needs.

record = (Schema().structure("سجل")
          .field("شخص / person", dtype="list")
          .field("رقم الهوية الوطنية / national id number")
          .field("نوع السجل", choices=TYPES)
          .field("يحتوي بيانات شخصية", choices=["نعم", "لا"]))

model.extract(ticket, record)
# {'سجل': [{'شخص / person': ['سعود الدوسري'],
#           'رقم الهوية الوطنية / national id number': ['1098765432'],
#           'نوع السجل': ['شخص / person', 'رقم الهوية الوطنية / national id number'],
#           'يحتوي بيانات شخصية': ['لا']}]}

Read the last line carefully. On the held-out set the record-type field is right 96% of the time and the personal-data flag 100%, but on fresh support text the flag under-fires, as above, where an ID number is present and the answer should be نعم. Do not let the model's flag be the only gate: derive it from the extracted fields (bool(record["شخص / person"] or record["رقم الهوية الوطنية / national id number"])) and keep the choice field as a second opinion.

7 · Several records of one name from one text

Records were trained with mode/occurrence_policy metadata so that one text can yield a list of records. This is the weakest shape in the current checkpoint:

meeting = normalise("حضر الاجتماع المهندس فهد العتيبي من شركة أرامكو، والدكتورة ريم الشهري من "
                    "جامعة الملك سعود، والأستاذ ماجد القحطاني من هيئة الاتصالات.")

model.extract(meeting, Schema().structure("مشارك", mode="natural", occurrence_policy="all")
                                .field("الاسم").field("الجهة"))
# {'مشارك': [{'الاسم': 'فهد العتيبي', 'الجهة': ['أرامكو']}]}      <- one of three

Held-out pair F1 is 0.68 with recall as the limit (about one record returned per two or three in gold). When you need every participant, ask the entity head, which does return them all, and pair by proximity yourself:

model.extract_entities(meeting, ["شخص / person", "منظمة / organization"])
# {'entities': {'شخص / person': ['فهد العتيبي', 'ريم الشهري'],
#               'منظمة / organization': ['أرامكو', 'جامعة الملك سعود', 'هيئة الاتصالات']}}

8 · New labels, explained rather than named

The schema is text, so a label the model never saw can carry a description. Pass a dict instead of a list, for entity types and for classification labels alike:

model.extract(ticket, Schema().entities({
    "رقم مرجعي": "رقم يعرّف عميلاً أو وثيقة، مثل رقم الهوية أو رقم الفاتورة",
    "مبلغ":      "قيمة مالية مع عملتها",
}))
# {'entities': {'رقم مرجعي': ['1098765432'], 'مبلغ': ['450 ريالاً']}}

model.extract(ticket, Schema().classification("نوع الطلب", {
    "شكوى":     "العميل غير راضٍ عن خدمة أو فاتورة",
    "استفسار":  "العميل يسأل عن معلومة",
    "طلب خدمة": "العميل يريد تفعيل أو إلغاء خدمة",
}))
# {'نوع الطلب': 'استفسار'}

Relation descriptions are accepted by the API but did not help this checkpoint on business relations (شراكة, استحواذ came back empty); the relation head knows the 32 Wikidata predicates listed below.

9 · Batches and long documents

Every extract_* call has a batch_* twin that takes a list of texts, and a *_long twin that chunks a document (default 384 tokens with 64 of overlap) and merges the spans:

model.batch_extract_entities([ticket, meeting], ["شخص / person", "منظمة / organization"],
                             batch_size=8, threshold=0.3)
# [{'entities': {'شخص / person': ['سعود الدوسري'], 'منظمة / organization': []}},
#  {'entities': {'شخص / person': ['فهد العتيبي', 'ريم الشهري'],
#                'منظمة / organization': ['أرامكو', 'جامعة الملك سعود', 'هيئة الاتصالات']}}]

model.extract_entities_long(long_document, ["شخص / person", "رقم جوال / mobile number"],
                            chunk_size=384, chunk_overlap=64, include_spans=True)

Batching sorts by length internally; on one L4 the entity head scores about 200 sentences per second at batch 8.

threshold (default 0.5) is accepted by every call; lower it for recall, raise it for precision. Per-label entity thresholds tuned on the dev set are in inference_config.json, together with every task name and label set the model was trained on, in both languages.

What it was trained to answer

Entity types (17)

تاريخ / date, ترتيب / ordinal, رقم الهوية الوطنية / national id number, رقم جوال / mobile number, شخص / person, عدد / cardinal number, عملة / currency, عنوان / address, كمية / quantity, مبلغ مالي / money, مطار / airport, مكان / place, منظمة / organization, موقع إلكتروني / website, نسبة مئوية / percentage, وحدة قياس / unit of measurement, وقت / time

Classification tasks

task (ar)task (en)labels
اللهجةdialectمصري (egypt), خليجي (gulf), شامي (levant), مغاربي (magreb), فصحى (msa)
السخريةsarcasmغير ساخر (non-sarcastic), ساخر (sarcastic)
المشاعرsentimentسلبي (negative), محايد (neutral), إيجابي (positive), سلبي (Negative), محايد (Neutral), إيجابي (Positive), مختلط (Mixed)
نوع الخطابspeech actتأكيد (Assertion), توصية (Recommendation), تعبير (Expression), سؤال (Question), طلب (Request), أخرى (Miscellaneous)
الموضوعtopicتقنية (Tech), اقتصاد (Finance), سياسة (Politics), دين (Religion), طب (Medical), ثقافة (Culture), رياضة (Sports)

Relation types (32, Wikidata predicates from REDFM)

EnglishArabic
authorالمؤلف
capitalالعاصمة
cast memberمن طاقم العمل
charactersالشخصيات
childالابن
countryالدولة
country of citizenshipبلد الجنسية
directorالمخرج
followsيتبع
founded byأسسها
genreالنوع
headquarters locationالمقر
inceptionتاريخ التأسيس
instance ofنوع من
leagueالدوري
located in or next to body of waterيقع على مسطح مائي
locationالموقع
manufacturerالشركة المصنعة
member ofعضو في
mouth of the watercourseمصب النهر
named afterسمي نسبة إلى
notable workعمل بارز
occupationالمهنة
owned byمملوك لـ
part ofجزء من
participantمشارك
place of birthمكان الميلاد
replacesيحل محل
shares border withيحد
siblingشقيق
sportالرياضة
spouseالزوج

Training details

Start: weight average (α=0.5) of round 1 and fastino/gliner2.5-multi-v1. One epoch over 129,105 examples (entities 48k, classification 26k + ArSAS 12k + SANAD 10k, SREDFM relations 16k, structures 23k), effective batch 16, encoder LR 5e-06, task LR 0.0002, fp32 (bf16 diverges on this model), gradient checkpointing, gliner2 SamplingConfig with shuffle_entities=True, remove_entity_prob=0.2. 143 minutes on one NVIDIA L4. Every example was passed through gliner2's own collate before training; 5 were rejected.

Files

model.safetensors, config.json, encoder_config/, tokenizer files, and inference_config.json (entity labels and thresholds, task names in both languages, relation types, benchmark numbers).

Credits

Base model by Fastino AI. Data: WikiANN, IAHLT/MAFAT, Wojood sample (SinaLab), ArSarcasm v1/v2, ArSAS, SANAD, SREDFM/REDFM (Babelscape), IADD, Gulf Arabic Tweets 2018–2020, ShamNER. Trained on an OpenShift AI workbench.

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

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

GPU в ailist

Универсальный рантайм · gliner2

Нельзя запустить

Модели GLiNER требуют отдельного рантайма.

Класс архитектуры
BoundaryExtractor
Ревизия весов
9f93394

0 моделей