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

使用 ML.Net 和 C#/VB.Net 进行机器学习

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (37投票s)

2018 年 6 月 22 日

CPOL

18分钟阅读

viewsIcon

99519

downloadIcon

8843

使用 ML.Net 0.2 版本解决分类问题。

目录

引言

本文在不涉及数学原理的情况下,介绍了 .Net 中的机器学习。重点关注 .Net 中处理数据的基本工作流程及其结构,以便于对开源项目 ML.Net 0.2 版本 中的可用功能进行实验。

ML.Net 项目 0.2 版本支持 .Net Core 2.0.Net Standard 2.0,并且仅支持 x64 架构(目前 Any CPU 无法编译)。因此,它应该适用于支持 .Net Standard 2.0(例如:.Net Framework 4.6.1)的任何框架。该项目目前正在审核中。API 在未来可能会发生变化

背景

学习机器学习的基础知识并不容易,如果你想使用像 C# 或 VB.Net 这样的面向对象语言。因为大多数时候你必须先学习 Python,然后才能做其他事情,之后还需要找到带有示例数据的教程来教你更多。即使是像 [1] Accord.Net、Tensor.Flow 或 CNTK 这样的面向对象项目也很难理解,因为它们都有自己的 API,实现相同功能的方式也不同等等。我在 Build 2018 [2] 上的演示令我兴奋,因为它们表明我们可以使用通用的工作流程方法,该方法允许我们使用本地数据、本地 .Net 程序、本地模型和结果来评估该主题,而无需使用服务或其他编程语言,如 Python。

概述

机器学习是人工智能 (AI) 的一个子集,它可以回答 5 种类型的问题 [3]

监督式

  1. 分类(二元和多类别)
    问题:它属于哪个类别?
     
  2. 回归
    问题:是多少或有多少?
     

无监督式

  1. 排序
    问题:接下来我应该做什么?
     
  2. 聚类
    问题:这是如何组织的?
     
  3. 异常检测
    问题:这有什么奇怪的吗?
     

每种类型的问题都有许多应用,为了使用正确的机器学习方法,我们必须首先确定我们是否想要回答其中任何一个问题,如果回答,我们是否有数据支持。

监督机器学习

本文讨论了可用的 .Net 示例(包括源代码和示例数据),用于二元和多类别分类。这种类型的机器学习算法假设我们可以标记一个项目以确定它是否属于

  1. 两个组中的一个(二元分类)或
  2. 多个组中的一个(多类别分类)

当你想用是或否的答案来回答一个问题时,可以应用二元分类。你通常会发现自己将一个项目(图像或文本)分类到两个类别中的一个。例如,考虑一下客户对你最近的调查反馈是心情好(正面)还是不好(负面)的问题。

用机器学习来回答这个问题需要我们标记示例项目(例如:图像或文本)以属于其中一个组。正常的工作流程需要两组独立的标记数据

  1. 训练数据集(用于训练机器学习算法)和
  2. 评估数据集(用于衡量 ML 算法的效率)。

一个带标签的文本行可能看起来像这样

  • 1 你这有偏见的蠢孩子,快长大。
  • 0 希望这有帮助。

其中第一列的“1”表示负面情绪,第一列的“0”表示正面情绪。经验法则是,如果我们有更多的训练数据,ML 算法的效果会更好。并且还应确保训练数据和以后使用的数据是干净且高质量的,以支持有效的算法。

确定有效算法的整体工作流程,使用 KPI,由左侧下方的图表表示,其中我们(理想情况下)找到一个最能反映我们分类问题的模型。模型在此不作详细解释。在 ML.Net 的情况下,它是一个 zip 文件,包含从标记的训练数据中学习到的持久化事实。

第二个独立的评估数据集用于确定效率 KPI。此步骤通过比较机器学习算法的结果和可用的标签(不将标签用于算法)来估算我们的算法将来对项目的分类效果。衡量效率的 KPI 是,例如,正确分类的项目数与错误分类的项目数之比。如果我们发现我们的 KPI 不符合我们的期望,并且我们需要优化模型的方法,我们可以随时返回训练步骤,调整参数,或用另一种算法替换一种算法。

训练阶段希望能生成一个有效的模型,该模型可以在第二个预测阶段应用于对未来遇到的每个项目进行分类。此阶段需要前一个阶段的模型和要分类的项目,用于输出分类的预测(例如:正面或负面情绪)。

