Model
A multilingual PII detector for the 24 official EU languages, built to work on the text real systems see: web pages and raw scrapes, mailing lists, invoices, PDF tables and OCR output. It is the successor of bardsai/eu-pii-anonimization-multilang (v1) and keeps the same label set.
All numbers below use strict span scoring. A gold span counts as found only if every letter and digit in it is predicted with the right type, so missing part of a word is a miss. A predicted span counts as correct only if it lies inside a gold span of the same type and does not cut a word. Types are grouped into person, email, phone, address, ID, secret and URL.
| Test set (real text, hand-checked gold) | Spans | v1 | v2 |
|---|---|---|---|
| Mailing lists, Impressum/contact pages, raw scrapes, PDF text | 332 | 0.891 | 0.982 |
| ↳ of which a PDF registry table | 0.873 | 0.988 | |
| Fresh web pages (contact pages from 10 sites) | 75 | 0.921 | 0.968 |
| FineWeb-2 chunks (multilingual, held out) | 48 | 0.835 | 0.948 |
| All real text | 455 | 0.885 | 0.973 |
| Synthetic OCR'd business documents | 0.876 | 0.988 |
On all 455 real spans v2 has strict precision 0.966 and recall 0.980. The real test sets are small, so differences under about 0.01 are noise.
Public benchmarks (span F1, 7 coarse types; Nemotron and OpenPII train splits are part of both models' training data, Gretel is out of distribution):
| Benchmark | v1 | v2 |
|---|---|---|
| gretelai finance PII, multilingual (OOD) | 0.615 | 0.692 |
| nvidia/Nemotron-PII | 0.882 | 0.933 |
| ai4privacy OpenPII | 0.928 | 0.997 |
The scores above use the word-level decoder in pii_detector.py, shipped in this repo. Use it rather than a plain per-token argmax, which emits sub-word fragments.
from huggingface_hub import hf_hub_download
import importlib.util, sys
repo = "bardsai/eu-pii-anonimization-multilang-v2"
path = hf_hub_download(repo, "pii_detector.py")
spec = importlib.util.spec_from_file_location("pii_detector", path)
pii_detector = importlib.util.module_from_spec(spec); spec.loader.exec_module(pii_detector)
det = pii_detector.PIIDetector(repo) # GPU if available, else CPU
text = "Kontakt: Jan Kowalski, tel. 600 123 456, jan.kowalski@example.pl, ul. Długa 5, 00-238 Warszawa"
for span in det.predict(text):
print(span)
# {'start': 9, 'end': 21, 'label': 'PERSON_NAME', 'text': 'Jan Kowalski'} ...
print(det.anonymize(text))
# Kontakt: [PERSON_NAME], tel. [PHONE_NUMBER], [EMAIL_ADDRESS], [POSTAL_ADDRESS]
det.anonymize(text, labels={"PERSON_NAME", "EMAIL_ADDRESS"}) redacts only the listed types. Requires transformers (a version with ModernBERT support) and torch.
ONNX exports are in onnx/. The decoder can run them with onnxruntime (the tokenizer still comes from transformers):
det = pii_detector.PIIDetector(repo, onnx="onnx/model_quantized.onnx") # or "onnx/model.onnx"
| Weights | Size | Strict F1, all real text (455 spans) |
|---|---|---|
PyTorch / onnx/model.onnx (FP32) | 1.2 GB | 0.973 |
onnx/model_quantized.onnx (dynamic INT8) | 0.3 GB | 0.968 |
The FP32 export gives the same predictions as PyTorch. The INT8 file is 4x smaller and loses about half a point.
, ; | " < > ( ) [ ] { } * ^, and edge punctuation is trimmed.EMAIL_ADDRESS.PERSON_NAME, PERSON_ALIAS, PERSON_IDENTIFIER, PERSON_ATTRIBUTE, PERSON_ROLE_OR_TITLE, DATE_OF_BIRTH, EMAIL_ADDRESS, PHONE_NUMBER, CONTACT_HANDLE, POSTAL_ADDRESS, LOCATION, GEO_LOCATION, IP_ADDRESS, DEVICE_IDENTIFIER, IDENTIFYING_LINK, ACCOUNT_IDENTIFIER, AUTH_SECRET, BANK_ACCOUNT_IDENTIFIER, PAYMENT_CARD, PAYMENT_CARD_SECURITY, FINANCIAL_AMOUNT, DOCUMENT_IDENTIFIER, DOCUMENT_REFERENCE, VEHICLE_IDENTIFIER, ORGANIZATION_NAME, ORGANIZATION_IDENTIFIER, PROPER_NAME, HEALTH_DATA, BIOMETRIC_DATA, ETHNIC_ORIGIN, POLITICAL_OPINION, RELIGION_OR_BELIEF, SEXUAL_ORIENTATION, TRADE_UNION_MEMBERSHIP, CRIMINAL_OFFENCE_DATA.
Company registration and VAT numbers (NIP, REGON, KvK, USt-IdNr, SIREN and similar) are ORGANIZATION_IDENTIFIER, not personal IDs. The evaluation above scores person, contact, address, ID, secret and link types; GDPR special-category labels are kept from v1 but are not covered by these test sets.
All training data is openly licensed or generated:
The domains used in the real-text test sets were excluded from the training text.
model.safetensors, config.json: weights and label mappingtokenizer.json, tokenizer_config.json: tokenizeronnx/model.onnx: FP32 ONNX export (inputs input_ids, attention_mask; output logits)onnx/model_quantized.onnx: dynamic INT8 quantization of the ONNX export, for CPUpii_detector.py: the decoder used for all reported scores (PyTorch or ONNX)We build product ML for teams shipping AI to real users. bards.ai