共计 5289 个字符,预计需要花费 14 分钟才能阅读完成。
作者:熊唯,黄飞,腾讯 PCG/QQ 研发核心 /CV 利用研究组
AI 如果真的能够写代码了,程序员将何去何从?近几年,NLP 畛域的生成式工作有显著的晋升,那通过 AI
咱们能够让代码主动实现后续补全吗?本文次要介绍了如何应用 GPT2 框架实现代码主动补全的性能。
如果 AI 真的能够本人写代码了,程序员将何去何从?
我去年做过一个代码补全的小性能,打包为 androidStudio 插件,应用成果如下:
代码补全模型预测出的后果有时确实会惊吓到我,这也能学到~? 那如果给它见识了全世界的优良代码,再给足够量级参数和优良的模型框架,真的能够实现需求作为输出,间接输入代码吗?
“ 我的需要讲完了,你的代码呢?” 心愿能够看到这一天。
代码补齐性能有其余优良插件也已实现,比方 tabnine,Kite 和国产的 aixcoder。本文次要介绍下代码补全性能须要实现的整套流程。次要包含数据,算法和工程。
数据
家喻户晓,算法工程师大部分工夫都在解决数据。
深度学习是应用大数据训练模型的一个过程,数据是很重要的一个模块。人是会累的,劳动不好还导致记忆不好。AI 是你给多少数据它就能存储接管多少数据,学不到信息那是人的错,给的数据不好或者算法设计不好。所以咱们先尽可能多的筹备好训练数据。
1、数据采集
本文的目标是代码补全,训练数据就是代码段。思考到每种语言格调和语法都不统一,所以单个模型只针对一种代码语言。
我应用的训练数据次要来源于 GitHub,编写了一个简略的爬虫代码,指定语言后依据 stars 的排序下载工程。
Github 的 search API 官网地址:
https://developer.github.com/…
2、数据清理
间接下载的数据必定是不能间接用的,咱们还须要对数据进行清理。
首先,咱们的训练数据只须要工程中的代码文件,以 java 工程为例,咱们只保留.java 结尾的文件,其余文件可剔除。
其次,我的代码补全指标是代码段,不针对正文性能。而且对于代码补全训练时,咱们是会给定肯定范畴的上文,如果存在正文段会占用无效代码信息。另外正文除英文外其余字符不在我的训练 vocab 范畴内,所以须要对代码中正文和日志进行清理。
1. 删除代码行中存在除符号和英文外的字符
2. 删除日志行
3. 删除正文行,次要针对以下格局
/ 正文文本/
/**
正文段落
*/
// 正文文本
code // 正文
通过以上数据清理后,失去纯代码数据。
3、数据编码
失去了训练数据后还须要把代码文本进行编码。本文应用的是 bpe(byte pair encoder)字节对编码,次要为了数据压缩。bpe 简略了解为将一个单词再拆分为多个字母组合,比方 tencent 拆分为 ten-cent,这些组合形式则是依据大量数据,统计频率失去。因为咱们期待的代码补全性能是在行首输出几个字母,依据上文预期出本行内容。
假如 tensorflow 这个 token 被编码对应到一个 id,那我心愿输出 ten 就输入 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,提出 attention is all you need。2018 年 openAI 采纳了 transformers 构造在 18 年公布了 GPT。同年 google AI Language 公布了 bert 论文,提出的 BERT 模型在 11 个 NLP 工作上刷新了记录。2019 年 openAI 又推出了 GPT-2 模型。。
BERT(Bidirectional Encoder Representation from Transformers)是基于 transformers 框架的 encoder 局部,自编码语言模型,适宜 N-1(比方句子分类),N-N(比方词性标注)的工作,然而它并不适宜做生成工作。
GPT(Generative Pre-Training)基于 transformers 的 decoder 局部,自回归语言模型,适宜生成式工作。
代码补全性能就是基于 GPT2 框架,OPenAI 官网提供了多套 GPT2 预训练模型:
作为一个常常要把模型部署到挪动端的 CVer,看到这个参数级别,我抉择最小的模型进行 finetune。
对于 GPT 算法,上面这篇文章讲的很好,感兴趣同学能够看看:
https://zhuanlan.zhihu.com/p/…
本文在训练中应用 512 个上文,预测到回车符为终止。模型网络应用超参:12 个层,768 个暗藏节点,12 个 heads,采纳了 uber 的 Horovod 分布式框架进行训练。
infer 阶段采纳 beam-search 会导致整个预测过程特地耗时,所以参考了 https://arxiv.org/abs/1904.09…,采纳 top-k sampling,每次预测 top3 的后果再通过概率阈值过滤后作为最终候选输入。
最终 infer 成果:
输出一段代码,预测出后续代码,以回车符截止。
工程
家喻户晓,算法工程师大部分工夫都在做工程。
训练出模型后,还要把模型利用起来,所以还须要一些工程工作须要实现。代码补全性能,最合适的利用场景就是上 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 前向计算逻辑,从申请中获取 data 字段作为输出,infer 预测的后果列表作为输入返回给调用方。
通过下面的工作,咱们曾经提供了一个服务接口,返回咱们代码补全的预测后果。
插件编写
最初一步就是如何在 IDE 上应用性能了。咱们要开发 AS 的插件,须要应用 IntelliJ,首先须要在本机装置配置 IntelliJ IDEA
下载地址:
https://www.jetbrains.com/ide…
社区版源码:
https://github.com/JetBrains/…
好用的插件能够节俭程序员很多工夫,在插件实现时,我还增加了一个小的 git-blame 性能,实时查看指定行的 git 提交人,对于手 Q 这种多人单干的工作,比拟实用。大家也能够通过 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] 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