Advertisement

是不是有一天想象着让代码自动补全,今天他来了!!!

阅读量:
在这里插入图片描述

作者:熊唯,黄飞 ,腾讯 PCG/QQ研发中心/CV应用研究组

如果人工智能能够彻底实现编写代码的能力, 程序员将面临怎样的职业转型? 自然语言处理领域的生成式任务取得了显著进展, 这是否意味着我们可以利用AI技术让程序自动补充和完善后续的代码? 本文主要介绍了如何使用 GPT2 框架实现代码自动补全的功能。

如果 AI 真的可以自己写代码了,程序员将何去何从?

我去年开发了一个代码补全的项目。该项目成功地集成到 Android Studio 插件中。测试结果表明该插件具有良好的兼容性和稳定性。

在这里插入图片描述

代码补全模型预测出的结果有时确实令人难以置信(?),这也是一种学习的机会。如果给它展示全世界范围内的优质代码示例,并搭配合适的规模和高质量且灵活的模型架构,则可以直接将需求作为输入并生成相应的代码吗?

"我的需求讲完了,你的代码呢?" 希望可以看到这一天。

其他主流插件均已开发完成

其他主流插件均已开发完成,
例如tabnine、Kite以及国产aixcoder等工具的应用场景广泛。
本文将详细介绍代码补全功能所需实现的关键流程。
其中涉及的数据类型、核心算法以及系统的整体架构。

数据

众所周知,算法工程师大部分时间都在处理数据。

深度学习过程涉及利用大数据构建模型;在其中扮演着关键角色的是数据模块;人类容易感到疲劳,并且长期睡眠不足会影响记忆质量;AI系统能够根据输入的数据进行处理与学习,并且如果提供的数据质量不高或算法设计有问题,则可能导致无法有效学习;因此,在开始训练前应尽可能多地收集高质量的数据

1、数据采集 本文旨在进行代码填充。训练数据即为代码片段本身。鉴于各种编程语言之间的风格差异及语法不一的特点,在这种情况下,单一模型则专注于单一编程语言的学习与应用。

我的训练数据集主要取自 GitHub 平台。我开发了一个基础的爬虫脚本,在选定编程语言后按照项目的星号排序筛选出相关开源项目并导入。

Github 的 search API 官方地址:

https://developer.github.com/v3/search/

2、数据清理 获取的数据肯定无法直接使用, 我们需要对数据进行进一步的预处理.

为了解决这个问题,在实际应用中,我们通常只需要关注工程中的代码部分。例如,在Java项目中,并删除所有非.java文件。

其次,在进行代码补全时,默认关注点限定于纯代码区域,并且无需涉及任何注释相关功能。在特定上下文中(如某些编程环境中),为提高编码效率可能会给定一段上下文信息用于辅助补全操作。然而需要注意的是,在某些情况下(如包含非纯文本内容的区域),这部分上下文可能会占用到有效的编码空间并影响到编码效率。此外,在当前的训练数据集中仅限于英文字符范围内的信息会被考虑进去,并且为了确保数据质量需要对所有与编码无关的信息(包括注释和日志)进行定期清理以避免干扰编码逻辑。

1.删除代码行中存在除符号和英文外的字符

2.删除日志行

3.删除注释行,主要针对以下格式

/* 注释文本*/

/** 注释段落 */

// 注释文本

code //注释 经过以上数据清理后,得到纯代码数据。

3、数据编码 在获得训练数据后还需将代码文本进行编码。本文采用的是 bpe(byte pair encoder)字节对编码方法,其主要目的为实现高效的存储与传输。bpe的核心思想在于将每个词分解成更小的基本单位,例如在' tencent '一词中可分解为'ten'和'cent'两个部分,而这些基本单位的选择则是通过大规模的数据分析统计出最优的分解方式.我们的目标是通过在每一行开头输入几个字母即可推测出完整的代码内容.

假设将 tensorFlow 这个 token 对应到一个 ID,在训练过程中我会随机将 token 分段以便于后续处理,在编码时会将 tensorFlow 分段为 't-en-sor-flow' 等形式,并确保分割时确保被分割的部分都在词汇表中出现过。这样每个 token 在数据集中会被表示成多个 ID 的形式,并且模型在进行预测时可以通过这些 ID 得到对应的 tensorFlow 单词信息,并且换行符被视为预测结束的标志;这样经过上述处理我们就可以准备好用于训练的数据集之后就可以开始模型算法的学习工作了。

