作者:熊伟,黄飞,腾讯PCG/QQ研发中心/CV应用研究组AI如果真的会写代码,程序员会去哪里?近年来,NLP领域的生成任务有了明显的提升,那么能否利用AI来自动完成后续的代码补全呢?本文主要介绍如何使用GPT2框架实现代码自动补全功能。如果AI真的可以自己编写代码,程序员将何去何从?去年做了一个代码补全的小功能,打包成androidStudio插件。使用效果如下:代码补全模型预测的结果有时候真的吓到我了,这个也可以学习~?那如果给它看一眼世界上优秀的代码,再给它足够量级的参数和优秀的模型框架,它真的能实现需求作为输入直接输出代码吗?希望能看到这一天。代码补全功能已经被其他优秀的插件实现,比如tabnine、Kite和国产的aixcoder。本文主要介绍代码补全功能需要实现的整个过程。主要包括数据、算法和工程。数据是已知的,算法工程师大部分时间都在处理数据。深度学习是利用大数据训练模型的过程,数据是非常重要的一个模块。人很累,休息不好会导致记忆力变差。人工智能意味着它可以存储和接收你提供给它的尽可能多的数据。如果它不能学习信息,那是人类的错。给的数据不好或者算法设计不好。所以我们首先准备尽可能多的训练数据。1.数据收集本文的目的是代码补全,训练数据就是代码段。鉴于每种语言的风格和语法不一致,单个模型仅针对一种代码语言。我使用的训练数据主要来自GitHub,写了一个简单的爬虫代码,指定语言后,按照stars顺序下载项目。Github的搜索API官方地址:https://developer.github.com/v3/search/2。数据清洗直接下载的数据一定不能直接使用,我们还是要对数据进行清洗。首先,我们的训练数据只需要项目中的代码文件。以java项目为例,我们只保留.java结尾的文件,其他文件可以剔除。其次,我的代码补全目标是代码段,而不是注释函数。而对于代码补全训练,我们会给出一定范围的上面,如果有评论区,会占用有效代码信息。另外,英文以外的字符不在我的训练词汇范围内,所以需要清理代码中的注释和日志。1.删??除代码行中除符号和英文以外的字符2.删除日志行3.删除注释行,主要针对以下格式/*注释文字*//**注释段落*///注释文字代码//comment经过以上数据清洗,得到纯代码数据。3.数据编码得到训练数据后,需要对代码文本进行编码。本文采用bpe(bytepairencoder)字节对编码,主要是数据压缩。BPE简单理解就是把一个词拆分成多个字母组合,比如把tencent拆分成ten-cent,这些组合是根据大量数据和统计频率得到的。由于我们期望的代码补全功能是在行首输入几个字母,所以该行的内容按照上面的期望。假设tensorflow的token编码对应一个id,我不可能输入十就输出tensorflow。所以在训练过程中,我会随机打断token,比如打断tensorflow进入t-en-sor-flow进行编码。打断的原则是被切分的部分必须在词汇表中。数据编码后,code的每个token被编码成1~N个id。模型预测的id可以反向编码成token。回车被认为是预测终止符。经过上面的处理,我们已经准备好了训练数据,下面就可以进行算法部分了。模型算法众所周知,算法工程师大部分时间都花在算法上。在腾讯文档错别字的纠错要求中,我们使用了facebook提出的基于LSTM的seq2seq和基于CNN的seq2seq,可以得到很好的纠错效果。直到出现了NLP领域的“网红”——BERT,采用后准确率直接提升了8个点左右,不输google。下面简单介绍一下bert和gpt2。BERT和GPT22017年,谷歌提出了Transformer结构。没有rnn,没有cnn,你只需要注意力。2018年,openAI采用了transformers结构,2018年发布了GPT。同年,googleAILanguage发布了bertpaper,提出的BERT模型在11个NLP任务上刷新记录。2019年,openAI推出了GPT-2模型。.BERT(BidirectionalEncoderRepresentationfromTransformers)是基于transformers框架的编码器部分,一种自编码语言模型,适用于N-1(如句子分类)、N-N(如词性标注)任务,但它不适合生成任务。GPT(GenerativePre-Training)是基于transformers的decoder部分,一种自回归语言模型,适用于生成任务。Transformer框架图GPT2和BTER框架图代码补全功能基于GPT2框架。OPenAI官方提供多套GPT2预训练模型:官方提供GPT2参数作为CVer,经常将模型部署到移动端。看到这个参数水平,我选择最小的模型进行finetune。对于GPT算法,下面这篇文章很不错,有兴趣的同学可以看看:https://zhuanlan.zhihu.com/p/137350403GPT2预测过程本文在训练中使用512以上,预测回车符终止.模型网络使用超参数:12层,768个隐藏节点,12个头,使用uber的Horovod分布式框架进行训练。在infer阶段使用beam-search会导致整个预测过程特别耗时,所以参考https://arxiv.org/abs/1904.09751论文,使用top-k采样,每次top3预测结果通过概率阈值过滤,作为最终的候选输出。最终推断效果:输入一段代码,预测后续代码,回车结束。工程我们都知道,算法工程师大部分时间都花在工程上。模型训练好后,需要对模型进行应用,所以需要实现一些工程化工作。对于代码补全功能,最适合的应用场景就是使用IDE。nlp模型不适合本地部署。最后我选择将模型部署在GPU机器上,然后终端通过http请求获取预测的文字显示方案。后台部署Flask是一个Web应用框架,灵活、轻量级、易用。本文简单介绍一下如何使用flask启动一个web服务,以及如何访问和调用我们的功能接口。首先,我们创建一个conda环境:condacreate-nflaskpython=3.6sourceactivateflaskpipinstallflask在flask代码中添加一个接口函数:fromflaskimportFlaskfromflaskimportrequestapp=Flask()#route绑定一个函数到对应的url@app.route("/plugin",methods=['GET',])defsend():data=request.args.get('data')#模型预测逻辑out=model_infer(data)returnoutif__name__=='__main__':app.run(host='0.0.0.0',port=8080,debug=False)执行run.py代码,后台服务开始运行:clientrequest:url=http://ip:8080/plugin?data="input"其中model_infer函数需要实现模型的infer到计算逻辑,从请求中获取data字段作为输入,将infer预测的结果列表返回给调用者作为输出。经过上面的工作,我们已经提供了一个服务接口来返回我们代码补全的预测结果。写插件的最后一步就是如何在IDE上使用功能了。如果我们要开发AS插件,就需要用到IntelliJ。首先,我们需要在本地安装和配置IntelliJIDEA。下载地址:https://www.jetbrains.com/idea/download/Community版本源码:https://github.com/JetBrains/intellij-community是一款非常实用的插件,可以为程序员节省很多时间。在实现插件的时候,我还加入了一个git-blame的小功能,可以实时查看指定行的gitcommitter。对于手机QQ办公等多人协作,更实用。你也可以通过IntelliJ自己开发一些常用的功能。gitBlame的主要代码:publicclassGitBlameextendsAnAction{privatevoidshowPopupBalloon(finalEditoreditor,finalStringresult){ApplicationManager.getApplication().invokeLater(newRunnable(){publicvoidrun(){JBPopupFactoryfactory=JBPopupFactory.getInstance();factory.createHtmlTextBalloonBuilder(newBColor(newBColor)186,238,186),newColor(73,117,73)),null).setFadeoutTime(5000).createBalloon().show(factory.guessBestPopupLocation(editor),Balloon.Position.below);}});}@OverridepublicvoidactionPerformed(AnActionEvente){//TODO:insertactionlogichere//获取当前本地代码的根目录Stringbase_path=e.getProject().getBasePath();Stringfile_path=e.getProject().getProjectFilePath();//获取编辑器mEditorfinalEditormEditor=e.getData(PlatformDataKeys.EDITOR);if(null==mEditor){return;}SelectionModelmodel=mEditor.getSelectionModel();finalStringselectedText=model.getSelectedText();if(TextUtils.isEmpty(selectedText)){return;}//获取当前编辑文件的目录PsiFilemPsifile=e.getData(PlatformDataKeys.PSI_FILE);VirtualFilefile=mPsifile.getContainingFile().getOriginalFile().getVirtualFile();if(file!=null&&file.isInLocalFileSystem()){file_path=file.getCanonicalPath();}//gitkit工具JGitUtilgitKit=newJGitUtil();Stringfilename=file_path.replace(base_path+"/","");//获取blame信息intline_index=mEditor.getSelectionModel().getSelectionStartPosition().getLine();Stringblame_log=gitKit.git_blame(base_path,filename,line_index);//显示if(!blame_log.isEmpty()){showPopupBalloon(mEditor,blame_log);}}}本文代码补全插件的主要代码逻辑是调用上一步后台部署的请求//Requesturl格式(与flask接口一致)StringbaseUrl="http://ip:8080/plugin?data=";//获取当前编辑位置的文本PsiFilestr=position.getContainingFile();//根据获取代码结束对模型的上述限制Stringdata=getContentCode();Stringurl=baseUrl+data;//发送请求Stringresult=HttpUtils.doGet(url);//后处理ng逻辑,在提示框显示预测结果show()最终呈现形式:可以看出模型的预测结果还是不错的~以上是代码完成功能的实现和应用可以看成是一个AI自动代码编写的一小步。AI能否自己编写代码在嫌疑人追踪上达到TM的水平?我不敢说不可能,但以我现在的知识,是无法实现的。毕竟,编写代码的是程序员,将数据提供给算法的是程序。工作人员、算法设计或程序员,AI甚至还没有出现来帮助人类解决bug!\参考文献:[1]https://arxiv.org/abs/1706.03762[2]https://arxiv.org/abs/1810.04805[3]https://github.com/openai/gpt-2[4]https://arxiv.org/abs/1904.09751
