cbx-pii-extractor-17M
Finds personal information (PII) in English text and returns the exact position of each value with one of 18 labels. 17M parameters, about 6 ms per 1,000 tokens on a GPU and 37 ms on a CPU.
This is the fastest size, and the one to use on CPU. Fine-tuned from jhu-clsp/ettin-encoder-17m.
Current version: v0.2 (use --revision v0.2 to pin it). Also available in 68M and 32M sizes.
What's new in v0.2
- 18 labels instead of 12. Account numbers, government IDs and other IDs are split into specific types (table below).
- More accurate on chat and business documents:
- lowercase and first-name-only names;
- Slack mentions such as
<@U02NORA>; - verification codes;
- policy, member and account references;
- employee IDs in tables.
- The same speed as v0.1.
Upgrading from v0.1. The label names changed. Pin --revision v0.1 to keep the old ones, or map the new labels
back like this:
V01 = {"DATE_OF_BIRTH": "DATE", "BANK_ACCOUNT": "ACCOUNT_NUMBER", "PAYMENT_CARD": "ACCOUNT_NUMBER",
"CUSTOMER_ID": "ACCOUNT_NUMBER", "NATIONAL_ID": "GOV_ID", "PASSPORT": "GOV_ID", "TAX_ID": "GOV_ID",
"DRIVERS_LICENSE": "GOV_ID", "EMPLOYEE_ID": "OTHER_ID", "PATIENT_ID": "OTHER_ID"}
spans = [dict(s, label=V01.get(s["label"], s["label"])) for s in pii.extract(text)]
Labels
| Label | Covers | v0.1 label |
|---|---|---|
| PERSON | Names, including first names only, initials, nicknames and lowercase names | PERSON |
| DATE_OF_BIRTH | Dates of birth only (other dates are not labelled) | DATE |
| Email addresses | ||
| PHONE | Phone and fax numbers, including extensions | PHONE |
| ADDRESS | Full street addresses, with unit, city and postcode (a city or country on its own is not labelled) | ADDRESS |
| BANK_ACCOUNT | Bank account numbers and IBANs | ACCOUNT_NUMBER |
| PAYMENT_CARD | Card numbers, including the last four digits | ACCOUNT_NUMBER |
| CUSTOMER_ID | Customer, member and policy numbers, and account references (not ticket or order numbers) | ACCOUNT_NUMBER |
| NATIONAL_ID | Social security and national ID numbers | GOV_ID |
| PASSPORT | Passport numbers | GOV_ID |
| TAX_ID | Tax IDs | GOV_ID |
| DRIVERS_LICENSE | Driving licence numbers | GOV_ID |
| USERNAME | Usernames and handles; @ mentions include the @, and Slack mentions include <@ and > |
USERNAME |
| URL | Web addresses, with or without https:// | URL |
| IP_ADDRESS | IPv4 and IPv6 addresses | IP_ADDRESS |
| SECRET | Passwords, PINs, verification and access codes, API keys and tokens | SECRET |
| EMPLOYEE_ID | Employee and staff IDs | OTHER_ID |
| PATIENT_ID | Patient IDs and medical record numbers | OTHER_ID |
How to use
1. Download and install
pip install -U huggingface_hub
hf download CobuildX-AI/cbx-pii-extractor-17M --revision v0.2 --local-dir cbx-pii-extractor-17M
cd cbx-pii-extractor-17M
pip install -r requirements.txt
2. Use it in your code
PIIExtractor (in pii.py, part of this repo) loads the model once:
from pii import PIIExtractor
pii = PIIExtractor() # uses a GPU if one is available
pii.extract("Hi team, I'm Priya Raman (priya.raman@northwind.com).")
# [{'label': 'PERSON', 'text': 'Priya Raman', 'start': 13, 'end': 24},
# {'label': 'EMAIL', 'text': 'priya.raman@northwind.com', 'start': 26, 'end': 51}]
pii.redact("Hi team, I'm Priya Raman (priya.raman@northwind.com).")
# "Hi team, I'm [PERSON] ([EMAIL])."
pii.extract(text, labels=["PERSON", "EMAIL"]) # keep only some labels
- The model finds the PII.
redact()replaces each value it found with its label, or with your owntemplate. - Context length is 7,999 tokens, read in a single pass. Longer texts are split automatically.
3. Optional: check your setup
python example.py runs four sample texts and prints what it finds.
Results
Exact-match span F1 on three public benchmarks never used for training. A value counts only if its start, end and label are all correct.
| Model | Size | Privacy-Bench docs | Privacy-Bench messages | PII-TRACE | GPU, 1,000 tokens |
|---|---|---|---|---|---|
| cbx-pii-extractor-68M | 68M | 88.1% | 91.7% | 93.2% | 13 ms |
| cbx-pii-extractor-32M | 32M | 88.0% | 87.0% | 93.5% | 8 ms |
| cbx-pii-extractor-17M | 17M | 86.1% | 83.1% | 91.6% | 6 ms |
| PII-Tracer | 596M | 86.9% | 82.0% | 92.5% | 160 ms |
| GLiNER2-PII | 205M | 80.8% | 86.9% | 66.2% | 91 ms |
| Presidio (spaCy lg) | β | 82.4% | 85.8% | 64.7% | 85 ms (CPU) |
- Privacy-Bench docs: 656 human-annotated pages of business documents.
- Privacy-Bench messages: 5,198 chat messages.
- PII-TRACE: 500 conversations. It is PII-Tracer's own benchmark. Most of our remaining gap there is dates other than dates of birth, which we deliberately don't label. Without dates, this model scores 96.0% against 91.9% for PII-Tracer.
On text with no PII at all, this model flags 0.08 false values per 1,000 characters, against 1.6β3.6 for the other models.
How we evaluated
Same runs for every model. We ran every model ourselves, on the same texts, with the same scoring.
Benchmark sizes:
Benchmark Contents Annotated values in the scored types Privacy-Bench docs 656 pages of Word and PDF business documents 8,700 Privacy-Bench messages 5,198 chat messages 6,594 PII-TRACE 500 multi-turn conversations (public subset) 2,442 No overlap with training data. No training text shares a 13-word sequence with any benchmark.
How the benchmarks were used. Half of each benchmark was used to study errors while we designed the training data. Scores on the other half are within 0.6 points of the full-set numbers above.
A prediction counts as correct only when its start, end and type all match the annotation. Each benchmark's types were mapped to ours as below. Every model was scored on the types that both the benchmark and the model support.
| CBX PII Extractor | Privacy-Bench | PII-TRACE |
|---|---|---|
| PERSON | NAME_GIVEN + NAME_FAMILY (one name) | private_person |
| EMAIL_ADDRESS | private_email | |
| PHONE | PHONE_NUMBER | private_phone |
| ADDRESS | LOCATION_ADDRESS | private_address |
| URL | URL | private_url |
| BANK_ACCOUNT, PAYMENT_CARD, CUSTOMER_ID | ACCOUNT_NUMBER | account_number |
| USERNAME | USERNAME | Not annotated |
| EMPLOYEE_ID | EMPLOYEE_ID | Not annotated |
| DATE_OF_BIRTH | Not annotated | private_date |
| SECRET | Not annotated | secret |
Government IDs, IP addresses and patient IDs are not annotated in either benchmark, so this evaluation does not cover them.
Speed. Latency is the median over 50 texts of 1,000 tokens, processed one at a time, end to end (text in, spans out). It was measured on an NVIDIA TITAN RTX GPU (fp16) and an Intel i7-10700 CPU (8 threads), with all models on the same machine. The v0.1 card used a Tesla T4, so its timings are not directly comparable.
Limitations
- English only.
- Sensitive attributes such as health, religion or ethnicity are not detected.
- Account and reference numbers inside long documents, and usernames in documents, are found less reliably than names, emails and phones.
- Addresses are always labelled in full. Benchmarks that mark only the street will count them as boundary errors.
- A detection aid, not a guarantee: keep human review where redaction must be complete.
Licence
Apache-2.0. Trained on NVIDIA Nemotron-PII (CC-BY-4.0), Gretel PII masking EN v1 and Gretel synthetic PII finance (Apache-2.0), Wismut nym-pii (MIT), Kiji PII training data (Apache-2.0) and data we generated. Base model: Ettin (MIT, arXiv:2507.11412).
- Downloads last month
- 10
Model tree for CobuildX-AI/cbx-pii-extractor-17M
Base model
jhu-clsp/ettin-encoder-17m