虽然人工智能技术已经能完成各种超出想象的事情,但是人工智能仍卸不掉人工智障的标签。即使强如ChatGPT也会犯一些非常弱智的错误。网上流传的关于人工智能的表情包,比如下面这个:
又或者下面这张:
这里问题现在还不能完全避免,这也不是我们今天的主题。我们今天的主题是如何找到这类可以让AI犯错的例子,或者说如何创造出这种例子。这种技术被称为Adversarial Attack(对抗攻击)。
攻击神经网络的方式有很多,基于不同的先验知识可以分为黑盒攻击
和白盒攻击
。其中黑盒攻击假定我们不知道网络结构、网络权重,只知道网络的输入输出。而白盒攻击假定我们对模型了如指掌,我们知道网络的结构、网络权重、网络输入输出等。
基于不同的目的,可以分为源/目标误分类
、针对性误分类
、误分类
、置信度降低
。其中误分类
攻击目的最简单,就是让模型分类错误,这也是本文要实现的一种攻击。
实现攻击的方式也是多种多样的,本文使用一种名为Fast Gradient Sign Attack(FGSA)
的攻击方式,这种方式利用梯度信息对输入进修改,来达到攻击的目的。
要理解FGSA我们需要知道模型的训练方式,我们可以把模型看做一个函数Fθ(X),其中θ是模型的参数,另外可以用损失函数L(X,Y;θ)来表示模型的好坏。在训练的过程中,可以让参数θ延梯度gθ的反方向更新:θ-=ηgθ。此时我们的目的是希望模型能得到一个比较低的损失值。
攻击模型利用的同样是梯度,在训练的时候我们希望模型得到好的结果,因此我们更新模型的参数,并且是沿着loss降低的方向更新。而攻击模型的目的是希望得到一个让模型出错的输入,因此我们更新输入信息,并且沿着loss上升的方向更新。
接下来我们用代码来实现FGSA攻击,这里使用白盒攻击。所以需要先实现一个网络,这里以手写数字为例。
白盒攻击的特点是我们知道网络的全部细节,因此我们自己实现一个网络,这个网络的所有细节我们都可以知道。代码如下:
import torch
import torch.nn as nn
from torch import optim
from torchvision.transforms import ToTensor
from torchvision.datasets import MNIST
from torch.utils.data.dataloader import DataLoader
class DigitalRecognition(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28 * 28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10)
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
# 加载数据
train_dataset = MNIST("./", True, ToTensor(), download=True)
train_loader = DataLoader(train_dataset, 64)
# 构建模型
model = DigitalRecognition()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.002)
# 训练
for epoch in range(10):
for data, target in train_loader:
# forward
pred = model(data)
loss = criterion(pred, target)
optimizer.zero_grad()
# backward
loss.backward()
optimizer.step()
print(f"epoch: {epoch}, loss: {loss.item()}")
torch.save(model.state_dict(), 'digital.pth')
这里为了方便,省略了测试相关代码,准确率的计算也省去了。代码运行完成后,可以得到一个digital.pth
文件。训练部分的代码我们可以是任意的,只需要得到一个模型即可。
得到模型后,我们就可以开始生成攻击数据了。我们编写一个函数,这个函数输入模型的输入X,以及loss对X的梯度,得到一个带有攻击性的输入,代码如下:
def fgsm_attack(image, epsilon, data_grad):
"""
image: 需要更新的输入
epsilon:更新程度,epsilon越大图像变化越大,攻击越有效
data_grad:输入对应的梯度,即loss对image的导数
"""
# 获取梯度的正负信息
sign_data_grad = data_grad.sign()
# 让输入添加梯度信息,即让输入添加能让loss增大的信息
perturbed_image = image + epsilon * sign_data_grad
# 把结果映射到0-1之间
perturbed_image = torch.clamp(perturbed_image, 0, 1)
return perturbed_image
其中image是我们已有的数据,epsilon是超参数,我们只需要得到image对应的梯度就可以实现攻击了。求image梯度的代码如下:
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import utils
from torchvision.transforms import ToTensor
from torchvision.datasets import MNIST
from torch.utils.data.dataloader import DataLoader
import matplotlib.pyplot as plt
def fgsm_attack(image, epsilon, data_grad):
pass
# 加载数据
train_dataset = MNIST("./", True, ToTensor(), download=True)
train_loader = DataLoader(train_dataset, 64)
model = DigitalRecognition()
model.load_state_dict(torch.load('digital.pth'))
model.eval()
for data, target in train_loader:
# 设置输入自动求导
data.requires_grad = True
output = model(data)
loss = F.nll_loss(output, target)
model.zero_grad()
loss.backward()
# loss对data的梯度
data_grad = data.grad.data
# 对data进行修改
perturbed_data = fgsm_attack(data, .15, data_grad)
# 对攻击数据预测
output = model(perturbed_data)
grid = utils.make_grid(perturbed_data, normalize=True)
with torch.no_grad():
grid = grid.cpu().numpy().transpose((1, 2, 0))
print(output.argmax(dim=1).cpu().numpy().reshape((8, 8)))
plt.imshow(grid)
plt.show()
break
在调用loss.backward()后,pytorch帮我们自动求了loss对data的导数,然后调用fgsm_attack,把输入和梯度传进去对数据添加攻击信息。下面是带有攻击性的输入图像:
下面的矩阵是各个图像对应的预测结果:
[[3 0 3 3 4 8 8 9]
[3 2 9 2 8 8 3 3]
[8 3 3 8 3 0 4 3]
[2 3 8 3 8 2 8 8]
[3 2 8 2 8 0 6 4]
[9 3 5 1 8 5 0 8]
[1 9 8 0 5 7 8 7]
[0 4 8 2 0 8 8 9]]
添加攻击信息后的图像对人来说很容易辨别,但是网络无法正确预测。从结果来看我们达到了攻击的目的。
阅读量:1207
点赞量:0
收藏量:0