一种基于长短期记忆网络的代码推荐方法转让专利

申请号 : CN201710687197.4

文献号 : CN107506414B

文献日 :

基本信息:

PDF:

法律信息:

相似专利:

发明人 : 余啸殷晓飞刘进伍蔓姜加明崔晓晖

申请人 : 武汉大学

摘要 :

本发明涉及一种基于长短期记忆网络的代码推荐方法,针对现有代码推荐技术普遍存在推荐准确率低、推荐效率低等问题,本发明先将源代码提取成API序列,利用长短期记忆网络构建一个代码推荐模型,学习API调用之间的关系,然后进行代码推荐。并使用了dropout技术防止模型过拟合。同时提出运用ReLu函数代替传统饱和函数,解决梯度消失问题加快模型收敛速度,提高模型性能,充分发挥神经网络的优势。本发明的技术方案具有简单、快速的特点,能够较好地提高代码推荐的准确率和推荐效率。

权利要求 :

1.一种基于长短期记忆网络的代码推荐方法,其特征在于,包括以下步骤:

步骤1,通过网络爬虫从GitHub网站爬取至少一万个Java开源软件代码,且每个Java开源软件代码的更新版本次数均超过1000次,这些开源软件代码构成了源代码库,然后对源代码进行预处理形成API序列事务库,并生成API字典和API向量矩阵,具体包括:步骤1.1,使用网络爬虫从GitHub网站爬取至少一万个更新版本次数已超过至少1000次的Java开源软件代码,形成源代码库;

步骤1.2,以方法为单位,对一个方法中包含的代码提取出该方法的API序列,源代码库中所有方法提取出的所有API序列组成了API序列事务库;对方法中包含的代码提取出API序列的规则为只提取新建对象语句的API和对象调用方法语句的API;新建对象语句提取出的API表示为“类名.new”,这里的类名为新建的对象所属的类的名字;对象调用方法语句提取出的API表示为“类名.方法名”,这里的类名为该对象所属的类的名字;

步骤1.3,从API序列事务库中提取出API字典,并生成API向量矩阵;

API字典定义为:设API序列事务库为D,API字典可以表示为VD={1:API1,w1,2:API2,w2,…,i:APIi,wi,…n:APIn,wn},n为API字典的包含的API的个数,APIi表示VD中第i个API的名称,wi表示集合VD中第i个API的向量;

API字典和API向量矩阵的生成过程为:遍历API序列事务库,判断当前API是否存在于API字典中,如果存在,则忽略当前API,继续遍历下一个API,否则,将当前API加入到API字典中,并赋予其唯一的ID和赋予其一个随机的M维API向量;API字典中包含的n个API的n个M维的API向量组成了API向量矩阵;API向量矩阵是作为长短期记忆网络(Long Short-Term Memory,LSTM)模型的参数,在训练LSTM模型时会对API向量进行学习;

步骤2,构建API推荐模型,即构建长短期记忆网络;定义长短期记忆网络包括输入层、隐藏层、全链接层和输出层;其中,

输入层接受一串数值输入,经过前向传播输入到隐藏层,并与隐藏层上一时刻的输出共同影响隐藏层的当前输出,最后隐藏层产生的输出输入全链接层,全链接层输出数据,输入到输出层,输出层中的Softmax分类器输出最后的分类结果;

隐藏层的神经单元为长短期记忆单元,使用dropout技术防止长短期记忆网络过拟合,神经元激活函数使用ReLu函数;输入层的神经元个数为M,M为步骤1.3中生成的API向量的维度;隐藏层神经元个数为M,全链接层神经元个数为M,输出层的神经元个数为n,n为API字典中包含的API的个数,M、n的取值均为正整数;

步骤3,训练API推荐模型,即训练长短期记忆网络;

API推荐模型的输入为一个Nb×Ns行M列的矩阵Tinput,其中Nb表示批大小,Ns表示序列长度,M表示API向量的维度,矩阵第i行表示输入序列中的第i个API所对应的向量;

API推荐模型的目标矩阵Ttarget是一个Nb行Ns列的矩阵,其中第i行第j列表示输入序列中的第i个API所对应的目标输出API在步骤1.3中生成的API字典中的ID;

API推荐模型的输出为Nb×Ns行n列的输出概率矩阵Tprob,其中n表示API字典中包含的API的个数,第i行第j列的数表示输入序列中的第i个API输入后所预测的下一个API属于API字典中第j个API的概率;

该步骤包括以下步骤:

步骤3.1,将API序列事务库中的所有API序列头尾相连,产生一个API总序列;

步骤3.2,设置一个指针变量point,且point的初始值为1,从API总序列的第point个API开始,每次依次提取Ns个API,一共提取Nb批,对于每一个API,从API字典中读取其对应ID,并利用其ID,从API向量矩阵中提取该API所对应的向量,将其存入输入矩阵Tinput中;例如,第i批第j个API所对应的向量,存入输入矩阵Tinput中的第i×j行;对于目标矩阵,从API总序列的第point个API开始,每次依次提取Ns个API,一共提取Nb批,对于每一个API,从API字典中读取其对应ID,将对应的ID存入目标矩阵;最后,当输入矩阵和目标矩阵填充完毕后,令point变量指向API总序列中最后一个被目标矩阵读取的API;值得说明的是,当提取到API总序列中最后一个API后,继续提取API总序列中的第一个API;

步骤3.3,从输入矩阵中依次提取API向量,作为API推荐模型的输入,对于时刻t,从输入矩阵中依次每一行API的向量作为模型的输入向量,记该API为APIt,将输入标记为xt,则LSTM模型的隐藏层输入门计算结果为it=σ(wixt+uibt+vict-1),遗忘门计算为结果ft=σ(wfxt+ufbt+vfct-1),输出门计算为ot=σ(woxt+uobt+voct),最后隐藏层的输出为bt=ot·tanh(ct),数据从隐藏层传入全连接层,最后输出层使用Softmax分类器;输出层得到:其中|VD

|表示API字典中包含的API个数,θ表示神经网络当前权值,θ1表示输出层第一个输出节点对应的一套权值;最后,将公式转置,并存入输出概率矩阵中;重复此步骤,直到输入矩阵中的API向量全部输入API推荐模型当中;

