GitHub上现在托管有超过300种编程语言。从最广泛使用的语言比如Python,Java、Javascript等,到一些非常非常小众的语言例如Befunge,应有尽有。
但丰富的语种带来的一个挑战就是,如何即时鉴别它们?这影响到如何更好的搜索、发现其中的安全漏洞或者采取什么样的语法高亮。
而且编程语言识别起来,看似简单实则非常困难。文件扩展名是一个非常重要的区分标准,但很多时候非常混乱。比如“.pl”, “.pm”, “.t”, “.pod”,都跟Perl有关系;而“.h”,C、C++、Objective-C也都有在用。
甚至还会出现没有扩展名的情况,例如一些可执行脚本(curl,get,makefile等)。
Linguist已经可以完成84%的语言检测
那么GitHub是怎么解决上述问题呢?GitHub高级数据科学家Kavita Ganesan首先介绍了目前GitHub官方使用的语言鉴别工具:Linguist。
Linguist是一个基于Ruby的应用程序,它使用多种策略进行语言检测。比如利用命名约定和文件扩展名,考虑Vim或Emacs模型,以及文件顶部的内容(shebang)等。
Linguist通过启发式方法,通过一个小样本数据训练的朴素贝叶斯分类器来进行语言消歧义。
虽然Linguist在文件级语言预测方面做得很好(准确率为84%),但是当文件使用非常特殊的命名约定时,准确率就大幅下降了。更重要的是,当遇到没有提供文件扩展名的情况比如Gist、README文件、issue或者拉取请求中的代码片段,Linguist就无能为力了。
人工智能帮助完成剩下的语言检测工作
为了使语言检测能够更加健壮和可维护,GitHub又开发了一款名为OctoLingua的机器学习分类器,它基于人工神经网络(ANN)架构,可以处理棘手场景中的语言预测。
该模型的当前版本能够对GitHub托管的前50种语言进行预测,并在准确性和性能方面超越Linguist。
OctoLingua从头开始使用Python + Keras,以及TensorFlow后端进行构建,非常准确、健壮且易于维护。
数据源
OctoLingua的当前版本使用了从Rosetta Code检索的文件和内部众包的一组质量库的训练。语言集限制为GitHub上托管的Top 50。
Rosetta Code是一个出色的入门数据集,因为它包含用不同编程语言表示的相同任务的源码。例如,生成Fibonacci序列的任务可以用C、C ++、CoffeeScript、D、Java、Julia等表示。
但是,跨语言的覆盖范围并不统一,其中某些语言只有少量文件而某些文件的填充程度过于稀疏。因此,需要增加一些额外来源的训练集,以提高语言覆盖率和性能。
目前添加新语言的流程现已完全自动化,以编程方式从GitHub上的公共仓库收集源码。选择满足最低资格标准的仓库,例如具有最小数量的分支,以及涵盖目标语言和涵盖特定文件扩展名。
对于此阶段的数据收集,使用Linguist的分类确定仓库的主要语言。
特点:利用先验知识
传统上,对于神经网络的文本分类问题,通常采用基于存储器的体系结构,例如递归神经网络(RNN)和长短期记忆网络(LSTM)。
但是,鉴于编程语言在词汇、评论风格、文件扩展名、结构、库导入风格和其他微小差异,GitHub选择了一种更简单的方法:通过以表格形式提取某些相关功能来利用所有这些信息,并投喂给分类器。目前提取的功能如下:
人工神经网络(ANN)模型
上述特征作为使用具有Tensorflow后端的Keras构建的双层人工神经网络的输入。
下图显示特征提取步骤为分类器生成n维表格输入。当信息沿着网络层移动时,它通过dropout正则化并最终产生51维输出,该输出表示给定代码在前50种GitHub语言中每一种写入的预测概率加不写入的概率。
GitHub使用90%的数据集进行大约8个epochs的训练。此外,在训练步骤中从训练数据中删除了一定百分比的文件扩展名,以鼓励模型从文件的词汇表中学习,而不是过度填充文件扩展功能。
基准
下图显示了在同一测试集上计算的OctoLingua和Linguist的F1得分(精确度和召回之间的调和平均值)。
这里展示三个测试。第一个是测试集不受任何干预;第二个测试使用同一组测试文件,删除了文件扩展名信息;第三个测试也使用相同的文件集,但这次文件扩展名被加扰,以便混淆分类器(例如,Java文件可能有“.txt”扩展名、Python文件可能具有“.java”)扩展名。
在测试集中加扰或删除文件扩展名的目的是评估OctoLingua在删除关键功能或误导时对文件进行分类的稳健性。不严重依赖扩展的分类器对要点和片段进行分类非常有用,因为在这些情况下,人们通常不提供准确的扩展信息(例如,许多与代码相关的文件具有.txt扩展名)。
下表显示了OctoLingua如何在各种条件下保持良好的性能,表明该模型主要从代码的词汇表中学习,而不是从元信息(即文件扩展名)中学习。但是没有扩展名的话Linguist完全无法鉴别。
上图是OctoLingua与Linguist在同一测试集上的表现。
在训练期间删除文件扩展名的效果
如前所述,在训练期间,从训练数据中删除了一定百分比的文件扩展名,以鼓励模型从文件的词汇表中学习。下表显示了模型在训练期间删除了不同分数的文件扩展名的性能。
上图在三个测试变体中删除了不同百分比的文件扩展名后,OctoLingua的表现
请注意,在训练期间没有删除文件扩展名的情况下,OctoLingua对没有扩展名和随机扩展名的测试文件的性能与常规测试数据相比差距很大。而一旦在删除某些文件扩展名的数据集上训练模型时,模型性能在修改的测试集上的差距就没有那么大。
这证实了在训练时从一小部分文件中删除文件扩展名,会使分类器从词汇表中学到更多。它还表明,文件扩展功能虽然具有高度预测性,但却倾向于支配并阻止将更多权重分配给内容。
添加新语言支持
在OctoLingua中添加新语言非常简单。它首先获取新语言的大量文件,这些文件分为训练和测试集,然后通过预处理器和特征提取器运行。这个新的训练和测试装置被添加到现有的训练和测试数据库中。新的测试装置允许验证模型的准确性是否仍然可以接受。
上图使用OctoLingua添加新语言、
未来计划
截至目前,OctoLingua正处于“先进的原型设计阶段”。我们的语言分类引擎已经强大且可靠,但还不支持我们平台上的所有编码语言。除了扩大语言支持 - 这将是相当简单的 - 我们的目标是在各种粒度级别启用语言检测。我们当前的实现已经允许我们通过对机器学习引擎的一些小修改来对代码片段进行分类。将模型带到可以可靠地检测和分类嵌入式语言的阶段并不是太遥远。
我们也在考虑开源我们模型的可能性,如果您有兴趣,我们很乐意听取社区的意见。
参考链接:
https://github.blog/2019-07-02-c-or-java-typescript-or-javascript-machine-learning-based-classification-of-programming-languages/?utm_campaign=1562089474&utm_medium=social&utm_source=twitter&utm_content=1562089474