首发于AI前线
欺骗神经网络:创建你自己的对抗样本

欺骗神经网络:创建你自己的对抗样本

本文由 「AI前线」原创,原文链接:欺骗神经网络:创建你自己的对抗样本
作者|Daniel Geng and Rishi Veerapaneni
译者|孙浩
编辑|Emily

AI 前线导读:通过神经网络进行暗杀。听起来疯狂吗? 也许有一天这样的事会以你想不到的方式发生。当然,神经网络可以训练无人机或操纵其他大规模杀伤性武器,即使是通过无害 (以当前的状况来看) 网络训练后去驾驶一辆汽车,它也有可能违背其主人的意图。这是因为神经网络非常容易受“对抗样本”的影响。


“对抗样本”是神经网络的输入,它会导致网络不正确的输出。这里将通过一个例子来更好的说明这种状况。我们可以从左边的熊猫图片开始,一些网络认为它有 57.7% 的可信度是“熊猫”。熊猫类别也是所有类别中可信度最高的类别,因此网络得出的结论是,图像中的物体是一只熊猫。但是,通过添加少量精心构造的噪音,我们可以得到一张在人类看来与之前完全相同的图像,但网络认为它有 99.3% 的可信度是“长臂猿”。这是件很疯狂的事情!

来自 Goodfellow 的《理解和控制对抗样本》

那么,如何通过对抗样本来进行暗杀呢? 想象一下,用一个对抗样本来代替一个停止标志,这是一个人类可以立即识别的信号,但是神经网络可能都不会注意这点。现在想象一下,在一个繁忙的十字路口放置一个对抗的停车标志。当自动驾驶汽车接近十字路口时,车载神经网络将无法看到停车标志,进而继续驶入迎面而来的车流中,将它的乘客带入死亡的边缘 (理论上)。

这可能只是一个令人费解的稍微有点耸人听闻的例子,但它说明了人们如何利用对抗样本来伤害他人,这里还有更多的例子。例如,iPhone X 的“人脸识别”解锁功能依赖于神经网络识别人脸,因此也容易受到对抗攻击。人们可以构建对抗图像来绕过人脸识别的安全特性。其他生物识别安全系统也将面临风险,非法或错误的内容可能使用对抗样本绕过基于神经网络的内容过滤器。这些对抗样本的存在意味着,结合深度学习模型的系统实际上有很高的安全风险。


你可以通过把它们看作是神经网络的视觉幻象来理解对抗样本。视觉错觉可以欺骗人类的大脑,同理,对抗样本也可以欺骗神经网络。

上面提到的关于熊猫的对抗样本是一个有针对性的例子。在图像中添加了少量的精心构造的噪声,导致神经网络对图像进行了错误的分类,尽管对于人类来说图像看起来与之前相同。也有一些无针对性的例子,它们试图找到任何能欺骗神经网络的输入。这个输入对于人类来说可能看起来像白噪音,但是因为我们没有被限制去寻找在人类看来相似的输入,问题就简单多了。

我们可以为任何神经网络找到对抗样本,即使是被称为具有“超人类”能力的最先进的模型,这有点令人不安。事实上,创建对抗样本很容易,我们将在本文中向你展示如何做到这一点。你需要的所有代码和相关依赖都可以在这个 GitHub 的 repo 中找到。

病毒模仿,完美展现对抗样本的有效性

MNIST 上的对抗样本这部分的代码可以在下面的 GitHub repo 中找到 (下载代码对理解本文并非必需):GitHub Repo

我们将尝试在 MNIST 的数据集上训练一个前馈神经网络。MNIST 是一个 28*28 像素的手写数字图像的数据集。他们的样式如下:

6 张并排的 MNIST 图片

在开始之前,我们首先应该导入我们需要的库。

import network.network as network
import network.mnist_loader as mnist_loader
import pickle
import matplotlib.pyplot as plt
import numpy as np

有 50000 张训练图像和 10000 张测试图像。我们首先加载预先训练的神经网络 (这是厚着脸皮从这篇很棒的关于神经网络的文章中拿来的):

with open('trained_network.pkl', 'rb') as f:  
    net = pickle.load(f)  

training_data, validation_data, test_data = mnist_loader.load_data_wrapper()

对于那些不熟悉 pickle 的人解释一下,这是 python 序列化数据 (如写入到磁盘) 的一种方式,本质上就是保存类和对象。使用 pickle.load() 可以打开已保存的网络层版本。

关于这个预先训练过的神经网络。它有 784 个输入神经元 (每个对应 28*28=784 像素),一层有 30 个隐藏的神经元和 10 个输出神经元 (每个数字对应一个)。它的所有活态都是 s 形的;它的输出是一个表示网络预测的单热矢量,它通过最小化均方差误差损失来训练。

