教程:使用 ML.NET 检测产品销售中的异常

了解如何为产品销售数据生成异常情况检测应用程序。 本教程在 Visual Studio 中使用 C# 创建 .NET 控制台应用程序。

本教程中,您将学习如何:

  • 加载数据
  • 为尖峰异常检测创建转换器
  • 使用转换检测峰值异常
  • 为更改点异常情况检测创建转换
  • 使用变换检测变点异常

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

先决条件

注释

数据格式在product-sales.csv中,基于数据集“香波三年销售数据”,该数据集最初由 DataMarket 提供并由 Rob Hyndman 创建的时间序列数据库(TSDL)提供。 在 DataMarket 默认开放许可证下许可的“三年内销售”数据集。

创建控制台应用程序

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

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

  3. 在项目中创建名为 Data 的目录以保存数据集文件。

  4. 安装 Microsoft.ML NuGet 包

    注释

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

    在解决方案资源管理器中,右键单击项目并选择“ 管理 NuGet 包”。 选择“nuget.org”作为包源,选择“浏览”选项卡,搜索 Microsoft.ML ,然后选择“ 安装”。 选择“预览更改”对话框中的“确定”按钮,然后选择“许可接受”对话框中的“我接受”按钮(如果同意列出的程序包的许可条款)。 对 Microsoft.ML.TimeSeries 重复这些步骤。

  5. using文件的顶部添加以下指令:

    using Microsoft.ML;
    using ProductSalesAnomalyDetection;
    

下载您的数据

  1. 下载数据集并将其保存到之前创建的 Data 文件夹中:

    • 右键单击 product-sales.csv 并选择“保存链接(或目标)...”。

      请确保将 *.csv 文件保存到 数据 文件夹,或者在其他位置保存后,将 *.csv 文件移动到 数据 文件夹。

  2. 在解决方案资源管理器中,右键单击 *.csv 文件,然后选择“ 属性”。 在 高级 下,将 复制到输出目录 的值更改为 若较新则复制

下表是 *.csv 文件中的数据预览:

月份 ProductSales
1-1 月 271
1 月 2 日 150.9
..... .....
1-2 月 199.3
..... .....

创建类并定义路径

接下来,定义输入和预测类数据结构。

向项目添加新类:

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

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

    ProductSalesData.cs文件将在代码编辑器中打开。

  3. 将以下 using 指令添加到 ProductSalesData.cs顶部:

    using Microsoft.ML.Data;
    
  4. 删除现有类定义,并将具有两个类 ProductSalesDataProductSalesPrediction以下代码添加到 ProductSalesData.cs 文件中:

    public class ProductSalesData
    {
        [LoadColumn(0)]
        public string? Month;
    
        [LoadColumn(1)]
        public float numSales;
    }
    
    public class ProductSalesPrediction
    {
        //vector to hold alert,score,p-value values
        [VectorType(3)]
        public double[]? Prediction { get; set; }
    }
    

    ProductSalesData 指定输入数据类。 LoadColumn 属性指定应加载数据集中的哪些列(按列索引)。

    ProductSalesPrediction 指定预测数据类。 对于异常检测,预测包括警报以指示是否存在异常、原始分数和 p 值。 p 值越接近 0,就更有可能发生异常。

  5. 创建两个全局字段来保存最近下载的数据集文件路径和保存的模型文件路径:

    • _dataPath 具有用于训练模型的数据集的路径。
    • _docsize 包含数据集文件中的记录数。 你将用于 _docSize 计算 pvalueHistoryLength
  6. 将以下代码添加到指令正下方的 using 行,以指定这些路径:

    string _dataPath = Path.Combine(Environment.CurrentDirectory, "Data", "product-sales.csv");
    //assign the Number of records in dataset file to constant variable
    const int _docsize = 36;
    

初始化变量

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

    MLContext mlContext = new MLContext();
    

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

加载数据

