自动编程

“自动编程”这个名字听起来就让人浮想联翩,难道人工智能真的要走程序员的路,让程序员无路可走?本文的“自动编程”是一个从自然语言到编程语言的翻译任务,也就是用户对于一个功能用自然语言描述出来,然后自动编程系统能够将自然语言描述转换成具有相同功能的程序。自动编程的任务目标是在理解自然语言描述语义的基础上,将语义解码成机器可理解的逻辑表示,这属于自然语言处理任务中语义分析任务的一种。语义分析是将自然语言描述转换成机器可理解的逻辑表示,比如一阶逻辑表示、λ演算等逻辑形式,Python、Java等程序,SQL表达式,shell表达,以及语义图等形式。

modescope 自然语言转换为sql查询 自然语言生成代码_数据集

图1 自动编程示意图

自动编程这个问题,有几个挑战:

1. 如何处理自然语言与编程语言之间的语义差异?自然语言和编程语言虽然都有语法信息和结构信息,但是两者之间还是存在比较大的区别。比如,打乱自然语言序列中一些字符的顺序并不会影响语义上的理解,然而程序就不一样了,代码序列各个字符的位置都有严格的要求,打乱顺序不仅会导致语义上的理解错误,甚至会使得该段代码就是错误的。自然语言虽然也有主、谓、宾、定、状、补等语法,但是编程语言的语法约束则更为严格。

2. 如何建立程序代码的语言模型?在程序中,代码以行为基本单位组织起来,一行代码表示一个操作,多行代码的数据依赖和逻辑依赖组织成代码块。但是程序以字符串序列的形式表现,每个字符的位置是有确定性意义的,如果采用传统的RNN、LSTM网络如何处理字符的绝对和相对位置关系是一个挑战。

3. 如何保证模型训练的时候,自然语言描述和程序代码的语义一致性。

2 GANCoder总体结构

针对上述的几个挑战,我们提出了GANCoder。GANCoder顾名思义就是一个对抗生成网络(GAN),GAN网络在图像生成领域很有名,DeepFake、ZAO这些项目就是GAN的应用,生成的图片已经能够以假乱真,其中ZAO还能够实现视频流的换脸。同样,我们的GANCoder也是在GAN的基础上实现的。GANCoder与其他的GAN网络一样,由生成器(Generator)和判别器(Discriminator)组成。Generator和Discriminator的对抗博弈训练使得彼此相互提高,这也是GAN的精髓之处。Generator和Discriminator关系可以用道高一尺,魔高一丈来概括。

在介绍GANCoder的具体结构之前,首先介绍一下对于程序的建模问题。在我们这个项目,采用抽象语法树的形式表示程序代码。对于高级程序设计语言,比如Java,Python,Scala等语言,程序在编译的时候都会编译成抽象语法树(AST)。采用抽象语法树有几个原因,第一,抽象语法树的结构信息丰富,能够充分表达程序代码的逻辑结构;第二,构建AST会用到程序设计语言的语法规则,一般来说都是上下文无关文法(CFG)。上下文无关文法能够在构建抽象语法树的过程提供语法信息的约束,可以保证我们构建的抽象语法树在语法上的正确性;第三,AST是自然语言到程序之间的中间表示,如果我们需要代码序列,只要将抽象语法树解析成代码即可,一般高级程序设计语言都有这样子模块,只要调用相应的函数即可。

GANCoder主要有Generator和Discriminator组成,如图所示。其中Generator是一个Encoder-Decoder深度学习框架,其中Encoder负责将自然语言描述编码成中间语义向量,而Decoder则负责将前面Encoder的编码结果解码AST,最后只要把AST解析成程序代码片段。Generator的难点就是如何处理两种不同的语言,采用Encoder-Decoder框架的目的就是让Encoder和Decoder各自处理自然语言描述序列和编程语言序列,这样我们在处理问题的事就不用考虑两种语言模型的交互问题,简化了问题的复杂性,并且Encoder-Decoder模型也是机器翻译经常采用的框架。对于Discriminator而言,其任务则比较简单,它负责量化自然语言描述序列和抽象语法树的语义相似度。实质上,Discriminator是一个二分类器,判断AST和自然语言描述的语义是否一致。