步骤3.4,利用输出概率矩阵和目标矩阵计算交叉熵损失函数;交叉熵损失函数为其中,l表示指示函数,l(yt=j)表示当yt=j时,l(yt=j)=1,否则l(yt=j)=0,yt表示时刻i的目标输出API的ID; 表示输出概率矩阵中第i行第j列的输出概率;

步骤3.5,根据交叉熵损失函数,将网络中的权值W作为变量,计算网络中所有权值的梯度;并同时基于梯度剪裁,将权值的更新控制在一个设定范围;具体是:先设置一个名字为梯度剪裁的常量,标记为clip_gradient,在进行了反向传播时,将会得到每个参数的梯度,标记为diff,此时,并不选择直接更新权值,而是先求所有权重梯度的平方和,标记为sumsq_diff,如果所有权重梯度的平方和大于clip_gradient,则继续求出缩放因子,标记为scale_factor=clip_gradient/sumsq_diff;这个scale_factor在(0,1)之间;如果权重梯度的平方和sumsq_diff越大,那缩放因子将越小;最后将所有的权重梯度乘以这个缩放因子,这时得到的梯度才是最后的梯度信息;根据公式W=W-η▽J(θ)更新权值,▽J(θ)表示的是对应权值梯度,η表示学习率;

步骤3.6,重复步骤3.2-3.5,直至收敛,即损失J(θ)不再上升或者下降;

步骤4,对开发者正在编辑的代码提取出API序列,然后生成预测子序列集合;

步骤4.1,对开发者正在编辑的代码提取出API序列,并记为P={P1,P2,…,Pi,…,PL},其中Pi表示该API序列P中第i个API,PL表示该API序列P中第L个API,也即该API序列P中包含的API个数为L;提取出API序列的规则与步骤1.2中的规则相同;

步骤4.2,以第L个API为参考位置,向前选择所有长度小于等于阈值γ的子序列,即选取的子序列为Subi={PL-i,…,PL},其中1

步骤5,将步骤4中生成的预测子序列集合VSub中的序列依次输入到步骤3训练好的API推荐模型中,输出一个|VSub|行n列的概率矩阵,其中|VSub|为子序列集合VSub中包含的子序列个数,n为步骤一中产生的API字典中包含的API个数,概率矩阵的第i行第j列表示当先前API序列是预测子序列Subi时,下一个API是API字典中的第j个API的条件概率Pr(wj|Subi);

将产生的预测概率矩阵Tprediction每一列取最大值,得到一个一维概率矩阵t,该一维概率矩阵中值最大的一列为第m列,则优先推荐API字典中第m个API。

说明书 :

一种基于长短期记忆网络的代码推荐方法

技术领域

[0001] 本发明属于代码推荐领域,特别是涉及一种基于长短期记忆网络的代码推荐方法。

背景技术

