Incorporating External Knowledge through Pre-training for Natural Language to Code Generation论文笔记

Abstract

开放域代码生成是想要根据自然语言(NL)生成通用编程语言的代码(例如Python)。因为我们发现开发人员在编写代码时通常会在网络上查找,作者探索了将两种外部知识整合到 NL-to-code的有效性:

  • 从在线编程 QA 论坛 StackOverflow中自动挖掘NL-code对
  • 从编程语言API文档中自动挖掘NL-code对
    评估表明,将这两个来源的NL-code对与数据增强和基于检索的数据重采样相结合的方法,在代码生成测试平台 CoNaLa 上将当前最先进的技术的绝对 BLEU 分数提高了 2.2%。

1 Introduction

语义解析,是从自然语言的意图生成机器可执行的表示,通常侧重于有限域,或具有有限操作符集的特定领域语言。然而,最近出现了讲语义解析应用于自动生成编程语言源代码的趋势。该领域目前的工作使用了各种模型,尤其是神经结构,以实现良好的性能。

但是,通用语言(如Python)的开放域代码生成是困难的。例如:从C盘的目录中选择一个随机文件,期望的python代码为random.choice(os.listdir(‘C:\\’))去实现目的。这不仅要生成语法正确的代码,还需要对某些API和库的调用(并有可能组合使用),从而实现所需的功能。正如作者在第三节中展示的,目前的代码生成模型仍然难以生成参数位置正确的函数调用。例如,Yin和Neubig根据上述意图,使用基于转换的(transition-based)方法生成Python抽象语法树的模型可以保证生成语法正确的代码,但仍然是不正确输出random.savefig(random(compile(open(‘C:\\’))+100).isoformat()).

对于训练更准确的代码生成模型,一个已知的难题是现有的人工标记的数据集中可用于训练的数据是有限的,这些数据集不能够涵盖一些复杂功能可以在代码中实现的无数方式。然而,通过额外的人工标记来增加标记数据集的规模的代价太大。还有一种情况是开发者很少会参考NL和code配对的示例,而是从网络上获取外部资源并将其修改为需要的形式。受到上述事实的启发,作者建议通过一种新的训练策略来提高代码生成模型的性能:使用从外部资源中自动提取的数据(如API文档)对模型进行预训练,然后在手动标记的小型数据集上进行微调(2.1节)。该方法如图1所示,结合了从StackOverflow中自动挖掘NL-code代码对(2.2节)以及通用软件库的API文档(2.3节)。

该方法虽然与模型无关,但普遍使用。作者在一个最先进的基于语法的代码生成方法TranX(Yin和Neubig)上实现了它。在CoNaLa基准上进行实验,发现通过我们提出的方法结合外部知识可以将BLEU分数从30.1提升到32.3,胜过之前最先进的方法。对通过我们的模型生成的代码片段样本进行定性分析,发现生成的代码更有可能使用正确的API调用来实现所需的功能,并能够以正确的顺序排列参数。
在这里插入图片描述

2 Approach

2.1 Over-arching Framework

我们在这项工作中整合外部知识的总体策略是
(1) 在从外部资源获得的 NL-code对上预训练模型,然后
(2) 在一个小的手动标记的语料库上进行微调(fine-tune)。
这允许模型首先学习大量潜在的噪声数据,同时最终根据我们想要在测试时建模的实际 NL 和代码进行定制。为了执行此预训练,我们需要将外部数据源转换为 NL -code对,我们将在以下部分中介绍如何执行此操作。

2.2 Mined NL-code Pairs

当开发人员编写代码时,大多数人不可避免地会在网上搜索符合他们意图的代码片段,其中,最著名的在线资源是StackOverflow,一个流行的编程QA论坛。然而,并不是所有的StackOverflow上的代码都能符合提问者的意图——有些可能定义变量的方法或导入必要的库,而其他的代码可能完全不相关。Yin等人建议训练一个分类其来决定过一个NL-code对是否是有效的,从而产生一个大型但有噪音的NL意图和源代码片段的并行语料库。该方法分配的概率可以作为置信度,代表自动挖掘的NL-code对的质量。我们使用这些挖掘到的NL-code对作为外部知识的第一来源。

