65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Azure Machine Learning 检测垃圾邮件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.47/5 (12投票s)

2018 年 2 月 11 日

CPOL

14分钟阅读

viewsIcon

30038

downloadIcon

355

在 Azure 中训练二元分类器,然后在 C# 应用程序中使用它。

目录

引言

在本文中,我们将通过 Microsoft 的 Azure 机器学习工作室创建一个垃圾邮件分类器。然后,我们将训练好的分类器公开为 Web 服务,并从 C# 应用程序中调用它。

在开始之前,您需要注册一个免费的 Azure ML Studio 帐户,复制我的 Azure训练实验Web 服务实验,并克隆我的GitHub 仓库

数据预处理

格式化文件

我们将要使用的数据包含 2,000 条标记用于训练的消息和 100 条标记用于测试的消息。每条消息都标记为垃圾邮件或正常邮件(非垃圾邮件)。

# Spam training data
Spam,<p>But could then once pomp to nor that glee glorious of deigned. The vexed...
Spam,<p>His honeyed and land vile are so and native from ah to ah it like flash...
Spam,<p>Tear womans his was by had tis her eremites the present from of his dear...
...
# Ham training data
Ham,<p>Nights chamber with off it nearly i and thing entrance name. Into no sorrow...
Ham,<p>Chamber bust me. Above the lenore and stern by on. Have shall ah tempest...
Ham,<p>I usby chamber not soul horror that spoken for the of. I yore smiling chamber...
...
# Test data
Ham,<p>Bust by this expressing at stepped and. My my dreary a and. Shaven we spoken...
Ham,<p>Again on quaff nothing. It explore stood usby raven ancient sat melancholy...
Ham,<p>Tell floor perched. Doubting curious of only blessed ominous he implore...
...

为了更方便地处理这些数据,我们将它们分成两个 CSV 文件并添加标题。生成的train.csvtest.csv文件将如下所示:

classification,message
Spam,<p>But could then once pomp to nor...
Spam,<p>His honeyed and land vile are...
Spam,<p>Tear womans his was by had tis...
...

上传数据

现在我们需要将这些数据导入 Azure,以便我们可以进行操作。登录 ML Studio,然后点击页面左下角的+New。这将弹出一个菜单,您可以从中添加新的数据文件。

从这里,我们可以按照向导将train.csvtest.csv文件作为数据集上传到 ML Studio。

现在是激动人心的部分!再次点击+New,但这次转到Experiment菜单并选择Blank Experiment。在这里,我们将通过可视化方式设计一个用于清理数据和训练模型的算法。

从左侧面板,打开Saved Datasets > My Datasets,然后将train.csv数据集拖到工作区。

您可以通过右键单击项目并转到Dataset > Visualize来查看数据。

数据归一化

请注意,消息数据包含一些 HTML 标签、混合的大小写和标点符号。我们需要通过去除特殊字符并将所有内容转换为小写来稍微清理一下。要做到这一点,请在侧边栏中搜索Preprocess Text步骤,并将其拖到工作区。通过鼠标拖动一条线,将训练数据集的输出连接到Preprocess Text步骤的输入。

我们可以通过单击Preprocess Text步骤并调整屏幕右侧出现的设置来配置它。单击Launch column selector并选择message列,因为这是我们将要执行清理任务的列。除删除数字和特殊字符以及将所有内容转换为小写的选项外,取消选中所有其他选项。

现在,您可以使用屏幕底部的Run按钮来运行您的项目。完成后,您可以右键单击Preprocess Text步骤,像处理输入数据集一样可视化清理步骤后的数据。请注意,此步骤添加了一个新的Preprocessed message列,其中包含更干净的单词列表以供使用。

此时,我们的数据集有三列。由于我们只关心Preprocessed message列,因此我们将删除message列。从左侧列表拖动一个Select Columns in Dataset步骤,并将其连接到Preprocess Text步骤的输出。

