介绍
对抗鲁棒性 (adversarial robustness):我们能否开发出对输入的(测试时)扰动鲁棒的分类器,而这些扰动是由意图欺骗分类器的敌人产生的。
准备工作
Python 3.7
- pytorch 1.0
- cvxpy 1.0
- numpy/scipy/PIL/etc
需要的背景知识
深入
首先,我们使用 PyTorch 中(预训练的)ResNet50 模型来分类猪的这张图片。
PyTorch 中正常的图像分类策略是首先使用torchvision.transforms
模块对图像进行变换(至近似 0 均值,单位方差)。然而,因为我们想要在原来的(非标准化的)图像空间制造扰动,我们会用一个稍微不同的方法,实际上在 PyTorch 层上构建变换,以便我们可以直接输入图像。首先,让我们加载这张图像并调整大小为 224x224,即大多数 ImageNet 图像用作输入的默认大小(因此是预训练分类器)。
from PIL import Image
+from torchvision import transforms
+import matplotlib.pyplot as plt
+
+# read the image, resize to 224 and convert to PyTorch Tensor
+pig_img = Image.open("pig.jpg")
+preprocess = transforms.Compose([
+ transforms.Resize(224),
+ transforms.ToTensor(),
+])
+pig_tensor = preprocess(pig_img)[None, :, :, :]
+
+# plot image (note that numpy uses HWC whereas Pytorch uses CHW, so we need to convert)
+plt.imshow(pig_tensor[0].numpy().transpose(1, 2, 0))
现在让我们在必要的变换后加载预训练的 ResNet50 模型并将它应用到图像上(这里奇怪的索引只是用于遵循 PyTorch 标准,模块的所有输入应该是batch_size x num_channels x height x weight
的形式)。
import torch
+import torch.nn as nn
+from torchvision.models import resnet50
+
+# simple Module to normalize an image
+class Normalize(nn.Module):
+ def __init__(self, mean, std):
+ super(Normalize, self).__init__()
+ self.mean = torch.Tensor(mean)
+ self.std = torch.Tensor(std)
+ def forward(self, x):
+ return (x - self.mean.type_as(x)[None, :, None, None]) / self.std.type_as(x)[None, :, None, None]
+
+# values are standard normalization for ImageNet images,
+# from https://github.com/pytorch/examples/blob/master/imagenet/main.py
+norm = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
+
+# load pre-trained ResNet50, and put into evaluation mode (necessary to e.g. turn off batchnorm)
+model = resnet50(pretrained=True)
+model.eval()
# form predictions
+pred = model(norm(pig_tensor))
pred
现在有一个 1000 维的向量,包含 1000 个 imagenet 类别的 logit 值(即如果你想要把它转换成一个概率向量,你应该对这个向量使用 softmax 运算)。为了找到最大似然的类,我们简单地取这个向量中最大值的索引,并且在 imagenet 类的列表中查找该索引来找到对应的标签。
import json
+with open("imagenet_class_index.json") as f:
+ imagenet_classes = {int(i): x[1] for i, x in json.load(f).items()}
+print(imagenet_classes[pred.max(dim=1)[1].item()])
hog
成功识别出该图像是猪。
一些介绍的符号
现在我们尝试欺骗这个分类器把这张图像识别为其他东西。为了解释这一过程,我们要介绍一些符号。具体来说,我们会定义模型,或假设函数, 为从输入空间(上例中是一个三维的张量)到输出空间的映射。输出空间是一个 维的向量,其中 是正被预测的类的数量。注意像我们上面的模型,输出对应于 logit 空间,所以这些实数可正可负。 向量表示所有定义这个模型的参数(即所有的卷积滤波器,全连接层权重矩阵,偏差等等), 参数是当我们训练一个神将网络的时候通常去优化的。最后,注意这个 恰好对应于上面 Python 代码的model
对象。
其次,我们定义一个损失函数 为一个从模型预测和真实标签到一个非负数的映射。这个损失函数的语义是