概述
对抗样本指的是在数据集中故意地加入细微的干扰(有时人眼无法识别这样的干扰)所形成的输入样本,这样的样本会导致模型以高置信度给出一个错误的输出。以分类问题为例,对抗样本用数学语言可以描述为,给定输入数据\(x\)和分类器\(f\),对应的分类结果为\(f(x)\),假设存在一个非常小的扰动\(\epsilon\),使得\(f(x+\epsilon)!=f(x)\),那么\(f+\epsilon\)便为一个对抗样本。
下图为对抗样本的一个示例图:
按照攻击后的效果,对抗样本分为Targeted Attack和Non-Targeted Attack。前者在攻击之前会预先设定好攻击的目标,例如把红球识别为绿球,也就是说攻击之后的结果是确定的;而后者指的是攻击之前不设定攻击目标,只要攻击之后识别结果发生改变即可。
按照攻击成本,对抗样本分为白盒攻击、黑盒攻击和物理攻击。白盒攻击的难度最低,要求能够完整地获取模型的结构,包括模型的组成以及参数,并且可以完全地控制模型的输入。这一前置条件比较苛刻,通常用作学术研究或者是黑盒攻击和物理攻击的研究基础;黑盒攻击的难度相较于白盒攻击有了很大提高,它完全把要攻击的模型当作一个黑盒,对其结构没有任何了解,只能控制模型的输入。通过对比输入和输出的反馈来进行下一步攻击;物理攻击的难度最大,除了不了解模型的结构,对于模型输入的控制也很弱。以攻击图像分类模型为例,攻击样本要通过相机或者摄像头采集,然后经过一系列预处理之后再送入模型。
本文以计算机视觉相关的深度学习模型为例,对几种白盒攻击算法和黑盒攻击算法,以及常用的防御算法进行简单介绍。
白盒攻击算法
基于优化的对抗样本生成算法
深度学习模型的训练过程为,通过计算样本数据的预测值与真实值之间的损失函数,然后基于反向传播的方法计算损失函数的梯度,基于此不断调整模型的参数,减小损失函数的值,从而迭代计算出模型各层的参数。对抗样本的生成过程可以仿照这一流程,唯一不同的一点是,在迭代训练过程中,将网络的参数固定下来,把对抗样本当成需要训练的参数,然后通过反向传播来调整对抗样本的值。这种生成对抗样本的方法属于定向攻击算法。
下面的代码以训练好的ResNet为例,生成针对于ResNet的对抗样本。
1 | import torch |
1 | model=torchvision.models.resnet50(pretrained=True).to('cuda') #预训练模型是基于ImageNet数据集基础之上训练得到的,ImageNet包含的类别可参考https://blog.csdn.net/weixin_41770169/article/details/80482942 |
接下来,我们选取一张比萨的图片(分类编号为963),然后试图构造对抗样本,使模型将其误分类为拼图(编号611)
1 | img=cv2.imread('pizza.jpg')[...,::-1] |
1 | plt.imshow(img) |
1 | def construct_training_img(img): |
1 | training_img=construct_training_img(img) |
1 | target_tensor=torch.tensor([611]).to('cuda') |
1 | for i in range(200): |
1 | attack_img=training_img.squeeze().detach().cpu().numpy() |
1 | attack_img=construct_training_img(attack_img) |
1 | torch.argmax(model(attack_img)) |
tensor(611, device='cuda:0')
FGM/FGSM算法
FGM(Fast Gradient Method),即快速梯度算法,它可以作为无定向攻击和定向攻击算法使用。假设图片的原始数据为\(x\),图片识别的结果为\(y\)。给原始图像加上肉眼难以识别的细微变化\(\eta\),则变换后的图像为\(\tilde{x}=x+\eta\)。
将修改后的图像\(\tilde{x}\)输入分类模型中,则线性层的计算结果变为:\(\omega^T \tilde{x}=\omega^T x+\omega^T \eta\)。由于攻击样本的目的是以微小的修改,使得分类结果产生较大的变化,因此如果变化量与梯度的变化方向完全一致,也就是\(\eta=\text{sign}(\omega)\),那么分类结果将会产生较大的变化。
下面的代码为FGM的示例。由于代码其他部分与基于优化的基本类似,因此下面的代码仅仅包括了训练过程:
1 | for i in range(10): |
DeepFool算法
DeepFool也是一种基于梯度的白盒攻击算法,它属于无定向攻击算法。相较于FGM算法,它不用指定学习速率,算法本身可以计算出相对与FGM更小的扰动,从而达到攻击目的。
以简单的二分类问题为例,我们假设分类器\(f\)具有线性的分类平面。如果要改变某点\(x_0\)的分类结果,则一定要跨过分割平面。显然最短的移动距离就是垂直于分割平面进行移动。如果将这个距离记为\(r_*(x_0)\),\(f\)的参数设为\(w\),那么存在如下关系: \[ r_*(x_0)=\arg \min ||r||_2=-\frac{f(x_0)}{||w||_2^2}w \] 对于平面来说,\(w\)为平面的法向量,因此这一公式就代表将\(x_0\)沿着垂直于分类平面的方向移动。同时,\(w\)也是\(f\)的梯度方向。
而更加真实的情况则通常是分类器\(f\)具有一个分类曲面,此时就需要重复多次沿着梯度方向移动的过程。算法步骤如下:
对于多分类的情况则可以理解为二分类的扩展,相当于是多个One-vs-All分类器的组合。由于DeepFool属于无定向攻击,因此它在每次移动的过程中贪心地选取距离最近的分类超平面,并向这一方向移动。算法过程如下:
下面为DeepFool的代码实现,同样地,下面只包含训练生成对抗样本过程的代码:
1 | orig_label=604 |
JSMA算法
JSMA(Jacobian-based Saliency Map Attack)是\(l_0\)范数的白盒定向攻击算法,它追求尽量少地修改像素点。JSMA的特点是引入了Saliency Map的概念,用以表征输入特征对于预测结果的影响程度。
假设一个神经网络的输入为\(x\in \mathbb{R}^M\),输出为\(Y\in \mathbb{R}^N\),隐藏层的个数为\(n\),前向计算的结果为\(f(x)\),其中\(f(x)\)未经过Softmax层的处理。
算法的步骤如下:
计算前向导数:\(\nabla F(x)=\frac{\partial F(x)}{\partial x}\)
构造对抗显著图
分类器对于一个输入\(x\)的分类规则为\(\text{cls}(x)=\arg \max_j F_j(x)\)。假设分类器将\(x\)分类为\(j\),但是我们希望将其分为\(t\),即\(t=\arg\max_j F_j(x)\),以此构造对抗显著图: \[ S(X,t)[i]=\begin{cases} 0 &\text{if}~ \frac{\partial F_t(x)}{\partial x}<0~\text{or}~\sum_{j\ne t} \frac{\partial F_j(x)}{\partial x}>0 \\ (\frac{\partial F_t(x)}{\partial x})|\sum_{j\ne t} \frac{\partial F_j(x)}{\partial x}|~~&\text{otherwise}\\ \end{cases} \] 据此可以得到哪些像素位置的改变对于目标分类\(t\)的影响最大。如果对应导数值为正,则增大该位置像素可以增加目标\(t\)的分数;如果为负值则相反。
根据上一步构造的显著图,挑选使得\((\frac{\partial F_t(x)}{\partial x})|\sum_{j\ne t}\)的值最大的位置,然后增加或者减小其像素值,对应地就可以增加目标\(t\)的输出,然后重复迭代,直到攻击成功或者达到最大破坏阈值。
下面为JSMA算法的实现,同样只包含了生成对抗样本的过程:
1 | orig_label=604 |
CW算法
CW算法通常被认为是攻击能力最强的白盒攻击算法之一,同时也是一种基于优化的对抗样本生成算法。它的创新之处在于对损失函数的定义上。在定向攻击中,损失函数常常使用交叉熵,优化过程就是不断地减小目标函数的过程。
假设在原始数据\(x\)上增加扰动,生成对抗样本\(x+\delta\),对抗样本和原始数据之间的距离定义为\(D(x,x+\delta)\),那么整个优化函数可以定义为: \[ \min D(x,x+\delta)+c\cdot f(x+\delta) \] 其中,\(f\)为自定义的损失函数。参数\(c\)决定了扰动的大小,\(c\)越大则攻击成功率和扰动都会变大,通常使用二分查找的方式来选择尽可能小的\(c\)值。对于定向的\(l_2\)攻击,假设攻击目标的标签为\(t\),被攻击模型的输出为\(Z\),那么目标函数定义为: \[ f(x)=\max(\max\{Z(x)_i:i\ne t\}-Z(x)_t,-k) \] CW算法的另一个特点是对数据截断的处理。它使用了变换变量的方法,具体可以描述为,引入一个新的变量\(w\),将对抗样本\(x+\delta\)表示为\(x+\delta=\frac{1}{2}(\tanh (w)+1)\)。这样既保证了对抗样本的取值范围不溢出,也不会因为数据截断而导致梯度消失。这样,整个优化目标便可以写为: \[ \min ||\frac{1}{2}(\tanh (w)+1)-x||_2^2+c\cdot f(\tanh(w)+1) \] 下面为CW算法的实现,只包含了关键参数的定义和对抗样本的生成过程:
1 | min_=-3.0 |
1 | for outer_step in range(20): #二分法寻找最合适的参数c |
黑盒攻击算法
单像素攻击算法
单像素攻击(Single Pixel Attack)是一种典型的黑盒攻击算法,它的基本思想是,通过修改原始数据上的一个像素的值,让模型产生分类错误。
以图像分类模型\(f\)为例,假设图像为\(I\),它的通道数为\(l\)。分类标签为\(c(I)\),分类标签的集合表示为\(\{1,2,\dots,C\}\)。用\((b,x,y)\)代表坐标为\((x,y)\)的像素点的\(b\)号通道。设图像存在某个关键像素点\((*,x,y)\),通过修改它的值使得图像\(I\)变为\(I_p\),此时\(f\)的分类结果产生错误,即\(f(I_p)!=c(I)\)。
图像的扰动可以通过扰动函数\(\text{PERT}(I,p,x,y)=p\cdot \text{sign}(I(*,x,y))\)来实现,其中\(p\)为扰动系数,需要手动设置。则单像素攻击算法可以简单描述为,每次随机选择一个像素点,然后在对原图的基础上对该点施加扰动。如果扰动后的图像出现分类错误,则说明该点为关键像素点,否则则不是关键像素点。
下面为单像素攻击算法的代码示例。根据实际尝试,对于较高维度的输入(例如alexnet的3*224*224的输入),仅修改一个像素点根本无法实现攻击,需要随机选取多个像素同时修改才能攻击成功:
1 | import torch |
1 | model=torchvision.models.alexnet(pretrained=True).to('cuda')#预训练模型是基于ImageNet数据集基础之上训练得到的,ImageNet包含的类别可参考https://blog.csdn.net/weixin_41770169/article/details/80482942 |
接下来,我们选取一张沙漏的图片(分类编号为604),然后试图构造对抗样本
1 | img=cv2.imread('hourglass.jpg')[...,::-1] |
1 | plt.imshow(img) |
1 | def construct_training_img(img): |
1 | model.eval() |
1 | p=10.0 |
1 | print(torch.argmax(model(perturbed_img))) |
tensor(707, device='cuda:0')
迁移学习攻击
在黑盒攻击中,迁移学习攻击算法是一种非常重要的攻击算法。它的基本思想是,对于结构类似的神经网络,在面对相同对抗样本的攻击时,具有类似的表现。也就是说,如果一个样本可以攻击模型A,那么有一定的概率它也可以攻击模型B。
下面为使用ResNet50,用基于优化的对抗样本生成方法生成对抗样本,然后去攻击ResNet18的代码:
1 | import torch |
1 | model=torchvision.models.resnet50(pretrained=True).to('cuda') #预训练模型是基于ImageNet数据集基础之上训练得到的,ImageNet包含的类别可参考https://blog.csdn.net/weixin_41770169/article/details/80482942 |
接下来,我们选取一张图片(分类编号为963),然后试图构造对抗样本,使模型将其误分类为拼图(编号611)
1 | img=cv2.imread('pizza.jpg')[...,::-1] |
1 | def construct_training_img(img): |
1 | training_img=construct_training_img(img) |
1 | target_tensor=torch.tensor([611]).to('cuda') |
1 | for i in range(500): # 训练500轮使得对抗样本具有较强的鲁棒性 |
1 | attack_img=construct_training_img(attack_img) |
1 | torch.argmax(model(attack_img)) |
tensor(611, device='cuda:0')
使用上一步生成的对抗样本去攻击ResNet18,可以成功攻击:
1 | model2=torchvision.models.resnet18(pretrained=True).to('cuda') |
tensor(463, device='cuda:0')
试图使用ResNet50的对抗样本攻击AlexNet,无法让AlexNet出现识别错误:
1 | model2=torchvision.models.alexnet(pretrained=True).to('cuda') |
tensor(963, device='cuda:0')
对抗样本防御方法
常用的抵御对抗样本的方法包括:
- 对抗样本自身也存在着鲁棒性问题,也就是说对抗样本在经过旋转、滤波、亮度或对比度调整、加入噪声之后,可能会使得对抗样本失效,使得对抗样本仍可以正确分类。因此在不影响正常样本的分类这一前提下,可以加入一些图像预处理步骤,具体参数例如旋转角度、噪声添加量等需要根据模型来进行调整。
- 对抗训练:使用常见的攻击算法生成一系列的对抗样本,然后将对抗样本与正常样本一起送入模型中训练。
- 高斯数据增强:对抗训练的方法难以穷尽所有的对抗样本,而对抗样本的思路相当于是在正常样本中加入噪声,因此在训练中可以用高斯噪声去模拟这种噪声。在模型的训练过程中,在原始数据的基础上叠加高斯噪声,然后对模型进行训练,即可对模型进行加固。
- 使用去噪编码器,将数据经过去噪自编码器处理之后再送入模型中进行预测。
常用对抗样本工具箱
- AdvBox:由百度安全实验室研发的AI模型安全工具箱,https://github.com/advboxes/AdvBox
- ART:Welcome to the Adversarial Robustness Toolbox — Adversarial Robustness Toolbox 1.7.2 documentation
- FoolBox:bethgelab/foolbox: A Python toolbox to create adversarial examples that fool neural networks in PyTorch, TensorFlow, and JAX
参考
- AI安全之对抗样本入门
- DeepFool - 知乎 (zhihu.com)
- 对抗样本生成算法之JSMA算法_ilalaaa的博客-CSDN博客