TensorFlow 中的对抗图像

Adverserial images in TensorFlow

我正在阅读一篇文章,其中解释了如何欺骗神经网络来预测您想要的任何图像。我正在使用 mnist 数据集。

这篇文章提供了相对详细的演练,但写这篇文章的人正在使用 Caffe

无论如何,我的第一步是使用在 mnist 数据集上训练的 TensorFlow 创建逻辑回归函数。所以,如果我要 restore 逻辑回归模型,我可以用它来预测任何图像。例如,我将 number 7 喂给以下模型...

with tf.Session() as sess:  
    saver.restore(sess, "/tmp/model.ckpt")
    # number 7
    x_in = np.expand_dims(mnist.test.images[0], axis=0)
    classification = sess.run(tf.argmax(pred, 1), feed_dict={x:x_in})
    print(classification) 

>>>[7]

这会打印出正确的数字 [7]

现在文章解释说,为了攻破一个神经网络,我们需要计算神经网络的梯度。这是神经网络的导数。

文章指出,要计算梯度,我们首先需要选择一个预期的结果向其移动,并将输出概率列表设置为 0 处处为 0,预期结果为 1。 反向传播是一种计算梯度的算法。

然后Caffe中提供了关于如何计算梯度的代码...

def compute_gradient(image, intended_outcome):
    # Put the image into the network and make the prediction
    predict(image)
    # Get an empty set of probabilities
    probs = np.zeros_like(net.blobs['prob'].data)
    # Set the probability for our intended outcome to 1
    probs[0][intended_outcome] = 1
    # Do backpropagation to calculate the gradient for that outcome
    # and the image we put in
    gradient = net.backward(prob=probs)
    return gradient['data'].copy()

现在,我的问题是,我很难理解这个函数是如何通过仅向函数提供图像和概率来获得梯度的。因为我不完全理解这段代码,所以我很难将这段逻辑翻译成 TensorFlow.

我想我对 Caffe 框架的工作原理感到困惑,因为我以前从未 seen/used 它。如果有人可以逐步解释这个逻辑是如何工作的,那就太好了。

我已经知道 Backpropagation 的基础知识,所以您可能会认为我已经知道它是如何工作的。

这是文章本身的 link...https://codewords.recurse.com/issues/five/why-do-neural-networks-think-a-panda-is-a-vulture

我将向您展示如何在 TF 中进行生成对抗性图像的基础知识,要将其应用于已经学过的模型,您可能需要进行一些调整。

如果您想以交互方式尝试,代码块可以像 Jupyter notebook 中的块一样工作。如果您不使用笔记本,则需要添加 plt.show() 调用以显示绘图并删除 matplotlib 内联语句。代码基本上是 TF 文档中的简单 MNIST 教程,我会指出重要的区别。

第一块只是设置,没什么特别的...

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

# if you're not using jupyter notebooks then comment this out
%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np

from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf

获取 MNIST 数据(它有时会宕机,因此您可能需要手动从 web.archive.org 下载它并将其放入该目录)。我们没有像教程中那样使用一种热编码,因为现在 TF 有更好的函数来计算不再需要一种热编码的损失。

mnist = input_data.read_data_sets('/tmp/tensorflow/mnist/input_data')

在下一个区块中,我们正在做一些事情 "special"。输入图像张量被定义为一个变量,因为稍后我们要针对输入图像进行优化。通常你会在这里有一个占位符。它确实在这里限制了我们一点,因为我们需要一个确定的形状,所以我们一次只输入一个例子。这不是你想在生产中做的事情,但出于教学目的它很好(你可以用更多的代码来解决它)。标签是正常的占位符。

input_images = tf.get_variable("input_image", shape=[1,784], dtype=tf.float32)
input_labels = tf.placeholder(shape=[1], name='input_label', dtype=tf.int32)

我们的模型是标准的逻辑回归模型,就像教程中那样。我们只使用 softmax 来可视化结果,损失函数采用简单的 logits。

