博客专栏

EEPW首页 > 博客 > 独家 | 对抗图像和攻击在Keras和TensorFlow上的实现(2)

独家 | 对抗图像和攻击在Keras和TensorFlow上的实现(2)

发布人:数据派THU 时间:2020-12-21 来源:工程师 发布文章

接下来,让我们解析下指令参数:

# construct the argument parser and parsethe arguments

ap = argparse.ArgumentParser()

ap.add_argument("-i", "--image",required=True,

help="pathto input image")

args = vars(ap.parse_args())

我们在这里只需要一个命令行参数,--image,即输入图像存放在硬盘上的路径。

如果你从来没有处理过命令行参数和argparse ,我建议你看一下这篇教程。

接下来让载入输入图像并进行预处理:

# load image fromdisk and make a clone for annotation

print("[INFO] loadingimage...")

image = cv2.imread(args["image"])

output = image.copy()

# preprocess the input image

output = imutils.resize(output, width=400)

preprocessedImage = preprocess_image(image)

通过调用cv2.imread来载入输入图片。在第4行对这张图片进行了复制,以便后期在输出结果上面画框并标注上预测结果类别标签。

我们调整下输出图片的尺寸,让它的宽变为400像素,这样可以适配我们的电脑屏幕。在这里同样使用preprocess_image函数将其处理为可用于ResNet进行分类的输入图片。

加上我们预处理好的图片,接着载入ResNet并对图片进行分类:

# load thepre-trained ResNet50 model

print("[INFO] loadingpre-trained ResNet50 model...")

model = ResNet50(weights="imagenet")

# makepredictions on the input image and parse the top-3 predictions

print("[INFO] makingpredictions...")

predictions =model.predict(preprocessedImage)

predictions = decode_predictions(predictions, top=3)[0]

第3行,载入ResNet和该模型用ImageNet数据集预训练好的权重。

第6和7行,针对预处理好的图片进行预测,然后再用 Keras/TensorFlow 中的decode_predictions辅助函数对图片进行解码。

现在让我们看看神经网络预测的Top 3 (置信度前三)类别以及所展示的类别标签:

# loop over thetop three predictions

for(i, (imagenetID, label,prob))inenumerate(predictions):

# print the ImageNet class label ID of the top prediction to our

# terminal (we'll need thislabel for our next script which will

# perform the actual adversarial attack)

if i == 0:

print("[INFO] {} => {}".format(label, get_class_idx(label)))

# display the prediction to our screen

print("[INFO] {}.{}: {:.2f}%".format(i + 1, label, prob * 100))

第2行开始循环Top-3预测结果。

如果这是第一个预测结果(即Top-1预测结果),在输出终端显示可读的标签,然后利用 get_class_idx函数找出该标签对应的ImageNet的整数值索引。

还可以在终端上展示Top-3的标签和对应的概率值。

最终一步就是将Top-1预测结果标注在输出图片中:

# draw thetop-most predicted label on the image along with the

# confidence score

text = "{}:{:.2f}%".format(predictions[0][1],

predictions[0][2] * 100)

cv2.putText(output, text, (3, 20),cv2.FONT_HERSHEY_SIMPLEX, 0.8,

(0, 255, 0), 2)

# show the output image

cv2.imshow("Output", output)

cv2.waitKey(0)

输出图像在终端显示,如果你单击OpenCV窗口或按下任意键,输出图像将会关闭。

非对抗图像的分类结果

现在可以用ResNet来执行基本的图像分类(非对抗攻击)了。

首先在“下载“页获取源代码和图像范例。

从这开始,打开一个终端并执行以下命令:

$ pythonpredict_normal.py --image pig.jpg

[INFO] loading image...

[INFO] loadingpre-trained ResNet50 model...

[INFO] making predictions...

[INFO] hog => 341

[INFO] 1. hog: 99.97%

[INFO] 2.wild_boar: 0.03%

[INFO] 3. piggy_bank: 0.00%

2.png

图五:预训练好的ResNet模型可以正确地将这张图片分类为“猪(hog)”。

在这里你可以看到我们将一张猪的图片进行了分类,置信度为99.97%。

另外,这里还加上了hog标签的ID(341)。我们将会在下一章用到这个标签ID,我们会针对这张猪的输入图片进行一次对抗攻击。

在Keras和TensorFlow上实现对抗图像和对抗攻击

接下来就要学习如何在Keras和TensorFlow上实现对抗图像和对抗攻击。

打开generate_basic_adversary.py文件,插入以下代码:

# import necessary packages

from tensorflow.keras.optimizers import Adam

