教程:通过时序分析和 ML.NET 预测自行车租赁服务需求

了解如何使用单变量时序分析通过 ML.NET 在 SQL Server 数据库中存储的数据预测自行车租赁服务的需求。

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

  • 了解问题
  • 从数据库加载数据
  • 创建预测模型
  • 评估预测模型
  • 保存预测模型
  • 使用预测模型

先决条件

时序预测示例概述

此示例是一个 C# 控制台应用程序 ,它使用单变量时序分析算法(称为单一光谱分析)来预测自行车租赁需求。 可以在 GitHub 上的 dotnet/machinelearning-samples 存储库中找到此示例的代码。

了解问题

为了运行高效的作,库存管理起着关键作用。 在库存中拥有太多的产品意味着坐在货架上的无售产品不会产生任何收入。 产品太少导致销售损失,客户从竞争对手购买。 因此,常量问题是,要保持的最佳库存量是多少? 时序分析通过查看历史数据、识别模式以及使用此信息来预测将来某个时间的值,从而帮助回答这些问题。

用于分析本教程中使用的数据的技术是单变量时序分析。 单变量时序分析在特定时间间隔(例如每月销售额)中查看一段时间内的单个数值观察。

本教程中使用的算法是单数谱分析(SSA)。 SSA 的工作原理是将时序分解为一组主体组件。 这些组件可以解释为与趋势、噪音、季节性和许多其他因素相对应的信号的各个部分。 然后,重新构造这些组件,并用于在将来的某个时间预测值。

创建控制台应用程序

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

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

  3. 安装 Microsoft.ML 版本 NuGet 包

    注释

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

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

准备和了解数据

  1. 创建名为 Data 的目录。
  2. 下载 DailyDemand.mdf 数据库文件 并将其保存到 数据 目录。

注释

本教程中使用的数据来自 UCI 自行车共享数据集。 Hadi Fanaee-T 和 João Gama,《结合集成检测器和背景知识的事件标签》,《人工智能进展》(2013年):第1-15页,Springer Berlin Heidelberg,网络链接

原始数据集包含多个对应于季节性和天气的列。 由于本教程中使用的算法只需要单个数字列中的值,为了简洁起见,原始数据集已被缩减为仅包含以下列:

  • dteday:观察日期。
  • year:观察的编码年份(0=2011,1=2012)。
  • cnt:当天的自行车出租总数。

原始数据集映射到 SQL Server 数据库中具有以下架构的数据库表。

CREATE TABLE [Rentals] (
    [RentalDate] DATE NOT NULL,
    [Year] INT NOT NULL,
    [TotalRentals] INT NOT NULL
);

下面是数据的一个示例:

RentalDate 年份 TotalRentals
1/1/2011 0 985
1/2/2011 0 801
1/3/2011 0 1349

创建输入和输出类

  1. 打开 Program.cs 文件,并将现有 using 指令替换为以下内容:

    using Microsoft.ML;
    using Microsoft.ML.Data;
    using Microsoft.ML.Transforms.TimeSeries;
    using System.Data.SqlClient;
    
  2. 创建 ModelInput 类。 在 Program 类下方,添加以下代码。

    public class ModelInput
    {
        public DateTime RentalDate { get; set; }
    
        public float Year { get; set; }
    
        public float TotalRentals { get; set; }
    }
    

    ModelInput 类包含以下列:

    • RentalDate:观察日期。
    • :观察的编码年份(0=2011,1=2012)。
    • TotalRentals:当天的自行车出租总数。
  3. 在新创建的ModelOutput类下方创建ModelInput类。

    public class ModelOutput
    {
        public float[] ForecastedRentals { get; set; }
    
        public float[] LowerBoundRentals { get; set; }
    
        public float[] UpperBoundRentals { get; set; }
    }
    

    ModelOutput 类包含以下列:

    • ForecastedRentals:预测周期的预测值。
    • LowerBoundRentals:预测周期的预测最小值。
    • UpperBoundRentals:预测时间段的预测最大值。

定义路径并初始化变量

  1. 指令 using 下方定义变量来存储数据的位置、连接字符串以及保存已训练的模型的位置。

    string rootDir = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../"));
    string dbFilePath = Path.Combine(rootDir, "Data", "DailyDemand.mdf");
    string modelPath = Path.Combine(rootDir, "MLModel.zip");
    var connectionString = $"Data Source=(LocalDB)\\MSSQLLocalDB;AttachDbFilename={dbFilePath};Integrated Security=True;Connect Timeout=30;";
    
  2. mlContext通过在定义路径后添加以下行,使用新实例MLContext初始化变量。

    MLContext mlContext = new MLContext();
    

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

