教程:在 ML.NET 中使用二元分类分析网站评论的情绪

本教程演示如何创建 .NET Core 控制台应用程序,该应用程序对网站评论情绪进行分类并采取适当的措施。 二元情绪分类器在 Visual Studio 2022 中使用 C#。

在本教程中,你将了解:

  • 创建控制台应用程序
  • 准备数据
  • 加载数据
  • 生成和定型模型
  • 评估模型
  • 使用模型进行预测
  • 查看结果

可以在 dotnet/samples 存储库中找到本教程的源代码。

先决条件

创建控制台应用程序

  1. 创建名为“SentimentAnalysis”的 C# 控制台应用程序。 单击“下一步”按钮。

  2. 选择 .NET 6 作为要使用的框架。 单击“创建” 按钮。

  3. 在项目中创建名为“Data”的目录,用于保存数据集文件。

  4. 安装“Microsoft.ML NuGet 包” :

    注意

    除非另有说明,否则本示例使用前面提到的 NuGet 包的最新稳定版本。

    在“解决方案资源管理器”中,右键单击项目,然后选择“管理 NuGet 包” 。 选择“nuget.org”作为包源,然后选择“浏览”选项卡。搜索“Microsoft.ML”,选择所需的包,然后选择“安装”按钮 。 同意所选包的许可条款,继续执行安装。

准备数据

注意

