深度 | 详解首个系统性测试现实深度学习系统的白箱框架DeepXplore

comwayLi 2017-06-19

作者:Yoav Hollander

五月份,来自哥伦比亚大学和理海大学的几位研究者的论文《DeepXplore: Automated Whitebox Testing of Deep Learning Systems》提出了一种深度学习系统的自动白箱测试方法 DeepXplore,参阅机器之心的报道《 学界 | 新研究提出 DeepXplore:首个系统性测试现实深度学习系统的白箱框架》。近日,Yoav Hollander 在一篇博客文章中对这项研究进行了深度解读,同时还对「对基于机器学习的系统进行验证」这一主题进行了更广泛的探讨。机器之心对本文进行了编译介绍。

论文《DeepXplore: Automated Whitebox Testing of Deep Learning Systems》描述了一种新的且(我认为)相当重要的用于验证基于机器学习的系统的方法。而且其或多或少打破了机器学习世界和类似 CDV(覆盖驱动验证)的、动态验证的世界之间的界限。

在我阅读这篇论文时,我一直在对自己说:「恩,很不错嘛,不过,他们好像还忽略了什么。」所以我联系了一下作者,事实证明他们很清楚这个「什么」是什么(而且他们也正计划在未来的研究中解决其中的一些问题)。我将在这篇文章中引述他们的一些答案。

关于 DeepXplore

这篇论文描述了一种用于验证基于深度神经网络(DNN)的系统的方法,下面部分来自原论文的摘要:

我们提出了 DeepXplore:这是首个系统性测试真实深度学习系统的白箱框架(whitebox framework)。该框架解决两个难题:(1)生成能够激发一个深度学习系统逻辑的不同部分的输入(input);(2)在不涉及手动操作的情况下,识别深度学习系统的不正确行为。首先,我们引入了神经元覆盖(neuron coverage)来评估深度学习系统的不同部分(这些部分由用于测试的输入训练而成)。然后,利用多个有类似功能的深度学习系统作为交叉引证,因此避免了对错误行为的手动检查。我们表明了可以如何将在实现深度学习算法高神经元覆盖率时找到触发不同行为的输入的过程表示为一个联合优化问题,然后使用基于梯度的优化技术有效地解决。

DeepXplore 有效地在当前最先进的深度学习模型上发现了数千个不同的不正确的极端案例行为(corner-case behaviors,比如自动驾驶汽车撞向护栏、恶意软件伪装成好软件),这些模型是在五个流行的数据集上训练的,其中包括来自 Udacity 在山景城收集的驾驶数据和 ImageNet 数据。

其中有 4 个主要思想我比较喜欢:

  • 使用了神经元覆盖(neuron coverage);

  • 通过比较多个相似的 DNN 的输出来检查 DNN 的输出;

  • 自动「轻微推动」运行过程向目标移动,即:(2) 中定义的检查可以找到不一致的地方,同时 (1) 中定义的覆盖实现最大化,同时还考虑到了约束条件;

  • 使用了有效的基于梯度的联合优化而做到了 (3)。

但对于这其中的每一个,我都有问题。这不是坏事:实际上这篇论文带来的很多问题是一个很大的加分——我已经在期待后续的研究了。我的问题主要和 DeepXplore 的「仅有白箱(whitebox-only)」的本质有关(当然,这也是一个优点)。

让我按顺序逐一阐述一下这四个主要思想,讨论一下我喜欢它们的理由以及相关的问题:

1. 使用神经元覆盖

对于一组 DNN 运行,它们的覆盖指标是「在这些运行过程中有多大比例的神经元(DNN 节点)至少激活了一次」。其核心思想是,对于一个给定的 DNN 输入,每个神经元要么是激活的(即超过了阈值),要么就保持为 0。正如原论文说的一样:

最近的研究已经表明 DNN 中的每个神经元往往需要负责提取其输入的一个特定的特征……这个发现直观地解释了为什么神经元覆盖是一个用于 DNN 测试全面性(testing comprehensiveness)的好指标

