如今,人工智能(AI)已无处不在,被广泛应用于各个行业。在一些诸如自动驾驶、医疗诊断等性命攸关的应用场景中,保证AI模型的可靠性尤为重要,因此AI模型的测试越来越引起业界的重视。由于AI模型的测试与传统软件测试有着本质的不同,无论是学术界还是工业界,对于AI测试的研究还刚刚起步,本文总结了近年来AI模型(重点是深度学习模型)测试的白盒测试技术。
AI模型测试的重点
测试数据
AI模型的编程范式与传统软件有很大的不同,如图1所示,在传统的程序设计中,程序处理逻辑是由开发人员编写的,系统按照编写好的规则处理输入数据,输出答案。因此,传统的软件测试,我们可以通过对比答案来验证和确认程序的正确性。然而AI模型的处理逻辑不是人为编写,而是通过数据训练出来的。这就使AI模型成为一个黑箱,它的输出有很大的不确定性[1]。

在一些性命攸关的系统如自动驾驶系统中,这种不确定性可能会引发重大事故,比如2016年发生的特斯拉撞拖车的事故,由于日光很强,处于自动驾驶模式的特斯拉没能准确识别一辆白色的拖车,使得特斯拉被撞毁,如图所示。

由此我们可以看到,对于一个训练好的AI模型来说,某些特定的输入可能会导致模型输出错误的判断结果,比如特斯拉撞车事故中的强日光下的拖车影像。这种会导致模型判断错误的输入数据称为极端样例(corner cases),对于性命攸关的系统尤为重要。假如我们能够在测试AI模型时找到尽可能多的极端样例,那么对于测试AI模型的可靠性,以及后续提高AI模型的可靠性是非常有利的。为此,学术界提出了一些测试数据的生成和评价算法,来生成一些能够使模型出错的输入数据,或是对已有测试数据集的找错能力进行评价,进一步指导测试数据的生成。目前的研究主要回答以下两个问题:
如何评价一组测试数据的“找错能力”,即该组测试数据是否能找到AI模型尽可能多的输出错误?
如何尽可能多的找到极端样例?
测试数据的评价准则之一
神经元覆盖率
在传统的软件测试中,要评价一组测试用例是否能全面地测试出软件的bug,代码覆盖率是一个常用的指标。代码覆盖率可以包括语句覆盖、分支覆盖、行覆盖、函数覆盖等多种计算方式。但对于一个AI模型来说,前面已经说过,AI模型的逻辑不是由人一句一句编写的,因此传统的代码覆盖率不适用于AI模型的测试。
2017年,哥伦比亚大学的研究团队发表了一篇具有里程碑意义的论文《DeepXplore: Automated whitebox testing of deep learning systems》[2],提出了一种针对AI模型的新的覆盖率计算方式——神经元覆盖率,为后续的研究提供了一个新的方向。
什么是神经元覆盖率呢?目前的AI模型普遍采用深度神经网络的结构,一个深度神经网络是由多层神经元组成的,每一层包含若干个“神经元”,如图所示。每个神经元在数学上可以理解为是对一组输入的加权求和后的非线性函数输出。在神经网络中每个神经元的输出有两种状态,即“激活”和“非激活”,分别表示其输出对后续的神经元有无影响。可以看出,神经元就好比传统软件中的分支判断点,其激活与否影响了后续神经元的逻辑判断。

对于一组给定的测试数据,我们可以通过计算得出神经网络中所有“激活”状态的神经元。“激活”状态神经元的个数占神经元总数的比例就被定义为神经元覆盖率。比如图中的模型以小轿车图像作为输入时,激活的神经元(灰色)占神经元总数的5/8=62.5%。

