Building Program Vector Representation for Deep Learning

题目: Building Program Vector Representation for Deep Learning
作者: Lili Mou, Ge Li, Yuxuan Liu, Hao Peng, Zhi Jin, Yan Xu, Lu Zhang
单位: Software Institute, School of EECS, Peking University
出版: arxiv:1409.3358v1 [cs.SE] 11 Sep 2014
(作者把数据集放在了programming.grides.cn,然而现在我打不开这个网站,因此样本数量不得而知)

解决的问题

提出了用于构建程序向量表示的“编码标准”,使得利用深度学习来进行程序分析成为可能。

动机

深度学习的架构可以用来进行包括以下任务在内的程序分析任务:

  • bug检测(例如Feature-based detection of bugs in clones)
  • 克隆检测(例如Clone detection in software source code using operational similarity of statements)
  • 代码检索(例如Source code retrieval using sequence based similarity)
  • 代码推荐(例如Optimizing a search-based code recommendation system)

然而深度学习和程序分析间存在着障碍:
所有的程序符号(例如抽象语法树中的结点)都是离散的,不能直接喂给神经网络。因此我们将所有的符号都映射为一个向量,也就是将符号进行分布式表示。比较好的方法是先不关注要解决的特定任务,无监督地对符号表示进行学习,然后再将它们喂给神经网络进行监督学习。这种方法常用于自然语言处理,然而由于自然语言和程序语言之间结构的不同,现有的NLP中的表示方法不适用于程序语言。因此需要为程序语言构建新的向量表示。

程序表示:“编码标准(coding criterion)”

粒度

在将程序中的符号(symbol)表示为分布式向量前我们需要明确“符号”的粒度,可能的粒度有字符粒度,token粒度等等。以下逐个进行分析:

  • 字符粒度:在这一粒度,我们将每个字符作为一个符号,这意味着我们需要将a-z, A-Z, 0-9和程序中的所有标点进行向量表示。这种表示用在程序语言上是不恰当的,它在处理程序关键字和标识符上就会遇到困难。
  • token粒度:这种表示是最类似于NLP的,我们将程序中的所有token(包括类型int,double等以及标识符doubles,func等等)表示为向量形式。然而这种表示也存在一些严重的问题。首先,与自然语言不同的是,程序语言中的token的数量是无限的;有些标识符可能只会出现很少数词,例如tmp,这样我们就会得到我们并不想要的稀疏表示;此外这种token粒度的编码可能并不能有效地编码信息,例如我们需要两个token来表示一对圆括号,用于表示不同项的优先级,其实这种信息在一种更加精简的结构(例如AST)中就不需要显式表示了。
  • AST结点粒度:下图是用pycparser解析得到的一段C代码的AST。这种表示比起token粒度更加精简。并且AST结点的类型是有限的,AST的树形结构也能够让我们学习程序的结构化信息。然而AST结点的表示也有它的缺点,例如所有的标识符都是一样的表示。我们假设程序的结构化信息能够在很大程度上抓住程序的语义。
  • 语句粒度,函数粒度或更高粒度:理论上来说,语句,函数甚至一个程序都可以被映射为实值向量。然而这种表示并不能直接用于训练。一种可行的方法是借鉴NLP中的成分语义学,然而目前成分语义学也不能精确地抓住语义。

形式化

我们的表示学习的基准是相似的符号应该有相似的表示,并且,在某些方面相似的符号在特定维度上有相似的值。也就是“理清变化的潜在因素”。
在我们的场景下,相似度是基于如下的知识。我们认为ID和Constant是相似的,因为它们都与数据引用相关。For和While是相似的,因为它们都与循环有关。
为利用AST抓住这种相似性,我们提出“编码标准”。它的主要思想是AST中的一个结点的表示应该通过一个神经网络层由它的孩子结点表示来“编码”。
我们将结点x的向量计为vec(x), v e c ( ) R N f ,其中 N f 是特征维度(在我们的实验中设为30)。对于每个非叶结点p以及它的直接孩子 c 1 , , c n ,它们的表示是 v e c ( p ) , v e c ( c 1 ) , , v e c ( c n ) ,它们的关系是:

v e c ( p ) t a n h ( i = 1 n l i W i v e c ( c i ) + b )

