【论文阅读笔记1-用于代码搜索的多模态表示学习】Multimodal Representation for Neural Code Search

Multimodal Representation for Neural Code Search

论文地址:https://ieeexplore.ieee.org/document/9609178/

摘要

语义代码搜索是关于为给定的自然语言查询寻找语义相关的代码片段。在最先进的方法中,代码和查询之间的语义相似性被量化为它们在共享向量空间中的表示距离。在本文中,为了改进向量空间,我们在简化形式的AST上引入了树序列化方法,并为代码数据构建了多模态表示。我们使用大规模多语言的单一语料库进行了广泛的实验:CodeSearchNet。我们的结果表明,我们的树序列化表示和多模态学习模型都提高了代码搜索的性能。最后,我们定义了面向代码数据的语义和句法信息完整性的直观量化度量,以帮助理解实验结果。

1. 前言

在现代社会中,软件系统是不可或缺的,并且已经无处不在。它代表了广泛的应用程序,如代码搜索,这是搜索现有代码片段的任务。当开发或维护软件时,人们倾向于重用现有的支架或从实际使用示例中学习,而不是浪费时间重新发明轮子。一方面,代码搜索可以帮助程序员的日常工作。另一方面,它加强了开源社区的基础设施,比如GitHub。与使用现代搜索引擎的通用搜索相比,有一种高效和有效的代码搜索方式是有益的[1]。

语义代码搜索的任务是为给定的自然语言查询检索语义上最相关的代码片段。输入数据是一个查询,输出数据是代码片段的有序列表。就文本含义而言,排名较高的代码片段应该更类似于查询。在最先进的方法中,代码和查询数据被表示为相同长度的向量,因此它们的语义相似性的计算被转换为向量距离测量。

我们建议利用多模态代码表示来改进语义代码搜索。模态是指信息存在的渠道,多模态是指多种模态,即多种类型的信息。我们引入树序列化表示,以研究它们是否比令牌表示信息量更大,以及使用它们作为附加输入是否有益。树序列化表示是通过解析代码生成的数据输入到树结构中,然后将树序列化为序列。我们利用多模态学习来揭示多模态表示的能力,并通过与CodeSearchNet语料库上的给定基线进行比较来评估我们的方法[2]。此外,我们定义了两个直观的度量,即链接覆盖率和节点覆盖率,来分别量化各种代码表示所传达的句法和语义信息的完整性。

在我们的实验中,考虑到上下文信息,SelfAtt模型是最强的模型。在树序列化表示中,基于遍历的表示总是比基于采样的表示表现得更好。基于我们的结果,树遍历表示最多提高了16.88%的MRR分数。更多的结果表明,我们的多式联运方法超过基线5.23%至15.70%。与相应的单模态表征相比,多模态表征带来的改善最多为17.82%。此外,我们建议采用树表示以及多模态学习进行代码搜索。

本文的贡献如下:

  1. 我们提出了简化语义树,这是一种专门设计的抽象语法树形式,用于丰富程序的语义表示。基于这种新颖的树结构,我们系统地定义和评估了几种序列化方法。
  2. 我们证明了多模态学习对于代码搜索任务是有效的。我们使用我们的树序列化表示比较了单模态和多模态代码搜索的表示能力。据我们所知,这是第一次使用树序列化序列作为代码搜索的模态。
  3. 我们定义了两个简单而直观的量化指标,链接覆盖率和节点覆盖率,来衡量各种表示形式的语义和句法信息的完整性。它们在揭示代码搜索模态的有效性方面表现可靠。

本文其余部分的结构如下。第二节介绍了背景资料。在第三节中,我们提出了多模态表示的方法。在第四节中,描述了实验方法。在第五节中,显示并解释了实验结果。在第5-4小节中,讨论了对有效性的威胁。第六节回顾了相关工作和研究背景。第七节对整个工作进行了总结。

2. 背景

2.1 代码搜索

代码搜索的规范任务是为给定的自然语言查询找到最相关的代码片段。代码搜索引擎可以用信息检索技术和神经技术来制作[3]。在本文中,我们关注后者。如清单1所示,代码——查询对是一段自然语言查询和相应的源代码。查询可以是目标代码片段的简短文档,比如“向成员发送生日消息”。在整个工作中,我们将源代码标记称为“代码序列”,将查询标记称为“查询序列”。代码和查询序列用于训练神经代码搜索模型。

