手把手教你使用TensorFlow生成对抗样本 | 附源码

307 阅读7分钟
原文链接: click.aliyun.com
更多深度文章,请关注:yq.aliyun.com/cloud
如果说卷积神经网络是昔日影帝的话,那么生成对抗已然成为深度学习研究领域中一颗新晋的耀眼新星,它将彻底地改变我们认知世界的方式。对抗学习训练为指导人工智能完成复杂任务提供了一个全新的思路,生成对抗图片能够非常轻松的愚弄之前训练好的分类器,因此如何利用生成对抗图片提高系统的鲁棒性是一个很有研究的热点问题。 神经网络合成的 对抗 样本 很 容易 让人大吃一惊,这是因为 对输入 进行 小巧精心制作的扰动 就 可能导致神经网络以任意选择的方式 对 输入 进行 错误地分类 。鉴于对抗 样本 转移到物 质 世界 可以使其变得非常强大 , 因此 这是一个 值得关注 的安全问题。 比如说人脸识别,若一张对抗图像也被识别为真人的话,就会出现一些安全隐患及之后带来的巨大损失。对生成对抗图像感兴趣的读者可以关注一下最近的Kaggle 挑战赛 NIPS ,相关的信息可以参看博主的另外一篇:

《Kaggle 首席技术官发布—— (Kaggle)NIPS 2017 对抗学习挑战赛起步指南 》

在这篇文章中, 将手把手带领读者利 用TensorFlow 实现 一个简单的算法 来 合成对抗 样本 , 之后 使用这种技术 建立 一个鲁棒 的对抗性 例子 。 本文 是一个可执行的Jupyter notebook : 可以 下载 并自己实验 操作一下示例 !

建立

我们选择攻击在ImageNet数据集上 训练的Inception v3 网络。 首先 我们从TF-slim图像分类库 中 加载预先训练的网络。这部分不是 很有 趣,所以请 随意 跳过本部分 。
import tensorflow as tf
import tensorflow.contrib.slim as slim
import tensorflow.contrib.slim.nets as nets

tf.logging.set_verbosity(tf.logging.ERROR)
sess = tf.InteractiveSession()

首先,设置输入图像。使用tf.Variable 而不是 使用tf.placeholder,这是 因为 要确保 它是可训练的。当我们 需要 时,仍然可以 输入 它。

image = tf.Variable(tf.zeros((299, 299, 3)))

接下来,加载Inception v3 模型。

def inception(image, reuse):
    preprocessed = tf.multiply(tf.subtract(tf.expand_dims(image, 0), 0.5), 2.0)
    arg_scope = nets.inception.inception_v3_arg_scope(weight_decay=0.0)
    with slim.arg_scope(arg_scope):
        logits, _ = nets.inception.inception_v3(
            preprocessed, 1001, is_training=False, reuse=reuse)
        logits = logits[:,1:] # ignore background class
        probs = tf.nn.softmax(logits) # probabilities
    return logits, probs

logits, probs = inception(image, reuse=False)

接下来,加载预训练的 权重 。这个Inception v3 的 top-5 的 准 确率为93.9 %。

import tempfile
from urllib.request import urlretrieve
import tarfile
import os
data_dir = tempfile.mkdtemp()
inception_tarball, _ = urlretrieve(
    'http://download.tensorflow.org/models/inception_v3_2016_08_28.tar.gz')
tarfile.open(inception_tarball, 'r:gz').extractall(data_dir)
restore_vars = [
    var for var in tf.global_variables()
    if var.name.startswith('InceptionV3/')
]
saver = tf.train.Saver(restore_vars)
saver.restore(sess, os.path.join(data_dir, 'inception_v3.ckpt'))

接下来,编写一些代码来显示图像, 并 对它进行分类 及 显示分类结果。

import json
import matplotlib.pyplot as plt
imagenet_json, _ = urlretrieve(
    'http://www.anishathalye.com/media/2017/07/25/imagenet.json')
with open(imagenet_json) as f:
    imagenet_labels = json.load(f)
def classify(img, correct_class=None, target_class=None):
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 8))
    fig.sca(ax1)
    p = sess.run(probs, feed_dict={image: img})[0]
    ax1.imshow(img)
    fig.sca(ax1)
    
    topk = list(p.argsort()[-10:][::-1])
    topprobs = p[topk]
    barlist = ax2.bar(range(10), topprobs)
    if target_class in topk:
        barlist[topk.index(target_class)].set_color('r')
    if correct_class in topk:
        barlist[topk.index(correct_class)].set_color('g')
    plt.sca(ax2)
    plt.ylim([0, 1.1])
    plt.xticks(range(10),
               [imagenet_labels[i][:15] for i in topk],
               rotation='vertical')
    fig.subplots_adjust(bottom=0.2)
    plt.show()

示例图像

