FGSM攻擊機器學習模型

FGSM技術

對抗攻擊技術,因爲網絡的深層,很少的改變就有可能改變網絡中激活函數的方向,進而直接大量改變輸出。因此,從模型中得到特殊的輸入X就能讓模型產生嚴重的誤判,這種就是神經網絡攻擊技術。
我們希望得到和原輸入類似的輸入,但是與此同時儘可能讓輸出發生儘可能大的改變。這個優化問題寫成把訓練時的loss function加負號,再加正則項的無約束優化。迭代就可以得到X
寫成算法就是Fast Gradient Sign Method(FGSM),這裏使用無窮範數約束正則化目標函數
在這裏插入圖片描述
取符號函數是一個快速的技巧,因爲我們取inf-norm,在更新時的操作是把超過閾值的X clip到邊緣,這樣其實不需要一般梯度法的小步長,可以允許只給定方向進行更新,一次就更新到閾值。

實現

我這裏給出一個算法攻擊CNN的例子,當然FGSM也可以用在其他模型上

import os
import sys
import argparse
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.optim import Adam
from torch.utils.data import Dataset
import torch.utils.data as Data
import torchvision.transforms as transforms
import torchvision
from skimage.segmentation import slic
from pdb import set_trace

EPOCH=10
BATCH_SIZE=50
LR=0.001

train_data=torchvision.datasets.CIFAR10(
    root='C:/Users/Administrator/DL/cifar10',
    train=True,
    transform=torchvision.transforms.ToTensor()
)
train_loader = Data.DataLoader(
    dataset=train_data,
    batch_size=BATCH_SIZE,
    shuffle=True
)
test_data=torchvision.datasets.CIFAR10(
    root='C:/Users/Administrator/DL/cifar10',
    train=False,
    transform=torchvision.transforms.ToTensor()
)
test_loader = torch.utils.data.DataLoader(test_data, batch_size=BATCH_SIZE,
                                         shuffle=False)

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, 3, padding = 1)
        self.conv2 = nn.Conv2d(64, 64, 3, padding = 1)
        self.conv3 = nn.Conv2d(64, 128, 3, padding = 1)
        self.conv4 = nn.Conv2d(128, 128, 3, padding = 1)
        self.conv5 = nn.Conv2d(128, 256, 3, padding = 1)
        self.conv6 = nn.Conv2d(256, 256, 3, padding = 1)
        self.maxpool = nn.MaxPool2d(2, 2)
        self.avgpool = nn.AvgPool2d(2, 2)
        self.globalavgpool = nn.AvgPool2d(8, 8)
        self.bn1 = nn.BatchNorm2d(64)
        self.bn2 = nn.BatchNorm2d(128)
        self.bn3 = nn.BatchNorm2d(256)
        self.dropout50 = nn.Dropout(0.1)
        self.dropout10 = nn.Dropout(0.1)
        self.fc = nn.Linear(256, 10)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = F.relu(self.conv2(x))
        x = self.maxpool(x)
        x = self.dropout10(x)
        x = F.relu(self.conv3(x))
        x = F.relu(self.conv4(x))
        x = self.avgpool(x)
        x = self.dropout10(x)
        x = F.relu(self.conv5(x))
        x = F.relu(self.conv6(x))
        x = self.globalavgpool(x)
        x = self.dropout50(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x
    
import torch.optim as optim

net = Net()

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=LR)

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
net.to(device)

net = torch.load('cifar10.pkl')

這裏直接用了一個訓練好的模型,有需要的可以自己訓練一個可以用的CNN model。
然後設計FGSM的算法,其實並不特別,只需要在Pytorch自動求導時把輸入圖像的梯度一起算出來,然後取符號,用梯度上升法更新輸入的圖片。

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')


