Fork me on GitHub

20199104 2019-2020-2 《网络攻防实践》期末大作业

20199104 2019-2020-2 《网络攻防实践》期末大作业

我选择的论文是TensorFuzz: Debugging Neural Networks with Coverage-Guided Fuzzing,我将从我将从论文介绍、论文算法、实验复现、论文总结四个部分来介绍这篇论文以及我复现的一些结果与思考。

1 论文介绍

1.1 绪论

众所周知,机器学习模型很难解释和调试,神经网络尤其如此。机器学习模型难以调试或解释的原因有多种,从概念上很难确定用户需要了解模型中哪些特定术语,从特定形式的问题中获取答案的统计和计算上的困难。这一特性可能导致了最近机器学习的“再现性危机(reproducibility crisis)”——用难以调试的技术得出得到鲁棒的实验结论是困难的。神经网络可能特别难以调试,因为即使是相对简单的关于它们的形式问题,也可能在计算上很难回答,并且神经网络的软件实现可能会明显偏离理论模型,且绝大部分bug都不会导致神经网络崩溃、报错,只能让它训练了没效果。
该论文发表于ICML 2019 : 36th International Conference on Machine Learning,作者为Augustus Odena,David Andersen,本文介绍了神经网络的自动化软件测试技术,这些技术非常适合于发现只有少数某些输入会引发的错误。本文利用了来自传统软件工程——覆盖引导模糊化(coverage-guided fuzzing),简写为CGF,具体而言,作者开发了神经网络的覆盖引导模糊化方法。在覆盖引导模糊化中,神经网络输入的随机突变由覆盖度量引导到满足用户指定约束的目标。
具体来说,本文做出了以下贡献:
本文介绍了神经网络的覆盖引导模糊化的概念,并描述了如何用快速近似最近邻算法以一般的方式检查覆盖率。
本文为覆盖引导模糊化开源了一个名为TensorFuzz的软件库。
本文使用TensorFuzz在已训练的神经网络中查找数值问题,在神经网络及其量化版本之间查找分歧,以及在字符级语言模型中查找不良行为。

1.2 背景介绍

本节给出了传统软件覆盖引导模糊的背景,然后讨论了测试神经网络的现有方法,最后讨论了覆盖引导模糊如何与这些方法相关联。

1.2.1 覆盖引导模糊测试

在实际的软件测试中,覆盖引导模糊被用来查找许多严重的bug。最常用的两种覆盖引导模糊测试器是AFL和libFuzzer。这些模糊测试器已经以各种方式被扩展,以使它们更快、或增加代码中特定部分可以被定位的范围。
在覆盖引导模糊测试的过程中,模糊测试过程维护一个输入语料库,其中包含考虑的程序的输入,根据一些突变程序对这些输入进行随机变化,并且当它们行使新的“覆盖”时,突变输入(mutated inputs)被保存在语料库中。“覆盖率”(coverage)是什么呢?这取决于模糊器的类型和当前的目标。一种常见的衡量标准是已经执行的代码部分的集合。在这种度量下,如果一个新的输入导致代码在if语句中以不同于先前的方式分支,那么覆盖率就会增加。
覆盖引导模糊测试在识别传统软件中的缺陷方面非常成功,因此我们很自然地会问,覆盖引导模糊测试是否可以应用于神经网络?传统的覆盖率度量标准要跟踪哪些代码行已经执行。在最基本的形式中,神经网络被实现为一系列的矩阵乘法,然后是元素运算。这些操作的底层软件实现可能包含许多分支语句,但其中大多都是基于矩阵的大小,或基于神经网络的架构。因此,分支行为大多独立于神经网络输入的特定值。在几个不同的输入上运行的神经网络通常会执行相同的代码行,并使用相同的分支,但是由于输入和输出值的变化,会产生一些有趣的行为变化。因此,使用现有的覆盖引导模糊测试工具(如AFL)可能不会发现神经网络的这些行为。
在这项工作中,本文选择使用快速近似最近邻算法来确定两组神经网络的“激活”是否有意义上的不同。这提供了一个覆盖率的度量(coverage metric),即使神经网络的底层软件实现没有使用很多依赖于数据的分支,也能为神经网络生成有用的结果。