使用右侧属性面板中的Launch column selector选择classificationPreprocessed message列。如果您运行项目并可视化此步骤后的输出,您将看到message列不再存在。

虽然这不是严格必需的,但我选择将Preprocessed message列重命名回message。这可以通过添加一个Edit Metadata步骤来完成,选择您想要重命名的列,并指定一个新名称。

运行项目并可视化结果表明我们确实重命名了该列。

提取特征

现在,我们有了干净的输入数据,但是我们如何将其转换为可以实际用于训练分类器的东西呢?当然是使用 Vowpal Wabbit 算法!将一个Feature Hashing步骤添加到工作区,选择message列,并将Hashing bitsize设置为8

此步骤将执行三个重要操作:

  • 首先,它将从我们的message列中提取所有连续的单词对(称为二元组)。例如,句子"hello, I am Scott"将包含三个二元组:"hello i""i am""am scott"
  • 接下来,它将为每个二元组计算一个 8 位哈希。请注意,多个二元组可能会产生相同的哈希(哈希冲突),因此我们需要选择足够大的哈希大小以防止冲突,但也要选择足够小的哈希大小以避免耗尽系统资源。此步骤允许我们将句子这样复杂的输入空间减小为更易于管理的数字列表。
  • 最后,它将为每个哈希在输出数据集中创建一个新列。使用 8 位哈希,我们将得到正好 256(28)列。每个特征列将包含该消息中具有相同哈希的二元组数量的计数。

这就是我们将单个、海量的单词字符串转换为可供模型使用的数字表的过程。通过可视化此步骤的输出数据集,我们可以看到确实有很多特征列。

当然,并非所有 256 个特征列都有用。虽然其中一些在预测消息分类方面可能非常有用,但另一些可能对几乎所有消息都为零,因此没有预测能力。我们可以使用Filter Based Feature Selection步骤来计算最有价值的特征,并指定我们只想保留最重要的 100 个特征。

可视化此步骤的输出会发现我们的数据集中的特征列数量已大大减少。在可视化窗口中,您可以选择单个列以查看表示该列值分布的图表。

模型训练

选择分类器

现在我们的数据格式非常适合训练模型。对于此类数据,我们将使用双类逻辑回归分类器,因为我们的数据只有两种可能的分类:spamham。此分类器使用监督学习,这意味着我们在训练过程中向算法提供正确标记的样本数据,以便它以后能够学习对未标记数据进行分类。

要训练我们的分类器,我们需要引入一个Two-Class Logistic Regression步骤、一个Split Data步骤和一个Tune Model Hyperparameters步骤,并将它们连接起来,如下所示:

Two-Class Logistic Regression classifier实际上已经是一个可行的模型;它只是尚未经过训练。Tune Model Hyperparameters步骤将采用现有模型并调整参数,直到它能够正确分类一组输入数据。

您可能已经注意到其中有一个Split Data步骤。这将把数据分成两组,其中 90% 的数据进入Tune Model Hyperparameters步骤的训练数据输入,10% 的数据进入验证输入。在训练过程中,验证数据将用于测试模型的准确性。

评估分类器

现在我们已经训练好了一个模型,我们需要评估它的有效性。为此,我们将使用test.csv数据集,并将其通过与我们处理train.csv时完全相同的预处理步骤。只需复制并粘贴我们已经为训练集设置好的步骤即可。

请注意,我们没有将Filter Based Feature Selection步骤复制到测试集步骤中。尽管Feature Hashing步骤保证始终输出相同的列,但Filter Based Feature Selection步骤则不然。每次在新的dataset上运行时,它都会将一组不同的有用列传递到下一步。如果我们最终选择了错误的列,将在Score Model步骤中出现错误,该步骤期望输入数据集具有模型训练所用的所有相同列。

通过保留所有 256 列,我们保证所有特征都会存在,以便我们的训练模型可以选择它被训练用于使用的内容。拥有额外的列并无坏处,但缺少列则会造成损害。

