要閱讀 帶有插圖的文章版本 請(qǐng)前往 http://studyai.com/pytorch-1.4/beginner/fgsm_tutorial.html
如果你正在閱讀這篇文章,希望你能體會(huì)到一些機(jī)器學(xué)習(xí)模型是多么的有效。研究不斷推動(dòng)ML模型變得更快、更準(zhǔn)確和更高效忧额。 然而循衰,設(shè)計(jì)和訓(xùn)練模型的一個(gè)經(jīng)常被忽視的方面是安全性和健壯性璃搜,特別是在面對(duì)希望欺騙模型的對(duì)手時(shí)寇荧。
本教程將提高您對(duì)ML模型的安全漏洞的認(rèn)識(shí),并將深入了解對(duì)抗性機(jī)器學(xué)習(xí)的熱門話題怒坯。 您可能會(huì)驚訝地發(fā)現(xiàn),在圖像中添加不可察覺(jué)的擾動(dòng)會(huì)導(dǎo)致截然不同的模型性能藻懒。 鑒于這是一個(gè)教程剔猿,我們將通過(guò)一個(gè)圖像分類器的例子來(lái)探討這個(gè)主題。 具體來(lái)說(shuō)嬉荆,我們將使用第一種也是最流行的攻擊方法-快速梯度符號(hào)攻擊(Fast Gradient Sign Attack ,FGSM)來(lái)欺騙MNIST分類器归敬。
威脅模型(Threat Model)
有很多種類的對(duì)抗性攻擊,每種攻擊都有不同的目標(biāo)和攻擊者的知識(shí)假設(shè)。但是汪茧,總體目標(biāo) 是在輸入數(shù)據(jù)中增加最少的擾動(dòng)量椅亚,以導(dǎo)致期望的錯(cuò)誤分類。攻擊者的知識(shí)有幾種假設(shè)舱污,其中兩種假設(shè)是: 白盒子(white-box) 和 黑盒子(black-box)呀舔。 白盒子 攻擊假定攻擊者擁有對(duì)模型的全部知識(shí)和訪問(wèn)權(quán)限,包括體系結(jié)構(gòu)扩灯、輸入媚赖、輸出和權(quán)重。 黑盒子 攻擊假設(shè)攻擊者只能訪問(wèn)模型的輸入和輸出珠插,而對(duì)底層架構(gòu)或權(quán)重一無(wú)所知惧磺。 還有幾種目標(biāo)類型,包括 錯(cuò)誤分類(misclassification) 和 源/目標(biāo)錯(cuò)誤分類(source/target misclassification) 捻撑。 錯(cuò)誤分類 的目標(biāo)意味著對(duì)手只希望輸出分類是錯(cuò)誤的磨隘,而不關(guān)心新的分類是什么。 源/目標(biāo)錯(cuò)誤分類 意味著對(duì)手希望更改最初屬于特定源類的圖像布讹,從而將其歸類為特定的目標(biāo)類琳拭。
在這種情況下,F(xiàn)GSM攻擊是以 錯(cuò)誤分類 為目標(biāo)的 白盒攻擊 描验。 有了這些背景信息白嘁,我們現(xiàn)在可以詳細(xì)討論攻擊(attack)了。
快速梯度符號(hào)攻擊(Fast Gradient Sign Attack)
迄今為止膘流,第一次也是最流行的對(duì)抗性攻擊(adversarial attacks)之一被稱為 快速梯度符號(hào)攻擊(FGSM) 絮缅, 古德費(fèi)爾特對(duì)此進(jìn)行了描述: Explaining and Harnessing Adversarial Examples。 攻擊是非常強(qiáng)大的呼股,但卻是直觀的耕魄。它是設(shè)計(jì)用來(lái)攻擊神經(jīng)網(wǎng)絡(luò),利用他們的學(xué)習(xí)方式彭谁,梯度 吸奴。其思想很簡(jiǎn)單, 不是通過(guò)調(diào)整基于反向傳播梯度的權(quán)重來(lái)最小化損失缠局,而是 基于相同的反向傳播梯度調(diào)整輸入數(shù)據(jù)则奥, 使損失最大化 。換句話說(shuō)狭园,攻擊使用損失W.r.t輸入數(shù)據(jù)的梯度读处,然后調(diào)整輸入數(shù)據(jù)以最大化損失。
在我們進(jìn)入代碼之前唱矛,讓我們看一下著名的 FGSM 熊貓示例罚舱,并提取一些記號(hào)(notation)井辜。
fgsm_panda_image
從圖片中, x
是被正確分類為“panda”的原始圖像, y 是 x 的真正的類標(biāo)簽管闷。 θ 表示模型參數(shù)粥脚,并且 J(θ,x,y) 用來(lái) 訓(xùn)練網(wǎng)絡(luò)的損失。 攻擊將梯度反向傳播回輸入數(shù)據(jù)以進(jìn)行計(jì)算 ?xJ(θ,x,y) 渐北。 然后阿逃,它沿著使損失最大化的方向(i.e. sign(?xJ(θ,x,y))) 上 調(diào)整輸入數(shù)據(jù)一小步(? 或 0.007 在圖片中)。 由此產(chǎn)生的擾動(dòng)圖像(perturbed image), x′
, 就會(huì)被目標(biāo)網(wǎng)絡(luò) 誤分類(misclassified) 為 “gibbon”赃蛛, 但事實(shí)上 被擾動(dòng)的圖像依然是個(gè) “panda” 恃锉。
希望現(xiàn)在你已明了本教程的動(dòng)機(jī)了,所以讓我們跳到它的具體實(shí)現(xiàn)吧呕臂。
from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import numpy as np
import matplotlib.pyplot as plt
實(shí)現(xiàn)
在這一小節(jié)中, 我們將討論輸入?yún)?shù)破托,定義在攻擊之下的模型,然后編寫攻擊代碼然后將一些測(cè)試跑起來(lái)歧蒋。
輸入
本教程只有三個(gè)輸入土砂,定義如下:
epsilons - 要用于運(yùn)行的epsilon值列表。在列表中保持0很重要谜洽,因?yàn)樗砹嗽紲y(cè)試集上的模型性能萝映。而且,從直覺(jué)上說(shuō)阐虚, 我們認(rèn)為epsilon越大序臂,擾動(dòng)越明顯,但攻擊越有效实束,降低了模型的準(zhǔn)確性奥秆。由于 數(shù)據(jù)的范圍是 [0,1]
,任何epsilon值都不應(yīng)超過(guò)1咸灿。
pretrained_model - 通向預(yù)先訓(xùn)練過(guò)的MNIST模型的路徑构订,該模型是用 pytorch/examples/mnist 。 為了簡(jiǎn)單起見(jiàn)避矢,請(qǐng)?jiān)?這里 下載經(jīng)過(guò)預(yù)先訓(xùn)練的模型悼瘾。
use_cuda - 布爾標(biāo)志使用CUDA(如果需要和可用的話)。注意审胸,帶有CUDA的GPU對(duì)于本教程來(lái)說(shuō)并不重要分尸,因?yàn)镃PU不會(huì)花費(fèi)太多時(shí)間。
epsilons = [0, .05, .1, .15, .2, .25, .3]
pretrained_model = "data/lenet_mnist_model.pth"
use_cuda=True
受攻擊模型(Model Under Attack)
如前所述歹嘹,受攻擊的模型是與 pytorch/examples/mnist 相同的MNIST模型。您可以訓(xùn)練和保存自己的MNIST模型孔庭,也可以下載和使用所提供的模型尺上。 這里的網(wǎng)絡(luò)定義和測(cè)試dataloader是從MNIST示例中復(fù)制的材蛛。本節(jié)的目的是定義model和dataloader, 然后初始化模型并加載預(yù)先訓(xùn)練的權(quán)重怎抛。
# LeNet Model definition
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
self.conv2_drop = nn.Dropout2d()
self.fc1 = nn.Linear(320, 50)
self.fc2 = nn.Linear(50, 10)
def forward(self, x):
x = F.relu(F.max_pool2d(self.conv1(x), 2))
x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
x = x.view(-1, 320)
x = F.relu(self.fc1(x))
x = F.dropout(x, training=self.training)
x = self.fc2(x)
return F.log_softmax(x, dim=1)
# MNIST Test dataset 和 dataloader 聲明
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=False, download=True, transform=transforms.Compose([
transforms.ToTensor(),
])),
batch_size=1, shuffle=True)
# 定義我們要使用的設(shè)備
print("CUDA Available: ",torch.cuda.is_available())
device = torch.device("cuda" if (use_cuda and torch.cuda.is_available()) else "cpu")
# 初始化網(wǎng)絡(luò)
model = Net().to(device)
# 加載預(yù)訓(xùn)練模型
model.load_state_dict(torch.load(pretrained_model, map_location='cpu'))
# 將模型設(shè)置為評(píng)估模式. 這是為了 Dropout layers卑吭。
model.eval()
FGSM Attack
現(xiàn)在,我們可以通過(guò)擾動(dòng)原始輸入來(lái)定義創(chuàng)建對(duì)抗性樣例(adversarial examples)的函數(shù)马绝。 fgsm_attack 函數(shù)接收三個(gè)輸入: image 是原始的干凈圖像 (x
), epsilon 是 逐像素?cái)_動(dòng)量 (?), 而 data_grad 是損失相對(duì)于(w.r.t)輸入圖像的梯度: (?xJ(θ,x,y)
) 豆赏。 有了這三個(gè)輸入,該函數(shù)就會(huì)按下述方法 創(chuàng)建擾動(dòng)圖像(perturbed image):
perturbed_image=image+epsilon?sign(data_grad)=x+??sign(?xJ(θ,x,y))
最后, 為了保持?jǐn)?shù)據(jù)的原始范圍富稻,將擾動(dòng)圖像裁剪到 [0,1]
范圍內(nèi)掷邦。
# FGSM 攻擊代碼
def fgsm_attack(image, epsilon, data_grad):
# Collect the element-wise sign of the data gradient
sign_data_grad = data_grad.sign()
# Create the perturbed image by adjusting each pixel of the input image
perturbed_image = image + epsilon*sign_data_grad
# Adding clipping to maintain [0,1] range
perturbed_image = torch.clamp(perturbed_image, 0, 1)
# Return the perturbed image
return perturbed_image
測(cè)試函數(shù)
最后,本教程的中心結(jié)果來(lái)自于 test 函數(shù)椭赋。每次調(diào)用該測(cè)試函數(shù)都會(huì)在MNIST測(cè)試集上執(zhí)行完整的測(cè)試步驟抚岗, 并報(bào)告最終的準(zhǔn)確性。但是哪怔,請(qǐng)注意宣蔚,此函數(shù)也接受 epsilon 輸入。這是因?yàn)?test 函數(shù)報(bào)告了一個(gè)模型的準(zhǔn)確性认境, 該模型正受到來(lái)自實(shí)力 ?
的對(duì)手的攻擊胚委。更具體地說(shuō),對(duì)于測(cè)試集中的每個(gè)樣本叉信, 該函數(shù)計(jì)算loss w.r.t the input data (data_grad)亩冬,用 fgsm_attack (perturbed_data
) 創(chuàng)建一個(gè)受擾動(dòng)的圖像,然后檢查被擾動(dòng)的樣例是否是對(duì)抗性的茉盏。除了測(cè)試模型的準(zhǔn)確性外鉴未, 該函數(shù)還保存并返回了一些成功的對(duì)抗性樣例,以供以后可視化鸠姨。
def test( model, device, test_loader, epsilon ):
# Accuracy counter
correct = 0
adv_examples = []
# Loop over all examples in test set
for data, target in test_loader:
# Send the data and label to the device
data, target = data.to(device), target.to(device)
# Set requires_grad attribute of tensor. Important for Attack
data.requires_grad = True
# Forward pass the data through the model
output = model(data)
init_pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability
# If the initial prediction is wrong, dont bother attacking, just move on
if init_pred.item() != target.item():
continue
# Calculate the loss
loss = F.nll_loss(output, target)
# Zero all existing gradients
model.zero_grad()
# Calculate gradients of model in backward pass
loss.backward()
# Collect datagrad
data_grad = data.grad.data
# Call FGSM Attack
perturbed_data = fgsm_attack(data, epsilon, data_grad)
# Re-classify the perturbed image
output = model(perturbed_data)
# Check for success
final_pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability
if final_pred.item() == target.item():
correct += 1
# Special case for saving 0 epsilon examples
if (epsilon == 0) and (len(adv_examples) < 5):
adv_ex = perturbed_data.squeeze().detach().cpu().numpy()
adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) )
else:
# Save some adv examples for visualization later
if len(adv_examples) < 5:
adv_ex = perturbed_data.squeeze().detach().cpu().numpy()
adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) )
# Calculate final accuracy for this epsilon
final_acc = correct/float(len(test_loader))
print("Epsilon: {}\tTest Accuracy = {} / {} = {}".format(epsilon, correct, len(test_loader), final_acc))
# Return the accuracy and an adversarial example
return final_acc, adv_examples
運(yùn)行 Attack
實(shí)現(xiàn)的最后一部分是實(shí)際運(yùn)行攻擊铜秆。在這里,我們對(duì) epsilons 輸入中的每個(gè)epsilon值運(yùn)行一個(gè)完整的測(cè)試步驟讶迁。 對(duì)于每個(gè)epsilon连茧,我們還保存了最終的準(zhǔn)確性和一些成功的對(duì)抗性樣例,將在接下來(lái)繪制出來(lái)巍糯。 注意打印精度是如何隨著epsilon值的增加而降低的啸驯。另外,請(qǐng)注意 ?=0
表示原始測(cè)試的準(zhǔn)確性祟峦,沒(méi)有任何攻擊罚斗。
accuracies = []
examples = []
# Run test for each epsilon
for eps in epsilons:
acc, ex = test(model, device, test_loader, eps)
accuracies.append(acc)
examples.append(ex)
結(jié)果
Accuracy vs Epsilon
第一個(gè)結(jié)果是accuracy vs epsilon的圖。正如前面提到的宅楞,隨著epsilon的增加针姿,我們預(yù)計(jì)測(cè)試的準(zhǔn)確性會(huì)下降袱吆。 這是因?yàn)楦蟮膃psilon意味著我們朝著最大化損失的方向邁出了更大的一步。注意距淫,即使epsilon值是線性的绞绒, 曲線中的趨勢(shì)也不是線性的。例如榕暇,在 ?=0.05
處的準(zhǔn)確度僅比 ?=0.15 低4%蓬衡, 而 ?=0.2 的準(zhǔn)確度比 ?=0.15 低25%。 另外彤枢,注意模型的精度對(duì)10類分類器的隨機(jī)精度影響在 ?=0.25 和 ?=0.3
之間狰晚。
plt.figure(figsize=(5,5))
plt.plot(epsilons, accuracies, "*-")
plt.yticks(np.arange(0, 1.1, step=0.1))
plt.xticks(np.arange(0, .35, step=0.05))
plt.title("Accuracy vs Epsilon")
plt.xlabel("Epsilon")
plt.ylabel("Accuracy")
plt.show()
一些對(duì)抗性樣本
還記得沒(méi)有免費(fèi)午餐的思想嗎?在這種情況下堂污,隨著epsilon的增加家肯,測(cè)試精度降低,但擾動(dòng)變得更容易察覺(jué)盟猖。 實(shí)際上讨衣,攻擊者必須考慮的是準(zhǔn)確性、程度和可感知性之間的權(quán)衡式镐。在這里反镇,我們展示了在每個(gè)epsilon值下 一些成功的對(duì)抗性樣例。圖中的每一行都顯示不同的epsilon值娘汞。第一行是 ?=0
示例歹茶, 它表示原始的“干凈”圖像,沒(méi)有任何擾動(dòng)你弦。每幅圖像的標(biāo)題顯示“原始分類->對(duì)抗性分類”惊豺。 注意,當(dāng) ?=0.15 時(shí)禽作,擾動(dòng)開(kāi)始變得明顯尸昧,在 ?=0.3
時(shí)非常明顯。 然而旷偿,在所有情況下烹俗,人類仍然能夠識(shí)別正確的類別,盡管增加了噪音萍程。
# Plot several examples of adversarial samples at each epsilon
cnt = 0
plt.figure(figsize=(8,10))
for i in range(len(epsilons)):
for j in range(len(examples[i])):
cnt += 1
plt.subplot(len(epsilons),len(examples[0]),cnt)
plt.xticks([], [])
plt.yticks([], [])
if j == 0:
plt.ylabel("Eps: {}".format(epsilons[i]), fontsize=14)
orig,adv,ex = examples[i][j]
plt.title("{} -> {}".format(orig, adv))
plt.imshow(ex, cmap="gray")
plt.tight_layout()
plt.show()
下一步去哪里?
希望本教程能提供一些關(guān)于對(duì)抗性機(jī)器學(xué)習(xí)主題的見(jiàn)解幢妄。這里有許多潛在的方向可走。 這種攻擊代表了對(duì)抗性攻擊研究的開(kāi)始茫负,并且由于有許多關(guān)于如何攻擊和保護(hù)ML模型不受對(duì)手攻擊的想法蕉鸳。 實(shí)際上,在NIPS 2017的比賽中忍法,存在著一種對(duì)抗性的攻防競(jìng)爭(zhēng)潮尝, 本文 介紹了在這場(chǎng)比賽中所采用的許多方法:對(duì)抗攻擊和防御競(jìng)爭(zhēng)无虚。 防御方面的工作也帶來(lái)了使機(jī)器學(xué)習(xí)模型在一般情況下更加健壯的想法, 使機(jī)器學(xué)習(xí)模型既具有自然的擾動(dòng)性衍锚,又具有對(duì)抗性的輸入。
另一個(gè)方向是不同領(lǐng)域的對(duì)抗攻擊和防御嗤堰。對(duì)抗性研究并不局限于圖像領(lǐng)域戴质,請(qǐng)看 這個(gè) 對(duì)語(yǔ)音到文本模型的攻擊。 但是也許了解更多對(duì)抗性機(jī)器學(xué)習(xí)的最好方法是弄臟你的手(意思是讓你動(dòng)手嘗試)踢匣。 嘗試實(shí)現(xiàn)來(lái)自NIPS 2017 競(jìng)賽的不同的攻擊策略告匠,看看它與FGSM有何不同。然后离唬,試著保護(hù)模型不受你自己的攻擊后专。