1.2.2 神经网络的测试

用于测试和计算传统计算机程序测试覆盖率的方法不能直接应用于神经网络。由于上面讨论的原因,本文不能单纯的计算分支覆盖率。因此,作者必须考虑如何为神经网络写下有用的覆盖度量。虽然这项工作是第一个(据作者所知)探索的想法将覆盖引导模糊测试用于神经网络,但它并不是第一个解决神经网络的测试和测试覆盖率的问题。对于测试神经网络和测量它们的测试覆盖范围的方法,已经提出了各种各样的提议(其中许多集中在对抗性的例子)。本文在此研究这些提议:
Pei等人介绍了在以relu为激活函数的神经网络中引入神经元覆盖率的度量。一个测试用例达到全覆盖如果每个隐层的单元都有正值。然后使用梯度下降优化来交叉引用多层神经网络以发现异常行为。
Ma等人从两个方面概括神经元覆盖率。在k-multisection coverage中,抽取每个神经元在训练时的值的范围,分为k部分,然后度量k个部分是否都被“接触”。在神经元边界覆盖中,度量激活函数的值是否大于或小于某个值,然后评估测试集是否满足这些度量。
Sun受到修改的条件/决策覆盖范围启发引入一系列度量。以文中提到的神经网络覆盖方案目标为例:给定神经网络按层安排,(n1,n2)是邻接的两层,他们对于输入对(x,y)是神经网络覆盖方案当且仅当n1在x,y上有不同符号,n2在x,y上有不同符号,包含n1的层中的其他元素在x,y上有相同的符号。
TIAN等人将神经元覆盖度量应用于作为自动驾驶汽车软件的一部分深层神经网络。他们执行自然的图像变换,如模糊、剪切和变换,并使用变形测试的思想来寻找错误。
Wicker等人使用图像特定操作对图像分类器进行黑盒测试。与此同时,Sun等人利用了一种称为并行执行的补充方法。本文的方法类似于AFL或libFuzzer,他们的方法类似于CUTE。

1.2.3 改进的原因

AFL和libFuzzer的成功表明,在神经网络上起作用的类似工具也有测试的作用。理想情况下,我们将使用上面的覆盖度量来实现神经网络CGF。然而,所有这些度量标准,虽然在最初提出的上下文中可能是合适的,但是缺乏某些理想的品质。本文在下面描述为什么对于最相关的度量来说这是正确的。
Sun等人声称神经元覆盖度量太容易满足。特别是,他们显示从MNIST测试集中随机选择的25幅图像这对MNIST分类器产生了接近100%的神经元覆盖率。这个度量也专门用于校正线性单元(reLus),这限制了它的通用性。
神经元边界覆盖是好的,是因为它不依赖于使用reLus,但它仍然能独立地处理神经元。这导致它遭受与神经元覆盖相同的问题:很容易用很少的例子来实现所有的覆盖。
Sun等人的度量是对神经元覆盖范围的改进,可能在神经网络中有用,但是对于本文想要的应用,它们有几个缺点。它们仍然把reLus当作一个特例,他们需要特殊的修改来处理卷积神经网络,而且他们没有提供一个支持注意力模型或注意力残差学习的一般化。他们还依赖于神经网络的层次结构,这对于现代的深度学习架构来说是不正确的。
本文所要实现的覆盖率度量应该是简单的,计算代价低,且能够简单地兼容各种结构的神经网络。因此,本文提出保存每个输入相关的激活函数(或它们的某个子集),使用快速近似最近邻算法检查是否覆盖率增加,来看在预定的距离内是否有其他集合的激活函数。

1.3 本文的创新点

神经网络是机器学习中的一种模型,是一种模仿动物神经网络行为特征,进行分布式并行信息处理的算法数学模型。这种网络依靠系统的复杂程度,通过调整内部大量节点之间相互连接的关系,从而达到处理信息的目的。
神经网络通过每一层的神经元都提取特定的特征信息,然后传到更高层,这就类似传统软件的逻辑分支,我们用下图说明传统软件与神经网络之间的联系。
a图是传统的代码的逻辑分支,b图是输入一个汽车的图像,传入神经网络之后,每个神经元提取信息,然后根据提取的信息对汽车的颜色,轮子等进行概率的评价,最后得到0.95的概率是汽车。这个过程和传统的软件逻辑分支非常相似。