在这里插入图片描述
如清单1所示,代码-查询对是一段自然语言查询和相应的源代码。查询可以是目标代码片段的简短文档,比如“向成员发送生日消息”。在整个工作中,我们将源代码标记称为“代码序列”,将查询标记称为“查询序列”。代码和查询序列用于训练神经代码搜索模型。

2.2 用于代码搜索的连体网络

连体网络是一种人工神经网络,用于使用相同的编码器测量相同类型的两个输入之间的相似性[4]。伪连体网络更加灵活,因为它旨在用不同的编码器测量不同数据类型的相似性[5]。代码搜索的模型架构遵循使用伪连体网络[6]的实践,如图1所示。在该体系结构中,代码和查询序列分别被馈送到相应的编码器中以被转换成向量。训练目标是最小化相关代码和查询向量之间的距离。代码搜索模型使用向量之间的余弦距离来度量相似性。一旦经过训练,与给定查询语义最相关的代码片段是那些向量最接近通过余弦距离测量的查询向量的代码片段。学习目标是确保语义相似的向量尽可能接近。三重损失是基线和我们的模型中使用的目标函数。它优化查询向量,使其接近相应的代码向量,但远离其他代码向量。在训练过程中,每个代码——查询对(ci,qi)和相应的干扰器代码片段cj被馈送到代码编码器Ec和查询编码器Eq中。培训目标是尽量减少以下损失:
图2 暹罗网络
L o s s = − 1 N ∑ i log ⁡ ( exp ⁡ ( E c ( c i ) ⊤ E q ( q i ) ) ∑ j exp ⁡ ( E c ( c j ) ⊤ E q ( q i ) ) ) \mathrm{Loss}=-\frac1N\sum_i\log\left(\frac{\exp\left(E_c\left(\mathbf{c_i}\right)^\top E_q\left(\mathbf{q_i}\right)\right)}{\sum_j\exp\left(E_c\left(\mathbf{c_j}\right)^\top E_q\left(\mathbf{q_i}\right)\right)}\right) Loss=N1ilog jexp(Ec(cj)Eq(qi))exp(Ec(ci)Eq(qi))

三元组损失函数旨在最大化该对的代码ci和查询qi编码的内积,同时最小化目标代码片段ci和其干扰代码片段cj i 6=j之间的内积[2]。(ci,qi)和(cj,qi)分别表示阳性和阴性样本。对于用于代码搜索的暹罗网络,有各种各样可能的编码器。专用于顺序数据的典型编码器包括:

  • NBoW:神经单词袋(NBoW)简单地计算所有单词嵌入的加权平均值,以获得作为整个语义表示的句子嵌入[7,8]。
  • 1D-CNN:卷积神经网络(CNN)[9]使用卷积运算来分析不同大小感受野中的上下文信息。1D-CNN是指一维序列数据的模型。
  • Bi-RNN:递归神经网络(RNN)[10]使用时间序列形式的隐藏层来捕捉依赖关系。双RNN连接向前和向后方向的嵌入。
  • SelfAtt:基于Transformer model的模型使用自我注意机制和BERT的位置嵌入方式从上下文信息中学习[11,12]。

2.3 多模态学习

模态是指某种类型的信息存在的方式。例如,为了从绵羊中识别牧羊犬,我们可以充分利用各种形式的数据,如颜色、声音和运动模式的特征。多模态学习旨在建立能够处理和关联多模态数据的模型[13]。它基于这样一个事实,即数据语义可以用不同的方式捕获。由多模态数据产生的表示被称为多模态表示。因为多模态学习模型学习特征时考虑了来自各种模态的信息,所以它通常比仅研究独特模态数据的单模态学习模型表现更好[14]。

3. 方法

在这一节中,我们提出了一种新的神经代码搜索方法。这种方法遵循用于代码搜索的伪暹罗网络的设计。我们的核心直觉是构建一个编码器,充分利用来自源代码多个方面的信息。在本文中,这样一个方面被称为“模态”,根据深度玻尔兹曼机器多模态学习的开创性工作[15]。

3.1 总览

在这里插入图片描述

用于提取多个表示的工作流

