# 深度学习应用开发中常见挑战的实证研究

An Empirical Study of Common Challenges in Developing Deep Learning Applications

# 摘要

深度学习的最新进展促进了许多智能系统和应用的创新,如自动驾驶和图像识别。尽管在这个领域付出了巨大的努力和投资,但一个基本的问题仍然没有得到充分的研究——开发人员在构建深度学习应用程序时通常面临哪些挑战?为了寻找答案,本文对一个流行的问答网站Stack Overflow上的深度学习问题进行了大规模的实证研究。

我们手动检查了715个问题的样本,并确定了7种常见问题。我们进一步建立了一个分类模型来量化不同类型的深度学习问题在整个39,628个深度学习问题集中的分布。我们发现程序崩溃、模型迁移和实现问题是最常被问到的三个问题。在仔细检查了这些问题的公认答案后,我们总结了可能值得研究界关注的五个主要根本原因,包括API滥用,不正确的超参数选择,GPU计算,静态图形计算以及有限的调试和分析支持。

我们的研究结果强调了对跨框架差分测试等新技术的需求,以提高深度学习中的软件开发效率和软件可靠性。

# 介绍

深度学习已经成功地应用于许多领域,并得到了工业界和学术界的广泛关注。在软件工程界,人们对将深度学习应用于重要的软件工程问题越来越感兴趣,例如代码完成[1]、[2]、代码搜索[3]、克隆检测[4]、[5]、类型推断[6]和bug预测[7]、[8]。尽管最近在测试深度学习应用程序方面取得了进展[9]-[14],但开发人员在构建深度学习应用程序时面临哪些编程障碍和挑战仍然不清楚。目前还不清楚软件工程研究人员应该如何提供更好的工具支持来解决这些编程痛点,并提高构建和集成深度学习模型的数据科学家和机器学习工程师的生产力。

深度学习工程在编程范式和实践方面与传统的软件工程有很大的不同。深度学习应用程序是数据驱动的,开发人员定义一个所需的神经网络,并让它从大量的训练数据中自动学习模型参数。然而,传统的软件系统是逻辑驱动的,开发人员直接指定程序*前两位作者的贡献相同。

源代码中的逻辑。由于模型训练需要大量的计算,深度学习开发人员经常利用数据并行性并使用gpu加速模型训练。因此,深度学习中的软件正确性和鲁棒性更多地受到训练数据质量、网络架构、超参数选择和计算单元配置的影响。

从传统的软件开发转向深度学习工程带来了独特的挑战[15]。现在,开发人员经常求助于在线问答论坛,比如Stack Overflow,来寻找他们在软件开发过程中遇到的编程问题的解决方案。截至2018年7月,Stack Overflow已经积累了1600万个编程问题和2600万个答案。先前的工作利用Stack Overflow来研究移动应用程序开发[16]、web开发[17]和安全性[18]中的热门话题。在本文中,我们分析和挖掘Stack Overflow中提出的深度学习问题,以发现和理解开发深度学习应用程序的常见挑战。我们专注于三个流行的深度学习框架,TensorFlow, PyTorch和Deeplearning4j,并在Stack Overflow中提取39,628个相关的深度学习问题。

由于Stack Overflow中有大量的深度学习问题,手动分析所有这些问题是具有挑战性的。

因此,我们首先手动检查715个深度学习问题的样本,并根据问答内容和潜在的编程问题对它们进行分类。我们确定了七种编程问题——程序崩溃、模型迁移和部署、实现、训练异常、理解、安装和性能。然后,我们建立了一个基于关键字的分类模型来量化不同类型的深度学习问题在Stack Overflow中的分布。

在所有类型中,程序崩溃和模型迁移是最常被问到的两个问题。此外,绩效问题是最难回答的——只有25%的绩效问题有可接受的答案,而其他所有类别的这一比例为34%。性能问题也需要更长的时间(4.3小时比其他类别的2.5小时)才能得到正确答案。我们进一步检查每个问题下被接受或认可的回答帖子,以了解潜在编程问题的根本原因。我们强调了五个可能值得研究社区关注的根本原因——api滥用、不正确的超参数选择、GPU计算、静态图形计算以及有限的调试和分析支持

这项研究显示了深度神经网络在构建、训练、迁移和部署方面的几个发展痛点。我们的发现推动了对基于机器学习的系统进行调试和分析的进一步研究,以及为跨框架模型迁移扩展和发明新的差分测试。GPU的大量使用也需要新的技术来暴露GPU计算所强制执行的隐式编程约束。

本文的其余部分组织如下。第二节介绍了数据收集、人工检查程序和自动分类技术。第三节描述了我们的主要发现。第四节讨论了为深度学习扩展软件工程技术的机遇和挑战。第五节讨论有效性面临的威胁,第六节讨论相关工作。

第七部分总结了本文的工作,并讨论了今后的工作。

# 研究方法

本节介绍了本研究的研究问题,然后描述了数据收集和分析方法

# 1.研究问题

本研究调查了以下研究问题。

