共用方式為


教學:透過時間序列分析與 ML.NET 預測自行車租賃服務需求

學習如何利用 ML.NET 和 SQL Server 資料庫中的資料,使用單變量時間序列分析來預測自行車租賃服務的需求。

在本教學課程中,您將瞭解如何:

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

先決條件

時間序列預測範例概述

此範例是一個 C# 控制台應用程式 ,利用一種稱為奇異頻譜分析(Singular Spectrum Analysis)的單變量時間序列分析演算法,預測自行車租賃需求。 這個範例的程式碼可以在 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頁,柏林施普林出版社, 網頁連結

原始數據集包含多個欄位,對應於季節性和天氣。 為了簡潔起見,且本教學所用演算法僅需單一數值欄位的數值,原始資料集已濃縮為僅包含以下欄位:

  • dteday:觀察的日期。
  • 年份:觀測的編碼年份(0=2011,1=2012)。
  • CNT:當天的自行車租賃總數。

原始資料集會映射到SQL Server資料庫中的資料庫資料表,結構如下。

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

以下是部分資料:

租約日期 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 類別包含以下欄位:

    • 租賃日期:此日期為觀察日期。
    • 年份:觀測的編碼年份(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 類別包含以下欄位:

    • 預測租金:預測期間的預測值。
    • 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);
    

    第一年度,僅選取 Year 欄中的值小於1的情況下,將參數 upperBound 設為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 。 因為預測是有根據的猜測,並不總是百% 準確。 因此,了解最佳與最壞情況中由上下界定義的值範圍是很重要的。 此時,下界與上界的信心水準設為 95%。 信心水準可以相應地增加或降低。 數值越高,上下界之間的區間越寬,以達到所需的信心水準。

  2. 使用該 Fit 方法訓練模型並將資料擬合至先前定義 forecastingPipeline的 。

    SsaForecastingTransformer forecaster = forecastingPipeline.Fit(firstYearData);
    

評估模型

透過預測明年的數據與實際數值比較,評估模型的表現。

  1. 建立一個新的工具方法,名稱 EvaluateProgram.cs 檔案底部。

    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 資料庫找到這個教學的原始碼。

後續步驟