对抗样本常见攻击算法与模拟——JSMA
九涅·烧包包儿 / / 程序员 / 阅读量

类特征图(Class Saliency Map , csm)概念

> 参考论文 Deep Inside Convolutional Networks: Visualising Image Classification Models and Saliency Maps

在学习JSMA算法之前,先要看一下什么是显著图。显著图的概念是在上文中提及的,该论文的中文直译是:“深入CNN——可视化图片的分类模型以及显著图”。作者在文中提出了两种可视化的技术。一种是生成一张图片,使其类得分最大,第二种是计算某张图片对应的显著图。

类特征图的生成

着重介绍一下第二种方法:

给定一个简单的模型。考虑该模型的分类器是线性的:

$$S_c(I)=\omega _c^T I + b_c$$

  • $I$ :输入的图像(一维)
  • $\omega _c , b_c$ 权重和偏移
  • 可以发现,$\omega$ 很大程度上决定了图片的分类。

在卷积神经网络中,$S_c(I)$ 是对于图片I而言一个高维的非线性模型,采用一些线性的方法并不是很管用。然而,对于一张给定的图片$I_0$ ,我们可以将这个高位的线性模型在$I_0$处进行一阶泰勒展开,如下所示

$$S_c(I)≈\omega ^T I + b_c$$

而显著图致力于研究的,是每个像素的改变对于输出类分数的影响,也就是计算分数对每个像素$I_0$的导数。
$$\omega = \left.\cfrac{\partial{S_c}}{\partial{I}}\right|_{I_0}$$

该过程很显然可以用反向传播来实现。从另一种方向去理解这个基于梯度的算法是,求出每个像素点的梯度(梯度即表示变化最快的方向),即表示像素对所得分数的影响大小,梯度越大影响越大。

  • 如果图片是灰度图,那么csm就取图像梯度的绝对值;
  • 如果是RGB图,csm就取图像梯度3个通道中绝对值最大的那个通道。

csm中元素值的大小表示对应位置的图片像素对最终分类结果的影响程度。

类特征图的实现

之后的话,来搞一下这个类特征图。可想而知在2013年的时候TF应该还没有出,那可能还是一个塔纳托斯的时代。我总觉得有些奇怪,这个类特征图不就是梯度矩阵么?只不过取个绝对值而已吧?

时代到了2020年,tf2.0实现这个确实是非常简单的。(也许是我理解不到位?)

def compute_saliency_maps(X,Y):
    '''
    
    :param X: tensor of model , must be (-1,28,28,1)
    :param Y: label
    :return: 
    '''
    saliency = None
    ## Compute the score by a single forward pass
    with tf.GradientTape() as tape:
        tape.watch(X)
        scores = model(X)
        loss = tf.keras.losses.SparseCategoricalCrossentropy()(Y, scores)

    gradient = tape.gradient(loss, X)
    saliency = abs(gradient)
    return np.squeeze(saliency.numpy()) ## 由于我是黑白图像,如果是RGB图像,应该取对应通道中值最大的那一个
    
img = preprocess_input(x_train[IMG_INDEX])
saliency = compute_saliency_maps(img,y_train[IMG_INDEX])

结果展示

图片标题

JSMA 算法

参考论文 Papernot N , Mcdaniel P , Jha S , et al. The Limitations of Deep Learning in Adversarial Settings[J]. 2015.

JSMA,全程Jacobian-based Saliency Map Attack。中文直译的话是基于雅克比行列式的显著图攻击。

JSMA是典型的l0范数的白盒定向攻击算法,追求的是尽量减少修改的像素个数。

JSMA引入了显著图的概念,用以表征输入特征对预测结果的影响程度。

在一个典型的深度学习网络中(如下所示),输入为X,维度为M,输出为Y,维度为N,隐藏层的个数为n,前向计算的结果为F(X)。F(X)为输出层没有经过Softmax处理的结果,即所谓的Logits输出。Logits的输出经过Softmax处理后会隐藏其丰富的信息,对生成对抗样本的效果有一定影响。

图片标题

算法原理

JSMA算法主要包括三个过程:计算前向导数,计算对抗性显著图,添加扰动,以下给出具体解释。

计算前导函数

$\nabla F(x) = \cfrac{\partial F(X)}{\partial X} = \left[ \cfrac{\partial F_j(X)}{\partial x_i}\right]_{i \in 1..M,j \in 1..N}$

F是分类器最后一层隐藏层的输出。该导数的计算与反向传播非常类似,但是有两点不同

  • 前向导数是通过对神经网络最后一层输出求导得到的,而不是利用损失函数算出来的。
  • JSMA关心的是输入,而不是网络的参数,因此不考虑反向传播时候的梯度。