[0002] (1)代码推荐系统
[0003] 开发者往往采用成熟的软件框架和类库来进行开发,以提高软件开发的效率和质量。因此,开发者往往需要知道,如何通过调用相应的API来重用现有的类库或框架。但是学习不熟悉的库函数或框架中的API是软件开发过程中的一个较大的障碍。一方面,近年来各类成熟的软件框架中新增的API数量非常多,使得开发者需要花费更多的时间了解这些软件框架中的API。另一方面,不充足或不准确的API代码样例,不完整或有误的API注释文档以及API自身的复杂性等众多因素使得开发者对于API的学习和使用异常困难。
[0004] 现代软件开发工作流程的主体是集成开发环境(IDE)。最初其作为用户界面引入特定的编码语言,如普遍使用的C++和Java编程语言。至今,IDE已经演变成一种的独立计算产品,更像是一个高端的字符文档管理控制系统,而不仅仅是用于编码和调试工具的用户界面。其中,为解决开发者的API使用困难这一难题,许多高级开发工具的IDE的核心功能中包括了代码自动推荐功能。然而IDE中包含的代码推荐系统仅考虑API的类型兼容性和可见性,此类代码推荐系统面对复杂软件框架时,所推荐的API准确率低。主要原因是此类推荐方法根据简单规则对所有API筛选之后,推荐大量的方法或字段,最后按照字母表顺序排序并给出推荐结果。
[0005] 更准确的方法是通过挖掘API的使用模式,将之应用于代码推荐系统中,推荐与开发者需求高度相关的API,并呈现给开发者。现有的挖掘API使用模式的方法都有一定的缺点。例如,基于搜索的代码推荐技术虽然推荐效率很快,但是没有利用时序信息。而经验告诉我们,一个方法中的时序信息很重要。比如,一个API调用序列中,任何一个对象的使用,都必须在该对象构造和声明之后,任何一次文件读写,都必须在该文件创建之前。所以,API调用的顺序,即API调用的时序信息,可以帮助我们更加合理的挖掘API的使用模式。基于图的方式不仅考虑时序信息,还考虑了代码中的结构化信息,如数据依赖关系,控制依赖关系等,但是应用时使用的子图搜索技术效率低。基于自然语言处理的方法,考虑了时序信息,效率折中,且可以考虑多API之间的使用模式。
[0006] (2)深度学习
[0007] 近年来,深度学习应用在自然语言处理领域中有着非常优异的效果,其中循环神经网络模型(Recurrent Neural Networks,RNN)是最常用的深度学习模型之一。RNN可以处理任意长度的时序序列,在文本分类、机器翻译、词性标注和图片语义分析等方面表现出非凡的能力。然而,RNN模型也有不足。RNN的本质是在神经网络的隐藏层维护一个状态,用以记忆历史信息,但随着时序序列的增长,训练过程中会存在梯度消失(gradient vanishing)或梯度爆炸(gradient explosion)的问题。所以,RNN在输入序列超过一定长度的情况下表现不佳。另一方面,深度神经网络在训练时超过一定的迭代次数,容易发送过拟合现象。
[0008] 1)长短期记忆网络
[0009] 为了解决传统RNN存在的问题,长短期记忆网络(Long Short-Term Memory,LSTM)模型应运而生。LSTM模型是将神经网络中的隐藏层神经元替换成一个block结构。其中,block结构加入了输入门、输出门、遗忘门以及cell结构,用于控制对历史信息的学习和遗忘。使模型适合处理长序列问题。在此基础之上,有大量的学者进行了LSTM模型研究,衍生了多种改进的LSTM模型,例如由Gers提出的LSTM模型,加入了“窥视孔连接”(peephole connections),单元状态也将作为门限层的输入。由Chung等人提出的一种LSTM的衍生形式是门限递归单元,它将遗忘门和输入门结合输入到“更新门限”中,还将单元状态和隐藏状态合并,他的这一做法也越来越被接受。还有其他衍生结构如Tree-LSTM(树形结构长短时期记忆神经网络)、Bi-LSTM(双向长短时期记忆网络)等,都被广泛运用于解决自然语言处理的诸多问题。
[0010] 设时刻t,LSTM模型的记忆单元表示为ct,遗忘门表示为ft,输入门表示为it,输出门表示为ot三个门的元素值都在区间[0.1],在时刻t,LSTM的计算方式如公式(1)到公式(5)所示。
[0011] it=σ(wixt+uibt+vict-1)   (1)
[0012] ft=σ(wfxt+ufbt+vfct-1)   (2)
[0013] ot=σ(woxt+uobt+voct)   (3)
[0014]
[0015]
[0016] bt=ot·tanh(ct)   (6)
[0017] 其中,输入门的输入包含三个方面,如公式(1)所示,分别是当前时刻t输入层的输入、上一时刻t-1隐藏层的输出和上一时刻t-1LSTM中cell的状态,输入门用于控制当前隐藏层cell状态的输入,通过一定的运算来决定是否将输入信息输入到cell的状态中,其中1表示允许信息通过,对应的值需要更新,0表示不允许通过,对应的值不需要更新。
[0018] 其中,遗忘门的输入所包含的三个方面如同输入门,如公式(2)所示。遗忘门是用来控制上一个时刻t-1隐藏层存储的历史信息,根据上一时刻隐藏层的信息和当前输入信息,来决定上一时刻cell的历史信息ct-1的保留程度,其中1表示对应的信息保留,0表示对应的信息舍弃。
[0019] 其中,输出门的输入包含三个方面,如公式(3)所示,分别是当前时刻t输入层的输入、上一时刻t-1隐藏层的输出和当前时刻tLSTM中cell的状态。输出门用于控制当前隐藏节点的输出,其中1表示对应的值需要输出,0表示不需要输出。
[0020] 如公式(6)所示,时刻t,隐藏层的输出为bt,且由输出门控制输出信息。
[0021] 2)Dropout技术
[0022] Dropout技术是Hintion于2012年提出的一种防止神经网络过拟合的技术,dropout的工作机制是随机选择一定比例的隐藏层节点不工作,不工作的节点在本次训练中不更新权值,但权值依然保留,因为在下一次训练中,这些不工作的节点有可能被再次随机选择为工作的节点。而在模型的验证和使用的过程中,所有节点将会被使用到。Hinton的学生Alex Krizhevsky提出的深度卷积神经网络AlexNet将dropout技术实用化,在AlexNet的最后几个全连接层运用dropout技术,证明了dropout技术防止过拟合提高泛化能力的效果。
[0023] 3)ReLu函数
[0024] ReLu函数由Nair&Hinton于2010年提出,首次提出是运用于受限波尔兹曼机,由于ReLu将多数值映射为0,所以ReLu的应用为网络加入了稀疏性,使其更加符合人类神经元的特性。另一方面,相比于传统的S型激活函数容易饱和,造成梯度消失问题,ReLu并不存在这样的问题。而且,ReLu函数可以加快模型训练的收敛速度。
[0025] (3)语言模型
[0026] 1)词向量
[0027] 词向量是深度学习运用于自然语言处理领域的关键技术。词向量技术使用一个特征向量代替原来one-hot向量,用于表示一个自然语言中的“词汇”,将原本高维稀疏的向量压缩成一个低维稠密的向量。本发明将API类比成词汇,对应于自然语言处理中的词表,提出API字典,对应于词向量,提出API向量。
[0028] 2)概率语言模型
[0029] 软件具有自然性,统计语言模型已经被应用到各种软件工程任务当中,如代码推荐,代码完成等。这些技术将源代码当成一种特别的自然语言,并使用统计学的自然语言处理技术分析源代码。
[0030] 语言模型是一种如何生成语言的概率模型,它会告诉我们,一个具体的句子在一种语言中产生的可能性。对于一个句子y,y=(y1,y2,...,yn)是这个句子的词序列,语言模型的功能是估计联合概率Pr(y1,y2,...,yn)。已知公式所以,计算联合概率Pr(y1,y2,...,yn)可以转化为计算句子中每一个词给定先前词的条件概率。但条件概率的估计是困难的,目前使用“n-gram”模型来近似计算,如公式Pr(yt|y1,...,yt-1)≈Pr(yt|y1-n+1,...,yt-1)所示。但是,其缺点是假设下一个词只跟n-1个先前词有关。
[0031] 神经语言模型是一种基于神经网络的语言模型。不同于“n-gram”模型根据固定长度的的先前词预测下一个词,神经语言模型可以根据更长的先前词序列预测下一个词,与此同时,神经语言模型可以非常有效地学习出词向量。

发明内容