为了证明神经网络是经过训练的我们可以写一个简单的测试小函数:

def predict(n):
    # Get the data from the test set
    x = test_data[n][0]

    # Get output of network and prediction
    activations = net.feedforward(x)
    prediction = np.argmax(activations)

    # Print the prediction of the network
    print('Network output: ')
    print(activations)

    print('Network prediction: ')
    print(prediction)

    print('Actual image: ')

    # Draw the image
    plt.imshow(x.reshape((28,28)), cmap='Greys')

该方法从测试集中选择样本,显示它,然后在神经网络中使用 net.feedforward(x) 方法运行它。以下是一些图片的输出:







左边是 MNIST 的图像。右侧是神经网络的 3 个输出,称为活态。输出信号越大,神经网络就越认为图像就是那个数字。

现在我们有一个训练有素的网络,但是我们如何去骗它呢? 我们将首先从一个简单的无针对性的方法开始,一旦我们得到预期目的,我们就可以使用一个很酷的技巧来修改这个方法,将其改为一种有针对性的方法。

无针对性攻击这个思路是生成一些图像,以便使神经网络产生特定的输出。例如,我们的目标标签 / 输出是:

也就是说,我们想要得到一个图像,它经过神经网络得到的输出结果是上面的向量。换句话说,找到一个图像,让神经网络认为该图像是一个 5(提醒一下,我们是零索引)。事实证明,我们可以把这当作一个优化问题,就像我们训练网络一样。我们将我们想要生成的图像称为 (一个 784 维向量,为了计算简便,我们把 28*28 像素的图像拉平了)。我们将定义一个代价函数:

是 L2 范式的平方。是我们从上面获得的目标标签。我们的图像经过神经网络的输出是。我们可以看到,如果我们的图像经过神经网络的输出结果非常接近我们的目标标签,那么相应的代价就会很低。如果网络的输出结果与我们的目标相去甚远,那么代价值就会很高。因此,找到一个向量使代价值 C 最小,神经网络预测的图像就是我们的目标标签。我们现在的问题是找到这个向量。请注意,这个问题与我们训练神经网络的方式非常相似,在这里我们定义了一个代价函数,然后选择权重和偏差 (又称参数) 来最小化代价函数。在生成对抗样本的情况下,我们不使用权重和偏差来最小化成本,而是保持权重和偏差不变 (本质上是保持整个网络常量),并选择一个输入使代价最小化。

为了做到这一点,我们将采用与训练神经网络相同的方法。也就是说,我们将使用梯度下降法!我们可以使用反向传播求出输入的代价函数的导数,,,然后使用梯度下降更新找到使代价最小化的最佳的。

反向传播通常用于计算权重和代价偏差的梯度,但通常而言反向传播只是一种算法,它可以有效地计算出计算图上的梯度 (这就是神经网络)。因此,它也可以用于计算神经网络输入的代价函数的梯度。

现在让我们看看生成对抗样本的代码:

def adversarial(net, n, steps, eta):
"""
net : network object
    neural network instance to use
n : integer
    our goal label (just an int, the function transforms it into a one-hot vector)
steps : integer
    number of steps for gradient descent
eta : integer
    step size for gradient descent
"""
# Set the goal output
goal = np.zeros((10, 1))
goal[n] = 1

# Create a random image to initialize gradient descent with
x = np.random.normal(.5, .3, (784, 1))

# Gradient descent on the input
for i in range(steps):
    # Calculate the derivative
    d = input_derivative(net,x,goal)

    # The GD update on x
    x -= eta * d

return x


首先我们创建,在代码中称为 goal。接下来,我们初始化作为一个随机的 784 维向量。有了这个向量,我们就可以开始使用梯度下降法了,这实际上只有两行代码。第一行 d = input_derivative(net,x,goal) 使用反向传播计算 (笔记中完整的代码是为需要的人编写的,但在这里我们就不做描述了,因为它实际上只是一大堆的数学知识。如果你想要获得对反向传播更好的描述 (input_derivative 所做的事情),可以到这个网站查阅 (顺便说一下,我们从那里得到了神经网络的实现))。梯度下降循环的第二行和最后一行,x- = eta * d 是对上 GD 的更新。我们沿着梯度方向与阶梯大小的 eta 方向移动。

以下是针对每个类的无针对性对抗样本以及神经网络的预测:

无针对性"O"



无针对性"3"



无针对性"5"



左边是无针对性的对抗测试 (一个 28 X 28 像素的图像)。当给出图像的时候,右边就绘制了网络的活态。

令人难以置信的是,神经网络认为有些图像是某些数字的可信度非常高。“3”和“5”就是很好的例子。对于大多数其他数字,神经网络表现出的活态就很低,这表明神经网络有些混乱了。效果看起来很不错!

