Samouczek: prognozowanie zapotrzebowania na usługi wypożyczania rowerów za pomocą analizy szeregów czasowych i ML.NET

Dowiedz się, jak prognozować zapotrzebowanie na usługę wypożyczania rowerów przy użyciu jednowariowej analizy szeregów czasowych na danych przechowywanych w bazie danych SQL Server z ML.NET.

Ten samouczek zawiera informacje na temat wykonywania następujących czynności:

  • Omówienie problemu
  • Ładowanie danych z bazy danych
  • Tworzenie modelu prognozowania
  • Ocena modelu prognozowania
  • Zapisywanie modelu prognozowania
  • Korzystanie z modelu prognozowania

Wymagania wstępne

Omówienie przykładu prognozowania szeregów czasowych

Ten przykład jest aplikacją konsolową platformy .NET Core w języku C# , która prognozuje zapotrzebowanie na wynajem rowerów przy użyciu jednowariowego algorytmu analizy szeregów czasowych znanego jako Analiza pojedynczego spektrum. Kod dla tego przykładu można znaleźć w repozytorium dotnet/machinelearning-samples w witrynie GitHub.

Omówienie problemu

Aby można było uruchomić wydajną operację, zarządzanie zapasami odgrywa kluczową rolę. Posiadanie zbyt dużej ilości produktu w magazynie oznacza, że produkty niesprzedazone siedzą na półkach, nie generując żadnych przychodów. Zbyt mało produktów prowadzi do utraty sprzedaży i klientów kupujących od konkurentów. W związku z tym stałe pytanie brzmi, jaka jest optymalna ilość zapasów, aby utrzymać się pod ręką? Analiza szeregów czasowych pomaga zapewnić odpowiedź na te pytania, przeglądając dane historyczne, identyfikując wzorce i używając tych informacji do prognozowania wartości w przyszłości.

Technika analizowania danych używanych w tym samouczku polega na niezmiennej analizie szeregów czasowych. Pojedyncza analiza szeregów czasowych analizuje pojedynczą obserwację liczbową w danym okresie w określonych odstępach czasu, takich jak miesięczna sprzedaż.

Algorytm używany w tym samouczku to Pojedyncza analiza spektrum (SSA). SSA działa przez rozdzielenie szeregów czasowych na zestaw głównych składników. Te składniki można interpretować jako części sygnału odpowiadającego trendom, szumowi, sezonowości i wielu innym czynnikom. Następnie te składniki są odtwarzane i używane do prognozowania wartości w przyszłości.

Tworzenie aplikacji konsolowej

  1. Utwórz aplikację konsolową języka C# o nazwie "BikeDemandForecasting". Kliknij przycisk Dalej.

  2. Wybierz platformę .NET 6 jako platformę do użycia. Kliknij przycisk Utwórz.

  3. Instalowanie pakietu NuGet w wersji Microsoft.ML

    Uwaga

    W tym przykładzie użyto najnowszej stabilnej wersji pakietów NuGet wymienionych, chyba że określono inaczej.

    1. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt i wybierz polecenie Zarządzaj pakietami NuGet.
    2. Wybierz pozycję "nuget.org" jako źródło pakietu, wybierz kartę Przeglądaj , wyszukaj Microsoft.ML.
    3. Zaznacz pole wyboru Dołącz wersję wstępną .
    4. Wybierz przycisk Zainstaluj .
    5. Wybierz przycisk OK w oknie dialogowym Podgląd zmian , a następnie wybierz przycisk Akceptuję w oknie dialogowym Akceptacja licencji, jeśli zgadzasz się z postanowieniami licencyjnymi dla wymienionych pakietów.
    6. Powtórz te kroki dla programów System.Data.SqlClient i Microsoft.ML.TimeSeries.

Przygotowywanie i zrozumienie danych

  1. Utwórz katalog o nazwie Dane.
  2. Pobierz plik bazy danych DailyDemand.mdf i zapisz go w katalogu Data.

Uwaga

Dane używane w tym samouczku pochodzą z zestawu danych UCI Bike Sharing. Fanaee-T, Hadi i Gama, Joao, "Etykietowanie zdarzeń łączących detektory zespołu i wiedzę w tle", Progress in Artificial Intelligence (2013): pp. 1-15, Springer Berlin Berlin, Web Link.

Oryginalny zestaw danych zawiera kilka kolumn odpowiadających sezonowości i pogodzie. W przypadku zwięzłości i ponieważ algorytm używany w tym samouczku wymaga tylko wartości z jednej kolumny liczbowej, oryginalny zestaw danych został skrócony w celu uwzględnienia tylko następujących kolumn:

  • dteday: Data obserwacji.
  • year: zakodowany rok obserwacji (0=2011, 1=2012).
  • cnt: Całkowita liczba wypożyczeń rowerów dla tego dnia.

Oryginalny zestaw danych jest mapowany na tabelę bazy danych z następującym schematem w bazie danych SQL Server.

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

Poniżej przedstawiono przykład danych:

Wypożyczenie Year (Rok) TotalRentals
1/1/2011 0 985
1/2/2011 0 801
1/3/2011 0 1349

Tworzenie klas wejściowych i wyjściowych

  1. Otwórz plik Program.cs i zastąp istniejące using instrukcje następującymi instrukcjami:

    using Microsoft.ML;
    using Microsoft.ML.Data;
    using Microsoft.ML.Transforms.TimeSeries;
    using System.Data.SqlClient;
    
  2. Utwórz klasę ModelInput. Program Poniżej klasy dodaj następujący kod.

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

    Klasa ModelInput zawiera następujące kolumny:

    • RentalDate: data obserwacji.
    • Rok: zakodowany rok obserwacji (0=2011, 1=2012).
    • TotalRentals: Całkowita liczba wypożyczeń rowerów dla tego dnia.
  3. Utwórz ModelOutput klasę poniżej nowo utworzonej ModelInput klasy.

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

    Klasa ModelOutput zawiera następujące kolumny:

    • ForecastedRentals: przewidywane wartości dla prognozowanego okresu.
    • LowerBoundRentals: przewidywane wartości minimalne dla prognozowanego okresu.
    • UpperBoundRentals: przewidywane wartości maksymalne dla prognozowanego okresu.

Definiowanie ścieżek i inicjowanie zmiennych

  1. Poniżej instrukcji using zdefiniuj zmienne do przechowywania lokalizacji danych, parametrów połączenia i miejsca zapisania wytrenowanego modelu.

    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. Zainicjuj mlContext zmienną przy użyciu nowego wystąpienia MLContext , dodając następujący wiersz po zdefiniowaniu ścieżek.

    MLContext mlContext = new MLContext();
    

    Klasa MLContext jest punktem wyjścia dla wszystkich operacji ML.NET, a inicjowanie metody mlContext tworzy nowe środowisko ML.NET, które może być współużytkowane przez obiekty przepływu pracy tworzenia modelu. Jest ona podobna, koncepcyjnie, do DBContext w programie Entity Framework.

Ładowanie danych

  1. Utwórz DatabaseLoader , że ładuje rekordy typu ModelInput.

    DatabaseLoader loader = mlContext.Data.CreateDatabaseLoader<ModelInput>();
    
  2. Zdefiniuj zapytanie, aby załadować dane z bazy danych.

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

    ML.NET algorytmy oczekują, że dane będą typu Single. W związku z tym wartości liczbowe pochodzące z bazy danych, które nie są typu Real, należy przekonwertować wartość zmiennoprzecinkową o pojedynczej precyzji na Realwartość .

    Kolumny Year i TotalRental są zarówno typami całkowitymi w bazie danych. Za pomocą wbudowanej CAST funkcji są one rzutowane na Real.

  3. Utwórz element , DatabaseSource aby nawiązać połączenie z bazą danych i wykonać zapytanie.

    DatabaseSource dbSource = new DatabaseSource(SqlClientFactory.Instance,
                                    connectionString,
                                    query);
    
  4. Załaduj dane do elementu IDataView.

    IDataView dataView = loader.Load(dbSource);
    
  5. Zestaw danych zawiera dwa lata wartości danych. Tylko dane z pierwszego roku są używane do trenowania, drugi rok jest przechowywany w celu porównania rzeczywistych wartości z prognozą wygenerowaną przez model. Przefiltruj FilterRowsByColumn dane przy użyciu przekształcenia.

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

    W pierwszym roku wybierane są tylko wartości w Year kolumnie mniejszej niż 1, ustawiając upperBound parametr na 1. Z drugiej strony dla drugiego roku wartości większe lub równe 1 są wybierane przez ustawienie parametru lowerBound na 1.

Definiowanie potoku analizy szeregów czasowych

  1. Zdefiniuj potok, który używa klasy SsaForecastingEstimator do prognozowania wartości w zestawie danych szeregów czasowych.

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

    Obiekt forecastingPipeline przyjmuje 365 punktów danych dla pierwszego roku i próbki lub dzieli zestaw danych szeregów czasowych na 30-dniowe (miesięczne) interwały określone przez seriesLength parametr . Każda z tych próbek jest analizowana przez cotygodniowe lub 7-dniowe okno. Podczas określania, jaka jest prognozowana wartość dla następnych okresów, wartości z poprzednich siedmiu dni są używane do przewidywania. Model jest ustawiony na prognozowanie siedmiu okresów w przyszłości zgodnie z definicją parametru horizon . Ponieważ prognoza jest świadomym zgadywaniem, nie zawsze jest to 100% dokładne. W związku z tym dobrze jest znać zakres wartości w najlepszych i najgorszych scenariuszach, zgodnie z definicją w granicach górnych i dolnych. W tym przypadku poziom ufności dla dolnych i górnych granic wynosi 95%. Poziom ufności można odpowiednio zwiększyć lub zmniejszyć. Im wyższa wartość, tym szerszy zakres znajduje się między górną i dolną granicą w celu osiągnięcia żądanego poziomu ufności.

  2. Fit Użyj metody , aby wytrenować model i dopasować dane do wcześniej zdefiniowanego forecastingPipelineelementu .

    SsaForecastingTransformer forecaster = forecastingPipeline.Fit(firstYearData);
    