•RQ1:深度学习中经常被问到的问题是什么?这个问题旨在发现深度学习应用开发中的常见编程问题和障碍。

•RQ2:哪些深度学习问题难以解决?这个问题旨在通过测量正确答案的数量和接收这些答案所需的时间来检验解决不同类型编程问题的难度。

RQ3:主要的根本原因是什么?这个问题旨在了解这些编程问题的原因,以便告知软件工程研究人员为深度学习工程设计更好的方法和工具支持。

# 2.数据收集

为了识别深度学习中常见的编程问题,我们收集了与三个代表性和流行的深度学习框架TensorFlow [19], PyTorch[20]和Deeplearning4j[21]相关的堆栈溢出(SO)问题。这些框架在实践中被广泛采用,但在计算范式和体系结构设计上存在差异。TensorFlow和Deeplearning4j采用静态计算图,其中神经网络必须在训练之前首先定义(即定义和运行)。然而,PyTorch采用动态计算图,并动态定义神经网络(即按运行定义)。TensorFlow和PyTorch都提供Python api,而Deeplearning4j提供Java和Scala api。

与TensorFlow和PyTorch相比,Deeplearning4j与Apache Spark等分布式计算平台紧密集成,因此本质上支持分布式训练。从2018年12月的SO数据转储中[22],我们提取了39,628个用tensorflow, pytorch或deeplearning4j标记的问题。表1显示了与每个框架相关的问题数量及其视图计数。

# 3.手动检查

我们采用开放编码方法[23]对从Stack Overflow收集的深度学习问题进行检查和分类。

前两位作者首先共同检查了每个框架中的50个深度学习问题,并根据潜在的编程问题和症状提炼出一组初始类别。如果一个问题与多个编程问题相关,那么它将被分配到所有相关类别。然后,两位作者在初始分类的基础上,独立地对更多的问题进行分类。如果一个作者发现一个问题不属于一个现有的类别,作者与另一个作者讨论,并根据需要添加一个新的类别。需要对380个岗位进行抽样,才能在39628个与深度学习相关的SO岗位中达到95%的置信水平和5%的置信区间。然而,我们继续检查更多的帖子,直到我们没有发现新的频繁类别。这是定性分析中的一个标准程序,当见解聚合时停止收集新数据。最后,作者比较他们的标签结果,讨论任何分歧,并完善类别。最后,两位作者检查了715个SO问题,并确定了七个类别(在第III-A节中讨论)。

为了了解深度学习问题的根本原因,我们仔细检查了问题帖子下的公认答案(如果有的话),并将其解释和解决方案总结为根本原因。虽然Stack Overflow不建议这样做,但一些SO用户也可能会在帖子下评论并表达感激之情来支持正确的答案。因此,如果没有明确接受的答案,我们将通过回答帖子下的评论来识别此类认可的答案。对于715个深度学习问题的样本,整个人工检查和根本原因分析过程大约需要400个工时。

# 4.自动分类

为了量化整个深度学习问题集,我们建立了一个分类模型,该模型自动将深度学习问题分类为前面手动检查步骤中确定的七个类别之一。该模型在每个类别中使用词频和一组手动选择的关键字的组合进行分类。前两位作者采用与前人相同的方法,人工检查每个类别中出现频率最高的200个词,并共同选择具有代表性的关键词[24],[25]。为了衡量一个词对每个类别的贡献,我们为每个词分配一个权重θ ci和θ ci = tf(wci)/ PN j= 1tf (wcc j),其中tf(w)表示词w在帖子中的出现频率。对于手动选择的关键字,我们分配额外的权重α (α∈[0,1])来强调它们对类别的贡献。

给定一个深度学习问题,我们计算其相对于每个类别的得分,其中k指第k个类别,Manual是一个二元变量,指示关键字是否是手动选择的代表性关键字。我们将手动分类的715个问题帖子分为429个训练实例(60%)和286个测试实例(40%)。我们以0.01的粒度对参数a进行微调。当设置o为0.18时,分类模型获得了最佳的测试准确率,准确率为79.6%,召回率为80.3%。表二显示了

使用这种自动化技术,我们能够量化Stack Overflow中整个深度学习问题集中编程问题的总体分布。我们对其他文档分类模型进行了实验,例如将tf-idf与SVM结合使用,但发现这些模型表现不佳,因为许多SO帖子都是短文档,而且缺乏训练数据。尽管如此,我们并不认为这种分类算法是最好的算法,也不认为它是一种新的贡献。我们采用该算法是因为它构建简单,并且达到了合理的准确率(79.6%的准确率和80.3%的召回率),可以很好地估计Stack Overflow中提出的不同类型的深度学习问题的普遍性。

复制包。39628个深度学习问题的整个数据集、手动检查电子表格和自动分类工具都是公开的。I希望对深度学习进行类似分析的软件工程研究人员可以使用此复制包和数据集。

# 结果分析

# A.RQ1:哪些深度学习问题经常被问到?