这是有监督机器学习工作流程的简要概述。我们需要理解这一点才能进一步处理本文讨论的代码示例。那么,让我们逐个看示例。

二元分类

维基百科情感分析

本节讨论的示例基于 ML.Net 教程中的 情感分析二元分类场景

训练阶段

上一节讨论的工作流程在本文附带的演示项目中得到了一定程度的实现。演示项目包含两个可执行项目

  • 训练和
  • 预测

如果我们编译并启动训练项目,我们会得到以下输出

Training Data Set
-----------------
Not adding a normalizer.
Making per-feature arrays
Changing data from row-wise to column-wise
Processed 250 instances
Binning and forming Feature objects
Reserved memory for tree learner: 1943796 bytes
Starting to train ...
Not training a calibrator because it is not needed.

Evaluating Training Results
---------------------------

PredictionModel quality metrics evaluation
------------------------------------------
Accuracy: 61,11%
     Auc: 96,30%
 F1Score: 72,00%

我们在这里看到程序如何首先训练模型,然后在第二步评估结果。

训练预测模块共享对前面提到的 Model.zip 文件的引用(必须手动复制 - 见下文),对 ML.Net 库的引用,以及在 Models 项目中定义的输入数据和分类输出的通用模型

public class ClassificationData
{
    [Column(ordinal: "0", name: "Label")]
    public float Sentiment;

    [Column(ordinal: "1")]
    public string Text;
}

public class ClassPrediction
{
    [ColumnName("PredictedLabel")]
    public bool Class;
}
Public Class ClassificationData
    <Column("0", "Label")>
    Public Sentiment As Single

    <Column("1")>
    Public Text As String
End Class

Public Class ClassPrediction
    <ColumnName("PredictedLabel")>
    Public [Class] As Boolean
End Class

ClassificationData 中定义的属性将每一列映射到文本输入文件中存在的输入。对于文本文件中的每一行,Label 列定义了包含我们要从中训练的类定义的项。Text 属性本身不能被标记为“特征”,因为它包含多个“列”(文本文件中的情况)。这就是为什么我们需要在下面的管道中添加 new TextFeaturizer("Features", "Text") 行来将文本读入输入数据结构。

ClassificationData 是我们输入及其应如何映射到LabelFeature 的粗略描述。尝试删除 Label 列定义,编译并执行,以验证如果找不到名为 Label 的列,系统将抛出异常。

ClassPrediction 只声明一个二元输出结果,该结果预期是一个 Boolean 值,将输入映射到两个二元类别之一。这部分对于

  1. 验证学习是否成功(在测试阶段有已知输入)和
  2. 确定机器学习算法在使用其模型进行生产时实际的分类。

总结:ClassificationData 用于描述我们希望如何处理输入(始终包含LabelFeatures),而 ClassPrediction 将此输入映射到学习到的结果。

使用 ClassificationData 定义消耗文本输入的训练管道如下所示

internal static async Task<PredictionModel<ClassificationData, ClassPrediction>>
      TrainAsync(string trainingDataFile, string modelPath)
  {
      var pipeline = new LearningPipeline();

      pipeline.Add(new TextLoader(trainingDataFile).CreateFrom<ClassificationData>());

      pipeline.Add(new TextFeaturizer("Features", "Text"));

      pipeline.Add(new FastTreeBinaryClassifier() { NumLeaves = 5, NumTrees = 5, MinDocumentsInLeafs = 2 });

      PredictionModel<ClassificationData, ClassPrediction> model =
                          pipeline.Train<ClassificationData, ClassPrediction>();

      // Saves the model we trained to a zip file.
      await model.WriteAsync(modelPath);

      // Returns the model we trained to use for evaluation.
      return model;
  }
Async Function TrainAsync(
                      ByVal trainingDataFile As String,
                      ByVal modelPath As String)
                      As Task(Of PredictionModel(Of ClassificationData, ClassPrediction))

    Dim pipeline = New LearningPipeline()

    pipeline.Add(New TextLoader(trainingDataFile).CreateFrom(Of ClassificationData)())

    pipeline.Add(New TextFeaturizer("Features", "Text"))

    pipeline.Add(New StochasticDualCoordinateAscentBinaryClassifier())

    Dim model As PredictionModel(Of ClassificationData, ClassPrediction) =
                                 pipeline.Train(Of ClassificationData, ClassPrediction)()

    ' Saves the model we trained to a zip file.
    Await model.WriteAsync(modelPath)

    ' Returns the model we trained to use for evaluation.
    Return model