ML.NET 中的数据表示为 IDataView 接口IDataView 是描述表格数据(数字和文本)的灵活高效方法。 可以将数据从文本文件或其他源(例如 SQL 数据库或日志文件)加载到对象 IDataView

  1. 在创建 mlContext 变量后添加以下代码:

    IDataView dataView = mlContext.Data.LoadFromTextFile<ProductSalesData>(path: _dataPath, hasHeader: true, separatorChar: ',');
    

    LoadFromTextFile() 定义数据架构并在文件中读取。 它接收数据路径变量并返回一个 IDataView

时序异常情况检测

异常检测会标记出意外或异常的事件或行为。 它提供了查找问题的线索,并帮助你回答“这是奇怪的吗?

“这是否奇怪”异常检测的示例。

异常检测是检测时序数据离群值的过程,即在给定的输入时序数据中,其行为与预期不符或显得“异常”。

异常情况检测在很多方面都很有用。 例如:

如果你有汽车,你可能想知道:这个油表读数正常,还是我有泄漏? 如果要监视能耗,需要知道:是否发生中断?

可以检测到两种类型的时序异常:

  • 峰值 表示系统中异常行为的临时突发。

  • 更改点 表示系统中一段时间内持续更改的开始。

在 ML.NET 中,IID 峰值检测或 IID 更改点检测算法适用于 独立且相同的分布式数据集。 它们假设输入数据是一系列数据点,这些点从 一个平稳分布中独立采样。

与其他教程中的模型不同,时序异常检测器转换直接对输入数据进行作。 该方法 IEstimator.Fit() 不需要训练数据来生成转换。 不过,它确实需要数据架构,该架构由从空列表 ProductSalesData生成的数据视图提供。

你将分析相同的产品销售数据,以检测峰值和更改点。 生成和训练模型过程与峰值检测和更改点检测相同;主要区别是使用的特定检测算法。

峰值检测

峰值检测的目标是识别那些与大多数时序数据值显著不同的、突然且短暂的突发情况。 必须及时检测这些可疑的罕见项目、事件或观察,以尽量减少这些可疑物品、事件或观察。 以下方法可用于检测各种异常,例如:中断、网络攻击或病毒 Web 内容。 下图是时序数据集中的峰值示例:

显示两个峰值检测的屏幕截图。

添加 CreateEmptyDataView() 方法

将以下方法添加到 Program.cs

IDataView CreateEmptyDataView(MLContext mlContext) {
    // Create empty DataView. We just need the schema to call Fit() for the time series transforms
    IEnumerable<ProductSalesData> enumerableData = new List<ProductSalesData>();
    return mlContext.Data.LoadFromEnumerable(enumerableData);
}

生成 CreateEmptyDataView() 一个空数据视图对象,该对象具有用作方法输入 IEstimator.Fit() 的正确架构。

创建 DetectSpike() 方法

DetectSpike() 方法:

  • 从估算器创建转换。
  • 根据历史销售数据检测峰值。
  • 显示结果。
  1. DetectSpike()使用以下代码在Program.cs文件的底部创建方法:

    DetectSpike(MLContext mlContext, int docSize, IDataView productSales)
    {
    
    }
    
  2. 使用 IidSpikeEstimator 训练模型进行峰值检测。 使用以下代码将其添加到 DetectSpike() 方法:

    var iidSpikeEstimator = mlContext.Transforms.DetectIidSpike(outputColumnName: nameof(ProductSalesPrediction.Prediction), inputColumnName: nameof(ProductSalesData.numSales), confidence: 95d, pvalueHistoryLength: docSize / 4);
    
  3. DetectSpike() 方法中添加以下内容作为下一行代码,以创建峰值检测转换:

    小窍门

    参数 confidence 会影响 pvalueHistoryLength 检测峰值的方式。 confidence 确定模型对峰值的敏感程度。 置信度越低,算法就越有可能检测“更小”峰值。 该 pvalueHistoryLength 参数定义滑动窗口中的数据点数。 此参数的值通常是整个数据集的百分比。 越低 pvalueHistoryLength,模型越快,就忘记了以前的大峰值。

    ITransformer iidSpikeTransform = iidSpikeEstimator.Fit(CreateEmptyDataView(mlContext));
    
  4. 添加以下代码行以将数据转换为 productSales 方法中的 DetectSpike() 下一行:

    IDataView transformedData = iidSpikeTransform.Transform(productSales);
    

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

  5. 将你 transformedData 转换为强类型 IEnumerable ,以便更轻松地使用 CreateEnumerable() 方法和以下代码进行显示:

    var predictions = mlContext.Data.CreateEnumerable<ProductSalesPrediction>(transformedData, reuseRowObject: false);
    
  6. 使用以下 Console.WriteLine() 代码创建显示标头行:

    Console.WriteLine("Alert\tScore\tP-Value");
    

    你将在峰值检测结果中显示以下信息:

    • Alert 指示给定数据点的峰值警报。
    • Score ProductSales是数据集中给定数据点的值。
    • P-Value “P”代表概率。 p 值越接近 0,数据点就越有可能是异常。
  7. 使用以下代码遍历 predictionsIEnumerable 并显示结果:

    foreach (var p in predictions)
    {
        if (p.Prediction is not null)
        {
            var results = $"{p.Prediction[0]}\t{p.Prediction[1]:f2}\t{p.Prediction[2]:F2}";
    
            if (p.Prediction[0] == 1)
            {
                results += " <-- Spike detected";
            }
    
            Console.WriteLine(results);
        }
    }
    Console.WriteLine("");
    
  8. 在对方法的调用下面添加对 DetectSpike() 方法的 LoadFromTextFile() 调用:

    DetectSpike(mlContext, _docsize, dataView);
    