图1显示了不同类型的深度学习问题在39628个SO帖子中的分布。总体而言,程序崩溃、模型迁移和实现是最常被问到的三个DL问题,而理解和性能则不那么常被问。训练异常和模型迁移等类别代表了深度学习应用程序特有的新兴编程问题。尽管程序崩溃和实现等其他类别是众所周知的软件工程问题,但由于深度学习系统中的数据驱动计算范式和GPU利用率,它们的问题仍然显示出编程挑战的新方面。

实施此实现类别中的问题涉及如何实现所需的功能或如何使用感兴趣的API。开发人员还想知道如何将所需神经网络的实现适应不同的任务或不同的数据集。由于深度学习大量使用GPU进行模型训练,许多实现问题涉及如何有效地使用GPU,例如,如何分配特定数量的GPU内存,如何在CPU和GPU之间分离计算等。例如,一位开发人员询问如何正确地将数据预处理操作分配给CPU,以便GPU只能专注于训练(1982年后4600年)。

实施问题的一个有趣的子类别是关于分布式培训。在单个GPU上训练大型神经网络时间。因此,开发人员经常采用具有多个GPU的分布式训练。分布式训练中的一个典型问题是,当神经网络在每个GPU上复制时,如何优化其参数共享策略(即数据并行)。邮政39595747询问如何在与参数服务器同步之前首先聚合同一机器中GPU之间的梯度,以避免带宽饱和。与数据并行主义相比,模型谈判主义要求开发人员手动将神经网络划分为不同的部分,并将其分配给不同的设备(CPU和GPU),由于神经元之间的数据依赖性,这更为棘手(例如,Post42069147)分区打破的依赖关系越多,就可以引入越多的通信来跨设备传输数据。总的来说,考虑到网络架构和分布式环境,如何在多个设备之间划分数据和操作以实现最佳和可靠的训练性能是一项挑战。

程序崩溃。此程序崩溃类别中的问题涉及导致程序崩溃的运行时异常。我们主要讨论了三种类型的崩溃错误-形状不一致,数值错误,CPU/GPU不兼容,这在深度学习应用中经常出现,但在传统软件系统中并不常见。