加载数据

  1. 创建 DatabaseLoader 加载类型的 ModelInput记录。

    DatabaseLoader loader = mlContext.Data.CreateDatabaseLoader<ModelInput>();
    
  2. 定义从数据库加载数据的查询。

    string query = "SELECT RentalDate, CAST(Year as REAL) as Year, CAST(TotalRentals as REAL) as TotalRentals FROM Rentals";
    

    ML.NET 算法预期数据为类型 Single。 因此,来自数据库的非Real类型的数值(即非单精度浮点值)必须转换为Real

    YearTotalRental列都是数据库中的整数类型。 CAST使用内置函数时,它们都强制转换为 Real

  3. 创建一个 DatabaseSource 连接到数据库并执行查询。

    DatabaseSource dbSource = new DatabaseSource(SqlClientFactory.Instance,
                                    connectionString,
                                    query);
    
  4. 将数据加载到 .IDataView

    IDataView dataView = loader.Load(dbSource);
    
  5. 数据集包含两年的数据。 只有第一年的数据用于训练,第二年会保留,以便将实际值与模型生成的预测进行比较。 使用 FilterRowsByColumn 转换筛选数据。

    IDataView firstYearData = mlContext.Data.FilterRowsByColumn(dataView, "Year", upperBound: 1);
    IDataView secondYearData = mlContext.Data.FilterRowsByColumn(dataView, "Year", lowerBound: 1);
    

    对于第一年,仅选择 upperBound 列中小于 1 的值,方法是将 Year 参数设置为 1。 相反,对于第二年,通过将参数设置为 lowerBound 1 来选择大于或等于 1 的值。

定义时序分析管道

  1. 定义一个管道,该管道使用 SsaForecastingEstimator 预测时序数据集中的值。

    var forecastingPipeline = mlContext.Forecasting.ForecastBySsa(
        outputColumnName: "ForecastedRentals",
        inputColumnName: "TotalRentals",
        windowSize: 7,
        seriesLength: 30,
        trainSize: 365,
        horizon: 7,
        confidenceLevel: 0.95f,
        confidenceLowerBoundColumn: "LowerBoundRentals",
        confidenceUpperBoundColumn: "UpperBoundRentals");
    

    forecastingPipeline 收集第一年的 365 个数据点,并按照 seriesLength 参数将时序数据集采样或拆分为 30 天的时间间隔,例如每月。 每个示例都通过每周或 7 天窗口进行分析。 确定下一个时间段的预测值时,前七天的值用于进行预测。 模型设置为根据参数定义的 horizon 未来预测 7 个周期。 因为预测是一个明智的猜测,所以它并不总是 100% 准确。 因此,最好知道上限和下限定义的最佳和最坏情况下的值范围。 在这种情况下,下限和上限的置信度设置为 95%。 可以相应地增加或减少置信度。 值越高,范围越宽,介于上限和下限之间,以实现所需的置信度级别。

  2. 使用该方法 Fit 训练模型并将数据拟合到之前定义的 forecastingPipeline数据。

    SsaForecastingTransformer forecaster = forecastingPipeline.Fit(firstYearData);
    

评估模型