End Function

ML.Net 框架附带了一个可扩展的管道概念,其中不同的处理步骤可以像上面所示一样插入。TextLoader 步骤从文本文件加载数据,而 TextFeaturizer 步骤将给定的输入文本转换为特征向量,这是一个给定文本的数值表示。然后将此数值表示馈送到 ML 社区称为“学习器”的东西中。在这种情况下,学习器是 FastTreeBinaryClassifier

学习器或训练器是这样的组件,它将数值特征向量转换为模型,该模型以后可用于对未来的输入进行分类。这些学习器的文档目前正在构建中,并非所有学习器都已完全实现和测试。对于二元分类,可以使用一些替代学习器(只需编辑构造函数,如下所示)

分类方法 准确性 AUC F1 分数
new AveragedPerceptronBinaryClassifier() 61.11% 81.48% 72.00%
new FastForestBinaryClassifier() { NumThreads=2, NumLeaves = 25, NumTrees = 25, MinDocumentsInLeafs = 2 } 72.22% 97.53% 78.26%
new FastTreeBinaryClassifier() { NumLeaves = 5, NumTrees = 5, MinDocumentsInLeafs = 2 } 61,11% 96,30% 72,00%
new GeneralizedAdditiveModelBinaryClassifier() 50.00% 83.95% 66.67%
new LinearSvmBinaryClassifier() 72.22% 90.12% 76.19%
new LogisticRegressionBinaryClassifier() 50.00% 86.42% 66.67%
new StochasticDualCoordinateAscentBinaryClassifier 83.33% 98.77% 85.71%
new StochasticGradientDescentBinaryClassifier() 55.56% 90.12% 69.23%

测试以上所有学习器并表明 StochasticDualCoordinateAscentBinaryClassifier 根据测量的 KPI 工作效果最佳。这些 KPI 由 BinaryClassificationMetrics 实例衡量,该实例还提供其他 KPI,例如精确度和召回率。请注意,您仍然可以分析更多 KPI,例如内存消耗和处理时间,这些也未在此处测量。提出的测试相对较小且简短。我们还可以使用不同设置的单个学习器,这仍可能显示出显着改进。当我们面临对大量项目(文本或图像等)进行自动分类的问题时,能够玩转这些不同的场景似乎是一个有趣的练习。

所以,这就是机器学习工作原理的概览。机器消耗数据(文本),将其转换为数值向量,并将矢量化数据集成到模型中。模型是第一阶段的主要输出。让我们看看分类阶段以了解完整的​​工作流程。

预测阶段

预测阶段是生产环境中运行的代码,用于在新项目到达系统时对其进行分类的模块。此部分在 Wikipedia_SentimentAnalysis 解决方案的 Prediction 项目的 PredictAsync 方法中实现。此方法代码如下所示