加载示例图像,并确保它 已 被正确分类。
import PIL
import numpy as np
img_path, _ = urlretrieve('http://www.anishathalye.com/media/2017/07/25/cat.jpg')
img_class = 281
img = PIL.Image.open(img_path)
big_dim = max(img.width, img.height)
wide = img.width > img.height
new_w = 299 if not wide else int(img.width * 299 / img.height)
new_h = 299 if wide else int(img.height * 299 / img.width)
img = img.resize((new_w, new_h)).crop((0, 0, 299, 299))
img = (np.asarray(img) / 255.0).astype(np.float32)
classify(img, correct_class=img_class)

5262cfb93234605d7b5a26286ad133ceda23228e 对抗样本 给定 一个 图像X ,神经网络输出标签上的概率分布 为P(y|X) 。当 手工 制作对抗输入时,我们想要找到一个X' , 使得logP(y'|X')被最大化为目标标签y' , 即 输入将被错误分类为目标类 。 通过约束一些 ℓ∞ 半径为ε 的箱 ,要求‖X- X'‖∞≤ε , 我们可以确保X' 与 原始X 看起来不太一样 。 在这个框架中, 对抗样本 是解决一个约束优化 的 问题,可以使用 反向传播 和 投影梯度下降来解决 ,基本上 也 是用 与训练网络本身相同的技术 。算法很简单: 首先将 对抗样本 初始化为X'←X 。然后,重复以下 过程直到收敛 :

1. X'←X^+α⋅logP(y'|X')

2. X'←clip(X' , X - ε , X+ε)

初始化

首先 从最简单的部分开始:编写一个TensorFlow op 进行 相应的 初始化。
x = tf.placeholder(tf.float32, (299, 299, 3))

x_hat = image # our trainable adversarial input
assign_op = tf.assign(x_hat, x)

梯度下降步骤

接下来,编写梯度下降步骤以最大化目标类的对数概率(或 最小化交叉熵 )。
learning_rate = tf.placeholder(tf.float32, ())
y_hat = tf.placeholder(tf.int32, ())

labels = tf.one_hot(y_hat, 1000)
loss = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=[labels])
optim_step = tf.train.GradientDescentOptimizer(
    learning_rate).minimize(loss, var_list=[x_hat])

投影步骤

最后,编写投影步骤,使 得对抗样本 在视觉上与原始图像相似。另外, 将其限定 为[0 ,1] 范围内 保持有效的图像。
epsilon = tf.placeholder(tf.float32, ())

below = x - epsilon
above = x + epsilon
projected = tf.clip_by_value(tf.clip_by_value(x_hat, below, above), 0, 1)
with tf.control_dependencies([projected]):
    project_step = tf.assign(x_hat, projected)

执行

最后,准备合成一个对抗 样本 。我们任意选择“ 鳄梨酱 ” ( imagenet class 924 )作为我们的目标类。

demo_epsilon = 2.0/255.0 # a really small perturbation
demo_lr = 1e-1
demo_steps = 100
demo_target = 924 # "guacamole"

# initialization step
sess.run(assign_op, feed_dict={x: img})

# projected gradient descent
for i in range(demo_steps):
    # gradient descent step
    _, loss_value = sess.run(
        [optim_step, loss],
        feed_dict={learning_rate: demo_lr, y_hat: demo_target})
    # project step
    sess.run(project_step, feed_dict={x: img, epsilon: demo_epsilon})
    if (i+1) % 10 == 0:
        print('step %d, loss=%g' % (i+1, loss_value))
    

adv = x_hat.eval() # retrieve the adversarial example

结果如下
step 10, loss=4.18923
step 20, loss=0.580237
step 30, loss=0.0322334
step 40, loss=0.0209522
step 50, loss=0.0159688
step 60, loss=0.0134457
step 70, loss=0.0117799
step 80, loss=0.0105757
step 90, loss=0.00962179
step 100, loss=0.00886694

这种对抗 图像 与原始图像在视觉上无法区分,没有可 见的人为加工 。但是它 会以很高的概率分类 为“ 鳄梨酱 ” 。

classify(adv, correct_class=img_class, target_class=demo_target)

a713c0142dc9c7fe63442402faca292c983fb78f

鲁棒的对抗样本

现在来看一个更高级的例子。遵循 我们的 方法来合成稳健的对抗样本, 以找到 对 猫图像的单一扰动,这在 某些 选择的 变换 分布下同时对抗 , 可以选择任何可微分变换的分布 ; 在这篇文章中,我们将 合成 一个单一的对抗输入, 设置θ∈[- π/4 , π/4] ,这 对旋转是鲁棒的 。

在 继续下面的工作 之前,检查一下 之前 的例子 是否能对抗 旋转,比如说 设置角度为θ=π/8

ex_angle = np.pi/8