作者后面附上了一段这个算式是如何计算的推导。不过TF应该是自带求导数的方法的。无非会复杂一些。之后有空来看一下推导吧

构建对抗显著图(Adersarial Saliency Maps)

之前谈及到显著图的概念,不过之前的是类显著图,这里是对抗显著图。

我们知道对于一个分类器,其分类规则可以简单概括为

$$predict(X) = arg; \max\limits_j F_j(X)$$

对于定向攻击,如果希望分类器将$X$分类为$t$ 则有
$$t = arg; \max\limits_j F_j(X)$$

因此,根据扰动方式的不同(正向扰动和反向扰动),作者提出了两种计算对抗性显著图的方式.

正向扰动的显著图

$$S(\mathbf{X}, t)[i]=\left{\begin{array}{l}
0 \text { if } \frac{\partial \mathbf{F}{t}(\mathbf{X})}{\partial \mathbf{X}{i}}<0 \text { or } \sum_{j \neq t} \frac{\partial \mathbf{F}{j}(\mathbf{X})}{\partial \mathbf{X}{i}}>0 \
\left(\frac{\partial \mathbf{F}{t}(\mathbf{X})}{\partial \mathbf{X}{i}}\right)\left|\sum_{j \neq t} \frac{\partial \mathbf{F}{j}(\mathbf{X})}{\partial \mathbf{X}{i}}\right| \text { otherwise }
\end{array}\right.$$

  • 忽略$X_i$对模型的贡献为负数的情况。也就意味着
  • $\frac{\partial \mathbf{F}{t}(\mathbf{X})}{\partial \mathbf{X}{i}}$的值应该为正,这样就能够向着梯度上升的方向迭代
  • $\sum_{j \neq t} \frac{\partial \mathbf{F}{j}(\mathbf{X})}{\partial \mathbf{X}{i}}$的值应该为负,这样就能够在让特征$X_i$上升的同时,对其他类梯度计算的结果保持基本不变或下降。

负向扰动的显著图

$$S(\mathbf{X}, t)[i]=\left{\begin{array}{l}
0 \text { if } \frac{\partial \mathbf{F}{t}(\mathbf{X})}{\partial \mathbf{X}}>0 \text { or } \sum{j \neq t} \frac{\partial \mathbf{F}{j}(\mathbf{X})}{\partial \mathbf{X}{i}}<0 \
\left|\frac{\partial \mathbf{F}{t}(\mathbf{X})}{\partial \mathbf{X}{i}}\right|\left(\sum_{j \neq t} \frac{\partial \mathbf{F}{j}(\mathbf{X})}{\partial \mathbf{X}{i}}\right) \text { otherwise }
\end{array}\right.$$

  • 忽略$X_i$对模型的贡献为负数的情况。与刚才的结果相反。

使用显著图挑选需要改变的像素位置

精心构建的显著图告诉我们,只要增加对应像素的值,就能够对应增加目标t的输出,对此进行不断迭代。直到攻击成功或者达到最大阈值。

在之后,作者发现,其实找到单个满足要求的特征非常困难。因此作者考虑到了另一种解决方案。通过对抗显著图找到对分类器影响程度最大的两个特征。也就是

$$\arg \max {\left(p{1}, p_{2}\right)}\left(\sum_{i=p_{1}, p_{2}} \frac{\partial \mathbf{F}{t}(\mathbf{X})}{\partial \mathbf{X}{i}}\right) \times\left|\sum_{i=p_{1}, p_{2}} \sum_{j \neq t} \frac{\partial \mathbf{F}{j}(\mathbf{X})}{\partial \mathbf{X}{i}}\right|$$

由于扰动方式的不同,这种算法也有两种方法。这是对于正向扰动而言的,负向扰动的话其实就是更改绝对值的位置。

之后将$p_i,p_j$的修改添加到图中,重复迭代即可完成任务。作者在文中提及需要注意两点

  • 迭代的重复次数是有限的。
  • 添加扰动后,对应的$p_i,p_j$不应在此参与扰动的过程。

代码实现

计算前导函数

with tf.GradientTape() as tape:
    tape.watch(input_image)
    s = model(input_image)
    derivative = tape.gradient(s[0,target_label],input_image)         

其实前导函数的计算应该是相当相当麻烦的。但是tf已经具有了非常强大的导数功能,这样前导函数矩阵就算出来了。

对抗特征图

参考了兜哥书上的代码。

##实现saliency_map
def saliency_map(derivative, mask):
    ## derivative 前导矩阵,mask初始为全1,且与img大小一样
    alphas = derivative * mask
    
    ## pixel influence on sum of residual classes
    betas = -np.ones_like(alphas)
    
    ## 下面一步过滤了全0
    sal_map = np.abs(alphas) * np.abs(betas) * np.sign(alphas * betas)
    
    ## 找到最小元素的位置
    ## 算出α参数,这里简化了β参数的计算,相当于只关注了α参数。
    idx = np.argmin(sal_map)
    
    ##转换成(p1,p2)格式      
    idx = np.unravel_index(idx, mask.shape)
    pix_sign = np.sign(alphas)[idx]

    return idx, pix_sign

完整代码


def JSMA(input_image,target_label,epochs=100,theta=.1,max_=1,min_=0):
    '''
    
    :param input_image: 输入的图片 28*28*1
    :param target_label: 目标标签 0...9
    :param epochs: 最大迭代次数
    :param theta: 扰动系数,论文中是.1
    :param max_: 最大像素边界
    :param min_: 最小像素边界
    :return: adv_image 28*28*1
    '''
    input_image = preprocess_input(input_image)
    mask = np.ones_like(input_image)

    for epoch in range(epochs):
        input_image = tf.constant(input_image,dtype=tf.float32)
        with tf.GradientTape() as tape:
            tape.watch(input_image)
            s = model(input_image)
            derivative = tape.gradient(s[0,target_label],input_image)         
        predictions = np.squeeze(s)
        input_image = input_image.numpy()
        top_k = predictions.argsort()[-3:][::-1]
        predictions_id=top_k[0]
        ## print("epoch={} label={}".format(epoch,predictions_id))
        
        if predictions_id == target_label:
            ## for node_id in top_k:
            ##     print('(score = %.5f)(id = %d)' % (predictions[node_id],predictions_id))
            break
        idx,pix_sign = saliency_map(derivative,mask)
        ## apply perturbation 
        input_image[idx]+=pix_sign * theta * (max_ - min_) 
        ##达到极限的点不再参与更新
        if (input_image[idx]<=min_) or (input_image[idx]>=max_):
            ## print("idx={} over {}".format(idx,input_image[idx]))
            mask[idx]=0
            input_image[idx]=np.clip(input_image[idx], min_, max_)
        
    return input_image

实验结果

Image Size 784 Shape (28, 28, 1)
Noise L_0 norm: 3 1%
Noise L_2 norm: 1.0802212953567505 12%
Noise L_inf norm: 1.0 1%
图片标题
Image Size 784 Shape (28, 28, 1)
Noise L_0 norm: 33 5%
Noise L_2 norm: 2.406897783279419 24%
Noise L_inf norm: 1.0 1%
图片标题
Image Size 784 Shape (28, 28, 1)
Noise L_0 norm: 20 3%
Noise L_2 norm: 2.312763214111328 30%
Noise L_inf norm: 1.0 1%
图片标题

最后学着论文的样子做了一下汇总的图片,和原始论文的结果基本相似吧。

图片标题

JSMA变体——JSMA-Z

上述JSMA,后来被称为JSMA-F。而后来又有JSMA-Z算法提出,其实指利用softmax层的输入来计算。(或者说softmax上一层的输出)

对于算法来说,只要修改一个地方就能实现JSMA-Z。

    mask = np.ones_like(input_image)
    layer_model = tf.keras.Model(inputs=model.input, outputs=model.layers[9].output) ## 取第10层的输出
    for epoch in range(epochs):
        input_image = tf.constant(input_image,dtype=tf.float32)
        with tf.GradientTape() as tape:
            tape.watch(input_image)
            s = layer_model(input_image)
            derivative = tape.gradient(s[0,target_label],input_image)         

这也是keras中获取某层输出的方法。若改为model.layers[10](softmax层),该算法就变回了JSMA-F。

以下是结果的对比:

>JSMA_Z
(28, 28, 1) (28, 28, 1)
Image Size 784 Shape (28, 28, 1)
Noise L_0 norm: 43 6%
Noise L_2 norm: 1.9052457809448242 25%
Noise L_inf norm: 1.0 1%
图片标题
>JSMA_F
(28, 28, 1) (28, 28, 1)
Image Size 784 Shape (28, 28, 1)
Noise L_0 norm: 25 4%
Noise L_2 norm: 2.5376861095428467 33%
Noise L_inf norm: 1.0 1%
图片标题

顺带一提

在论文中,看到了一张很不错的图。也就是威胁模型的图,非常形象直观。

图片标题
支付宝捐赠
请使用支付宝扫一扫进行捐赠
微信捐赠
请使用微信扫一扫进行赞赏
有 0 篇文章