形状不一致。形状不一致是指操作与层之间多维数组不匹配导致的运行时错误。图2显示了卷积神经网络中形状不一致的示例。给定一个[?]形状的输入张量,28,28,1],第一个卷积层(第13-17行)产生形状为[?[0,14,14,64]。然而,第二个ReLu层期望一个形状为[?,7,7,64],导致形状不一致。为了将输出张量减少到正确的形状,第一个卷积层(第3行)的步幅应该设置为[1,2,2,1],而不是[1,1,1,1]。当形状不一致发生时,开发人员经常表示希望查看网络层之间的输入和输出张量形状(Post 51460835)。

然而,决定张量的形状并不简单,因为现代深度学习框架中的高级网络构建api隐藏了场景背后的一切。此外,由于张量可以具有动态形状,因此不能简单地通过静态类型检查来检测形状不一致。相反,它需要定制的数据流分析,考虑层连接和转换张量形状的操作。

数字错误。由于深度神经网络广泛使用浮点计算[26],它们在训练和评估中都很容易受到数字错误的影响。众所周知,数字错误很难调试,部分原因是它们可能只由一小部分很少遇到的输入触发。一个典型的数值错误是非数字(NaN)值。例如,在图3中,表达式ys_重塑*tf.log(预测)(第3行)可能产生0 * log(0)并进一步产生NaN值,因为前一个ReLU层可以输出零。添加一个小的正值(第4行)可以防止这个错误。在TensorFlow中,通常建议使用另一个函数tf.nn。softmax_cross_entropy_with_logits用于计算交叉熵,因为这个函数可以正确处理可能导致NaN错误的极端情况。

CPU / GPU不相容。在将模型训练从CPU切换到GPU时,经常会出现GPU/CPU不兼容的情况。作为一种常见的做法,开发人员通常首先在CPU上运行和测试他们的深度神经网络,然后将其移植到GPU以加快训练速度。与CPU相比,GPU支持不同的数据类型和操作,这些操作是针对GPU内核间的并行计算进行定制和优化的。为了在GPU上进行训练,开发人员必须确保其神经网络中的所有数据和操作都与GPU兼容。现代DL框架通常会很好地隐藏这种隐式设计决策,并公开全局标志和api,以便在CPU和GPU之间自动传输数据。因此,开发者只需要对他们现有的代码做一些小的改变就可以将其移植到GPU上。然而,做这样细微的改变很容易出错。在图4中,即使程序员通过在第3行调用cuda()将输入数据转换为与GPU兼容的版本,当在GPU上运行时,代码片段将在第12行抛出TypeError,因为程序员忘记将其他数据(例如,decoder_hidden, decoder_context)转换为GPU。在一个庞大而复杂的神经网络中,这样的错误很难被发现,从而导致程序崩溃。

训练异常。这一类与不可靠的训练行为有关。神经网络训练本质上是一个基于损失函数测量的先前预测误差不断调整模型参数的过程。为了达到较高的预测精度,训练过程通常涉及大量的训练数据和一系列优化技巧,如mini-batching和feature scaling。

任何训练数据错误或执行不当的优化技巧都可能导致训练异常。Stack Overflow上报告了各种训练异常,包括极低或极高的精度、从未下降的损失值、过拟合、迭代之间的不连续精度值、不稳定的损失值等。在这里,我们讨论一个在Stack Overflow中经常被问到的代表性例子。在Post 34743847中,训练损失一开始很低,但很快增加到一个很大的值,并且不断来回反弹,没有收敛。原因是学习率是0.1,这对于这个特定的任务来说太高了。高学习率会导致网络参数的急剧更新,导致梯度值不断增加的恶性循环(即梯度爆炸)。此外,不适当的权值初始化也会引起爆炸梯度。例如在Post 36565430中,对于一个超过10层的深度神经网络,额外的层可能会使梯度过于不稳定,使损失函数迅速退化到NaN。解决这个问题的最佳方法是使用Xavier初始化[27]。否则,初始值的方差往往过高,造成不稳定。

由于训练异常不会使程序崩溃,因此没有错误消息或开发人员可以从中开始调查的堆栈跟踪。为了调试这种异常行为,有经验的开发人员可以打印参数和梯度来控制并观察它们的值在训练期间的变化情况。然后,他们可以根据自己的启发式来决定调整哪个超参数。但是,在调试具有数千个神经元和数百万个参数的大型神经网络时,这种跟踪-误差方法是冗长而繁琐的。现有的可视化工具,如TensorBoard[28]和Visdom[29]可以提供整个训练过程的鸟瞰图。然而,这些工具只是在训练期间绘制高级定量度量,但缺乏可追溯性,无法确定哪个语句、操作或超参数可能导致这种异常。

模型迁移和部署。这类问题涉及在不同框架之间移植模型实现,或者跨不同框架或平台部署保存的模型。在这样的迁移或部署过程中,由于不同框架、平台或库版本之间的变化或不兼容,经常会发生行为不一致。例如,在Post 49447270中,开发人员希望将基于theano的CNN模型的权重导入到用PyTorch编写的预测器中。由于Theano和PyTorch默认在卷积层中采用不同的矩阵格式,因此如果没有矩阵格式转换,预测器无法正常工作。作为另一个例子,开发人员发现,当恢复在移动设备的服务器上训练的保存模型时,输出质量要低得多(Post 49454430)。确保同一模型在不同部署设置中的行为一致性是至关重要的。然而,由于缺乏工具支持系统地测试和比较不同平台或框架之间的模型行为,这种行为不一致通常很难诊断

为了在移动设备或嵌入式系统上部署神经模型,通常使用量化技术来减小模型尺寸[30]。但是,量化可能会导致各种错误,例如程序崩溃和性能问题。例如,在将浮点精度降低到8位之后,开发人员发现量化模型比原始模型慢了大约20倍(Post 39469708)。随着深度学习模型从云服务器迁移到移动设备和物联网设备的需求不断增加,需要一个系统地检测同一模型在不同设置下的行为一致性的差异测试框架,以提高模型的鲁棒性。

性能。这个性能类别的问题涉及深度学习模型的训练时间和内存使用。训练神经网络需要大量的计算量。

因此,开发人员经常关注执行时间和内存使用方面的训练性能。Stack Overflow中的许多性能问题都询问有关如何优化模型实现以及如何修复内存泄漏等性能错误的建议。开发人员还会询问不同平台、框架和gpu之间的性能差异。例如,一个开发人员使用MXNet和TensorFlow为ImageNet实现了一个图像识别模型,但他发现,当使用四个gpu进行训练时,TensorFlow比MXNet慢得多(Post 36047937)。

关于TensorFlow vs. Caffe (Post 37255626), PyTorch vs. TensorFlow (Post 50784130)等也有类似的问题。

当这种不同的行为发生在不同的设置或框架中时,开发人员经常会怀疑这是由框架差异还是他们自己代码中的实现错误引起的。这表明需要对不同平台和框架之间的性能差异进行诊断和经验基准测试

除了训练时间,GPU利用率是另一个重要的指标,开发人员经常使用它来诊断深度学习中的性能缺陷。GPU的使用取决于许多因素,包括神经网络的大小、批处理大小和数据预处理。

例如,使用GPU实际上可以减缓具有轻计算的小型神经网络的整体训练过程,因为内存分配和数据传输的开销可能会超过加速的好处(例如,3868777号邮政,47900761号邮政)。通过一次提供多个训练实例来训练神经网络,小批量梯度下降是利用GPU的全部功率的一种常见技巧。由于小批量,开发人员经常会遇到GPU使用率低的问题(47971512。邮政52159053)。通过并行计算多个训练实例的梯度,使用大批量可以使训练过程更快地收敛。另一方面,大批量也可能导致内存不足错误,因为它需要大量的内存来保存这样的大批量和相应的张量(Post 38010666)。员额45916769)。CPU计算也可能成为GPU使用的瓶颈。例如,在《35274405号邮报》中,开发人员发现GPU使用率在训练期间总是在0%和80%之间来回波动。原因是每次训练迭代都是从队列中获取和预处理一批训练实例开始的,这真的很慢,因此会阻碍GPU的计算。