async Task<PredictionModel<ClassificationData, ClassPrediction>> PredictAsync(
    string modelPath,
    string[] classNames,
    IEnumerable<ClassificationData> predicts = null,
    PredictionModel<ClassificationData, ClassPrediction> model = null)
{
    if (model == null)
      model = await PredictionModel.ReadAsync<ClassificationData, ClassPrediction>(modelPath);

    if (predicts == null) // do we have input to predict a result?
        return model;

    IEnumerable<ClassPrediction> predictions = model.Predict(predicts);

    Console.WriteLine("Classification Predictions");

    IEnumerable<(ClassificationData sentiment, ClassPrediction prediction)> sentimentsAndPredictions =
        predicts.Zip(predictions, (sentiment, prediction) => (sentiment, prediction));

    foreach (var item in sentimentsAndPredictions)
    {
        string textDisplay = item.sentiment.Text;

        if (textDisplay.Length > 80)
            textDisplay = textDisplay.Substring(0, 75) + "...";

        Console.WriteLine("Prediction: {0} | Text: '{1}'",
                          (item.prediction.Class ? classNames[0] : classNames[1]), textDisplay);
    }
    Console.WriteLine();

    return model;
}
Async Function PredictAsync(ByVal modelPath As String,
                            ByVal Optional classNames As String() = Nothing,
                            ByVal Optional predicts As IEnumerable(Of ClassificationData) = Nothing,
                            ByVal Optional model As PredictionModel(Of ClassificationData, ClassPrediction) = Nothing)
                            As Task(Of PredictionModel(Of ClassificationData, ClassPrediction))
    If model Is Nothing Then
        model = Await PredictionModel.ReadAsync(Of ClassificationData, ClassPrediction)(modelPath)
    End If

    If predicts Is Nothing Then Return model

    Console.WriteLine()
    Console.WriteLine("Classification Predictions")
    Console.WriteLine("--------------------------")

    For Each Item In predicts
        Dim predictedResult = model.Predict(Item)

        Dim textDisplay As String = Item.Text

        If textDisplay.Length > 80 Then
            textDisplay = textDisplay.Substring(0, 75) & "..."
        End If

        Dim resultClass = classNames(0)
        If predictedResult.[Class] = False Then resultClass = classNames(1)

        Console.WriteLine("Prediction: {0} | Text: '{1}'", resultClass, textDisplay)
    Next
    Console.WriteLine()

    Return model
End Function

方法中的 PredictionModel.ReadAsync 行将模型从文件系统加载到内存中的 PredictionModel

PredictionModel<ClassificationData, ClassPrediction> model = await
                           PredictionModel.ReadAsync<ClassificationData, ClassPrediction>(modelPath);
model = Await PredictionModel.ReadAsync(Of ClassificationData, ClassPrediction)(modelPath)

加载的模型存储在项目Learned 文件夹中。此 Model.zip 文件必须从训练模块的输出中复制,每当我们发现显着改进并希望在预测模块中利用它时。

模型加载代码行以下的任何内容都会根据加载的模型评估输入,并在方法最后部分输出预测的分类。您可以使用交互式输入提示来测试您自己的样本文本,并小规模测试学习到的内容和未学习到的内容。请记住,学习到的数据通常是经过清理的(与原始输入不同),您只能进行这样的小规模测试。一个更好、更合理的测试可能是输入真实数据源的最后 n 行文本,获取它们的分类,然后查看独立审阅者是否具有非常接近的结果。

在本节中,我们看到了在非常“简单”的场景下,二元分类如何用于情感分析。但 ML 的真正优势在于,每种类型的问题(此处:这是 A 还是 B?)都可以应用于各种应用程序。让我们在下一节中回顾另一个示例,以审查另一个二元分类用例。

你收到了垃圾邮件

本节讨论的示例数据基于 codeproject 文章 You've Got Spam。这个二元分类项目的目标是确定给定文本是否应被分类为垃圾邮件。

本文附带的 YouGotSpam_Analysis 解决方案的源代码与上一节解释的代码几乎相同。可执行项目实际上也非常相似。唯一的区别是用于训练和测试评估的数据源,在本例中是来自 上面 codeproject 文章的测试数据(请参阅训练项目中的Data 文件夹)。训练项目生成此输出

Training Data Set
-----------------
Not adding a normalizer.
Making per-feature arrays
Changing data from row-wise to column-wise
Processed 2000 instances
Binning and forming Feature objects
Reserved memory for tree learner: 24082752 bytes
Starting to train ...
Not training a calibrator because it is not needed.

Evaluating Training Results
---------------------------

PredictionModel quality metrics evaluation
------------------------------------------
Accuracy: 100,00%
     Auc: 100,00%
 F1Score: 100,00%

...这表明我们可以达到与原始 基于 Python 的文章 中所示的相同 KPI。

您可以再次使用预测项目从文件系统中加载模型,并使用进一步的输入对其进行测试。

到目前为止讨论的项目表明,ML.Net 在将机器学习集成到 .Net 框架方面可能很有用。但如果我想对超过 2 个类别进行分类(例如:负面、中性、正面情绪)怎么办?下一节将探讨此类用例的数据分类。

多类别分类

语言检测

本节讨论的示例数据是从 http://wortschatz.uni-leipzig.de 下载并预处理的(删除了引号字符“"”)以改进解析体验。

