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.
Outputs recorded on with the v02 weights on CPU (PyTorch 2.8.0, Transformers 4.56.1). The person and the numbers are made up.
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.

Exact match on the Turkish PII Masking Benchmark
Slicev01v02
All 1,000 rows0.8800.944
Schema-neutral rows (903)0.9000.951
Rows not used for selection (531)0.8850.945
Mask everything0.8510.917
Mask only named fields0.8950.940
Mask everything except named fields0.8930.967
Requested field not in the text0.9601.000
Trap texts with no personal data0.8650.950
Long text0.6560.844
Uppercase text0.7750.838
Personal data with Turkish suffixes0.7580.803
Several people in one text0.6000.750
Numbers written as words0.9461.000
Multi-field records0.9680.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.

  1. 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.
  2. 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.
  3. Continued full fine-tuning from v01. One epoch, learning rate 2e-5, effective batch size 32, loss on the output tokens only.
  4. 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.