基于特定代码查询的原始代码片段,我们从多种形式中提取代码表示。工作流如图2所示。输入数据是代码片段,输出数据是代码序列和树序列。首先,我们将代码片段解析为抽象语法树(AST)。为了使树结构在语义上更好地用于代码搜索,我们将AST转换成一种新的树结构,称为简化语义树(SST),这是我们在本文中新引入的。然后,我们通过采样树路径[16,17]或遍历树结构[18,19],从SST中提取树序列化表示。最终,在我们的多模态学习方法中,这些树序列化表示分别补充了传统的令牌表示,即源代码令牌序列。

3.2 简化语义树

在这里插入图片描述

我们设计了一种新的树结构SST,用于提取源代码的树序列化表示。SST是简化AST树结构的近似方法,突出了代码片段的语义信息。与AST相比,SST去掉了不必要的树节点,改进了树节点的标签。尽管AST已经具有树结构并且能够进行树序列化,但是SST在语义上更具信息量,并且对各种编程语言更通用。

对于任何给定的AST,我们执行三个操作来构建相应的SST。第一个操作是修剪对于代码搜索来说语义上没有意义的树节点,比如像“int”和“boolean”这样的类型声明,像“public”和“abstract”这样的修饰符关键字,像“async”和“await”这样的函数关键字。移除节点的完整列表因不同语言而异。第二个操作是用描述性标签替换语句节点和表达式节点的标签,如对for-loop和while-loop语句使用“loop”,对精确的字符串变量使用“literal”,目标是帮助网络掌握语法不同节点背后的一般概念。第三个操作是统一来自不同语言的语义相似的标签的表达,如将“函数”、“程序”、“定义”和“模块”统一为“模块”。它有望促进某种形式的跨编程语言的迁移学习。

为了更好地理解SST,让我们研究一下清单1所示的代码片段。这段代码遍历所有成员并检查今天是否是某人的生日,如果是,它调用SMS函数。这些蓝色的单词是标识符。如图3所示,代码片段被解析为AST。在树结构中,蓝色块中的大多数叶节点是有意义的,而其他叶节点在语义上似乎没有意义,例如带下划线的“self”,它仅仅是为了显式地表示类的实例。与叶节点相反,大多数非终端节点对应于关键字、标点符号和缩进。

现在检查图4中相应的SST。在树结构中,一些叶节点被移除(在图中为灰色)。非终端节点被语言间共享的统一语义表达式所取代。例如,包含“-Stmt”或“Expr”后缀的标签被更短的跨语言版本替换(例如,“ForEachStmt”变成“loop”)。如果观察AST和SST之间的差异,移除的节点以灰色显示,简化的标签以粗体显示。

3.3 树序列化

接下来,我们根据采样和遍历技术序列化SST。其动机是从树结构中提取线性序列。这些序列是更适用于上述典型编码器的顺序数据。有两个选项可以将SST序列化为令牌序列。一种方法是从树结构[16,17]中提取树路径,即树节点之间的连接,然后对收集的树路径进行过滤和采样。另一种方法是通过tre遍历序列化整个结构[18,19]。它们分别被称为基于采样的表示和基于遍历的表示。在本文中,我们研究了两种基于采样的表示和两种遍历表示。
在这里插入图片描述

  • RootPath[16]对从单个叶节点到根节点的非终端节点的路径进行采样。如图5所示,正是这样一个根路径连接了标签为“周年纪念”的叶节点和根节点。树路径上的所有节点都在橙色块中。

在这里插入图片描述

  • LeafPath[17]对两个任意叶节点之间的非终端节点的路径进行采样。如图6所示,就是这样一个叶路径连接标签为“周年纪念”的叶节点和标签为“短信”的另一个叶节点。树路径上的所有节点都在橙色块中。

在这里插入图片描述

  • 基于结构的遍历(SBT)[18]是一种基于遍历的表示。SBT表示是通过自上而下的递归树遍历获得的。详细步骤如下:1)从根节点开始,我们首先用一对括号来表示树的结构,并将根节点本身放在右括号后面。2)遍历根节点的子树,并将子树的所有根节点放入括号中。3)递归遍历每个子树,直到遍历所有节点并获得最终序列。
  • 左——子-右——兄弟(LCRS)[19]是一种基于遍历的表示。其思想是将一般树转换成左——子-右——兄弟形式的二叉树,如图8所示。然后通过有序树遍历获得LCRS表示。对于每个子树,我们使用括号来分隔父节点、左子树和右子树。

