cbx-indic-pii-extractor-17M
Finds personal information (PII) in Indian text and returns the exact position of each value with one of 27 labels, including Aadhaar, PAN, UPI IDs, IFSC codes, voter IDs and vehicle numbers. It reads 12 Indian languages and Indian English (Hindi, Bengali, Marathi, Tamil, Telugu, Kannada, Malayalam, Gujarati, Punjabi, Odia, Assamese, Urdu), in native script, romanised (Indian languages in English letters) and code-mixed text. 16.7M parameters; about 11 ms for a short message and 72 ms for a 2,000-character document on a CPU (8 threads), about 5 ms on a GPU.
This is the smallest and fastest size, for tight memory or CPU budgets. Also available in 58M and 36M sizes. Distilled from a larger internal model, which scores higher (see Results) and is not released.
How it was made
- Smaller version of cbx-indic-pii-extractor-58M. It keeps 6 of the 58M's 22 layers (layers 0, 4, 8, 12, 16 and 20) and its 40,000-token vocabulary.
- Compressed word table: each token is stored as 128 numbers and expanded to 384 by a shared matrix.
pii.pyin this repo loads it; plainAutoModelloading does not. - Retrained on the same data, learning from both the labels and the predictions of a larger internal model (knowledge distillation; the teacher is not released).
Labels
| Label | Covers |
|---|---|
| PERSON | Names, including first names or surnames on their own and initials; titles (Shri, Dr, ji) are not part of the name |
| PHONE | Phone numbers |
| Email addresses | |
| ADDRESS | Postal addresses including the PIN code, and a PIN code on its own (a city or state on its own is not labelled) |
| DATE_OF_BIRTH | Dates of birth |
| DATE | Every other date, in any format and script, with a time written next to it |
| AMOUNT | Money amounts with their currency sign or word (₹25,000, Rs. 500/-, २५ हज़ार रुपये) |
| BANK_ACCOUNT | Bank account numbers |
| IFSC | IFSC branch codes |
| UPI_ID | UPI IDs |
| PAYMENT_CARD | Card numbers, including masked ones (XXXX XXXX XXXX 1234) |
| AADHAAR | Aadhaar numbers, including masked ones |
| PAN | PAN numbers |
| VOTER_ID | Voter ID (EPIC) numbers |
| RATION_CARD | Ration card numbers |
| PASSPORT | Passport numbers |
| DRIVERS_LICENSE | Driving licence numbers |
| VEHICLE_REG | Vehicle registration numbers |
| NATIONAL_ID | Other national IDs: ABHA number and address, APAAR ID |
| TAX_ID | GSTIN and TAN (PAN has its own label) |
| CUSTOMER_ID | Customer, CIF, loan, policy and consumer numbers (not order, transaction or ticket numbers) |
| EMPLOYEE_ID | Employee codes, PF UAN, ESIC numbers |
| PATIENT_ID | MRN, UHID and hospital registration numbers |
| SECRET | OTPs, MPINs, passwords, CVVs |
| USERNAME | Usernames and handles |
| URL | Web addresses |
| IP_ADDRESS | IPv4 and IPv6 addresses |
Codes that are not personal data (order, invoice, ticket, transaction, SKU, batch, tracking and booking numbers, public helplines) are not labelled.
How to use
1. Download and install
pip install -U huggingface_hub
hf download CobuildX-AI/cbx-indic-pii-extractor-17M --local-dir cbx-indic-pii-extractor-17M
cd cbx-indic-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("नमस्ते, मेरा नाम अनीता कुलकर्णी है। मेरा मोबाइल नंबर 98204 51763 है और ईमेल anita.k@example.in है।")
# [{'label': 'PERSON', 'text': 'अनीता कुलकर्णी', 'start': 17, 'end': 31},
# {'label': 'PHONE', 'text': '98204 51763', 'start': 53, 'end': 64},
# {'label': 'EMAIL', 'text': 'anita.k@example.in', 'start': 76, 'end': 94}]
pii.redact("नमस्ते, मेरा नाम अनीता कुलकर्णी है। मेरा मोबाइल नंबर 98204 51763 है और ईमेल anita.k@example.in है।")
# "नमस्ते, मेरा नाम [PERSON] है। मेरा मोबाइल नंबर [PHONE] है और ईमेल [EMAIL] है।"
pii.extract(text, labels=["PERSON", "AADHAAR"]) # 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 8,192 tokens, read in a single pass. Longer texts are split automatically.
- Masked values keep their mask:
XXXX XXXX 4521is returned whole as AADHAAR,**9813as BANK_ACCOUNT.
3. Optional: check your setup
python example.py runs four sample texts (Hindi, romanised Hindi, Tamil, Bengali) and prints what it finds.
4. Optional: ONNX Runtime
onnx/model.onnx runs the same model with ONNX Runtime (1.23 or later) and gives the same tags (checked on 300
texts). python onnx_example.py shows how to use it with PIIExtractor. It uses ONNX Runtime's fused attention
operator (com.microsoft.MultiHeadAttention), so other ONNX runtimes may not load it.
Results
IndicPIIDetX v0.1 (CobuildX, DOI 10.57967/hf/11457): 10,035 synthetic documents in the same 13 languages and three writing styles, five document types (chat and SMS, emails and letters, forms with scan noise, call transcripts, chatbot prompts), 1,489 of them with no PII but with look-alike codes. Made independently of our training data (different generator, different name lists). Scores on its test split (8,028 documents). Typed F1: a predicted value counts if it overlaps an annotated value of the same type. Scored on the 14 types every model in the table can name (see How we evaluated). The benchmark's own scorer pairs each prediction with at most one annotated value, so its card shows slightly lower numbers.
| Model | Typed F1 | Precision | Recall | Documents without PII flagged | CPU, median per text |
|---|---|---|---|---|---|
| cbx-indic-pii-extractor-58M | 0.930 | 0.945 | 0.915 | 19.9% | 16 ms |
| cbx-indic-pii-extractor-36M | 0.888 | 0.943 | 0.839 | 13.4% | 8 ms |
| cbx-indic-pii-extractor-17M | 0.869 | 0.943 | 0.806 | 16.2% | 5 ms |
| Larger internal model (teacher, not released) | 0.957 | 0.959 | 0.955 | 15.0% | – |
| Chitragupta (with its rules) | 0.944 | 0.912 | 0.978 | 53.4% | 73 ms |
| Chitragupta (model only) | 0.941 | 0.904 | 0.981 | 53.3% | 75 ms |
| IndicBERT-PII | 0.813 | 0.808 | 0.818 | 61.0% | 21 ms |
| GLiNER-PII large | 0.490 | 0.727 | 0.370 | 45.1% | 244 ms |
- Precision and false alarms are where this model is strongest: it flags 16.2% of the documents without PII, against 53.4% for Chitragupta.
- Chitragupta finds more values (recall), mostly names and bank / ID numbers. Our larger internal model (the teacher of the 36M and 17M) scores 0.957.
- All 27 labels (our models only): typed F1 0.851. With CUSTOMER_ID and NATIONAL_ID added to the 14 types: 0.847 (Chitragupta, which has no label for them, 0.871).
- CPU time is the median over 200 texts of the Ylemis benchmark below (median 96 characters), one at a time, 8 threads, all models on the same machine. On longer texts the gap grows.
Two public benchmarks. These helped us decide what to add to our training data (ID-shaped codes that are not PII, new ID formats), so they are not blind tests for these models; the comparison is shown for reference.
| Model | Ylemis India-PII: typed F1 | IndiaPII-Bench: typed F1 | IndiaPII-Bench: look-alike codes flagged |
|---|---|---|---|
| cbx-indic-pii-extractor-58M | 0.986 | 0.946 | 13.3% |
| cbx-indic-pii-extractor-36M | 0.981 | 0.958 | 19.8% |
| cbx-indic-pii-extractor-17M | 0.982 | 0.944 | 12.3% |
| Chitragupta (with its rules) | 0.941 | 0.900 | 47.0% |
| Chitragupta (model only) | 0.931 | 0.890 | 47.7% |
| IndicBERT-PII | 0.727 | 0.632 | 64.0% |
| GLiNER-PII large | 0.867 | 0.934 | 56.2% |
- Ylemis India-PII (Pranshurs/ylemis-india-pii-benchmark): 10,000 texts; location, organisation and medical-ID annotations are not scored (we do not label them).
- IndiaPII-Bench (maskflow-ai/indiapii-bench): 2,000 texts with annotated look-alike codes; "flagged" is the share of those codes a model marks as PII.
On our own held-out synthetic test set (same generators as the training data, so optimistic) the span F1 is 0.993.
Speed (one text at a time, end to end; NVIDIA TITAN RTX fp16, Intel i7-10700 CPU with 8 threads):
| Short message (~340 characters) | Document (2,000 characters) | |
|---|---|---|
| GPU | 4.4 ms | 5.3 ms |
| CPU | 11 ms | 72 ms |
How we evaluated
- Same runs for every model. We ran every model ourselves, on the same texts, with the same scorer.
- Types. Each model's labels were mapped to the 14 types every model in the table can name: PERSON, ADDRESS, PHONE, EMAIL, AADHAAR, PAN, TAX_ID (GSTIN), IFSC, UPI_ID, VOTER_ID, VEHICLE_REG, PASSPORT, DRIVERS_LICENSE, BANK_ACCOUNT. A predicted PIN code counts as an address. Annotated values of other types are left out, and predictions on them are not counted.
- Settings. IndicBERT-PII at its card's threshold 0.5, GLiNER-PII at 0.3 with one fixed prompt list; texts longer than a model's limit (IndicBERT-PII 512 tokens, GLiNER 768 words) were read in consecutive windows. Adjacent pieces of the same type are joined for every model alike.
- No overlap with training data. No IndicPIIDetX document appears in our training data, even with numbers and codes ignored; its names come from different lists (a few common names occur in both).
- Teacher. The larger internal model is a fine-tuned mmBERT-base (307M parameters); it is not released.
Limitations
- Names written in native script, especially less common names, are missed more often than by the larger internal model, and this is the weakest point of this size.
- CUSTOMER_ID, NATIONAL_ID, PATIENT_ID, EMPLOYEE_ID and RATION_CARD values are found less reliably than names, phones and emails, and bank account numbers are sometimes labelled as other account types.
- Phone numbers written in a 3-3-4 pattern (
784-448-0140) can be read as Aadhaar numbers. - Values next to each other in JSON or CSV with no space between them can be returned as one span.
- AMOUNT and DATE cover every amount and date, also prices and dates not tied to a person; benchmarks that label only personal ones count those as false alarms.
- Trained on synthetic text only. Sensitive attributes such as health, religion or caste are not detected.
- A detection aid, not a guarantee: keep human review where redaction must be complete.
Training data
About 74,000 synthetic documents in the 13 languages and three writing styles, made by us: templates written by large language models (gpt-5.4-mini, and Sarvam-30B for an earlier part), with every personal value filled in by code (valid checksums for Aadhaar, PAN, GSTIN and similar), plus documents full of codes that are not PII, and forms, tables, key-value records, CSV and JSON. No real personal data.
Licence
Apache-2.0. Base model mmBERT-small (MIT).