對抗樣本之FGSM原理&實戰(zhàn)

目錄
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樣本生成

公式

  1. 如下圖,其中 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ù)圖如下。
  2. ?(epsilon)的值通常是人為設(shè)定 ,可以理解為學(xué)習(xí)率,一旦擾動值超出閾值,該對抗樣本會被人眼識別。
在這里插入圖片描述
在這里插入圖片描述
  1. 之后,原始圖像x + 擾動值 η = 對抗樣本 x + η 。
  2. 理解公式后,感覺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)容我會講解代碼以及自己的理解。

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ū)留言。力所能及,必答之。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容