另外需要注意的是,我们需要使用未用于训练的数据来评估模型的准确性,因为预测模型真正的考验是它在预测我们未训练过的数据方面的表现。模型也可能被过度训练(或过拟合),导致它记住数据而不是开发出基于数据的通用预测算法。

我们需要添加的最后两个步骤是Score Model步骤和Evaluate Model步骤(它们的作用正如其名称所示)。这是最终连接好的项目。Score Model将采用我们训练好的分类器,针对测试数据集进行运行,并附加一列,表示其对消息分类的最佳猜测。Evaluate Model将根据已知和预测的分类计算预测准确性。

运行项目!在所有内容运行完毕后,右键单击Evaluate Model步骤并可视化验证结果。您应该会看到模型具有很高的准确性(越接近 1.0 越好)。由于我们的数据非常简单,我们获得了完美的 100% 准确率!

这个分类器效果非常好,所以我们将保存它。右键单击Tune Model Hyperparameters步骤并将训练好的最佳模型保存。

创建 Web 服务

在 Azure 中设置

保存了可行的模型后,我们现在可以设置一个 Web 服务,以便从任何地方使用该模型。首先,通过单击Save As并为其命名一个听起来像 Web 服务的名称来复制 Azure 中的实验。

训练数据和实际数据之间的主要区别在于,训练数据包含一个分类列,而实际数据不包含。显然,通过 Web 服务发送给我们的数据不会已经分类,因此我们需要进行一些更改来处理这种情况。

首先,在现有的train.csvPreprocess Text步骤之间添加一个Select Columns in Dataset步骤,并将其设置为仅选择message列。这将删除classification列,以便我们可以像数据来自 Web 服务请求一样测试我们的项目。

Preprocess Text步骤之后的现有 Select Columns in Dataset步骤需要修改为仅选择Preprocessed message列。以前,它也选择了classification列,但在这里我们不需要它。

接下来,您可以移除Feature HashingScore Model之间的所有步骤,然后将Feature Hashing直接连接到Score Model的数据集输入。从左侧菜单的 Trained Models 中拖动我们保存的模型,并将其连接到Score Model的模型输入(原本连接未训练模型的地方)。

Score Model的输出数据集包含整个输入数据集(所有 256 列),并添加了一个模型分数列,因此如果我们打算从 Web 服务调用中返回它,我们需要将其缩小。在Score Model之后添加另一个Select Columns in Dataset,并移除之前存在的Evaluate Model。将其配置为仅选择Scored Labels列(由评分步骤添加的列)。

最后,我们可以拖入一个Web service input步骤并将其连接到Preprocess Text步骤,再拖入一个Web service output步骤并将其连接到最终的Select Columns in Dataset步骤。在设置中,将 Web 输入参数名称设置为message,输出参数设置为classification

现在一切都已准备就绪,可以创建 Web 服务了!运行项目以确保其正常工作,然后单击页面底部的Deploy Web Service按钮。在随后的屏幕上,记下您的秘密 API 密钥,因为稍后访问 Web 服务时需要它。另外,单击Request/Response链接以获取您的服务终结点和一些有用的 API 文档。

从应用程序调用

如果您还没有这样做,请克隆或下载与此项目相关的代码并运行它。第一次运行程序时,会弹出一个要求输入 API 密钥的窗口。将您的 Azure API 密钥粘贴到此框中,然后单击OK。另外,请确保在App.config中更新您的 Web 服务终结点的AzureEndpoint设置。

当应用程序启动并运行时,将任何消息粘贴到表单中,然后单击Classify来运行您的模型。如果一切按预期工作,您将从 Web 服务中获得分类结果。

第二个选项卡允许您将整个标记数据文件发送到 Web 服务进行分类,然后验证响应。在对整个数据集(训练和测试合并)运行此模型后,我们获得了 100% 的准确率!

