Pada penglihatan manusia, gambar panda yang diberi derau sebesar 1% tetap terlihat sebagai panda — tetapi model klasifikasi citra dapat mengubah jawabannya menjadi “gibbon” dengan keyakinan penuh. Input semacam itu disebut adversarial example: sampel yang digeser secara terhitung ke arah yang memperbesar kesalahan model. Chapter ini membedah rumusnya (FGSM), menjelaskan mengapa arsitektur deep learning secara struktural rentan, mendemonstrasikan keruntuhannya dengan model mini PyTorch, dan menutup dengan dua jalur pertahanan. Seluruh contoh dijalankan pada model mainan milik sendiri di CPU — untuk memahami mekanisme, bukan untuk dipakai pada sistem milik orang lain.
Rumus FGSM dan Mengapa Model Rapuh #
Fast Gradient Sign Method (FGSM) dari Goodfellow dkk. (arXiv:1412.6572 ) membangkitkan gangguan dalam satu langkah:
x' = x + eps * sign( gradien loss terhadap x )Arah sign(gradien) adalah arah yang menaikkan loss paling cepat per satuan perubahan, jadi eps menggeser setiap fitur sebesar eps saja — dalam demo kita, data berskala simpangan baku 1,0 sehingga eps = 0,1 berarti gangguan 10% dari derau alami data. Model deep learning rapuh terhadap geseran kecil ini karena dua sifat yang sudah kita bahas sepanjang Course. Pertama, model bekerja linier di sekitar input: lapisan-lapisan menjumlahkan bobot, dan pada dimensi tinggi penjumlahan ribuan koefisien kecil menghasilkan pergeseran keluaran yang besar. Kedua, gradien terhadap input tersedia gratis — penyerang cukup memakai mesin autograd yang sama dengan yang dipakai training.
Demo: Model Mini yang Runtuh #
Berkas fgsm_mini.py melatih MLP tiga kelas pada 900 sampel sintetis 64 fitur hingga akurasi uji 0,930, lalu menyerang 600 sampel uji dengan FGSM pada lima nilai epsilon. Satu sampel dibedah konkret — prediksi dan confidence sebelum-sesudah — dan bagian terakhir menguji transferability: gangguan yang dirancang pada model1 diuji ke model2 yang dilatih dari awal dengan seed berbeda, meniru skenario penyerang yang tidak memiliki akses internal ke model yang diserang:
"""Adversarial examples dengan FGSM (Fast Gradient Sign Method).
Model MLP tiga kelas pada data sintetis 64 fitur. Gangguan sekecil
epsilon pada tiap fitur — jauh di bawah simpangan baku data —
mengubah prediksi secara drastis. Ditutup dengan uji transferability:
gangguan yang dirancang pada satu model tetap efektif pada model lain
yang tidak pernah dilihat penyerang. Contoh diuji nyata pada CPU
dengan PyTorch 2.14 dan seed tetap; seluruh kode hanya untuk edukasi.
"""
import torch
torch.manual_seed(7)
# ---------- 1. Data sintetis: 3 kelas, 64 fitur ----------
n, d, k = 1500, 64, 3
pusat = torch.zeros(k, d)
pusat[0, 0], pusat[1, 1], pusat[2, 2] = 2.5, 2.5, 2.5
label = torch.randint(k, (n,))
X = torch.randn(n, d) + pusat[label]
X_latih, y_latih = X[:900], label[:900]
X_uji, y_uji = X[900:], label[900:]
def latih(seed):
torch.manual_seed(seed)
mlp = torch.nn.Sequential(
torch.nn.Linear(d, 128), torch.nn.ReLU(),
torch.nn.Linear(128, 128), torch.nn.ReLU(),
torch.nn.Linear(128, k))
opt = torch.optim.Adam(mlp.parameters(), lr=0.01,
weight_decay=1e-4)
for _ in range(2000):
opt.zero_grad()
loss = torch.nn.functional.cross_entropy(mlp(X_latih), y_latih)
loss.backward()
opt.step()
return mlp
model1 = latih(7)
with torch.no_grad():
pred_asli = model1(X_uji).argmax(1)
p_asli = torch.softmax(model1(X_uji), 1)
print(f"akurasi uji model1 : "
f"{(pred_asli == y_uji).float().mean():.3f}")
print(f"confidence rata-rata (asli) : {p_asli.max(1).values.mean():.3f}")
# ---------- 2. FGSM: x' = x + eps * sign(gradien loss wrt x) ----------
def fgsm(model, x, y, eps):
x = x.clone().detach().requires_grad_(True)
loss = torch.nn.functional.cross_entropy(model(x), y)
g, = torch.autograd.grad(loss, x)
return (x + eps * g.sign()).detach()
print()
print("===== SWEEP EPSILON (600 sampel uji) =====")
print(f"{'eps':>5s} {'L2 delta / L2 data':>18s} {'pred. berubah':>13s} "
f"{'akurasi jadi':>12s}")
for eps in [0.02, 0.05, 0.10, 0.20, 0.30]:
x_adv = fgsm(model1, X_uji, y_uji, eps)
with torch.no_grad():
pred_adv = model1(x_adv).argmax(1)
berubah = (pred_adv != pred_asli).float().mean()
rasio = (x_adv - X_uji).norm(dim=1).mean() / X_uji.norm(dim=1).mean()
print(f"{eps:5.2f} {rasio:18.1%} {berubah:13.1%} "
f"{(pred_adv == y_uji).float().mean():12.3f}")
# ---------- 3. Satu sampel konkret ----------
eps_demo = 0.20
x_adv = fgsm(model1, X_uji, y_uji, eps_demo)
with torch.no_grad():
pred_adv = model1(x_adv).argmax(1)
p_adv = torch.softmax(model1(x_adv), 1)
i = int((pred_adv != pred_asli).nonzero()[0, 0])
print()
print(f"===== SATU SAMPEL (baris uji ke-{i}, eps={eps_demo}) =====")
print(f"label benar : kelas {y_uji[i].item()}")
print(f"prediksi asli : kelas {pred_asli[i].item()} "
f"(confidence {p_asli[i].max():.3f})")
print(f"prediksi teracau : kelas {pred_adv[i].item()} "
f"(confidence {p_adv[i].max():.3f})")
delta = (x_adv[i] - X_uji[i])
print(f"perubahan tiap fitur dibatasi {eps_demo} "
f"(simpangan baku data 1.0)")
print(f"fitur yang berubah di atas 0.01 : "
f"{(delta.abs() > 0.01).sum().item()}/{d}")
# ---------- 4. Transferability antar model ----------
model2 = latih(21)
with torch.no_grad():
pred_asli2 = model2(X_uji).argmax(1)
print()
print("===== TRANSFERABILITY: gangguan dari model1 diuji ke model2 =====")
print(f"akurasi uji model2 (seed berbeda): "
f"{(pred_asli2 == y_uji).float().mean():.3f}")
for eps in [0.20, 0.30]:
x_adv = fgsm(model1, X_uji, y_uji, eps)
with torch.no_grad():
pred_adv2 = model2(x_adv).argmax(1)
pindah = (pred_adv2 != pred_asli2).float().mean()
print(f"eps={eps:.2f}: prediksi model2 berubah {pindah:.1%}")Output yang Dihasilkan:
akurasi uji model1 : 0.930
confidence rata-rata (asli) : 0.973
===== SWEEP EPSILON (600 sampel uji) =====
eps L2 delta / L2 data pred. berubah akurasi jadi
0.02 1.9% 2.3% 0.907
0.05 4.8% 4.2% 0.888
0.10 9.6% 9.0% 0.840
0.20 19.2% 25.2% 0.678
0.30 28.7% 43.3% 0.497
===== SATU SAMPEL (baris uji ke-3, eps=0.2) =====
label benar : kelas 0
prediksi asli : kelas 0 (confidence 0.925)
prediksi teracau : kelas 2 (confidence 0.994)
perubahan tiap fitur dibatasi 0.2 (simpangan baku data 1.0)
fitur yang berubah di atas 0.01 : 64/64
===== TRANSFERABILITY: gangguan dari model1 diuji ke model2 =====
akurasi uji model2 (seed berbeda): 0.928
eps=0.20: prediksi model2 berubah 21.0%
eps=0.30: prediksi model2 berubah 38.5%Contoh di atas diuji nyata dengan PyTorch 2.14 pada CPU (mesin uji tidak memiliki GPU) dan selesai dalam sekitar 17 detik; seed tetap membuat seluruh angka dapat direproduksi. Mari kita telaah kode di atas baris per baris:
- Baris 16-23 (data sintetis): tiga kelas diletakkan pada sumbu fitur berbeda dengan jarak 2,5 simpangan baku — data yang sehat dan mudah, sengaja dipilih agar keruntuhan akurasi murni berasal dari serangan, bukan dari kualitas model.
- Baris 26-40 (fungsi
latih): fungsi pelatih MLP dua lapisan tersembunyi dengan Adam 2000 epoch; seed menjadi parameter sehingga model1 dan model2 hanya berbeda inisialisasi, meniru dua model independen yang dilatih pada data sama. - Baris 52-57 (fungsi
fgsm): inti serangan — input di-clonedan diberirequires_grad, loss dihitung,torch.autograd.gradmengambil gradien terhadap input, lalux + eps * g.sign()menggeser tiap fitur sebesar epsilon persis ke arah kenaikan loss. - Baris 63-70 (sweep epsilon): untuk tiap epsilon dicetak rasio jarak L2 gangguan terhadap jarak L2 data, persentase prediksi yang berubah, dan akurasi tersisa; tabel output memperlihatkan transisi mulus dari gangguan tak terasa (eps 0,02; 2,3% berubah) hingga keruntuhan (eps 0,30; akurasi 0,497 — setara tebakan acak tiga kelas).
- Baris 74-86 (satu sampel): baris uji ke-3 dipilih karena prediksinya berubah; confidence kelas 0 sebesar 0,925 berbalik menjadi kelas 2 dengan confidence 0,994 padahal seluruh 64 fitur hanya digeser maksimum 0,2 — keyakinan model tidak mencerminkan ketahanan input.
- Baris 93-103 (transferability): gangguan dari model1 membuat 21,0% (eps 0,20) hingga 38,5% (eps 0,30) prediksi model2 berubah tanpa penyerang pernah menyentuh model2 — bukti bahwa kerentanan ini dibagikan antar model yang dilatih pada data serupa, sehingga serangan bisa disiapkan lewat “model bayangan”.
Pertahanan: Adversarial Training dan Sertifikasi #
Pertahanan yang paling teruji adalah adversarial training: selama training, tiap batch ditambah salinan teracau FGSM (atau varian iteratif PGD yang lebih kuat) sehingga model belajar pada wilayah input yang digeser — tutorial resmi FGSM di PyTorch memperlihatkan pola lengkapnya. Jalur kedua adalah certified robustness: metode seperti randomized smoothing membuktikan secara matematis bahwa prediksi tidak berubah dalam radius tertentu, menukar akurasi bersih dengan jaminan terukur. Jalur ketiga bersifat sistem: pembatasan input (validasi rentang, kompresi lossy) dan monitoring penyimpangan distribusi pada saat inferensi, yang dibahas sebagai drift pada Monitoring Model Produksi .
Perhatikan juga pola yang akan berulang pada chapter berikutnya: penyerang tidak butuh akses istimewa — gradien, keluaran confidence, bahkan sekadar contoh data sudah cukup. Serangan berikutnya bahkan tidak menyentuh model sama sekali, melainkan menyerang sebelum model dilatih: racun yang disisipkan ke data latih.