W = tf.get_variable("weights", shape=[784, 10], dtype=tf.float32, initializer=tf.random_normal_initializer())
b = tf.get_variable("biases", shape=[1, 10], dtype=tf.float32, initializer=tf.zeros_initializer())

logits = tf.matmul(input_images, W) + b
softmax = tf.nn.softmax(logits)

损失是标准的交叉熵。在训练步骤中需要注意的是,传入了一个明确的变量列表——我们已将输入图像定义为训练变量,但我们不想在训练逻辑回归时尝试优化图像,只是权重和偏差- 所以我们明确声明。

loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,labels=input_labels,name='xentropy')

mean_loss = tf.reduce_mean(loss)

train_step = tf.train.AdamOptimizer(learning_rate=0.1).minimize(mean_loss, var_list=[W,b])

开始会话...

sess = tf.Session()
sess.run(tf.global_variables_initializer())

由于批量大小为 1,训练速度比应有的慢。就像我说的,这不是你想在生产中做的事情,但这只是为了教授基础知识...

for step in range(10000):
    batch_xs, batch_ys = mnist.train.next_batch(1)
    loss_v, _ = sess.run([mean_loss, train_step], feed_dict={input_images: batch_xs, input_labels: batch_ys})

此时我们应该有一个足够好的模型来演示如何生成对抗图像。首先,我们得到一个带有标签“2”的图像,因为这些很容易,所以即使我们的次优分类器也应该正确(如果没有,运行 这个单元格再次;)这一步是随机的,所以我可以'不能保证它会起作用)。

我们正在将输入图像变量设置为该示例。

sample_label = -1
while sample_label != 2:
    sample_image, sample_label = mnist.test.next_batch(1)
    sample_label
plt.imshow(sample_image.reshape(28, 28),cmap='gray')

# assign image to var
sess.run(tf.assign(input_images, sample_image));
sess.run(softmax) # now using the variable as input, no feed dict

# should show something like
# array([[ 0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.]], dtype=float32)
# With the third entry being the highest by far.

现在我们要"break"分类。我们想改变图像,使其在网络眼中看起来更像另一个数字,而不改变网络本身。为此,代码看起来与我们之前的代码基本相同。我们定义了一个 "fake" 标签,与之前相同的损失(交叉熵),我们得到了一个优化器来最小化假损失,但是这次 var_list 只包含输入图像 - 所以我们赢了'改变逻辑回归权重:

fake_label = tf.placeholder(tf.int32, shape=[1])
fake_loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,labels=fake_label)
adversarial_step = tf.train.GradientDescentOptimizer(learning_rate=1e-3).minimize(fake_loss, var_list=[input_images])

下一个块旨在 运行 交互多次,同时您会看到图像和分数发生变化(此处向标签 8 移动):

sess.run(adversarial_step, feed_dict={fake_label:np.array([8])})
plt.imshow(sess.run(input_images).reshape(28,28),cmap='gray')
sess.run(softmax)

你第一次 运行 这个块,分数可能仍然会严重指向 2,但它会随着时间的推移而改变,在几 运行s 之后你应该看到类似下面的内容图像 - 请注意,图像仍然看起来像 2,背景中有一些噪声,但“2”的分数约为 3%,而“8”的分数超过 96%。

请注意,我们从未真正明确地计算过梯度——我们不需要,TF 优化器负责计算梯度并对变量应用更新。如果你想获得渐变,你可以使用 tf.gradients(fake_loss, input_images).

相同的模式适用于更复杂的模型,但您要做的是像往常一样训练您的模型 - 使用更大批量的占位符,或使用带有 TF 读取器的管道,以及何时进行您将使用输入图像变量作为输入重新创建网络的对抗图像。只要所有变量名称保持不变(如果您使用相同的函数来构建网络,它们应该保持不变),您就可以使用网络检查点进行恢复,然后应用此 post 中的步骤来获得对抗性图片。您可能需要尝试学习率等。