为了生成这些树序列化表示,我们解析原始代码片段来构建SST,然后从标签是根节点标识符的每个叶节点开始提取根路径。为了更好的语义表达,我们忽略标签是单字符标识符的叶节点,如“t”或“x”,除非每个代码片段没有足够的根路径。一旦我们收集了所有的根路径,我们就把它们随机组合成对来生成叶路径。类似地,我们优先考虑叶路径,其对应的叶节点具有多字符标识符,如“成本”或“比率”,作为标签,因为它们被视为语义信息最丰富。相比之下,我们只需要实现基于结构的遍历和有序遍历的函数以及树变换算法来生成SBT和LCRS表示,而不需要任何额外的处理工作。根路径和叶路径表示由一个称为采样路径N的数字控制。如果M是树中的节点数,则有M个唯一的根路径序列和M∫(m-1)/2个唯一的叶路径序列。对于叶路径表示,我们丢弃低质量的叶路径序列,其序列长度大于建议的长度阈值=8或两侧的树高差小于建议的宽度阈值=2[20,21]。同时,采样树路径数量的默认参数是N=20。更高的值承诺更好的结果,但需要更多的计算能力,所以这里有一个权衡。

3.4多模态学习

图9

在我们的方法中,我们从源代码中提取两个模态,从自然语言表示中提取一个模态,并在它们之上采用多模态学习。这两种形式是代码片段的普通和树序列化表示。在下文中,令牌表示的形式称为代码序列,并表示令牌。SST的树序列化表示称为树序列。

我们的多模态学习模型的体系结构是一个伪连体体系结构,如图9所示。考虑到SelfAtt模型在各种自然语言任务中表现良好,我们在模型中采用了三种SelfAtt模型作为编码器。代码和树编码器并行工作用于代码表示,查询编码器用于查询表示。我们向码编码器提供码序列,向树编码器提供树序列,这两个编码器将各自的序列数据转换成向量,再转换成令牌向量和树向量。查询编码器接收查询序列作为输入,并计算查询向量作为输出。更具体地说,SBT或LCRS表示被用作树编码器的输入数据,而其他树序列化表示也是适用的。在我们的多模态学习模型中,所有三个模态向量都具有相同的长度。我们通过求和来组合代码和树向量。这将计算一个联合向量,它是源代码的多模态表示。然后将联合向量与查询向量一起训练,以确保语义相似的代码向量在共享向量空间中接近查询向量。通常,我们计算余弦距离来量化语义相似度。

4. 实验

这项工作调查了以下研究问题:

RQ1:当使用不同的编码器时,暹罗网络的代码搜索性能如何?

RQ2:所考虑的单峰树序列化表示的相对性能如何?

RQ3:代码搜索的多模态表示的有效性如何?

使用GraphSearchNet数据集

度量方法

使用平均倒数秩(MRR)和标准化贴现累积增益(NDCG)分数作为性能指标。MRR分数用于衡量参赛者提交之前的模型,然后计算NDCG分数作为比赛的进一步标准。MRR或NDCG分数越高,表现越好。前者的计算是在测试集上进行的,后者是在一个小规模的人工评估集上进行的。MRR分数量化了目标代码片段对给定查询的排名,它只关心最相关结果的排名。最相关的代码片段应该排名最高,其排名位置越低,MRR分数越低。当计算测试集中的MRR分数时,对于每个代码查询对,来自同一批中其他对的999个代码片段扮演干扰物的角色。所有批次的平均值是最终的MRR分数。NDCG分数量化了候选代码片段的排名与最理想排名之间的相似性,它关心所有候选结果之间的整体排名顺序。最理想的情况是,更相关的代码片段总是排在那些不太相关的代码片段之前。额外评估集上NDCG分数的计算是专门为挑战而做的。评估集由99个自然语言查询和每个查询的10个候选代码片段组成。NDCG分数是在整个评估集上计算的。

信息完整度