有了这样的认识,那么神经元覆盖率(neuron coverage)的定义也就很好理解了,覆盖率就是被激活的神经元的占比,被激活的神经元就是输出值大于阈值的单元,DNN所有神经元为集合N={n1,n2,...},测试集则为T={x1,x2,...}那么神经元n的输出就为 out(n,x)。神经元覆盖率就可以定义为:

显然对于上图的覆盖率就是5/8。
同样我们可以看到,在几个不同的输入上运行的神经网络通常会执行相同的代码行,并使用相同的分支,但是由于输入和输出值的变化,会产生一些有趣的行为变化。因此,使用现有的覆盖引导模糊测试工具(如AFL)可能不会发现神经网络的这些行为。本文实现的工具就是为了发现神经网络的这些行为。

2 论文算法

2.1 基础知识

2.1.1 Fuzzing

Fuzzing中文一般指模糊测试,意味着测试用例的不确定和模糊。在现实中我们无法穷举所有的输入作为测试用例,在编写测试用例时我们也没有办法将所有的输入都遍历进行测试一遍;除此之外,测试人员也无法想到所有可能的异常场景,简单来说,Fuzzing是指通过构造测试输入,对软件进行大量测试来发现软件中的漏洞的一种模糊测试方法。在现实的漏洞挖掘中,Fuzzing因其简单高效的优势,成为非常主流的漏洞挖掘方法。
Fuzzing具体的定义如下所示:

  1. Fuzzing首先是一种自动化技术,即软件自动执行相对随机的测试用例。因为是依靠计算机软件自动执行,所以测试效率相对人来讲远远高出几个数量级。比如,一个优秀的测试人员,一天能执行的测试用例数量最多也就是几十个,很难达到100个。而Fuzzing工具可能几分钟就可以轻松执行上百个测试用例。
  2. Fuzzing技术本质是依赖随机函数生成随机测试用例,随机性意味着不重复、不可预测,可能有意想不到的输入和结果。
  3. 根据概率论里面的“大数定律”,只要我们重复的次数够多、随机性够强,那些概率极低的偶然事件就必然会出现。Fuzzing技术就是大数定律的典范应用,足够多的测试用例和随机性,就可以让那些隐藏的很深很难出现的Bug成为必然现象。
    Fuzzing基本的实现方案如下图所示:
  4. 准备好随机或者半随机方式生成的数据。
  5. 将准备好的数据导入被测试的系统。
  6. 按照一定规则(变异)产生一批畸形数据。
  7. 将畸形数据逐一送入软件进行解析,并监视软件是否会抛出异常。
  8. 记录软件产生的异常信息,如寄存器状态,栈状态。
  9. 用日志或其他UI形式向测试人员展示异常信息,以进一步鉴定这些错误是否能被利用。

2.1.2 启发式算法

启发式算法(heuristic algorithm)是相对于最优化算法提出的。一个问题的最优算法求得该问题每个实例的最优解。启发式算法可以这样定义:一个基于直观或经验构造的算法,在可接受的花费(指计算时间和空间)下给出待解决组合优化问题每一个实例的一个可行解,该可行解与最优解的偏离程度一般不能被预计。

2.2 关键技术

在实际的软件测试中,覆盖引导模糊测试(Coverage-guided fuzzing)被用来查找许多严重的bug。最常用的两种覆盖引导模糊测试器是AFL和libFuzzer。这些模糊测试器已经以各种方式被扩展。本文实现的方式类似于AFL,以下将以AFL为例,对覆盖引导模糊测试进行介绍。

2.2.1 AFL

