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

DeepFool 算法

相关论文 Moosavi Dezfooli, Seyed Mohsen and Fawzi, Alhusseinand Frossard, Pascal, DeepFool: a simple and accurate method to fool deepneural networks, CVPR 2016.

DeepFool也是一种基于梯度的白盒攻击算法。通常作为一种无定向的攻击算法使用,对于FGSM而言,DeepFool不用指定学习率$\epsilon$ 算法本身可以算出相对FGSM跟小的扰动来达到攻击目的

算法原理

二分类

假设分割平面是一个直线,直线两侧对应不同的分类结果。

图片标题

对于样本点$x_0$若是想要改变分类结果,那就一定要跨过分隔平面。我们知道,两点之间直线最短,,计该距离为$\Gamma * (x)$ ,分类器为$f(.)$。对于平面内的一点x0,想改变它的分类结果,实际上就是把它给“移动”到平面的另一边,这个$\Gamma * (x)$做到最短,那就是沿着$f(.)$单位法向量方向即可。

二维的情况以及高维的情况如下所示。

图片标题

在原论文中,作者加入了一张图片来解释其模型背后的几何原理。

图片标题

对于上述伪代码,我们假定$x_0 ∈R^n$,绿色的块代表着$l$ (见上面一张图),可以看出l与分类器相切,目前我们的$x_0$在决策面的右侧,之后x0应该希望能够“移动”到橘色的线上($l=0$)

因此,deepfool的二分类伪代码可以这么表示

图片标题

每次迭代的时候,我们让扰动沿着与分类器相切的平面的法向量方向移动,当添加扰动的图片抵达分类器的另一端(也就是变成另一个分类的时候)攻击即可达成

多分类

在处理完二分类问题后,我们来考虑多分类的问题。多分类的问题中,我们需要寻找的应该是一个最小的偏移,这样经过不断迭代就可以完成攻击。

之前我有个疑问,为什么不是最大的偏移呢?后来我想明白了,记录一下:
我们知道,分类器$f(.)$是一个高维度的平面(如上图的峰谷所示),而每次偏移,是根据分类器的切平面算出来的,因此偏移是“线性的”,而分类器是“非线性的”,只有当每次取最小,并经过多次迭代,才能够得到“非线性的偏移”。

搞懂了这个,就可以看明白多分类问题的deepfool算法。

图片标题

定向攻击

之前说的都是非定向攻击的算法,如果改成定向攻击,难度系数也不高。所需要修改的就是内循环的部分。定义一个额外的输入$Target ;;t$
迭代退出条件是达到最大迭代次数或者分类标签等于定向攻击标签$t$,定向攻击成功,另外每次也不用取最小值。具体可以参考下文的代码

代码实现

数据预处理

数据集的话用的是手写数字数据集,为啥不用更加多分类的一些样本呢....因为我的GPU显存不够....

model = tf.keras.models.load_model("minst_cnn2.h5")

def show_img(img,title=""):
    plt.figure()
    plt.imshow(img)
    plt.title('{}'.format(title))
    plt.show()

(x_train, y_train), (x_test, y_test)=tf.keras.datasets.mnist.load_data()

x_train = x_train.reshape(-1,28,28,1).astype(np.float32) /255.0
x_test = x_test.reshape(-1,28,28,1).astype(np.float32) / 255.0

print(x_train.shape)

数据的维度是(Batch_id,28,28,1)这样子。标签为0...1

多分类非定向攻击

我第一次真正写这种对抗样本攻击。数据维度的处理有些生疏。还请见谅。


def NoneTargetDeepFool(input_image,input_label,epochs=2000,overshoot=0.2):
    '''
    
    :param input_image: 28*28*1
    :param input_label: 0..9
    :param epochs: 2000
    :param overshoot: 0.2 作者文中定义的超参数
    :return: img 28*28*1
    '''
    global predictions_id
    global gradient
    input_image = np.expand_dims(input_image, axis=0)
    input_image = np.vstack([input_image])
    r_tot = np.zeros(input_image.shape)
    pert = 1e10
    for epoch in range(epochs):        
        input_image = tf.constant(input_image,dtype=tf.float32)
        for target_label in range(0,10):
            """
            寻找最小的pert(也就是公式中的\\bar{l})
            """
            # 我的是手写数据集,所以分类结果是0-9
            with tf.GradientTape() as tape:
                tape.watch(input_image)
                predictions = model(input_image)
                loss = tf.keras.losses.SparseCategoricalCrossentropy()(input_label, predictions)
            # 利用tf2.0中的梯度带进行正常运算
            predict = np.squeeze(predictions) # np.squeeze 降维打击!
            _predictions_id = predict.argsort()[-3:][::-1][0] # 拿出可能性最高的,以便之后记录
            _gradient = tape.gradient(loss, input_image) # 计算梯度
            
            f = predict[input_label] #获取倒数第二层的输出
            _pert = abs(f) / np.linalg.norm(_gradient.numpy().reshape(-1)) # 相当于flatten
            if(_pert<pert): # 取最小的
                pert = _pert
                predictions_id = _predictions_id
                gradient = _gradient
                
        if predictions_id != input_label:
            """
            若预测和输入的标签不一致,则结束
            """
            predictions = model(input_image)
            predict = np.squeeze(predictions)
            top_k = predict.argsort()[-3:][::-1]
            for node_id in top_k:
                score = predict[node_id]
                print('(score = %.5f)(id = %d)' % (score, node_id))
            break

        # 否则, 计算 r_i 和 r_tot
        r_i = (pert + 1e-8) * gradient / np.linalg.norm(gradient)
    
        r_tot = np.float32(r_tot + r_i)
    
        input_image = input_image - (1 + overshoot) * r_tot
    
        input_image = np.clip(input_image, 0, 1)
    
    return (input_image,predictions_id)

