PII Masking - Qwen2.5-3B SFT+DPO (Bahasa Indonesia)

Model 3B untuk masking data pribadi (NAMA, NIK, NOMOR_HP) di teks administratif Bahasa Indonesia. Fine-tuned dari Qwen2.5-3B-Instruct dengan SFT + DPO (QLoRA 4-bit). Adapter only - butuh base model Qwen/Qwen2.5-3B-Instruct.

Tugas

Diberikan teks Bahasa Indonesia yang mengandung PII, model mengembalikan teks yang sama dengan setiap PII diganti tag:

Tipe Tag
Nama orang [NAMA]
NIK (16 digit) [NIK]
Nomor HP (awalan 08) [NOMOR_HP]

Distraktor TIDAK di-mask: nomor rekening, nomor transaksi, kode referensi, dan angka lain yang bukan data pribadi.

Inference (copy-paste, self-contained)

import torch
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer

# 1. Load base model (fp16 - ~6GB VRAM)
base = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2.5-3B-Instruct",
    torch_dtype=torch.float16,
    device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-3B-Instruct")

# 2. Load DPO adapter
model = PeftModel.from_pretrained(base, "qrizan/pii-masking-qwen2.5-3b-dpo_beta0_5")
model.eval()

# 3. System prompt
SYSTEM_PROMPT = (
    "Anda adalah sistem masking PII (data pribadi) untuk teks Bahasa Indonesia.\n"
    "Tugas Anda: kembalikan teks yang SAMA PERSIS, tetapi ganti setiap data pribadi "
    "dengan tag yang sesuai.\n\n"
    "Aturan masking:\n"
    "- Nama orang -> [NAMA]\n"
    "- NIK (16 digit) -> [NIK]\n"
    "- Nomor HP (diawali 08) -> [NOMOR_HP]\n\n"
    "JANGAN di-mask (biarkan apa adanya): nomor rekening bank, nomor transaksi, "
    "kode referensi, dan angka lain yang bukan data pribadi.\n\n"
    "Ketentuan keluaran:\n"
    "- Keluarkan HANYA teks hasil masking.\n"
    "- Jangan tambahkan kalimat pengantar, penjelasan, catatan, atau tanda kutip.\n"
    "- Pertahankan seluruh kata, tanda baca, dan spasi lain seperti aslinya.\n"
    "- Jika sebuah data pribadi ditulis dengan spasi/pemisah di tengah digit, tetap "
    "ganti seluruhnya dengan satu tag."
)

def mask_pii(text: str, max_new_tokens: int = 256) -> str:
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"Mask PII pada teks berikut:\n{text}"},
    ]
    inputs = tokenizer.apply_chat_template(
        messages, tokenize=True, add_generation_prompt=True,
        return_tensors="pt", return_dict=True,
    ).to(model.device)

    with torch.no_grad():
        output_ids = model.generate(
            **inputs,
            max_new_tokens=max_new_tokens,
            do_sample=False,
            pad_token_id=tokenizer.eos_token_id,
        )

    prompt_len = inputs["input_ids"].shape[1]
    result = tokenizer.decode(output_ids[0][prompt_len:], skip_special_tokens=True)
    return result.strip()


# Contoh
print(mask_pii("Halo, saya Budi Santoso, NIK 3273010101010001, HP 081234567890."))
# Output: Halo, saya [NAMA], NIK [NIK], HP [NOMOR_HP].

print(mask_pii("Transfer ke rekening 1234567890 atas nama Siti Rahayu."))
# Output: Transfer ke rekening 1234567890 atas nama [NAMA].
# (Nomor rekening tidak di-mask - ini distraktor, bukan PII)

Evaluasi

Semua metrik diukur di eval.jsonl (100 record sintetis Bahasa Indonesia, 15 di antaranya template holdout yang tidak pernah dilihat saat training - genuinely held-out).

Metrik Base (zero-shot) SFT DPO Frontier (GPT-4o-mini)
leak_rate 18.70% 0.00% 0.00% 3.48%
over_mask_rate 47.42% 7.22% 0.00% 0.00%
breach_rate 30.00% 0.00% 0.00% 8.00%
exact_match 0.0% 93.0% 97.0% 89.0%
mean_jaccard 0.749 0.951 0.984 0.973

Template 8 focus (glued-text, genuinely held-out, 7 record):

Metrik SFT DPO (ini)
over_mask_rate 100% (7/7) 0% (0/7)
leak_rate 0% 0%
exact_match 0% (0/7) 57.1% (4/7)
mean_jaccard 0.304 0.771

DPO menurunkan over-mask di template glued-text dari 100% โ†’ 0%, tapi 3/7 record masih punya tag hallucination minor (mis. [NAMA] โ†’ [FAMA]) - bukan leak literal, tapi tidak exact match. Tidak ada regresi di 12 template lain (non-holdout tetap 100% exact match).

Training

SFT DPO
Base Qwen2.5-3B-Instruct SFT adapter
Method Unsloth QLoRA 4-bit TRL DPOTrainer + PEFT
LoRA r=16, alpha=16 r=16, alpha=16
Data 400 train / 80 dev 12 preference pairs (template 8)
Epochs 5 40
LR 2e-4 5e-5
Beta (KL) - 0.5
Infra Kaggle T4 Kaggle T4

Limitasi

  • PII scope terbatas - hanya NAMA, NIK, NOMOR_HP. Email, alamat, nomor rekening sebagai PII belum ditangani.
  • Data sintetis - 13 template generator. Generalisasi ke teks dunia nyata belum diuji.
  • Tag hallucination residual - 3/7 record glued-text punya tag minor error. over_mask_rate/leak_rate buta terhadap ini - cek exact_match untuk gambaran lengkap.
  • Model 3B - mungkin kurang kapabel untuk konteks sangat kompleks (narasi panjang, multi-entitas).

Adapter terkait

Downloads last month
4
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Model tree for qrizan/pii-masking-qwen2.5-3b-dpo_beta0_5

Base model

Qwen/Qwen2.5-3B
Adapter
(1343)
this model