算法 自动采集列表(如何使用GPT2框架实现代码自动补全的功能?(上))
优采云 发布时间: 2021-09-10 21:13算法 自动采集列表(如何使用GPT2框架实现代码自动补全的功能?(上))
作者:魏雄、黄飞、腾讯PCG/QQ研发中心/简历应用研究组
如果AI真的会写代码,程序员会去哪里?近年来,NLP领域的生成任务有了明显的提升。能否通过AI让代码自动完成后续补全?本文主要介绍如何使用GPT2框架实现代码补全功能。
如果人工智能真的可以自己写代码,程序员会去哪里?
我去年做了一个小代码补全功能,打包成androidStudio插件,效果如下:
代码补全模型预测的结果有时候确实能吓到我,这也能学~?那么如果给它洞察世界上优秀的代码,再给它足够的幅度参数和优秀的模型框架,真的可以实现需求作为输入直接输出代码吗?
“我的要求已经结束,你的代码呢?”我希望看到这一天。
代码补全功能也已经被其他优秀的插件实现了,比如tabnine、Kite和国内的aixcoder。本文主要介绍代码补全功能需要实现的全过程。主要包括数据、算法和工程。
数据
众所周知,算法工程师大部分时间都花在处理数据上。
深度学习是一个利用大数据训练模型的过程,数据是一个非常重要的模块。人累了,休息不好会导致记忆力差。 AI 意味着它可以存储和接收尽可能多的数据。如果你没有学习这些信息,那是人为的错。给定的数据不好或算法设计不好。所以我们首先准备尽可能多的训练数据。
1、数据采集
本文的目的是代码补全,训练数据是代码段。考虑到每种语言风格和语法不一致,单一模型只针对一种代码语言。
我使用的训练数据主要来自GitHub。我写了一个简单的爬虫代码。指定语言后,我按照星星的顺序下载项目。
Github 搜索 API 官方地址:/v3/search/
2、数据清理
直接下载的数据不能直接使用。我们还需要清理数据。
首先,我们的训练数据只需要项目中的代码文件。以java项目为例,我们只保留.java结尾的文件,其他文件可以去掉。
其次,我的代码补全的目标是代码段,而不是注释功能。而对于代码补全训练,我们会给出一定范围的上述内容,如果有注释部分,会占用有效的代码信息。另*敏*感*词*内,所以代码中的注释和日志需要清理。
1.删除代码行中除符号和英文以外的字符的存在
2.删除日志行
3.删除注释行,主要针对以下格式
/* 注释文本*/
/**
注释段落
*/
// 注释文本
code //注释
经过以上数据清洗,得到纯代码数据。
3、数据编码
得到训练数据后,需要对代码文本进行编码。本文使用bpe(byte pair encoding)字节对编码,主要用于数据压缩。 Bpe 简单理解为将一个单词拆分为多个字母组合。例如,腾讯拆分为十美分。这些组合基于大量数据和统计频率。由于我们期望的代码补全功能是在行首输入几个字母,所以这一行的内容是按照上面的来期望的。
假设token tensorflow被编码为对应一个id,那么我希望输入十,输出tensorflow是不可能的。所以在训练过程中,我会随机打断token,比如打断tensorflow到t-en-sor-flow进行编码。打断的原则是被分割的部分必须在词汇表中。数据编码后,将编码的每个token编码成1~N个id。模型预测的id可以反向编码为token。回车符被认为是预测的终止符。经过上面的处理,我们已经准备好了训练数据,接下来就可以进入算法部分了。
模型算法
众所周知,算法工程师大部分时间都在研究算法。
在腾讯文档的错别字和纠错要求中,我们使用了基于LSTM的seq2seq和facebook的基于CNN的seq2seq,可以得到很好的纠错效果。直到NLP出现了“网红”-BERT,采用后准确率直接提升了8分左右,而且是google。下面简单介绍一下bert和gpt2。
BERT 和 GPT2
2017 年年中,google 提出了 Transformer 结构。不需要rnn,不需要cnn,引起关注就是你所需要的。 2018年openAI采用transformers结构,2018年发布GPT。同年google AI Language发布bert论文,提出的BERT模型在11个NLP任务上创下新纪录。 2019 年,openAI 推出了 GPT-2 模型。 .
BERT(Bidirectional Encoder Representation from Transformers)基于transformers框架的encoder部分,自编码语言模型,适用于N-1(如句子分类)、NN(如词性标注)任务,但不适合生成Task。
GPT(Generative Pre-Training)基于transformers的*敏*感*词*部分,一种自回归语言模型,适用于生成任务。
代码补全功能基于GPT2框架。 OPenAI官方提供多套GPT2预训练模型:
作为经常将模型部署到移动端的CVer,看到这个参数级别,我选择了最小的模型进行finetune。
对于GPT算法,/p/137350403这个文章很不错,有兴趣的同学可以看看。
本文在训练中使用了512个文本,预测回车符结束。模型网络使用超参数:12 层、768 个隐藏节点和 12 个头。它使用 uber 的 Horovod 分布式框架进行训练。
infer阶段使用beam-search会导致整个预测过程特别耗时,所以参考/abs/1904.09751论文,使用top-k采样,并且每次将top3预测的结果通过概率阈值过滤作为最终候选输出。
最终推断效果:
输入代码,预测后续代码,以回车结束。
工程
众所周知,算法工程师大部分时间都花在工程上。
模型训练好后,需要应用模型,所以需要实现一些工程工作。代码补全功能最适合的应用场景是使用IDE。 nlp模型不适合本地部署,最后选择在GPU机器上部署模型,然后终端通过http请求获得预测文本显示的解决方案。
后台部署
Flask 是一个 Web 应用程序框架,灵活、轻便且易于使用。本文简单介绍了如何使用flask启动一个web服务,以及如何访问和调用我们的功能接口。首先我们创建一个 conda 环境:
conda create -n flask python=3.6
source activate flask
pip install flask
在代码中添加一个接口函数:
from flask import Flask
from flask import request
app = Flask()
# route把一个函数绑定到对应的 url 上
@app.route("/plugin",methods=['GET',])
def send():
data = request.args.get('data')
# 模型预测逻辑
out = model_infer(data)
return out
if __name__ == '__main__':
app.run(host='0.0.0.0',port=8080, debug=False)
执行run.py代码,后台服务启动并运行:
客户请求:
url = http://ip:8080/plugin?data="输入"
model_infer函数需要实现模型的infer前向计算逻辑。从请求中获取数据字段作为输入,将infer预测的结果列表作为输出返回给调用者。
经过上述工作,我们提供了一个服务接口,用于返回我们代码完成的预测结果。
插件编写
最后一步是如何使用IDE上的功能。如果我们要开发AS插件,需要用到IntelliJ,首先我们需要在本机上安装配置IntelliJ IDEA
下载地址:/idea/download/
社区版源码:/JetBrains/intellij-community
一个简单易用的插件可以为程序员节省大量时间。插件实现的时候,我还加了一个小的git-blame功能,可以实时查看指定行的git committer。对于手机QQ等多人协同工作,更实用。也可以通过 IntelliJ 自己开发一些常用功能。
gitBlame 的主要代码:
public class GitBlame extends AnAction {
private void showPopupBalloon(final Editor editor, final String result) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
JBPopupFactory factory = JBPopupFactory.getInstance();
factory.createHtmlTextBalloonBuilder(result, null, new JBColor(new Color(186, 238, 186), new Color(73, 117, 73)), null)
.setFadeoutTime(5000)
.createBalloon()
.show(factory.guessBestPopupLocation(editor), Balloon.Position.below);
}
});
}
@Override
public void actionPerformed(AnActionEvent e) {
// TODO: insert action logic here
//获得当前本地代码根目录
String base_path = e.getProject().getBasePath();
String file_path = e.getProject().getProjectFilePath();
//获取编辑mEditor
final Editor mEditor = e.getData(PlatformDataKeys.EDITOR);
if (null == mEditor) {
return;
}
SelectionModel model = mEditor.getSelectionModel();
final String selectedText = model.getSelectedText();
if (TextUtils.isEmpty(selectedText)) {
return;
}
//获取当前编辑文档的目录
PsiFile mPsifile = e.getData(PlatformDataKeys.PSI_FILE);
VirtualFile file = mPsifile.getContainingFile().getOriginalFile().getVirtualFile();
if (file != null && file.isInLocalFileSystem()) {
file_path = file.getCanonicalPath();
}
//gitkit工具
JGitUtil gitKit = new JGitUtil();
String filename = file_path.replace(base_path+"/","");
//得到blame信息
int line_index = mEditor.getSelectionModel().getSelectionStartPosition().getLine();
String blame_log = gitKit.git_blame(base_path,filename,line_index);
//展示
if (!blame_log.isEmpty()){
showPopupBalloon(mEditor, blame_log);
}
}
}
本文代码补全插件的主要代码逻辑是调用上一步后台部署的请求。
// 请求url格式(和flask接口一致)
String baseUrl = "http://ip:8080/plugin?data=";
// 获取当前编辑位置文本
PsiFile str = position.getContainingFile();
// 根据模型上文限制获取代码端
String data = getContentCode();
String url = baseUrl+data;
// 发送请求
String result = HttpUtils.doGet(url);
// 后处理逻辑,在提示框显示预测结果
show()
最终呈现形式:
可以看出模型的预测结果还是不错的~
以上就是代码补全功能的实现和应用,可以看作是AI自动编写代码的一小步。
AI自己写代码有可能在嫌疑人追踪上达到TM的水平吗?我不敢说不可能,但以我目前的认知,是不可能实现的。毕竟,是程序员编写代码并将数据提供给算法。是程序员,算法设计师是程序员,AI连帮助人类解决bug的功能都没有! \
参考资料:
[1] /abs/1706.03762
[2] /abs/1810.04805
[3] /openai/gpt-2
[4] /abs/1904.09751