AFL(American Fuzzy Lop)是由安全研究员Michał Zalewski开发的一款基于覆盖引导(Coverage-guided)的模糊测试工具,它通过记录输入样本的代码覆盖率(Code coverage),从而调整输入样本以提高覆盖率,增加发现漏洞的概率。其工作流程如图所示:

  1. 从源码编译程序时进行插桩,以记录代码覆盖率(Code Coverage);
  2. 选择一些输入文件,作为初始测试集加入输入队列(queue);
  3. 将队列中的文件按一定的策略进行“突变”;
  4. 如果经过变异文件更新了覆盖范围,则将其保留添加到队列中;
  5. 上述过程会一直循环进行,期间触发了crash的文件会被记录下来。

AFL的工作流程就如上所示,AFL其中比较关键的一点就是变异如何选取,接下来,本文将给出变异的主要流程。变异执行主要步骤如下:

  1. 从队列Seeds集合中选择一个Seed作为初始变异样本。
  2. 根据Fuzzing参数“-d”来确定是否跳过确定性变异。
  3. 随机变异部分,随机选择7以内的数n,产生2^n的变异数,在该迭代次数内随机选择表(如图4所示)中的一种变异方式,实现随机位置的变异。
  4. 变异后的输入样本由程序执行。
  5. 评估执行后的结果,如果产生了crash 或发现新的路径则加入相应的数据集。

接下来的问题是如何对模糊测试进行判断优劣?
代码覆盖率(Code coverage)是模糊测试中一个极其重要的概念,使用代码覆盖率可以评估和改进测试过程,执行到的代码越多,找到bug的可能性就越大,毕竟,在覆盖的代码中并不能100%发现bug,在未覆盖的代码中却是100%找不到任何bug的,所以本节中就将详细介绍代码覆盖率的相关概念。
代码覆盖率是一种度量代码的覆盖程度的方式,也就是指源代码中的某行代码是否已执行;对二进制程序,还可将此概念理解为汇编代码中的某条指令是否已执行。其计量方式很多,但无论是GCC的GCOV还是LLVM的Sanitizer-Coverage,都提供函数(function)、基本块(basic-block)、边界(edge)三种级别的覆盖率检测,更具体的细节可以参考LLVM的官方文档。

2.2.2 快速近似最近邻算法

使用快速近似最近邻算法可以用来以一般的方式检查覆盖率。本文接下来将简单的介绍近似最近邻算法。
如下图所示,我们首先给出最近邻(Nearest Neighbor)的概念和近似最近邻(Approximate Nearest Neighbor)的概念:
最近邻(Nearest Neighbor)的概念:给定一拥有n个点的点集P(P1,P2,…Pn),在此集合中寻找距离q点最近的一个点。
近似最近邻(Approximate Nearest Neighbor)的概念:给定一拥有n个点的点集P(P1,P2,…Pn),在点集中寻找点Pi,这个Pi满足d(q,Pi)≤c×d(q,P),其中d(q,P)是P中距离q点最近一点到q的距离。

我们使用局部敏感哈希(Locality-sensetive Hashing)算法来解决近似最近邻的问题,接下来,本文将简单介绍局部敏感哈希的概念,具体的算法可以见参考文献[3]。
局部敏感哈希(Locality-sensetive Hashing),常简称为LSH。局部敏感哈希在部分中文文献中也会被称做位置敏感哈希。LSH是一种哈希算法, 主要运用到高维海量数据的快速近似查找。近似查找便是比较数据点之间的距离或者是相似度。因此,很明显,LSH是向量空间模型下的东西。一切数据都是以点或者说以向量的形式表现出来的。
局部敏感哈希函数需要满足以下两个条件:
(1)如果d(x,y) ≤ d1,则h(x) = h(y)的概率至少为p1;
(2)如果d(x,y) ≥ d2,则h(x) = h(y)的概率至多为p2;
其中d(x,y)表示x和y之间的距离,d1 < d2, h(x)和h(y)分别表示对x和y进行hash变换。满足以上两个条件的hash functions称为(d1,d2,p1,p2)-sensitive。而通过一个或多个(d1,d2,p1,p2)-sensitive的hash function对原始数据集合进行hashing生成一个或多个hash table的过程称为Locality-sensitive Hashing 局部敏感哈希,简单来说,就是经过hash之后仍然要保持相似度。
看完前面的概念可能会很疑惑,其实我们总结一下,使用Sim()表示相似度,给出一个简易的定义为:
(1)如果Sim(x,y) 高,则h(x) = h(y)的概率高;
(2)如果Sim(x,y) 低, 则h(x) = h(y)的概率低;
假设一个哈希函数为Hash(x) = x%8,那么我们现在有三个数据分别为255、257和1023,我们知道255和257本身在数值上具有很小的差距,也就是说它们在三者中比较相似。我们将上述的三个数据通过Hash函数转换:
Hash(255) = 255%8 = 7;
Hash(257) = 257%8 = 1;
Hash(1023) = 1023%8 = 7;
我们通过上述的转换结果可以看出,本身很相似的255和257在转换以后变得差距很大,而在数值上差很多的255和1023却对应相同的转换结果。上述的Hash函数从数值相似度角度来看,它不是一个局部敏感哈希,因为经过它转换后的数据的相似性丧失了。举个不是很恰当的例子,当hash函数使用的为整除10,向下取余,可以视为一个局部敏感哈希。