attack_index=3
show_img(img=np.squeeze(x_train[attack_index]),title=y_train[attack_index])

(x_attack,y_attack)  = NoneTargetDeepFool(input_image=x_train[attack_index],input_label=y_train[attack_index])

show_img(img = np.squeeze(x_attack[0]),title=y_attack)

多分类定向攻击


def TargetDeepFool(input_image,target_label,epochs=200,overshoot=0.02):
    """
    
    :param input_image: 28*28*1 
    :param target_label: 0...9
    :param epochs: 最大迭代次数
    :param overshoot: 原文定义 0.02
    :return: 
    """
    global predictions_id
    input_image = np.expand_dims(input_image, axis=0)
    input_image = np.vstack([input_image])
    r_tot = np.zeros(input_image.shape)
    for epoch in range(epochs):
        input_image = tf.constant(input_image,dtype=tf.float32)
    
        with tf.GradientTape() as tape:
            # """
            # 不太一样的是这里,此时损失函数应该是target_label而不是原本图片的label
            # """
            tape.watch(input_image)
            predictions = model(input_image)
            loss = tf.keras.losses.SparseCategoricalCrossentropy()(target_label, predictions)
        
        # """
        # 我们也不用特地去找最小的了
        # """
        predict = np.squeeze(predictions) # np.squeeze 降维打击!
        top_k = predict.argsort()[-3:][::-1]
        predictions_id = top_k[0]
        if epoch%10 == 0 : print("epoch={} loss={} label={}".format(epoch, loss, predictions_id))
        gradient = tape.gradient(loss, input_image)
        
        if predictions_id == target_label:
            print("epoch={} loss={} label={}".format(epoch, loss, predictions_id))
            for node_id in top_k:
                score = predict[node_id]
                print('(score = %.5f)(id = %d)' % (score, node_id))
            break
        f = predict[target_label]
        pert = abs(f) / np.linalg.norm(gradient.numpy().reshape(-1))
        
        # 计算 r_i 和 r_tot
        r_i = (pert + 1e-8) * gradient / np.linalg.norm(gradient)
    
        r_tot = np.float32(r_tot + r_i)
        input_image = input_image - (1 + overshoot) * r_tot
    
        input_image = np.clip(input_image, 0, 1)
    
    return (input_image,predictions_id)

attack_index=5
target = 9
show_img(img=np.squeeze(x_train[attack_index]),title=y_train[attack_index])

(x_attack,y_attack) = TargetDeepFool(input_image=x_train[attack_index],target_label=target)

show_img(img = np.squeeze(x_attack[0]),title=y_attack)

结果展示

多分类非定向攻击

图片标题

说实话,我这个多分类非定向攻击的结果并不是很理想。我试了试其他的样本,结果都是...和原图片差了很多很多。道理不是很明白。可能是代码哪里有问题,也可能是图片太小了,可以操作的空间不够丰富吧。

多分类定向攻击

图片标题

定向攻击的结果就好很多。

ART的结果

后来我尝试采用相同的模型,并利用ART自带的DeepFool算法。不过ART仅仅支持非定向攻击,发现ART的结果与我的结果基本类似。

img = np.stack([x_train[3]])
label = np.stack([[0, 0, 0, 0, 0, 0, 0, 0, 1, 0]])
# Step 6: Generate adversarial test examples
attack = DeepFool(classifier=classifier, max_iter=20)
img_adv = attack.generate(x=img, y=label)

predict1 = classifier.predict(img)
predict2 = classifier.predict(img_adv)
title1 = np.argmax(predict1, axis=1)
title2 = np.argmax(predict2, axis=1)
show_d(img[0], img_adv[0], title1, title2)
图片标题
支付宝捐赠
请使用支付宝扫一扫进行捐赠
微信捐赠
请使用微信扫一扫进行赞赏
有 0 篇文章