通过预测明年的数据并将其与实际值进行比较来评估模型的性能。

  1. 创建Evaluate文件底部调用的新实用工具方法。

    Evaluate(IDataView testData, ITransformer model, MLContext mlContext)
    {
    
    }
    
  2. Evaluate 方法中,通过对训练的模型使用 Transform 该方法预测第二年的数据。

    IDataView predictions = model.Transform(testData);
    
  3. 使用 CreateEnumerable 该方法从数据中获取实际值。

    IEnumerable<float> actual =
        mlContext.Data.CreateEnumerable<ModelInput>(testData, true)
            .Select(observed => observed.TotalRentals);
    
  4. 使用 CreateEnumerable 该方法获取预测值。

    IEnumerable<float> forecast =
        mlContext.Data.CreateEnumerable<ModelOutput>(predictions, true)
            .Select(prediction => prediction.ForecastedRentals[0]);
    
  5. 计算实际值与预测值之间的差异,通常称为错误。

    var metrics = actual.Zip(forecast, (actualValue, forecastValue) => actualValue - forecastValue);
    
  6. 通过计算平均绝对误差和根均方误差值来衡量性能。

    var MAE = metrics.Average(error => Math.Abs(error)); // Mean Absolute Error
    var RMSE = Math.Sqrt(metrics.Average(error => Math.Pow(error, 2))); // Root Mean Squared Error
    

    若要评估性能,请使用以下指标:

    • 平均绝对误差:度量预测与实际值的接近程度。 此值的范围介于 0 和无穷大之间。 越接近 0,模型的质量就越好。
    • 根均方误差:汇总模型中的错误。 此值的范围介于 0 和无穷大之间。 越接近 0,模型的质量就越好。
  7. 将指标输出到控制台。

    Console.WriteLine("Evaluation Metrics");
    Console.WriteLine("---------------------");
    Console.WriteLine($"Mean Absolute Error: {MAE:F3}");
    Console.WriteLine($"Root Mean Squared Error: {RMSE:F3}\n");
    
  8. Evaluate调用以下调用Fit()该方法的方法。

    Evaluate(secondYearData, forecaster, mlContext);
    

保存模型

如果对模型感到满意,请保存它以供以后在其他应用程序中使用。

  1. 方法 Evaluate() 下方创建一个 TimeSeriesPredictionEngineTimeSeriesPredictionEngine 是一种方便的方法,用于进行单个预测。

    var forecastEngine = forecaster.CreateTimeSeriesEngine<ModelInput, ModelOutput>(mlContext);
    
  2. 将模型保存到前面定义的MLModel.zip变量指定的名为modelPath的文件。 Checkpoint使用该方法保存模型。

    forecastEngine.CheckPoint(mlContext, modelPath);
    

使用模型预测需求

  1. 在方法下方,创建一Evaluate个名为 Forecast 的新实用工具方法。

    void Forecast(IDataView testData, int horizon, TimeSeriesPredictionEngine<ModelInput, ModelOutput> forecaster, MLContext mlContext)
    {
    
    }
    
  2. 在该方法中 Forecast ,使用 Predict 该方法预测未来七天的租金。

    ModelOutput forecast = forecaster.Predict();
    
  3. 对齐七个周期的实际值和预测值。

    IEnumerable<string> forecastOutput =
        mlContext.Data.CreateEnumerable<ModelInput>(testData, reuseRowObject: false)
            .Take(horizon)
            .Select((ModelInput rental, int index) =>
            {
                string rentalDate = rental.RentalDate.ToShortDateString();
                float actualRentals = rental.TotalRentals;
                float lowerEstimate = Math.Max(0, forecast.LowerBoundRentals[index]);
                float estimate = forecast.ForecastedRentals[index];
                float upperEstimate = forecast.UpperBoundRentals[index];
                return $"Date: {rentalDate}\n" +
                $"Actual Rentals: {actualRentals}\n" +
                $"Lower Estimate: {lowerEstimate}\n" +
                $"Forecast: {estimate}\n" +
                $"Upper Estimate: {upperEstimate}\n";
            });
    
  4. 迭代处理预测输出并将其显示在控制台上。

    Console.WriteLine("Rental Forecast");
    Console.WriteLine("---------------------");
    foreach (var prediction in forecastOutput)
    {
        Console.WriteLine(prediction);
    }
    

运行应用程序

  1. 下面调用 Checkpoint() 该方法调用该方法 Forecast

    Forecast(secondYearData, 7, forecastEngine, mlContext);
    
  2. 运行该应用程序。 类似于下面的输出应显示在控制台上。 为了简洁起见,输出已压缩。

    Evaluation Metrics
    ---------------------
    Mean Absolute Error: 726.416
    Root Mean Squared Error: 987.658
    
    Rental Forecast
    ---------------------
    Date: 1/1/2012
    Actual Rentals: 2294
    Lower Estimate: 1197.842
    Forecast: 2334.443
    Upper Estimate: 3471.044
    
    Date: 1/2/2012
    Actual Rentals: 1951
    Lower Estimate: 1148.412
    Forecast: 2360.861
    Upper Estimate: 3573.309
    

检查实际值和预测值会显示以下关系:

实际与预测比较

虽然预测的数值无法准确预测租赁数量,但它们提供了更窄的数值范围,从而帮助运营优化资源使用。

祝贺! 现已成功构建时序机器学习模型来预测自行车租赁需求。

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

后续步骤