理解力

这个理解类别中的问题要求澄清深度学习中的概念、算法和框架API。通常,这些理解问题只是通过引用或总结现有的学习资源来回答。然而,就像任何其他新兴技术一样,SO的大部分帖子都是关于文档的清晰性和深度学习中缺乏代码示例的。例如,在Post34240703中,开发人员对交叉熵函数中logits的使用感到困惑,并要求提供代码示例来说明两个类似函数tf之间的差异。nn。softmax和tf.nn。softmax交叉熵与1位数字。

安装

此安装类别中的问题与不兼容的库安装引起的软件可靠性问题有关。众所周知,由于复杂的软件和硬件依赖性以及版本不兼容,深度学习框架很难安装。例如,每个TensorFlow版本仅与某些cuDNN库版本兼容,这进一步取决于某些CUDA版本和Nvidia GPU型号。由于DL框架的快速发展,许多框架版本都不向后兼容。像conda和pip这样的包管理器并不总是让安装变得更容易,尤其是当DL框架依赖关系与其他软件混杂在一起时。升级单个库可能会造成灾难性后果,破坏整个依赖链并影响其他已安装的软件。

我们还分析了多年来深度学习问题在不同问题类别中的分布,如图5所示。从2015年开始,深度学习问题的数量每年都在急剧增加,这表明深度学习越来越受欢迎。尽管多年来没有发生重大主题变化,但安装问题的比例从2015年的20%下降到2018年的11%。同样,执行问题的百分比也略有下降,从19%降至16%。这可能是由于DL框架越来越稳定,它们的文档也越来越全面。例如,现在有一个TensorFlow网页,可以跟踪发布在Stack Overflow和GitHub上的构建和安装问题[31]。相比之下,模型迁移问题的比例随着时间的推移不断增加,从2015年的14%增加到2018年的22%。基于手动检查,大多数模型迁移问题都是关于从Theano和Caffe迁移到TensorFlow和PyTorch,而不是相反。这可能是由于谷歌和脸书的强大开发团队,TensorFlow和PyTorch现在提供了更多的功能和工具支持,因此吸引了比Theano和Caffe更多的开发人员。事实上,由于来自行业的竞争,Theano已经停止了积极的开发和维护[32]。

# B.RQ2:哪些类型的深度学习问题更难解决?

堆栈溢出鼓励用户接受答案,如果答案是正确的或有助于解决问题。因此,如果问题有一个可接受的答案,我们认为Stack Overflow问题已经解决。我们从两个角度研究了解决不同类型的深度学习问题的困难。首先,我们分析一个类别中有多少深度学习问题接受了答案。在那些有公认答案的问题中,我们进一步分析了在Stack Overflow上发布问题后需要多长时间才能得到正确答案。

图6显示了每个类别中接受答案的深度学习问题的百分比。大约41%的理解问题接受了答案,这是所有类别中接受率最高的。

在手工检查的基础上,由于开发人员不熟悉机器学习概念或文档不清楚,会提出许多理解性问题。与其他类型的问题相比,这类理解题相对容易回答。相比之下,只有25%的绩效问题有可接受的答案,这是最低的。此外,在所有类别中,性能问题也需要最长的时间才能得到正确答案。如图7所示,性能问题平均需要4.3小时才能得到正确答案,而程序崩溃问题只需要1.7小时。与程序崩溃等其他问题相比,性能问题没有可追溯到特定代码行的错误消息。开发人员必须使用分析工具首先诊断哪个操作是瓶颈,以便进一步调查根本原因。

然而,深度学习框架的分析支持仍然很原始。例如,由于PyTorch不提供任何分析器,PyTorch开发人员必须使用Python模块(如time)手动检测神经网络来收集性能指标。此外,性能错误经常发生在训练阶段,其中许多需要特定的gpu或分布式环境来重现(例如,Post 49235599, Post 37255626)。另一方面,许多程序崩溃,如类型错误和形状不一致,经常发生在图形构建阶段,这只需要CPU,因此相对容易被其他开发人员复制

# C. RQ3:主要的根本原因是什么?

当然,一些SO问题仅仅是因为开发人员没有足够的机器学习背景或没有仔细阅读文档。然而,很大一部分问题是由不同开发人员重复犯的常见错误引起的。此外,缺乏工具支持也使得很难诊断深度学习中的某些类型的错误,因此开发人员别无选择,只能求助于在线问答论坛,这可能值得框架开发人员和软件工程研究人员的注意。

在手工检查期间,我们确定了常见编程问题的五个主要根源。

API滥用。API使用违规是各种运行时错误和训练异常的根本原因。例如,开发人员必须在loss.backward()之后调用zero_()来调零梯度,以避免PyTorch中的异常(Post 46513276)。一些API使用违规是隐式的,很难发现。例如,TensorFlow需要初始化所有的tf。变量对象通过调用initialize_all_variables,这对于用户定义的变量来说很容易遵循。然而,某些函数,如adam和momentum优化器在内部定义变量,因此开发人员仍然需要在训练前显式初始化变量(post 33788989, 36007883)。在这种情况下,提供关于api和函数的关键内部的更多透明信息是至关重要的。