此处讨论的多类别分类用例是根据给定文本检测语言。试想一下,您有社交媒体代理团队,并且您正试图将不同语言的在线客户反馈(例如聊天)转达给说该语言的正确团队。

本节附带的 LanguageDetection 解决方案遵循前面讨论的二元分类示例的结构。我们有一个训练项目,一个预测项目,以及一个在可执行文件之间共享的Models 类库。训练项目可用于使用特定学习器创建模型。然后可以将成功模型从训练项目复制到预测项目,以供使用和进行未来的多类别分类。

LanguageDetection 解决方案的预测项目在定义 ClassificationData 类中的 LanguageClass 属性和 ClassPrediction 类中的 Class 属性的方式上有所不同。这两个属性都必须是 float 数据类型才能支持多类别分类。

public class ClassificationData
{
    [Column(ordinal: "0", name: "Label")]
    public float LanguageClass;

    [Column(ordinal: "1")]
    public string Text;
}

public class ClassPrediction
{
    [ColumnName("PredictedLabel")]
    public float Class;
}
Public Class ClassificationData
    <Column("0", "Label")>
    Public LanguageClass As Single

    <Column("1")>
    Public Text As String
End Class

Public Class ClassPrediction
    <ColumnName("PredictedLabel")>
    Public [Class] As Single
End Class

ClassificationData 中的输入映射与二元分类问题中的相同。唯一的区别是我们输入的文本文件中的Label 列有多个值。

ClassPrediction 中的输出映射不同,因为现在我们必须映射到 float 值才能进行多于一个类别的分类。

所需的训练管道如下所示

async Task<PredictionModel<ClassificationData, ClassPrediction>>
TrainAsync(string trainingDataFile, string modelPath)
{
    var pipeline = new LearningPipeline();

    pipeline.Add(new TextLoader(trainingDataFile).CreateFrom<ClassificationData>());

    pipeline.Add(new Dictionarizer("Label"));
    pipeline.Add(new TextFeaturizer("Features", "Text"));

    pipeline.Add(new StochasticDualCoordinateAscentClassifier());
    //pipeline.Add(new LogisticRegressionClassifier());
    //pipeline.Add(new NaiveBayesClassifier());

    pipeline.Add(new PredictedLabelColumnOriginalValueConverter() { PredictedLabelColumn = "PredictedLabel" });

    // Train the pipeline based on the dataset that has been loaded, transformed.
    PredictionModel<ClassificationData, ClassPrediction> model =
                        pipeline.Train<ClassificationData, ClassPrediction>();

    await model.WriteAsync(modelPath); // Saves the model we trained to a zip file.

    return model;
}
Async Function TrainAsync(ByVal trainingDataFile As String,
                          ByVal modelPath As String)
                          As Task(Of PredictionModel(Of ClassificationData, ClassPrediction))

    Dim pipeline = New LearningPipeline()

    pipeline.Add(New TextLoader(trainingDataFile).CreateFrom(Of ClassificationData)())

    pipeline.Add(New Dictionarizer("Label"))

    pipeline.Add(New TextFeaturizer("Features", "Text"))

    pipeline.Add(New StochasticDualCoordinateAscentClassifier())
    'pipeline.Add(new LogisticRegressionClassifier());
    'pipeline.Add(new NaiveBayesClassifier());

    pipeline.Add(New PredictedLabelColumnOriginalValueConverter() With
    {
        .PredictedLabelColumn = "PredictedLabel"
    })

    ' Train the pipeline based on the dataset that has been loaded, transformed.
    Dim model As PredictionModel(Of ClassificationData, ClassPrediction) =
                        pipeline.Train(Of ClassificationData, ClassPrediction)()

    ' Saves the model we trained to a zip file.
    Await model.WriteAsync(modelPath)

    ' Returns the model we trained to use for evaluation.
    Return model
End Function

Dictionarizer("Label"); 步骤将带有标记输入值(0-5)的每一行映射到一个桶中。PredictedLabelColumnOriginalValueConverter 将预测值(向量)映射回原始值数据类型(float)。

编译并运行训练模块会得到此输出

Training Data Set
-----------------
Not adding a normalizer.
Using 4 threads to train.
Automatically choosing a check frequency of 4.
Auto-tuning parameters: maxIterations = 48.
Auto-tuning parameters: L2 = 2.778334E-05.
Auto-tuning parameters: L1Threshold (L1/L2) = 1.
Using best model from iteration 8.
Not training a calibrator because it is not needed.

