2.基於梯度的攻擊——FGSM


  FGSM原論文地址:https://arxiv.org/abs/1412.6572

  1.FGSM的原理

    FGSM的全稱是Fast Gradient Sign Method(快速梯度下降法),在白盒環境下,通過求出模型對輸入的導數,然后用符號函數得到其具體的梯度方向,接着乘以一個步長,得到的“擾動”加在原來的輸入  上就得到了在FGSM攻擊下的樣本。

    FGSM的攻擊表達如下:

    那么為什么這樣做有攻擊效果呢?就結果而言,攻擊成功就是模型分類錯誤,就模型而言,就是加了擾動的樣本使得模型的loss增大。而所有基於梯度的攻擊方法都是基於讓loss增大這一點來做的。可以仔細回憶一下,在神經網絡的反向傳播當中,我們在訓練過程時就是沿着梯度方向來更新更新w,b的值。這樣做可以使得網絡往loss減小的方向收斂。

那么現在我們既然是要使得loss增大,而模型的網絡系數又固定不變,唯一可以改變的就是輸入,因此我們就利用loss對輸入求導從而“更新”這個輸入。(當然,肯定有人問,神經網絡在訓練的時候是多次更新參數,這個為什么僅僅更新一次呢?主要因為我們希望產生對抗樣本的速度更快,畢竟名字里就有“fast”,當然了,多次迭代的攻擊也有,后來的PGD(又叫I-FGSM)以及MIM都是更新很多次,雖然攻擊的效果很好,但是速度就慢很多了)

    為什么不直接使用導數,而要用符號函數求得其方向?這個問題我也一直半知半解,我覺得應該是如下兩個原因:1.FGSM是典型的無窮范數攻擊,那么我們在限制擾動程度的時候,只需要使得最大的擾動的絕對值不超過某個閥值即可。而我們對輸入的梯度,對於大於閥值的部分我們直接clip到閥值,對於小於閥值的部分,既然對於每個像素擾動方向只有+-兩個方向,而現在方向已經定了,那么為什么不讓其擾動的程度盡量大呢?因此對於小於閥值的部分我們就直接給其提升到閥值,這樣一來,相當於我們給梯度加了一個符號函數了。2.由於FGSM這個求導更新只進行一次,如果直接按值更新的話,可能生成的擾動改變就很小,無法達到攻擊的目的,因此我們只需要知道這個擾動大概的方向,至於擾動多少我們就可以自己來設定了~~(歡迎討論)

  2.FGSM的進一步解釋

    FGSM的原作者在論文中提到,神經網絡之所以會受到FGSM的攻擊是因為:1.擾動造成的影響在神經網絡當中會像滾雪球一樣越來越大,對於線性模型越是如此。而目前神經網絡中傾向於使用Relu這種類線性的激活函數,使得網絡整體趨近於線性。2.輸入的維度越大,模型越容易受到攻擊。(下面兩張圖是原來在實驗室講PPT時做的圖就直接拿過來用了,有需要的自取,鏈接:https://pan.baidu.com/s/1sYH0cZTLZ_JBHt_0S26NPA 提取碼:xhjz)

 

 上圖的解釋其實也是原論文中的解釋,雖然這里是直接使用梯度,沒有用符號函數處理過,但道理是一樣的。

 

可以看到,對於一個簡單的線性分類器,loss對於x的導數取符號函數后即w,即使每個特征僅僅改變0.5,分類器對x的分類結果由以0.9526的置信概率判斷為0變成以0.88的置信概率判斷為1.   

  3.FGSM的代碼實現(pytorch)

 

 1 class FGSM(nn.Module):
 2     def __init__(self,model):
 3         super().__init__()
 4         self.model=model#必須是pytorch的model
 5         self.device=torch.device("cuda" if (torch.cuda.is_available()) else "cpu")
 6     def generate(self,x,**params):
 7         self.parse_params(**params)
 8         labels=self.y
 9         if self.rand_init:
10             x_new = x + torch.Tensor(np.random.uniform(-self.eps, self.eps, x.shape)).type_as(x).cuda()
11 
12         # get the gradient of x
13         x_new=Variable(x_new,requires_grad=True)
14         loss_func = nn.CrossEntropyLoss()
15         preds = self.model(x_new)
16         if self.flag_target:
17             loss = -loss_func(preds, labels)
18         else:
19             loss = loss_func(preds, labels)
20         self.model.zero_grad()
21         loss.backward()
22         grad = x_new.grad.cpu().detach().numpy()
23         # get the pertubation of an iter_eps
24         if self.ord==np.inf:
25             grad =np.sign(grad)
26         else:
27             tmp = grad.reshape(grad.shape[0], -1)
28             norm = 1e-12 + np.linalg.norm(tmp, ord=self.ord, axis=1, keepdims=False).reshape(-1, 1, 1, 1)
29             # 選擇更小的擾動
30             grad=grad/norm
31         pertubation = grad*self.eps
32 
33         adv_x = x.cpu().detach().numpy() + pertubation
34         adv_x=np.clip(adv_x,self.clip_min,self.clip_max)
35 
36         return adv_x
37 
38     def parse_params(self,eps=0.3,ord=np.inf,clip_min=0.0,clip_max=1.0,
39                      y=None,rand_init=False,flag_target=False):
40         self.eps=eps
41         self.ord=ord
42         self.clip_min=clip_min
43         self.clip_max=clip_max
44         self.y=y
45         self.rand_init=rand_init
46         self.model.to(self.device)
47         self.flag_target=flag_target
View Code

 

其實FGSM的實現還不是很難~~各個工具包都有實現,可以參考自己實現一下,值得說明的是rand_initflag_target兩個參數:

  •   rand_init為True時,模型給x求導之前會為其添加一個隨機噪聲(噪聲類型可以自己指定),據說這樣效果過會好一點。
  •     flag_target為False時,即為無目標攻擊,此時的loss是loss_func(preds, labels),這里的lables是正確的lables,當flag_target為False時,即為有目標攻擊,此時的loss是-loss_func(preds, labels),這里的lables是指定的label,故loss前面加負號,這個時候更新x,相當於正常的梯度下降了,因為,這個時候loss_func(preds, labels)是往我們希望的方向優化的。

最后補充一句就是,由於產生的對抗樣本可能會在(0,1)這個范圍之外,因此需要對x clip至(0,1)。

 
        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM