Spaces:
Sleeping
Sleeping
File size: 10,874 Bytes
134375c f9fbcba 134375c e672d7b 134375c b401778 134375c 78101a7 f9fbcba 78101a7 f9fbcba a0af152 f9fbcba 78101a7 b401778 134375c f9fbcba 78101a7 f9fbcba 78101a7 f9fbcba 134375c b401778 f9fbcba 134375c f9fbcba 134375c e672d7b 134375c f0fdf2a e672d7b 134375c e672d7b 134375c e672d7b 134375c e672d7b 134375c e672d7b 134375c e672d7b 134375c 4e94c90 134375c 4e94c90 b3fa1e9 4e94c90 b3fa1e9 4e94c90 b3fa1e9 4e94c90 134375c a35b73b 4e94c90 134375c 4e94c90 134375c 4e94c90 134375c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 | """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()
|