Instructions to use Elafnawaf/gliner2-arabic-multitask with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- GLiNER2
How to use Elafnawaf/gliner2-arabic-multitask with GLiNER2:
from gliner2 import GLiNER2 model = GLiNER2.from_pretrained("Elafnawaf/gliner2-arabic-multitask") # Extract entities text = "Apple CEO Tim Cook announced iPhone 15 in Cupertino yesterday." result = extractor.extract_entities(text, ["company", "person", "product", "location"]) print(result) - Notebooks
- Google Colab
- Kaggle
- GLiNER2 Arabic Multi-Task โ entities, classification, structured records, relations
- Quick start
- Normalise first โ this is not optional
- 1 ยท Entities
- 2 ยท Classification
- 3 ยท Structured records (JSON)
- 4 ยท Relations
- Everything at once
- 5 ยท Multi-label classification
- 6 ยท Records with choice fields and list fields
- 7 ยท Several records of one name from one text
- 8 ยท New labels, explained rather than named
- 9 ยท Batches and long documents
- What it was trained to answer
- Results
- Training data
- Training details
- Intended use and limitations
- Files
- Credits
- Quick start
GLiNER2 Arabic Multi-Task โ entities, classification, structured records, relations
One encoder, four heads, all fine-tuned for Arabic. This is a
GLiNER2 checkpoint (boundary architecture,
287M parameters, mDeBERTa-v3 encoder) built on fastino/gliner2.5-multi-v1. The base model's
extraction heads were trained on English and other European languages; the encoder had
seen Arabic, the heads had not. This checkpoint trains all four heads on Arabic
supervision so that a single model can, from one forward pass and with no task-specific
code:
| head | what you ask | what you get |
|---|---|---|
| entities | a list of entity types | every span of each type |
| classification | one or more tasks, each with its label set | one label per task (single-label) or several (multi-label) |
| structured extraction | a record schema (field names) | a JSON record whose values are substrings of the text |
| relations | a list of relation types | (head, tail) pairs for each type |
The schema is part of the input. GLiNER2 reads the task names and label names as
text, so the model has to have seen Arabic schemas to answer them. Half of the training
examples carried an Arabic schema (ุงูู
ุดุงุนุฑ โ ุฅูุฌุงุจู / ุณูุจู / ู
ุญุงูุฏ) and half the
English equivalent, with the same gold answer, so the finished model answers either
language. Everything is zero-shot in principle โ you can ask for types it never saw โ
but the tables below list what it was actually tuned on.
Architecture note. The boundary architecture pairs start and end positions directly, so any span length that fits the encoded window is reachable โ unlike the span architecture, which is capped at max_width: 8 tokens. Record metadata (occurrence_policy, field cardinality, anchored records) is live here.
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-multitask")
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)
| English | Arabic |
|---|---|
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 |
ุงูุฒูุฌ |
Results
Measured on held-out Arabic before and after the multi-task fine-tune. "Before" is the
base checkpoint fastino/gliner2.5-multi-v1 answering the same Arabic schemas.
| head | metric | before | after |
|---|---|---|---|
| entities | span-exact micro-F1 (1,500 test sentences, 17 types) | 0.352 | 0.693 |
| structure | field-level F1 (600 held-out records) | 0.242 | 0.791 |
| relations | triple F1 on REDFM Arabic, human-revised (344 sentences) | 0.171 | 0.406 |
Classification accuracy (600 held-out examples, per task)
| task | before | after |
|---|---|---|
dialect |
0.265 | 0.618 |
entity types present |
0.438 | 0.625 |
sarcasm |
0.264 | 0.806 |
sentiment |
0.588 | 0.758 |
speech act |
0.168 | 0.814 |
topic |
0.657 | 0.971 |
ุฃููุงุน ุงูููุงูุงุช |
0.538 | 0.692 |
ุงูุณุฎุฑูุฉ |
0.712 | 0.797 |
ุงูููุฌุฉ |
0.222 | 0.571 |
ุงูู
ุดุงุนุฑ |
0.639 | 0.732 |
ุงูู
ูุถูุน |
0.768 | 0.970 |
ููุน ุงูุฎุทุงุจ |
0.319 | 0.782 |
Relations are scored on Babelscape/REDFM Arabic, which is human-revised and has no
Arabic train split. Training used Babelscape/SREDFM Arabic, which is automatically
annotated โ so the relation number is measured against people, not against the
annotator the model learned from. evaluation.json in this repo holds the full gate
report.
The four extra shapes were scored separately on held-out dev examples (before โ after): multi-label per-label F1 0.58 โ 0.94, choice-field record type 0.01 โ 0.96 and personal-data flag 0.04 โ 1.00, several-records-per-text pair F1 0.53 โ 0.68, named-role relations triple F1 0.32 โ 0.51 (identical when asked as plain head/tail, so treat it as an ordinary relation result).
Training data
All supervision is public and Arabic. Each source is capped so that no head dominates the summed loss; the caps below are the number of examples drawn per source.
| source | used for | examples | link |
|---|---|---|---|
| WikiANN + IAHLT + Wojood sample + synthetic Saudi PII | entities | 40,000 | entities |
| ArSarcasm | sentiment, sarcasm, dialect | 12,000 | sarcasm |
| ArSAS | speech act, sentiment | 14,000 | arsas |
| SANAD | news topic | 14,000 | sanad |
| SREDFM (ar) | relations + free structure records | 22,000 | sredfm |
| entity gold โ records | structured extraction | 14,000 | โ |
| derived | multi-label classification | 10,000 | โ |
| derived | records with ChoiceField / list fields | 14,000 | โ |
| derived | several records per text | 7,000 | โ |
| derived | relations with named roles | 7,000 | โ |
Entity corpus (64,365 MSA sentences, converted to character offsets, 17 bilingual labels): wikiann 32,600, iahlt-mafat 26,449, synthetic-pii 4,380, wojood-sample 936. The synthetic PII sentences add Saudi national IDs, mobile numbers, addresses and websites, which none of the public corpora annotate.
The ner_struct, multilabel, rich_records, multi_instance and named_roles sets
are derived from the entity gold spans and from SREDFM's typed entities: the same text
re-expressed as a record, a multi-label task, a record with a ChoiceField, several
records of one name, and relations with named roles. They teach capabilities the plain
form does not exercise. Schema language was Arabic for 50% of examples and English
for the rest, always with the same gold answer.
Training details
| base checkpoint | fastino/gliner2.5-multi-v1 |
| trainable parameters | 287,355,159 (full fine-tune, no LoRA) |
| examples | 113,003 train / 7,215 dev after dropping 33 examples longer than 384 tokens |
| epochs | 2 (14,126 optimizer steps) |
| batch | 2 ร gradient accumulation 8 = effective 16 |
| learning rates | encoder 1e-5, task heads 5e-4, linear schedule, 10% warm-up |
| regularisation | weight decay 0.01, gradient clipping 1.0 |
| precision | fp32 โ with bf16 autocast this model's boundary head overflows within ~300 steps and every weight becomes NaN; fp32 was both stable and faster on an L4 |
| hardware | 1 ร NVIDIA L4 (24 GB), 4.6 h |
| checkpoint selection | lowest dev loss (evaluated every 1,766 steps) |
Intended use and limitations
Built for Arabic text analytics on customer-service, social and news text: PII detection and redaction, intent/sentiment/dialect tagging, pulling structured records out of messages, and light knowledge-graph population.
- Dialects. The entity corpus is Modern Standard Arabic. Dialect supervision comes only from ArSarcasm's five-way dialect label; Gulf coverage beyond that is untested.
- Relation predicates come from Wikipedia/Wikidata (
ุงูู ููุฉ,ุงูุนุงุตู ุฉ,ุงูุฒูุฌ). They are not domain relations for customer service or media monitoring; asking for such types works zero-shot but is not what the head was tuned on. - Ask for dates, amounts and percentages in short type lists. On natural prose the entity
head drops
ุชุงุฑูุฎ / date,ู ุจูุบ ู ุงูู / moneyandูุณุจุฉ ู ุฆููุฉ / percentagewhen they are requested together with the full 8-type list, yet finds them (0.7โ0.9 confidence) when asked with three or four types. Training passed all 17 labels as explicit negatives on WikiANN/IAHLT sentences, which taught "long list + prose = no numbers"; those types are also 57โ100% synthetic in training, so their held-out scores measure the template. Until the next round, call the entity head twice: once for person/organization/place, once for the numeric types. - Structured extraction is extractive. A returned value that is not a substring of the input is a hallucination; the evaluation counted zero on the held-out set, but check in production.
- Classification collapse and overconfidence. Dialect, sarcasm and sentiment lean heavily on
the majority label (
ูุตุญู,ุบูุฑ ุณุงุฎุฑ,ู ุญุงูุฏ), and the reported confidence is near 1.0 even when wrong, so it is not a usable signal for those tasks. Topic, speech act and message type are reliable. - Not a replacement for a rules layer on identifiers: pair the
ุฑูู ุงููููุฉ ุงููุทููุฉandุฑูู ุฌูุงูtypes with a regex validator (Arabic-Indic digits included).
Files
| file | purpose |
|---|---|
model.safetensors, config.json, encoder_config/ |
weights and architecture |
tokenizer.json, tokenizer_config.json, special_tokens_map.json |
mDeBERTa tokenizer |
inference_config.json |
every trained task name / label set (ar + en), per-label entity thresholds, normaliser note |
evaluation.json |
the before/after gate report the numbers above come from |
Credits
GLiNER2 by Fastino AI (fastino/gliner2.5-multi-v1, Apache-2.0). Data: WikiANN (Pan et al.),
IAHLT Arabic NER, ArSarcasm (Abu Farha & Magdy), ArSAS (Elmadany et al.), SANAD
(Einea et al.), REDFM / SREDFM (Huguet Cabot et al.). Licence of this checkpoint follows
the most restrictive source, CC BY-SA 4.0.
- Downloads last month
- -
Model tree for Elafnawaf/gliner2-arabic-multitask
Base model
fastino/gliner2.5-multi-v1