from tensorflow.keras.applications import ResNet50

from tensorflow.keras.losses importSparseCategoricalCrossentropy

from tensorflow.keras.applications.resnet50 import decode_predictions

from tensorflow.keras.applications.resnet50 import preprocess_input

import tensorflow as tf

import numpy as np

import argparse

import cv2

在第2-10行中引入我们所需的Python包。你会注意到我们再次用到了ResNet50 架构,以及对应的preprocess_input函数(用于预处理/缩放输入图像)和 decode_predictions用于解码预测输出和显示可读的ImageNet标签。

SparseCategoricalCrossentropy 用于计算标签和预测值之间的分类交叉熵损失。利用稀疏版本的分类交叉熵,我们不需要像使用scikit-learn的LabelBinarizer或者Keras/TensorFlow的to_categorical功能一样用one-hot对类标签编码。

例如在predict_normal.py脚本中有preprocess_image 功能,我们在这个脚本上同样需要:

defpreprocess_image(image):

# swap color channels, resizethe input image, and add a batch

# dimension

image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)

image = cv2.resize(image, (224, 224))

image = np.expand_dims(image, axis=0)

# return the preprocessedimage

return image

除了省略了调用preprocess_input函数,这一段代码与上一段代码相同,当我们开始创建对抗图像时,你们马上就会明白为什么省去调用这一函数。

接下来,我们有一个简单的辅助程序,clip_eps:

defclip_eps(tensor, eps):

# clip the values of thetensor to a given range and return it

return tf.clip_by_value(tensor,clip_value_min=-eps,

clip_value_max=eps)

这个函数的目的就是接受一个输入张量tensor,然后在范围值 [-eps, eps]内对输入进行截取。

被截取后的tensor会被返回到调用函数。

接下来看看generate_adversaries 函数,这是对抗攻击的灵魂:

defgenerate_adversaries(model, baseImage,delta, classIdx, steps=50):

# iterate over the number ofsteps

for step inrange(0, steps):

# record our gradients

with tf.GradientTape()as tape:

# explicitly indicate thatour perturbation vector should

# be tracked for gradient updates

tape.watch(delta)

generate_adversaries 方法是整个脚本的核心。这个函数接收四个必需的参数,以及第五个可选参数:

model:ResNet50模型(如果你愿意,你可以换成其他预训练好的模型,例如VGG16,MobileNet等等);

baseImage:原本没有被干扰的输入图像,我们有意针对这张图像创建对抗攻击,导致model参数对它进行错误的分类。

delta:噪声向量,将会被加入到baseImage中,最终导致错误分类。我们将会用梯度下降均值来更新这个delta 向量。

classIdx:通过predict_normal.py脚本所获得的类别标签整数值索引。

steps:梯度下降执行的步数(默认为50步)。

第3行开始循环设定好的步数。

接下来用GradientTape来记录梯度。在tape上调用 .watch方法指出扰动向量是可以用来追踪更新的。

现在可以建造对抗图像了:

# add our perturbation vector to the base image and

# preprocess the resulting image

adversary = preprocess_input(baseImage + delta)

# run this newly constructed image tensor through our

# model and calculate theloss with respect to the

# *original* class index

predictions = model(adversary,training=False)

loss = -sccLoss(tf.convert_to_tensor([classIdx]),

predictions)

# check to see if we arelogging the loss value, and if

# so, display it to our terminal

if step % 5 == 0:

print("step: {},loss: {}...".format(step,

loss.numpy()))

# calculate the gradients ofloss with respect to the

# perturbation vector

gradients = tape.gradient(loss, delta)

# update the weights, clipthe perturbation vector, and

# update its value

optimizer.apply_gradients([(gradients, delta)])

delta.assign_add(clip_eps(delta, eps=EPS))

# return the perturbationvector

return delta

第3行将delta扰动向量加入至baseImage的方式来组建对抗图片,所得到的结果将放入ResNet50的preprocess_input函数中来进行比例缩放和结果对抗图像进行归一化。

接下来几行的意义是:

第7行用model参数导入的模型对新创建的对抗图像进行预测。

第8和9行针对原有的classIdx(通过运行predict_normal.py得到的top-1 ImageNet类别标签整数值索引)计算损失。

第12-14行表示每5步就显示一次损失值。

第17行,在with声明外根据扰动向量计算梯度损失。

接着,可以更新delta向量,截取掉超出 [-EPS,EPS] 范围外的值。

最终,把得到的扰动向量返回至调用函数——即最终的delta值,该值能让我们建立用来欺骗模型的对抗攻击。