注意,这些节点对应的特征并不一定能够用我们的语言直观描述(比如:一个有两只眼睛的物体)。但 DNN 训练的优化过程通常确实会使它们对应于一些「可复用的特征」,即让 DNN 在许多不同输入上都有用的东西。

研究者之前也尝试过自动使神经元活跃,比如在论文《Understanding Neural Networks Through Deep Visualization》为了可视化神经元的工作,但使用神经元覆盖(neuron coverage)来进行验证应该是一个新的好想法。

可能的问题:

但是注意,神经元覆盖(类似于 软件验证中的代码覆盖(code coverage))是真正的实现覆盖(implementation coverage)。而实现覆盖就我们所知是不够的,主要是因为其对因疏忽造成的漏洞没有助益:如果你接收/传输 软件的模块遗忘(SW module forgot)来实现传输,或遗忘有关「接收同时传输」的问题,然后你可以实现 100% 的代码覆盖并达到完美,直到有人实际写出了一个传输测试(或用户自己尝试传输)。

参见覆盖的多个讨论(实现,功能等等):https://blog.foretellix.com/2016/12/23/verification-coverage-and-maximization-the-big-picture/

这对于 DNN 也是一样:如果你忘记在「靠右行驶」、谨慎转弯或巴士上涂鸦的人上训练的驾驶 DNN,那么你可能会在一个漏洞很多的系统上实现 100% 的覆盖。

作者回复:

我们完全同意你的观点。完全的神经元覆盖(full neuron coverage)(就像代码覆盖)无法保证能找到所有可能的漏洞。话虽如此,我们也在思考扩展神经元覆盖的定义,使之能够包含不同类型的覆盖(比如在传统软件中就像路径覆盖(path coverage)一样的神经元路径覆盖(neuron path coverage))。

我认为扩展神经元覆盖能或多或少带来一些帮助。假设(这里简化了很多)我们的驾驶 DNN 有一个用于检测「狗在公路上」的神经元和另一个用于检测「猫在公路上」的神经元。那么,除了分别单个覆盖它们,我们还想要覆盖它们的反例(「没有狗」)和组合(「有狗有猫」、「有猫且没狗」)以及序列(「有猫然后有狗」——这就是上面提到的神经路径覆盖)。

但是,这只是实现覆盖,下面可看到更多评论。

通过比较其它实现来检查 DNN

他们检查行为的方式是比较多个、或多或少不同的基于 DNN 的实现,来看它们是否吻合。这是 软件「差分测试(differential testing)」的一种扩展,其(对于你们这些来自硬件验证的人来说)基本上和原来的「通过一个参考模型进行检查」差不多。

潜在问题:

通过参考模型检查通常是假设该参考模型是(几乎)完美的,即是可以被信任的。当然,事实并非如此,所以是有潜在问题的。

比如说,这能找到遗漏的 bug 吗?只能考虑我们在训练过程中考虑过的一些 DNN 案例,而不能考虑其它的。但我很怀疑这是否足够。该论文说道:

如果所有被测试的 DNN 都会犯同样的错,那么 DeepXplore 不能生成对应的测试案例。但是,我们发现这在实际中并不是一个显著问题,因为大多数 DNN 是独立构建和训练的,它们都会犯同样错误的概率比较低。

但这是假设 (a) 你在现实和充满竞争的市场中能够获取许多不同的实现,和 (b) 至少有一位作者(比如 Tsunami)想过这并不一定现实。

作者回复:

这确实是差分测试的一个限制。以一种独立的方式测试一个模型的一种方法是使用对抗 DNN 测试技术,这种方法目前仅允许实现人眼不可见的轻微扰动。你也许可以扩展对抗训练来使用范围广泛的现实约束。比如说,通过改变光的效果。但是,这种方法的主要问题是缺乏数据标签。我们很难确定一个 DNN 模型是否做出了正确的分类。理论上,任何扰动都可以任意改变图像的真实标签。

