代码表示学习(也称 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+的负样本的点积是不同的。
总体 MCL 损失定义如下:

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]