"""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" 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 donanımında `spaces` paketi vardır ve GPU yalnızca dekore edilmiş # fonksiyon çalışırken tahsis edilir. cpu-basic'te paket yoktur; aynı kod # dekoratörsüz ve CPU'da çalışsın diye ikisi de destekleniyor. try: import spaces ZEROGPU = True except ImportError: spaces = None ZEROGPU = False if not ZEROGPU: torch.set_num_threads(os.cpu_count() or 2) print("Model yükleniyor… ZeroGPU:", ZEROGPU) tokenizer = AutoTokenizer.from_pretrained(MODEL_ADI) model = AutoModelForCausalLM.from_pretrained( MODEL_ADI, dtype=torch.bfloat16 if (ZEROGPU or torch.cuda.is_available()) else torch.float32, ).eval() if ZEROGPU or torch.cuda.is_available(): model = model.to("cuda") IM_END_ID = tokenizer.convert_tokens_to_ids(IM_END) print("Model hazır. Cihaz:", next(model.parameters()).device) 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 = [ ["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", ""], ["iban:TR330006100519786457841326,telefon:05321112233", "Tam maskeleme", ""], ["Ali Kaya 0532 111 22 33 numarasından aradı, " "ibanı TR33 0006 1005 1978 6457 8413 26", "Yalnızca IBAN", ""], ["Müşteri Selin Aksoy tc 10000000146 telefon 0507 123 45 67", "Ad soyad açık kalsın", ""], ["şube kodu 732 ve işlem kodu 419 olarak görünüyor", "Tam maskeleme", ""], ["kargo takip kodu 12345678901 henüz hareket etmemiş", "Tam maskeleme", ""], ["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"], ] 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()