实际上,无需一个「独立的」检查事物的方式,这可能是一个相当好的方式。但其 CDV 选项(这确实意味着大量的工作)说需要人类来写出单独的/人工的检查/断言集。

比如说,如果我们在谈论的是 Udacity 驾驶案例,你可以写一些「不能朝其它汽车行驶」之类的检查……这些都是概率检查,所以这是一种微不足道的工作,但做自动驾驶汽车的人可能会做。另外随便一提,安全问题永远不是只靠机器学习就可以的。

这些人也会使用功能覆盖来增强实现覆盖,比如「我们是不是走过了一个左转弯?一次没有保护的左转弯?一次没有汽车和我们同一条道路的左转弯?一次雨天的左转弯?」正如我前面提到的那样。

差分检查还有另一个潜在的问题:假设其中有比较大的重叠,但其中每个都会考虑一些其它不会考虑的案例。然后运行 DeepXplore 将会将这些案例标记为「不一致(inconsistent)」,然后你必须检查完它们全部,然后找到其中正确的部分(即:这是我的实现中的 bug 吗?还是只是其他人遗忘的一个?)

另外,还经常有「没有一个正确答案」的情况:汽车可以左转也可以右转,或者决策边界可以有所不同(「我可以启动了吗?」)。而当不一致性仅仅意味着「这里可能有一个错误时」,我们可能会需要一个长的、人工的过程。

推动运行检查错误,同时最大化覆盖并服从约束

这可能是最有意思的部分了。正如他们描述的一样:

最后,我们表明尽可能生成测试输入(该输入需要在最大化深度学习系统的神经元覆盖的同时揭示出尽可能多的有区别的行为(即多个相似深度学习系统之间的差异))的问题可以如何被形式化成一个联合优化的问题,而且其可以在大规模真实世界深度学习分类器上得到有效的解决。和传统的程序不同,由深度学习系统所使用的大多数流行的深度神经网络(DNN)所逼近的函数是可微分的。因此,在给出了对应模型的白箱权限的情况下,这些输入对应的梯度可以得到准确的计算。

我真的很喜欢这一点:这既在同一个工具中包含了我们所说的覆盖最大化(coverage maximization)和漏洞寻找(bug-hunting)。他们也尝试在一些约束条件下做到这一点:比如说,在视觉任务中,他们仅仅使图像更暗或覆盖图像的小边角,而在检测文件中的恶意软件时,他们坚持给文件的结构加一些约束条件。

潜在问题:

这里有一个大问题,我将其称为「约束问题(constraint problem)」:因为,可允许的约束不够灵活,所以,得到的输入只能成为「用于训练的输入,加上一些小改动」。

人们很希望能够明确灵活的限制(flexible constraint)是什么,因为这些限制让简单的修改和巨大的改动都成为了可能(就像在左边开车或在公交车上画画一样),同时它们也可以完全避免不现实的改变(比如漂浮在空气中的汽车)。

这一切可能吗?听起来的确很难,但是恶意软件的例子向我们展示了人们可以在一定程度上自定义约束。

在大部分验证中我最喜欢的结论是一些 CDV(覆盖驱动验证)的变体。因此,我期望为验证所有系统而创造一种随机验证的、基于模型的输入,这其中包括了基于 DNN 的系统。使用 DeepXplore 的人们只讨论获取现存输入与将这些输入进行变异的方法。寻找一些结合的方法应该是很有趣的。

作者回复:

你是对的,我们在图像设置中提出的约束仍然不够灵活。之所以考虑它们是因为它们可以被梯度有效的引导。实际上,还有很多其它的数据增强技术:对图像而言,我们可以旋转、翻转它们,甚至我们可以做一些语义转换(比如:将 BMW X6 改变成 BMW X1)。然而,我们不能使用梯度来对这些转换(transformation)进行有效地计算——我们只能随机用这些约束来转换这些图像,希望部分转换可以引发模型间的不同状态。指明这些约束(它们可被梯度有效引导)的类别、和性质是非常有趣的。