[0032] 针对代码推荐系统中,现有代码推荐算法存在的无法考虑时序信息、推荐效率低等问题,本发明提出一种基于长短期记忆网络的代码推荐方法。
[0033] 本发明提供的技术方案是一种基于长短期记忆网络的代码推荐方法,包括以下步骤:
[0034] 一种基于长短期记忆网络的代码推荐方法,其特征在于,包括以下步骤:
[0035] 步骤1,通过网络爬虫从GitHub网站爬取至少一万个Java开源软件代码,且每个Java开源软件代码的更新版本次数均超过1000次,这些开源软件代码构成了源代码库,然后对源代码进行预处理形成API序列事务库,并生成API字典和API向量矩阵,具体包括:
[0036] 步骤1.1,使用网络爬虫从GitHub网站爬取至少一万个Java开源软件代码,且每个Java开源软件代码的更新版本次数均超过1000次,这些开源软件代码构成了源代码库。
[0037] 步骤1.2,以方法为单位,对一个方法中包含的代码提取出该方法的API序列,源代码库中所有方法提取出的所有API序列组成了API序列事务库。对方法中包含的代码提取出API序列的规则为只提取新建对象语句的API和对象调用方法语句的API。新建对象语句提取出的API表示为“类名.new”,这里的类名为新建的对象所属的类的名字。对象调用方法语句提取出的API表示为“类名.方法名”,这里的类名为该对象所属的类的名字。
[0038] 步骤1.3,从API序列事务库中提取出API字典,并生成API向量矩阵。
[0039] API字典定义为:设API序列事务库为D,API字典可以表示为VD={1:API1,w1,2:API2,w2,…,i:APIi,wi,…n:APIn,wn},n为API字典的包含的API的个数,APIi表示VD中第i个API的名称,wi表示集合VD中第i个API的向量。
[0040] API字典和API向量矩阵的生成过程为:遍历API序列事务库,判断当前API是否存在于API字典中,如果存在,则忽略当前API,继续遍历下一个API,否则,将当前API加入到API字典中,并赋予其唯一的ID和赋予其一个随机的M维API向量。API字典中包含的n个API的n个M维的API向量组成了API向量矩阵。API向量矩阵是作为长短期记忆网络(Long Short-Term Memory,LSTM)模型的参数,在训练LSTM模型时会对API向量进行学习。
[0041] 步骤2,构建API推荐模型,即构建长短期记忆网络。定义长短期记忆网络包括输入层、隐藏层、全链接层和输出层;其中,
[0042] 输入层接受一串数值输入,经过前向传播输入到隐藏层,并与隐藏层上一时刻的输出共同影响隐藏层的当前输出,最后隐藏层产生的输出输入全链接层,全链接层输出数据,输入到输出层,输出层中的Softmax分类器输出最后的分类结果。
[0043] 隐藏层的神经单元为长短期记忆单元(LSTM),使用dropout技术防止长短期记忆网络过拟合,神经元激活函数使用ReLu函数。输入层的神经元个数为M,M为步骤1.3中生成的API向量的维度。隐藏层神经元个数为M,全链接层神经元个数为M,输出层的神经元个数为n,n为API字典中包含的API的个数,M、n的取值均为正整数。
[0044] 步骤3,训练API推荐模型,即训练长短期记忆网络。
[0045] API推荐模型的输入为一个Nb×Ns行M列的矩阵Tinput,其中Nb表示批大小,Ns表示序列长度,M表示API向量的维度,矩阵第i行表示输入序列中的第i个API所对应的向量。
[0046] API推荐模型的目标矩阵Ttarget是一个Nb行Ns列的矩阵,其中第i行第j列表示输入序列中的第i个API所对应的目标输出API在步骤1.3中生成的API字典中的ID。
[0047] API推荐模型的输出为Nb×Ns行n列的输出概率矩阵Tprob,其中n表示API字典中包含的API的个数,第i行第j列的数表示输入序列中的第i个API输入后所预测的下一个API属于API字典中第j个API的概率。
[0048] 该步骤包括以下步骤:
[0049] 步骤3.1,将API序列事务库中的所有API序列头尾相连,产生一个API总序列。
[0050] 步骤3.2,设置一个指针变量point,且point的初始值为1,从API总序列的第point个API开始,每次依次提取Ns个API,一共提取Nb批,对于每一个API,从API字典中读取其对应ID,并利用其ID,从API向量矩阵中提取该API所对应的向量,将其存入输入矩阵Tinput中。例如,第i批第j个API所对应的向量,存入输入矩阵Tinput中的第i×j行。对于目标矩阵,从API总序列的第point个API开始,每次依次提取Ns个API,一共提取Nb批,对于每一个API,从API字典中读取其对应ID,将对应的ID存入目标矩阵。最后,当输入矩阵和目标矩阵填充完毕后,令point变量指向API总序列中最后一个被目标矩阵读取的API。值得说明的是,当提取到API总序列中最后一个API后,继续提取API总序列中的第一个API。
[0051] 步骤3.3,从输入矩阵中依次提取API向量,作为API推荐模型的输入,对于时刻t,从输入矩阵中依次每一行API的向量作为模型的输入向量,记该API为APIt,将输入标记为xt,则LSTM模型的隐藏层输入门计算结果为it=σ(wixt+uibt+vict-1),遗忘门计算为结果ft=σ(wfxt+ufbt+vfct-1),输出门计算为ot=σ(woxt+uobt+voct),最后隐藏层的输出为bt=ot·tanh(ct),数据从隐藏层传入全连接层,最后输出层使用Softmax分类器。输出层得到:
[0052]
[0053] 其中|VD|表示API字典中包含的API个数,θ表示神经网络当前权值,θ1表示输出层第一个输出节点对应的一套权值。最后,将公式转置,并存入输出概率矩阵中。重复此步骤,直到输入矩阵中的API向量全部输入API推荐模型当中。
[0054] 步骤3.4,利用输出概率矩阵和目标矩阵计算交叉熵损失函数。交叉熵损失函数为其中,l表示指示函数,l(yt=j)表示当yt=j时,l(yt=j)=1,否则l(yt=j)=0,yt表示时刻i的目标输出API的ID。 表示输出概率矩阵中第i行第j列的输出概率。
[0055] 步骤3.5,根据交叉熵损失函数,将网络中的权值作为变量,计算网络中所有权值的梯度。并同时基于梯度剪裁,将权值的更新控制在一个设定范围;具体是:先设置一个名字为梯度剪裁的常量,标记为clip_gradient,在进行了反向传播时,将会得到每个参数的梯度,标记为diff,此时,并不选择直接更新权值,而是先求所有权重梯度的平方和,标记为sumsq_diff,如果所有权重梯度的平方和大于clip_gradient,则继续求出缩放因子,标记为scale_factor=clip_gradient/sumsq_diff。这个scale_factor在(0,1)之间。如果权重梯度的平方和sumsq_diff越大,那缩放因子将越小。最后将所有的权重梯度乘以这个缩放因子,这时得到的梯度才是最后的梯度信息。根据公式W=W-η▽J(θ)更新权值,▽J(θ)表示的是对应权值梯度,η表示学习率。
[0056] 步骤3.6,重复步骤3.2-3.5,直至收敛,即损失J(θ)不再上升或者下降。
[0057] 步骤4,对开发者正在编辑的代码提取出API序列,然后生成预测子序列集合。
[0058] 步骤4.1,对开发者正在编辑的代码提取出API序列,并记为P={P1,P2,…,Pi,…,PL},其中Pi表示该API序列P中第i个API,PL表示该API序列P中第L个API,也即该API序列P中包含的API个数为L。提取出API序列的规则与步骤1.2中的规则相同。
[0059] 步骤4.2,以第L个API为参考位置,向前选择所有长度小于等于阈值γ的子序列,即选取的子序列为Subi={PL-i,…,PL},其中1
[0060] 步骤5,将步骤4中生成的预测子序列集合VSub中的序列依次输入到步骤3训练好的API推荐模型中,输出一个|VSub|行n列的概率矩阵,其中|VSub|为子序列集合VSub中包含的子序列个数,n为步骤一中产生的API字典中包含的API个数,概率矩阵的第i行第j列表示当先前API序列是预测子序列Subi时,下一个API是API字典中的第j个API的条件概率Pr(wj|Subi)。将产生的预测概率矩阵Tprediction每一列取最大值,得到一个一维概率矩阵t,该一维概率矩阵中值最大的一列为第m列,则优先推荐API字典中第m个API。
[0061] 针对现有代码推荐技术普遍存在推荐准确率低、推荐效率低等问题,本发明先将源代码提取成API序列,利用长短期记忆网络构建一个代码推荐模型,学习API调用之间的关系,然后进行代码推荐。并使用了dropout技术防止模型过拟合。同时提出运用ReLu函数代替传统饱和函数,解决梯度消失问题加快模型收敛速度,提高模型性能,充分发挥神经网络的优势。本发明的技术方案具有简单、快速的特点,能够较好地提高代码推荐的准确率和推荐效率。

