Self-contained NER model that detects PII in Belgian medical documents written
in English. Fine-tuned obi/deid_bert_i2b2 (INT8-quantized ONNX) plus a
deterministic regex/checksum layer. Trained on 100% synthetic data — no real
patient records anywhere in its provenance.
Install & use
pip install onnxruntime transformers
python
1from infer import detect_pii
2detect_pii("Patient X, National Registration No. 85.09.12-984.19, Rue de la Loi 16, 5000 Namur")3# -> [{"start": .., "end": .., "label": "INSZ", "text": ".."}, ...]
CLI smoke test: python infer.py (runs a built-in demo) or python infer.py "your text".
What detects what
Source
Labels
Model (INT8 ONNX)
NAME, DATE, ADDRESS, AGE, PHONE, ORGANIZATION
Regex + mod-97 checksum (english_regex.py)
INSZ, RIZIV, IBAN, BTW_EENHEID, EMAIL, URL
The model was trained on the 6 model-owned labels only; the national identifiers
(INSZ/RIZIV), IBAN, VAT, email and URL are handled by the regex layer at
inference and merged in. Both halves are required — the ONNX alone will not
detect INSZ/RIZIV/EMAIL/URL. infer.py merges them (regex-owned labels take
priority on overlap).
Files
model_int8.onnx fine-tuned model, INT8 dynamic-quantized (108 MB)
config.json label map (id -> B-/I- tag) + model config
tokenizer.json fast tokenizer (self-contained)
tokenizer_config.json
english_regex.py the regex/checksum layer (INSZ/RIZIV/IBAN/BTW/EMAIL/URL)
infer.py tokenise (sliding window 512/stride 128) -> ONNX -> BIO
decode -> merge with regex -> spans
To use the fp32 model instead, drop model_fp32.onnx in this folder and set
PII_ONNX=model_fp32.onnx.
Quality (indist, held from the same synthetic generator)
INT8 vs fp32: size 431 MB -> 108 MB (-74.8%), ~1.4x faster, strict F1
0.9986 -> 0.9985 (negligible). In-distribution F1 ~0.99; out-of-distribution
(hand-authored stress docs) ~0.83 — the documented, expected ceiling.
Known limitations
Trained on synthetic Belgian-English clinical text; not validated on real
English patient documents (none exist for this track).
Minor boundary imperfections on some surface forms (e.g. a compact
+32XXXXXXXXX phone can lose the final digit; a URL can include a trailing
period). These slightly under-/over-shoot the span, not the detection.
Personal, exploratory model — not an official/clinical-grade PII service.