melikegks's picture
3 yeni örnek + politika çeşitlendirmesi
4e94c90 verified
Raw
History Blame Contribute Delete
10.9 kB
"""Türkçe PII Guard — Hugging Face Space demosu.
Model uçtan uca çalışır: metni alır, maskelenmiş metni döndürür.
Şema dışı etiket üretebildiği için çıktı bir whitelist katmanından geçirilir
ve kullanıcıya ayrıca gösterilir — ham model davranışı gizlenmez.
"""
import os
import re
import time
import gradio as gr
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_ADI = "melikegks/turkish-pii-guard-0.8b"
# Konteynerde stdout tamponlanır; loglar anında görünsün diye kapatıyoruz.
os.environ.setdefault("PYTHONUNBUFFERED", "1")
GECERLI_ETIKETLER = {
"[AD]", "[TCKN]", "[DOGUM_TARIHI]", "[DOGUM_YERI]", "[ANNE_ADI]",
"[ANNE_KIZLIK]", "[BABA_ADI]", "[PASAPORT_NO]", "[EHLIYET_NO]", "[SGK_NO]",
"[IMZA]", "[IBAN]", "[HESAP_NO]", "[KART]", "[KART_SKT]", "[CVV]",
"[MAAS]", "[VERGI_NO]", "[MUSTERI_NO]", "[KREDI_NOTU]", "[POLICE_NO]",
"[SOZLESME_NO]", "[KRIPTO_CUZDAN]", "[TEL]", "[EMAIL]", "[ADRES]",
"[KONUM]", "[SAGLIK]", "[DIN]", "[ETNIK_KOKEN]", "[SENDIKA]",
"[BIYOMETRIK]", "[CEZA_KAYDI]", "[KAN_GRUBU]", "[ENGEL_DURUMU]",
"[SIFRE]", "[PIN]", "[KULLANICI_ADI]", "[IP_ADRES]", "[MAC_ADRES]",
"[IMEI]", "[CIHAZ_ID]", "[PLAKA]", "[SASI_NO]", "[MOTOR_NO]",
"[RUHSAT_NO]", "[SICIL_NO]", "[ISYERI]", "[AILE]", "[REFERANS]",
}
ETIKET_RE = re.compile(r"\[[A-Z_]+\]")
IM_END = "<|im_end|>"
TALIMATLAR = {
"Tam maskeleme":
"Metindeki tüm kişisel ve hassas bilgileri uygun etiketlerle maskele. "
"Diğer kısımları değiştirme.",
"Yalnızca IBAN":
"Yalnızca IBAN bilgisini maskele. Diğer bilgileri değiştirme.",
"Yalnızca telefon":
"Sadece telefon numarasını maskele. Geri kalan her şey aynen kalsın.",
"Ad soyad açık kalsın":
"Kişinin adını açık bırak. Diğer kişisel bilgileri maskele.",
"IBAN açık kalsın":
"IBAN bilgisini değiştirme. Diğer kişisel ve hassas bilgileri maskele.",
"Sadece finansal veriler":
"Bu metinde sadece finansal bilgileri maskele.",
"Sadece özel nitelikli (KVKK md. 6)":
"Bu metinde sadece KVKK madde 6 kapsamındaki özel nitelikli kişisel "
"verileri maskele.",
"Serbest (aşağıya yaz)": "",
}
# ZeroGPU tespiti ORTAM DEĞİŞKENİNDEN yapılmalı, paketin varlığından değil:
# `spaces` requirements.txt'te olduğu için cpu-basic'te de kuruluyor ve
# "import edilebiliyorsa GPU vardır" varsayımı yanlış sonuç veriyor.
# HF, ZeroGPU donanımında SPACES_ZERO_GPU=true tanımlar.
#
# Ayrıca ZeroGPU'da torch.cuda.is_available() açılışta False döner — GPU
# yalnızca @spaces.GPU ile dekore edilmiş fonksiyon çalışırken bağlanır.
# Bu yüzden cuda kontrolü tek başına da yeterli değil.
ZEROGPU = os.environ.get("SPACES_ZERO_GPU", "").lower() in ("1", "true")
if ZEROGPU:
import spaces
else:
spaces = None
# os.cpu_count() konteynerde HOST çekirdek sayısını verir; Space'e ayrılan
# 2 vCPU'ya 32 thread açmak çekirdekleri birbirine bekletir ve üretimi
# yavaşlatır. sched_getaffinity cgroup sınırını görür.
try:
cekirdek = len(os.sched_getaffinity(0))
except AttributeError:
cekirdek = os.cpu_count() or 2
torch.set_num_threads(max(1, min(cekirdek, 4)))
print("CPU thread:", torch.get_num_threads(), flush=True)
GPU_VAR = ZEROGPU or torch.cuda.is_available()
print("Model yükleniyor… ZeroGPU:", ZEROGPU, flush=True)
tokenizer = AutoTokenizer.from_pretrained(MODEL_ADI)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ADI,
dtype=torch.bfloat16 if GPU_VAR else torch.float32,
).eval()
if GPU_VAR:
model = model.to("cuda")
IM_END_ID = tokenizer.convert_tokens_to_ids(IM_END)
print("Model hazır. Cihaz:", next(model.parameters()).device, flush=True)
def gpu_gerekiyorsa(fn):
"""ZeroGPU'da @spaces.GPU uygular, CPU'da fonksiyonu olduğu gibi bırakır."""
return spaces.GPU(duration=20)(fn) if ZEROGPU else fn
@gpu_gerekiyorsa
@torch.inference_mode()
def maskele(metin, talimat):
"""Maskelenmiş metni ve geçen süreyi döndürür."""
mesajlar = [
{"role": "system", "content": talimat},
{"role": "user", "content": metin},
]
prompt = tokenizer.apply_chat_template(
mesajlar, tokenize=False, add_generation_prompt=True,
enable_thinking=False,
)
girdi = tokenizer(prompt, return_tensors="pt",
add_special_tokens=False).to(model.device)
# Çıktı girdiyle yaklaşık aynı uzunlukta: maskeleme metni yeniden yazmaz,
# yalnızca bazı parçaları etiketle değiştirir. Sabit 320 yerine girdiye
# bağlı sınır, model <|im_end|> üretmezse boşa dönmeyi engeller.
girdi_uzunlugu = girdi["input_ids"].shape[1]
sinir = min(256, max(48, girdi_uzunlugu + 24))
baslangic = time.time()
cikti = model.generate(
**girdi,
max_new_tokens=sinir,
do_sample=False, # maskeleme belirlenimci olmalı
eos_token_id=IM_END_ID,
pad_token_id=tokenizer.pad_token_id,
use_cache=True,
)
sure = time.time() - baslangic
yeni = cikti[0, girdi_uzunlugu:]
return tokenizer.decode(yeni, skip_special_tokens=True).strip(), sure
def calistir(metin, secim, serbest_talimat):
metin = (metin or "").strip()
if not metin:
return "", "Metin gir.", ""
talimat = serbest_talimat.strip() if secim == "Serbest (aşağıya yaz)" \
else TALIMATLAR[secim]
if not talimat:
talimat = TALIMATLAR["Tam maskeleme"]
cikti, sure = maskele(metin, talimat)
etiketler = ETIKET_RE.findall(cikti)
uydurma = sorted({e for e in etiketler if e not in GECERLI_ETIKETLER})
if not etiketler:
ozet = "Kişisel veri bulunmadı — metin değiştirilmedi."
else:
sayim = {}
for e in etiketler:
sayim[e] = sayim.get(e, 0) + 1
ozet = "Maskelenen: " + ", ".join(
f"{e}×{n}" if n > 1 else e for e, n in sorted(sayim.items()))
ozet += f" · {sure:.1f} sn"
if uydurma:
uyari = (
f"⚠️ Şema dışı etiket üretildi: {', '.join(uydurma)}\n\n"
"Model 50 etiketin dışında bir etiket uydurdu — genellikle "
"metindeki komşu kelimeden türetir. 5.000 örnekte ~9 kez görülür "
"(%0,18). Üretimde bu satırlar reddedilmeli veya insan "
"incelemesine gönderilmelidir."
)
else:
uyari = "✅ Tüm etiketler şema içinde."
return cikti, ozet, uyari
ORNEKLER = [
# Tam maskeleme
["müşteri Ahmet Yıldız 0542 321 45 67 numarasından aradı, "
"iade hesabı TR12 0001 2000 0034 5678 9012 34", "Tam maskeleme", ""],
["tc kimlik numaram otuz dört yirmi üç altmış beş yetmiş sekiz doksan bir, "
"acil bakar mısınız", "Tam maskeleme", ""],
["aracımın plakası 34 ABC 123, şasi numarası NM0KXXTTFKJ123456 "
"kasko yenilenecek", "Tam maskeleme", ""],
# Aynı görünen iki 16 hane: biri kart, diğeri seri numarası
["kart numaram 5401 2345 6789 1234 işlem yapamıyorum",
"Sadece finansal veriler", ""],
["ürün seri numarası 5401234567891234 olarak kayıtlı", "Tam maskeleme", ""],
# Aynı cümlede yok sayılan kod ve maskelenen güvenlik kodu
["işlem kodu 419 ama kartın arkasındaki güvenlik kodu 732",
"Tam maskeleme", ""],
["kargo takip kodu 12345678901 henüz hareket etmemiş",
"Ad soyad açık kalsın", ""],
["maaşım 45.000 TL kredi başvurusu için yeterli mi",
"Sadece finansal veriler", ""],
# Aynı cümle, dört politika — talimatın çıktıyı nasıl çevirdiğini gösterir
["Ali Kaya 0532 111 22 33 numarasından aradı, "
"ibanı TR33 0006 1005 1978 6457 8413 26", "Yalnızca IBAN", ""],
["Ali Kaya 0532 111 22 33 numarasından aradı, "
"ibanı TR33 0006 1005 1978 6457 8413 26", "IBAN açık kalsın", ""],
["Ali Kaya 0532 111 22 33 numarasından aradı, "
"ibanı TR33 0006 1005 1978 6457 8413 26", "Yalnızca telefon", ""],
["Ali Kaya 0532 111 22 33 numarasından aradı, "
"ibanı TR33 0006 1005 1978 6457 8413 26", "Ad soyad açık kalsın", ""],
# Seçici politikalar
["iban:TR330006100519786457841326,telefon:05321112233",
"Yalnızca telefon", ""],
["Müşteri Selin Aksoy tc 10000000146 telefon 0507 123 45 67",
"Ad soyad açık kalsın", ""],
["başvuru sahibi Zeynep Arslan, mail zeynep.arslan@firma.com.tr, "
"adres Cumhuriyet Mah. Lale Sok. No:14 Kadıköy İstanbul",
"Ad soyad açık kalsın", ""],
# Kategori ve serbest talimat
["personel kaydımda kan grubum A Rh negatif ve inanç bilgim Alevi yazıyor",
"Sadece özel nitelikli (KVKK md. 6)", ""],
["ben emre taş tel 0531 222 44 55 iban da TR330006100519786457841326",
"Serbest (aşağıya yaz)", "kişisel bilgi ne varsa kapat geri kalan yazıya dokunma"],
["vpn tanımlayın ip adresim 88.240.12.34, cihaz kimliğim HM8C-S7JK-YDFX",
"Serbest (aşağıya yaz)", "teknik kimlikler dahil tüm kişisel verileri gizle"],
]
ACIKLAMA = """
# Türkçe PII Guard
Türkçe metindeki kişisel verileri tespit edip maskeleyen 0.8B model.
50 etiket tanır ve **kapsam talimatını okur** — aynı metin farklı politika
altında farklı maskelenir.
Model [melikegks/turkish-pii-guard-0.8b](https://huggingface.co/melikegks/turkish-pii-guard-0.8b)
**Not:** Buraya gerçek kişisel veri girme. Değerlendirme tamamen sentetik veriyle
yapılmıştır; gerçek metindeki performans ölçülmemiştir.
"""
with gr.Blocks(title="Türkçe PII Guard") as demo:
gr.Markdown(ACIKLAMA)
with gr.Row():
with gr.Column():
metin = gr.Textbox(
label="Metin", lines=5,
placeholder="müşteri Ahmet Yıldız 0542 321 45 67 numarasından aradı…",
)
secim = gr.Radio(
choices=list(TALIMATLAR), value="Tam maskeleme",
label="Maskeleme politikası",
)
serbest = gr.Textbox(
label="Serbest talimat", lines=2,
placeholder="Örn: Maaşı gizle, ad soyad ve telefona dokunma.",
)
calistir_btn = gr.Button("Maskele", variant="primary")
with gr.Column():
cikti = gr.Textbox(label="Maskelenmiş metin", lines=5,
show_copy_button=True)
ozet = gr.Textbox(label="Özet", lines=2)
uyari = gr.Textbox(label="Şema kontrolü", lines=4)
gr.Examples(examples=ORNEKLER, inputs=[metin, secim, serbest])
calistir_btn.click(calistir, [metin, secim, serbest], [cikti, ozet, uyari])
metin.submit(calistir, [metin, secim, serbest], [cikti, ozet, uyari])
demo.queue().launch()