GPU计算。在构建深度学习应用程序时,使用gpu会带来一系列编程挑战。gpu施加了关键的编程约束,必须遵循这些约束来避免运行时错误、训练异常和性能错误。例如,开发人员无法通过在GPU中调用x.data. NumPy()直接将PyTorch张量x转换为等效的NumPy数组。相反,开发人员必须首先将张量移动到CPU,然后通过调用x.data.cpu(). NumPy()将其转换为NumPy (Post 44351506)。

在将深度学习模型从CPU移植到GPU时,必须在训练之前先重写这些不支持的函数调用。尽管GPU在深度学习中扮演着如此重要的角色,但许多GPU接口和使用场景并没有很好的文档记录。例如,Post 38580201演示了如何使用函数list_local_devices列出TensorFlow中可用的gpu,该函数收到128个赞。然而,这个函数根本没有文档记录。

超参数选择不正确。建模错误,如不适当的超参数,往往是意外的模型行为,如低精度和过拟合的根本原因。例如,Post 37914647中,开发人员构建了一个简单的神经网络来学习算术加法,但发现即使使用垃圾数据,训练准确率也始终是100%。原因是开发者本打算学习一个线性回归模型来近似加法,却错误地使用了为分类问题设计的交叉熵损失函数。开发者应该使用均方误差(MSE)损失函数。另一个开发人员为小词汇表设置了太高的嵌入维度,这会导致过拟合(Post 48541336)。因此,挖掘超参数的常见组合和值并展示其他人如何设置超参数是有益的。

静态图计算。与PyTorch不同,TensorFlow采用静态计算图,其中神经网络必须在训练前定义。虽然这种定义并运行的模式可以通过小批处理提高训练性能,但它并不容易编程,导致许多编程错误。一个常见的错误是在训练过程中定义神经网络,如图8所示。第14行的函数调用train_op在每次训练迭代中不断向神经网络添加新的操作,使网络变得越来越大,从而迅速耗尽计算能力。静态图计算也会给训练行为的调试带来困难,导致Stack Overflow中的许多调试问题。在TensorFlow中训练计算图时,不能使用诸如pdb2之类的通用调试器来检查运行时值。相反,TensorFlow使用tf。会话接口,实现静态图形与训练过程之间的通信。开发人员必须使用tf计算张量。首先调用Session函数,然后将其值打印到控制台以供进一步分析,这并不简单。

在Stack Overflow中,许多开发人员对如何在TensorFlow中检查运行时值感到困惑(例如,Post 47710467, Post 33679382, Post 33633370)。这与传统的软件调试非常不同,在传统的软件调试中,开发人员可以简单地注入一个断点并逐步执行。尽管TensorFlow现在为TensorFlow模型提供了一个名为tfdbg3的调试器,您可以在其中分步执行并检查值,但正如许多开发人员在Stack Overflow中抱怨的那样,设置起来很麻烦(例如,Post 44211871, Post 46025966, Post 46104266)。

有限的调试和分析支持。调试深度学习模型与调试常规程序有本质上的不同。因为深度学习模型的决策逻辑不是由开发人员直接指定的,而是从训练数据中学习的。深度学习模型的主干是数据流图。虽然堆栈跟踪可能指向特定的代码行,但真正的错误可能存在于训练数据、超参数选择、硬件和版本控制中。不同的训练数据和超参数可能导致意想不到的行为差异,这很难通过比较高级指标(如随时间的训练损失)来调试。设置随机种子、数据丢失和一些GPU操作会导致模型训练中的不确定性,使其更难调试(Post 39938307)。当GPU或分布式设置中出现错误时,开发人员无法轻松跟踪哪个操作在哪个步骤引发错误(Post 50661415)。在GPU和分布式环境中,也有有限的工具支持运行时监控或分析(Post 34775522)。

开发人员除了等待训练过程完成,盯着日志文件,并通过试验和错误调整模型之外,不能做太多事情。

当出现性能问题时,开发人员发现很难确定是哪一行代码或哪个操作引入了瓶颈。PyTorch和Deeplearning4j中的分析支持仍然很原始。在PyTorch中,开发人员必须使用内置的Python模块(如time4和gc5)手动检测神经网络来收集执行时间和内存指纹。类似地,开发人员还必须使用通用的JVM分析器来分析Deeplearning4j模型。TensorFlow提供了自己的分析功能,运行时统计功能,与它的可视化工具TensorBoard紧密集成。然而,TensorBoard使用起来很麻烦,因为开发人员必须编写额外的样板代码来设置TensorBoard并收集性能指标。在Stack Overflow中,我们观察到设置和使用TensorBoard时出现的各种错误,如web浏览器兼容性错误(Post 33680397)和显示错误(Post 34416702)。