神经元覆盖率的提出为测试数据的评价提供了一个标准。一组测试数据如果能够覆盖尽可能多的神经元,那么这组测试数据对AI模型的测试就更为全面,更有可能包含那些极端样例。为此,学术界对神经元覆盖率这一准则进行了进一步的研究。
在传统软件测试中,代码覆盖率有行覆盖、语句覆盖、逻辑覆盖等计算方式,从不同的维度对覆盖率进行评价。神经元覆盖率如果只是单一地计算激活的神经元占总神经元的比例,未免有些粗糙,不能反映模型结构上的特点。为此,2018年来自哈尔滨工业大学的研究团队提出了DeepGauge,其中包含了从不同的维度计算神经元覆盖率的准则[3]。
DeepGauge将神经元覆盖率的计算分为两种粒度:神经元级和层级,前者对单个神经元的覆盖情况进行统计,后者对一层中的神经元覆盖情况进行统计。对单个神经元来说,DeepGauge将神经元的输出由原来的“激活”与“非激活”两种简单状态进一步细化为由训练阶段输出的[最小值,最大值]定义的一个取值区间,并将该区间等分成若干小段,然后输入测试数据,统计每个神经元的输出落在哪些小段中,最后计算出被覆盖的小段占总段数的比例,称为多节神经元覆盖率(Multisection Neuron Coverage)。同时对于某些测试数据导致神经元的输出落在取值区间外的情况也做了统计,计算出输出在区间外的神经元占神经元总数的比例,称为神经元边界覆盖率(Neuron Boundary Coverage)。特别地,对于输出超过最大值的神经元,将其视为过分活跃的神经元,这种神经元可能在模型内传递有用的决策信息,被称为“强神经元”,强神经元所占的比例称为强神经元激活覆盖率(Strong Neuron Activation Coverage)。
对于一层的神经元,DeepGauge提出了top-k神经元覆盖率,统计每一层中输出最大的前k个神经元,定义其为top-k神经元,最后计算每层中曾经成为top-k的神经元个数与神经元总数之比。DeepGauge的实验表明,这些更为细化的神经元覆盖率计算方法能够更好地评价一组测试数据对AI模型可靠性检测的能力。
测试数据的评价准则之二
变异测试
神经元覆盖率是衡量测试数据完备程度的一种有效指标,然而它的一个问题是,无法证明神经元覆盖率与AI模型准确度有关联。为此,哈尔滨工业大学的研究团队在2018年提出了衡量测试数据集完备性的另一途径:变异测试(Mutation Testing)。变异测试在传统软件测试领域已经被广泛研究并使用多年,而此研究提出的深度学习模型变异测试工具DeepMutation,是业界第一次将变异测试的理念应用于AI模型的测试[4]。
变异测试的基本思想是,首先对被测程序做一些微小的改动,生成一系列原程序的变异体(mutant)。这些微小的改动被称为变异算子(mutation operator),变异算子一般在符合语法前提下仅对被测程序作微小改动。然后从大量变异体中排除等价变异体(equivalent mutant,即在语义上与原程序一致的变异体)。在剩余的所有非等价变异体中,如果某测试用例在变异体和原程序上的执行结果不一致,则该变异体被“杀死”。假如一套测试用例集能够杀死所有的非等价变异体,则该测试用例集被认为是完备的,否则需要额外设计新的测试用例来杀死存活的变异体。DeepMutation基于变异测试的理念设计了一种评价测试数据集完备性的方法,如图所示。