峰值检测结果

结果应如下所示。 在处理过程中,将显示消息。 你可能会看到警告或处理消息。 为了清楚起见,已从以下结果中删除某些消息。

Detect temporary changes in pattern
=============== Training the model ===============
=============== End of training process ===============
Alert   Score   P-Value
0       271.00  0.50
0       150.90  0.00
0       188.10  0.41
0       124.30  0.13
0       185.30  0.47
0       173.50  0.47
0       236.80  0.19
0       229.50  0.27
0       197.80  0.48
0       127.90  0.13
1       341.50  0.00 <-- Spike detected
0       190.90  0.48
0       199.30  0.48
0       154.50  0.24
0       215.10  0.42
0       278.30  0.19
0       196.40  0.43
0       292.00  0.17
0       231.00  0.45
0       308.60  0.18
0       294.90  0.19
1       426.60  0.00 <-- Spike detected
0       269.50  0.47
0       347.30  0.21
0       344.70  0.27
0       445.40  0.06
0       320.90  0.49
0       444.30  0.12
0       406.30  0.29
0       442.40  0.21
1       580.50  0.00 <-- Spike detected
0       412.60  0.45
1       687.00  0.01 <-- Spike detected
0       480.30  0.40
0       586.30  0.20
0       651.90  0.14

更改点检测

Change points 是时序事件流分布值的持久更改,例如级别更改和趋势。 这些持久更改的持续时间比 spikes 并可能表明灾难性事件的时间要长得多。 Change points 通常不可见于裸眼,但可以使用以下方法等方法在数据中检测到。 下图是更改点检测的示例:

显示更改点检测的屏幕截图。

创建 DetectChangepoint() 方法