现有的可视化工具,如TensorBoard[28]和Visdom[29]提供了一个很好的高级指标总结,如迭代的训练精度。但是,这些工具不适合低级调试活动,例如检查运行时值和为NaN值设置观察点。一种常见的做法仍然是将运行时值打印到控制台或日志文件中,并手动扫描潜在的错误,如爆炸梯度。由于模型复杂性和数据量的不断增加,现在使用多个gpu或一组工作器来训练深度神经网络是很常见的。这种分布式训练为调试深度学习模型带来了更多挑战,因为运行时错误和训练异常与通信带宽和延迟以及神经网络在分布式设置中的划分和放置相结合。

# 研究机会

某些类别,如安装和理解,是软件工程中的一般问题,而不是深度学习应用程序特有的问题。然而,在深度学习应用中,许多问题,如程序崩溃和模型部署后的意外行为不一致,是由于安装的库与硬件不兼容或缺乏对库功能的理解。因此,为了构建可靠的深度学习系统,重要的是要了解与这些方面相关的常见问题,并考虑更好的库依赖管理和文档解决方案,特别是考虑到深度学习库的快速革命以及与传统软件系统相比硬件单元的大量使用。

我们在下面简要讨论了我们的发现对未来研究的三个主要含义。

**含义1:需要挖掘隐式API使用协议和超参数组合。**深度学习框架和GPU计算执行了许多隐式但关键的API使用协议。不遵守这些约束将导致程序崩溃、训练异常和性能错误。深度学习框架通常缺乏这些API使用协议的文档。因此,从GitHub上可用的神经网络实现中自动挖掘此类API使用协议是有益的。

此外,由于超参数通常被指定为函数参数,挖掘常见的超参数组合并显示其他开发人员如何设置它们可以为模型设计和调优提供指导。

**含义2:需要简化调试和分析。**运行时监控和分析支持需要改进,特别是在使用多个gpu和机器进行训练时。测试和运行时验证技术对于检测训练异常非常有用。例如,设想的技术应该使数据科学家和机器学习工程师能够以表达方式指定所需的高级属性,作为可以在训练迭代中传播的测试预言或断言。然后,可以在运行时捕获违反这些属性的异常训练执行状态(例如,数值错误、并发问题、不正确的参数配置),以便进一步分析。

含义3:需要跨框架和跨平台的差异测试。当在不同的框架之间迁移模型或将模型部署到不同的平台(例如,Android, IOS, web浏览器)时,经常会发生行为不一致。然而,由于缺乏工具支持,很难发现和诊断这种行为不一致。

现有的测试技术主要关注于识别测试输入,这些测试输入可以改善测试生成的给定测试覆盖率度量[9]-[12],[33]。因此,需要一个差异测试框架,系统地测试和发现同一模型在不同设置下的行为不一致性,特别是随着从云服务器迁移和部署深度学习模型到移动设备和其他边缘计算设备的需求不断增加。

作为未来的工作,我们计划向专业机器学习工程师和数据科学家发送调查问卷并进行半结构化访谈,以了解解决行业中这些挑战的常见实践和原则,并征求有关所需工具支持的反馈。

# 对有效性的威胁

内部效度。手工分析和自动分类的结合对内部有效性提出了几个威胁。首先,Stack Overflow中对深度学习问题的人工分析是主观的。为了减少偏差,两位作者独立检查了总共715个深度学习问题,并讨论了分类分歧。该分类法是在两位作者解决所有分歧后达成共识的基础上最终确定的。

由于手动检查所有39,628个深度学习问题具有挑战性,因此我们开发了一种自动化技术,将它们分为手动分析中确定的七个类别。我们的分类技术的准确率和召回率都在80%左右,这对于软件工程中的自动自然语言分析来说是普遍可以接受的[34]-[37]。然而,问题仍然不可避免地会被错误分类。自动分类是基于人工识别的类别,可能无法完全覆盖整个39,628个问题中的所有类别。为了缓解这一问题,作者采用开放式编码方法[23],迭代开发和完善常见问题的类别,并继续检查更多的SO帖子,直到没有发现新的编程问题类别。

在RQ2中,我们将SO问题的公认答案视为正确答案。然而,一些SO用户可能会通过在答案帖子下面评论来认可正确答案,而不是明确地接受它们为正确答案。因此,我们可能会错过不被提问者接受的正确答案。因此,在图6中,每个类别中真实正确答案的百分比可能略高一些。

外部效度。外部效度关注的是我们结果的普遍性。为了缓解这一问题,我们分析了与三种具有不同计算范式和基础设施的流行和代表性深度学习框架相关的编程问题。但是,我们仍然可能会错过其他深度学习框架中不包括在我们研究范围内的独特编程问题,例如MXNet[38]和Caffe[39]。由于我们只分析Stack Overflow中提出的深度学习问题,我们可能会忽略其他来源的有价值的见解。在未来的工作中,我们计划对专业的深度学习工程师进行深度访谈,并征求他们的反馈,以缓解这一问题。此外,鉴于深度学习框架的快速发展,目前尚不清楚我们的研究结果将有效多久,以及未来可能出现哪些新的挑战。