为了直观地度量代码表示的信息完整性,我们定义了链接覆盖率和节点覆盖率作为量化度量。链路覆盖率衡量的是句法信息的完备性,节点覆盖率衡量的是语义信息的完备性。覆盖率是要在所有组件之间使用的树组件(例如树链接或树节点)的覆盖率。因此,覆盖率越高,表明信息量越大或信息内容越完整。链接覆盖定义如下,从所有树链接中获取每对最近的树节点之间的多少树链接,因此语法信息被表示为每对最近的节点之间的树链接被树序列覆盖的程度。节点覆盖定义如下,从所有树节点中获取多少树节点,因此语义信息被表示为树序列包含树节点的唯一标签的程度。我们只是将共享相同标签的树节点计数为一个唯一的树节点。以图4所示的SST图、图5和图6所示的根路径和叶路径为例。对于节点覆盖率,总共有31个树链接,因为我们直接计算最近的树节点之间的链接。按照相同的思路,根路径和叶路径分别覆盖8个和7个链路,因此它们的链路覆盖率分别为25.81%和22.58%。对于节点覆盖,总共有32个树节点,但共享16个唯一标签,因此SST只有16个唯一树节点。类似地,根路径和叶路径分别包含8个和6个唯一节点,因此它们的节点覆盖率分别为50.00%和37.50%。链路覆盖率和节点覆盖率的公式定义如下:

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fnote.youdao.com%2Fyws%2Fres%2F7%2FWEBRESOURCE9a5405f782a34e68add8328652042d87&pos_id=img-mq5nLYeh-1699184495607在这里插入图片描述

其中C表示序列Si的集合,T是生成序列Si的树。此外,linksO和nodesO表示来自给定对象O的树链接和树节点,例如序列、序列集合或树,例如AST或SST。根据定义,对于给定的代码片段,令牌表示的链路覆盖率和节点覆盖率分别为0%和100%。对于树序列化表示,如果基于采样,如RootPath或LeafPath,这些比率在0%和100%之间,如果基于遍历,对于SBT,两个比率都是100%,对于LCR,节点覆盖率是100%。此外,如果计算引入的表示的组合。这些定义仍然有效,因为不同的表示来自同一个树结构。尽管链接覆盖率和节点覆盖率可以指示输入数据可能包含多少语法和语义信息,但这并不意味着更多的信息总是带来更好的结果。此外,考虑到输入数据的信息完整性和特征提取器的复杂性之间的权衡,如果不在模型设计和训练中引入更多的工作,直接将整个树作为输入数据是不可行的。

5. 实验结果

5.1 哪个编码器最好

在这里插入图片描述

总体而言,NBoW模型在学习和泛化能力方面是最好的。在考虑一些上下文信息的模型中,SelfAtt模型表现得最有竞争力。根据CodeSearchNet数据集,神经代码搜索在Python上更容易,但在JavaScript和Ruby上更难。

5.2 信息覆盖率

在这里插入图片描述

如表IV所示,大多数树序列化表示比Uni-Code的令牌表示更有效。最好的代表是Uni-SBT,因为它在两种语言中都有最好的分数。Python和Ruby上表现最好的分别是Uni-LCRS和Uni-SBT,MRR分数分别提高了15.60%和16.88%。此外,单叶路径表现最差。尽管不是最好的,Uni-RootPath仍然超越了Uni-Code,并为两种语言带来了稳定的改进。Uni-SBT具有最好和最稳定的性能。简而言之,基于遍历的表示,如SBT和LCRS,比基于采样的表示,如RootPath和LeafPath表现得更好。据我们所知,这一发现从未在文献中报道过。

对于单峰学习,树序列化表示优于纯文本表示。总的来说,Uni-SBT是最好的树序列化表示。我们的结果还表明,基于遍历的表示往往比基于采样的表示更好。

5.3多模态的作用

在这里插入图片描述

表VI给出了不同多模态表示的MRR分数和变化率。行用于表示,列用于语言。例如,Python上多根路径表示的MRR分数为0.8410,其变化率为11.65%。如表VI所示,在Python和Ruby上,Uni-Code的性能总是比多模态表示差。它清楚地显示了我们的多模态学习方法的优势,因此,单码和树序列化表示的组合总是比单码带来改进。与其他算法相比,多叶路径算法的性能最差。在Python和Ruby上,多LCR和多根路径分别表现最好。此外,多SBT有接近他们的MRR分数。通过比较多模态表征和相应的单模态表征,我们发现基于采样的表征获得更高的MRR分数。尽管单叶路径和多叶路径都不如其他路径,但叶路径的增幅最大。Python和Ruby上从单叶路径到多叶路径的变化范围是

对于代码搜索,多模态表示优于单模态表示。考虑到简洁性和有效性,RootPath和SBT分别是推荐的基于采样和基于遍历的表示。总的来说,多模态表示可以被认为是神经代码搜索的新技术。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值