模型算法

众所周知,算法工程师大部分时间都在研究算法。

为了解决腾讯文档中的错别字纠正问题,在自然语言处理领域内我们采用的是基于LSTM的seq2seq模型以及Facebook提出的基于CNN的另一种seq2seq模型。经过实验验证该方案能够达到较好的纠正效果值得肯定的是随着NLP技术的发展其代表之一BERT以其卓越的表现赢得了广泛赞誉其精度提升幅度显著达8个百分点左右不愧是人工智能领域的重量级解决方案接下来将简要介绍Bert及其相关的模型GPT-2

BERT 和 GPT2

于 2017 年中期, Google 提出了 Transformer 架构. 在无需 recurrent neural networks (RNN) 或 convolutional neural networks (CNN) 的情况下,提出了基于注意力机制的语言模型. 在这一年的同一时间, OpenAI 发布了基于 transformer 架构的模型. Google's AI Research Lab 则发布了BERT论文,提出的BERT模型在十一项自然语言处理任务上均取得了领先成果. 在同一年, OpenAI 推出了GPT-2模型.

BERT(Bidirectional Encoder Representation from Transformers)是由 transformer 框架中的编码器组件构成的模型。该系统是一个预训练语言模型,在处理 N-1 类型的任务(如句子分类)以及 N-N 类型的任务(如词性标注)方面表现突出。然而该系统不具备生成能力。

该模型由 transformers 的解码器部分构成作为自回归语言模型适用于生成式任务

Transformer框架图
GPT2和BTER框架示意图

代码补全功能就是基于 GPT2 框架,OPenAI 官方提供了多套 GPT2 预训练模型:

官方提供GPT2参数

为了将模型快速部署至移动设备而常担任移动开发任务的CV研究者/工程师,在面对这一较大的参数规模时会选择最小计算资源需求且能够满足性能需求的小型模型进行微调优化。

对于 GPT 算法,下面这篇文章讲的很好,感兴趣同学可以看看:

https://zhuanlan.zhihu.com/p/137350403

GPT2的预测过程

在训练过程中采用512维的上下文窗口,并将换行符作为终止标志进行预测。该模型架构包含12层网络主体、768个隐藏单元以及12个注意力头,并采用horovod分布式技术实现并行训练以提升效率。

在infer阶段使用beam search可能导致整个预测过程耗费大量时间。经过研究发现, 参考文献https://arxiv.org/abs/1904.09751后, 并采用了top-K采样方法来优化预测效率。在具体实现中, 首先计算候选输入的所有可能输出项, 然后仅保留概率值最高的前三个结果; 最后, 根据设定的概率阈值筛选出有效候选, 作为最终输出。

最终 infer 效果:

在这里插入图片描述

输入一段代码,预测出后续代码,以回车符截止。

工程

众所周知,算法工程师大部分时间都在做工程。

建立好模型之后还需将模型运用起来完成相关任务。代码补全功能使用IDE时是最为合适的解决方案。nlp模型不适合本地部署最终决定采用GPU服务器进行部署。终端通过HTTP接口获取预测结果以显示处理后的文本信息。

后台部署

Light and easy-to-use framework for building web applications. This article will briefly explain how to use Flask to start a web service and access/invoke our functional interfaces. The first step is to create a new conda environment with the required packages properly installed.

复制代码
    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 得到的预测结果列表返回给客户端。

通过前期工作, 我们已开发出一个服务接口, 并将该系统的预测输出结果作为返回值。

插件编写

最后一步就是实现AS功能在集成开发环境中的配置。我们需要创建一个插件以实现AS的功能,并且可以通过IntelliJ开发这一插件。第一步是安装并配置IntelliJ IDEA软件以便后续操作。

下载地址:

https://www.jetbrains.com/idea/download/

社区版源码 :

https://github.com/JetBrains/intellij-community

实用的插件能够显著提升程序员的工作效率,在开发插件的过程中, 我还增添了一个简单的git-blame功能模块, 用于快速定位具体提交者的工具, 对于团队协作项目中, 这一功能非常有用. 此外, 大家也可以基于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 系统仍无法实现自动辅助发现与修复程序漏洞的功能!

参考资料:

[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

file

全部评论 (0)

还没有任何评论哟~