目录

对抗性示例生成

创建时间: 2018年8月14日 |上次更新时间:2024 年 8 月 27 日 |上次验证时间:未验证

作者: Nathan Inkawhich

如果您正在阅读本文,希望您能体会到一些 机器学习模型是。研究不断推动 ML 模型 更快、更准确、更高效。然而,一个经常 设计和训练模型时被忽视的方面是安全性和 稳健性,尤其是面对想要愚弄的对手 模型。

本教程将提高您对安全漏洞的认识 的 ML 模型,并将深入了解对抗性的热门话题 机器学习。你可能会惊讶地发现,添加难以察觉 对图像的扰动会导致模型截然不同 性能。鉴于这是一个教程,我们将探讨该主题 via 示例。具体来说,我们将使用 第一种也是最流行的攻击方法,Fast Gradient Sign Attack (FGSM) 来欺骗 MNIST 分类器。

威胁模型

就上下文而言,对抗性攻击有许多类别,每类都有 攻击者知识的不同目标和假设。然而,在 常规 总体目标是添加最少的扰动 添加到输入数据中,从而导致所需的错误分类。有 攻击者知识的几种假设,其中两种 分别是:white-boxblack-box白盒攻击假定 攻击者拥有对模型的完整知识和访问权限,包括 架构、输入、输出和权重。黑盒攻击假定 攻击者只能访问模型的输入和输出,并且 对底层架构或权重一无所知。有 还有几种类型的目标,包括误分类源/目标误分类误分类目标均值 攻击者只希望 output classification 出错,但确实 不在乎新分类是什么。源/目标 错误分类意味着对手想要更改 最初是特定源类的,因此它被归类为 specific 目标类。

在这种情况下,FGSM 攻击是一种以错误分类为目标的白盒攻击。有了这些背景信息,我们现在就可以 详细讨论攻击。

快速梯度标志攻击

迄今为止,最早也是最流行的对抗性攻击之一是 称为快速梯度符号攻击 (FGSM),并进行了描述 由 Goodfellow 等人。等人在解释和利用对抗性 示例。攻击非常 功能强大,但直观。它旨在通过以下方式攻击神经网络 利用他们的学习方式 gradients。这个想法很简单,而是 而不是通过根据 反向传播的梯度,则攻击会调整输入数据以最大化 基于相同反向传播梯度的损失。换句话说, 攻击使用损失相对于输入数据的梯度,然后 调整输入数据以最大化损失。

在我们进入代码之前,让我们看一下著名的 FGSM panda 示例并提取 一些符号。

fgsm_panda_image

从图中可以看出,\(\mathbf{x}\) 是原始输入图像 被正确分类为“熊猫”,\(y\) 是真实标签 对于 \(\mathbf{x}\),\(\mathbf{\theta}\) 表示模型 参数,并且 \(J(\mathbf{\theta}, \mathbf{x}, y)\) 是损失 用于训练网络。攻击反向传播了 梯度返回输入数据以计算 \(\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y)\)。然后,它会进行调整 输入数据在 picture) 的方向(即 \(sign(\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y))\)) 上,它将 使损失最大化。然后,由此产生的扰动图像 \(x'\) 被目标网络错误地分类为“长臂猿”,而它仍然 显然是一只“熊猫”。

希望现在本教程的动机很明确,所以让我们跳起来 到实现中。

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

实现

在本节中,我们将讨论本教程的输入参数 定义受到攻击的模型,然后对攻击进行编码并运行一些测试。

输入

本教程只有三个输入,定义为 遵循:

  • epsilons- 用于运行的 epsilon 值列表。是的 请务必在列表中保留 0,因为它表示模型 性能。此外,直观地,我们会 预计 ε 越大,扰动就越明显 但就降级模型而言,攻击越有效 准确性。由于这里的数据范围是 \([0,1]\),所以没有 epsilon 值应超过 1。

  • pretrained_model- 预训练 MNIST 模型的路径,该模型是 使用 pytorch/examples/mnist 进行训练。 为简单起见,请在此处下载预训练模型。

  • use_cuda- boolean 标志以使用 CUDA(如果需要且可用)。 请注意,具有 CUDA 的 GPU 对于本教程并不重要,因为 CPU 将 不需要太多时间。

epsilons = [0, .05, .1, .15, .2, .25, .3]
pretrained_model = "data/lenet_mnist_model.pth"
use_cuda=True
# Set random seed for reproducibility
torch.manual_seed(42)
<torch._C.Generator object at 0x7fce85f71b30>

模型受到攻击

如前所述,受到攻击的模型与 pytorch/examples/mnist 中的 MNIST 模型相同。 您可以训练并保存自己的 MNIST 模型,也可以下载并使用 提供的模型。这里的 Net definition 和 test dataloader 有 是从 MNIST 示例中复制的。本节的目的是 定义 Model 和 DataLoader,然后初始化 Model 并加载 预训练权重。

# LeNet Model definition
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, 3, 1)
        self.conv2 = nn.Conv2d(32, 64, 3, 1)
        self.dropout1 = nn.Dropout(0.25)
        self.dropout2 = nn.Dropout(0.5)
        self.fc1 = nn.Linear(9216, 128)
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2)
        x = self.dropout1(x)
        x = torch.flatten(x, 1)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.dropout2(x)
        x = self.fc2(x)
        output = F.log_softmax(x, dim=1)
        return output