备选方案

我很好奇 Azure 模型比我自己进行简单的词频分析准确多少,因此我创建了WordSearchClassifier,它正是这样做的。它通过将每条消息划分为二元组(两个词的顺序分组)并在整个训练集中计数它们的频率来工作。

public void Train(string trainingFile)
{
    bigrams = new Dictionary<string, int>();
    hamCutover = 0;
    string[] trainingData = File.ReadAllLines(trainingFile);
    List<LabeledMessage> messages = new List<LabeledMessage>();

    // Read all the messages
    foreach (string line in trainingData)
    {
        if (line.StartsWith("Ham") || line.StartsWith("Spam"))
        {
            string[] data = line.Split(new char[] { ',' }, 2);
            messages.Add(new LabeledMessage(data[0], data[1]));
        }
    }

    // For each distinct bigram across the training set, keep a running total of the 
	// number of times it appears in Ham messages as opposed to Spam
    foreach (LabeledMessage message in messages)
    {
        string[] words = ParseBigrams(CleanMessage(message.Message));

        foreach (string word in words)
        {
            if (bigrams.ContainsKey(word))
            {
                bigrams[word] += message.RealClassification == "Ham" ? 1 : -1;
            }
            else
            {
                bigrams.Add(word, message.RealClassification == "Ham" ? 1 : -1);
            }
        }
    }

    // Calculate the average cutover point where the summation of bigram occurrences 
	// switches from a Ham prediction to Spam
    hamCutover = messages.Select(x => GetScore(x.Message)).Average();
}

一旦我们的模型训练完成,我们将拥有一个二元组字典及其与正常邮件或垃圾邮件的关联倾向。然后,我们将对训练集中的每条消息进行评分,并找到通常将正常邮件与垃圾邮件分开的值。对于这些训练数据,根据我们的评分方法,该临界点大约为 10.88。

是的,我知道平均值在技术上不是分割分数的正确方法。这似乎之所以有效,是因为我们有相同数量的正常邮件和垃圾邮件,并且所有消息的长度都大致相同。这里有一个很酷的图表来弥补我的懒惰。

一旦我们完成训练,要对消息进行分类,只需计算分数,然后查看它是否高于或低于此阈值。

private string RunModel(string message)
{
    return GetScore(message) > hamCutover ? "Ham" : "Spam";
}

private double GetScore(string message)
{
    double score = 0;
    string[] messageBigrams = ParseBigrams(CleanMessage(message));

    foreach (string word in messageBigrams)
    {
        if (bigrams.ContainsKey(word))
        {
            score += bigrams[word];
        }
    }

    return score / messageBigrams.Length;
}

该方法实际上能够分类 2,000 个训练模式中的 1,986 个,以及 100 个未见过测试模式中的 100 个,总体准确率为 99.33%。这实际上相当不错,并且它在 420 毫秒内处理了所有 2,100 条消息。

总结

我们成功地在 Azure 中训练了一个分类器,该分类器在检测消息是否为垃圾邮件方面获得了100% 的准确率。我们还探索了一种更直接的词频方法,该方法获得了令人惊讶的 99.33% 的准确率。

有趣的事实!我的第一个模型使用了 16 位哈希(用于 65,536 个特征)并选择了最重要的 1,000 个特征。在写完整篇文章以引用这些数字之后,我又对设置进行了一些调整,并发现我直到将哈希位数降低到 6 位(64 个特征)时,准确率才低于 100%。

这是我第一次使用 Azure ML,在撰写本文的过程中我学到了很多东西。我的数据科学背景相对较短,但确实包括几门数据挖掘(我们当时使用 Weka)的研究生课程,以及对解决 CAPTCHA 的持续着迷。

谢谢!

历史

  • 2018 年 2 月 11 日:首次发布
  • 2018 年 2 月 12 日:添加了 Azure 实验链接
© . All rights reserved.