本教程的数据集摘自 KDD 2015 中由 Kotzias 等提出的“From Group to Individual Labels using Deep Features”, 并托管在 UCI 机器学习存储库中(Dua, D. 和 Karra Taniskidou, E.(2017))。 UCI 机器学习存储库 [http://archive.ics.uci.edu/ml ]。 加利福尼亚州,加利福尼亚大学:欧文分校,信息与计算机科学学院。

  1. 下载 UCI Sentiment Labeled Sentences 数据集 zip 文件并解压缩。

  2. yelp_labelled.txt 文件复制到已创建的“Data”目录中。

  3. 在“解决方案资源管理器”中,右键单击 yelp_labeled.txt 文件并选择“属性”。 在“高级”下,将“复制到输出目录”的值更改为“如果较新则复制” 。

创建类和定义路径

  1. 将以下附加的 using 语句添加到“Program.cs”文件顶部:

    using Microsoft.ML;
    using Microsoft.ML.Data;
    using SentimentAnalysis;
    using static Microsoft.ML.DataOperationsCatalog;
    
  2. 将以下代码添加到 using 语句正下方的行中,以创建字段来保存最近下载的数据集文件路径:

    string _dataPath = Path.Combine(Environment.CurrentDirectory, "Data", "yelp_labelled.txt");
    
  3. 接下来,为输入数据和预测结果创建类。 向项目添加一个新类:

    • 在“解决方案资源管理器” 中,右键单击项目,然后选择“添加” >“新项” 。

    • 在“添加新项” 对话框中,选择“类” 并将“名称” 字段更改为“SentimentData.cs” 。 然后,选择“添加”按钮。

  4. “SentimentData.cs” 文件随即在代码编辑器中打开。 将下面的 using 语句添加到 SentimentData.cs 的顶部:

    using Microsoft.ML.Data;
    
  5. 删除现有类定义并向“SentimentData.cs”文件添加以下代码,其中有两个类 SentimentDataSentimentPrediction

    public class SentimentData
    {
        [LoadColumn(0)]
        public string? SentimentText;
    
        [LoadColumn(1), ColumnName("Label")]
        public bool Sentiment;
    }
    
    public class SentimentPrediction : SentimentData
    {
    
        [ColumnName("PredictedLabel")]
        public bool Prediction { get; set; }
    
        public float Probability { get; set; }
    
        public float Score { get; set; }
    }
    

如何准备数据

输入数据集类 SentimentData 拥有一个用于用户评论 (SentimentText) 的 string,以及一个用于情绪的 bool (Sentiment),值为 1(正面)或 0(负面)。 这两个字段都附加了 LoadColumn 特性,其描述了每个字段的数据文件顺序。 此外,Sentiment 属性具有 ColumnName 特性,以将其指定为 Label 字段。 下面的示例文件没有标题行,如下所示:

情绪文本 情绪(标签)
女服务员的服务速度有点慢。 0
酥皮不行。 0
哇...喜欢这个地方。 1
服务很及时。 1

SentimentPrediction 是在模型训练后使用的预测类。 它继承自 SentimentData,因此输入 SentimentText 可与输出预测结果一并显示。 Prediction 布尔值是随附新的输入 SentimentText 提供时模型预测出的值。

输出类 SentimentPrediction 包含另外两个由模型计算得出的属性:Score(模型计算得出的原始分数)和 Probability(校准到具有积极情绪的文本几率的分数)。

在本教程中,最重要的属性是 Prediction

加载数据

ML.NET 中的数据表示为 IDataView 接口IDataView 是用于描述表格数据(数字和文本)的一种灵活且有效的方法。 可从文本文件或实时(例如,SQL 数据库或日志文件)将数据加载到 IDataView 对象。

MLContext 类是所有 ML.NET 操作的起点。 初始化 mlContext 会创建一个新的 ML.NET 环境,可在模型创建工作流对象之间共享该环境。 从概念上讲,它与实体框架中的 DBContext 类似。

准备应用,然后加载数据:

  1. 使用以下代码替换 Console.WriteLine("Hello World!") 行,以声明和初始化 mlContext 变量:

    MLContext mlContext = new MLContext();
    
  2. 将以下代码作为下一行代码:

    TrainTestData splitDataView = LoadData(mlContext);
    
  3. 使用以下代码在 Program.cs 文件底部创建 LoadData() 方法:

    TrainTestData LoadData(MLContext mlContext)
    {
    
    }
    

    LoadData() 方法执行以下任务:

    • 加载数据。
    • 将加载的数据集拆分为训练数据集和测试数据集。
    • 返回拆分的训练数据集和测试数据集。
  4. 将以下代码添加为 LoadData() 方法的首行:

    IDataView dataView = mlContext.Data.LoadFromTextFile<SentimentData>(_dataPath, hasHeader: false);
    

    LoadFromTextFile() 方法用于定义数据架构并读取文件。 它使用数据路径变量并返回 IDataView

拆分数据集以进行模型训练和测试

准备模型时,使用部分数据集来训练它,并使用部分数据集来测试模型的准确性。

  1. 要将加载的数据拆分为所需的数据集,请添加以下代码作为 LoadData() 方法中的下一行:

    TrainTestData splitDataView = mlContext.Data.TrainTestSplit(dataView, testFraction: 0.2);
    

    上述代码使用 TrainTestSplit() 方法将加载的数据集拆分为训练数据集和测试数据集,并在 DataOperationsCatalog.TrainTestData 类中返回它们。 使用 testFraction 参数指定数据的测试集百分比。 默认值为 10%,在本例中使用 20%,以评估更多数据。

  2. LoadData() 方法末尾返回 splitDataView

    return splitDataView;
    

生成和定型模型

  1. 在对 LoadData 方法的调用下方添加对 BuildAndTrainModel 方法的以下调用:

    ITransformer model = BuildAndTrainModel(mlContext, splitDataView.TrainSet);
    

    BuildAndTrainModel() 方法执行以下任务:

    • 提取并转换数据。
    • 定型模型。
    • 根据测试数据预测情绪。
    • 返回模型。
  2. 使用以下代码在 LoadData() 方法下方创建 BuildAndTrainModel() 方法:

    ITransformer BuildAndTrainModel(MLContext mlContext, IDataView splitTrainSet)
    {
    
    }
    

提取和转换数据

  1. FeaturizeText 作为下一行代码调用:

    var estimator = mlContext.Transforms.Text.FeaturizeText(outputColumnName: "Features", inputColumnName: nameof(SentimentData.SentimentText))
    

    上述代码中的 FeaturizeText() 方法将文本列 (SentimentText) 转换为机器学习算法使用的数字键类型的 Features 列,并将其作为新的数据集列添加:

    情绪文本 情绪 特征
    女服务员的服务速度有点慢。 0 [0.76, 0.65, 0.44, …]
    酥皮不行。 0 [0.98, 0.43, 0.54, …]
    哇...喜欢这个地方。 1 [0.35, 0.73, 0.46, …]
    服务很及时。 1 [0.39, 0, 0.75, …]

添加学习算法

此应用使用对数据项或数据行进行分类的分类算法。 应用将网站评论分类为正面评论或负面评论,因此,请使用二元分类任务。

将机器学习任务追加到数据转换定义中,方法是在 BuildAndTrainModel() 中添加以下代码作为下一行代码:

.Append(mlContext.BinaryClassification.Trainers.SdcaLogisticRegression(labelColumnName: "Label", featureColumnName: "Features"));

SdcaLogisticRegressionBinaryTrainer 是你的分类训练算法。 此算法会追加到 estimator 并接受特征化的 SentimentText (Features) 和 Label 输入参数,以便从历史数据中学习。

定型模型

BuildAndTrainModel() 方法中添加以下代码作为下一代码行,使模型适应 splitTrainSet 数据,并返回经过训练的模型:

Console.WriteLine("=============== Create and Train the Model ===============");
var model = estimator.Fit(splitTrainSet);
Console.WriteLine("=============== End of training ===============");
Console.WriteLine();

Fit() 方法通过转换数据集并应用训练来训练模型。

返回定型模型以用于评估

BuildAndTrainModel() 方法末尾返回模型:

return model;

评估模型

训练模型后,使用测试数据验证模型的性能。

  1. 使用以下代码紧跟 BuildAndTrainModel() 之后创建 Evaluate() 方法:

    void Evaluate(MLContext mlContext, ITransformer model, IDataView splitTestSet)
    {
    
    }
    

    Evaluate() 方法执行以下任务:

    • 加载测试数据集。
    • 创建 BinaryClassification 计算器。
    • 评估模型并创建指标。
    • 显示指标。
  2. 使用以下代码在 BuildAndTrainModel 方法调用下方添加对新方法的调用:

    Evaluate(mlContext, model, splitDataView.TestSet);
    
  3. 将以下代码添加到 Evaluate() 以转换 splitTestSet 数据:

    Console.WriteLine("=============== Evaluating Model accuracy with Test data===============");
    IDataView predictions = model.Transform(splitTestSet);
    

    之前的代码使用 Transform() 方法对测试数据集提供的多个输入行进行预测。

  4. 通过在 Evaluate() 方法中添加以下代码作为下一代码行来评估模型:

    CalibratedBinaryClassificationMetrics metrics = mlContext.BinaryClassification.Evaluate(predictions, "Label");
    

获得预测集 (predictions) 后,Evaluate() 方法会对模型进行评估,其会将预测值与测试数据集中的实际 Labels 进行比较,并返回有关模型执行情况的 CalibratedBinaryClassificationMetrics 对象。

显示用于模型验证的指标

使用以下代码显示指标:

Console.WriteLine();
Console.WriteLine("Model quality metrics evaluation");
Console.WriteLine("--------------------------------");
Console.WriteLine($"Accuracy: {metrics.Accuracy:P2}");
Console.WriteLine($"Auc: {metrics.AreaUnderRocCurve:P2}");
Console.WriteLine($"F1Score: {metrics.F1Score:P2}");
Console.WriteLine("=============== End of model evaluation ===============");
  • Accuracy 指标可获取模型的准确性,即测试集中正确预测所占的比例。

  • AreaUnderRocCurve 指标指示模型对正面类和负面类进行正确分类的置信度。 应该使 AreaUnderRocCurve 尽可能接近 1。

  • F1Score 指标可获取模型的 F1 分数,该分数是查准率查全率之间的平衡关系的度量值。 应该使 F1Score 尽可能接近 1。

预测测试数据结果

  1. 使用下面的代码紧随 Evaluate() 方法后创建 UseModelWithSingleItem() 方法:

    void UseModelWithSingleItem(MLContext mlContext, ITransformer model)
    {
    
    }
    

    UseModelWithSingleItem() 方法执行以下任务:

    • 创建测试数据的单个注释。
    • 根据测试数据预测情绪。
    • 结合测试数据和预测进行报告。
    • 显示预测结果。
  2. 使用以下代码在 Evaluate() 方法调用的正下方添加对新方法的调用:

    UseModelWithSingleItem(mlContext, model);
    
  3. 添加以下代码,以便作为 UseModelWithSingleItem() 方法中的第一行进行创建:

    PredictionEngine<SentimentData, SentimentPrediction> predictionFunction = mlContext.Model.CreatePredictionEngine<SentimentData, SentimentPrediction>(model);
    

    PredictionEngine 是一个简便 API,可使用它对单个数据实例执行预测。 PredictionEngine 不是线程安全。 可以在单线程环境或原型环境中使用。 为了在生产环境中提高性能和线程安全,请使用 PredictionEnginePool 服务,这将创建一个在整个应用程序中使用的 PredictionEngine 对象的 ObjectPool。 请参阅本指南,了解如何在 ASP.NET Core Web API 中使用 PredictionEnginePool

    注意

    PredictionEnginePool 服务扩展目前处于预览状态。

  4. 通过创建一个 SentimentData 实例,在 UseModelWithSingleItem() 方法中添加一个注释来测试定型模型的预测:

    SentimentData sampleStatement = new SentimentData
    {
        SentimentText = "This was a very bad steak"
    };
    
  5. 通过在 UseModelWithSingleItem() 方法中将以下代码作为下一行代码添加,将测试评论数据传递到 PredictionEngine

    var resultPrediction = predictionFunction.Predict(sampleStatement);
    

    Predict() 函数对单行数据进行预测。

  6. 使用以下代码显示 SentimentText 和相应的情绪预测:

    Console.WriteLine();
    Console.WriteLine("=============== Prediction Test of model with a single sample and test dataset ===============");
    
    Console.WriteLine();
    Console.WriteLine($"Sentiment: {resultPrediction.SentimentText} | Prediction: {(Convert.ToBoolean(resultPrediction.Prediction) ? "Positive" : "Negative")} | Probability: {resultPrediction.Probability} ");
    
    Console.WriteLine("=============== End of Predictions ===============");
    Console.WriteLine();
    

使用模型进行预测

部署和预测批项目

  1. 使用下面的代码紧随 UseModelWithSingleItem() 方法后创建 UseModelWithBatchItems() 方法:

    void UseModelWithBatchItems(MLContext mlContext, ITransformer model)
    {
    
    }
    

    UseModelWithBatchItems() 方法执行以下任务:

    • 创建批处理测试数据。
    • 根据测试数据预测情绪。
    • 结合测试数据和预测进行报告。
    • 显示预测结果。
  2. 使用以下代码在 UseModelWithSingleItem() 方法调用的正下方添加对新方法的调用:

    UseModelWithBatchItems(mlContext, model);
    
  3. 添加一些评论,以测试 UseModelWithBatchItems() 方法中的定型模型预测:

    IEnumerable<SentimentData> sentiments = new[]
    {
        new SentimentData
        {
            SentimentText = "This was a horrible meal"
        },
        new SentimentData
        {
            SentimentText = "I love this spaghetti."
        }
    };
    

预测评论情绪

使用模型通过 Transform() 方法预测评论数据情绪:

IDataView batchComments = mlContext.Data.LoadFromEnumerable(sentiments);

IDataView predictions = model.Transform(batchComments);

// Use model to predict whether comment data is Positive (1) or Negative (0).
IEnumerable<SentimentPrediction> predictedResults = mlContext.Data.CreateEnumerable<SentimentPrediction>(predictions, reuseRowObject: false);

合并并显示预测结果

使用以下代码为预测创建标头:

Console.WriteLine();

Console.WriteLine("=============== Prediction Test of loaded model with multiple samples ===============");

由于 SentimentPrediction 继承自 SentimentDataTransform() 方法使用预测字段填充 SentimentText。 随着 ML.NET 进程继续执行,每个组件会添加列,这让显示结果变得轻松:

foreach (SentimentPrediction prediction  in predictedResults)
{
    Console.WriteLine($"Sentiment: {prediction.SentimentText} | Prediction: {(Convert.ToBoolean(prediction.Prediction) ? "Positive" : "Negative")} | Probability: {prediction.Probability} ");
}
Console.WriteLine("=============== End of predictions ===============");

结果

结果应如下所示。 处理期间将显示消息。 你可能会看到警告或处理消息。 为清楚起见,已经从下面的结果中删除这些内容。

Model quality metrics evaluation
--------------------------------
Accuracy: 83.96%
Auc: 90.51%
F1Score: 84.04%

=============== End of model evaluation ===============

=============== Prediction Test of model with a single sample and test dataset ===============

Sentiment: This was a very bad steak | Prediction: Negative | Probability: 0.1027377
=============== End of Predictions ===============

=============== Prediction Test of loaded model with a multiple samples ===============

Sentiment: This was a horrible meal | Prediction: Negative | Probability: 0.1369192
Sentiment: I love this spaghetti. | Prediction: Positive | Probability: 0.9960636
=============== End of predictions ===============

=============== End of process ===============
Press any key to continue . . .

祝贺你! 现在,你已成功生成用于分类和预测消息情绪的机器学习模型。

生成成功的模型是一个迭代过程。 由于本教程使用小型数据集来提供快速模型训练,因此该模型的初始质量较低。 如果对模型质量不满意,可以通过尝试提供更大的训练数据集,或通过为每种算法选择具有不同超参数的不同训练算法来改进它。

可以在 dotnet/samples 存储库中找到本教程的源代码。

后续步骤

在本教程中,你将了解:

  • 创建控制台应用程序
  • 准备数据
  • 加载数据
  • 生成和定型模型
  • 评估模型
  • 使用模型进行预测
  • 查看结果

进入下一教程了解详细信息