使用 Azure ML 对代码片段进行分类






3.93/5 (8投票s)
使用 Azure ML Studio 中的神经网络检测代码片段的编程语言
目录
引言
在本文中,我将解释如何使用 Azure Machine Learning Studio 训练神经网络分类器,以实现识别代码片段编程语言 90% 的准确率。
本文面向具有 ML Studio 经验的读者。有关 ML Studio 基本知识的更详细教程,请阅读我的另一篇文章。
运行代码
以下是运行示例应用程序需要执行的操作。
- 注册一个免费的 Azure ML Studio 帐户
- 复制我的Azure Web 服务。
- 点击“在 Studio 中打开”
- 点击“运行”
- 点击“设置 Web 服务”
- 复制你的私有 API 密钥
- 点击“请求/响应”
- 复制你的请求 URI(端点)
- 克隆我的GitHub 存储库
- 编译并运行
- 出现提示时输入你的服务端点
- 出现提示时输入你的 API 密钥
- 在文本字段中输入一个代码片段,然后点击“检测”
数据预处理
我们将使用的数据包含来自 25 种不同编程语言的 677 个标记代码片段。每个片段都包含在 <pre>
标签中,并使用 lang
属性进行标记。
<pre lang="ObjectiveC">
var protoModel : ProtoModel?
override func viewDidLoad() {
super.viewDidLoad()
// create and setup the model
self.protoModel = ProtoModel()
self.protoModel?.delegate = self
// setup the view
labelWorkStatus.text = "Ready for Work"
}
</pre>
我做的第一件事是从 <pre>
标签中解析出这些代码片段,并将它们放入一个易于管理的标记片段列表中。使用正则表达式可以轻松完成。
public List<CodeSnippet> ExtractLabeledCodeSnippets(string html)
{
List<CodeSnippet> snippets = new List<CodeSnippet>();
MatchCollection matches =
Regex.Matches(html, "<pre.*?lang\\=\\\"(.*?)\\\".*?>(.*?)<\\/pre>",
RegexOptions.Singleline | RegexOptions.IgnoreCase);
foreach (Match m in matches)
{
snippets.Add(new CodeSnippet(m.Groups[1].Value, HttpUtility.HtmlDecode(m.Groups[2].Value)));
}
return snippets;
}
这些片段不能作为一大块文本输入到分类器中,因此我需要找到一种方法来衡量片段内的独特特征。这是通过一种称为特征提取的方法完成的,其中每个片段被分解成小的块,然后进行哈希和计数。
我的第一个尝试是简单地按空格和换行符分割代码,以便每个代码块成为自己的特征。
class CleanFeatureExtractor : IFeatureExtractor
{
public List<string> ExtractFeatures(string input)
{
return input.Replace("\r", " ").Replace("\n", " ")
.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
}
问题在于,在某些语言中,你可能会很长时间没有空格,因此有极高数量的特征在整个数据集中只出现一次。
我的下一个尝试在前面的基础上进行了扩展,除了空格和换行符外,还按符号进行分割。符号对于语言的标识至关重要,因此我也将它们作为自己的特征保留下来。
class NGramFeatureExtractor : IFeatureExtractor
{
public List<string> ExtractFeatures(string input)
{
List<string> features = new List<string>();
// Clean input
input = input.ToLower().Replace("\r", " ").Replace("\n", " ");
// Extract words
features.AddRange(Regex.Replace(input, "[^a-z]", " ")
.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries));
// Extract symbols
features.AddRange(Regex.Replace(input, "[a-zA-Z0-9]", " ")
.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries));
return features;
}
}
不幸的是,我在这里也无法获得任何像样的结果。
我的最终尝试,也是我最终使用的尝试,是将整个片段分解成连续的字母组。这为每个片段产生了大量特征,但这无关紧要,因为我们将在训练之前删除无用特征。
class LetterGroupFeatureExtractor : IFeatureExtractor
{
public int LetterGroupLength { get; set; }
public LetterGroupFeatureExtractor(int length)
{
LetterGroupLength = length;
}
public List<string> ExtractFeatures(string input)
{
Listt<string> features = new Listt<string>();
// Clean input
input = input.ToLower().Replace("\r", "")
.Replace("\n", "").Replace(" ", "");
for (int i = 0; i < input.Length - LetterGroupLength; i++)
{
features.Add(input.Substring(i, LetterGroupLength));
}
return features;
}
}
这将把像 <b>bold</b>
这样的代码片段转换为许多 N 个字符长的字符串。对于 N=3
,上面的代码将被分解成 9 个特征:“<b>
”、“b>b
”、“>bo
”……你懂的。
我尝试了各种长度的字母组特征,发现 3 是一个关键数字。字母组大小越大,准确率就逐渐下降。
这些提取的特征随后被保存到 CSV 文件中,包含两列——一列是已知的语言,另一列是提取的特征列表(基本上看起来像一个非常长的重复)。这就是我上传到 Azure 进行训练的文件。
在训练之前,我们需要对我们任意长度的特征列表进行哈希处理,以便获得固定数量的列来输入分类器。这就是我们如何将复杂的输入数据简化为可以训练的内容。
我选择使用 15 位哈希(给我 32,768 个特征列),然后抛弃所有不属于最重要的 2,000 列。为此,我选择了“互信息”评分方法,因为它产生了最高的准确率(其次是 Spearman Correlation 和 Chi Squared)。
在训练之前我做的最后一件事是将数据集按 80%-20% 的比例分割为训练集和测试集,以便之后我可以计算在未训练过的数据集上的准确率。
训练模型
在训练方面,我尝试了所有四种可用的多类分类器,发现神经网络效果最好(其次是逻辑回归)。决策树和决策森林分类器的准确率从未超过 78%。在调整隐藏层神经元数量后,我最终确定为 100。
训练模型后,我很高兴地看到在未见过的数据上的总体准确率为 **90.54%**。模型将所有 ObjectiveC 片段都误分类为 C++,并且在 JavaScript 上似乎遇到了困难,但总体而言,它做得很好。
在此之后,我将我的模型作为 Azure 中的 Web 服务公开(阅读此文了解如何操作),将其连接到我的应用程序,然后运行了所有片段。结果相当令人满意。我在训练集和测试集上获得了 **96%** 的总体准确率。
Total: 677
Correct: 653
Incorrect: 24
Accuracy: 96.455%
改进结果
此时我的模型的准确率相当不错,但这仅基于几百个代码样本。为了解决这个问题,我从 GitHub 下载了 24 种不同语言的大量项目,并提取了 46,459 个标记的代码样本。
大部分片段仅来自 3 种语言,所以我决定将每种语言限制在 2,000 个样本。我还排除了重复和非常小的片段,并尝试尽可能多地删除注释。这使我总共有 17,359 个片段。
然后将这些片段进行划分,其中 90% 用于训练,10% 用于测试。
训练花费了更长的时间(因为它需要处理 160MB 的数据),但最终在 1,735 个未见过(测试)片段上获得了 99% 的总体准确率。
最后,我将原始的 677 个片段(未用于训练)通过新的、更大的模型进行处理。不幸的是,正如你在下面的混淆矩阵中所见,它只能正确分类 53% 的片段。
我认为结果差异很大,原因在于我的片段的格式与提供的片段差异太大。如果给我几百个小时,我可能会手动清理数据并获得更好的结果,但那就算了。我还考虑编写一个抓取器从 Code Project 文章中下载一百万个标记的片段,但我想我还是不想被封禁。
我尝试寻找一种方法来删除注释和字符串字面量,并为已知的关键字赋予额外的权重,但这只有在你首先知道你正在处理什么语言时才有效。预处理步骤必须对训练集和实际数据集完全相同,因此标签是禁止使用的(因为实际数据没有标签)。
总结
我成功训练了一个模型,能够对 67 个未见过(测试)的片段进行 90% 的分类,对所有 677 个片段进行 96% 的分类。
初始数据集很小,所以我创建了自己的训练集,在 1,735 个未见过(测试)的片段上获得了 99% 的准确率,但在原始的 677 个片段上准确率只有 53%(由于格式差异)。
这是一个非常有益的项目,我在其中学到了很多东西。
谢谢!
历史
- 2018 年 2 月 16 日 - 初始发布