2.3 TensorFuzz库的实现

从Fuzzing中得到启发,本文实现了一个工具,我们称之为TensorFuzz。它的工作方式与其他模糊测试器类似,但不同之处在于它更适合于神经网络测试。TensorFuzz不是用C或C++编写的任意计算机程序,而是向任意的TensorFlow graph提供输入。它不是通过查看基本块或控制流的变化来测量覆盖率,而是通过查看计算图的“激活”来测量覆盖率。在2.3.1节中,本文讨论了Fuzzing的整体架构,包括数据流和基本构造块。在2.3.2中,本文将更详细地讨论构建模块。特别是,本文描述了如何从语料库中取样,如何执行突变,以及如何评估覆盖率和目标函数。本文特别注意描述与定义神经网络覆盖度量相关的特殊挑战,并解释如何使用近似最近邻近来处理这些挑战。

2.3.1 基础Fuzzing流程

Fuzzing的总体结构与普通计算机程序的覆盖引导模糊器(CGF)的结构非常相似。主要的区别在于,我们不是与一个我们已经测试过的任意计算机程序交互,本文是与TensorFlow图形交互,本文可以向TensorFlow图形馈送输入并从中获取输出。
模糊器从包含计算图的至少一组输入的种子语料库开始。与传统的覆盖引导模糊器不同,我们不只是输入大的字节数组。取而代之的是,本文将输入限制为在某种意义上是有效的神经网络输入。如果输入是图像,则本文将输入限制为具有正确的大小和形状,并且与所考虑的数据集的输入像素处于相同的间隔内。如果输入是字符序列,本文只允许从训练集中提取的词汇中的字符。
给定这个种子语料库,Fuzzing按如下方式进行:在被指示停止之前,Fuzzer根据我们将称为输入选择器的某个组件从输入语料库中选择元素。出于本节的目的,可以想象输入选择器是随机统一选择的,本文将在2.3.2中描述更复杂的策略。
给定输入后,变异器组件将对该输入执行某种修改。修改可以像翻转图像中输入像素的符号一样简单,也可以被限制为遵循对语料库元素随时间进行的修改的某种约束——有关这方面的更多信息,请参见2.3.2。
最后,变异的输入可以馈送到神经网络。在TensorFuzz中,从神经网络中提取两件事:一组覆盖数组,从中计算实际覆盖范围;以及一组元数据数组,从中计算目标函数的结果。
一旦计算了覆盖率,如果变异的输入行使了新的覆盖率,它将被添加到语料库中,如果它使目标函数得到满足,它将被添加到测试用例列表中。
将整个流程的图给出,如下图所示。

2.3.2 Fuzzing过程细节

在本节中,我们将更详细地描述Fuzzer的组成部分。
输入选择器(Input Chooser:):在任何给定的时间,模糊者必须从现有语料库中选择要变异的输入。最优选择当然是问题相关的,而传统的遗传算法依赖于各种启发式方法来做出这一决定。对于我们测试的应用程序,本文最终确定了以下启发式方法,作者发现它很快:

其中p(ck,t) 给出了在时间t选择语料库元素ck的概率,tk是元素ck被添加到语料库的时间。这表明,最近采样的输入在发生突变时更有可能产生有用的新覆盖,但随着时间的推移,这种优势会减弱。
变异器(Mutator):一旦输入选择器选择了语料库中的一个元素进行变异,就需要应用这些变异。在这项工作中,本文必须实现图像输入和文本输入的突变。对于图像输入,我们实现了两种不同类型的变异。第一种方法是只将用户可配置方差的白噪声添加到输入。第二种方法是添加白噪声,但限制突变元素与原始元素之间的差异,使其具有用户可配置的L-∞范数。如果我们想要找到满足某个目标函数的输入,但似乎仍然与用作种子的原始输入属于同一“类”,则这种类型的约束突变会很有用。在这两种类型的图像突变中,我们对突变后的图像进行裁剪,使其位于与用于训练被模糊化的神经网络的输入相同的范围内。
对于文本输入,因为我们不能简单地向字符串添加统一的噪声,所以本文根据以下策略进行变异,本文统一地随机执行以下操作之一:在随机位置删除字符、在随机位置添加随机字符或在随机位置替换随机字符
目标函数(Objective Function):通常情况下,我们会怀着一定的目标来运行模糊器。也就是说,本文希望神经网络达到某种特定的状态–也许是我们认为是错误的状态。目标函数用于评估是否已达到该状态。当变异的输入输入到计算图中时,覆盖率数组和元数据数组都将作为输出返回。将目标函数应用于元数据数组,并标记导致满足目标的输入。
覆盖率分析器(Coverage Analyzer):覆盖率分析器负责从TensorFlow运行时读取数组,将它们转换为表示覆盖率的python对象,并检查该覆盖率是否为新的。检查新覆盖的算法是模糊器正常工作的核心。
一个理想的覆盖率分析器的特点是:我们希望它检查神经网络是否处于它以前没有处于的“状态”,这样我们就可以发现测试集可能没有捕获到的错误行为。本文希望检查速度快(所以我们可能希望它简单),这样本文就可以快速发现许多这样的不良行为。作者希望它可以适用于许多不同类型的计算图,这样从业者就可以使用本文实现的工具,而不必进行特殊的调整。覆盖率要达到全覆盖是很难的,另外实际上也不会覆盖到很多可能的行为。最后,本文希望获得新的覆盖范围,以帮助我们取得增量进展,以便持续的模糊收益率继续上涨。
本文使用的方法是读取整个激活向量,并将新的激活向量视为新的覆盖。然而,这样的覆盖度量不会提供有用的指导,因为大部分输入都会产生新的覆盖。最好检测激活矢量是否接近先前观察到的矢量。实现这一点的一种方法是使用近似最近邻算法。当我们得到一个新的激活向量时,我们可以查找它的最近邻,然后检查最近邻在欧几里德距离中有多远,如果距离大于某个量L,则将输入添加到语料库中。
这基本上是本文所做的。目前,作者使用一个叫做FLANN的开源库来计算近似最近邻问题。一般来说,您可能不需要看到所有的激活。要使用的激活集目前是一个经验调整的问题。本文发现,通过只跟踪逻辑或逻辑之前的层,通常可以获得好的结果。需要注意的一个潜在优化:你实际上不需要知道最近的邻,你只需要知道在某个范围内是否存在邻。为此,可以使用一个距离敏感的Bloom过滤器。

2.3.3 Batching和不确定性

还有另外两个问题是TensorFlow图所特有的,值得一提。首先,实际存在的几乎所有TensorFlow图都被设计成利用现代图形处理器提供的硬件并行性。因此,一次只获取一个变异输入的覆盖率和元数据可能是浪费。相反,我们将突变作为一个批处理来执行,并将一批输入到计算图中,然后在一批输出数组上检查覆盖率和目标函数。第二,计算图通常会给出不确定的输出——这是因为指令执行不确定,也是因为图中内置了基本上随机的运算。目前,我们已经选择了尽可能简单的方式来处理这个问题:如果同个输入执行两次产生不同的覆盖,那么直接在输入集合中包含两次。

3 实验复现

3.1 实验环境

