编辑
2025-11-27
C#
00

目录

先决条件
步骤 1:创建控制台应用程序
步骤 2:安装 NuGet 包
步骤 3:添加数据模型类
用于加载数据
用于接收预测结果
步骤 4:编写 Program.cs
主要步骤说明
SpikeEstimator 检测器
运行与结果
总结

本教程通过一个 C# 控制台应用示例,演示如何使用 ML.NET 对时序数据进行异常检测,帮助你在销售量等关键指标发生异常变化时及时捕捉峰值和更改点。

先决条件

  1. Visual Studio 2022(已安装 “.NET 桌面开发” 工作负载),或者你也可以使用 VS Code、JetBrains Rider 等编辑器,只要有 .NET 开发环境即可。
  2. 引用ML.NET
  3. 安装 .NET 6 SDK 或更高版本。
  4. 一份产品销售数据集(例如名为 statsfinal.csv 的文件),其中包含两列:
    • 日期(Date)
    • 销售量(Sales)

示例数据格式如下(仅节选):

DateSales
13-06-20105422
14-06-20107047
15-06-20101572
16-06-20105657
17-06-20103668
18-06-20102898

步骤 1:创建控制台应用程序

  1. 打开 Visual Studio,依次选择 “创建新项目” → “控制台应用程序”。
  2. 选择 “.NET 6” 作为目标框架。
  3. 依次单击 “下一步” → “创建” 即可完成项目创建。
  4. 在解决方案中创建一个 “Data” 文件夹,并将前述的 statsfinal.csv 文件放置于此。

步骤 2:安装 NuGet 包

在 “解决方案资源管理器” 中,右键单击你的项目并选择 “管理 NuGet 包”,然后:

  1. 搜索并安装 “Microsoft.ML” 最新稳定版本。
  2. 同样方式搜索并安装 “Microsoft.ML.TimeSeries” 最新稳定版本。

image.png 这些包包含本教程所需的核心功能和时序分析功能。


步骤 3:添加数据模型类

用于加载数据

C#
// 用于加载数据 public class ProductSalesData { [LoadColumn(0)] public string? Dt; [LoadColumn(1)] public float Sales; }

这里用 [LoadColumn] 特性来指定 CSV 文件中每列应映射到的 C# 属性。Sales解析为 float 类型,以便后续在 ML.NET 中使用。

用于接收预测结果

C#
// 用于接收预测结果 public class ProductSalesPrediction { [VectorType(3)] public double[]? Prediction { get; set; } }

步骤 4:编写 Program.cs

打开 Program.cs 并添加以下代码,展示完整逻辑:

C#
using Microsoft.ML; namespace App13 { internal class Program { // 数据集路径 private static string _dataPath = Path.Combine(Environment.CurrentDirectory, "Data", "statsfinal.csv"); // 数据集中包含的行数数量,根据实际数据设置 private const int _docSize = 36; static void Main(string[] args) { // 1. 创建新的 MLContext MLContext mlContext = new MLContext(); // 2. 加载数据 IDataView dataView = mlContext.Data.LoadFromTextFile<ProductSalesData>( path: _dataPath, hasHeader: true, separatorChar: ',' ); // 3. 执行“峰值”检测 DetectSpike(mlContext, _docSize, dataView); // 4. 执行“更改点”检测 DetectChangepoint(mlContext, _docSize, dataView); Console.WriteLine("========= 结束,按任意键退出 ========="); Console.ReadKey(); } /// <summary> /// 峰值异常检测 /// </summary> private static void DetectSpike(MLContext mlContext, int docSize, IDataView productSales) { Console.WriteLine("=============== 峰值检测 ==============="); // 1. 创建一个 Spike 检测器(IIDSpikeEstimator) var iidSpikeEstimator = mlContext.Transforms.DetectIidSpike( outputColumnName: nameof(ProductSalesPrediction.Prediction), inputColumnName: nameof(ProductSalesData.Sales), confidence: 95d, // 表示 95% 的置信度 pvalueHistoryLength: docSize / 4 ); // 2. 模型训练 var iidSpikeTransform = iidSpikeEstimator.Fit(CreateEmptyDataView(mlContext)); // 3. 使用训练好的模型转换实际数据 IDataView transformedData = iidSpikeTransform.Transform(productSales); // 4. 将结果投影到 IEnumerable 方便读取 var predictions = mlContext.Data.CreateEnumerable<ProductSalesPrediction>( transformedData, reuseRowObject: false ); Console.WriteLine("Alert\tScore\tP-Value"); int index = 0; foreach (var p in predictions) { var prediction = p.Prediction; // Prediction[0] 为警报(0 或 1),[1] 为原始分数,[2] 为 p-value if (prediction != null) { Console.WriteLine($"{prediction[0]}\t{prediction[1]:F2}\t{prediction[2]:F2}"); } index++; } Console.WriteLine("=============== 峰值检测完成 ===============\n"); } /// <summary> /// 更改点异常检测 /// </summary> private static void DetectChangepoint(MLContext mlContext, int docSize, IDataView productSales) { Console.WriteLine("=============== 更改点检测 ==============="); // 1. 创建一个 Change Point 检测器(IIDChangePointEstimator) var iidChangePointEstimator = mlContext.Transforms.DetectIidChangePoint( outputColumnName: nameof(ProductSalesPrediction.Prediction), inputColumnName: nameof(ProductSalesData.Sales), confidence: 95d, changeHistoryLength: docSize / 4 ); // 2. 模型训练 var iidChangePointTransform = iidChangePointEstimator.Fit(CreateEmptyDataView(mlContext)); // 3. 使用训练好的模型转换实际数据 IDataView transformedData = iidChangePointTransform.Transform(productSales); // 4. 将结果投影到 IEnumerable 方便读取 var predictions = mlContext.Data.CreateEnumerable<ProductSalesPrediction>( transformedData, reuseRowObject: false ); Console.WriteLine("Alert\tScore\tP-Value\tMartingale Value"); foreach (var p in predictions) { var prediction = p.Prediction; // Prediction[0] 为警报(0 或 1),[1] 为原始分数, [2] 为 p-value, [3] 为 martingale value if (prediction != null) { Console.WriteLine($"{prediction[0]}\t{prediction[1]:F2}\t{prediction[2]:F2}\t{prediction[3]:F2}"); } } Console.WriteLine("=============== 更改点检测完成 ===============\n"); } /// <summary> /// 创建一个空的 IDataView (仅用于“拟合”操作) /// </summary> private static IDataView CreateEmptyDataView(MLContext mlContext) { // 创建空列表 var enumerable = new List<ProductSalesData>(); return mlContext.Data.LoadFromEnumerable(enumerable); } } }

主要步骤说明

  1. 使用 MLContext 来初始化 ML.NET 环境(类似 EF 中的 DbContext)。
  2. LoadFromTextFile 来将 CSV 文件加载到 IDataView。
  3. 使用 DetectIidSpike / DetectIidChangePoint 分别进行峰值和更改点检测,得到训练管线 (Estimator)。
  4. 通过 Fit + Transform 进行模型训练和数据转换。
  5. CreateEnumerable 将结果投影成可枚举类型,方便在控制台直接查看输出。

SpikeEstimator 检测器

DetectIidSpike 检测器是 Microsoft ML.NET 中用于检测时间序列数据中独立同分布(IID)异常的工具。以下是其主要参数的说明:

  1. outputColumnName:
    • 类型: string
    • 描述: 指定输出列的名称,该列将包含预测的结果(警报、分数、P 值等),用于后续分析。
  2. inputColumnName:
    • 类型: string
    • 描述: 指定输入列的名称,该列包含用于检验的实际数值数据(例如销售数据)。
  3. confidence:
    • 类型: double
    • 描述: 指定用于检测的置信度水平,通常在 0 到 100 之间。值越高,表示要求的检测显著性越高。常用的置信度值为 95d,表示系统会以 95% 的置信度来判断是否发现异常。
  4. pvalueHistoryLength:
    • 类型: int
    • 描述: 该参数定义用于计算历史 P 值的样本数量。它决定了算法在检测峰值时参考的历史数据长度。一般来说,这是输入数据集大小的一个四分之一(例如:docSize / 4),确保检测器有足够的信息来进行准确的评估。

运行与结果

image.png

predictionProductSalesPrediction 类定义,包含了模型在检测过程中生成的预测结果。以下是 prediction 输出各参数的说明:

  1. 警报 (Alert):
    • 这是一个二元值,指示该数据点是否被视为异常。值为 1 表示检测到异常(峰值或更改点),值为 0 表示没有检测到异常。
  2. 原始分数 (Score):
    • 这是检测到的异常的置信分数,表示该数据点的异常程度。数值越高,表示该点越可能是异常点。
  3. P-值 (P-Value):
    • 这是进行统计检验时计算得出的 P 值,用于说明检测结果的显著性。P 值越小,表示发现的结果更有可能是真实的异常而不仅仅是随机波动。
  4. Martingale 值 (Martingale Value):
    • 仅适用于更改点检测,表示在检测到的更改点周围的趋势变化程度。它衡量的是在更改点之前和之后的预期收益的统计性质,通常用于筛选出明显的趋势变化。

总结

通过以上步骤,你可以快速地将 ML.NET 的异常检测功能集成到自己的 .NET 应用中,实现对时序销售数据(或者其他关键业务指标)的峰值和更改点监测。这样可在异常出现时及时通知相关人员并采取措施。

本示例只是基础流程,实际项目可在此基础上进行更多完善:

  • 将检测结果和原数据结合可视化输出。
  • 增加更多特征列(如节假日、市场活动等),以便结合业务上下文进行处理。
  • 与其他模型(如回归或预测模型)结合,完善整体数据分析流水线。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!