附图说明

[0062] 图1本发明流程图;
[0063] 图2本实施例readTxtFile方法的代码;
[0064] 图3本实施例writeTxtFile方法的代码;
[0065] 图4本实施例提取出的API事务数据库;
[0066] 图5本实施例提取出的API字典;
[0067] 图6本实施例长短期记忆网络;

具体实施方式

[0068] 为了便于本领域普通技术人员理解和实施本发明,下面结合附图及实施例对本发明作进一步的详细描述,应当理解,此处所描述的实施示例仅用于说明和解释本发明,并不用于限定本发明。
[0069] 本发明提供的基于长短期记忆网络的代码推荐方法的流程见附图1,所有步骤可由本领域技术人员采用计算机软件技术实现流程自动运行。实施例具体实现过程如下:
[0070] 步骤1,为了让源代码库具有很高的可信度和实用性,通过网络爬虫从GitHub网站爬取至少一万个Java开源软件代码,且每个Java开源软件代码的更新版本次数均超过1000次,这些开源软件代码构成了源代码库,然后对源代码进行预处理形成API序列事务库,并生成API字典和API向量矩阵,具体包括:
[0071] 步骤1.1,爬取至少一万个Java开源软件代码,且每个Java开源软件代码的更新版本次数均超过1000次,这些开源软件代码构成了源代码库。
[0072] 步骤1.2,以方法为单位,对一个方法中包含的代码提取出该方法的API序列,源代码库中所有方法提取出的所有API序列组成了API序列事务库。对方法中包含的代码提取出API序列的规则为只提取新建对象语句的API和对象调用方法语句的API。新建对象语句提取出的API表示为“类名.new”,这里的类名为新建的对象所属的类的名字。对象调用方法语句提取出的API表示为“类名.方法名”,这里的类名为该对象所属的类的名字。
[0073] 本实施例中,图2中readTxtFile方法中的代码,第一条语句“File file=new File(filePath)”是新建对象语句,提取出的API为File.new,第二条语句“if(file.isFile())”是对象调用方法语句,提取出的API为File.isFile,第三条语句“FileInputStream stream=new  FileInputStream(file)”是新建对象语句,提取出的API为FileInputStream.new,第四条语句“InputStreamReader read=new InputStreamReader(stream)”是新建对象语句,提取出的API为InputStreamReader.new,第五条语句“BufferedReader bufferedReader=new BufferedReader(read)”是新建对象语句,提取出的API为BufferedReader.new,第六条语句“String lineTxt=null”既不为新建对象语句也不为对象调用语句,因此提取不出API,第七条语句“while((lineTxt=bufferedReader.readLine())!=null)”是对象调用语句,提取出的API为
BufferedReader.readLine,第八条语句“System.out.println(lineTxt)”为对象调用语句,提取出的API为System.out.println,第九条语句“read.close()”为对象调用语句,提取出的API为InputStreamReader.close。因此,图2中的readTxtFile方法中代码所提取出的API序列为File.new,File.isFile,FileInputStream.new,InputStreamReader.new,BufferedReader.new,BufferedReader.readLine,System.out.println,
InputStreamReader.close。
[0074] 本实施例中,图3中writeTxtFile()方法中的代码,第一条语句“File bookFile=new File(“book.txt”)”是新建对象语句,提取出的API为File.new,第二条语句“Scanner bookSc=new Scanner(bookFile)”是新建对象语句,提取出的API为Scanner.new,第三条语句“File authorFile=new File(“authors.txt”)”是新建对象语句,提取出的API为File.new,第四条语句“FileWriter authorFW=new FileWriter(authorFile)”是新建对象语句,提取出的API为FileWriter.new,第五条语句“while(bookSc.hasNextLine())”是对象调用语句,提取出的API为Scanner.hasNextLine,第六条语句“String bookinfo=bookSc.nextLine()”是对象调用语句,提取出的API为Scanner.nextLine,第七条语句“authorFW.append(bookinfo)”是对象调用语句,提取出的API为FileWriter.append,第八条语句“authorFW.close()”是对象调用语句,提取出的API为FileWriter.close,第九条语句“bookSc.close()”是对象调用语句,提取出的API为Scanner.close。因此,图2中的writeTxtFile方法中代码所提取出的API序列为File.new,Scanner.new,File.new,FileWriter.new,Scanner.hasNextLine,Scanner.nextLine,FileWriter.append,FileWriter.close,Scanner.close。
[0075] 最终这两个方法提取出的所有API序列组成了如图4所示的API序列事务库。
[0076] 步骤1.3,从API序列事务库中提取出API字典,并生成API向量矩阵。
[0077] API字典定义为:设API序列事务库为D,API字典可以表示为VD={1:API1,w1,2:API2,w2,…,i:APIi,wi,…n:APIn,wn},n为API字典的包含的API的个数,APIi表示VD中第i个API的名称,wi表示集合VD中第i个API的向量。
[0078] API字典和API向量矩阵的生成过程为:遍历API序列事务库,判断当前API是否存在于API字典中,如果存在,则忽略当前API,继续遍历下一个API,否则,将当前API加入到API字典中,并赋予其唯一的ID和赋予其一个随机的M维API向量。API字典中包含的n个API的n个M维的API向量组成了API向量矩阵。API向量矩阵是作为长短期记忆网络(Long Short-Term Memory,LSTM)模型的参数,在训练LSTM模型时会对API向量进行学习。
[0079] 本实施例中,遍历图4中API序列事务库,API序列事务库第一个API序列的第一个API File.new不存在于API字典中,赋予其唯一的ID为1和赋予其一个随机的100维API向量w1=[0.1,0.3,0.5,0.5,…,0.5],并将其加入API字典中,当前的API字典为VD={1:File.new,w1}。第一个API序列的第二个API File.isFile不存在于API字典中,赋予其唯一的ID为2和赋予其一个随机的100维API向量w2=[0.2,0.5,0.5,0.4,…,0.7],并将其加入API字典中,当前的API字典为VD={1:File.new,w1,2:File.ifFile,w2}。第一个API序列的第三个API FileInputStream.new不存在于API字典中,赋予其唯一的ID为3和赋予其一个随机的100维API向量w3=[0.4,0.2,0.5,0.2,…,0.2],并将其加入API字典中,当前的API字典为VD={1:File.new,w1,2:File.ifFile,w2,3:FileInputStream.new,w3}。第一个API序列的第四个API InputStreamRead.new不存在于API字典中,赋予其唯一的ID为4和赋予其一个随机的100维API向量w4=[0.3,0.3,0.5,0.2,…,0.9],并将其加入API字典中,当前的API字典为VD={1:File.new,w1,2:File.ifFile,w2,3:FileInputStream.new,w3,4:
InputStreamRead.new,w4}。第一个API序列的第五个API BufferedRead.new不存在于API字典中,赋予其唯一的ID为5和赋予其一个随机的100维API向量w5=[0.1,0.6,0.5,
0.6,…,0.5],并将其加入API字典中,当前的API字典为VD={1:File.new,w1,2:
File.ifFile,w2,3:FileInputStream.new,w3,4:InputStreamRead.new,w4,5:
BufferedRead.new,w5}。第一个API序列的第六个API BufferedRead.readLine不存在于API字典中,赋予其唯一的ID为6和赋予其一个随机的100维API向量w6=[0.5,0.3,0.5,
0.7,…,0.3],并将其加入API字典中,当前的API字典为VD={1:File.new,w1,2:
File.ifFile,w2,3:FileInputStream.new,w3,4:InputStreamRead.new,w4,5:
BufferedRead.new,w5,6:BufferedRead.readLine,w6}。第一个API序列的第七个API System.out.println不存在于API字典中,赋予其唯一的ID为7和赋予其一个随机的100维API向量w7=[0.1,0.3,0.5,0.5,…,0.5],并将其加入API字典中,当前的API字典为VD={1:
File.new,w1,2:File.ifFile,w2,3:FileInputStream.new,w3,4:InputStreamRead.new,w4,5:BufferedRead.new,w5,6:BufferedRead.readLine,w6,7:System.out.println,w7}。
第一个API序列的第八个API InputStreamReader.close不存在于API字典中,赋予其唯一的ID为8和赋予其一个随机的100维API向量w8=[0.7,0.2,0.1,0.8,…,0.3],并将其加入API字典中,当前的API字典为VD={1:File.new,w1,2:File.ifFile,w2,3:
FileInputStream.new,w3,4:InputStreamRead.new,w4,5:BufferedRead.new,w5,6:
BufferedRead.readLine,w6,7:System.out.println,w7,8:InputStreamReader.close,w8}。
[0080] API序列事务库中第二个API序列的第一个API File.new存在于API字典中,忽略。第二个API序列的第二个API Scanner.new不存在于API字典中,赋予其唯一的ID为9和赋予其一个随机的100维API向量w9=[0.3,0.8,0.2,0.1,…,0.7],并将其加入API字典中,当前的API字典为VD={1:File.new,w1,2:File.ifFile,w2,3:FileInputStream.new,w3,4:
InputStreamRead.new,w4,5:BufferedRead.new,w5,6:BufferedRead.readLine,w6,7:
System.out.println,w7,8:InputStreamReader.close,w8,9:Scanner.new,w9}。第二个API序列的第三个APIFile.new存在于API字典中,忽略。第二个API序列的第四个API FileWriter.new不存在于API字典中,赋予其唯一的ID为10和赋予其一个随机的100维API向量w10=[0.4,0.2,0.8,0.7,…,0.3],并将其加入API字典中,当前的API字典为VD={1:
File.new,w1,2:File.ifFile,w2,3:FileInputStream.new,w3,4:InputStreamRead.new,w4,5:BufferedRead.new,w5,6:BufferedRead.readLine,w6,7:System.out.println,w7,8:
InputStreamReader.close,w8,9:Scanner.new,w9,10:FileWriter.new,w10}。第二个API序列的第五个API Scanner.hasNextLine不存在于API字典中,赋予其唯一的ID为11和赋予其一个随机的100维API向量w11=[0.1,0.4,0.5,0.3,…,0.1],并将其加入API字典中,当前的API字典为VD={1:File.new,w1,2:File.ifFile,w2,3:FileInputStream.new,w3,4:
InputStreamRead.new,w4,5:BufferedRead.new,w5,6:BufferedRead.readLine,w6,7:
System.out.println,w7,8:InputStreamReader.close,w8,9:Scanner.new,w9,10:
FileWriter.new,w10,11:Scanner.hasNextLinec,w11}。第二个API序列的第六个API Scanner.nextLine不存在于API字典中,赋予其唯一的ID为12和赋予其一个随机的100维API向量w12=[0.5,0.3,0.5,0.7,…,0.3],并将其加入API字典中,当前的API字典为VD={1:File.new,w1,2:File.ifFile,w2,3:FileInputStream.new,w3,4:
InputStreamRead.new,w4,5:BufferedRead.new,w5,6:BufferedRead.readLine,w6,7:
System.out.println,w7,8:InputStreamReader.close,w8,9:Scanner.new,w9,10:
FileWriter.new,w10,11:Scanner.hasNextLinec,w11,12:Scanner.nextLine,w12}。第二个API序列的第七个API FileWriter.append不存在于API字典中,赋予其唯一的ID为13和赋予其一个随机的100维API向量w13=[0.3,0.1,0.7,0.3,…,0.6],并将其加入API字典中,当前的API字典为VD={1:File.new,w1,2:File.ifFile,w2,3:FileInputStream.new,w3,4:
InputStreamRead.new,w4,5:BufferedRead.new,w5,6:BufferedRead.readLine,w6,7:
System.out.println,w7,8:InputStreamReader.close,w8,9:Scanner.new,w9,10:
FileWriter.new,w10,11:Scanner.hasNextLinec,w11,12:Scanner.nextLine,w12,13:
FileWriter.append,w13}。
[0081] 第二个API序列的第八个API FileWriter.close不存在于API字典中,赋予其唯一的ID为14和赋予其一个随机的100维API向量w14=[0.4,0.8,0.4,0.2,…,0.1],并将其加入API字典中,当前的API字典为VD={1:File.new,w1,2:File.ifFile,w2,3:FileInputStream.new,w3,4:InputStreamRead.new,w4,5:BufferedRead.new,w5,6:
BufferedRead.readLine,w6,7:System.out.println,w7,8:InputStreamReader.close,w8,
9:Scanner.new,w9,10:FileWriter.new,w10,11:Scanner.hasNextLinec,w11,12:
Scanner.nextLine,w12,13:FileWriter.append,w13,14:FileWriter.close,w14}。第二个API序列的第九个API Scanner.close不存在于API字典中,赋予其唯一的ID为15和赋予其一个随机的100维API向量w15=[0.5,0.2,0.3,0.1,…,0.2],并将其加入API字典中,最终提取出的API字典为VD={1:File.new,w1,2:File.ifFile,w2,3:FileInputStream.new,w3,4:
InputStreamRead.new,w4,5:BufferedRead.new,w5,6:BufferedRead.readLine,w6,7:
System.out.println,w7,8:InputStreamReader.close,w8,9:Scanner.new,w9,10:
FileWriter.new,w10,11:Scanner.hasNextLinec,w11,12:Scanner.nextLine,w12,13:
FileWriter.append,w13,14:FileWriter.close,w14,15:Scanner.close,w15}。本实施例中,API字典中包含的15个API的15个100维的API向量组成了如图5所示的API向量矩阵。
[0082] 步骤2,构建API推荐模型,即构建长短期记忆网络。请见图6,长短期记忆网络由输入层、隐藏层、全链接层和输出层构成。其中输入层接受一串数值输入,经过前向传播输入到隐藏层,并与隐藏层上一时刻的输出共同影响隐藏层的当前输出,最后隐藏层产生的输出输入全链接层,全链接层输出数据,输入到输出层,输出层中的Softmax分类器输出最后的分类结果。具体实施时,隐藏层的神经单元为长短期记忆单元(LSTM),使用dropout技术防止长短期记忆网络过拟合,神经元激活函数使用ReLu函数。本实施例中,输入层的神经元个数为100,100为步骤1.3中生成的API向量的维度。隐藏层神经元个数为100,全链接层神经元个数为100,输出层的神经元个数为15,15为API字典中包含的API的个数。
[0083] 步骤3,训练API推荐模型,即训练长短期记忆网络。
[0084] API推荐模型的输入为一个Nb×Ns行M列的矩阵Tinput,其中Nb表示批大小,Ns表示序列长度,M表示API向量的维度,矩阵第i行表示输入序列中的第i个API所对应的向量。
[0085] API推荐模型的目标矩阵Ttarget是一个Nb行Ns列的矩阵,其中第i行第j列表示输入序列中的第i个API所对应的目标输出API在步骤1.3中生成的API字典中的ID。
[0086] API推荐模型的输出为Nb×Ns行n列的输出概率矩阵Tprob,其中n表示API字典中包含的API的个数,第i行第j列的数表示输入序列中的第i个API输入后所预测的下一个API属于API字典中第j个API的概率。
[0087] 该步骤主要包括以下步骤:
[0088] 步骤3.1,将API序列事务库中的所有API序列头尾相连,产生一个API总序列。
[0089] 本实施例对图4中的API序列数据库中的所有API序列头尾相连后,产生一个API总序列为:File.new,File.isFile,FileInputStream.new,InputStreamReader.new,BufferedReader.new,BufferedReader.readLine,System.out.println,InputStreamReader.close,File.new,Scanner.new,File.new,FileWriter.new,Scanner.hasNextLine,Scanner.nextLine,FileWriter.append,FileWriter.close,Scanner.close。
[0090] 步骤3.2,设置一个指针变量point(point的初始值为1),从API总序列的第point个API开始,每次依次提取Ns个API,一共提取Nb批,对于每一个API,从API字典中读取其对应ID,并利用其ID,从API向量矩阵中提取该API所对应的向量,将其存入输入矩阵Tinput中。例如,第i批第j个API所对应的向量,存入输入矩阵Tinput中的第i×j行。对于目标矩阵,从API总序列的第point个API开始,每次依次提取Ns个API,一共提取Nb批,对于每一个API,从API字典中读取其对应ID,将对应的ID存入目标矩阵。最后,当输入矩阵和目标矩阵填充完毕后,令point变量指向API总序列中最后一个被目标矩阵读取的API。值得说明的是,当提取到API总序列中最后一个API后,继续提取API总序列中的第一个API。
[0091] 本实施例,设批大小Nb是2,序列长度Ns是2,API向量维度是100。初始阶段,point=0,从API总序列的第1个API开始,依次提取2个API,一共提取2批,因此提取出的API为File.new,File.isFile,FileInputStream.new,InputStreamReader.new。对于每一个API,从API字典中读取其对应ID,并利用其ID,从API向量矩阵中提取该API所对应的向量,将其存入输入矩阵Tinput中,因此输入矩阵 目标矩阵从API
总序列的第2个API开始,依次提取2个API,一共提取2批,因此提取出的API为File.isFile,FileInputStream.new,InputStreamReader.new,BufferedReader.new,对于每一个API,从API字典中读取其对应ID,存入目标矩阵Ttarget中,因此目标矩阵
[0092] API字典包含的API个数为15,则建立一个大小为4(=2×2)行100列输入矩阵,建立一个4(=2×2)行15列的输出概率矩阵,建立一个2行2列的目标矩阵。
[0093] 步骤3.3,从输入矩阵中依次提取API向量,作为API推荐模型的输入,对于时刻t,从输入矩阵中依次每一行API的向量作为模型的输入向量,记该API为APIt,将输入标记为xt,则LSTM模型的隐藏层输入门计算结果为it=σ(wixt+uibt+vict-1),遗忘门计算为结果ft=σ(wfxt+ufbt+vfct-1),输出门计算为ot=σ(woxt+uobt+voct),最后隐藏层的输出为bt=ot·tanh(ct),数据从隐藏层传入全连接层,最后输出层使用Softmax分类器。输出层得到:
[0094]
[0095] 其中|VD|表示API字典中包含的API个数,θ表示神经网络当前权值,θ1表示输出层第一个输出节点对应的一套权值。最后,将公式转置,并存入输出概率矩阵中。重复此步骤,直到输入矩阵中的API向量全部输入API推荐模型当中。
[0096] 本实施例中,假设将步骤3.2中产生的输入矩阵输入到API推荐模型中,得到输出概率矩阵为
[0097]
[0098] 步骤3.4,利用输出概率矩阵和目标矩阵计算损失函数。交叉熵损失函数为其中,l表示指示函数,l(yt=j)表示当yt=j时,l(yt=j)=1,否则l(yt=j)=0,yt表示时刻i的目标输出API的ID。 表示输出概率矩阵中第i行第j列的输出概率。
[0099] 本实施例目标矩阵 根据步骤3.3中的输出概率矩阵,得到损失如下:
[0100]
[0101] 步骤3.5,根据损失函数,将网络中的权值作为变量,计算网络中所有权值的梯度。与此同时,引入梯度剪裁技术,将权值的更新控制在一个合适的范围中,更好的解决梯度消失或者梯度下降问题。具体实施时,先设置一个名字为梯度剪裁的常量,标记为clip_gradient,在进行了反向传播时,将会得到每个参数的梯度,标记为diff,此时,并不选择直接更新权值,而是先求所有权重梯度的平方和,标记为sumsq_diff,如果所有权重梯度的平方和大于clip_gradient,则继续求出缩放因子,标记为scale_factor=clip_gradient/sumsq_diff。这个scale_factor在(0,1)之间。如果权重梯度的平方和sumsq_diff越大,那缩放因子将越小。最后将所有的权重梯度乘以这个缩放因子,这时得到的梯度才是最后的梯度信息。根据公式W=W-η▽J(θ)更新权值,▽J(θ)表示的是对应权值梯度,η表示学习率。
[0102] 步骤3.6,重复步骤3.2-3.5,直至收敛,即损失J(θ)不再上升或者下降。
[0103] 步骤4,对开发者正在编辑的代码提取出API序列,然后生成预测子序列集合。
[0104] 步骤4.1,对开发者正在编辑的代码提取出API序列,并记为P={P1,P2,…,Pi,…,PL},其中Pi表示该API序列P中第i个API,PL表示该API序列P中第L个API,也即该API序列P中包含的API个数为L。提取出API序列的规则与步骤1.2中的规则相同。
[0105] 步骤4.2,以第L个API为参考位置,向前选择所有长度小于等于阈值γ的子序列,即选取的子序列为Subi={PL-i,…,PL},其中1
[0106] 本实施例中,假设用户正在编辑代码为:
[0107]
[0108]
[0109] 对该代码提取出的API序列为:File.new,Scanner.new,Scanner.hasNextLine,Scanner.NextLine。需要预测API的语句为“noteSc.?”。设阈值γ为3,则可以得到预测子序列Sub1={Scanner.NextLine},Sub2={Scanner.hasNextLine,Scanner.NextLine},Sub3={Scanner.new,Scanner.hasNextLine,Scanner.NextLine}。这些子序列的集合即为预测子序列集合VSub={Sub1,Sub2,Sub3}。
[0110] 步骤5,将步骤4中生成的预测子序列集合VSub中的序列依次输入到步骤3训练好的API推荐模型中,输出一个|VSub|行n列的概率矩阵,其中|VSub|为子序列集合VSub中包含的子序列个数,n为步骤一中产生的API字典中包含的API个数,概率矩阵的第i行第j列表示当先前API序列是预测子序列Subi时,下一个API是API字典中的第j个API的条件概率Pr(wj|Subi)。将产生的预测概率矩阵Tprediction每一列取最大值,得到一个一维概率矩阵t,该一维概率矩阵中值最大的一列为第m列,则优先推荐API字典中第m个API。
[0111] 本实施例中,以步骤4中得到的预测子序列集合为例,则建立一个3行15列的预测概率矩阵,将预测子序列Sub1={Scanner.NextLine},Sub2={Scanner.hasNextLine,Scanner.NextLine},Sub3={Scanner.new,Scanner.hasNextLine,Scanner.NextLine}依次输入到已训练的模型中,将输出存入预测概率矩阵,假设得到的预测概率矩阵为[0112]
[0113] 将产生的预测概率矩阵Tprediction每一列取最大值,得到一个一维概率矩阵t=[0.6,0.3,0.5,0.2,0.3,0.2,0.3,0.4,0.3,0.5,0.2,0.3,0.4,0.3,0.8],由于一维概率矩阵的第15列值最大,所以优先推荐API字典中第15个API。
[0114] 本文中所描述的具体实施例仅仅是对本发明精神作举例说明。本发明所属技术领域的技术人员可以对所描述的具体实施例做各种各样的修改或补充或采用类似的方式替代,但并不会偏离本发明的精神或者超越所附权利要求书所定义的范围。