《GraphCodeBERT: Pre-Training Code Representations With DataFlow》 ICLR 2021
近年来,应用于编程语言的预训练模型得到飞速发展,相关任务比如code search, code completion, code summarization 也得到提升。但是,现有的预训练模型是将code snippet(代码片段)视为一个token序列,忽视了代码的结构。
本文的GraphCodeBERT,没有用句法级别的AST,而是用的代码的数据流(data flow )来表示源代码信息。代码的数据流是一个graph,节点表示一个变量变量(variable),边表示变量之间的依赖关系(where-the-value-comes-from)。不用AST是考虑到数据流图不像AST这么复杂,不会带来不必要的深层信息。
本文的下游任务是natural language code search代码搜索、clone detection克隆检测、code translation代码翻译、code refinement修bug。
数据流图data flow
data flow是一个graph,节点是变量,边表示where the value of each variable comes from。
**为什么要建图?**对于同一个源代码,用不同的抽象语法得到的AST是不同的,但是代码的数据流是不变的。因此数据流图可以提供重要的语义信息。
举个例子比如 v = max value − min value
,程序员并不一定总是按照规定命名变量,因此想要了解变量 v 的语义,可以考虑变量v的来源,来源于数据流中的max和min。此外数据流图还可以支持解析同一变量在不同的执行阶段所具有的不同的语义信息,比如图中的x3, x7, x9, x11虽然都是 x这个token,但是语义信息是不同的,当作token序列训练时不太合适的。
构造数据流图的方法如上所示,对于一段代码片段 C = { c 1 , c 2 , … , c n } C = \left\{c_{1}, c_{2}, \ldots, c_{n}\right\} C={
c1,c2,…,cn},先用编译工具(Tree-sitter)将其解析成 AST,AST包含了代码段的句法信息,将AST的叶子节点识别为变量序列 V = { v 1 , v 2 , … , v k } V=\left\{v_{1}, v_{2}, \ldots, v_{k}\right\} V={
v1,v2,…,vk}。然后将每个变量作为一个节点,有向边 ε = ⟨ v i , v j ⟩ \varepsilon=\left\langle v_{i}, v_{j}\right\rangle ε=⟨vi,vj⟩ 表示变量 j 的值依赖于 变量 i 的值。举例如代码 x = expr
,x依赖于等号右侧表达式中的所有变量,所以数据流图是有向图,a指向x意味着x依赖于a。有向边的集合是 E = { ε 1 , ε 2 , … , ε l } E=\left\{\varepsilon_{1}, \varepsilon_{2}, \ldots, \varepsilon_{l}\right\} E={
ε1,ε2,…,εl},代码C的数据流图表示为 G ( C ) = ( V , E ) \mathcal{G}(C)=(V, E) G(C)=(V,E)。
模型
模型架构用的就是标准BERT,一些模型结构参数就不细讲了。唯一的区别是在Attention模块里有一个基于图 G ( C ) = ( V , E ) \mathcal{G}(C)=(V, E) G(C)=(V,E)的mask(毕竟图结构信息得用)
输入输出
有三种序列:代码片段 C = { c 1 , c 2 , … , c n } C=\left\{c_{1}, c_{2}, \ldots, c_{n}\right\} C={ c1,c2,…,cn},该段代码的注释文本片段 W = { w 1 , w 2 , … , w m } W=\left\{w_{1}, w_{2}, \ldots, w_{m}\right\} W={ w