实验所需要的环境在文档requirements.txt中进行了说明,但是在此之前我们首先需要安装关于神经网络的一些软件。
(1)CUDA Toolkit 9.0
(2)CUDNN v7.4.1 适用于CUDA 9.0 win10版本
(3)安装Anaconda 3
(4)使用conda 创建python 3.4的环境,之后进行安装requirements.txt即可
(5)添加环境变量
(6)运行给定的程序即可
搭建实验的环境的难点主要在于conda创建、激活虚拟环境。具体可以参考网上博客,以下给出所需要使用的命令。

conda create -n xxl_env python=3.4 #创建环境
conda activate xxl_env #进入环境
pip install -r requirements.txt #安装requirements.txt给出的环境
python examples/nans/nan_model.py --checkpoint_dir=/tmp/nanfuzzer --data_dir=/tmp/mnist --training_steps=35000 --init_scale=0.25 
python examples/nans/nan_fuzzer.py --checkpoint_dir=/tmp/nanfuzzer --total_inputs_to_fuzz=1000000 --mutations_per_corpus_item=100 --alsologtostderr --ann_threshold=0.5 #运行给定的程序即可

3.2 实验结果

本文实现了TensorFuzz几种不同应用,以证明它在一般情况下是有用的。

3.2.1 TensorFuzz可以有效地在训练好的神经网络中发现数值误差

由于神经网络使用浮点数学,因此无论是在训练期间还是在评估期间,它们都容易受到数值问题的影响。众所周知,这些问题很难debug,部分原因是它们可能只由一小部分很少遇到的输入触发。这是CGF可以提供帮助的一个例子。我们专注于查找导致非数(NaN)值的输入。
发现数值误差是很重要的: 数值错误,尤其是那些导致 NaN 的数值错误。使用覆盖引导模糊可以用于在部署系统之前找到大量的错误,并减少错误在危险环境中造成的风险。
覆盖引导模糊可以快速找到数值错误:通过覆盖引导模糊,我们可以简单地添加检查数值选项到元数据中,并运行我们的 Fuzzer。为了测试这个假设,我们训练了一个全连接神经网络来分类 MNIST 数字。我们故意使用了一个实现效果比较差的交叉熵损失函数,以增加数值错误出现的可能性。我们用 100 的 mini-batch 大小训练了 35000 个迭代步,直到达到 98% 的验证准确率。然后我们检查到,不存在导致数值错误的 MNIST 数据集元素。尽管如此,TensorFuzz 却在多个随机初始化中快速找到了 NaN,如下图所示。

基于梯度的搜索技术可能无法帮助寻找数值错误:覆盖引导模糊的一个潜在缺陷是,基于梯度的搜索技术可能比随机搜索技术更加高效。然而,我们并不清楚如何明确基于梯度搜索的目标。
随机搜索在寻找某些数值错误方面是极度低效的。为了证实随机搜索不够高效且覆盖引导对于提高效率很有必要,我们对比了随机搜索方法。我们实现了一个基线随机搜索算法,并用 10 个不同的随机初始化在语料库的 10 万个样本上运行了该算法。基线算法在所有实验中未找到一个非限定元素。

3.2.2 CGF 解决模型和量化版本不一致的问题

量化(quantization)是一种神经网络权重被保存,且在执行神经网络计算的时候使用更少计算内存位数来表示数值的过程。量化是一种降低计算成本或神经网络规模的流行方法,广泛应用于手机上神经网络,如Android神经网络API或TFLite,也应用于机器学习硬件(谷歌的 Tensor Processing Unit或是NVidia’s TensorRT)
寻找由量化导致的错误非常重要:当然,如果量化显著减少了模型准确率,那量化就没有什么意义了。给定一个量化模型,如果能检查出量化减少的多少的准确率是最好的。
仅检查已有的数据只能找到很少的错误:作为基线实验,我们训练了一个使用 32 位浮点数的 MNIST 分类器(这一次没有故意引入数值错误)。然后把所有权重和激活值修剪为 16 位。之后,我们对比了 32 位和 16 位模型在 MNIST 测试集上的预测,没有找到任何不一致性。
CGF 可以快速在数据周围的小区域中找到很多错误:然后运行 Fuzzer,变化限制在种子图像周围的半径为 0.4 的无限范数球中,其中仅使用了 32 位模型作为覆盖的激活值。我们将输入限制在种子图像附近,因为这些输入几乎都有明确的类别语义。通过这些设置,fuzzer 可以生成 70% 样本的不一致性。因此,CGF 允许我们寻找在测试时出现的真实错误,如图所示。