# MNIST Test dataset and dataloader declaration
test_loader = torch.utils.data.DataLoader(
    datasets.MNIST('../data', train=False, download=True, transform=transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize((0.1307,), (0.3081,)),
            ])),
        batch_size=1, shuffle=True)

# Define what device we are using
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)

# Load the pretrained model
model.load_state_dict(torch.load(pretrained_model, map_location=device, weights_only=True))

# Set the model in evaluation mode. In this case this is for the Dropout layers
model.eval()
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Failed to download (trying next):
HTTP Error 403: Forbidden

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz to ../data/MNIST/raw/train-images-idx3-ubyte.gz

  0%|          | 0.00/9.91M [00:00<?, ?B/s]
100%|##########| 9.91M/9.91M [00:00<00:00, 130MB/s]
Extracting ../data/MNIST/raw/train-images-idx3-ubyte.gz to ../data/MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Failed to download (trying next):
HTTP Error 403: Forbidden

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz to ../data/MNIST/raw/train-labels-idx1-ubyte.gz

  0%|          | 0.00/28.9k [00:00<?, ?B/s]
100%|##########| 28.9k/28.9k [00:00<00:00, 42.7MB/s]
Extracting ../data/MNIST/raw/train-labels-idx1-ubyte.gz to ../data/MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Failed to download (trying next):
HTTP Error 403: Forbidden

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz to ../data/MNIST/raw/t10k-images-idx3-ubyte.gz

  0%|          | 0.00/1.65M [00:00<?, ?B/s]
100%|##########| 1.65M/1.65M [00:00<00:00, 75.8MB/s]
Extracting ../data/MNIST/raw/t10k-images-idx3-ubyte.gz to ../data/MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
Failed to download (trying next):
HTTP Error 403: Forbidden

Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz
Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz to ../data/MNIST/raw/t10k-labels-idx1-ubyte.gz

  0%|          | 0.00/4.54k [00:00<?, ?B/s]
100%|##########| 4.54k/4.54k [00:00<00:00, 24.2MB/s]
Extracting ../data/MNIST/raw/t10k-labels-idx1-ubyte.gz to ../data/MNIST/raw

CUDA Available:  True

Net(
  (conv1): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1))
  (dropout1): Dropout(p=0.25, inplace=False)
  (dropout2): Dropout(p=0.5, inplace=False)
  (fc1): Linear(in_features=9216, out_features=128, bias=True)
  (fc2): Linear(in_features=128, out_features=10, bias=True)
)

FGSM 攻击

现在,我们可以通过以下方式定义创建对抗样本的函数 扰乱原始输入。该函数需要三个 inputs,image 是原始的干净图像 (\(x\)),epsilon 是 像素级扰动量 (\(\epsilon\)),data_grad 是相对于输入图像的损失梯度 (\(\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y)\)).函数 然后创建扰动图像作为fgsm_attack

\[perturbed\_image = 图像 + epsilon*sign(data\_grad) = x + \epsilon * sign(\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y)) \]

最后,为了保持数据的原始范围, 扰动图像被裁剪到范围 \([0,1]\)。

# FGSM attack code
def fgsm_attack(image, epsilon, data_grad):
    # Collect the element-wise sign of the data gradient
    sign_data_grad = data_grad.sign()
    # Create the perturbed image by adjusting each pixel of the input image
    perturbed_image = image + epsilon*sign_data_grad
    # Adding clipping to maintain [0,1] range
    perturbed_image = torch.clamp(perturbed_image, 0, 1)
    # Return the perturbed image
    return perturbed_image

# restores the tensors to their original scale
def denorm(batch, mean=[0.1307], std=[0.3081]):
    """
    Convert a batch of tensors to their original scale.

    Args:
        batch (torch.Tensor): Batch of normalized tensors.
        mean (torch.Tensor or list): Mean used for normalization.
        std (torch.Tensor or list): Standard deviation used for normalization.

    Returns:
        torch.Tensor: batch of tensors without normalization applied to them.
    """
    if isinstance(mean, list):
        mean = torch.tensor(mean).to(device)
    if isinstance(std, list):
        std = torch.tensor(std).to(device)

    return batch * std.view(1, -1, 1, 1) + mean.view(1, -1, 1, 1)

测试功能

最后,本教程的核心结果来自函数。对此测试函数的每次调用都会在 MNIST 测试集并报告最终准确性。但是,请注意, 此函数还接受 epsilon 输入。这是因为该函数报告了受到攻击的模型的准确性 来自实力强大的对手 \(\epsilon\)。更具体地说,对于 测试集中的每个样本,该函数都会计算 loss 对输入数据 (\(data\_grad\)) 的 Tim Bean 的 Tim S 的 Tim S 的 图像替换为 (\(Perturbed\_data\)),然后检查 如果 Perturbed 示例是对抗性的。除了测试 accuracy 的 Zip 函数中,该函数还会保存并返回一些 成功的对抗性示例,稍后可视化。testtestfgsm_attack

def test( model, device, test_loader, epsilon ):

    # Accuracy counter
    correct = 0
    adv_examples = []

    # Loop over all examples in test set
    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, don't 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

        # Restore the data to its original scale
        data_denorm = denorm(data)

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

        # Reapply normalization
        perturbed_data_normalized = transforms.Normalize((0.1307,), (0.3081,))(perturbed_data)

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

        # 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
            # Special case for saving 0 epsilon examples
            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:
            # Save some adv examples for visualization later
            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(f"Epsilon: {epsilon}\tTest Accuracy = {correct} / {len(test_loader)} = {final_acc}")

    # Return the accuracy and an adversarial example
    return final_acc, adv_examples

奔跑攻击

实现的最后一部分是实际运行攻击。这里 我们对 epsilons 输入中的每个 epsilon 值运行一个完整的测试步骤。 对于每个 epsilon,我们还保存了最终的准确率和一些成功的 对抗性示例将在接下来的部分中绘制。请注意 打印精度随着 epsilon 值的增加而降低。也 注意 \(\epsilon=0\) 情况代表原始测试精度, 没有攻击。

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)
Epsilon: 0      Test Accuracy = 9912 / 10000 = 0.9912
Epsilon: 0.05   Test Accuracy = 9605 / 10000 = 0.9605
Epsilon: 0.1    Test Accuracy = 8743 / 10000 = 0.8743
Epsilon: 0.15   Test Accuracy = 7111 / 10000 = 0.7111
Epsilon: 0.2    Test Accuracy = 4877 / 10000 = 0.4877
Epsilon: 0.25   Test Accuracy = 2717 / 10000 = 0.2717
Epsilon: 0.3    Test Accuracy = 1418 / 10000 = 0.1418

结果

准确性 vs Epsilon

第一个结果是准确度与 epsilon 图。如前所述 早些时候,随着 epsilon 的增加,我们预计测试准确性会降低。 这是因为更大的 ε 意味着我们在 方向,这将使损失最大化。请注意,曲线中的趋势是 即使 epsilon 值是线性间隔的,也不是线性的。为 例如,\(\epsilon=0.05\) 处的准确率只低了 4% 左右 比 \(\epsilon=0\) 多,但 \(\epsilon=0.2\) 处的准确率为 25% 低于 \(\epsilon=0.15\)。此外,请注意模型的准确性 达到 \(\epsilon=0.25\)\(\epsilon=0.3\) 之间的 10 类分类器的随机精度。

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()
准确性 vs Epsilon

对抗示例示例

还记得天下没有免费午餐的想法吗?在这种情况下,随着 epsilon 的增加 测试精度降低,但扰动变得更容易 感知。实际上,准确性之间存在权衡 攻击者必须考虑的降级和可感知性。在这里,我们 展示每个 epsilon 中一些成功的对抗性示例 价值。绘图的每一行都显示不同的 epsilon 值。第一个 row 是代表原始 “干净”的图像,没有扰动。每张图像的标题显示 “原始分类 - >对抗性分类。”注意, 扰动在 \(\epsilon=0.15\) 处开始变得明显,并且 在 \(\epsilon=0.3\) 处非常明显。然而,在所有情况下,人类都是 尽管增加了噪音,但仍能够识别正确的类。

# 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(f"Eps: {epsilons[i]}", fontsize=14)
        orig,adv,ex = examples[i][j]
        plt.title(f"{orig} -> {adv}")
        plt.imshow(ex, cmap="gray")
plt.tight_layout()
plt.show()
7 -> 7, 9 -> 9, 0 -> 0, 3 -> 3, 5 -> 5, 2 -> 8, 1 -> 3, 3 -> 5, 4 -> 6, 4 -> 6, 9 -> 4, 5 -> 6, 9 -> 5, 9 -> 5, 3 -> 2, 3 -> 5, 5 -> 3, 1 -> 6, 4 -> 9, 7 -> 9, 7 -> 2, 8 -> 2, 4 -> 8, 3 -> 7, 5 -> 3, 8 -> 3, 0 -> 8, 6 -> 5, 2 -> 3, 1 -> 8, 1 -> 9, 1 -> 8, 5 -> 8, 7 -> 8, 0 -> 2

下一步去哪里?

希望本教程能对 adversarial 这个话题有所了解 机器学习。从这里开始,有许多潜在的方向。 这种攻击代表了对抗性攻击研究的开端 并且由于后来有许多关于如何攻击和 保护 ML 模型免受对手的攻击。事实上,在 NIPS 2017 上有一个 对抗性攻防比赛和使用的许多方法 本文描述了 Adversarial Attacks 和 防御比赛。作品 On Defense 也导致了制作机器学习模型的想法 一般来说,对自然扰动和对抗性扰动都更稳健 精心制作的输入。

另一个方向是对抗性攻击和防御在不同的 域。对抗性研究不仅限于图像领域,勾选 出去这个攻击 语音转文本模型。但也许是了解更多信息的最佳方式 对抗性机器学习就是让你的手弄脏。尝试 实施与 NIPS 2017 竞赛不同的攻击,并了解如何操作 它与 FGSM 不同。然后,尝试保护模型免受你自己的影响 攻击。

根据可用资源,下一步的方向是修改 支持以批处理、并行和/或分布式方式处理工作的代码 vs 在上面每个循环中一次处理一次一次攻击。epsilon test()

脚本总运行时间:(4 分 1.060 秒)

由 Sphinx-Gallery 生成的图库

文档

访问 PyTorch 的全面开发人员文档

查看文档

教程

获取面向初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并解答您的问题

查看资源