modescope 自然语言转换为sql查询 自然语言生成代码_自然语言_02

图2  GANCoder总体构架图

基于CFGGenerator

Generator是GANCoder的重要组成部分,其是一个基于编程语言上下文无关文法的Encoder-Decoder框架。用户给出一个功能的自然语言描述,Generator则将它解析成具有相同功能的程序代码。Generator也是我们研究的目的,也是一个从自然语言到编程语言的翻译器。如图所示,是Generator的构架图。

Encoder-Decoder框架是一个比较灵活的深度学习模型,Encoder和Decoder并没有固定的形式,可以选用常见的CNN、RNN、LSTM等的网络。因此可以在自己的项目中根据问题的特征选择合适的模型。在GANCoder中,我们采用双向LSTM网络作为Encoder。双向LSTM网络,可以从左到右和从右到左两个方向对序列进行编码,最后将字符两个编码向量做拼接处理,即为当前字符的最后编码向量。采用双向LSTM网络将自然语言描述序列编码成中间语义向量,该向量也是Decoder解码生成程序的先验知识。


图3  GANCoder生成器

Decoder相对于Encoder来说,就比较复杂了。上文我们也说了对于程序的建模采用的抽象语法树的形式,因此Decoder将Encoder编码的语言向量解解码的过程也就是构建抽象语法树的过程。抽象语法树的生成,就是该程序设计语言上下文无关文法规则的运用,抽象语法树的每一个节点的结构都是语法规则产生式的抽象逻辑。因此在Encoder的设计上,我们采用的是基于编程语言的上下文无关文法的单向LSTM网络。将LSTM网络展开,时间轴上的每一个节点其实就是一次文法规则的运用。在生成抽象语法树过程中,上下文无关文法提供了LSTM网络解码的先验知识和语法约束,因此最后构建的抽象语法树在语法上的正确性能够得以保证。

modescope 自然语言转换为sql查询 自然语言生成代码_语法树_03

上图展示了自然语言描述序列“Callthe function sorted to sort the list my_list, andreturn the result in reverse order”翻译成代码序列“sorted(my_list,reverse=True)”的过程。从图中我们可以看出抽象语法树的构建过程,以实线为时间轴,自顶向下,自左向右的顺序连续生成抽象语法树的节点。图中的虚线表示父节点与子节点之间的联系,在生成的时候,父节点会向子节点提供语义信息。由于上下文无关文法的特点,抽象语法树的兄弟节点之间没有语义联系。此外,抽象语法树的叶子节点表示程序代码序列的字符,而非叶子节点则表示上下文无关文法的产生式的运用规则。

modescope 自然语言转换为sql查询 自然语言生成代码_自然语言_04

图4 Decoder构建抽象语法树

深度学习的注意力机制借鉴了人类的注意力思维方式,被广泛应用在自然语言处理,图像分类以及语音识别等各种不同类型的深度学习任务中,并且取得了显著的成果。因此,Decoder在解码的过程中也采用了注意力机制,计算抽象语法树节点与自然语言描述序列字符之间的语义联系。这种方法能够加强自然语言描述序列与程序代码语义联系;此外,注意力机制的运用,将自然语言描述序列化的结构与抽象语法树树型结构进行对齐。

在处理机器翻译或者本文中自动编程问题的时候,会遇到一些问题,那就是在目的语言中生成源语言中的某些字符。虽然也可以让生成模型产生相应的字符,但是这无疑会增加问题的难度,因此最好的方式是能够将该字符从源字符串中复制到目的序列中。在GANCoder中采用PointerNet的Copy机制解决这一问题。在抽象语法树中,叶子结点一些字符就是直接从自然语言描述中复制过来的。上述的例子中,函数名sorted以及列表名my_list都是在自然语言描述中出现的字符。Copy机制现在在双语翻译等任务中广泛使用,不仅能够增加翻译结果的流畅性,还能够解决OOM问题。

以上是对GANCoder的Generator的简单介绍。下面将介绍GANCoder的另外一个组成部分Discriminator。

基于Tree-LSTMDiscriminator

GANCoder的Discriminator的主要作用就是鉴别Generator生成的AST是否是符合自然语言描述语义序列的语义。Discriminator主要是量化生成的程序与自然语言之间的相似性。由于Generator是基于编程语言的语法信息进行解码的,因此生成的程序在语法来说是没有问题的。在传统的GAN 网络中,Discriminator的任务是判断Generator的生成结果是否服从原始数据的分布特征。在Discriminator中,对于自然语言描述的语义编码仍然采用Generator中Encoder的方法,使用双向LSTM网络将整个序列编码成中间语义向量;通过编码AST来得到程序的语义。Discriminator自底向上,从抽象语法树的叶子节点开始向上对整棵抽象语法树进行编码,最后的编码向量作为程序片段的语义向量。采用这种方法,可以自底向上学习到程序片段的语法和逻辑。Discriminator采用Tree-LSTM网络实现对抽象语法树的编码,如图3所示。然后预测自然语言描述语义向量与抽象语法树语义向量之间的相似程度。

modescope 自然语言转换为sql查询 自然语言生成代码_nlp_05

图5  树型LSTM

前面已经简单介绍了GANCoder的组成以及各个部分的主要功能。其实传统的GAN网络在生成离散数据的问题中面临了一个缺陷,就是Discriminator的梯度难以传递到Generator,因此在训练的过程中难以取得较好的结果。在GANCoder中我们采用Policy Gradient的方法去避免Discriminator到Generator梯度的后向传播问题。此外,对于GAN网络的训练,有几种方法,一是直接训练,从一开始Generator和Discriminator直接对抗博弈训练;而是先分别预训练Generator和Discriminator,让两者达到一定的准确率的时候在一起对抗博弈训练。一般来说采用第二种方法,因为GAN在训练的时候容易崩溃,而预训练可以给两者提供一定的先验知识。最后实验也是证明了这一点。

实验结果

GANCoder在Django,Jobs和ATIS三个数据集上进行了实验。表1首先对比了不同训练方式对于模型结果的影响。实验也证明预训练能够有效提高模型的准确率。

modescope 自然语言转换为sql查询 自然语言生成代码_nlp_06

modescope 自然语言转换为sql查询 自然语言生成代码_nlp_07

        表2对比了主流代码生成模型和本文提出的GANCoder模型。与传统的Encoder-Deocder 模型,比如 SEQ2SEQ 和SEQ2TREE相比,GANCoder在 Django 数据集上将准确率提升了 24.6% 和 30.3%。这两种模型在Jobs数据集上效果较好,而在另外两个数据集上的效果差强人意。ASN模型在ATIS数据集上可以达到最佳效果,却没有在另外两个数据集上的结果。而LPN+COPY模型和SNM+COPY模型在Django数据集上显示很好效果,却也同样没有在另外两个数据集上的结果。GANCoder尽管在某些特定数据集上与某模型相比还有一点差距,但是实验结果也显示性能相当。因此,GANCoder可以在各个数据集上都取得比较稳定的且令人满意的效果,是一种值得尝试的代码生成方法。

 

GANCoder是当前热门的生成模型GAN在自然语言处理的一次尝试,也为自动编程任务提供了一个新的思路。其实利用GAN来生成离散的程序代码数据确实是一个痛点,我们也在尝试用其他方法解决。另外,GANCoder目前只能实现自然语言与编程语言line2line的翻译任务,未来的研究应该是研究MultiLine2MultiLine的问题,对于这个问题,则要解决数据依赖和逻辑依赖的问题,也是未来研究的一个工作。

本文是实验室原创:本人负责把学弟成果进行展示。