def FGSM(image, target, epsilon, model, iterations = 10):
    '''
    對給定的model和輸入image,找到一張合適的攻擊圖片
    希望在儘可能小的delta下,造成儘可能大的target誤判
    '''
    model.eval()
    image = image.clone()     #取副本,不改動數據集
    image, target = image.to(device), target.to(device)
    
    
    output = model(image)       #計算
    pred = output.max(1, keepdim=True)[1]
    print("Origin class:",classes[target.item()])
    print("Prediction before attack:",classes[pred.item()])
    
    for t in range(iterations):
        image.requires_grad = True
        output = model(image)       #計算
        pred = output.max(1, keepdim=True)[1]
        # 計算是否分類錯誤,如果錯誤則攻擊成功,停止迭代
        if pred.item() != target.item():
            break
        loss = F.cross_entropy(output, target)  #交叉熵損失
        model.zero_grad()
        loss.backward()
        data_grad = image.grad.data     #計算輸入的導數
        sign_data_grad = data_grad.sign()  #符號函數
        image = image.detach()
        image += epsilon * sign_data_grad
        image = torch.clamp(image, 0, 1)

    print("Total attack times: %d"%t)
    print("Prediction after attack:",classes[pred.item()])
    return image

實測一下,注意不要用那些因爲模型太爛沒法正確分類的樣本測試,那就沒有意義了。

img_indices = [0,4,8,10,20,45]
images = torch.tensor(train_data.train_data[img_indices]).float().permute(0, 3, 1, 2)/255.
labels = torch.tensor(train_data.train_labels)[img_indices]

for i in range(len(images)):
    print("Case: %d"%(i+1))
    image, label = images[i:i+1], labels[i:i+1]
    plt.figure()
    plt.subplot(121)
    plt.imshow(image.view(3,32,32).permute(1,2,0))
    adv_image = FGSM(image, label, 0.01, net)
    plt.subplot(122)
    show_adv_image = adv_image.detach().cpu().view(3,32,32).permute(1,2,0).numpy()
    plt.imshow(show_adv_image)

輸出如下

Case: 1
Origin class: frog
Prediction before attack: frog
Total attack times: 1
Prediction after attack: cat
Case: 2
Origin class: car
Prediction before attack: car
Total attack times: 1
Prediction after attack: plane
Case: 3
Origin class: ship
Prediction before attack: ship
Total attack times: 3
Prediction after attack: plane
Case: 4
Origin class: deer
Prediction before attack: deer
Total attack times: 1
Prediction after attack: cat
Case: 5
Origin class: deer
Prediction before attack: deer
Total attack times: 2
Prediction after attack: cat
Case: 6
Origin class: car
Prediction before attack: car
Total attack times: 1
Prediction after attack: plane

其中兩份圖片如下所示
在這裏插入圖片描述
人眼無法看出兩者的差別,似乎只是把原圖片加上了一個小噪聲,但是這將會讓模型的輸出分類完全改變。仔細看可以看到兩張圖片的背景好像加上了一些條紋,這就讓模型直接把圖片認成“飛機”。

其他知識

這種攻擊方法只有我們獲得了模型的架構和全部參數後才能湊效,那麼當我們只想攻擊一個已經部署了的系統,比如一個exe程序,一個api,甚至一塊FPGA板子又該怎麼做呢?
這種攻擊稱爲“黑箱攻擊“。事實上,我們可以自己搭建一個另外的系統,並把它train到和目標的系統表現相似(用目標系統的輸出去訓練自己的模型),然後用上面的方法攻擊自己的模型。這時取得的這份攻擊用輸入數據一樣可以用於攻擊黑箱,而且一般都會湊效。
那麼又有了一個問題,既然我們的系統只要被公衆使用就有被攻擊的危險,那麼要怎麼預防攻擊呢?
最簡單的方法就是使用特殊的“防火牆”,我們把用戶輸入的圖片做某些處理(比如高斯模糊),就很大概率能讓攻擊用的信號被掩蓋下去,這樣模型就不會產生誤判。
當前,也可以用集成的方法,讓多個學習器同時參與分類,也能讓結果更魯棒,不容易遭到惡意攻擊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章