在对抗脚本的核心实现后,接下来就是解析命令行参数:

# construct the argumentparser and parse the arguments

ap = argparse.ArgumentParser()

ap.add_argument("-i", "--input", required=True,

help="path tooriginal input image")

ap.add_argument("-o", "--output", required=True,

help="path tooutput adversarial image")

ap.add_argument("-c", "--class-idx", type=int,required=True,

help="ImageNetclass ID of the predicted label")

args = vars(ap.parse_args())

我们的对抗攻击Python脚本需要三个指令行参数:

--input: 输入图像的磁盘路径(例如pig.jpg);

--output: 在构建进攻后的对抗图像输出(例如adversarial.png);

--class-idex:ImageNet数据集中的类别标签整数值索引。我们可以通过执行在“非对抗图像的分类结果”章节中提到的predict_normal.py来获得这一索引。

接下来是几个变量的初始化,以及加载/预处理--input图像:

# define theepsilon and learning rate constants

EPS = 2 / 255.0

LR = 0.1

# load the inputimage from disk and preprocess it

print("[INFO] loadingimage...")

image = cv2.imread(args["input"])

image = preprocess_image(image)

第2行定义了用于在构建对抗图像时来裁剪tensor的epsilon值(EPS)。2 / 255.0 是EPS的一个用于对抗类刊物或教程的标准值和指导值(如果你想要了解更多的默认值,你可以参考这份指导)。

在第3行定义了学习速率。经验之谈,LR的初始值一般设为0.1,在创建你自己的对抗图像时可能需要调整这个值。

最后两行载入输入图像,利用preprocess_image辅助函数来对其进行预处理。

接下来,可以载入ResNet模型:

# load thepre-trained  ResNet50 model for running inference

print("[INFO] loadingpre-trained ResNet50 model...")

model = ResNet50(weights="imagenet")

# initializeoptimizer and loss function

optimizer = Adam(learning_rate=LR)

sccLoss = SparseCategoricalCrossentropy()

第3行载入在ImageNet数据集上训练好的ResNet50模型。

我们将会用到Adam优化器,以及稀疏的分类损失用于更新我们的扰动向量。

让我们来构建对抗图像:

# create a tensorbased off the input image and initialize the

# perturbation vector (we will update this vector via training)

baseImage = tf.constant(image,dtype=tf.float32)

delta = tf.Variable(tf.zeros_like(baseImage), trainable=True)

# generate the perturbation vector to create an adversarialexample

print("[INFO]generating perturbation...")

deltaUpdated = generate_adversaries(model, baseImage,delta,

args["class_idx"])

# create theadversarial example, swap color channels, and save the

# output image to disk

print("[INFO]creating adversarial example...")

adverImage = (baseImage +deltaUpdated).numpy().squeeze()

adverImage = np.clip(adverImage, 0, 255).astype("uint8")

adverImage = cv2.cvtColor(adverImage,cv2.COLOR_RGB2BGR)

cv2.imwrite(args["output"], adverImage)

第3行根据输入图像构建了一个tensor,第4行初始化扰动向量delta。

我们可以把ResNet50、输入图像、初始化后的扰动向量、及类标签整数值索引作为参数,用来调用generate_adversaries并更新delta向量。

在 generate_adversaries函数执行时,会一直更新delta扰动向量,生成最终的噪声向量deltaUpdated。

在倒数第4行,在baseImage上加入deltaUpdated 向量,就生成了最终的对抗图像(adverImage)。

然后,对生成的对抗图像进行以下三步后处理:

将超出[0,255] 范围的值裁剪掉;

将图片转化成一个无符号8-bit(unsigned 8-bit)整数(这样OpenCV才能对图片进行处理);

将通道顺序从RGB转换成BGR。

在经过这些处理步骤后,就可以把对抗图像写入到硬盘里了。

真正的问题来了,我们新创建的对抗图像能够欺骗我们的ResNet模型吗?

下一段代码就可以回答这一问题:

# run inferencewith this adversarial example, parse the results,

# and display the top-1 predicted result

print("[INFO]running inference on the adversarial example...")

preprocessedImage = preprocess_input(baseImage +deltaUpdated)

predictions =model.predict(preprocessedImage)

predictions = decode_predictions(predictions, top=3)[0]

label = predictions[0][1]

confidence = predictions[0][2] * 100

print("[INFO] label:{} confidence: {:.2f}%".format(label,

confidence))

# draw the top-most predicted label on the adversarial imagealong

# with theconfidence score

text = "{}: {:.2f}%".format(label, confidence)