在这一点上,可能会有一些事情让你感到困扰。如果我们想要做出一个对应于“5”的对抗样本那么我们想要找到一个,将其输入到神经网络后,输出的结果就会尽可能接近代表“5”的一维向量。然而,为什么梯度下降没有找到“5”的图像呢? 毕竟,神经网络几乎肯定会认为“5”的图像实际上是“5”(因为它确实是“5”)。关于为什么会发生这种情况的一个可能的理论是:

所有可用的 28×28 像素图像的间隔是巨大的。有种不同的 28×28 像素的黑白图像。下面作个对比,我们对可观测宇宙中原子数量的普遍估计数量是。如果宇宙中的每个原子都包含另一个宇宙那么我们就会有个原子。如果每个原子包含另一个也包含宇宙的原子,以此相互包含大约 23 次,那么我们几乎可以得到个原子。所以大体上可以看出,可分析图像的数量是惊人地庞大。

在所有这些照片中,只有极少的一部分能够被人类的眼睛识别成数字。面对很多图片时,神经网络能将大部分的图像识别成数字 (部分原因是因为我们的神经网络从没有在看起来不像数字的图像上训练过,所以,假如是一张看起来不像数字的图像,神经网络输出的结果几乎会是随机的)。所以当我们开始寻找一个对于神经网络来说像数字的图像时,我们更有可能找到一个看起来像噪音或静电干扰的图像,而不是仅仅通过概率找到一个对于人类来说像数字的图像。

针对性攻击

这些对抗样本很酷,但对人类来说,它们看起来就像噪音。如果我们能有一个看起来像某种东西的对抗样本,那不是更酷吗?也许一张“2”的图像神经网络会认为是“5”?事实证明这是可能的!我们只对原始代码做了非常小的修改。我们给我们最小化的代价函数加了一项。新代价函数如下:

就是我们所期望的对抗样本的样子 (是一个 784 维向量,和我们输入的维度相同)。所以我们现在要做的就是同时最小化这两项。左边的项我们已经见过了。一旦给定,最小化这项将会使神经网络输出。最小化第二项将试图促使我们的对抗图像尽可能的接近 (因为两个向量越接近标准值会越小),这恰恰就是我们想要的!另外,前面的λ是一个超参数,它决定了哪项更重要。和大多数的超参一样,我们在反复试验后发现.05 是λ一个非常恰当的值。

如果你了解岭回归你可能就会对上面的代价函数非常熟悉。事实上,我们可以将上面的代价函数解释为,在我们的模型中为我们的对抗样本放置了一个先验的例子。

如果你对正规化 Regularization 不了解,可以通过搜索引擎了解更多的信息。

实现最小化新代价函数的代码几乎与原始代码相同 (我们称这个函数为 sneaky_adversarial(),因为我们使用针对性攻击是偷偷摸摸的。命名一直是编程中最难的部分。)

def sneaky_adversarial(net, n, x_target, steps, eta, lam=.05):
    """
    net : network object
        neural network instance to use
    n : integer
        our goal label (just an int, the function transforms it into a one-hot vector)
    x_target : numpy vector
        our goal image for the adversarial example
    steps : integer
        number of steps for gradient descent
    eta : integer
        step size for gradient descent
    lam : float
        lambda, our regularization parameter. Default is .05
    """

    # Set the goal output
    goal = np.zeros((10, 1))
    goal[n] = 1

    # Create a random image to initialize gradient descent with
    x = np.random.normal(.5, .3, (784, 1))

    # Gradient descent on the input
    for i in range(steps):
        # Calculate the derivative
        d = input_derivative(net,x,goal)

        # The GD update on x, with an added penalty 
        # to the cost function
        # ONLY CHANGE IS RIGHT HERE!!!
        x -= eta * (d + lam * (x - x_target))

    return x

我们唯一改变的是梯度下降更新:

x -= eta
 (d + lam
 (x - x_target)) 。

eta 项是我们代价函数的新项。来看看新迭代的方法产出的结果:

针对性“7”[x_target=3]



针对性“9”[x_target=5]



针对性“2”[x_target=8]



左边是有针对性的对抗样本 (一个 28 X 28 像素的图像)。当给出图像的时候,右边就绘制了网络的活态。

需要注意的是,与非目标攻击一样,这里有两种行为。要么是神经网络被完全欺骗,而我们想要的数字的活态是非常高的 (例如“针对性 5”图像),要么是网络被困惑住了,所有的活态都很低 (例如“针对性 7”图像)。有趣的是,现在有更多的图像在前一个类别中,完全欺骗了神经网络,而不只是让它困惑。看起来,在梯度下降的过程中,那些被规范化为“数字类”的对抗样本更容易使收敛效果更好。

