Az elmélet és a képlet tiszta sor, a környezetünk pedig készen áll. Most jön a lényeg: hogyan ültetjük át az FGSM matematikai formuláját – \(x’ = x + \epsilon \cdot \text{sign}(\nabla_x J(\theta, x, y))\) – működő Python kódba? Ahelyett, hogy egyben rád zúdítanám a kész függvényt, bontsuk le a folyamatot a leggyakoribb kérdések mentén, amelyek egy ilyen implementáció során felmerülnek.
Milyen bemenetekre és kimenetekre van szükségünk?
Mielőtt egyetlen sort is írnánk, gondoljuk végig, mit kell a támadást generáló függvényünknek kapnia, és mit várunk el tőle. Egy logikusan felépített függvény drasztikusan leegyszerűsíti a későbbi munkát.
- Bemenetek:
image: A PyTorch tenzor, ami a támadni kívánt bemeneti képet tartalmazza.epsilon: A perturbáció mértékét szabályozó hiperparaméter (egy lebegőpontos szám).data_grad: A veszteségfüggvény gradiense a bemeneti képhez képest. Ezt kell majd felhasználnunk a zaj generálásához.
- Kimenet:
perturbed_image: Az eredeti képből generált, rosszindulatú zajjal ellátott új kép, szintén PyTorch tenzorként.
A függvényünk váza tehát valahogy így nézne ki:
# PyTorch importálása
import torch
def fgsm_attack(image, epsilon, data_grad):
"""
FGSM támadás generálása a bemeneti kép és a gradient alapján.
Argumentumok:
image (torch.Tensor): Az eredeti, tiszta kép.
epsilon (float): A perturbáció mértékét szabályozó érték.
data_grad (torch.Tensor): A veszteség gradiense a bemeneti képhez képest.
Visszatérési érték:
torch.Tensor: A perturbált, adverzárius kép.
"""
# ... az implementáció itt következik ...
pass
Hogyan alkalmazzuk a képletet a gradientre?
Ez az FGSM lelke. A képlet a gradient előjelét (sign) kéri. A PyTorch szerencsére rendelkezik egy beépített függvénnyel erre: torch.sign(). Ez a függvény elemenként megállapítja egy tenzor elemeinek előjelét, és egy új tenzort ad vissza, amelyben -1, 0 vagy 1 értékek szerepelnek.
# A gradient előjelének meghatározása
sign_data_grad = data_grad.sign()
Ha ez megvan, a képlet következő lépése az előjel-tenzor megszorzása az epsilon értékkel, majd hozzáadása az eredeti képhez. Ez egy egyszerű elemenkénti szorzás és összeadás.
# A perturbált kép létrehozása a képlet alapján
perturbed_image = image + epsilon * sign_data_grad
Mi a helyzet a képi adatok érvényességével?
Itt jön egy kritikus, gyakorlati lépés, amit a puszta matematikai képlet nem tartalmaz. A képeink pixelértékei általában egy meghatározott tartományban vannak (pl. [0, 1] a normalizált képeknél, vagy [0, 255] az egész számoknál). A zaj hozzáadása után könnyen előfordulhat, hogy egyes pixelértékek „kilógnak” ebből a tartományból (pl. -0.1 vagy 1.2 lesz az értékük).
Ezek az érvénytelen értékek problémát okozhatnak a modellnek, és nem is reprezentálnak valós képet. A megoldás az, hogy a perturbált kép értékeit „levágjuk” (clamp) az érvényes tartományon belülre.
A PyTorch erre is kínál egy egyszerű megoldást, a torch.clamp() függvényt, amellyel megadhatjuk a minimális és maximális elfogadható értékeket.
# A pixelértékek visszaszorítása az érvényes [0,1] tartományba
perturbed_image = torch.clamp(perturbed_image, 0, 1)
A teljes FGSM generáló függvény egyben
Most, hogy minden lépést külön-külön megvizsgáltunk, rakjuk össze a teljes, működőképes függvényt. Ez a kód fogja képezni a következő fejezetben végrehajtott támadásunk alapját.
import torch
def fgsm_attack(image, epsilon, data_grad):
"""
FGSM támadás generálása a bemeneti kép és a gradient alapján.
Ez a függvény feltételezi, hogy a bemeneti kép pixelértékei a [0,1]
tartományban vannak normalizálva.
Argumentumok:
image (torch.Tensor): Az eredeti, tiszta kép.
epsilon (float): A perturbáció mértékét szabályozó érték.
data_grad (torch.Tensor): A veszteség gradiense a bemeneti képhez képest.
Visszatérési érték:
torch.Tensor: A perturbált, adverzárius kép.
"""
# 1. Lépés: A gradient előjelének összegyűjtése
# Ez a lépés felel meg a sign(∇x J(...)) résznek a képletben.
sign_data_grad = data_grad.sign()
# 2. Lépés: A perturbált kép létrehozása az FGSM képlet alapján
# x' = x + ε * sign(...)
perturbed_image = image + epsilon * sign_data_grad
# 3. Lépés: A kép értékeinek "clamping"-je, hogy a [0,1] tartományban maradjanak
# Ez egy kritikus gyakorlati lépés az érvényes képi adatok biztosítására.
perturbed_image = torch.clamp(perturbed_image, 0, 1)
# 4. Lépés: A kész adverzárius kép visszaadása
return perturbed_image
Az FGSM perturbáció generálásának folyamata a kódban.
Ezzel a tiszta és jól dokumentált függvénnyel a kezünkben már mindenünk megvan ahhoz, hogy a következő fejezetben ténylegesen végrehajtsuk a támadást egy betanított modellen, és kiértékeljük annak hatékonyságát.