Evaluating Training Results
---------------------------

PredictionModel quality metrics evaluation
------------------------------------------
  Accuracy Macro: 98.66%
  Accuracy Micro: 98.66%
   Top KAccuracy: 0.00%
         LogLoss: 7.50%

 PerClassLogLoss:
       Class: 0 - 11.18%
       Class: 1 - 4.08%
       Class: 2 - 5.95%
       Class: 3 - 10.43%
       Class: 4 - 7.86%
       Class: 5 - 5.52%

ML.Net 版本 0.2 中有三个多类别分类学习器,它们的 KPI 对比如下所示

 

分类方法 输出
new StochasticDualCoordinateAscentClassifier()

宏观准确率:98.66%
微观准确率:98.66%
Top K 准确率:0.00%
LogLoss:7.50%

按类别 LogLoss
类别:0 - 11.18%
类别:1 - 4.08%
类别:2 - 5.95%
类别:3 - 10.43%
类别:4 - 7.86%
类别:5 - 5.52%

new LogisticRegressionClassifier()

宏观准确率:98.52%
微观准确率:98.52%
Top K 准确率:0.00%
LogLoss:8.63%

按类别 LogLoss
类别:0 - 13.32%
类别:1 - 4.67%
类别:2 - 7.09%
类别:3 - 11.50%
类别:4 - 8.98%
类别:5 - 6.19%

new NaiveBayesClassifier()

宏观准确率:96.58%
微观准确率:96.58%
Top K 准确率:0.00%
LogLoss:3,453.88%

按类别 LogLoss
类别:0 - 3,453.88%
类别:1 - 3,453.88%
类别:2 - 3,453.88%
类别:3 - 3,453.88%
类别:4 - 3,453.88%
类别:5 - 3,453.88%

 

因此,这就是我们如何基于单个特征输入列对文本进行多类别分类。同样的机器学习方法(二元或多类别)也适用于多个特征输入列,正如我们接下来将看到的。

鸢尾花分类

版本 1

本节讨论的多类别分类问题是模式识别领域中的一个著名参考测试 [4]。原始数据库由 Ronald Fisher 于 1936 年创建,此处审查的 ML.Net 示例来自 ML.Net 教程的入门部分。问题陈述是创建一个算法,该算法接受多个浮点值(代表花朵的属性)的输入向量,并且该算法的输出应该是最可能的花朵名称。

在 ML.Net 中执行此操作需要我们创建一个具有多个列的输入映射

public class ClassificationData
{
    [Column("0")]
    public float SepalLength;

    [Column("1")]
    public float SepalWidth;

    [Column("2")]
    public float PetalLength;

    [Column("3")]
    public float PetalWidth;

    [Column("4")]
    [ColumnName("Label")]
    public string Label;
}
Public Class ClassificationData
    <Column("0")>
    Public SepalLength As Single

    <Column("1")>
    Public SepalWidth As Single

    <Column("2")>
    Public PetalLength As Single

    <Column("3")>
    Public PetalWidth As Single

    <Column("4")>
    <ColumnName("Label")>
    Public Label As String
End Class

我们输入一组特征列(即 SepalLength、SepalWidth、PetalLength、PetalWidth),这些列稍后合并到一个名为 Features 的向量中。在这种情况下,Label 是一个字符串,在训练和测试阶段作为最后一列给出,用于标识每一行数据。

预测类别的结果(不出所料)应该是一个字符串

public class ClassPrediction
{
    [ColumnName("PredictedLabel")]
    public string Class;
}
Public Class ClassPrediction
    <ColumnName("PredictedLabel")>
    Public [Class] As String
End Class

此情况下的训练代码与上一节非常相似

