开发人员经常想知道如何使用 api 实现某种功能(例如,如何解析 XML 文件)。在这方面,基于 API 相关的自然语言查询获取 API 使用序列非常有帮助。给定一个查询,现有的方法利用信息检索模型来搜索匹配的 API 序列。这些方法将查询和 api 视为单词袋,缺乏对查询语义的深刻理解。
我们提出了 DeepAPI,一种基于深度学习的方法来为给定的自然语言查询生成 API 使用序列。它不采用单词袋假设,而是学习查询中的单词序列和相关 api 序列。DeepAPI 采用了一个名为 RNN 编码器-解码器的神经语言模型。它将单词序列(用户查询)编码为固定长度的上下文向量,并基于上下文向量生成 API 序列。通过考虑各个 api 的重要性,我们还增加了 RNN 编码器-解码器。我们从 GitHub 上收集了超过 700 万个注释代码片段,以经验来评估我们的方法。结果表明,我们的方法产生了大量准确的 API 序列,并优于相关的方法。
1.介绍
为了实现某种功能,例如,如何解析 XML 文件,开发人员经常通过调用相应的 api 重用现有的类库或框架。在这方面,获取要使用的 api 及其使用顺序(api 中的方法调用顺序)是很有帮助的。例如,“解析 XML”,使用 JDK 库,期望的 API 使用顺序如下:
DocumentBuilderFactory.newInstance
DocumentBuilderFactory.newDocumentBuilder
DocumentBuilder.parse
然而,学习不熟悉的库或软件框架的 api 可能是开发人员的一个重大障碍。一个大型的软件库,如。net framework 和 JDK,可能包含数百甚至数千个 api。在实践中,API 方法的使用模式通常没有很好地记录在中。在微软 2009 年进行的一项调查中,67.6%的受访者提到了学习 api 的资源不足或缺乏造成的障碍。另一项实地研究发现,API 用户面临的主要挑战是发现可以帮助完成任务的 API 子集。
发现 api 及其使用顺序的常见场所是搜索引擎。许多开发人员从一般的 web 搜索引擎,如谷歌和 Bing 中搜索 api。开发人员还可以在 GitHub 这样的开源存储库上执行代码搜索,然后利用 API 使用模式挖掘器来获得适当的 API 序列。
然而,对于编程任务来说,搜索引擎通常效率低下且不准确。一般的 web 搜索引擎不是专门为支持编程任务而设计的。开发人员需要手动检查许多 web 页面,以了解 api 及其使用顺序。此外,大多数搜索引擎都是基于关键字匹配,而没有考虑自然语言查询的语义。通常很难发现相关的代码片段和相关的 api。
最近,Raghothaman 等人提出了 SWIM,它使用统计词对齐模型将自然语言查询翻译成可能的 api 列表。然后,SWIM 使用 API 列表检索相关的 API 序列。然而,它所使用的统计词对齐模型是基于词袋假设的,没有考虑词和 api 的序列。因此,它不能识别自然语言查询的深层语义。例如,正如他们在论文中所描述的,很难区分“convert int to string”和“convert string to int”查询。
为了解决这些问题,我们提出了 DeepAPI,这是一种新颖的基于深度学习的方法,可以根据自然语言查询生成相关的 API 使用序列。我们将 API 学习问题表述为机器翻译问题:给定一个自然语言查询 x = x1,……, xN xi
是一个关键字,我们的目标是把它翻译成一个 API 序列 y = y1,……yT 在 yj 是一个 API。DeepAPI 从两个方面展示了对自然语言查询的深刻理解:
•首先,DeepAPI 不是匹配关键字,而是通过将单词嵌入上下文的向量表示来学习单词的语义,从而识别语义相关的单词。
•其次,DeepAPI 学习的是自然语言查询中的单词序列以及相关 api 的序列,而不是单词到单词对齐。
DeepAPI 采用了一个名为 RNN 编码器-解码器的神经语言模型。给定一个标注 API 序列的语料库,即。DeepAPI 训练语言模型,该模型将每个单词序列(注释)编码成固定长度的上下文向量,并基于上下文向量解码 API 序列。然后,响应一个 API 相关的用户查询,它通过咨询神经语言模型生成 API 序列。
为了评估 DeepAPI 的有效性,我们从 GitHub 上收集了 700 万个带注释的代码片段。我们选择了 1 万个实例进行测试,其余的实例用于训练模型。经过 240 小时的训练(100 万次迭代),我们使用 BLEU score 来测量 DeepAPI 的精度,这是一种广泛使用的机器翻译精度测量方法。我们的结果显示,DeepAPI 的平均 BLEU 得分为 54.42,优于两种相关方法,即基于模式挖掘的代码搜索(11.97)和 SWIM(19.90)。我们还要求 DeepAPI 从真实的查询日志和相关工作中收集 30 个 API 相关查询。平均而言,第一个相关结果的排名是 1.6。排名前 5 位的 80%和排名前 10 位的 78%被认为是相关的。我们的评估结果证实了 DeepAPI 的有效性。
我们工作的主要贡献如下:
•
据我们所知,我们是第一个将深度学习技术应用于 API 学习的公司。与最先进的技术相比,我们的方法可以获得更精确的 API 使用顺序。
•
我们开发 DeepAPI1,一个基于自然语言查询生成 API 使用序列的工具。我们使用 700 万个带注释的 Java 代码片段的语料库,以经验的方式评估 DeepAPI 的准确性。
本文的其余部分组织如下。第 2 节介绍了基于深度学习的神经语言模型的背景。第 3 节介绍了基于深度学习的神经语言模型 RNN 编码器-解码器在 API 学习中的应用。第 4 节描述了我们方法的详细设计。第 5 节介绍了评价结果。第 6 节讨论我们的工作,随后是介绍相关工作的第 7 节。我们在第 8 节结束本文。
我们的工作采用并增强了来自深度学习和神经机器翻译的最新先进技术
这些技术是建立在序列到序列学习的基础上的,也就是说,以另一个序列为条件生成一个序列(通常是一个自然语言句子)。在本节中,我们将讨论这些技术的背景。
据观察,软件具有的自然性。统计语言模型已经适应于许多软件工程任务,如学习自然代码约定、代码建议和代码完成。这些技术将源代码视为一种特殊的语言,并使用统计 NLP 技术对其进行分析。
语言模型是一种关于语言中如何生成句子的概率模型。它告诉我们一个句子在一种语言中发生的可能性有多大。对于一个句子 y,其中 y = (y1,……yT )是一个单词序列,语言模型的目的是估计其单词 Pr(y1,……yT )。自
它等价于根据 y 中每个单词的前一个单词来估计其概率,也就是说,一个单词的前一个单词可能是什么。
公关(yt| y1,……yt−1)是很难估计的,大多数应用程序使用“n-gram 模型”来近似它,即
n-gram 被定义为 n 个连续的单词。这个近似意味着下一个单词 yt 只以前面的 n−1 个单词为条件。
2.3 RNN 编码器-解码器模型
RNN 编解码器是基本神经语言模型(RNNLM)的扩展。它假设有两种语言,一种源语言和一种目标语言。给定源语言的句子 x,它生成目标语言的句子 y。为此,它首先总结了源词 x 的序列 1,……, xTx 转换成固定长度的上下文向量:
ht = f(ht−1,xt) (6)
和
f 是一个非线性函数,映射源语言 x 的一个词 t 进入一个隐藏的状态 ht 考虑之前的隐藏状态 ht−1。最后一个隐藏状态 hTx 被选为上下文向量 c。
然后,通过连续预测一个单词 y 生成目标句子 yt 取决于源语境 c 和之前的单词 y1,……yt−1:
上述程序,即, f 和 p 可以用两个递归神经网络分别表示,编码器 RNN 学习将变长源序列转换为定长上下文向量,解码器 RNN 学习目标语言模型并生成以上下文向量为条件的序列。编码器 RNN 逐个读取源词。在每个时间戳 t,它读取一个单词,然后更新并记录一个隐藏状态。当读取一个单词时,它计算当前隐藏状态 ht 使用当前的单词 xt 和之前隐藏的状态 ht−1。当它读完序列末尾字时,它选择最后一个隐藏状态 hTx c.解码器 RNN 通过参考上下文向量依次生成目标词(式 8)。首先将上下文向量设置为解码器 RNN 的初始隐藏状态。在每个时间戳 t,它根据当前隐藏状态和上下文向量生成一个单词。然后,它使用生成的单词更新隐藏状态(公式 6)。当生成句子结尾单词时,它停止。
现在我们提出了将 RNN 编码器-解码器模型应用于 API 学习的思想。我们将用户查询作为源语言,API 序列作为目标语言。图 2 显示了 RNN 编码器-解码器模型的一个示例,用于将一系列英语单词读取的文本文件翻译成一系列 api。编码器 RNN 逐个读取源词。当它读取第一个读到的单词时,它将该单词嵌入到向量 x 中 1 并计算当前隐藏状态 h1 使用 x1。然后,它读取第二个单词文本,将其嵌入到 x 中 2,并更新隐藏状态 h1 到 h2 使用 x2。这个过程一直持续,直到编码器读取最后一个 word 文件并获得最终状态 h3。末态 h3 被选为上下文向量 c。
解码器 RNN 尝试使用上下文向量 c 按顺序生成 api。它首先生成作为第一个单词 y0。然后,它计算隐藏状态 h1 基于上下文向量 c 和 y0,并预测第一个 API 文件阅读器。新根据 h1。然后计算下一个隐藏状态 h2 根据前面的词向量 y1,上下文向量 c,并预测第二个 API 缓冲恐惧器。新根据 h2。这个过程一直持续到预测序列末尾的单词为止。
在目标序列中,查询的不同部分对 API 的重要性可能不同。例如,考虑使用默认编码的查询保存文件和目标 API 序列文件。新的 FileOutputStream。新的 FileOutput——流。FileOutputStream 写。关闭,word 文件比默认值对目标 API 文件更重要。在我们的工作中,我们采用了基于注意力的 RNN 编码-解码器模型,这是一个最新的模型,从每个目标单词的输入序列中选择重要的部分。而不是使用相同的上下文向量 c (c = hTx ),注意模型定义了个体 cj ’s 表示每个目标词 yj 作为所有历史隐藏状态 h 的加权总和 1,……hTx 。
基本的 RNN 编码-解码器模型也不考虑目标序列中单个单词的重要性。在 API 学习的环境中,不同的 API 对于编程任务有不同的重要性。例如,API Logger.log 在许多代码片段中被广泛使用。然而,它不能帮助理解编程任务的关键过程。在序列生成过程中,这种普遍存在的 api 将被“弱化”。
我们增加了 RNN 编码器-解码器模型,通过考虑 API 的个体重要性来预测 API 序列。我们定义基于 idf 的权重来衡量 API 的重要性如下:
其中 N 是训练集中 API 序列的总数,Nyt 表示序列的个数,其中 API yt 使用 IDF,普遍出现的 api 的权值较低,而不常见的 api 的权值较高。
我们使用原料药重量作为成本函数的惩罚项。
在本节中,我们将描述 DeepAPI,这是一种基于深度学习的方法,它根据与 API 相关的自然语言查询生成相关的 API 使用序列。DeepAPI 采用 RNN 编码器-解码器模型来完成 API 的学习任务。图 3 显示了 Deep- API 的总体架构。它包括一个离线培训阶段和一个在线翻译阶段。在训练阶段,我们准备了一个大规模的 API 序列标注语料库(带有相应自然语言标注的 API 序列)。注释 API 序列用于训练深度学习模型,即。中描述的 RNN 编码器-解码器语言模型
首先构建一个包含 API 序列和自然语言注释的大规模数据库,用于训练 RNN 编码器-解码器模型。我们从 GitHub 下载从 2008 年到 2014 年创建的 Java 项目。为了删除玩具或实验项目,我们只选择至少有一颗星的项目。我们总共从 GitHub 上收集了 442,928 个 Java 项目。我们使用每个项目的最后一张快照。
如第 3 节所述,我们将基于注意力的 RNN 编码器-解码器模型用于 API 学习。RNN 有多种实现,我们使用 GRU,它是一种最先进的 RNN,在许多任务中都表现很好。我们构建的模型如下:我们使用两个 RNN 作为编码器——一个是直接编码源句子的正向 RNN,一个是编码反向源句子的反向 RNN。它们的输出上下文向量被连接到解码器,这也是一个 RNN。所有的 rn 都有 1000 个隐藏单元。我们将词嵌入的维数设置为 120。我们将在第 5.4 节中讨论参数调优的细节。
所有的模型都使用 minibatch Adadelta 进行训练,自动调整学习速率。我们设置批大小(即。(每个批的实例数)为 200。为了训练神经网络,我们将源词汇和目标词汇限制为 API 序列和注释中最常用的前 10,000 个单词。
为了实现,我们使用了开源的深度学习框架 GroundHog。我们在一个带有 Nvidia K20 GPU 的服务器上训练我们的模型。培训持续 240 小时,迭代 100 万次。
到目前为止,我们已经讨论了神经语言模型的训练,该模型根据自然语言查询输出最有可能的 API 序列。然而,一个 API 可以有多种用途。为了获得一个可供用户选择的 API 序列的排序列表,我们需要根据每个步骤中 API 序列的概率生成更多的 API 序列。
DeepAPI 使用一种启发式搜索策略——波束搜索,来寻找语言模型给出的代价值最小(使用公式 13 计算)的 API 序列。Beam search 逐步搜索每一步产生的 api。在每个时间步,从所有开销值最小的分支中选择 n 个 api,其中 n 为波束宽度。然后它删除剩余的分支,并继续选择可能的 api,直到它满足序列结束符号。图 5 显示了一个为查询“read text file”生成 API 序列的波束搜索(波束宽度=2)的例子。首先,选择' START '作为生成序列中的第一个 API。然后,它根据语言模型估计所有可能出现的 api 的概率。它根据公式 13 计算它们的成本值,并选择 File。new 和 FileIn- putStream。new 的成本最小值分别为 6 和 8。然后,它会忽略其他 api 的分支,并在文件之后继续评估可能的 api。
我们通过测量 DeepAPI 在 API 序列生成上的准确性来评估其有效性。具体来说,我们的评估涉及以下研究问题:
•
RQ1: DeepAPI 生成 API 使用序列的准确性如何?
•
RQ2: DeepAPI 在不同参数设置下的准确性如何?
•
RQ3:增强的 RNN 编码器-解码器模型是否提高了 DeepAPI 的准确性?
我们使用 BLEU 分数来测量生成的 API 序列的准确性。BLEU 分数衡量候选序列与参考序列(通常是人类书写的序列)的接近程度。它是机器学习和自然语言处理文献中广泛使用的机器翻译精度度量方法。在我们的 API 学习上下文中,我们将给定一个查询生成的 API 序列作为候选,将相同查询的人类编写的 API 序列(从代码中提取)作为引用。我们使用 BLEU 来测量生成的 API 序列与人类编写的 API 序列有多接近。
通常,BLEU 测量候选序列到引用的 n 克的命中量。计算方法如下:
其中每个 pn 为 n 克的精度.
我们比较了我们的方法与两种最先进的 API 学习方法的准确性,即带有模式挖掘的代码搜索和 SWIM。
为了获得给定查询的相关 API 序列,可以使用信息检索技术在代码语料库上执行代码搜索,然后利用 API 使用模式挖掘器在返回的代码片段中识别适当的 API 序列。
我们将 DeepAPI 与这种方法进行比较。我们使用 Lucene 对给定的自然语言查询执行代码搜索,使用 upminer 执行 API 使用模式挖掘。Lucene 是一个开源的信息检索引擎,已经被集成到许多代码搜索引擎中。和这些代码搜索引擎做的一样,我们将源代码视为纯文本文档,并使用 Lucene 构建源代码索引和执行文本检索。UP-Miner 是一个模式挖掘工具,它从代码片段中生成 API 序列模式。它首先对从代码片段中提取的 API 序列进行集群,然后从集群序列中识别频繁的模式。最后,它对频繁模式进行聚类以减少冗余。我们使用 UP- Miner 从基于 lucene 的代码搜索返回的代码片段中挖掘 API 使用序列。
SWIM 是一个最近提出的代码合成工具,它也支持基于自然语言查询的 API 序列搜索。给定一个查询,它使用统计词对齐模型将查询关键字扩展为相关 api 的列表。在可能的 API 列表中,SWIM 使用 Lucene 搜索相关的 API 序列。最后,它根据 API 序列合成代码片段。由于代码合成超出了我们的范围,我们只将 Deep- API 与 SWIM 的 API 学习组件进行比较,也就是说,从一个自然语言查询到一个 API 序列。
评估设置:我们首先使用 BLEU 评分评估生成的 API 序列的准确性。如 4.1 节所述,我们收集了一个包含 7519,907 个 hAPI 序列、注释 i 对的数据库。我们将它们分为一个测试集和一个训练集。测试集包括 10,000 对,而训练集由剩余的实例组成。我们使用训练集训练所有模型,并计算测试集中的 BLEU 分数。我们计算前 n 个结果中每个测试实例的最高 BLEU 分数。
结果:表 1 显示了 DeepAPI、SWIM 和 Code Search (Lucene+UP-Miner)的 BLEU 得分。每一列显示了一个方法的平均 BLEU 分数。结果表明,DeepAPI 生成的 API 序列具有更高的精度。仅检查前 1 名的结果时,DeepAPI 的 BLEU 得分为 54.42,高于 SWIM (BLEU=19.90)和 Code Search (BLEU=11.97)。比 SWIM 算法提高了 173%,比 Code Search 算法提高了 355%。对前 5 名和前 10 名的结果进行检验,也得到了类似的结果。评价结果验证了 DeepAPI 所采用的深度学习方法的有效性
我们还定性比较了 DeepAPI 在不同参数设置下的精度。我们分析了两个参数,即词嵌入维数和隐藏单元数。我们改变这两个参数的值,并评估它们对 BLEU 分数的影响。
在第三节,我们描述两个增强原 RNN Encoder-Decoder 模型 API 的任务学习:一种引起 RNN Encoder-Decoder 提出(3.1 节)和一个增强 RNN Encoder-Decoder 新的代价函数(3.2 节)提出的我们。我们现在评估使用原始 RNN 编码器-解码器模型构建的增强模型是否提高了 DeepAPI 的精度。
表 3 显示了三种型号的 BLEU 得分。基于注意力的 RNN 编译码器在 API 学习上优于基本的 RNN 编译码器模型。前 1 名、前 5 名和前 10 名(以 BLEU 评分计算)的相对提高率分别为 8%、5%和 4%。这个结果证实了我们方法中使用的基于注意力的 RNN 编码解码器的有效性。
表 3 还显示,与基于注意力的 RNN 编码器-解码器模型相比,使用新的代价函数的增强模型得到了更好的结果。前 1 名、前 5 名和前 10 名(以 BLEU 评分计算)分别提高了 4%、2%和 1%。从图 7 可以看出,在不同的参数设置下,增强模型的性能略有不同,最优 λ 约为 0.035。实验结果证实了所提出的代价函数对 RNN 编码器-解码器模型的有效性。
API 学习的一个主要挑战是代码和自然语言描述之间的语义鸿沟。现有的基于信息检索的方法通常都有一个词袋假设,缺乏对自然语言和代码的高级语义的深刻理解。我们已经确定了 DeepAPI 解决这个问题的三个优势。
本文所研究的 api 均为 Java api,本文所研究的 api 及相关项目均为 JDK api。因此,它们可能不能代表其他库和编程语言的 api。在未来,我们将把这个模型扩展到其他库和编程语言。我们从文档注释的第一句中收集了 API 序列的注释。评论中的其他句子也可能提供丰富的信息。此外,开头的句子可能有噪音。在未来,我们将研究一种更好的 NLP 技术来为代码提取注释。
在原始的游泳论文中训练数据集,使用 Bing.com 的 clickthrough 数据进行评估。这类数据对大多数研究人员来说并不容易获得。为了公平和容易的比较,我们在 GitHub 和 Java 文档中收集的数据集上评估 SWIM(与评估 DeepAPI 相同)。我们使用从文档注释中收集的 API 序列的注释来训练模型。未来,我们将在包括 Bing 点击数据在内的各种数据集上评估 SWIM 和 DeepAPI。在未来,我们将进行更精确的程序分析,创建更好的训练集。
在代码搜索方面有大量的工作。例如,McMillan 等人提出了一种名为 Portfolio 的代码搜索工具,可以检索和可视化相关功能及其用法。Chan 和 Cheng 设计了一种方法来帮助用户找到仅给出简单文本短语的 api 的用法。Lv 等人,提出了 CodeHow,这是一个代码搜索工具,包含了扩展的布尔模型和 API 匹配。他们首先通过将查询与 API 文档进行匹配来找到与查询相关的 API。然后,它们通过考虑与代码检索中的查询相关的 api 来提高代码搜索性能。如第 6 节所述,DeepAPI 与代码搜索技术的区别在于,它不依赖于信息检索技术,并且可以理解单词序列和查询语义。
与从自然语言查询中生成 API 序列不同,有许多技术关注于此
人们提出了许多相关的技术来从自然语言查询中生成代码片段。例如,Raghothaman 等人提出了 SWIM,这是一种代码合成技术,使用 Bing 搜索日志将用户查询转换为感兴趣的 api,然后合成描述这些 api 使用的惯用代码。SWIM 有一个组件,可以根据用户的自然语言查询生成 API 序列。我们的方法和游泳在许多方面不同。首先,SWIM 使用统计词对齐生成 api 包。该词对齐模型不考虑自然语言查询中的词嵌入和词序列,在查询理解上有一定的局限性。其次,为了生成 API 序列,SWIM 使用一袋 API 从代码存储库中搜索 API 序列。它没有考虑不同 api 的相对位置。Fowkes 和 Sutton 建立了概率模型,共同为简短的自然语言语句和源代码片段建模。我们的方法和他们的方法的主要区别是两方面的。首先,他们使用一个词袋模型来表示不识别词序列的自然语言句子。其次,他们使用传统的概率模型,不能识别语义相关的词。
最近,一些研究人员探索了将深度学习技术应用于源代码的可能性。利用深度学习的典型应用是提取源代码特征。例如 Mou 等人,提出学习用于深度学习任务的向量表示源代码。Mou 等人也提出了基于树结构的卷积神经网络用于编程语言处理。深度学习也被应用于代码生成。例如,Mou 等人提出使用 RNN 编码器-解码器模型从自然语言用户意图生成代码。他们的结果表明了将深度学习技术应用于从高度同构数据集(简单的编程任务)生成代码的可行性。深度学习也被应用于代码补全。例如 White 等人将 RNN 语言模型应用于源代码文件,证明了其在预测软件令牌方面的有效性。Raychev 等提出应用 RNN 语言模型来完成部分有洞的程序。在我们的工作中,我们探索了深度学习技术在 API 学习中的应用.
在本文中,我们应用了一种深度学习方法,RNN 编码器-解码器,为给定的 API 相关的自然语言查询生成 API 使用序列。我们的实证研究表明,提出的方法是有效的 API 序列生成。虽然深度学习在其他领域显示出了希望,但我们是第一个观察到它在 API 学习中的有效性的人。
本文所描述的基于 RNN 编码器-解码器的神经语言模型对其他软件工程问题如代码搜索和 bug 定位有一定的帮助。在未来,我们将探索该模型在这些问题上的应用。我们还将研究从生成的 API 序列合成示例代码。