目錄
1、FGSM原理
2、pytorch實現(xiàn)
2.1 建立模型
2.2 FGSM模塊
2.3 測試
2.4 可視化對比
2.5 對比樣本與對抗樣本
1、FGSM原理
論文 Explaining and harnessing adversarial examples. 這篇論文由Goodfellow等人發(fā)表在ICLR2015會議上,是對抗樣本生成領(lǐng)域的經(jīng)典論文。
- FGSM(fast gradient sign method)是一種基于梯度生成對抗樣本的算法,屬于對抗攻擊中的無目標(biāo)攻擊(即不要求對抗樣本經(jīng)過model預(yù)測指定的類別,只要與原樣本預(yù)測的不一樣即可)
- 我們在理解簡單的dp網(wǎng)絡(luò)結(jié)構(gòu)的時候,在求損失函數(shù)最小值,我們會沿著梯度的反方向移動,使用減號,也就是所謂的梯度下降算法;而FGSM可以理解為梯度上升算法,也就是使用加號,使得損失函數(shù)最大化。先看下圖效果,goodfellow等人通過對一個大熊貓照片加入一定的擾動(即噪音點),輸入model之后就被判斷為長臂猿。
FGSM樣本生成
公式
- 如下圖,其中 x 是原始樣本,θ 是模型的權(quán)重參數(shù)(即w),y是x的真實類別。輸入原始樣本,權(quán)重參數(shù)以及真實類別,通過 J 損失函數(shù)求得神經(jīng)網(wǎng)絡(luò)的損失值,?x 表示對 x 求偏導(dǎo),即損失函數(shù) J 對 x 樣本求偏導(dǎo)。sign是符號函數(shù),即sign(-2),sign(-1.5)等都等于 -1;sign(3),sign(4.7)等都等于 1。sign函數(shù)圖如下。
- ?(epsilon)的值通常是人為設(shè)定 ,可以理解為學(xué)習(xí)率,一旦擾動值超出閾值,該對抗樣本會被人眼識別。

在這里插入圖片描述

在這里插入圖片描述
- 之后,原始圖像x + 擾動值 η = 對抗樣本 x + η 。
- 理解公式后,感覺FGSM并不難。其思想也和dp神經(jīng)網(wǎng)絡(luò)類似,但它更像是一個逆過程。我們機器學(xué)習(xí)算法中無論如何都希望損失函數(shù)能越小越好;那對抗樣本就不一樣了,它本身就是搞破壞的東西,當(dāng)然是希望損失值越大越好,這樣算法就預(yù)測不出來,就會失效。
2、pytorch實現(xiàn)
聲明:代碼來源于pytorch官網(wǎng),跳轉(zhuǎn);你要是想看官網(wǎng)直接跳轉(zhuǎn)即可,但是以下的內(nèi)容我會講解代碼以及自己的理解。
下面代碼需要下載預(yù)訓(xùn)練模型,將其放在如下代碼指定文件夾下,預(yù)訓(xùn)練模型下載
pytorch不會 ?見 pytorch從基礎(chǔ)到實戰(zhàn),做學(xué)術(shù)可離不開它勒。
2.1 建立模型
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
# 這里的epsilon先設(shè)定為幾個值,到時候后面可視化展示它的影響如何
epsilons = [0, .05, .1, .15, .2, .25, .3]
# 這個預(yù)訓(xùn)練的模型需要提前下載,放在如下url的指定位置,下載鏈接如上
pretrained_model = "data/lenet_mnist_model.pth"
use_cuda=True
# 就是一個簡單的模型結(jié)構(gòu)
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)
# 運行需要稍等,這里表示下載并加載數(shù)據(jù)集
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=False, download=True, transform=transforms.Compose([
transforms.ToTensor(),
])),
batch_size=1, shuffle=True)
# 看看我們有沒有配置GPU,沒有就是使用cpu
print("CUDA Available: ",torch.cuda.is_available())
device = torch.device("cuda" if (use_cuda and torch.cuda.is_available()) else "cpu")
# Initialize the network
model = Net().to(device)
# 加載前面的預(yù)訓(xùn)練模型
model.load_state_dict(torch.load(pretrained_model, map_location='cpu'))
# 設(shè)置為驗證模式.
# 詳解見這個博客 https://blog.csdn.net/qq_38410428/article/details/101102075
model.eval()
Out:
- 定義的網(wǎng)絡(luò)模型較為簡單,如下
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QqouDTJj-1620884909006)(C:\Users\劉志遠(yuǎn)\AppData\Local\Temp\1620882912362.png)]
2.2 FGSM模塊
# FGSM attack code
def fgsm_attack(image, epsilon, data_grad):
# 使用sign(符號)函數(shù),將對x求了偏導(dǎo)的梯度進(jìn)行符號化
sign_data_grad = data_grad.sign()
# 通過epsilon生成對抗樣本
perturbed_image = image + epsilon*sign_data_grad
# 做一個剪裁的工作,將torch.clamp內(nèi)部大于1的數(shù)值變?yōu)?,小于0的數(shù)值等于0,防止image越界
perturbed_image = torch.clamp(perturbed_image, 0, 1)
# 返回對抗樣本
return perturbed_image
2.3 測試
- 由于FGSM算法一次迭代即可,所以沒必要單獨加個train模塊,直接在測試模塊實現(xiàn)就行
def test( model, device, test_loader, epsilon ):
# 準(zhǔn)確度計數(shù)器
correct = 0
# 對抗樣本
adv_examples = []
# 循環(huán)所有測試集
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
# 這里都是為后面的可視化做準(zhǔn)備
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:
# 這里都是為后面的可視化做準(zhǔn)備
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
- 這個函數(shù)看起來很多,其中有很多操作都是為后面的可視化做準(zhǔn)備,這里我說下比較重要的代碼
- 重要的肯定就是損失函數(shù)關(guān)于x的偏導(dǎo),如何求出嘍。
- model.zero_grad(): PyTorch文檔中提到,如果grad屬性不為空,新計算出來的梯度值會直接加到舊值上面。 為什么不直接覆蓋舊的結(jié)果呢?這是因為有些Tensor可能有多個輸出,那么就需要調(diào)用多個backward。 疊加的處理方式使得backward不需要考慮之前有沒有被計算過導(dǎo)數(shù),只需要加上去就行了。我們的情況很簡單,就一個輸出,所以需要使用這條語句
- loss.backward():這條語句并不會更新參數(shù),它只會求出各個中間變量的grad(梯度)值 ,當(dāng)然也求出了損失函數(shù)關(guān)于x的偏導(dǎo)啦
- data_grad = data.grad.data:由于前面使用了loss.backward() ,所以data這個tensor的grad屬性,自然就有值了,還是損失函數(shù)對于x的偏導(dǎo)值
2.4 可視化對比
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)
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()
Out:

在這里插入圖片描述
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DvgZtpPf-1620884909008)(C:\Users\劉志遠(yuǎn)\AppData\Local\Temp\1620884610752.png)]
2.5 對比樣本與對抗樣本
# 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()
Out:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RAHotFG7-1620884909009)(C:\Users\劉志遠(yuǎn)\AppData\Local\Temp\1620884743243.png)]
如有疑惑,以下評論區(qū)留言。力所能及,必答之。