Ocena modelu

Oceń, jak dobrze działa model, prognozując dane z przyszłego roku i porównując je z rzeczywistymi wartościami.

  1. Utwórz nową metodę narzędzia o nazwie Evaluate w dolnej części pliku Program.cs .

    Evaluate(IDataView testData, ITransformer model, MLContext mlContext)
    {
    
    }
    
  2. Evaluate Wewnątrz metody prognozowanie danych z drugiego roku przy użyciu Transform metody z wytrenowanym modelem.

    IDataView predictions = model.Transform(testData);
    
  3. Pobierz rzeczywiste wartości z danych przy użyciu CreateEnumerable metody .

    IEnumerable<float> actual =
        mlContext.Data.CreateEnumerable<ModelInput>(testData, true)
            .Select(observed => observed.TotalRentals);
    
  4. Pobierz wartości prognozy przy użyciu CreateEnumerable metody .

    IEnumerable<float> forecast =
        mlContext.Data.CreateEnumerable<ModelOutput>(predictions, true)
            .Select(prediction => prediction.ForecastedRentals[0]);
    
  5. Oblicz różnicę między wartościami rzeczywistymi i prognozami, często określanymi jako błąd.

    var metrics = actual.Zip(forecast, (actualValue, forecastValue) => actualValue - forecastValue);
    
  6. Mierzenie wydajności przez obliczanie wartości Błędu bezwzględnego średniej i średniej kwadratu głównego.

    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
    

    Aby ocenić wydajność, używane są następujące metryki:

    • Średni błąd bezwzględny: mierzy, jak bliskie są przewidywania rzeczywistej wartości. Ta wartość waha się od 0 do nieskończoności. Im bliżej 0, tym lepsza jakość modelu.
    • Główny błąd średniokwadratowy: podsumowuje błąd w modelu. Ta wartość waha się od 0 do nieskończoności. Im bliżej 0, tym lepsza jakość modelu.
  7. Wyprowadź metryki do konsoli.

    Console.WriteLine("Evaluation Metrics");
    Console.WriteLine("---------------------");
    Console.WriteLine($"Mean Absolute Error: {MAE:F3}");
    Console.WriteLine($"Root Mean Squared Error: {RMSE:F3}\n");
    
  8. Wywołaj metodę Evaluate poniżej wywołującą metodę Fit() .

    Evaluate(secondYearData, forecaster, mlContext);
    

Zapisywanie modelu

Jeśli model jest zadowalający, zapisz go do późniejszego użycia w innych aplikacjach.

  1. Evaluate() Poniżej metody utwórz element TimeSeriesPredictionEngine. TimeSeriesPredictionEngine jest wygodną metodą tworzenia pojedynczych przewidywań.

    var forecastEngine = forecaster.CreateTimeSeriesEngine<ModelInput, ModelOutput>(mlContext);
    
  2. Zapisz model w pliku o nazwie MLModel.zip określonej przez wcześniej zdefiniowaną modelPath zmienną. Checkpoint Użyj metody , aby zapisać model.

    forecastEngine.CheckPoint(mlContext, modelPath);
    

Prognozowanie zapotrzebowania przy użyciu modelu

  1. Evaluate Poniżej metody utwórz nową metodę narzędzia o nazwie Forecast.

    void Forecast(IDataView testData, int horizon, TimeSeriesPredictionEngine<ModelInput, ModelOutput> forecaster, MLContext mlContext)
    {
    
    }
    
  2. Forecast Wewnątrz metody użyj Predict metody , aby prognozować wypożyczenie przez następne siedem dni.

    ModelOutput forecast = forecaster.Predict();
    
  3. Wyrównuj wartości rzeczywiste i prognozowane dla siedmiu okresów.

    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. Iteruj dane wyjściowe prognozy i wyświetlaj je w konsoli.

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

Uruchamianie aplikacji

  1. Poniżej wywołania Checkpoint() metody wywołaj metodę Forecast .

    Forecast(secondYearData, 7, forecastEngine, mlContext);
    
  2. Uruchom aplikację. Dane wyjściowe podobne do poniższych powinny pojawić się w konsoli programu . W celu zwięzłości dane wyjściowe zostały skondensowane.

    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
    

Inspekcja rzeczywistych i prognozowanych wartości pokazuje następujące relacje:

Porównanie wartości rzeczywistych i prognozowanych

Chociaż prognozowane wartości nie przewidują dokładnej liczby wypożyczeń, zapewniają bardziej wąski zakres wartości, które umożliwiają operację optymalizacji wykorzystania zasobów.

Gratulacje! Udało Ci się utworzyć model uczenia maszynowego szeregów czasowych w celu prognozowania zapotrzebowania na wypożyczanie rowerów.

Kod źródłowy tego samouczka można znaleźć w repozytorium dotnet/machinelearning-samples .

Następne kroki