5.2.1 使用pytorch生成对抗样本
下面介绍在pytorch平台生成对抗样本的基本过程,示例代码为:
https://github.com/duoergun0729/adversarial_examples/blob/master/code/5-case2-pytorch.ipynb
在示例中,通过损失函数在反向传递过程中直接调整原始图像的值,直到满足最大迭代次数或者对抗样本预测值达到预期为止,如图5-6所示。
图5-6 使用pytorch生成对抗样本的流程
首先加载使用的库文件,其中被攻击的模型在torchvision中。
import torch
import torchvision
from torchvision import datasets, transforms
from torch.autograd import variable
import torch.nn as nn
from torchvision import models
import numpy as np
import cv2
定义使用的计算设备,默认使用的是cpu资源,如果有可用的gpu资源也将会自动使用。
#获取计算设备,默认是cpu
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
加载经典的熊猫图片并进行标准化处理。opencv默认加载图片的方式是bgr格式而不是rgb格式,需要手工进行转换。攻击的对象是alexnet,对应的预训练模型是基于imagenet 2012数据集进行训练的,因此对数据进行标准化时,不能简单地进行归一化处理,而是需要使用特定的均值mean和标准差std。另外需要强调的是,opencv默认保存图片时使用的是whc格式,即第三个维度是通道数据,比如本例中为[224,224,3],但是pytorch是cwh格式,因此需要转换成[3,224,224],这一过程是调用img.transpose完成的。
image_path="../picture/cropped_panda.jpg"
orig = cv2.imread(image_path)[..., ::-1]
orig = cv2.resize(orig, (224, 224))
img = orig.copy().astype(np.float32)
#标准化
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
img /= 255.0
img = (img - mean) / std
#whc转换成cwh
img = img.transpose(2, 0, 1)
#[3,224,224]-> [1,3,224,224]
img=np.expand_dims(img, axis=0)
img = variable(torch.from_numpy(img).to(device).float())
实例化alexnet模型,并设置为预测模式。预测模式和训练模式的主要区别是,dropout和bn层的行为不一样,比如预测模式时,dropout会让全部数据通过,训练模式时会按照设定值随机丢失一部分数据。对原始数据进行预测,预测的标签值为388,表示为熊猫。
#使用预测模式主要影响dropout和bn层的行为
model = models.alexnet(pretrained=true).to(device).eval()
label=np.argmax(model(img).data.cpu().numpy())
print("label={}".format(label))
label=388
设置仅输入数据可以计算梯度并被反向传递调整,模型的其他参数锁定不修改。在pytorch中通过设置requires_grad值即可完成上述设置。优化器torch.optim.adam只可调整输入数据img。
#图像数据梯度可以获取
img.requires_grad = true
#设置为不保存梯度值,自然也无法修改
for param in model.parameters():
param.requires_grad = false
optimizer = torch.optim.adam([img])
设置损失函数,在本例中要进行定向攻击,可以使用交叉熵torch.nn.crossentropyloss。
target=288
target=variable(torch.tensor([float(target)]).to(device).long())
loss_func = torch.nn.crossentropyloss()
下面进行迭代计算,优化的目标是让损失函数(或称目标函数)最小化。迭代过程中优化器不断调整输入数据img,这一过程需要手工调用loss.backward进行反向传递,通过optimizer.step调整img。整个迭代过程的退出条件是达到最大迭代次数或者img预测值达到预期。
for epoch in range(epochs):
# 梯度清零
optimizer.zero_grad()
# forward + backward
output = model(img)
loss = loss_func(output, target)
label=np.argmax(output.data.cpu().numpy())
print("epoch={} loss={} label={}".format(epoch,loss,label))
#如果定向攻击成功
if label == target:
break
loss.backward()
optimizer.step()
经过26轮迭代后,预测的label从388变成了288,即豹子。imagenet 2012可以识别1000种物品和动物,具体的对应列表可以参考https://blog.csdn.net/u010165147/article/details/72848497,需要指出的是label(标签)是从0开始编号,该文献是从1开始编号,因此label为288的物体,在文献中需要查找的id为289。
epoch=22 loss=3.264517307281494 label=388
epoch=23 loss=2.973933696746826 label=293
epoch=24 loss=2.72090482711792 label=290
epoch=25 loss=2.472418785095215 label=290
epoch=26 loss=2.244988441467285 label=288
最后我们将对抗样本数据还原成图像数据,并和原始数据进行对比。如图5-7所示,一共分为三个子图,分别表示原始图像、对抗样本和扰动量。
adv=img.data.cpu().numpy()[0]
#[3,224,244]->[224,224,3]
adv = adv.transpose(1, 2, 0)
#还原成正常图片像素范围并取整
adv = (adv * std) + mean
adv = adv * 255.0
adv = np.clip(adv, 0, 255).astype(np.uint8)
show_images_diff(orig,388,adv,target.data.cpu().numpy()[0])
图5-7 原始数据和对抗样本的对比示意图