首先DeepMutation设计了一系列变异算子,对训练好的模型做一些改动。这些变异算子包括修改训练数据的变异算子和修改模型本身的变异算子,比如训练数据的变异算子包括插入重复数据、改变数据标签、删除部分数据、数据加入噪声等;模型的变异算子包括修改模型的权重、添加/删除隐层、交换神经元的位置等。通过变异算子生成一系列的变异模型。然后将测试数据输入变异模型中,如果某个测试数据在变异模型中的输出与原模型不一致,则该变异模型被杀死。最后DeepMutation统计一个分数——被杀死的变异模型数量占变异模型总数的比例——来衡量测试数据集的完备性。比起覆盖率,变异测试判断准则与模型判别准确性关联更大。
测试数据生成技术
有了以上这些测试数据集的评价准则,我们就可以以此指导测试数据的生成。目前的研究主要以覆盖率最大化为目标指导测试数据的生成。
DeepXplore作为提出了神经元覆盖率的一项研究,同时也给出了一个生成测试数据以使覆盖率最大化的自动化方法。首先对于被测模型A,找到几个功能相似的模型B、C、D等作为参考模型,在一些常见的AI应用领域,多个功能相同的模型是容易找到的(如图像分类领域就有VGG、ResNet、DenseNet等)。给定相同的输入,如果模型A的输出不同于其他参考模型,那么有很大概率A的输出是错误的。
接着,DeepXplore用一些已有的测试输入作为种子,通过不断循环迭代地修改测试输入,使得被测模型A的输出与其他参考模型的输出差异最大化(即生成能够引发被测模型错误的corner case),同时使得被测模型A的神经元覆盖率尽可能大。在修改测试数据时,使用梯度上升法,使得目标函数(神经元覆盖率+模型间输出差异)达到最大值。通过这样的双重优化,最终能够生成一系列新的测试数据,这些测试数据不但能最大化被测模型A的神经元覆盖率,而且还很有可能是能够引发被测模型判断错误的极端样例。这样的测试数据可以加入到模型的训练数据中,训练出可靠性更好的模型。
哈尔滨工业大学的研究团队则提出了另一种测试数据生成算法——DeepCT,一种基于组合测试的算法[5]。神经元覆盖率没有考虑同层中神经元之间的相互作用。DeepCT就是以一种层析的方式检测模型某一层中神经元之间的相互作用,提出组合测试覆盖标准,并指导测试集的生成。
组合测试的理念是,在软件的功能测试中,如果通过检查系统输入的所有取值组合来进行充分的测试,则可能的组合数量十分庞大。因此考虑从所有组合中选择一个规模较小的子集作为测试用例集。根据观察,对于很多应用程序来说,很多程序错误都是由少数几个参数的相互作用导致的,有研究发现超过70%的错误是由某两个参数的相互作用触发的,超过90%的错误是由3个以内的参数互相作用而引发的。这样,我们可以选择一组测试用例,使得对于任意t(t是一个小的正整数,一般是2或者3)个参数,这t个参数的所有可能取值的组合至少被一个测试用例覆盖。
DeepCT就是以这样一种思路来定义测试数据的覆盖标准。例如模型的某一层中,有四个神经元{n1,n2,n3,n4},考虑任意两个神经元的输出组合,共六种{n1,n2},{n1,n3},{n1,n4},{n2,n3},{n2,n4},{n3,n4},每个神经元有两个状态{0,1},分别对应未激活和激活,那么这6组神经元可能的输出组合共6*4=24种(每组神经元有4种输出状态)。假设一组测试输入使得4个神经元的输出如图所示,那么这组测试输入一共覆盖了24种输出状态中的20种,其中{n1,n3}=(0,1),{n1,n3}=(1,0),{n2,n4}=(1,0),{n2,n4}=(0,1)没有覆盖到。则这组测试输入的2元组合密集覆盖率=20/24=83.3%。DeepCT以最大化组合密集覆盖率为目标,将生成测试数据定义为一个线性优化过程,从种子数据开始用Cplex求解器计算出新的测试数据。

总结
一组好的测试数据能够测试出AI模型潜在的缺陷,因此现有的研究也将重点放在如何评价和生成测试数据集。虽然目前学术界已经提出了一些测试技术,但将这些测试技术应用到实际应用中还要克服一些难题,比如对于黑盒测试,在不知道AI模型参数和结构的情况下,如何评价测试数据集的优劣?生成的测试数据集能否模拟真实世界中的输入情况?这些都是AI模型测试需要重点研究突破的问题。
参考文献
[1] François C. Deep learning with Python[J]. 2017.
[2] Pei K, Cao Y, Yang J, et al. Deepxplore: Automated whitebox testing of deep learning systems[C]//proceedings of the 26th Symposium on Operating Systems Principles. 2017: 1-18.
[3] Ma L, Juefei-Xu F, Zhang F, et al. Deepgauge: Multi-granularity testing criteria for deep learning systems[C]//Proceedings of the 33rd ACM/IEEE International Conference on Automated Software Engineering. 2018: 120-131.
[4] Ma L, Zhang F, Sun J, et al. Deepmutation: Mutation testing of deep learning systems[C]//2018 IEEE 29th International Symposium on Software Reliability Engineering (ISSRE). IEEE, 2018: 100-111.
[5] Ma L, Juefei-Xu F, Xue M, et al. Deepct: Tomographic combinatorial testing for deep learning systems[C]//2019 IEEE 26th International Conference on Software Analysis, Evolution and Reengineering (SANER). IEEE, 2019: 614-618.

上海市计算机软件评测重点实验室(简称SSTL)由上海市科委批准成立于1997年,是全国最早开展信息系统质量与安全测评的第三方专业机构之一,隶属于上海计算机软件技术开发中心。




