KI: PRAKTEK 12: Privacy-Preserving AI (Differential Privacy)
PRAKTEK 12: Privacy-Preserving AI (Differential Privacy) Tujuan Di akhir sesi ini, mahasiswa bisa: Memahami trade-off: privasi vs akurasi (utility). Menerapkan differential privacy (DP) pada analitik sederhana (mean, count, histogram). Mengukur utility loss (mis. MAE/RMSE) setelah diberi noise. Menghasilkan dataset/hasil analitik yang lebih aman untuk dibagikan. (Opsional) Mengamankan file hasil rilis memakai GnuPG (enkripsi & tanda tangan). Inti mindset: “Kalau datanya akan dibagi, asumsi terburuk: musuh punya informasi tambahan.” DP membantu mengurangi risiko “kebocoran lewat statistik”. Konsep Inti yang Perlu Dipahami 1. Apa itu Differential Privacy? Secara sederhana: sebuah mekanisme memenuhi differential privacy jika hasil rilis tidak berubah drastis walaupun 1 orang di dataset ditambahkan atau dihapus. Parameter utama: ε (epsilon) ε kecil ⇒ privasi lebih kuat ⇒ noise lebih besar ⇒ akurasi turun ε besar ⇒ privasi lebih lemah ⇒ noise kecil ⇒ akurasi naik 2. Kenapa “Noise Injection” masuk akal? Karena banyak kebocoran privasi terjadi bukan dari “membuka row data”, tapi dari: rilis mean / count / histogram dashboard statistik UMKM/kampus/kelas laporan “rata-rata nilai” + filter kecil (mis. per prodi, per angkatan) DP menambahkan noise terukur agar statistik tetap berguna, tapi sulit dipakai untuk menebak data individu. 3. Istilah penting (biar tidak bingung) Sensitivity: seberapa besar output statistik bisa berubah jika 1 data berubah. Laplace mechanism: noise dari distribusi Laplace (umum untuk count/mean). Clipping: membatasi nilai agar sensitivity terkendali (praktik wajib). Rule utama DP di praktikum ini: sebelum DP, lakukan clipping agar sensitivity jelas. Tools (Open Source) Ubuntu 24.04 Python 3 (venv, pip) Library: numpy, pandas (Opsional) matplotlib untuk visualisasi (Opsional) GnuPG untuk mengamankan file rilis Skenario Real yang Bisa Diimplementasi Bayangkan Anda punya dataset internal: age (umur) income (pendapatan) score (nilai) department (kategori) Anda ingin merilis: Rata-rata pendapatan per kelas/kelompok Jumlah orang per kategori Histogram nilai untuk laporan Tanpa DP, mean/count bisa jadi celah (mis. difference attack saat seseorang tahu semua orang kecuali 1). LANGKAH PRAKTIKUM (Ubuntu 24.04) Step 0 — Setup Environment
sudo apt update sudo apt install -y python3 python3-venv python3-pip gnupg mkdir -p praktek12_dp && cd praktek12_dp python3 -m venv .venv source .venv/bin/activate pip install -U pip pip install numpy pandas matplotlib
Step 1 — Siapkan Dataset Contoh (atau pakai data Anda)
Kita buat dataset sintetis supaya aman untuk latihan, tapi strukturnya realistis.
Buat file: generate_dataset.py
import numpy as np
import pandas as pd
def main():
rng = np.random.default_rng(42) n = 2000
dept = rng.choice(["TI", "SI", "DKV", "Bisnis"], size=n, p=[0.35, 0.30, 0.20, 0.15]) age = rng.integers(18, 45, size=n)
# income realistis (lognormal) dalam juta rupiah income = rng.lognormal(mean=2.2, sigma=0.45, size=n) # ~ 5-20 jutaan income = income * 1_000_000
# score 0-100 score = np.clip(rng.normal(loc=78, scale=10, size=n), 0, 100)
df = pd.DataFrame({
"department": dept,
"age": age,
"income": income.round(0).astype(int),
"score": score.round(1),
})
df.to_csv("raw_dataset.csv", index=False)
print("OK: raw_dataset.csv dibuat. Jumlah row:", len(df))
print(df.head(5))
if __name__ == "__main__":
main()
Jalankan: python generate_dataset.py Step 2 — Implementasi Differential Privacy (Noise Injection + Clipping) Kita akan: clip income ke rentang wajar (mis. 0..50 juta) hitung mean/count/histogram tambahkan noise Laplace dengan parameter ε bandingkan utility (error) Buat file: dp_release.py import argparse import numpy as np import pandas as pd
def laplace_noise(scale: float, rng: np.random.Generator) -> float:
# Laplace(0, b) => scale = b return rng.laplace(0.0, scale)
def dp_count(true_count: int, epsilon: float, rng: np.random.Generator) -> int:
# sensitivity untuk count = 1 scale = 1.0 / epsilon noisy = true_count + laplace_noise(scale, rng) return int(max(0, round(noisy)))
def dp_mean(values: np.ndarray, epsilon: float, clip_min: float, clip_max: float, rng: np.random.Generator) -> float:
"""
DP mean via Laplace:
- clip values => sensitivity mean = (clip_max - clip_min) / n
- add Laplace noise to mean
"""
clipped = np.clip(values, clip_min, clip_max)
n = len(clipped)
if n == 0:
return float("nan")
true_mean = float(np.mean(clipped)) sensitivity = (clip_max - clip_min) / n scale = sensitivity / epsilon noisy_mean = true_mean + laplace_noise(scale, rng) return noisy_mean
def dp_histogram(values: np.ndarray, bins: np.ndarray, epsilon: float, rng: np.random.Generator) -> np.ndarray:
""" DP histogram: - hitung count per bin - tambahkan Laplace noise ke setiap bin Catatan: ini menghabiskan budget DP per bin (komposisi). Untuk praktikum, kita pakai epsilon_per_bin = epsilon / k """ counts, _ = np.histogram(values, bins=bins) k = len(counts) epsilon_per_bin = epsilon / max(1, k)
noisy = []
for c in counts:
noisy.append(dp_count(int(c), epsilon_per_bin, rng))
return np.array(noisy, dtype=int)
def utility_metrics(true_arr: np.ndarray, noisy_arr: np.ndarray) -> dict:
err = noisy_arr.astype(float) - true_arr.astype(float)
mae = float(np.mean(np.abs(err)))
rmse = float(np.sqrt(np.mean(err**2)))
return {"MAE": mae, "RMSE": rmse}
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--input", default="raw_dataset.csv")
parser.add_argument("--epsilon", type=float, default=1.0)
parser.add_argument("--seed", type=int, default=123)
parser.add_argument("--clip_income_max", type=float, default=50_000_000.0) # 50 juta
args = parser.parse_args()
rng = np.random.default_rng(args.seed) df = pd.read_csv(args.input)
eps = args.epsilon
if eps <= 0:
raise ValueError("epsilon harus > 0")
# ====== RELEASE 1: DP Count per department ====== dept_counts = df["department"].value_counts().sort_index() true_counts = dept_counts.values
noisy_counts = np.array([dp_count(int(c), eps, rng) for c in true_counts], dtype=int)
# ====== RELEASE 2: DP Mean income (global) ====== income = df["income"].to_numpy(dtype=float) dp_income_mean = dp_mean(income, eps, clip_min=0.0, clip_max=args.clip_income_max, rng=rng) true_income_mean = float(np.mean(np.clip(income, 0.0, args.clip_income_max)))
# ====== RELEASE 3: DP Histogram score ====== score = df["score"].to_numpy(dtype=float) bins = np.array([0,10,20,30,40,50,60,70,80,90,100], dtype=float) true_hist, _ = np.histogram(score, bins=bins) dp_hist = dp_histogram(score, bins=bins, epsilon=eps, rng=rng)
# ====== Utility evaluation ====== count_metrics = utility_metrics(true_counts, noisy_counts) hist_metrics = utility_metrics(true_hist, dp_hist)
result = {
"epsilon": eps,
"release": {
"dp_count_per_department": {
"departments": dept_counts.index.tolist(),
"true_counts": true_counts.tolist(),
"noisy_counts": noisy_counts.tolist(),
"utility": count_metrics,
},
"dp_mean_income": {
"clip_max": args.clip_income_max,
"true_mean": true_income_mean,
"noisy_mean": dp_income_mean,
"abs_error": abs(dp_income_mean - true_income_mean),
},
"dp_hist_score": {
"bins": bins.tolist(),
"true_hist": true_hist.tolist(),
"noisy_hist": dp_hist.tolist(),
"utility": hist_metrics,
}
}
}
# Simpan "dataset aman" versi rilis (bukan raw rows!)
# Konsep penting: seringkali yang aman itu *released statistics*, bukan "anonymized rows"
import json
with open("dp_release.json", "w") as f:
json.dump(result, f, indent=2)
print("OK: dp_release.json dibuat.")
print("Ringkas:")
print("- epsilon:", eps)
print("- mean income true vs noisy:", true_income_mean, "=>", dp_income_mean)
print("- count utility (MAE/RMSE):", count_metrics)
print("- hist utility (MAE/RMSE):", hist_metrics)
if __name__ == "__main__":
main()
Jalankan beberapa eksperimen epsilon: python dp_release.py --epsilon 0.2 python dp_release.py --epsilon 1.0 python dp_release.py --epsilon 5.0 Yang harus kamu amati: ε=0.2 → privasi kuat, noise besar, error naik ε=5.0 → privasi lemah, noise kecil, error turun Step 3 — Bandingkan Utility Secara “Data Scientist” Biar fun, bikin tabel perbandingan. Jalankan: for e in 0.2 0.5 1 2 5; do
echo "==== EPSILON $e ====" python dp_release.py --epsilon $e | tail -n 6
done Tantangan (menantang tapi doable): Tentukan epsilon “layak rilis” untuk laporan kampus/UMKM. Target utility misalnya: MAE count per kategori < 10 Error mean < 3% dari true mean Output Praktikum (Yang Dikumpulkan) Mahasiswa mengumpulkan: dp_release.json (hasil rilis DP) Laporan singkat (1–2 halaman) berisi: nilai ε yang dicoba perbandingan error (MAE/RMSE) + interpretasi rekomendasi ε final dan alasan catatan trade-off yang terjadi Catatan penting: yang Anda rilis adalah privacy-preserving analytics, bukan “dataset baris-per-baris”. BONUS: Amankan File Rilis dengan GnuPG (Enkripsi + Tanda Tangan) Ini realistis banget: Anda mau kirim dp_release.json ke dosen/tim. 1. Generate key (sekali saja)
gpg --full-generate-key Pilih: jenis: RSA and RSA size: 3072 atau 4096 isi nama & email Cek: gpg --list-keys 2. Enkripsi file untuk penerima (recommended) Misal penerima punya email dosen@example.ac.id (harus sudah ada public key-nya di keyring Anda). Import public key dulu jika perlu: gpg --import dosen_publickey.asc Enkripsi: gpg --encrypt --recipient dosen@example.ac.id dp_release.json
- hasil: dp_release.json.gpg
3. Decrypt (di sisi penerima)
gpg --decrypt dp_release.json.gpg > dp_release_decrypted.json 4. Tanda tangan file (integrity)
gpg --detach-sign dp_release.json
- hasil: dp_release.json.sig
Verifikasi: gpg --verify dp_release.json.sig dp_release.json Diskusi Kelas Pertanyaan pemantik: Kalau Anda rilis mean income per departemen, berapa minimal ukuran kelompok agar aman? Apa yang terjadi kalau filter dibuat terlalu spesifik (mis. “TI semester 1 kelas A”)? Apakah DP cukup? Kapan perlu tambahan: access control, audit log, dan encryption? Takeaway yang wajib dibawa pulang: Privasi itu desain sistem, bukan cuma “hapus kolom NIK”. DP adalah alat untuk membuat rilis statistik lebih aman dan terukur.