目录
1、FGSM原理
2、pytorch实现
2.1 建立模型
2.2 FGSM模块
2.3 测试
2.4 可视化对比
2.5 对比样本与对抗样本
1、FGSM原理
论文 Explaining and harnessing adversarial examples. 这篇论文由Goodfellow等人发表在ICLR2015会议上,是对抗样本生成领域的经典论文。
FGSM(fast gradient sign method)是一种基于梯度生成对抗样本的算法,属于对抗攻击中的无目标攻击(即不要求对抗样本经过model预测指定的类别,只要与原样本预测的不一样即可)
我们在理解简单的dp网络结构的时候,在求损失函数最小值,我们会沿着梯度的反方向移动,使用减号,也就是所谓的梯度下降算法;而FGSM可以理解为梯度上升算法,也就是使用加号,使得损失函数最大化。先看下图效果,goodfellow等人通过对一个大熊猫照片加入一定的扰动(即噪音点),输入model之后就被判断为长臂猿。
FGSM样本生成
公式
如下图,其中x 是原始样本,θ 是模型的权重参数(即w),y是x的真实类别。输入原始样本,权重参数以及真实类别,通过 J 损失函数求得神经网络的损失值,∇x 表示对 x 求偏导,即损失函数 J 对 x 样本求偏导。sign是符号函数,即sign(-2),sign(-1.5)等都等于 -1;sign(3),sign(4.7)等都等于 1。sign函数图如下。
损失只是概率的负对数。损失值通过神经网络反向传播,神经网络的参数根据所使用的优化器进行更新
ϵ(epsilon)的值通常是人为设定 ,可以理解为学习率,一旦扰动值超出阈值,该对抗样本会被人眼识别。
在这里插入图片描述
在这里插入图片描述
之后,原始图像x + 扰动值 η = 对抗样本 x + η 。
理解公式后,感觉FGSM并不难。其思想也和dp神经网络类似,但它更像是一个逆过程。我们机器学习算法中无论如何都希望损失函数能越小越好;那对抗样本就不一样了,它本身就是搞破坏的东西,当然是希望损失值越大越好,这样算法就预测不出来,就会失效。
2、pytorch实现
声明:代码来源于pytorch官网,跳转;你要是想看官网直接跳转即可,但是以下的内容我会讲解代码以及自己的理解。
下面代码需要下载预训练模型,将其放在如下代码指定文件夹下,预训练模型下载
pytorch不会 ?见pytorch从基础到实战,做学术可离不开它勒。
2.1 建立模型
from__future__importprint_functionimporttorchimporttorch.nnasnnimporttorch.nn.functionalasFimporttorch.optimasoptimfromtorchvisionimportdatasets,transformsimportnumpyasnpimportmatplotlib.pyplotasplt# 这里的epsilon先设定为几个值,到时候后面可视化展示它的影响如何epsilons=[0,.05,.1,.15,.2,.25,.3]# 这个预训练的模型需要提前下载,放在如下url的指定位置,下载链接如上pretrained_model="data/lenet_mnist_model.pth"use_cuda=True# 就是一个简单的模型结构classNet(nn.Module):def__init__(self):super(Net,self).__init__()self.conv1=nn.Conv2d(1,10,kernel_size=5)self.conv2=nn.Conv2d(10,20,kernel_size=5)self.conv2_drop=nn.Dropout2d()self.fc1=nn.Linear(320,50)self.fc2=nn.Linear(50,10)defforward(self,x):x=F.relu(F.max_pool2d(self.conv1(x),2))x=F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)),2))x=x.view(-1,320)x=F.relu(self.fc1(x))x=F.dropout(x,training=self.training)x=self.fc2(x)returnF.log_softmax(x,dim=1)# 运行需要稍等,这里表示下载并加载数据集test_loader=torch.utils.data.DataLoader(datasets.MNIST('../data',train=False,download=True,transform=transforms.Compose([transforms.ToTensor(),])),batch_size=1,shuffle=True)# 看看我们有没有配置GPU,没有就是使用cpuprint("CUDA Available: ",torch.cuda.is_available())device=torch.device("cuda"if(use_cudaandtorch.cuda.is_available())else"cpu")# Initialize the networkmodel=Net().to(device)# 加载前面的预训练模型model.load_state_dict(torch.load(pretrained_model,map_location='cpu'))# 设置为验证模式. # 详解见这个博客 https://blog.csdn.net/qq_38410428/article/details/101102075model.eval()
Out:
定义的网络模型较为简单,如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QqouDTJj-1620884909006)(C:\Users\刘志远\AppData\Local\Temp\1620882912362.png)]
2.2 FGSM模块
# FGSM attack codedeffgsm_attack(image,epsilon,data_grad):# 使用sign(符号)函数,将对x求了偏导的梯度进行符号化sign_data_grad=data_grad.sign()# 通过epsilon生成对抗样本perturbed_image=image+epsilon*sign_data_grad# 做一个剪裁的工作,将torch.clamp内部大于1的数值变为1,小于0的数值等于0,防止image越界perturbed_image=torch.clamp(perturbed_image,0,1)# 返回对抗样本returnperturbed_image
2.3 测试
由于FGSM算法一次迭代即可,所以没必要单独加个train模块,直接在测试模块实现就行
deftest(model,device,test_loader,epsilon):# 准确度计数器correct=0# 对抗样本adv_examples=[]# 循环所有测试集fordata,targetintest_loader:# Send the data and label to the devicedata,target=data.to(device),target.to(device)# Set requires_grad attribute of tensor. Important for Attackdata.requires_grad=True# Forward pass the data through the modeloutput=model(data)init_pred=output.max(1,keepdim=True)[1]# get the index of the max log-probability# If the initial prediction is wrong, dont bother attacking, just move onifinit_pred.item()!=target.item():continue# Calculate the lossloss=F.nll_loss(output,target)# Zero all existing gradientsmodel.zero_grad()# Calculate gradients of model in backward passloss.backward()# Collect datagraddata_grad=data.grad.data# Call FGSM Attackperturbed_data=fgsm_attack(data,epsilon,data_grad)# Re-classify the perturbed imageoutput=model(perturbed_data)# Check for successfinal_pred=output.max(1,keepdim=True)[1]# get the index of the max log-probabilityiffinal_pred.item()==target.item():correct+=1# 这里都是为后面的可视化做准备if(epsilon==0)and(len(adv_examples)<5):adv_ex=perturbed_data.squeeze().detach().cpu().numpy()adv_examples.append((init_pred.item(),final_pred.item(),adv_ex))else:# 这里都是为后面的可视化做准备iflen(adv_examples)<5:adv_ex=perturbed_data.squeeze().detach().cpu().numpy()adv_examples.append((init_pred.item(),final_pred.item(),adv_ex))# Calculate final accuracy for this epsilonfinal_acc=correct/float(len(test_loader))print("Epsilon: {}\tTest Accuracy = {} / {} = {}".format(epsilon,correct,len(test_loader),final_acc))# Return the accuracy and an adversarial examplereturnfinal_acc,adv_examples
这个函数看起来很多,其中有很多操作都是为后面的可视化做准备,这里我说下比较重要的代码
重要的肯定就是损失函数关于x的偏导,如何求出喽。
model.zero_grad(): PyTorch文档中提到,如果grad属性不为空,新计算出来的梯度值会直接加到旧值上面。 为什么不直接覆盖旧的结果呢?这是因为有些Tensor可能有多个输出,那么就需要调用多个backward。 叠加的处理方式使得backward不需要考虑之前有没有被计算过导数,只需要加上去就行了。我们的情况很简单,就一个输出,所以需要使用这条语句
loss.backward():这条语句并不会更新参数,它只会求出各个中间变量的grad(梯度)值,当然也求出了损失函数关于x的偏导啦
data_grad = data.grad.data:由于前面使用了loss.backward() ,所以data这个tensor的grad属性,自然就有值了,还是损失函数对于x的偏导值
2.4 可视化对比
accuracies=[]examples=[]# Run test for each epsilonforepsinepsilons:acc,ex=test(model,device,test_loader,eps)accuracies.append(acc)examples.append(ex)plt.figure(figsize=(5,5))plt.plot(epsilons,accuracies,"*-")plt.yticks(np.arange(0,1.1,step=0.1))plt.xticks(np.arange(0,.35,step=0.05))plt.title("Accuracy vs Epsilon")plt.xlabel("Epsilon")plt.ylabel("Accuracy")plt.show()
Out:
在这里插入图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DvgZtpPf-1620884909008)(C:\Users\刘志远\AppData\Local\Temp\1620884610752.png)]
2.5 对比样本与对抗样本
# Plot several examples of adversarial samples at each epsiloncnt=0plt.figure(figsize=(8,10))foriinrange(len(epsilons)):forjinrange(len(examples[i])):cnt+=1plt.subplot(len(epsilons),len(examples[0]),cnt)plt.xticks([],[])plt.yticks([],[])ifj==0:plt.ylabel("Eps: {}".format(epsilons[i]),fontsize=14)orig,adv,ex=examples[i][j]plt.title("{} -> {}".format(orig,adv))plt.imshow(ex,cmap="gray")plt.tight_layout()plt.show()
Out: