基于梯度的攻击(Gradient-based Attacks):在开源模型上计算对抗梯度并迁移至闭源模型

好的,没问题。

基于梯度的攻击:对抗样本迁移的艺术与实践

各位同学,大家好!今天我们来深入探讨一个非常有趣且重要的领域——对抗样本攻击,特别是基于梯度的攻击方法,以及它们在开源模型上生成,并迁移到闭源模型上的能力。

对抗样本是指经过精心设计、对原始输入进行微小扰动后,能够导致机器学习模型产生错误预测的样本。这种现象揭示了机器学习模型的脆弱性,并引发了对模型鲁棒性和安全性的广泛关注。更进一步,如果在一个模型上生成的对抗样本能够在另一个模型上成功欺骗,我们称之为对抗样本的迁移性。

本次讲座将分为以下几个部分:

  1. 对抗样本攻击的基础概念:定义、分类和意义。
  2. 基于梯度的攻击方法详解:包括Fast Gradient Sign Method (FGSM), Basic Iterative Method (BIM), Projected Gradient Descent (PGD)等。
  3. 对抗样本的迁移性:原理、影响因素和评估方法。
  4. 代码实战:使用PyTorch实现FGSM和PGD攻击,并演示对抗样本的迁移。
  5. 防御策略简介:简要介绍一些常见的防御对抗样本攻击的方法。

一、对抗样本攻击的基础概念

定义:

对抗样本,顾名思义,是那些“对抗”机器学习模型的样本。它们通常是通过对原始输入数据添加微小、难以察觉的扰动而产生的。这些扰动虽然人眼难以察觉,却能导致模型输出错误的预测结果。

分类:

对抗样本可以根据不同的标准进行分类:

  • 基于攻击目标:
    • 目标攻击:攻击者希望模型输出特定的错误类别。
    • 非目标攻击:攻击者仅希望模型输出错误的类别,而不在乎具体是什么类别。
  • 基于攻击知识:
    • 白盒攻击:攻击者完全了解模型的结构、参数和训练数据。
    • 灰盒攻击:攻击者部分了解模型的结构或训练数据。
    • 黑盒攻击:攻击者对模型一无所知,只能通过输入输出来进行攻击。
  • 基于攻击范围:
    • 像素级攻击:直接修改图像的像素值。
    • 语义级攻击:在图像的语义层面进行修改,例如添加一个贴纸。

意义:

对抗样本的存在揭示了机器学习模型的内在脆弱性。这种脆弱性不仅存在于学术研究中,也可能被恶意攻击者利用,对现实世界的应用造成威胁。例如,自动驾驶系统可能被对抗样本欺骗,导致交通事故;人脸识别系统可能被对抗样本绕过,造成安全漏洞。因此,研究对抗样本对于提高机器学习模型的鲁棒性和安全性至关重要。

二、基于梯度的攻击方法详解

基于梯度的攻击方法是生成对抗样本最常用的方法之一。其核心思想是利用模型的梯度信息,找到使模型预测结果发生改变的方向,并在该方向上对输入进行微小扰动。

1. Fast Gradient Sign Method (FGSM)

FGSM是最早也是最简单的基于梯度的攻击方法。其公式如下:

x_adv = x + epsilon * sign(grad_x J(x, y, theta))

其中:

  • x_adv 是生成的对抗样本。
  • x 是原始输入样本。
  • epsilon 是扰动的大小,控制对抗样本与原始样本之间的差异程度。
  • J(x, y, theta) 是模型的损失函数,其中 x 是输入,y 是真实标签,theta 是模型的参数。
  • grad_x J(x, y, theta) 是损失函数关于输入的梯度。
  • sign() 是符号函数,返回梯度的符号(+1或-1)。

原理:

FGSM沿着梯度方向对输入进行扰动,目的是使损失函数最大化,从而导致模型预测错误。epsilon 控制了扰动的大小,通常设置为一个较小的数值,以保证对抗样本与原始样本的相似性。

优点:

  • 计算速度快,实现简单。

缺点:

  • 生成的对抗样本质量相对较低,容易被防御。
  • 容易陷入局部最优解。

2. Basic Iterative Method (BIM)

BIM是对FGSM的改进,它通过多次迭代的方式来生成对抗样本。其公式如下:

x_adv_0 = x
x_adv_{t+1} = clip(x_adv_t + alpha * sign(grad_x J(x_adv_t, y, theta)), x - epsilon, x + epsilon)

其中:

  • x_adv_0 是初始对抗样本,等于原始输入样本。
  • x_adv_t 是第t次迭代后的对抗样本。
  • alpha 是每次迭代的步长。
  • clip() 函数将对抗样本限制在 [x - epsilon, x + epsilon] 范围内,以保证对抗样本与原始样本的相似性。

原理:

BIM通过多次迭代,逐步调整输入,使得损失函数最大化。每次迭代都沿着梯度方向进行微小扰动,并使用 clip() 函数将扰动限制在一定范围内,防止对抗样本与原始样本差异过大。

优点:

  • 生成的对抗样本质量比FGSM更高。
  • 不容易陷入局部最优解。

缺点:

  • 计算速度比FGSM慢。
  • 仍然可能被防御。

3. Projected Gradient Descent (PGD)

PGD是目前最常用的基于梯度的攻击方法之一。它与BIM类似,也是一种迭代攻击方法,但使用了更强的投影操作,使其在对抗样本的生成过程中更加稳定。其公式如下:

x_adv_0 = x + uniform(-epsilon, epsilon)  # 随机初始化
x_adv_{t+1} = clip(proj(x_adv_t + alpha * sign(grad_x J(x_adv_t, y, theta)), x, epsilon), 0, 1)

其中:

  • uniform(-epsilon, epsilon) 表示在 [-epsilon, epsilon] 范围内随机初始化一个扰动。
  • proj(x_adv_t + alpha * sign(grad_x J(x_adv_t, y, theta)), x, epsilon) 是投影操作,将 x_adv_t + alpha * sign(grad_x J(x_adv_t, y, theta)) 投影到以 x 为中心,半径为 epsilon 的 Lp 球上。

原理:

PGD首先在 [-epsilon, epsilon] 范围内随机初始化一个扰动,然后通过多次迭代,逐步调整输入。每次迭代都沿着梯度方向进行微小扰动,并使用投影操作将扰动限制在 Lp 球内。投影操作可以防止对抗样本逃离可容忍的扰动范围,并提高对抗样本的鲁棒性。

优点:

  • 生成的对抗样本质量很高,具有很强的攻击性。
  • 对模型的鲁棒性要求较高,可以用来评估模型的安全性。

缺点:

  • 计算速度较慢。
  • 实现相对复杂。

Lp 球的投影操作

投影操作的目标是将一个向量 x' 投影到以 x 为中心,半径为 epsilon 的 Lp 球上。Lp 球的定义如下:

||x' - x||_p <= epsilon

其中,||.||_p 表示 Lp 范数。常见的 Lp 范数包括 L2 范数和 Linf 范数。

  • L2 范数投影:
def l2_proj(x_adv, x, epsilon):
  """
  L2范数投影
  """
  delta = x_adv - x
  n = np.linalg.norm(delta.ravel(), ord=2)
  if n <= epsilon:
    return x_adv
  else:
    return x + epsilon * delta / n
  • Linf 范数投影:
def linf_proj(x_adv, x, epsilon):
  """
  Linf范数投影
  """
  return np.clip(x_adv, x - epsilon, x + epsilon)

三、对抗样本的迁移性

原理:

对抗样本的迁移性是指在一个模型上生成的对抗样本,在另一个模型上也能成功欺骗。这种现象的产生原因比较复杂,目前主要有以下几种解释:

  • 模型之间的相似性: 如果两个模型在结构、参数或训练数据上比较相似,那么它们对输入数据的处理方式也可能比较相似,从而导致对抗样本的迁移。
  • 特征的鲁棒性: 一些对抗样本可能利用了输入数据中一些具有鲁棒性的特征,这些特征在不同的模型中都存在,从而导致对抗样本的迁移。
  • 决策边界的相似性: 不同的模型可能具有相似的决策边界,从而导致对抗样本的迁移。

影响因素:

对抗样本的迁移性受到多种因素的影响:

  • 模型结构: 不同的模型结构对抗样本的敏感程度不同,结构相似的模型迁移性可能更高。
  • 训练数据: 使用相同或相似的训练数据训练的模型,其对抗样本的迁移性可能更高。
  • 攻击方法: 不同的攻击方法生成的对抗样本,其迁移性也不同。例如,PGD攻击生成的对抗样本通常比FGSM攻击生成的对抗样本具有更高的迁移性。
  • 参数设置: 攻击参数,如epsilon和迭代次数,也会影响对抗样本的迁移性。

评估方法:

对抗样本迁移性的评估通常采用以下步骤:

  1. 在一个模型(源模型)上生成对抗样本。
  2. 将生成的对抗样本输入到另一个模型(目标模型)中。
  3. 评估目标模型对对抗样本的分类准确率。
  4. 计算迁移率:迁移率 = (对抗样本导致目标模型错误分类的数量) / (对抗样本的总数量)。

迁移率越高,表示对抗样本的迁移性越强。

四、代码实战

接下来,我们将使用PyTorch实现FGSM和PGD攻击,并演示对抗样本的迁移。

环境准备:

首先,需要安装以下Python库:

pip install torch torchvision numpy matplotlib

代码:

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import numpy as np
import matplotlib.pyplot as plt

# 定义一个简单的CNN模型
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(torch.relu(self.conv1(x)))
        x = self.pool(torch.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x

# 加载CIFAR-10数据集
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

# 训练模型
net = Net()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

for epoch in range(2):  # loop over the dataset multiple times

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data

        optimizer.zero_grad()

        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()
        if i % 2000 == 1999:    # print every 2000 mini-batches
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0

print('Finished Training')

# 保存模型
PATH = './cifar_net.pth'
torch.save(net.state_dict(), PATH)

# 定义FGSM攻击
def fgsm_attack(image, epsilon, data_grad):
    sign_data_grad = data_grad.sign()
    perturbed_image = image + epsilon * sign_data_grad
    perturbed_image = torch.clamp(perturbed_image, -1, 1) # 归一化到 [-1, 1]
    return perturbed_image

# 定义PGD攻击
def pgd_attack(model, image, label, epsilon, alpha, num_iter):
    image_adv = image.clone().detach().requires_grad_(True)
    for _ in range(num_iter):
        output = model(image_adv)
        loss = nn.CrossEntropyLoss()(output, label)
        loss.backward()
        data_grad = image_adv.grad.data
        # 使用投影梯度上升
        image_adv = image_adv + alpha * data_grad.sign()
        # 投影到epsilon球内
        image_adv = torch.min(torch.max(image_adv, image - epsilon), image + epsilon)
        # 限制在 [-1, 1] 范围内
        image_adv = image_adv.clamp(-1, 1)
        image_adv.requires_grad_(True) # 下一次迭代需要梯度
        image_adv.grad.zero_()

    return image_adv.detach() # 不需要梯度

# 测试FGSM攻击
def test_fgsm(model, test_loader, epsilon):
    correct = 0
    total = 0
    adv_examples = []
    for data, target in test_loader:
        data.requires_grad = True
        output = model(data)
        init_pred = output.max(1, keepdim=True)[1]
        if init_pred.item() != target.item():
            continue

        loss = nn.CrossEntropyLoss()(output, target)
        model.zero_grad()
        loss.backward()
        data_grad = data.grad.data
        perturbed_data = fgsm_attack(data, epsilon, data_grad)
        output = model(perturbed_data)
        final_pred = output.max(1, keepdim=True)[1]
        if final_pred.item() == target.item():
            correct += 1
        total += 1

        if len(adv_examples) < 5:
            adv_ex = perturbed_data.squeeze().detach().numpy()
            adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) )

    final_acc = correct/float(total)
    print("Epsilon: {}tTest Accuracy = {} / {} = {}".format(epsilon, correct, total, final_acc))
    return final_acc, adv_examples

# 测试PGD攻击
def test_pgd(model, test_loader, epsilon, alpha, num_iter):
    correct = 0
    total = 0
    adv_examples = []
    for data, target in test_loader:
        output = model(data)
        init_pred = output.max(1, keepdim=True)[1]
        if init_pred.item() != target.item():
            continue

        perturbed_data = pgd_attack(model, data, target, epsilon, alpha, num_iter)
        output = model(perturbed_data)
        final_pred = output.max(1, keepdim=True)[1]
        if final_pred.item() == target.item():
            correct += 1
        total += 1

        if len(adv_examples) < 5:
            adv_ex = perturbed_data.squeeze().detach().cpu().numpy()
            adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) )

    final_acc = correct/float(total)
    print("Epsilon: {}tAlpha: {}tIterations: {}tTest Accuracy = {} / {} = {}".format(epsilon, alpha, num_iter, correct, total, final_acc))
    return final_acc, adv_examples

# 加载模型
net = Net()
net.load_state_dict(torch.load(PATH))
net.eval()  # 设置为评估模式

# 测试原始模型的准确率
correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total))

# FGSM攻击参数
epsilon = 0.03

# PGD攻击参数
alpha = 0.007
num_iter = 10

# 执行FGSM攻击
accuracy_fgsm, examples_fgsm = test_fgsm(net, testloader, epsilon)

# 执行PGD攻击
accuracy_pgd, examples_pgd = test_pgd(net, testloader, epsilon, alpha, num_iter)

# 可视化对抗样本 (FGSM)
cnt = 0
plt.figure(figsize=(8,10))
for i in range(len(examples_fgsm)):
    cnt += 1
    plt.subplot(2,3,cnt)
    plt.xticks([], [])
    plt.yticks([], [])
    orig,adv,ex = examples_fgsm[i]
    plt.title("{} -> {}".format(classes[orig], classes[adv]))
    plt.imshow(np.transpose(ex, (1, 2, 0)))  # 调整维度顺序
plt.tight_layout()
plt.show()

# 可视化对抗样本 (PGD)
cnt = 0
plt.figure(figsize=(8,10))
for i in range(len(examples_pgd)):
    cnt += 1
    plt.subplot(2,3,cnt)
    plt.xticks([], [])
    plt.yticks([], [])
    orig,adv,ex = examples_pgd[i]
    plt.title("{} -> {}".format(classes[orig], classes[adv]))
    plt.imshow(np.transpose(ex, (1, 2, 0)))  # 调整维度顺序
plt.tight_layout()
plt.show()

# 为了演示迁移性,我们假设有一个黑盒模型(无法访问),
# 但我们可以使用对抗样本进行测试。
# 为了模拟,我们在这里使用相同的模型,但在实际场景中,
# 这会是一个完全不同的、无法访问的模型。

def test_transferability(model, test_loader, attack_method, epsilon, alpha=0.007, num_iter=10):
    """
    测试对抗样本在同一模型上的迁移性 (作为黑盒攻击的模拟).
    """
    correct = 0
    total = 0
    for data, target in test_loader:
        if attack_method == "fgsm":
            data.requires_grad = True
            output = model(data)
            loss = nn.CrossEntropyLoss()(output, target)
            model.zero_grad()
            loss.backward()
            data_grad = data.grad.data
            perturbed_data = fgsm_attack(data, epsilon, data_grad)
        elif attack_method == "pgd":
            perturbed_data = pgd_attack(model, data, target, epsilon, alpha, num_iter)
        else:
            raise ValueError("Invalid attack method. Choose 'fgsm' or 'pgd'.")

        output = model(perturbed_data)
        final_pred = output.max(1, keepdim=True)[1]
        if final_pred.item() == target.item():
            correct += 1
        total += 1

    final_acc = correct / float(total)
    print(f"Transferability Test ({attack_method}): Accuracy = {correct} / {total} = {final_acc}")
    return final_acc

# 测试FGSM对抗样本的迁移性(使用相同的模型作为黑盒模型的替代)
print("Testing Transferability (FGSM):")
transfer_accuracy_fgsm = test_transferability(net, testloader, "fgsm", epsilon)