2.3 API Documentation

第二,处于这样的直觉,即现代软件开发大部分都依赖于库。开发人员经常求助于编程语言和软件库参考,我们考虑使用API文档作为另一个外部知识的来源。图2显示了Python标准库中的一些例子。它包含了对库、类、方法、函数和参数的描述,这些文档已经是由代码签名及其描述组成的配对形式。然而,文档中显示的签名主要提供了API的原型而不是出现在源代码中有效的API用法。为了清晰,文档中的文字描述往往很冗长,而开发人员提出的问题通常很简洁。我们使用一些启发式方法来转换这些内容,以模拟代码生成系统可能面临的真实输入。
在这里插入图片描述
大多数API在签名中定义了必要和可选的参数。在实际使用中,开发者通常不提供或只提供其中的一些参数。为了模拟这种情况,我们按照正确的语法,对可选参数的所有可能组合(有一定的限制)进行排列组合,并将他们附加到需要的参数上。对于类构造函数和方法,我们根据类的名称创建一个启发式的变量名称,去存储实例化的类对象,并根据该变量调用方法。为了对创建的每个代码片段都进行简洁的描述,我们只保留了文档的第一句话,以及代码片段中每个参数的第一句话。在极少数情况下,在原始描述中找不到参数,我们将另一个包含这些参数的语句添加到NL片段的末尾,以确保代码中的所有变量都包含在NL中。附录A中详细介绍了这一过程。

2.4 Re-samling API Knowledge

不同来源的外部知识具有不同的特点。
从StackOverflow中自动挖掘的NL-code对很好地代表了开发人员可能会提出的问题,但不可避免地会有噪音。
API文档中的NL-code对是干净的,但可能会与开发人员提出的实际问题有差别。例如,库curses拥有比json多得多的API条目(178vs17),而json被更频繁地询问和使用。这种预训练和微调之间的分布变化会导致性能下降,如后面3.2节所示。
为了缓解这个问题,我们提出了一种基于检索的重采样方法,以缩小API文档和我们想要建模的实际NL-code对之间的差距。我们使用人工标注数据Dann和挖掘的数据Dmine来模拟NL-code对的分布,因为它们都是由真实用户产生的。对于这个实际使用分布中的每个样本,我们都会从api文档Dapi获得的对集合中检索k个NL-code对,并累加正在检索的每个y∈Dapi的代码对的频率:在这里插入图片描述
x是真是用户产生的代码对,包含Dann和D mined
其中R(x,Dapi,k)是根据NL意图或代码片段从Dapi中检索出给定x的前k个最相似的样本。
δ(·)是Kronecker delta(克罗内克)函数,如果内部条件为真,则返回1,否则返回0。
我们使用Elasticsearch中实现的BM25检索算法(Jones等人,2000年)。我们取此频率并计算平滑后的概率分布,使用变量τ∈[1,∞]表示:
在这里插入图片描述
当τ从1变为∞时,P(Y)从与频率成正比的分布转变为均匀分布。利用这个分布,我们可以从API文档中抽取更有可能是广泛使用的API调用的NL-code对。

3 Experiments

3.1 Experimental Settings

Dataset and Metric:虽然该方法一般适用,且与模型无关,但出于评估目的,我们选择CoNaLa(Yen等人,2018年)作为人工注释的数据集(2,179个训练样本、200个开发样本和500个测试样本)。它涵盖了具有不同意图的关于Python的真实英语查询。我们使用与CoNaLa基准相同的评估指标,语料库级别的BLEU根据测试集中的目标代码输出进行计算。
Mined Pairs:我们使用CoNaLa-Mined(Yen等人,2018年)数据集,从StackOverflow自动挖掘的Python中的600K个NL-code对(2.2)。我们根据它们的置信度进行排序,发现大约前100K个样本在代码正确性和NL-code对应方面比较好。因此,我们选择前100K对进行实验。
API Documentation Pairs:我们解析了Python 3.7.5发行版中包含的所有模块文档,包括库、内置类型和函数。经过预处理(2.3),我们从Python API文档创建了大约13K不同的NL-code对(无需重新采样)。为了公平比较,我们还为重新采样设置抽样了相同数量的配对(2.4)。
Methods:我们选择当前最先进的自然语言到代码生成模型Tranx(Yen and NeuBig,2018)和假设重新排序(Yen和NeuBig,2019)作为基础模型。此外,我们引入了长度归一化(Cho等人,2014年),防止定向搜索倾向于更短而不是更长的结果。

  • MAN指的是完全使用CoNaLa进行训练。
  • MAN+MINE指的是先对挖掘的数据进行预训练,然后在CoNaLa上进行微调。
  • MAN+MINE+API将挖掘的数据和API文档结合起来进行预训练。
    - 作为与我们的基于分布的方法(由dist.,2.4表示)的比较,
    - 我们还尝试直接从API文档中检索前5个NL-code对(表示为DIRECT)。
    Implementation Details:我们在重采样中对k={1,3,5}和τ={1,2,5}进行了实验,发现k=1和τ=2的性能最好。我们遵循Tranx中最初的超参数,只是在预训练和微调中分别使用了64和10的batch大小。

3.2 Results

结果如表1。

  1. 我们首先可以看到,通过在预训练期间纳入更多噪声挖掘的数据,由于通过更大的训练集增加了覆盖率,因此会有微小的改进。

  2. 此外,如果我们添加从API文档中获取的对进行预训练,而不重新采样,则性能会下降,从而验证了2.4中提到的分布迁移的挑战。

  3. 比较两种重采样策略 direct vs. dist,以及两种不同的检索目标 NL intent vs. code进行比较:

  • 我们可以看到dist将code作为检索目标时,性能会更好。
    • 我们希望使用code来检索pairs的性能更好,因为它使生成目标,即code snippet,更接近真实世界的分布,从而更好地训练解码器。
    • 部分原因还在于API描述与开发人员提出的问题本质上不同(例如,他们有更冗长的措辞),导致意图检索不那么准确。
  1. 最后,我们将假设重新排序(hypothesis reranking)应用于基本模型和我们的最佳方法,发现我们提出的融入外部知识的策略所提供的改进大多与假设重新排序所提供的改进是正交的。
    在这里插入图片描述
  • 在展示了我们建议的重新采样策略的有效性之后,我们对使用较多的API与较少使用的API的性能感兴趣,因为这可能会影响整体性能。我们使用字符串匹配启发式获得数据集中使用的标准Python API,并计算每个数据实例中API的平均使用频率。然后,我们从500个测试样本中选择API使用频率最高的200个实例和最差的200个实例。在预训练中添加API文档前后,BLEU在两个拆分上的得分都出现了改善:高频拆分从28.67分提高到30.91分,低频拆分从27.55分提高到30.05分,这表明重采样虽然会向高频API倾斜,但通过适当的平滑温度实验,仍然有助于低频API性能的提高。除了使用BLEU评分进行整体评估外,我们还对生成的哪些类型的标记进行了更细粒度的分析。我们在生成代码的抽象语法树上应用启发式算法来识别测试数据中API调用和变量名的令牌,并计算每个令牌级的准确度。添加外部资源后,API调用准确率从31.5%提高到36.8%,变量名准确率从41.2%提高到43.0%,这意味着API调用和参数使用都得到了改善。

3.3 Case Study

v删除空格以外的非字母数字字符删除空格以外的非字母数字字符删除空格以外的非字母数字字符删除空格以外的非字母数字字符删除空格以外的非字母数字字符在这里插入图片描述删除空格以外的非字母数字字符
从C盘的目录内容‘C:\’中选择一个随机文件。

我们在表2中进一步显示了从基线和我们的最佳方法中选择的输出。通常,我们可以看到代码生成任务的仍然具有挑战性,特别是对于需要嵌套或链接的API调用或具有更多参数的函数的更复杂的意图。
vanilla模型已经可以生成基本函数和将字符串/变量复制到输出,但我们观察到,合并外部知识主要通过两种方式改进结果:1)更好地放置API的参数,2)更好地选择应该为特定目的使用哪个API调用。

  • 更好的API参数位置:在第一个示例中,可以看到,尽管基线使函数调用“open()”正确,但它无法生成写入模式的正确的第二个参数,而我们的方法能够成功生成适当的“w”。
  • 更好的API调用:在第二个和第三个示例中,我们可以看到基线使用了错误的API调用,有时会自己“拼凑”API(例如,“random.savefig()”)。然而,我们方法的输出虽然不完美,但在生成实际存在的、对意图有意义的正确API调用方面要成功得多。