防止对手的攻击

太不可思议了! 我们刚刚创建了一些图像来欺骗神经网络。我们的下一个问题是,我们是否能够防止这些类型的攻击。如果你仔细观察原始图像和对抗样本,你会发现对抗样本中有一些灰色背景。

目标图像



原始图像



背景中有噪声的一个对抗样本。

我们可以先尝试一种比较简单的做法,使用二进制阈值来彻底地将背景色完全变白:

def binary_thresholding(n, m):
"""
n: int 0-9, the target number to match
m: index of example image to use (from the test set)
"""

# Generate adversarial example
x = sneaky_generate(n, m)

# Binarize image
x = (x > .5).astype(float)

print("With binary thresholding: ")

plt.imshow(x.reshape(28,28), cmap="Greys")
plt.show()

# Get binarized output and prediction
binary_activations = net.feedforward(x)
binary_prediction = np.argmax(net.feedforward(x))

print("Prediction with binary thresholding: ")
print(binary_prediction)

print("Network output: ")
print(binary_activations)

下面是结果:

对抗图片:



二进制化的图像



二进制阈值对 MNIST 对抗图像的影响。左边是图像,右边是神经网络的输出。

结果表明二进制的阈值起作用了!但是这种保护对抗攻击的方法不是很好。并不是所有的图片都有白色背景。例如,在这篇文章的开头,看看熊猫的图象。对图像进行二进制阈值的处理可能会消除噪声,但大程度的干扰了熊猫的图像。甚至可能到了连网络 (和人类) 都不能认出它是一只熊猫的地步。

对熊猫进行二进制的阈值处理会产生一个满是斑点的图像

我们可以尝试的另一种更普遍的方法是,同时在正确标注的对抗样本和原始训练测试集上训练一个新的神经网络。在 ipython notebook 中有实现代码 (注意,代码需要大约 15 分钟的时间来运行)。这样做之后,可以使所有对抗图像的测试集的准确率达到 94%,这样的效果非常好。但是,这种方法有它自己的局限性。在现实生活中,你不太可能知道你的攻击者是如何产生对抗样本的。

还有很多其他的方法可以保护我们免受对抗攻击,在这篇介绍性的文章中我们没有涉足,但是这个问题仍然是一个开放的研究课题,如果你感兴趣的话,有很多关于这个主题的论文。

黑盒攻击

对对抗样本的一个有趣但很重要的观察结果是,它们通常没有特定的模型或架构。为一个神经网络架构生成的对抗样本可以很好的移植到另一个架构上。换句话说,如果你想要欺骗一个模型,你可以根据它建立你自己的模型和对抗样本。那么,这些相同的对抗样本很可能也会欺骗其它的模型。

这具有重要的含义,因为这意味着我们有可能为一个完整的黑盒模型创建一个对抗样本,而我们对其内部机制事先不必了解。事实上,伯克利的一个团队成功地利用这种方法对一个商业人工智能分类系统发起了一次攻击。

结论

在我们迈向未来的过程中,我们的日常生活中包含了越来越多的神经网络和深度学习算法,我们必须非常小心并且记住这些模型很容易被愚弄。尽管神经网络在某种程度上从生物学上获得了启发,并且在各种各样的任务中具有接近 (甚至超过) 人类的能力,但对抗样本告诉我们,它们的运作方式与真实的生物是不一样的。正如我们所看到的那样,神经网络很容易就会失败,而且是灾难性的,这种状况对我们人类来说是完全陌生的。

我们不完全理解神经网络,所以用我们人类的直觉来描述神经网络是不明智的。例如,你经常会听到人们说“神经网络认为这张图像是猫的,因为它是橙色的。”问题是神经网络并不会像人类那样思考。它们本质上只是一系列的矩阵与一些相加的非线性数据的乘积。正如对抗样本所展现的那样,这些模型的输出是非常脆弱的。我们必须注意,不能把让机器实现人类品质的希望寄托于神经网络上,尽管他们有人类的能力。也就是说,我们不能将机器学习模式拟人化。



一个被训练用来检测哑铃的神经网络“相信”“哑铃”有时会与一个脱离身体的手臂配对。这显然不是我们所期望的。来自 Google Research。

总而言之,对抗样本的存在应该让我们保持谦逊。他们向我们表明,尽管我们取得了巨大的进步,但仍有许多我们不知道的东西。

英文原文链接:

ml.berkeley.edu/blog/20

更多干货内容,可关注AI前线,ID:ai-front,后台回复「AI」、「TF」、「大数据」可获得《AI前线》系列PDF迷你书和技能图谱。

编辑于 2018-02-26

文章被以下专栏收录