angle = tf.placeholder(tf.float32, ())
rotated_image = tf.contrib.image.rotate(image, angle)
rotated_example = rotated_image.eval(feed_dict={image: adv, angle: ex_angle})
classify(rotated_example, correct_class=img_class, target_class=demo_target)

25ab4cd7164975cddbd462feb80252f2b2c3b5ad

看起来 我们之前生成 的对抗 样本 不是旋转不变的!

那么,如何使 得 一个对抗 样本 对变换的分布是 鲁棒 的 呢 ?给定一些 变 换分布T , 我们 可以最大化 Et~TlogP(y'|t(X')) , 约束条件为‖X- X'‖∞≤ε 。可以通过投影梯度下降 法 来解决这个优化问题,注意到 ∇Et~TlogP(y'|t(X'))与Et~TlogP(y'|t(X' ))相等 , 并在每个梯度下降步骤中来 逼近 样本 。

可以使用一个技巧 让TensorFlow 为我们做到这一点 , 而不是 通过 手动实现梯度采样 得到 : 我们 可以模拟基于采样的梯度下降,作为随机分类器的集合中的梯度下降,随机分类器从分布中随机抽取并在分类之前 变 换输入。

num_samples = 10
average_loss = 0
for i in range(num_samples):
    rotated = tf.contrib.image.rotate(
        image, tf.random_uniform((), minval=-np.pi/4, maxval=np.pi/4))
    rotated_logits, _ = inception(rotated, reuse=True)
    average_loss += tf.nn.softmax_cross_entropy_with_logits(
        logits=rotated_logits, labels=labels) / num_samples

我们可以重复使用assign_op 和project_step ,但 为了 这个新目标 , 必须写一个新的optim_step 。

最后,我们准备运行PGD 来产生对抗输入。 和 前面的例子 一样 ,选择“ 鳄梨酱 ” 作为我们的目标类。

demo_epsilon = 8.0/255.0 # still a pretty small perturbation
demo_lr = 2e-1
demo_steps = 300
demo_target = 924 # "guacamole"

# initialization step
sess.run(assign_op, feed_dict={x: img})

# projected gradient descent
for i in range(demo_steps):
    # gradient descent step
    _, loss_value = sess.run(
        [optim_step, average_loss],
        feed_dict={learning_rate: demo_lr, y_hat: demo_target})
    # project step
    sess.run(project_step, feed_dict={x: img, epsilon: demo_epsilon})
    if (i+1) % 50 == 0:
        print('step %d, loss=%g' % (i+1, loss_value))
    

adv_robust = x_hat.eval() # retrieve the adversarial example

结果如下

step 50, loss=0.0804289
step 100, loss=0.0270499
step 150, loss=0.00771527
step 200, loss=0.00350717
step 250, loss=0.00656128
step 300, loss=0.00226182

这种对抗图像被高度信任地归类为“鳄梨酱”,即使是旋转的情况下!

rotated_example = rotated_image.eval(feed_dict={image: adv_robust, angle: ex_angle})
classify(rotated_example, correct_class=img_class, target_class=demo_target)

89955316941bb5671df76e016a20bddb4c4423d9

评估

下面 来看一下在整个角度范围内产生的 鲁棒 对抗 样本 的旋转不变性,看P(y'|x') 在θ∈[- π/4 , π/4] 。

thetas = np.linspace(-np.pi/4, np.pi/4, 301)

p_naive = []
p_robust = []
for theta in thetas:
    rotated = rotated_image.eval(feed_dict={image: adv_robust, angle: theta})
    p_robust.append(probs.eval(feed_dict={image: rotated})[0][demo_target])
    
    rotated = rotated_image.eval(feed_dict={image: adv, angle: theta})
    p_naive.append(probs.eval(feed_dict={image: rotated})[0][demo_target])

robust_line, = plt.plot(thetas, p_robust, color='b', linewidth=2, label='robust')
naive_line, = plt.plot(thetas, p_naive, color='r', linewidth=2, label='naive')
plt.ylim([0, 1.05])
plt.xlabel('rotation angle')
plt.ylabel('target class probability')
plt.legend(handles=[robust_line, naive_line], loc='lower right')
plt.show()

f1b92341a1f20106d505ffa02ed3a6f2e6c7f458

从图中 蓝色曲线 可以看到, 生成的对抗样本是 超级有效的。

作者信息

Anish Athalye :MIT 在读博士生,对分布式系统、系统安全及人工智能感兴趣。

e2e399c8957ca9bfc3d7b1a44937f72c675edad0

学术 : www.anish.io/

Email :aathalye@mit.edu

Github: github.com/anishathaly…

本文由北邮@爱可可-爱生活 老师推荐, 阿里云云栖社区 组织翻译。

文章原标题《A Step-by-Step Guide to Synthesizing Adversarial Examples 》,作者: Anish Athalye , 译者:海棠,审阅:

文章为简译,更为详细的内容,请查看原文