# 测试PGD对抗样本的迁移性(使用相同的模型作为黑盒模型的替代)
print("Testing Transferability (PGD):")
transfer_accuracy_pgd = test_transferability(net, testloader, "pgd", epsilon, alpha, num_iter)

代码解释:

  1. 模型定义: 定义了一个简单的CNN模型用于CIFAR-10数据集的分类。
  2. 数据集加载: 加载CIFAR-10数据集,并进行预处理。
  3. 模型训练: 训练CNN模型。
  4. FGSM攻击实现: 实现了FGSM攻击函数。
  5. PGD攻击实现: 实现了PGD攻击函数。
  6. 攻击测试: 测试FGSM和PGD攻击的成功率,并可视化对抗样本。
  7. 迁移性测试: 使用相同的模型模拟黑盒模型,测试对抗样本的迁移性。

运行结果:

运行上述代码,可以看到原始模型在CIFAR-10数据集上的准确率较高,经过FGSM和PGD攻击后,模型的准确率显著下降,说明对抗样本可以成功欺骗模型。同时,可以看到对抗样本在模拟的黑盒模型上也能产生一定的攻击效果,说明对抗样本具有一定的迁移性。

五、防御策略简介

对抗样本攻击对机器学习模型的安全性构成了严重威胁,因此,研究防御对抗样本攻击的方法至关重要。以下是一些常见的防御策略:

  • 对抗训练: 对抗训练是一种通过将对抗样本添加到训练数据中,来提高模型鲁棒性的方法。具体来说,在每次迭代中,我们生成对抗样本,并将它们与原始样本一起输入到模型中进行训练。通过这种方式,模型可以学习到如何正确分类对抗样本,从而提高其鲁棒性。
  • 防御蒸馏: 防御蒸馏是一种通过训练一个更鲁棒的“学生”模型,来防御对抗样本攻击的方法。首先,我们使用一个原始模型(“教师”模型)来生成软标签(soft labels),然后使用这些软标签来训练“学生”模型。由于软标签包含了更多关于数据分布的信息,因此“学生”模型可以学习到更鲁棒的特征,从而提高其防御对抗样本攻击的能力。
  • 输入预处理: 输入预处理是一种通过对输入数据进行预处理,来消除或减少对抗扰动的方法。常见的输入预处理方法包括图像压缩、图像滤波和随机噪声添加等。这些方法可以有效地消除或减少对抗扰动,从而提高模型的鲁棒性。
  • 梯度掩码: 梯度掩码是一种通过对模型的梯度进行修改,来阻止攻击者利用梯度信息生成对抗样本的方法。常见的梯度掩码方法包括梯度裁剪和梯度平滑等。这些方法可以有效地阻止攻击者利用梯度信息生成对抗样本,从而提高模型的安全性。
  • 认证鲁棒性: 认证鲁棒性是一种通过为模型的预测结果提供正式的鲁棒性保证,来防御对抗样本攻击的方法。认证鲁棒性方法通常基于形式化验证技术,可以证明模型在一定范围内的扰动下,仍然能够输出正确的预测结果。

思考题

  1. 不同的epsilon值对FGSM和PGD攻击的成功率有什么影响?
  2. 不同的迭代次数对PGD攻击的成功率有什么影响?
  3. 为什么PGD攻击生成的对抗样本比FGSM攻击生成的对抗样本更具有迁移性?
  4. 对抗训练如何提高模型的鲁棒性?
  5. 研究对抗样本攻击和防御对于人工智能的未来发展有什么意义?

总结

今天我们讨论了基于梯度的对抗样本攻击,包括FGSM, BIM和PGD,还涉及到对抗样本的迁移性,以及一些防御策略。对抗样本的存在揭示了机器学习模型的脆弱性,因此,研究对抗样本对于提高机器学习模型的鲁棒性和安全性至关重要。希望今天的讲座能帮助大家对这个领域有更深入的了解。

请大家课后认真复习,完成思考题,并尝试使用不同的数据集和模型进行实验,加深对对抗样本攻击和防御的理解。下次课我们将讨论更高级的对抗样本攻击方法和防御策略。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注