天天看點

FGSM攻擊機器學習模型

FGSM技術

對抗攻擊技術,因為網絡的深層,很少的改變就有可能改變網絡中激活函數的方向,進而直接大量改變輸出。是以,從模型中得到特殊的輸入X就能讓模型産生嚴重的誤判,這種就是神經網絡攻擊技術。

我們希望得到和原輸入類似的輸入,但是與此同時盡可能讓輸出發生盡可能大的改變。這個優化問題寫成把訓練時的loss function加負号,再加正則項的無限制優化。疊代就可以得到X

寫成算法就是Fast Gradient Sign Method(FGSM),這裡使用無窮範數限制正則化目标函數

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
           

其中兩份圖檔如下所示

FGSM攻擊機器學習模型

人眼無法看出兩者的差别,似乎隻是把原圖檔加上了一個小噪聲,但是這将會讓模型的輸出分類完全改變。仔細看可以看到兩張圖檔的背景好像加上了一些條紋,這就讓模型直接把圖檔認成“飛機”。

其他知識

這種攻擊方法隻有我們獲得了模型的架構和全部參數後才能湊效,那麼當我們隻想攻擊一個已經部署了的系統,比如一個exe程式,一個api,甚至一塊FPGA闆子又該怎麼做呢?

這種攻擊稱為“黑箱攻擊“。事實上,我們可以自己搭建一個另外的系統,并把它train到和目标的系統表現相似(用目标系統的輸出去訓練自己的模型),然後用上面的方法攻擊自己的模型。這時取得的這份攻擊用輸入資料一樣可以用于攻擊黑箱,而且一般都會湊效。

那麼又有了一個問題,既然我們的系統隻要被公衆使用就有被攻擊的危險,那麼要怎麼預防攻擊呢?

最簡單的方法就是使用特殊的“防火牆”,我們把使用者輸入的圖檔做某些處理(比如高斯模糊),就很大機率能讓攻擊用的信号被掩蓋下去,這樣模型就不會産生誤判。

目前,也可以用內建的方法,讓多個學習器同時參與分類,也能讓結果更魯棒,不容易遭到惡意攻擊。

繼續閱讀