该方法 DetectChangepoint() 执行以下任务:

  • 利用估算器创建转换。
  • 根据历史销售数据检测更改点。
  • 显示结果。
  1. DetectChangepoint()使用以下代码在方法声明之后DetectSpike()创建方法:

    void DetectChangepoint(MLContext mlContext, int docSize, IDataView productSales)
    {
    
    }
    
  2. 使用以下代码在方法中创建 DetectChangepoint()

    var iidChangePointEstimator = mlContext.Transforms.DetectIidChangePoint(outputColumnName: nameof(ProductSalesPrediction.Prediction), inputColumnName: nameof(ProductSalesData.numSales), confidence: 95d, changeHistoryLength: docSize / 4);
    
  3. 如前所述,通过在方法中添加以下代码 DetectChangePoint() 行,从估算器创建转换:

    小窍门

    更改点的检测会稍有延迟,因为模型需要确保当前偏差是永久性更改,而不仅仅是在创建警报之前出现一些随机峰值。 此延迟量等于 changeHistoryLength 参数。 通过增加此参数的值,更改检测警报对更持久的更改,但权衡将是更长的延迟。

    var iidChangePointTransform = iidChangePointEstimator.Fit(CreateEmptyDataView(mlContext));
    
  4. Transform()通过将以下代码添加到以下代码DetectChangePoint(),使用该方法转换数据:

    IDataView transformedData = iidChangePointTransform.Transform(productSales);
    
  5. 正如之前所做的那样,将你 transformedData 转换为强类型 IEnumerable ,以便更轻松地使用 CreateEnumerable()方法和以下代码进行显示:

    var predictions = mlContext.Data.CreateEnumerable<ProductSalesPrediction>(transformedData, reuseRowObject: false);
    
  6. 使用以下代码创建一个显示标头,作为方法中的 DetectChangePoint() 下一行:

    Console.WriteLine("Alert\tScore\tP-Value\tMartingale value");
    

    你将在更改点检测结果中显示以下信息:

    • Alert 表示给定数据点的变更点警报。
    • Score ProductSales是数据集中给定数据点的值。
    • P-Value “P”代表概率。 P 值越接近 0,数据点就越有可能是异常。
    • Martingale value 用于根据 P 值序列标识数据点“奇怪”的方式。
  7. 使用以下代码遍历predictionsIEnumerable并显示结果:

    foreach (var p in predictions)
    {
        if (p.Prediction is not null)
        {
            var results = $"{p.Prediction[0]}\t{p.Prediction[1]:f2}\t{p.Prediction[2]:F2}\t{p.Prediction[3]:F2}";
    
            if (p.Prediction[0] == 1)
            {
                results += " <-- alert is on, predicted changepoint";
            }
            Console.WriteLine(results);
        }
    }
    Console.WriteLine("");
    
  8. 在调用DetectSpike()方法之后,将以下调用添加到DetectChangepoint()方法:

    DetectChangepoint(mlContext, _docsize, dataView);
    

更改点检测结果

结果应如下所示。 在处理过程中,将显示消息。 你可能会看到警告或处理消息。 为了清楚起见,已从以下结果中删除了一些消息。

Detect Persistent changes in pattern
=============== Training the model Using Change Point Detection Algorithm===============
=============== End of training process ===============
Alert   Score   P-Value Martingale value
0       271.00  0.50    0.00
0       150.90  0.00    2.33
0       188.10  0.41    2.80
0       124.30  0.13    9.16
0       185.30  0.47    9.77
0       173.50  0.47    10.41
0       236.80  0.19    24.46
0       229.50  0.27    42.38
1       197.80  0.48    44.23 <-- alert is on, predicted changepoint
0       127.90  0.13    145.25
0       341.50  0.00    0.01
0       190.90  0.48    0.01
0       199.30  0.48    0.00
0       154.50  0.24    0.00
0       215.10  0.42    0.00
0       278.30  0.19    0.00
0       196.40  0.43    0.00
0       292.00  0.17    0.01
0       231.00  0.45    0.00
0       308.60  0.18    0.00
0       294.90  0.19    0.00
0       426.60  0.00    0.00
0       269.50  0.47    0.00
0       347.30  0.21    0.00
0       344.70  0.27    0.00
0       445.40  0.06    0.02
0       320.90  0.49    0.01
0       444.30  0.12    0.02
0       406.30  0.29    0.01
0       442.40  0.21    0.01
0       580.50  0.00    0.01
0       412.60  0.45    0.01
0       687.00  0.01    0.12
0       480.30  0.40    0.08
0       586.30  0.20    0.03
0       651.90  0.14    0.09

祝贺! 现已成功生成机器学习模型,用于检测销售数据中的峰值和更改点异常。

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

在本教程中,你将学习到如何:

  • 加载数据
  • 训练模型以检测峰值异常情况
  • 使用训练的模型检测峰值异常
  • 训练模型以检测更改点异常情况
  • 使用训练模式检测更改点异常

后续步骤

查看机器学习示例 GitHub 存储库,探索季节性数据异常情况检测示例。