其中 W i R N f × N f 是结点 c i 的权重矩阵, b R N f 是偏置项。 W i 的权重是由 c i 下的叶子数决定的。
l i = # l e a v e s   u n d e r   c i # l e a v e s   u n d e r   p

AST中不同的结点会有不同数量的孩子,所以 W i 的数量不好确定。以往有两种方法解决这个问题,一是动态池化,对于 v e c ( c i ) 的每个维度我们取总和或最大值,这样就只需要一个权重矩阵了,这和CBOW模型是一样的。这种方法的缺点是c的位置信息会被完全丢失。另一种方法是对于每个位置赋予一个不同的矩阵。这种方法用在依赖树上很有效果,但是我们场景里权重实在是太多了。
我们利用了一种叫连续二叉树的模型,模型中只有两个权重矩阵 W l W r 。我们的权重矩阵是这两个矩阵的结合。也就是说不管结点的孩子数有多少,我们将其看成一棵二叉树。如果p包含 n 2 个孩子,那么对于 c i
W i = n i n 1 W l + i 1 n 1 W r

如果n=1,
W i = 1 2 W l + 1 2 W r

有了这两个矩阵,结点的位置信息就能够编码进网络中了。
现在我们就可以通过欧式距离的平方来计算相似度了:
d =∥ v e c ( p ) t a n h ( i = 1 n l i W i v e c ( c i ) + b ) 2 2

根据我们的“编码准则”,d越小越好,然而我们并不能直接将上式最小化。为解决这个问题引入了负采样方法。对于每个样本x,一个新的负样本 x c 被生成。因为 x c 违反了数据的模式,它就需要有一个比d大的距离。对于我们的程序表示学习,我们从每个训练样本中随机选择一个符号,然后将它用随机选择的一个符号来代替。目标就是 d c 应该至少为 d + Δ ,其中 Δ 是边界,通常选择为1。这样损失函数就是
J ( d ( i ) , d c ( i ) ) = m a x { 0 , Δ + d ( i ) d c ( i ) }

为防止模型过拟合,我们可以为模型加上 l 2 正则。
我们采用加了动量的随机梯度下降算法作为优化算法对模型进行训练。

实验

我们首先利用近邻查询和k-means聚类来评估我们的表达。随后进行有监督的程序分类任务。

近邻查询和k-means聚类

为了验证AST中相似结点具有相似的向量表示。对每个查询,我们利用欧式距离来对所有其他符号进行分类。结果如下表所示:

我们可以看到ID和Constant距离很近,这是合理的因为它们都和数据引用相关。相似的还有ArrayRef和BinaryOp,它们都与数据的处理有关。If, For, While, Break也彼此类似。FuncDecl, ArrayDecl, PtrDecl也彼此类似。并且这几组互相差别很大。
为了进一步证明上面的结论,我们进行了k-means聚类。k设定为3,结果如下所示:

监督学习评估

我们将我们学习到的表达喂给TCNN来进行程序的分类。数据集来自一个OJ系统,其中包含大量的学生的编程问题。我们从其中挑选了4个问题来实现我们的程序分类。我们的目标就是输入一个程序,然后输出此程序属于4个问题中的哪一个。
下图是前40个epoch的训练和验证曲线,每个epoch是对所有样本的一次迭代。X轴是训练过程中的epoch数,Y轴是交叉熵损失。

我们将TCNN与程序分类任务的其他方法进行了对比实验。其他方法使用了词袋模型,分别利用了逻辑回归和RBF核的SVM,实验结果如下图所示:

扫描二维码关注公众号,回复: 927836 查看本文章

未来工作

程序建模的不同方向

除了树形表示之外,程序还可以用语句序列来表示。这种表示方法在API使用模式挖掘中已经被应用。我们也可以在这样的表示上应用深度学习。此外,将程序视作2维信号也是一个新颖的方法。缩进和换行本身就包含了程序的语义信息。因此可以利用计算机视觉中的方法来进行深度学习。

程序先验知识与网络架构的结合

我们提出的TCNN就是将程序AST的局部特性先验知识与网络架构的一次结合。此外,传统程序分析技术也可以于神经网络结合,现在来看两者的结合似乎还很困难,但是一旦结合成功两者都将获益。

猜你喜欢

转载自blog.csdn.net/m0_37924639/article/details/80224922