async Task<PredictionModel<ClassificationData, ClassPrediction>>
    TrainAsync(string trainingDataFile, string modelPath)
{
    var pipeline = new LearningPipeline();

    pipeline.Add(new TextLoader(trainingDataFile).CreateFrom<ClassificationData>(separator: ','));

    pipeline.Add(new Dictionarizer("Label"));

    pipeline.Add(new ColumnConcatenator("Features", "SepalLength", "SepalWidth", "PetalLength", "PetalWidth"));

    pipeline.Add(new StochasticDualCoordinateAscentClassifier());

    pipeline.Add(new PredictedLabelColumnOriginalValueConverter() { PredictedLabelColumn = "PredictedLabel" });

    // Train the pipeline based on the dataset that has been loaded, transformed.
    PredictionModel<ClassificationData, ClassPrediction> model =
                        pipeline.Train<ClassificationData, ClassPrediction>();

    await model.WriteAsync(modelPath);

    return model;
}
Async Function TrainAsync(ByVal trainingDataFile As String,
                          ByVal modelPath As String)
                          As Task(Of PredictionModel(Of ClassificationData, ClassPrediction))

    Dim pipeline = New LearningPipeline()

    pipeline.Add(New TextLoader(trainingDataFile).CreateFrom(Of ClassificationData)(separator:=","c))

    pipeline.Add(New Dictionarizer("Label"))

    pipeline.Add(New ColumnConcatenator("Features", "SepalLength", "SepalWidth", "PetalLength", "PetalWidth"))

    pipeline.Add(New StochasticDualCoordinateAscentClassifier())

    pipeline.Add(New PredictedLabelColumnOriginalValueConverter() With {
        .PredictedLabelColumn = "PredictedLabel"
    })

    ' Train the pipeline based on the dataset that has been loaded, transformed.
    ' Saves the model we trained to a zip file.
    Dim model As PredictionModel(Of ClassificationData, ClassPrediction) =
                         pipeline.Train(Of ClassificationData, ClassPrediction)()
    
    Await model.WriteAsync(modelPath)

    ' Returns the model we trained to use for evaluation.
    Return model
End Function

这里只有两件新事情。在这种情况下,原始输入数据是用逗号分隔的列表,因此,我们在管道中加载文本文件数据时必须使用 separator: ',' 参数。我们使用 ColumnConcatenator 将特征列集转换为一个由名为 Features 的向量组成的列。

输出与我们之前看到的类似(我们再次可以尝试上一节所示的其他两个学习器)

Training Data Set
-----------------
Automatically adding a MinMax normalization transform, use 'norm=Warn' or 'norm=No' to turn this behavior off.
Using 4 threads to train.
Automatically choosing a check frequency of 4.
Auto-tuning parameters: maxIterations = 45452.
Auto-tuning parameters: L2 = 2.667051E-05.
Auto-tuning parameters: L1Threshold (L1/L2) = 0.
Using best model from iteration 1956.
Not training a calibrator because it is not needed.

Evaluating Training Results
---------------------------

PredictionModel quality metrics evaluation
------------------------------------------
  Accuracy Macro: 95.73%
  Accuracy Micro: 95.76%
   Top KAccuracy: 0.00%
         LogLoss: 8.19%

 PerClassLogLoss:
       Class: 0 - 0.72%
       Class: 1 - 10.62%
       Class: 2 - 13.43%

同样,我们可以使用 IrisClassification 解决方案的训练模块来训练不同的学习器和设置,并使用预测模块来使用先前确定的模型预测新的分类。

在本节中,我们看到了如何使用 ColumnConcatenator 转换器将 4 个输入列(SepalLength、SepalWidth、PetalLength、PetalWidth)转换为一个矢量化的 Features 列。一种不需要我们在管道代码中使用 ColumnConcatenator 的等效方法是使用以下输入类定义

public class ClassificationData
{
    public float SepalLength
    {
      get { return Features[0]; }
      set { Features[0] = value; }
    }
    
    public float SepalWidth 
    {
      get { return Features[1]; }
      set { Features[1] = value; }
    }
    
    public float PetalLength
    {
      get { return Features[2]; }
      set { Features[2] = value; }
    }
    
    public float PetalWidth 
    {
      get { return Features[3]; }
      set { Features[3] = value; }
    }

    [Column("0-3")]
    [ColumnName("Features")]
    [VectorType(4)] public float[] Features = new float[4];

