Turkish PII detection and masking
turkish-pii-detection is an open 270M-parameter model for Turkish PII detection and masking: it finds personally identifiable information (PII) in Turkish text and replaces it with labels. You give it the text and a short instruction in Turkish saying what to mask, and it returns the rewritten text. It is small enough to run on a CPU, so the text never has to leave your machine.
- Size
- 270M parameters
- Base model
cagrigungor/pii-guard-turkish-270m, a Gemma 3 270M fine-tune- Label schema
- 53 labels
- Benchmark
- 0.944 exact match on 1,000 rows
- CPU speed
- about 600 ms per short sentence
- Version
- v02, released
- Licence
- Gemma Terms of Use
How Turkish PII masking works
The model reads an instruction and a text, and writes the text back with the requested fields replaced by labels such as [AD] for a name, [TCKN] for a national ID number, [TEL] for a phone number and [EMAIL]. The same weights handle three kinds of policy.
- Input
- müşteri Ayşe Yılmaz tc 12345678901 tel 0532 111 22 33 e-posta demo@example.com.
| Policy | Instruction | Output |
|---|---|---|
| Mask everything | Metindeki tüm kişisel verileri uygun etiketlerle maskele. | müşteri [AD] tc [TCKN] tel [TEL] e-posta [EMAIL]. |
| Only phone numbers | Metindeki yalnızca telefon numaralarını maskele; diğer tüm bilgileri olduğu gibi koru. | müşteri Ayşe Yılmaz tc 12345678901 tel [TEL] e-posta demo@example.com. |
| Everything except names | Metindeki kişi isimleri hariç tüm kişisel verileri uygun etiketlerle maskele. Kişi isimlerini olduğu gibi koru. | müşteri Ayşe Yılmaz tc [TCKN] tel [TEL] e-posta [EMAIL]. |
Typical uses are cleaning support messages, application logs or document excerpts before they go to an LLM, a log store or a vendor. The model returns text only. If you need character offsets or token labels, use a span-based named-entity recognition (NER) model.
Python quick start
Install PyTorch and Transformers, then run this on CPU.
python -m pip install torch==2.8.0 transformers==4.56.1
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_ID = "halilneed/turkish-pii-detection"
REVISION = "28644718923ae38b0105c9f3d2be57312ad0ced3" # v02
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, revision=REVISION)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID, revision=REVISION, torch_dtype=torch.float32
).to("cpu").eval()
end_turn = tokenizer.convert_tokens_to_ids("<end_of_turn>")
def mask_turkish_pii(text, instruction):
prompt = (
f"{tokenizer.bos_token}<start_of_turn>user\n{instruction}\n\n"
f"Metin: {text}<end_of_turn>\n<start_of_turn>model\n"
)
inputs = tokenizer(prompt, return_tensors="pt", add_special_tokens=False)
with torch.inference_mode():
sequence = model.generate(
**inputs, max_new_tokens=512, do_sample=False,
eos_token_id=end_turn, pad_token_id=tokenizer.eos_token_id,
)
generated = sequence[0, inputs["input_ids"].shape[1]:]
return tokenizer.decode(generated, skip_special_tokens=True).strip()
text = "müşteri Ayşe Yılmaz tc 12345678901 tel 0532 111 22 33 e-posta demo@example.com."
print(mask_turkish_pii(text, "Metindeki tüm kişisel verileri uygun etiketlerle maskele."))
# müşteri [AD] tc [TCKN] tel [TEL] e-posta [EMAIL].
Keep the prompt format as shown. It starts with the BOS token and uses Gemma turn markers, and the tokenizer call sets add_special_tokens=False so that BOS is not added twice. The instruction and the text are both in Turkish.
REVISION pins the v02 weights. Leave it out to follow main, or pass revision="v01" for the first release. On a GPU, load with torch.bfloat16 and device_map="auto" instead of .to("cpu"), and move the inputs to the model with .to(model.device).
The GitHub repository has a step-by-step tutorial and a local demo. The browser demo runs a quantised ONNX build on your own device. Its first load downloads about 472 MB, and its output can differ from the Python model. The benchmark scores have not been re-checked for that build.
Benchmark results
I evaluate on cagrigungor/turkish-pii-masking-benchmark, a public set of 1,000 synthetic Turkish examples. The metric is whole-row exact match: the full output has to equal the expected string, so one wrong label fails the row. On it, turkish-pii-detection v02 scores 0.944 exact match on all 1,000 rows and 0.945 on the 531 rows not used for model selection.
| Slice | v01 | v02 |
|---|---|---|
| All 1,000 rows | 0.880 | 0.944 |
| Schema-neutral rows (903) | 0.900 | 0.951 |
| Rows not used for selection (531) | 0.885 | 0.945 |
| Mask everything | 0.851 | 0.917 |
| Mask only named fields | 0.895 | 0.940 |
| Mask everything except named fields | 0.893 | 0.967 |
| Requested field not in the text | 0.960 | 1.000 |
| Trap texts with no personal data | 0.865 | 0.950 |
| Long text | 0.656 | 0.844 |
| Uppercase text | 0.775 | 0.838 |
| Personal data with Turkish suffixes | 0.758 | 0.803 |
| Several people in one text | 0.600 | 0.750 |
| Numbers written as words | 0.946 | 1.000 |
| Multi-field records | 0.968 | 0.980 |
The benchmark was not used as training data, and generated rows that overlapped with it were filtered out. I did look at aggregate benchmark feedback while developing v02 (scores per slice and, once, accuracy per label group), so I split the benchmark in two by id. The mixing ratio for the final weights was chosen on one half (469 rows) and the other half (531 rows) was not used for that choice. That half went from 0.885 to 0.945.
The v01 column was re-measured with the same script. The v01 card reported 0.882 on all rows and 0.902 on the schema-neutral rows, two rows higher in each case, because of a difference in batched bfloat16 inference.
For context, two larger models report scores on the same benchmark in their own cards: cagrigungor/pii-guard-turkish-0.8b reports 0.876 on all rows, and saturday-labs/turkish-pii-guard-0.8b (formerly melikegks/turkish-pii-guard-0.8b) reports 0.922 on the schema-neutral rows. I have not re-run them.
Exact match is a strict score for comparing models. It is not entity recall, and it does not mean 94% of real documents are safe to share.
How it was trained
v01 () is a continued fine-tune of cagrigungor/pii-guard-turkish-270m, which is based on Gemma 3 270M. I generated 24,000 synthetic banking and ERP-style messages weighted toward the slices where the starting model was weak (uppercase text, long records, numbers written as words, several people in one text) and trained for one epoch. The benchmark score went from 0.740 to 0.882, but two slices got worse: personal data with suffixes, and trap texts that contain no personal data.
v02 () went after those two.
- A new synthetic generator. 53 labels, about 1,500 hand-written templates (banking, HR, health, telecom, e-commerce, vehicles, IT logs, long emails and call transcripts), a suffix engine that follows Turkish vowel harmony, uppercase and ASCII variants, numbers written as words and about 600 different instructions. 40,000 examples.
- Teacher alignment. Label boundaries were aligned to v01’s wherever v01 produced a valid masking with the same label set. 7,136 examples that conflicted with v01 on semantic labels such as health, family and biometrics were dropped, which left 32,864.
- Continued full fine-tuning from v01. One epoch, learning rate 2e-5, effective batch size 32, loss on the output tokens only.
- Weight interpolation. The fine-tuned model alone dropped to 0.844 on the benchmark because it had drifted toward the generator’s distribution. Averaging its weights 50/50 with v01, a method known as WiSE-FT, scored 0.944, higher than v01 (0.880) and higher than the fine-tuned model alone.
The v01 story is written up in Turkish on Medium: Daha büyük model eğitmedim, zayıf dilimleri eğittim.
Limitations
- All evaluation is on synthetic data. Measure the model on your own text before you rely on it.
- The weakest slices are text with several people (0.750), personal data carrying Turkish suffixes, as in “Cem Aslan’ın” (0.803), and uppercase text (0.838).
- Short fragments without context can be left unmasked. “vergi numarası 4810271935” on its own may pass through. The same number in a sentence about a customer is masked.
- It can miss personal data, change text it should have left alone, or produce a label outside the schema. Check the output, and treat empty or cut-off output as a failure.
- Masking is not the same as anonymisation, and using the model does not make a process compliant with KVKK (the Turkish data protection law) or GDPR.
- The weights are covered by the Gemma Terms of Use. The code in the GitHub repository is MIT.
Measured speed: about 600 ms per sentence (50 characters on average) and 1.6 GB of RAM on an Intel i5-12400F with fp32, eight threads and batch size one.
Questions about Turkish PII detection with this model
Is this a Turkish NER model?
No. It generates masked text and does not return token labels or character offsets. If you need spans, use a token-classification model.
Can I run it without sending text to an API?
Yes. Download the weights once and run the model locally with Transformers, on CPU or GPU. The browser demo also runs on your own device.
Which personal data does it cover?
The schema has 53 labels. They include names, national ID numbers (TCKN), phone numbers, email addresses, IBANs and workplaces, as well as special categories such as health and biometric data.
How is it different from the KVKK classifier?
This model rewrites text. The Turkish KVKK classifier only says which categories of personal data a text contains, which helps when choosing a masking policy.
Which version will I download?
main serves v02. Pass revision="v01" for the first release, or pin a commit hash if you need reproducible results.
Does masking make my data KVKK compliant?
No. Masked output is not automatically anonymous, and compliance depends on how the data is processed, not on the model. The personal data and AI guide shows how to decide what to mask and what to keep in-house.
Türkçe kişisel veri maskeleme modeli
turkish-pii-detection, Türkçe metindeki kişisel verileri verilen talimata göre etiketlerle değiştiren, 270M parametreli açık bir modeldir. Tüm kişisel verileri, yalnızca seçtiğiniz alanları ya da belirttiğiniz alanlar dışındaki her şeyi maskeleyebilir. GPU gerektirmeyecek kadar küçüktür: işlemcide (CPU) çalışır ve metin makinenizden çıkmaz.
Çıktı olarak yalnızca maskelenmiş metni verir; karakter konumu, token etiketi ya da KVKK kategorisi döndürmez. v02, 1.000 sentetik örnekten oluşan herkese açık benchmark’ta 0.944 tam eşleşme skoruna ulaştı (v01: 0.880). Model seçiminde kullanılmayan 531 örnekte skor 0.945’tir. En zayıf olduğu dilim çok kişili metinlerdir (0.750).
Ağırlıklar ve model kartı Hugging Face’te, Python kullanım örnekleri GitHub’dadır. Tarayıcı demosunda kendi metninizi deneyebilirsiniz; model cihazınızda çalışır. Üretime almadan önce modelin başarımını kendi verinizde ölçün ve çıktıyı doğrulayın.
Links
- Model card and weights
- Browser demo
- Masking tool: a rule-based companion for TCKN, IBAN, phone and email, with reversible labels
- Code, tutorial and evaluator on GitHub
- Turkish KVKK classifier, Turkish BSEBY classifier and Turkish prompt injection classifier
- Personal data and AI: a short guide for teams on what to mask and what to keep in-house before text reaches a model
- All models
If the model is useful to you, a like on its Hugging Face page helps other people find it.