81melody/algerianDeBERTa-realestate-ner-v2
algerianDeBERTa-realestate-ner-v2
High-recall NER for Algerian real estate text
Same architecture and training data as `81melody/algerianDeBERTa-realestate-ner` (the precision-optimised),This one trades a few precision points for measurably higher recall, especially on neighbourhood names
Handles the exact language mix found on Facebook Marketplace and Algerian classified groups: Darja, Arabizi, French, and heavy code-switching.
v1 vs v2 — when to use which
Use v2 when:
- You need maximum location coverage , it finds ~50% more neighbourhood mentions per listing ( "Hussein dey", "Bab Ezzouar", "l3achour" are consistently extracted even when phrasing is irregular)
- You are building a search / matching engine and prefer recall over precision (missing a match is worse than a false positive)
- You are running a second-pass re-extraction to find locations missed by a stricter model
Use v1 when:
- You need clean, high-confidence entities for structured storage or analytics
- Precision matters more than recall (e.g. price indexing, surface-area statistics)
Quick comparaison
Summary comparaison : 
Score distribution 
Entity counts 
Model Highlights
No separate test-set evaluation was run for this version. For test metrics (F1 = 0.9672 on a held-out set of 95 posts) see v1 model card.
Quick Start
Option 1 : pipeline (standard, recommended for short texts)
Uses aggregation_strategy="max": for each surface word the subword token with the highest entity-class score wins, then consecutive spans that have the same type are merged automatically
from transformers import pipeline
ner = pipeline(
"token-classification",
model="81melody/algerianDeBERTa-realestate-ner-v2",
aggregation_strategy="max",
)
print(ner("سلام ، خصني اف2 فالعاصمة في ميسوني ولا اودان ولا ديدوش ، في هاد الجويه لي عندو يتوصل معيا في الخاص"))
print(ner("Appartement F4 à vendre Oran centre 120m² 4ème étage acte notarié"))
For texts that may exceed 192 tokens, pass sliding-window arguments directly to the pipeline call:
result = ner(
long_text,
truncation=True,
max_length=192,
stride=64,
)Each result dict contains entity_group, word, score, start, end.
Option 2 : Manual sliding window inference (production / long texts)
For real estate posts that frequently exceed one chunk + to adapt with The small vocab size of the first version of the base model (30k), the approach below is more robust for production use: it averages the probability vectors of overlapping tokens across all chunks, then merges subword pieces that form the same surface word before BIO decoding
This fixes a common artefact where words like "cherche" (tokenised as ["cher", "che"]) or prices like "1.700" (tokenised as ["1", ".", "700"]) get truncated mid-word if a trailing subword happens to predict O
from transformers import AutoTokenizer, AutoModelForTokenClassification
import torch
import numpy as np
from typing import List
MODEL_NAME = "81melody/algerianDeBERTa-realestate-ner-v2"
MAX_SEQ_LEN = 192
STRIDE = 64
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForTokenClassification.from_pretrained(MODEL_NAME)
model.eval()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
def extract_entities(text: str) -> List[dict]:
enc = tokenizer(
text,
return_tensors="pt",
max_length=MAX_SEQ_LEN,
stride=STRIDE,
truncation=True,
return_overflowing_tokens=True,
return_offsets_mapping=True,
padding="max_length",
)
enc.pop("overflow_to_sample_mapping", None)
offsets = enc.pop("offset_mapping")
with torch.no_grad():
logits = model(**{k: v.to(device) for k, v in enc.items()}).logits
probs = torch.softmax(logits, dim=-1).cpu().numpy()
attention = enc["attention_mask"].numpy()
off_np = offsets.numpy()
char_probs = {}
for c in range(probs.shape[0]):
for t in range(off_np.shape[1]):
if attention[c, t] == 0:
continue
cs, ce = int(off_np[c, t, 0]), int(off_np[c, t, 1])
if cs == 0 and ce == 0:
continue
if cs not in char_probs:
char_probs[cs] = {"end": ce, "vecs": [probs[c, t]]}
else:
char_probs[cs]["vecs"].append(probs[c, t])
if not char_probs:
return []
items = sorted(char_probs.items())
word_tokens = []
w_start, w_info = items[0]
w_end = w_info["end"]
w_avg_p = np.mean(w_info["vecs"], axis=0)
for cs, info in items[1:]:
if cs == w_end:
w_end = info["end"]
else:
word_tokens.append({"start": w_start, "end": w_end, "avg_p": w_avg_p})
w_start = cs
w_end = info["end"]
w_avg_p = np.mean(info["vecs"], axis=0)
word_tokens.append({"start": w_start, "end": w_end, "avg_p": w_avg_p})
label_map = model.config.id2label
entities, current = [], None
for w in word_tokens:
idx = int(np.argmax(w["avg_p"]))
label = label_map[idx]
score = float(w["avg_p"][idx])
if label == "O":
if current:
entities.append(current)
current = None
elif label.startswith("B-"):
if current:
entities.append(current)
current = {
"entity": label[2:],
"word": text[w["start"]:w["end"]],
"score": score,
"_sc": [score], "_s": w["start"], "_e": w["end"],
}
elif label.startswith("I-"):
etype = label[2:]
if current and current["entity"] == etype:
current["word"] = text[current["_s"]:w["end"]]
current["_e"] = w["end"]
current["_sc"].append(score)
current["score"] = float(np.mean(current["_sc"]))
else:
if current:
entities.append(current)
current = {
"entity": etype,
"word": text[w["start"]:w["end"]],
"score": score,
"_sc": [score], "_s": w["start"], "_e": w["end"],
}
if current:
entities.append(current)
return [
{"entity": e["entity"], "word": e["word"], "score": round(e["score"], 6)}
for e in entities
]
print(extract_entities('khasni appartement fi bab zouar 200 m2'))
#[{'entity': 'TRANSACTION', 'word': 'khasni', 'score': 0.703333},
#{'entity': 'PROPERTY_TYPE', 'word': 'appartement', 'score': 0.973926},
#{'entity': 'NEIGHBORHOOD', 'word': 'bab zouar', 'score': 0.902971},
#{'entity': 'SURFACE', 'word': '200 m2', 'score': 0.930045}]
Entity Schema
The model uses a 27-label BIO scheme covering 13 entity types.
Performance
Evaluated on the validation set (87 posts / 586 tokenized chunks).
Val F1 (micro, seqeval): 0.9780
Val Precision (micro): 0.9646
Val Recall (micro): 0.9918Test-set evaluation (strict entity-level seqeval on 95 held-out posts) was performed only on the last epoch (test F1 = 0.9672), This version was not re-evaluated on the test set to avoid data leakage into version selection
Production extraction profile (2,505 unseen listings)
Measured on posts never seen during training:
The +24 NEIGHBORHOOD gap is the strongest production signal for choosing this version: it reliably spans multi-word neighbourhood names ("Hussein dey", "Bab Ezzouar", "bordj el kiffan") that the more conservative v1 sometimes under-segments
Training Details
base_model: algerianDeBERTa (DeBERTa-v2)
architecture: DebertaV2ForTokenClassification
num_labels: 27 (BIO, 13 entity types)
saved_at_epoch: 7
max_seq_len: 192
stride: 64
optimizer: AdamW
peak_lr: 2e-5
llrd_factor: 0.9
weight_decay: 0.01
grad_accum_steps: 2
warmup_ratio: 0.1
schedule: cosine with warmup
label_smoothing: 0.05
class_weighting: inverse-frequency, capped at 10×
dropout: 0.1Limitations
- Lower precision than v1: Confidence scores are ~2–5 pp lower across all entity types. Surface-only units (
m,مترwithout a number) and single-character fragments (ر,ف) can appear as false-positive WILAYA or FLOOR entities. A confidence threshold ofscore ≥ 0.75removes the bulk of these - NEIGHBORHOOD F1: Neighbourhood names in Algeria vary widely in spelling. This version finds more of them but also introduces more borderline extractions
- PRICE splitting: Multi-part prices (
6 000 000) are occasionally split into separate entities rather than merged as one span. - Geography scope: Optimised for the 58 Algerian wilayas
- Platform distribution: Trained on Facebook posts , casual, informal register
Intended Use
Citation
If you use this model or the dataset in your research, please cite:
@misc{himeur2026algeriandeberta_ner_v2,
title = {algerianDeBERTa-realestate-ner-v2: Named Entity Recognition
for Algerian Real Estate Text in Darja, Arabizi, and French},
author = {Himeur, Ayoub},
year = {2026},
publisher = {Hugging Face},
url = {https://huggingface.co/81melody/algerianDeBERTa-realestate-ner-v2},
note = {Fine-tuned DeBERTa-v2 on annotated
Algerian Facebook real estate posts, 13 entity types}
}License
Apache 2.0 free for commercial and research use with attribution
Contact
mohamed.himeur@student.unamur.be