3.2.3 CGF在字符级语言模型中查找不良行为

给定一个机器学习模型,我们也许能够把它的某些行为描述为不良行为,即使把这种特性纳入到训练损失中可能是不方便的,甚至是难以控制的。举一个例子,我们训练了Karpathy中描述的类型的字符级语言模型。特别是,我们修改了https://github.com/sherjilozair/char-rnn-tensorflow 的代码,在Tiny Shakespeare数据集上训练了一个2层的LSTM。
然后,我们考虑在给定启动字符串的情况下,从这个训练好的语言模型中取样的应用字符串。例如,人们可以想象在自动完成的应用程序中完成这样的事情。为了说明起见,我们确定了两个我们可以通过模糊器近似执行的需求:首先,模型不应该在一行中重复同一个单词太多次。其次,该模型不应该输出黑名单中的单词。
我们用模糊模型来覆盖LSTM的隐藏状态。我们根据逻辑上的softmax从模型中取样,使用固定的随机种子,我们在每次取样时重置。我们使用第2.3.2节中描述的突变函数。
我们运行了一个TensorFuzz实例和一个随机搜索实例,每个实例持续24小时。张量模糊能够生成重复单词,随机搜索也是如此。然而,TensorFuzz能够从我们的黑名单中生成十个单词中的六个,而随机搜索只能生成一个。

4 论文总结

本文提出了神经网络的覆盖引导模糊测试的概念,并描述了如何在这种情况下构建一个有用的覆盖率检查器。我们已经通过使用TensorFuzz查找数值误差、发现神经网络和它们的量化版本之间的分歧,证明了TensorFuzz的实用性。最后,我们将同时发布TensorFuzz的实现,以便其他研究人员既可以在我们的工作基础上进行研究,也可以使用Fuzzer来查找实际的问题。

5 学习总结和建议意见

5.1 学习总结

经过一个学期的学习,我从一开始对网络攻防的知识一无所知,到现在也算是略有了解吧,中间的学习过程也算是艰辛。写博客从最开始的主要是参考其他同学的博客,到后来自己认真的思考,认真的查资料,积累了攻防的知识,提升自己的动手能力。虽然我的研究生学习的方向其实和网络攻防方向一点不相关(现在跟着老师学习密码方向),但是我觉得学习的技巧其实是共通的,其实都是找资料,学习基础知识,实践的这一个过程。有些知识看起来简单,但是实现时会出现各种奇奇怪怪的错误。
总的来说网络攻防是一门动手能力很强的课程,真的很锻炼动手能力。这门课也算是从另外一个方向让我了解了整个网络攻防领域,至少对于基础的概念和方法都能了解,也算是拓宽了我在这个方向的知识面,算是长见识了吧。
其实学完这门课之后,也学习到如何在网络上找资源,看到一些乱码也会先下意思的想到是不是base64加密,也能从加密中的乱码中使用base64解密出百度云的资源,也能找到一些特殊的资源,这也算是一些特殊的收获吧。

5.2 建议意见

其实我感觉这门课以现在这种方式上课就挺好,如果能在上课的时候少查一些人(或者是抽人改为随机的,或是就是像现在某几次课一样,布置课后当天完成的作业),将空出来的时间来进行多讲一些知识(就是讲上周实验会常出现的问题应该怎么解决,一些软件的使用方法一类的)会让我们对学习的知识有更多的了解。
其实感觉研究生的学习,与本科生不太一样,就是自己学习摸索的一个过程,我个人觉得每周实践和课后实践的这个实践方式对我学习帮助很大,因为我感觉,从自己找材料学习到实践这个过程,这也是从另外的一个方面锻炼了自己的学习能力。

参考文献

posted @ 2020-06-14 22:58  20199104许星霖  阅读(877)  评论(0编辑  收藏  举报