cv2.putText(adverImage, text, (3, 20),cv2.FONT_HERSHEY_SIMPLEX, 0.5,

(0, 255, 0), 2)

# show the output image

cv2.imshow("Output", adverImage)

cv2.waitKey(0)

在第4行又一次创建了一个对抗图像,方式还是在原始输入图像中加入delta噪声向量,但这次我们利用ResNet的preprocess_input功能来处理。

生成的预处理图像进入到ResNet,然后会得到top-3预测结果并对他们进行解码(第5和6行)。

接着我们获取到top-1标签和对应的概率/置信度,并将这些值显示在终端上(第7-10行)。

最后一步就是把最高的预测值标在输出的对抗图像上,并展示在屏幕上。

对抗图像和攻击的结果

准备好见证一次对抗攻击了吗?

从这里开始,你就可以打开终端并执行下列代码了:

$ python generate_basic_adversary.py --inputpig.jpg --output adversarial.png --class-idx 341

[INFO] loading image...

[INFO] loading pre-trained ResNet50 model...

[INFO] generatingperturbation...

step: 0, loss:-0.0004124982515349984...

step: 5, loss:-0.0010656398953869939...

step: 10, loss:-0.005332294851541519...

step: 15, loss: -0.06327803432941437...

step: 20, loss: -0.7707189321517944...

step: 25, loss: -3.4659299850463867...

step: 30, loss: -7.515471935272217...

step: 35, loss: -13.503922462463379...

step: 40, loss: -16.118188858032227...

step: 45, loss: -16.118192672729492...

[INFO] creating adversarial example...

[INFO] running inference on theadversarial example...

[INFO] label: wombat confidence: 100.00%

1.png

图六:之前,这张输入图片被正确地分在了“猪(hog)”类别中,但现在因为对抗攻击而被分在了“袋熊(wombat)”类别里!

我们的输入图片 pig.jpg 之前被正确地分在了“猪(hog)”类别中,但现在它的标签却成为了“袋熊(wombat)”!

将原始图片和用generate_basic_adversary.py脚本生成的对抗图片放在一起进行对比:

图片

图七:在左边是原始图片,分类结果正确。右边将是对抗图片,被错误地分在了“袋熊(wombat)”类别中。而对于人类的眼睛来看完全分辨不出两张图片的有什么区别。

左边是最初猪的图像,在右边是输出的对抗图像,这张图像被错误的分在了“袋熊(wombat)”类别。

就像你看到的一样,这两张图片没有任何可感知的差别,我们人类的眼睛看不出任何区别,但对于ResNet来说确实完全不同的。

这很好,但我们无法清晰地掌控对抗图像被最终识别的类别标签。这会引起以下问题:

我们有可能掌控输入图片的最终类别标签吗?答案是肯定的,这会成为我下一篇教程的主题。

总结来说,对抗图像和对抗攻击真的是令人细思极恐。但如果等我们看到下一篇教程,就可以提前防御这种类型的进攻。稍后再详细说明下。

致谢

如果没有Goodfellow, Szegedy和其他深度学习的研究者的工作,这篇教程就无法完成。

另外,这篇教程所用到的实现代码灵感来自于TensorFlow官方实现的《Fast Gradient Signed Method》。我强烈建议你去看看其他的示例,每一段代码都比这篇教程中的在理论和数学上更加明确。

总结

在这篇教程中,你学到关于对抗攻击的知识,关于他们是怎样工作的,以及随着人工智能和深度神经网络与这个世界的关联越来越高,对抗攻击就会构成更大的威胁。

接着我们利用深度学习库Keras 和TensorFlow实现了基本的对抗攻击算法。

利用对抗攻击,我们可以蓄意扰乱一张输入图片,例如:

1、这张输入图片会被错误分类。

2、然而,肉眼看上去被扰乱的图片还是和之前一样。

利用这篇教程所使用的方法,我们并不能控制图片最终被判别的类别标签——我们所做的只是创造一个噪声向量,并将其嵌入到输入图像中,导致深度神经网络对其错误分类。

如果我们能够控制最终的类别标签会怎样呢?比如说,我们拿一张“狗”的图片,然后制造一次对抗攻击,让卷积神经网络认为这是一张“猫”的图片,这有没有可能呢?

答案是肯定的——我们会在下一篇教程中来谈论这一话题。

原文链接:

https://www.pyimagesearch.com/2020/10/19/adversarial-images-and-attacks-with-keras-and-tensorflow/

原文标题:

Adversarial images andattacks with Keras and TensorFlow

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。



关键词:

相关推荐

技术专区

关闭