    [Column("4")]
    [ColumnName("Label")]
    public string Label;
}
Public Class ClassificationData
    Public Property SepalLength As Single
        Get
            Return Features(0)
        End Get
        Set(ByVal value As Single)
            Features(0) = value
        End Set
    End Property

    Public Property SepalWidth As Single
        Get
            Return Features(1)
        End Get
        Set(ByVal value As Single)
            Features(1) = value
        End Set
    End Property

    Public Property PetalLength As Single
        Get
            Return Features(2)
        End Get
        Set(ByVal value As Single)
            Features(2) = value
        End Set
    End Property

    Public Property PetalWidth As Single
        Get
            Return Features(3)
        End Get
        Set(ByVal value As Single)
            Features(3) = value
        End Set
    End Property

    <Column("0-3","Features")>
    <VectorType(4)>
    Public Features As Single() = New Single(3) {}
    <Column("4", "Label")>
    Public Label As String
End Class

但是,通过如上所示的 ClassificationData 定义来定义实际特征集是不好的做法。因此,我们应该删除 [ColumnName("Features")] 行,并在管道代码中插入 new ColumnConcatenator("Features", nameof(Digit.Features))。此设计可以在我们尝试评估不同特征配置时提供更大的灵活性。

版本 2

让我们暂时假设我们不希望机器学习算法处理字符串(因为我们确实想本地化应用程序的这一部分)。回到处理整数值并将每个整数视为索引以指示分类(花朵类型)会是一种更好的做法。但具体该如何做呢?我们可以像这样更改输入和预测输出的定义

public class ClassificationData
{
    public float SepalLength
    {
      get { return Features[0]; }
      set { Features[0] = value; }
    }
    
    public float SepalWidth
    {
      get { return Features[1]; }
      set { Features[1] = value; }
    }
    
    public float PetalLength
    {
      get { return Features[2]; }
      set { Features[2] = value; }
    }
    
    public float PetalWidth
    {
      get { return Features[3]; }
      set { Features[3] = value; }
    }

    [Column("0-3")]
    [ColumnName("Features")]
    [VectorType(4)] public float[] Features = new float[4];

    [Column("4")]
    [ColumnName("Label")]
    public float Label;
}

public class ClassPrediction
{
    [ColumnName("PredictedLabel")]
    public uint Class;
}
Public Class ClassificationData
    Public Property SepalLength As Single
        Get
            Return Features(0)
        End Get
        Set(ByVal value As Single)
            Features(0) = value
        End Set
    End Property

    Public Property SepalWidth As Single
        Get
            Return Features(1)
        End Get
        Set(ByVal value As Single)
            Features(1) = value
        End Set
    End Property

    Public Property PetalLength As Single
        Get
            Return Features(2)
        End Get
        Set(ByVal value As Single)
            Features(2) = value
        End Set
    End Property

    Public Property PetalWidth As Single
        Get
            Return Features(3)
        End Get
        Set(ByVal value As Single)
            Features(3) = value
        End Set
    End Property

    <Column("0-3")>
    <ColumnName("Features")>
    <VectorType(4)>
    Public Features As Single() = New Single(3) {}

    <Column("4")>
    <ColumnName("Label")>
    Public Label As Single
End Class

Public Class ClassPrediction
    <ColumnName("PredictedLabel")>
    Public [Class] As UInteger
End Class

接下来,我们将不得不从前一个解决方案的管道中删除 PredictedLabelColumnOriginalValueConverter,以下是调整此方案的方法(假设我们也调整了数据)。此方法也可以在附带的 IrisClassification_uint 解决方案中进行验证。

结论

审查的示例应用程序表明,ML.Net 在将机器学习集成到 .Net 框架方面具有有趣的价值(即使在 0.2 版本)。我们已经看到,二元和多类别分类可以基于不同类型的输入和输出。此输入和输出始终需要

  • 一个 Label 列和一个 Features 列作为输入,以及
  • 一个 PredictedLabel 列作为输出。

输入和输出的数据类型是灵活的,因为可以使用转换器将值转换为数字和向量,以将输入馈送到引擎,并且在我们必须解释分类结果时,显然也可能进行相同的转换。

希望本文有所帮助,并有助于入门。请以星级评价的形式给我您的反馈,或者如果您认为有重要的内容需要添加或更改,请告诉我,因为这可以帮助我们所有人进一步开发基于 ML.Net 的应用程序。

参考文献

历史

  • 2018-06-18 添加了 VB.Net 示例,并修复了 Wikipedia 示例中的一个小 bug(将默认学习器更改为最佳默认学习器,而不是默认使用最差的学习器)。
© . All rights reserved.