使用IBM ART库生成对抗样本——生成将数字3预测为8的对抗样本
【FGSM生成对抗样本的原理和步骤】
快速梯度符号方法(Fast Gradient Sign Method,FGSM)是一种常用的生成对抗样本的方法。它的基本原理是利用模型的梯度信息来找到一个可以最大化模型误差的方向,然后沿着这个方向对输入数据进行微小的扰动,从而生成对抗样本。
以下是FGSM生成对抗样本的基本步骤:
1. 计算损失函数的梯度:首先,我们需要选择一个损失函数,然后计算这个损失函数关于输入数据的梯度。这个梯度表示了在输入空间中,哪个方向的变化可以最大化损失函数的值。
2. 计算扰动:然后,我们使用梯度的符号(即梯度的正负)来计算扰动。这个扰动表示了在输入空间中,哪个方向的变化可以最大化模型的误差。
3. 生成对抗样本:最后,我们将这个扰动添加到原始的输入数据上,从而生成对抗样本。这个对抗样本在人眼看来和原始的输入数据几乎没有区别,但是却能够导致模型产生错误的预测。
需要注意的是,FGSM是一种单步的对抗攻击方法,它只进行一次梯度更新。这使得FGSM非常快速和高效,但是生成的对抗样本的质量可能不如多步的对抗攻击方法。
代码如下:
import numpy as np import tensorflow as tf from tensorflow.keras.datasets import mnist from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Flatten, Dense from tensorflow.keras.utils import to_categorical from art.estimators.classification import TensorFlowV2Classifier from art.attacks.evasion import FastGradientMethod import matplotlib.pyplot as plt import os # 加载MNIST数据集 (train_images, train_labels), (test_images, test_labels) = mnist.load_data() train_images = train_images.astype('float32') / 255 test_images = test_images.astype('float32') / 255 train_labels = to_categorical(train_labels) test_labels = to_categorical(test_labels) # 创建一个简单的全连接神经网络模型 model = Sequential([ Flatten(input_shape=(28, 28)), Dense(128, activation='relu'), Dense(10, activation='softmax'), ]) # 编译和训练模型 model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) model.fit(train_images, train_labels, epochs=5) # 创建ART分类器 classifier = TensorFlowV2Classifier( model=model, nb_classes=10, input_shape=(28, 28), loss_object=tf.keras.losses.CategoricalCrossentropy(from_logits=True), clip_values=(0, 1) ) # 选择一个目标标签(我们希望模型将数字3识别为数字8) target_label = to_categorical(8, num_classes=10) target_label = np.tile(target_label, (len(test_images), 1)) # 选择数字3的图像 images = test_images[test_labels.argmax(axis=1) == 3] preds = model.predict(images) print('Predicted before:', preds.argmax(axis=1)) # 创建FGSM实例 attack = FastGradientMethod(estimator=classifier, targeted=True) # 生成对抗样本 adv_images = attack.generate(x=images, y=target_label) # 使用模型对对抗样本进行预测 preds = model.predict(adv_images) print('Predicted:', preds.argmax(axis=1)) # 创建保存图像的目录 原始的数字3的图像和对抗样本 if not os.path.exists('org'): os.makedirs('org') if not os.path.exists('new'): os.makedirs('new') for i in range(len(images)): plt.imsave(f'org/{i}.png', images[i], cmap='gray') plt.imsave(f'new/{i}.png', adv_images[i], cmap='gray')
输出:
....
原始数字3的样本:
对抗样本(预测为8的):
如果只是生成扰动图片,则代码修改为:
# 其他代码保持不变... import numpy as np import tensorflow as tf from tensorflow.keras.datasets import mnist from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Flatten, Dense from tensorflow.keras.utils import to_categorical from art.estimators.classification import TensorFlowV2Classifier from art.attacks.evasion import FastGradientMethod import matplotlib.pyplot as plt import os # 加载MNIST数据集 (train_images, train_labels), (test_images, test_labels) = mnist.load_data() train_images = train_images.astype('float32') / 255 test_images = test_images.astype('float32') / 255 train_labels = to_categorical(train_labels) test_labels = to_categorical(test_labels) # 创建一个简单的全连接神经网络模型 model = Sequential([ Flatten(input_shape=(28, 28)), Dense(128, activation='relu'), Dense(10, activation='softmax'), ]) # 编译和训练模型 model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) model.fit(train_images, train_labels, epochs=5) # 创建ART分类器 classifier = TensorFlowV2Classifier( model=model, nb_classes=10, input_shape=(28, 28), loss_object=tf.keras.losses.CategoricalCrossentropy(from_logits=True), clip_values=(0, 1) ) # 选择数字3的图像 images = test_images[test_labels.argmax(axis=1) == 3] preds = model.predict(images) print('Predicted before:', preds.argmax(axis=1)) # 创建一个目标标签(我们希望模型将数字3识别为数字8) target_label = to_categorical(8, num_classes=10) target_label = np.tile(target_label, (len(images), 1)) # 创建FGSM实例 attack = FastGradientMethod(estimator=classifier, targeted=True) # 初始化对抗样本为原始图像 adv_images = np.copy(images) for i in range(100): # 最多迭代100次 # 生成对抗样本的扰动 perturbations = attack.generate(x=adv_images, y=target_label) - adv_images # 计算所有样本的平均扰动 avg_perturbation = np.mean(perturbations, axis=0) # 将平均扰动添加到所有对抗样本上 adv_images += avg_perturbation # 使用模型对对抗样本进行预测 preds = model.predict(adv_images) print('Iteration:', i, 'Predicted:', preds.argmax(axis=1)) # 如果所有的预测结果都为8,那么停止迭代 if np.all(preds.argmax(axis=1) == 8): break # 保存对抗样本 for i in range(len(adv_images)): plt.imsave(f'new/adv_image_{i}.png', adv_images[i], cmap='gray') # 归一化平均扰动并保存为图像 avg_perturbation = (avg_perturbation - np.min(avg_perturbation)) / (np.max(avg_perturbation) - np.min(avg_perturbation)) plt.imsave('avg_perturbation.png', avg_perturbation, cmap='gray')
输出结果:
然后可以看到扰动图片:
代码说明:
这段代码的主要目标是生成对抗样本,使得一个训练好的神经网络模型将MNIST数据集中的数字3错误地识别为数字8。
以下是代码的主要步骤:
1. 加载和预处理数据:代码首先加载MNIST数据集,并将图像数据归一化到0, 1]范围。然后,它选择了所有的数字3的图像,并创建了一个目标标签,这是我们希望模型将数字3识别为的数字。
2. 创建和训练模型:代码创建了一个简单的全连接神经网络模型,并使用训练数据对它进行训练。
3. 创建ART分类器:代码创建了一个ART分类器,这个分类器是对原始模型的一个包装,它提供了一些额外的功能,例如生成对抗样本。
4. 生成对抗样本:代码创建了一个FGSM实例,并使用它在每次迭代中生成对抗样本的扰动。然后,它计算所有样本的平均扰动,并将这个平均扰动添加到所有对抗样本上。这个过程重复100次,或者直到所有的预测结果都为8。
5. 保存对抗样本和平均扰动:代码将每个对抗样本和平均扰动保存为图像。
这段代码的主要思想是,通过添加一些微小的扰动到原始图像上,可以使得神经网络模型的预测结果发生改变。这种方法可以用来测试模型的鲁棒性,或者用来攻击模型。
ART库地址:https://github.com/Trusted-AI/adversarial-robustness-toolbox/
【引申】
如果要生成针对交通信号牌的对抗样本,
(1)首先要找一个CNN,能够很好识别指示牌:
例如:
https://www.kaggle.com/code/shivank856/gtsrb-cnn-98-test-accuracy
https://cloud.tencent.com/developer/article/2201792
两个的精确率都还不错。
当然,他们都是基于开源的交通信号指示牌数据做的模型:Traffic sign recognition system based on Tensorflow convolutional neural network (CNN) (GTSRB dataset)
(2)下载数据集,也就是比赛中的源样本和目标样本图片,送入上述网络,看看识别结果,是否正常识别。
(3)将该网络导入,然后用上面的步骤生成对抗样本。
写在最后:
试了下cleverhans和art,还是art的库好用,本文用了FastGradientMethod,然而,生成对抗样本的成功率并不是100%,有些对抗样本可能无法欺骗模型。比如将贵宾犬图像错误地识别为长臂猿,就很难成功。
以下是一些可能的原因:
1. 对抗攻击的强度不够。在你的代码中,你使用了Fast Gradient Sign Method(FGSM)来生成对抗样本。FGSM有一个参数eps,它控制了对抗攻击的强度。如果eps太小,那么生成的对抗样本可能无法欺骗模型。你可以尝试增大eps的值。
2. 目标类别和原始类别太远。在你的代码中,你希望将贵宾犬(在ImageNet标签中的索引是263)识别为长臂猿(在ImageNet标签中的索引是368)。这两个类别在模型的特征空间中可能相距很远,所以需要更强的对抗攻击才能将一个类别转换为另一个类别。
3. 模型的鲁棒性太强。有些模型对对抗攻击有很好的鲁棒性,很难生成能欺骗它的对抗样本。你可以尝试使用其他的对抗攻击方法,例如DeepFool或Carlini & Wagner L2攻击。
你可以尝试以上的建议,看看是否能提高对抗样本的成功率。
补充:
DeepFool生成对抗样本原理
DeepFool是一种迭代的对抗攻击方法,其目标是找到最小的扰动,使得模型的预测结果改变。以下是DeepFool的基本步骤:
1. 初始化:选择一个输入样本,计算其在模型中的预测结果。
2. 迭代:在每一步迭代中,找到使得模型预测结果改变的最小扰动。这个扰动是通过线性化模型的决策边界,并计算输入样本到这个线性边界的距离来得到的。
3. 生成对抗样本:将计算得到的扰动添加到原始的输入样本上,生成对抗样本。
DeepFool的优点是生成的对抗样本的扰动通常比FGSM更小,但是需要进行多次迭代,计算复杂度较高。
Carlini & Wagner L2攻击生成对抗样本原理
Carlini & Wagner L2攻击(C&W L2攻击)是一种优化方法,其目标是找到最小的L2范数扰动,使得模型的预测结果改变。以下是C&W L2攻击的基本步骤:
1. 定义优化问题:C&W L2攻击的目标是最小化扰动的L2范数以及一个关于模型预测结果的损失函数。
2. 求解优化问题:使用梯度下降等优化算法求解这个优化问题,找到最小的扰动。
3. 生成对抗样本:将计算得到的扰动添加到原始的输入样本上,生成对抗样本。
C&W L2攻击的优点是生成的对抗样本的质量通常非常高,但是需要求解一个优化问题,计算复杂度较高。
【PGD算法】
PGD(Projected Gradient Descent)算法是一种优化算法,主要用于处理带有约束的优化问题。其基本原理如下:
1. 梯度下降:在每一步,算法首先按照目标函数的负梯度方向进行一步更新,这一步和普通的梯度下降算法相同。
2. 投影:由于问题带有约束,所以更新后的解可能不满足约束条件。因此,算法需要将更新后的解投影回可行域,即找到可行域中离更新后的解最近的点作为新的解。
这两步交替进行,直到满足停止条件(如达到最大迭代次数,或者目标函数值的改变小于某个阈值)。
PGD算法的一个重要应用是在对抗学习中生成对抗样本。在这种情况下,目标函数通常是模型的损失函数,约束条件是对抗样本的扰动范围。通过PGD算法,可以找到在约束条件下使模型损失函数最大的对抗样本,从而评估模型的鲁棒性。