教學課程:使用時間序列分析和 ML.NET 預測自行車出租服務需求

瞭解如何使用單變數時間序列分析,針對儲存在具有 ML.NET 之SQL Server資料庫中的資料,預測自行車出租服務的需求。

在本教學課程中,您會了解如何:

  • 了解問題
  • 從資料庫載入資料
  • 建立預測模型
  • 評估預測模型
  • 儲存預測模型
  • 使用預測模型

必要條件

時間序列預測樣本概觀

此範例是 C# .NET Core 主控台應用程式 ,可預測自行車租用的需求,其使用稱為單一頻譜分析的單變數時間序列分析演算法。 您可以在 GitHub 的 dotnet/machinelearning-samples 存放庫找到此樣本的程式碼。

了解問題

為了確保作業執行的效率,庫存管理扮演重要角色。 若一個產品的庫存過多表示在貨架上未售出的產品無法產生任何收益。 產品太少會導致失去銷售機會,客戶向競爭對手購買產品。 因此,不變的問題在於,最適當的庫存數量為何? 時間序列分析可藉由檢視歷史資料、識別模式,以及使用此資訊來預測未來某個時間的值,提供這個問題的答案。

本教學課程中用來分析資料的技術是單變量時間序列分析。 單變量時間序列分析會在特定間隔查看單一數值觀察,例如每月銷售額。

本教學課程使用的演算法是單一頻譜分析 (SSA)。 SSA 的功用是將時間序列分解成一組主體元件。 這些元件可以解譯為與趨勢、雜訊、季節性和許多其他因素對應的訊號部分, 然後,這些元件會重新建構,並在未來一段時間用來預測值。

建立主控台應用程式

  1. 建立名為「BikeDemandForecasting」的 C# 主控台應用程式。 按 [下一步] 按鈕。

  2. 選擇 .NET 6 作為要使用的架構。 按一下 [ 建立 ] 按鈕。

  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 和 Joao Gama 共同撰寫的'Event labeling combining ensemble detectors and background knowledge', Progress in Artificial Intelligence (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
2011/1/1 0 985
2011/2/1 0 801
2011/3/1 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:觀察的日期。
    • Year:觀察的編碼年份 (0=2011、1=2012)。
    • TotalRentals:該日的自行車出租總數。
  3. 在新建立的 ModelInput 類別下方建立 ModelOutput 類別。

    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,來選取小於 1 的 Year 資料行值。 相反地,在第二年,會藉由將 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 參數所定義,系統會將此模型設為預測未來七個期間。 預測是根據資訊所做的猜測,所以不一定 100% 正確, 因此,最好知道上限和下限所定義之最佳和最差案例情況中的值範圍。 在這個情況下,下限和上限的信賴等級會設為 95%。 信賴等級可據以增加或減少。 值越高,上限和下限之間的範圍就越寬,以達到所需的信賴等級。

  2. 使用 Fit 方法,將模型定型,並調整資料使其符合先前定義的 forecastingPipeline

    SsaForecastingTransformer forecaster = forecastingPipeline.Fit(firstYearData);
    

評估模型

藉由預測下一年的資料,並將其與實際值比較,來評估模型的執行效能。

  1. Program.cs 檔案底部建立名為 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. 將模型儲存至先前定義的 modelPath 變數所指定的 MLModel.zip 檔案。 使用 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 存放庫中找到本教學課程的原始程式碼。

下一步