工业界和学术界的任何突破都可能显著改变开发人员编写深度学习程序的方式。例如,微软和脸书在2017年发布了开放神经网络交换(ONNX)的第一个版本,这是一种用于可互换机器学习模型的新的神经网络交换格式和生态系统[40]。随后,IBM、华为、英特尔、AMD、ARM和高通宣布支持这一举措。对ONNX的日益采用和支持可能会改变与模型迁移相关的问题的主题

# 相关工作

最相关的工作是最近对TensorFlow构建的深度学习应用程序中的175个软件错误的研究[41]。

我们的工作扩展了这一研究,确定了关于深度学习的七类常见问题。我们不仅分析了与TensorFlow相关的715个Stack Overflow问题,还分析了另外两种具有不同计算范式和架构设计的流行深度学习框架。除了软件缺陷之外,我们还确定了其他开发问题,例如训练异常和模型迁移。

我们还发现,除了像[41]中建议的那样增强故障定位和修复技术外,暴露隐式API使用约束、提供更多交互式调试和分析支持以及为跨不同框架和平台的迁移和部署模型启用差异测试也很重要。

Thung等人[42]和Sun等人[43]都研究了机器学习库和框架中的软件bug类别。

Thung等人[42]手工检查了Apache Mahout、Lucene和OpenNLP中的500个bug样本,并根据Seaman等人[44]提出的bug分类对它们进行了分组。他们进一步分析了错误的严重性,以及修复每个类别中的错误所需的平均时间和努力。Sun等[43]在三个机器学习系统Scikit-learn、Paddle和Caffe中手工检查了328个已关闭的bug。他们提出了7个新的bug类别,并确定了12种通常用于修复这些bug的修复模式。我们的工作的不同之处在于,我们关注的是使用深度学习框架时的软件开发问题和挑战,而不是构建和维护这些框架。

最近,软件工程界在测试深度学习应用方面取得了许多进展[9]-[14],[45]-[49]。DeepXplore[9]提出了一种称为神经元覆盖率的测试有效性度量,并开发了一种神经元覆盖率指导的差分测试技术,该技术可以发现不同深度学习模型之间的行为不一致性。

DeepTest[10]是一个测试生成框架,它使用一组预定义的图像变换来适应标记的驾驶场景,以增加自动驾驶系统的神经元覆盖率。DeepRoad[13]在DeepTest的基础上进行了改进,利用生成式对抗网络(GAN)自动将图像转换为不同的驾驶场景,例如下雪或下雨的情况。DeepConcolic[12]是一种concolic测试方法,通过在具体执行和符号分析之间交替,增量地为深度神经网络生成测试输入。DeepGauge[11]和DeepCT[50]通过提出一组基于神经元值的测试标准和基于神经交互的标准来扩展神经元覆盖度量,并证明新标准在捕获由对抗性示例引起的软件缺陷方面更有效。Kim等人[14]提出了一种新的测试标准,称为惊喜充分性,衡量输入数据在神经元激活状态方面遵循训练数据统计分布的程度。请注意,这些技术中的大多数都侧重于通过生成测试输入来提高深度学习模型的鲁棒性。在这项研究中,我们发现开发人员也可能犯实现错误或选择次优超参数,导致各种程序崩溃和训练异常,这在传统软件系统中很少见到。我们的研究结果表明,需要测试和故障定位支持,允许开发人员系统地检查深度学习应用程序的期望行为和属性,例如,梯度不应该是NaN,类似于开发人员如何编写单元测试用例来检查传统软件系统。Ma等人[33]提出了一种深度神经网络的调试技术,称为MODE。MODE对神经网络进行状态分析,识别导致错误预测测试数据的神经元,并选择新的输入样本对错误的神经元进行重新训练。MODE旨在解决由于训练数据不足而导致的过拟合和欠拟合问题。我们的研究发现了其他类型的错误,如数值错误和性能问题,这需要更多的交互式调试和分析技术。

# 总结

本文对深度学习中的编程问题和错误进行了大规模的研究。我们在Stack Overflow中手动检查了715个深度学习问题的样本,并确定了七种常见问题。我们发现,深度学习中新的数据驱动编程范式引入了新的软件开发问题,如训练异常和模型迁移,这些问题在传统软件系统中没有观察到。基于人工检查的见解,我们构建了一种自动分类技术,用于量化Stack Overflow中不同类型的深度学习问题。在所有类别中,程序崩溃和模型迁移是最常被问到的两个话题。我们还发现,就获得正确答案和等待正确答案的时间而言,性能问题是最难回答的深度学习问题。

尽管深度学习在许多领域得到了成功的应用,但我们的研究表明,对开发工具链的支持仍处于早期阶段。我们的研究激发了调试和分析机器学习和基于人工智能的系统的需求,以及扩展和发明跨框架模型迁移的新差异测试,这是一个及时的主题和迫切的问题,需要解决,以影响当今和未来的现实世界系统。

更新时间: 2024年1月22日星期一下午1点51分