Page 231 - 《软件学报》2021年第7期
P. 231

牛长安  等:基于指针生成网络的代码注释自动生成模型                                                      2149


                 源代码不同位置分配不同的 Attention 权重.这两个作用对输入是未经过分解的源代码来说是可行的,因为此时
                 的输入与 AST Encoder 的输入 AST 是完全对应的.但是,Hybrid-DeepCom 为了丰富语义信息对标识符进行了分
                 解,破坏了源代码的结构,使得 Code Encoder 和 AST Encoder 的输入不再完全对应,在增强了 Code Encoder 第 1
                 个作用的同时,不可避免地削弱了第 2 个作用.在一定程度上,这两个任务是互斥的,因为要丰富语义信息就需要
                 对标识符进行分解,这样就破坏了语法结构信息;而保留语法结构就会将大量的标识符变成“UNK”标签,丢失
                 了语义信息.因此,Code Encoder 无法同时在两个任务上达到最佳性能,只能在训练中找到两者中间的平衡点.
                    为了同时发挥两个任务的最优性能,CodePtr 引入了一个新的编码器将这两个作用分开,将第 2 个作用通过
                 Source Encoder 实现,Source Encoder 将分解前的源代码序列作为输入,与 AST Encoder 的输入完全对应,使结构
                 信息发挥更大的作用,同时也使 Code Encoder 更好、更专注地发挥提取语义信息的作用.由于第 2 个作用主要通
                 过 Attention 权重实现,并且,由于分解后的语义信息包含分解前的语义信息,为了避免冗余以及过多“UNK”标签
                 的干扰,我们将不把 Source Encoder 的最终隐藏状态传给解码器,而只在解码阶段使用其对应的 Attention 权重.
                    因此,CodePtr 有 3 个编码器:输入为源代码序列的源代码编码器(source encoder)、输入为分解后源代码的
                 代码编码器(code encoder)和输入为 SBT 序列的 AST 编码器(ast encoder).
                    Source Encoder 用于对未分解的源代码进行编码,一方面是由于上述原因,另一方面是为指针生成网络提
                 供 Attention 权重,因为分解过后的源代码序列中标识符已被分解,无法提供整个单词的 Attention 权重.对于长度
                                       s
                                           s
                 为 T s 未分解的源代码序列 X       xx s   s  , 编码器将其编码为隐藏状态,对于时刻 t,Source Encoder 接受当前时
                                           , ,..., x
                                          1  2   s T
                                                                        s
                                                                s
                                          s
                                                                         ,
                 刻的未分解的源代码序列输入 x 将上一时刻的隐藏状态 h 更新为 h 即
                                           ,
                                          t                    t  1    t
                                                             s
                                                       s
                                                      h   f  (,x h s  )                              (1)
                                                       t  s  t  t  1 
                 其中,f s 在本文中采用了 GRU 单元,由此可以得到 Source Encoder 的所有隐藏状态 output            [,h h s ,...,h s  ].
                                                                                          s
                                                                                      s   1  2   s T
                    分解过后的源代码序列相对于分解前的代码序列而言,大大降低了 OOV 词的比例,可以使编码器学习到更
                 丰富的语义信息,因此,Code Encoder 十分必要,对于长度为 T c 未分解的源代码序列 X                   xx  c ,..., x c  , 在 t 时刻,
                                                                                    c
                                                                                        c
                                                                                         ,
                                                                                        1  2   c T
                                                                         c
                                                   c
                                                                                 c
                                                                                  ,
                                                    ,
                 Code Encoder 接受分解后的代码序列输入 x 将上一时刻的隐藏状态 h 更新为 h 公式如下:
                                                                         1 
                                                   t
                                                                                 t
                                                                         t
                                                            c
                                                       c
                                                      h   f c (,x h t c  1   )                      (2)
                                                            t
                                                       t
                 其中,f c 同样采用了 GRU 单元,得到 Code Encoder 的隐藏状态 output      [,h h c 2 ,...,h c c T  ].
                                                                          c
                                                                          1
                                                                      c
                    对于结构性强的语言,源代码的结构信息同样重要,Java 代码对应的 AST 可以反映源代码的结构信息,而
                 Hu 等人在文献[12]中提出的 SBT 遍历方法可以无损地将 AST 树形结构转换为序列.因此,AST Encoder 将源代
                 码对应的 AST 的 SBT 序列作为输入,使 CodePtr 可以提取到源代码的结构信息.对于长度为 T a 的 SBT 序列
                                                                 t
                                                                                       a
                                                                                               a
                   a
                 X   x a , x a ,..., x a  , AST Encoder 在 t 时刻接受 SBT 序列输入 x 将上一时刻的隐藏状态 h 更新为 h 即
                                                                                                ,
                                                                  ,
                      1  2   a T                                 a                    t  1    t
                                                       a
                                                             a
                                                      h   f  (,x h a  )                              (3)
                                                       t  a  t  t  1 
                 其中,f a 为 GRU 单元,得到 AST Encoder 的隐藏状态 output    [,h h a ,...,h a  ].
                                                                  a
                                                              a   1  2   a T
                    3 个编码器中,Source Encoder 是专门为指针生成网络添加的编码器,Code Encoder 和 AST Encoder 可以同
                 时学习到源代码的语义信息和结构信息.对于多编码器的最终隐藏状态,CodePtr 在传入解码器之前使用了一个
                 维度映射层 ReduceHidden.
                 2.3    维度映射层ReduceHidden
                    在经典的 seq2seq 网络    [18] 中,编码器最后时刻的隐藏状态作为解码器的初始隐藏状态,这样可以使编码器
                 提取到的信息传递给解码器.而对于多编码器的情况,由于编码器和解码器的隐藏维度大小一般是相同的,因此
                 CodePtr 使用了维度映射层 ReduceHidden,使多个输出的隐藏状态可以输入到解码器中.
                    上一小节中提到了我们将不会把 Source Encoder 的最终隐藏状态向量传递给解码器,原因有两个:一是因
                 为 Source Encoder 的作用是在解码时匹配 AST 信息,并且在指针生成网络开启时为其提供源代码的 Attention
                 权重,这都是通过解码时提供 Attention 权重来实现的;二是因为最终隐藏状态向量中蕴含的是编码器提取到的
   226   227   228   229   230   231   232   233   234   235   236