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跟小的扰动来达到攻击目的

算法原理

二分类

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

对抗样本常见攻击算法与模拟——DeepFool-ShaoBaoBaoEr's Blog

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

二维的情况以及高维的情况如下所示。
对抗样本常见攻击算法与模拟——DeepFool-ShaoBaoBaoEr's Blog

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

对抗样本常见攻击算法与模拟——DeepFool-ShaoBaoBaoEr's Blog

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

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

对抗样本常见攻击算法与模拟——DeepFool-ShaoBaoBaoEr's Blog

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

多分类

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

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

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

对抗样本常见攻击算法与模拟——DeepFool-ShaoBaoBaoEr's Blog

定向攻击

之前说的都是非定向攻击的算法,如果改成定向攻击,难度系数也不高。所需要修改的就是内循环的部分。定义一个额外的输入$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)

结果展示

多分类非定向攻击

对抗样本常见攻击算法与模拟——DeepFool-ShaoBaoBaoEr's Blog

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

多分类定向攻击

对抗样本常见攻击算法与模拟——DeepFool-ShaoBaoBaoEr's Blog

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

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)
对抗样本常见攻击算法与模拟——DeepFool-ShaoBaoBaoEr's Blog