代码表示学习(也称 code embedding)旨在将源代码的语义编码为分布式向量,在最近基于深度学习的代码智能模型中起着重要作用。
存在问题
主要贡献:
输入表示:将代码的AST作为模型输入的一部分,该模型提供了一个具有深度优先遍历的 AST token 序列。下图展示了从图1中的 AST 中获得的部分 AST 序列示例。蓝色箭头表示节点之间的边。
给定一个自然语言注释 w = w 1 , w 2 , . . . , w ∣ w ∣ w={w_1,w_2,...,w_{|w|}} w=w1,w2,...,w∣w∣,其对应的源代码 c = c 1 , c 2 , . . . , c ∣ c ∣ c={c_1,c_2,...,c_{|c|}} c=c1,c2,...,c∣c∣,对应的 AST 序列 a = a 1 , a 2 , . . . , a ∣ a ∣ a={a_1,a_2,...,a_{|a|}} a=a1,a2,...,a∣a∣,SynCoBERT采用多模态(NL,PL,AST)的连结作为输入,即:
其中 [CLS] 是”分类任务“的特殊 token,出现在输入序列的开头。[SEP] 是分隔两种子序列的特殊 token。
模型结构:构建在多层 Transformer 编码器上。
给定一个 NL-PL-AST 三元组 { w , c , a } \{w,c,a\} {w,c,a} 数据点作为输入。我们从NL、PL 和 AST 的连接中随机选择15%的令牌。用[MASK]令牌替换其中的80%,用随机令牌替换10%,其余10%不变。MMLM的目标是预测 masked tokens 的交叉熵损失:
其中 M = w m ∪ c m ∪ a m M=w^m∪c^m∪a^m M=wm∪cm∪am 是 NL ( w m w^m wm),PL ( c m c^m cm) 和 AST ( a m a^m am) 中的 masked tokens 集合。V 表示词表大小。 y i M M L M y^{MMLM}_i yiMMLM 表示 masked token i i i 的标签, p i M M L M p^{MMLM}_i piMMLM 表示 token i i i 的预测概率。
标识符(identifier)作为一种典型的符号,在源代码中起着重要的作用。它可以被另一个字符串替换,而不会影响源代码的逻辑。考虑到标识符的重要性和所占的比例较大,我们将代码 token 类型分为标识符和非标识符。
与MMLM(预测15%的 code token)不同,我们对所有 code token 提出了标识符预测目标。对于源代码中的每个 token,如果它是标识符,则应用标签1,否则应用标签0,如图3所示。IP损失函数为二值分类损失定义为:
其中 p i I P p^{IP}_i piIP 是第 i i i 个 code token 预测为 标识符的概率, y i I P y^{IP}_i yiIP 是 第 i i i 个 code token 的标签。
在将AST树转换为序列时,可能会丢失一些关键的结构信息。受GraphCodeBERT中提出的数据流图的 edge masking 技术的启发,设计了 AST edge prediction 目标。以图3的 token ”result“ 为例,token(“assignment”、“result”)之间有一条边,token(“result”、“=”)之间没有边。为了整合这样的树结构信息,我们在AST中 mask edges,并要求模型预测这些 edges。该TEP目标的损失函数定义为:
其中, N a N_a Na 表示 所有的 AST 节点 pairs 的集合。如果第 i i i 个节点和 第 j j j 个节点之间有边, y ( i , j ) T E P y^{TEP}_{(i,j)} y(i,j)TEP 的值为1,否则为0。 p ( i , j ) T E P p^{TEP}_{(i,j)} p(i,j)TEP 是第 i i i 个节点和 第 j j j 个节点之间是否有边的概率,由两个节点的点击来表示。使用激活函数 sigmoid 来归一化 p ( i , j ) T E P p^{TEP}_{(i,j)} p(i,j)TEP 的值,使其处于 0 到 1 之间。
多模态对比学习
之前的工作已经表明,来自BERT的原生句子表示是由高频的 tokens 主导的。这种 token-imbalance 问题在代码中更为严重。以Python语言为例, ”def“ token几乎在所有函数中都会出现。
对比学习鼓励原始序列的表示更接近 ”positive“ 增广序列的表示,同时远离 ”negative“ 序列的表示,让模型在不同点之间学习更均匀的决策边界,以调整由于 token imbalance 造成的偏差。
最近的一些工作尝试去对比代码片段的相似处和不相似处。然而,它们只处理代码的单一模态,而忽略了编程语言的多模态特性。这些语义等价的模态可以提供补充信息,以学习更全面的代码表示。因此提出多模态对比学习。
我们使用成对数据和非成对数据来训练SYNCOBERT。成对数据是指带有配对的自然语言注释(NL)的代码(PL),非成对数据是指没有配对自然语言注释的独立代码。接下来,我们将解释如何为这两种情况构建正(positive)样本和负(negative)样本。
成对数据:
考虑 PL-AST vs AST-PL 来构建正样本。该方案的工作原理与上面介绍的 NL-PL-AST vs NL-AST-PL 的设置相同(不考虑 NL)。
为获取 MCL 负样本,采用 in-batch 和 cross-batch 采样方法。
对于批次大小为N的训练数据 b 1 = [ x 1 , . . . , x N ] b_1 = [x_1,...,x_N] b1=[x1,...,xN],我们可以首先使用前面描述的方法获得另一批大小为N的 positive data batch b 2 = [ x 1 + , . . . , x N + ] b_2=[x_1^+,...,x_N^+] b2=[x1+,...,xN+],其中 { x i , x i + } \{x_i,x_i^+\} {xi,xi+} 是 positive pair。对于 x i x_i xi,in-batch 和 cross-batch 的负样本为 { x j } , j ≠ i \{x_j\}, j ≠i {xj},j=i,这样,对于每个 x i x_i xi,我们可以得到 2N-2 个负样本的集合 X − X^- X−,如下图所示。
对于成对数据中的输入 x i x_i xi ,我们执行以下步骤(非成对数据类似):
首先,按照前面介绍的两种方法为 x i x_i xi 构造一个正样本 x i + x_i^+ xi+。
将 x i x_i xi 和 x i + x_i^+ xi+ 作为 SynCoBERT 的输入,然后我们可以得到它们的向量表示 h i = S y n C o B E R T ( x i ) h_i=SynCoBERT(x_i) hi=SynCoBERT(xi) 和 h i + = S y n C o B E R T ( x i + ) h_i^+=SynCoBERT(x_i^+) hi+=SynCoBERT(xi+)
最后,采用一个两层的 MLP f ( . ) f(.) f(.) ,它将表示映射到空间 v i = f ( h i ) , v i + = f ( h i + ) v_i=f(h_i),v_i^+=f(h_i^+) vi=f(hi),vi+=f(hi+),其中应用了对比损耗。通过非线性变换, h h h
中可以保留更多的信息。
对于表示为 v i v_i vi 的输入 x i x_i xi , 他有一个表示为 v i + v_i^+ vi+ 的正样本 x i + x_i^+ xi+ , 它也有一组大小为 2N-2 的负样本。我们将 X − X^- X− 中样本的表示为 V − = { v 1 − . . . v 2 N − 2 − } V^-=\{v_1^-...v_{2N-2}^-\} V−={v1−...v2N−2−},对比学习的目标是最大化正样本之间的表示相似度,同时最小化负样本之间的表示相似度。因此,我们将 positive pair ( x i x_i xi, x i + x_i^+ xi+) 的损失函数定义为:
其中,一对样本的相似度由其表示的点积定义,即 v i ⋅ v j v_i · v_j vi⋅vj
我们对同一对数据计算两次损失,即 ( x i x_i xi, x i + x_i^+ xi+) 变成 ( x i + x_i^+ xi+, x i x_i xi) ,因为 x i x_i xi 和 x i + x_i^+ xi+的负样本的点积是不同的。
SynCoBERT的整体损失函数是之前定义的几部分的和:
其中 Θ Θ Θ 包含模型的所有可训练参数。 λ λ λ是 L 2 L_2 L2 正则化系数,用于防止过拟合。
https://arxiv.org/abs/2108.04556
Comments: 9 pages, 3 figures, 5 tables
Subjects: Computation and Language (cs.CL); Artificial Intelligence (cs.AI); Programming Languages (cs.PL)
Cite as: arXiv:2108.04556 [cs.CL]