……提出数据(如图像)的现实模型/约束本身就是一个困难的问题。在 DeepXplore 里,我们以现实的输入作为起点,希望变异的样本是有效的,因此它们可以出现在现实世界。我们也试着从随机样本开始,后来发现 DeepXplore 能够发现不同诱导(difference-inducing)的输入,但是它们看起来不是一个不真实的图像。

在机器学习的世界里,一个执行类似任务的热门方法是使用生成对抗网络(GAN):给定一个随机向量,它可以学习生成真实的输入,这些输入和真正的输入是不可分辨的(例如,https://github.com/jayleicn/animeGAN)。我们也正在使用这个技术来研究如何生成输入。

我曾在博客上写了关于「用于验证的 GAN」的文章,我同意 GAN 可以帮助生成某种意义上直接的、真实的验证输入。但是我还是怀疑,认为其中的「新颖性」是会受到限制的,所以它还不够好。

我真正想看到的是有一个 pipeline,在它里面输入的生成是从一些强行的、高阶描述中生成的。例如,考虑如下(完全是推测)内容:假设我们有一个从文本到图像的神经网络,你告诉它,现在有这样一种图文联系:「一辆车从左边过来」,然后它你给出与你描述相关的图像。我描述了(在上面提到的帖子中)人们如何用一种 GAN 和一种 RNN 来处理这类事情的方法,但是很有可能它不是唯一的。

现在你将得到的图像(或图像的序列)输入 Udacity 的驾驶员 DNN,你会得到一个「必要的转向角度(required-steering-angle)」输出。现在,与其(比如说)对图片添加黑色的干扰,不如对文本输入进行干扰。而且你也可以将逻辑约束施加给文本。

如果这种方法奏效(很有可能),或许你还可以通过将另一个 DNN(它结合了多个这种文本到图像的 DNN 的输出)应用于复合场景(「一辆从左边开来的车、站在右边的狗、以及刚刚变绿的信号灯这三者的结合」),从而在输入中实现更高的复杂度和多样性。

这个观点可能不会如其所述那样有效——我只是试着概述一种更接近「真实 CDV(覆盖驱动验证)」的方向。

在 DNN 上进行有效、基于梯度的联合优化

在一篇早先的帖子里我表示我希望机器学习可以更多地用于对智能的自动化系统的验证工作中,参阅:https://blog.foretellix.com/2017/05/25/one-shot-imitation-learning-and-verification/。其中一个理由是:

深度神经网络是基于「可微分」计算的,所以这种属性让它更容易根据你的想法来进行操作。关于这一点更多的分析我将在后面的帖子中论述。

现在,「后面的帖子」就在你眼前。我们来看看作者是怎样解释他们如何计算导致错误(error-causing)的输入的。

注意到,在高阶层面,我们的梯度计算与在训练一个 DNN 过程中执行的反向传播运算类似,但是存在一个关键的区别,即与我们不同的是,反向传播运算将输入值作为一个常数,将权重参数作为一个变量。

换句话说,他们在用在根本上同样有效的线性代数机器(这种机器是训练和发展 DNN 去做其它事情的基础)来找出输入,这种输入将形成一个覆盖点(或一个误差,或二者都有)。

试想一下在进行常规硬件和软件验证时有多么困难:比如,为了达到一条具体的误差线,你使用了很多方法,如结合大量手动工作的智能随机(smart random),或者是 Concolic 生成(Concolic generation),或是(对足够小的系统而言)模型检查——你可以在这篇帖子(https://blog.foretellix.com/2016/09/01/machine-learning-for-coverage-maximization/)的文末找到相关参考文献。但是对于 DNN 而言,这相对简单一些。顺便提一句,DeepXplore 只能对静态的、状态较少的 DNN 进行报告,但是这项技术可被扩展来处理如牵涉一个输入序列的强化学习问题。

我感到这可以用来做很多事情(参见标题名为「Other applications of DNN gradients(深度神经网络梯度的其它应用)」的论文章节)。例如,这里有个初步、模糊的想法:我曾经写过有关「可解释的人工智能」的文章(https://blog.foretellix.com/2016/08/31/machine-learning-verification-and-explainable-ai/),而且提到了其有助于验证(以及其它很多事情)的原因。或许,我们可以用像 DeepXplore 的技巧(找到精准的界限和关于这些界限的补充样本)去描绘一个输入的本地区域(「如果这个输入变成了 this,那么回答就应该变成 that」), 因此,通过这种办法我们可以让用户了解 DNN。

其它评论

下面是我在阅读这篇论文时看到的一些零散的评论:

机器学习系统大概可能无法单独处理所有的安全问题:

如我在「强化学习与安全性」(https://blog.foretellix.com/2017/03/28/misc-stuff-mobileye-simulations-and-test-tracks/)中提到的一样,大部分人都假设:仅靠 DNN 是无法顾及到所有安全相关的边界情况(原因有很多,比如在错误的数据上过渡训练将会让系统偏离正常状态;再比如,你希望通过阅读代码来说服自己你的系统符合安全规范)。因此人们需要某种不是 DNN 的「安全 wrapper」(在上面提到的文章中我提到了三种这样的 wrapper)。

如果我说得没错,那么(假设 DeepXplore 受限于 DNN 系统),DeepXplore 就只能完成工作的一部分,对于它找出的每一个「错误」,我们都应该把它交给另一个更大的系统,然后观察 wrapper 是否可以将工作完成。这一切有可能很容易,但是也不一定。

作者回复道:

你是对的。任何真实世界的自动驾驶汽车都有某种 wrapper 来处理 DNN 可能的决定,而不是直接依赖 DNN 去处理所有的错误情况。所以,这个 wrapper 也应该是一个很好的测试目标。

关于对抗性样本:

我在《用机器学习验证机器学习?》这篇帖子里(https://blog.foretellix.com/2016/09/14/using-machine-learning-to-verify-machine-learning/)里提到了对抗性样本,我表示:

这篇文章《Transferability in Machine Learning: from Phenomena to Black-Box Attacks using Adversarial Samples》(https://arxiv.org/pdf/1605.07277.pdf)提到了多种机器学习(深度神经网络、支持向量机、决策树等等)系统中同样的欺骗性样本。最后,这篇文章《EXPLAINING AND HARNESSING ADVERSARIAL EXAMPLES》尝试解释为何欺骗性样本应该经常迁移(transfer)到所有线性模型。此外,本文还表示,正确分类的输入只在输入空间中形成「thin manifolds」。它们被欺骗性样本环绕,但是大部分输入空间都由「无效样本」组成,这些输入与训练关注的内容完全不相关。

如果,在执行同样任务时,欺骗了一个深度学习实现的对抗性样本也会倾向去欺骗其它的 DNN 实现,那么可能那些错误的样本也有能力办到同样的事。如果说得没错,那么通过在 K 实现之间进行比较的检查法就不会起作用,除非 K 非常大。

对此作者回复道:

可迁移性(transferability)真的是非常有趣的观察(observation)。在我们的实验里,我们发现尽管有些输入状态是可迁移的,但是大部分不是。实际上,差异测试的目标是找到这种不可迁移的状态。

同时,我们注意到,如果「无效样本」的空间实际上非常巨大,那么我们就需要多少将其切分成这样:即「虽然不在训练中进行考虑,但是它要已经包含在里面」(就像「在左边开车」);同时我们也要将其切分成这样:即「不因为好的原因而将其考虑进训练中」(就像「空间中的车辆,一个在另一个之上飞行一样」)。这就回到了上面提到的那个非常困难的「约束问题」。深度 | 详解首个系统性测试现实深度学习系统的白箱框架DeepXplore

相关推荐