仔细观察,我们可以发现挖掘示例和API文档的添加都可能带来了改进。从API文档添加的“open()”函数的示例使用默认模式“r”,因此学习“w”参数的含义是由于添加了挖掘的真实示例,但是学习参数放置(第一个文件名作为字符串,第二个简写模式标识符作为字符)可能是从API文档中发生的。在其他示例中,“random.choice()”和“re.sub()”都是Python标准库API,因此它们包含在API文档示例中。

4 Conclusion and Future Work

  • 我们提出了一种基于数据扩充、检索和数据重采样的模型无关方法,将外部知识融入到代码生成模型中,在CoNaLa开放域代码生成任务上取得了最先进的结果。
  • 在未来,通过使用测试用例自动执行生成的代码进行评估可能是评估代码生成结果的更好方法。将我们的重采样过程推广到zero-shot场景也可能很有用,在这种场景中,程序员编写了一个库并对其进行文档记录,但还没有人使用过它。例如,开发人员可以提供每个记录的API用法的相对估计值来指导重新采样;或者我们可以在语义方面找到与每个API调用最近的邻居,并使用现有的使用统计数据作为估计值来指导重新采样。

A API Documentation Pre-processing

在这里,我们将详细描述用于API文档预处理的启发式算法。目标是以API文档为源获取NL-code对。

A.1 Arguments

大多数API都会有参数,要么是必需的,要么是可选的。对于所需的参数,我们将其保留为“原样”。我们通过排列和采样来处理两种类型的可选参数,位置参数和关键字参数。在Python文档中,可选的位置参数用“[…,[…]]”括起来。嵌套的方括号通常用于表示多个可能的可选位置参数。另一种类型的可选参数是使用key=default形式的关键字参数实现的。

在实际使用中,开发人员通常只提供这些参数中的一部分或不提供。为了模拟这一点,我们排列了可选参数的所有可能组合,并将它们附加到所需的参数之后。
- 例如,如果文档中的代码签名写为“Collections.deque([iterable[,maxlen]])”,那么我们将生成所有3种可能的用法:“Collection tions.deque()”、“Collection tions.deque(Iterable)”和“Collection tions.deque(iterable,maxlen)”。
- 对于像“heapq.nmax(n,iterable,key=one)”这样的关键字参数,我们还将另外包括“heapq.nmax(n,iterable)”。
- 具有n个可选位置参数的函数的排列总数为n+1,且2n=?n0?+?n1?+...+?n n?
对于具有n个可选关键字参数的函数,这将导致具有许多可选关键字的函数的样本数量呈指数级增长。由于注意到开发人员很少指定所有可选参数,而倾向于使用默认值,我们只保留可选参数数量最少的前10个排列。

A.2 Class Initializers and Methods

其他启发式方法用于转换与类相关的代码签名,以模拟实际用法。

  • 对于文档中的类初始化器,我们使用类名的第一个字符来构造一个变量名为小写的赋值语句来存储实例化的类,例如d=collections.deque(iterable)
  • 对于类方法,我们在方法调用前面加上一个启发式创建的变量名,模拟实例化类上的实际方法调用,例如d.append(X)

A.3 Documentation

为了清楚起见,官方文档往往很冗长,而开发人员提出的真正问题通常都很简洁。因此,我们使用以下启发式方法在文档中只保留生成代码所必需的句子作为意图文本。

  • 我们保留第一句话,因为它通常描述API的功能。
  • 对于模拟API用法代码片段中的每个参数,我们在文档中包含通过字符串匹配提到该参数的第一句话。
  • 对于文档中没有提到的参数,我们在结尾处添加一句话:“使用参数‘arg name’…”以确保所有参数都被覆盖。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值