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






4.93/5 (37投票s)
使用 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]
监督式
- 分类(二元和多类别)
问题:它属于哪个类别?
- 回归
问题:是多少或有多少?
无监督式
- 排序
问题:接下来我应该做什么?
- 聚类
问题:这是如何组织的?
- 异常检测
问题:这有什么奇怪的吗?
每种类型的问题都有许多应用,为了使用正确的机器学习方法,我们必须首先确定我们是否想要回答其中任何一个问题,如果回答,我们是否有数据支持。
监督机器学习
本文讨论了可用的 .Net 示例(包括源代码和示例数据),用于二元和多类别分类。这种类型的机器学习算法假设我们可以标记一个项目以确定它是否属于
- 两个组中的一个(二元分类)或
- 多个组中的一个(多类别分类)
当你想用是或否的答案来回答一个问题时,可以应用二元分类。你通常会发现自己将一个项目(图像或文本)分类到两个类别中的一个。例如,考虑一下客户对你最近的调查反馈是心情好(正面)还是不好(负面)的问题。
用机器学习来回答这个问题需要我们标记示例项目(例如:图像或文本)以属于其中一个组。正常的工作流程需要两组独立的标记数据
- 训练数据集(用于训练机器学习算法)和
- 评估数据集(用于衡量 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
是我们输入及其应如何映射到Label 或 Feature 的粗略描述。尝试删除 Label 列定义,编译并执行,以验证如果找不到名为 Label 的列,系统将抛出异常。
ClassPrediction
只声明一个二元输出结果,该结果预期是一个 Boolean
值,将输入映射到两个二元类别之一。这部分对于
- 验证学习是否成功(在测试阶段有已知输入)和
- 确定机器学习算法在使用其模型进行生产时实际的分类。
总结:ClassificationData
用于描述我们希望如何处理输入(始终包含Label 和 Features),而 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% |
new LogisticRegressionClassifier() | 宏观准确率:98.52% |
new NaiveBayesClassifier() | 宏观准确率:96.58%
|
因此,这就是我们如何基于单个特征输入列对文本进行多类别分类。同样的机器学习方法(二元或多类别)也适用于多个特征输入列,正如我们接下来将看到的。
鸢尾花分类
版本 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 的应用程序。
参考文献
- [1] 机器学习框架
- [2] ML.Net 在 Build 2018 上的应用
- [3] 机器学习入门
- [4] 鸢尾花数据集模式识别
历史
- 2018-06-18 添加了 VB.Net 示例,并修复了 Wikipedia 示例中的一个小 bug(将默认学习器更改为最佳默认学习器,而不是默认使用最差的学习器)。