对抗样本之FGSM原理&实战

目录
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样本生成

公式

  1. 如下图,其中 x 是原始样本θ 是模型的权重参数(即w),y是x的真实类别。输入原始样本,权重参数以及真实类别,通过 J 损失函数求得神经网络的损失值,∇x 表示对 x 求偏导,即损失函数 J 对 x 样本求偏导。sign是符号函数,即sign(-2),sign(-1.5)等都等于 -1;sign(3),sign(4.7)等都等于 1。sign函数图如下。
  2. ϵ(epsilon)的值通常是人为设定 ,可以理解为学习率,一旦扰动值超出阈值,该对抗样本会被人眼识别。
在这里插入图片描述
在这里插入图片描述
  1. 之后,原始图像x + 扰动值 η = 对抗样本 x + η 。
  2. 理解公式后,感觉FGSM并不难。其思想也和dp神经网络类似,但它更像是一个逆过程。我们机器学习算法中无论如何都希望损失函数能越小越好;那对抗样本就不一样了,它本身就是搞破坏的东西,当然是希望损失值越大越好,这样算法就预测不出来,就会失效。

2、pytorch实现

声明:代码来源于pytorch官网,跳转;你要是想看官网直接跳转即可,但是以下的内容我会讲解代码以及自己的理解。

  • 下面代码需要下载预训练模型,将其放在如下代码指定文件夹下,预训练模型下载

  • pytorch不会 ?见 pytorch从基础到实战,做学术可离不开它勒。

2.1 建立模型

from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import numpy as np
import matplotlib.pyplot as plt

# 这里的epsilon先设定为几个值,到时候后面可视化展示它的影响如何
epsilons = [0, .05, .1, .15, .2, .25, .3]
# 这个预训练的模型需要提前下载,放在如下url的指定位置,下载链接如上
pretrained_model = "data/lenet_mnist_model.pth"
use_cuda=True

# 就是一个简单的模型结构
class Net(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)

    def forward(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)
        return F.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,没有就是使用cpu
print("CUDA Available: ",torch.cuda.is_available())
device = torch.device("cuda" if (use_cuda and torch.cuda.is_available()) else "cpu")

# Initialize the network
model = Net().to(device)

# 加载前面的预训练模型
model.load_state_dict(torch.load(pretrained_model, map_location='cpu'))

# 设置为验证模式. 
# 详解见这个博客 https://blog.csdn.net/qq_38410428/article/details/101102075
model.eval()

Out:

  • 定义的网络模型较为简单,如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QqouDTJj-1620884909006)(C:\Users\刘志远\AppData\Local\Temp\1620882912362.png)]

2.2 FGSM模块

# FGSM attack code
def fgsm_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)
    # 返回对抗样本
    return perturbed_image

2.3 测试

  • 由于FGSM算法一次迭代即可,所以没必要单独加个train模块,直接在测试模块实现就行
def test( model, device, test_loader, epsilon ):

    # 准确度计数器
    correct = 0
    # 对抗样本
    adv_examples = []

    # 循环所有测试集
    for data, target in test_loader:
        # Send the data and label to the device
        data, target = data.to(device), target.to(device)

        # Set requires_grad attribute of tensor. Important for Attack
        data.requires_grad = True

        # Forward pass the data through the model
        output = 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 on
        if init_pred.item() != target.item():
            continue

        # Calculate the loss
        loss = F.nll_loss(output, target)

        # Zero all existing gradients
        model.zero_grad()

        # Calculate gradients of model in backward pass
        loss.backward()

        # Collect datagrad
        data_grad = data.grad.data

        # Call FGSM Attack
        perturbed_data = fgsm_attack(data, epsilon, data_grad)

        # Re-classify the perturbed image
        output = model(perturbed_data)

        # Check for success
        final_pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability
        if final_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:
            # 这里都是为后面的可视化做准备
            if len(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 epsilon
    final_acc = correct/float(len(test_loader))
    print("Epsilon: {}\tTest Accuracy = {} / {} = {}".format(epsilon, correct, len(test_loader), final_acc))

    # Return the accuracy and an adversarial example
    return final_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 epsilon
for eps in epsilons:
    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 epsilon
cnt = 0
plt.figure(figsize=(8,10))
for i in range(len(epsilons)):
    for j in range(len(examples[i])):
        cnt += 1
        plt.subplot(len(epsilons),len(examples[0]),cnt)
        plt.xticks([], [])
        plt.yticks([], [])
        if j == 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:


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RAHotFG7-1620884909009)(C:\Users\刘志远\AppData\Local\Temp\1620884743243.png)]

如有疑惑,以下评论区留言。力所能及,必答之。

你可能感兴趣的:(对抗样本之FGSM原理&实战)