Tutoriel : Prévoir la demande pour un service de location de vélos avec une analyse de série chronologique et ML.NET

Découvrez comment prévoir la demande pour un service de location de vélos en utilisant une analyse de série chronologique univariée sur des données stockées dans une base de données SQL Server avec ML.NET.

Dans ce tutoriel, vous allez apprendre à :

  • Comprendre le problème
  • Charger les données depuis une base de données
  • Créer un modèle de prévision
  • Évaluer le modèle de prévision
  • Enregistrer un modèle de prévision
  • Utiliser un modèle de prévision

Prérequis

  • Visual Studio 2022 avec la charge de travail « Développement .NET Desktop » installée.

Vue d’ensemble de l’exemple de prévision de série chronologique

Cet exemple est une application console C# .NET Core qui prévoit la demande pour des locations de vélos en utilisant un algorithme d’analyse de série chronologique univariée, connu sous le nom de « Analyse spectrale singulière ». Vous trouverez le code de cet exemple dans le dépôt dotnet/machinelearning-samples sur GitHub.

Comprendre le problème

Pour obtenir un fonctionnement efficace, la gestion des stocks joue un rôle clé. Avoir une trop grande quantité d’un produit en stock signifie que les produits invendus stockés sur les étagères ne génèrent aucun revenu. Le fait d’avoir trop peu de produits entraîne une perte des ventes et des clients achetant auprès de concurrents. Par conséquent, la question constante est de savoir quelle est la quantité optimale de stock à garder sous la main ? L’analyse de séries chronologiques permet d’apporter une réponse à ces questions en examinant les données historiques, en identifiant les modèles et en utilisant ces informations pour prévoir des valeurs dans le futur.

La technique d’analyse des données utilisée dans ce didacticiel est l’analyse de séries chronologiques univariées. L’analyse des séries chronologiques univariées examine une seule observation numérique sur une période de temps, à des intervalles spécifiques, comme les ventes mensuelles.

L’algorithme utilisé dans ce tutoriel est Analyse spectrale singulière (SSA, Singular Spectrum Analysis). SSA fonctionne en décomposant une série chronologique en un ensemble de composants principaux. Ces composants peuvent être interprétés comme les parties d’un signal qui correspondent aux tendances, au bruit, à la saisonnalité et de nombreux autres facteurs. Ensuite, ces composants sont reconstruits et utilisés pour prévoir des valeurs dans le futur.

Création d’une application de console

  1. Créez une application console C# appelée « BikeDemandForecasting ». Cliquez sur le bouton Suivant.

  2. Choisissez .NET 6 comme framework à utiliser. Cliquez sur le bouton Créer.

  3. Installer le package NuGet de la version de Microsoft.ML

    Notes

    Cet exemple utilise la dernière version stable des packages NuGet mentionnés, sauf indication contraire.

    1. Dans l'Explorateur de solutions, cliquez avec le bouton droit sur votre projet, puis sélectionnez Gérer les packages NuGet.
    2. Choisissez « nuget.org » comme source du package, sélectionnez l’onglet Parcourir et recherchez Microsoft.ML.
    3. Cochez la case Inclure la préversion.
    4. Sélectionnez le bouton Installer.
    5. Cliquez sur le bouton OK dans la boîte de dialogue Aperçu des modifications, puis sur le bouton J’accepte dans la boîte de dialogue Acceptation de la licence si vous acceptez les termes du contrat de licence pour les packages répertoriés.
    6. Répétez ces étapes pour System.Data.SqlClient et Microsoft.ML.TimeSeries.

Préparer et comprendre les données

  1. Créez un répertoire appelé Data.
  2. Téléchargez le fichier de base de données DailyDemand.mdf et enregistrez-le dans le répertoire Data.

Notes

Les données utilisées dans ce tutoriel proviennent du jeu de données UCI Bike Sharing. Fanaee-T, Hadi, and Gama, Joao, « Event labeling combining ensemble detectors and background knowledge », Progress in Artificial Intelligence (2013): pp. 1-15, Springer Berlin Heidelberg, Lien web.

Le jeu de données d’origine contient plusieurs colonnes correspondant à la saisonnalité et à la météo. Par souci de concision et étant donné que l’algorithme utilisé dans ce tutoriel nécessite seulement les valeurs d’une colonne numérique, le jeu de données d’origine a été condensé pour inclure seulement les colonnes suivantes :

  • dteday : la date de l’observation.
  • year : l’année codée de l’observation (0=2011, 1=2012).
  • cnt : le nombre total de locations de vélos pour cette journée.

Le jeu de données d’origine est mappé à une table de base de données avec le schéma suivant dans une base de données SQL Server.

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

Voici un exemple de ces données :

RentalDate Year TotalRentals
1/1/2011 0 985
2/1/2011 0 801
3/1/2011 0 1349

Créer des classes d’entrée et de sortie

  1. Ouvrez le fichier Program.cs et remplacez les instructions using existantes par ceci :

    using Microsoft.ML;
    using Microsoft.ML.Data;
    using Microsoft.ML.Transforms.TimeSeries;
    using System.Data.SqlClient;
    
  2. Créez la classe ModelInput. Sous la classe Program, ajoutez le code suivant.

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

    La classe ModelInput contient les colonnes suivantes :

    • RentalDate : la date de l’observation.
    • Year : l’année codée de l’observation (0=2011, 1=2012).
    • TotalRentals : le nombre total de locations de vélos pour cette journée.
  3. Créez la classe ModelOutput sous la classe ModelInput nouvellement créée.

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

    La classe ModelOutput contient les colonnes suivantes :

    • ForecastedRentals : les valeurs prédites pour la période qui fait l’objet des prévisions.
    • LowerBoundRentals : les valeurs minimales prédites pour la période qui fait l’objet des prévisions.
    • UpperBoundRentals : les valeurs maximales prédites pour la période qui fait l’objet des prévisions.

Définir des chemins et initialiser des variables

  1. Sous les instructions using, définissez des variables pour stocker l’emplacement de vos données, la chaîne de connexion et où enregistrer le modèle entraîné.

    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. Initialisez la variable mlContext avec une nouvelle instance de MLContext en ajoutant la ligne suivante après avoir défini les chemins.

    MLContext mlContext = new MLContext();
    

    La classe MLContext est un point de départ pour toutes les opérations de ML.NET, et l’initialisation de mlContext crée un environnement ML.NET qui peut être partagé par les objets du workflow de création du modèle. Sur le plan conceptuel, elle est similaire à DBContext dans Entity Framework.

Chargement des données

  1. Créez DatabaseLoader, qui charge des enregistrements de type ModelInput.

    DatabaseLoader loader = mlContext.Data.CreateDatabaseLoader<ModelInput>();
    
  2. Définissez la requête pour charger les données depuis la base de données.

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

    Les algorithmes de ML.NET s’attendent à ce que les données soient de type Single. Par conséquent, les valeurs numériques provenant de la base de données qui ne sont pas de type Real, une valeur à virgule flottante simple précision, doivent être converties en Real.

    Les colonnes Year et TotalRental sont toutes deux de type entier dans la base de données. En utilisant la fonction intégrée CAST, elles sont toutes deux castées en Real.

  3. Créez une DatabaseSource pour vous connecter à la base de données et exécutez la requête.

    DatabaseSource dbSource = new DatabaseSource(SqlClientFactory.Instance,
                                    connectionString,
                                    query);
    
  4. Chargez les données dans un IDataView.

    IDataView dataView = loader.Load(dbSource);
    
  5. Le jeu de données contient deux années de données. Seules les données de la première année sont utilisées pour l’entraînement ; la deuxième année est destinée à comparer les valeurs réelles avec les prévisions produites par le modèle. Filtrez les données en utilisant la transformation FilterRowsByColumn.

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

    Pour la première année, seules les valeurs de la colonne Year inférieures à 1 sont sélectionnées en définissant le paramètre upperBound sur 1. À l’inverse, pour la deuxième année, les valeurs supérieures ou égales à 1 sont sélectionnées en définissant le paramètre lowerBound sur 1.

Définir le pipeline d’analyse de la série chronologique

  1. Définissez un pipeline qui utilise le SsaForecastingEstimator pour prévoir des valeurs dans un jeu de données de série chronologique.

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

    Le forecastingPipeline prend 365 points de données pour la première année, et échantillonne ou fractionne le jeu de données de série chronologique en intervalles de 30 jours (mensuels), comme spécifié par le paramètre seriesLength. Chacun de ces échantillons est analysé à travers une fenêtre hebdomadaire de 7 jours. Lors de la détermination de la valeur prévue pour la ou les périodes suivantes, les valeurs des sept jours précédents sont utilisées pour faire une prédiction. Le modèle est défini pour prévoir sept périodes dans le futur, comme défini par le paramètre horizon. Comme une prévision est une estimation éclairée, elle n’est pas toujours précise à 100 %. Par conséquent, il est intéressant de connaître la plage de valeurs dans les scénarios les meilleurs et les pires, tels que définis par les limites supérieure et inférieure. Dans le cas présent, le niveau de confiance pour les limites inférieure et supérieure est défini sur 95 %. Le niveau de confiance peut être augmenté ou diminué en conséquence. Plus la valeur est élevée, plus la plage est large entre les limites supérieure et inférieure pour atteindre le niveau de confiance souhaité.

  2. Utilisez la méthode Fit pour entraîner le modèle et ajuster les données au forecastingPipeline défini précédemment.

    SsaForecastingTransformer forecaster = forecastingPipeline.Fit(firstYearData);
    

Évaluer le modèle

Évaluez les performances du modèle en prévoyant les données de l’année suivante et en les comparant aux valeurs réelles.

  1. Créez une méthode utilitaire appelée Evaluate dans le bas du fichier Program.cs.

    Evaluate(IDataView testData, ITransformer model, MLContext mlContext)
    {
    
    }
    
  2. À l’intérieur de la méthode Evaluate, prévoyez les données de la deuxième année en utilisant la méthode Transform avec le modèle entraîné.

    IDataView predictions = model.Transform(testData);
    
  3. Obtenez les valeurs réelles des données en utilisant la méthode CreateEnumerable.

    IEnumerable<float> actual =
        mlContext.Data.CreateEnumerable<ModelInput>(testData, true)
            .Select(observed => observed.TotalRentals);
    
  4. Obtenez les valeurs des prévisions en utilisant la méthode CreateEnumerable.

    IEnumerable<float> forecast =
        mlContext.Data.CreateEnumerable<ModelOutput>(predictions, true)
            .Select(prediction => prediction.ForecastedRentals[0]);
    
  5. Calculez la différence entre les valeurs réelles et les valeurs prévues, couramment appelée « erreur ».

    var metrics = actual.Zip(forecast, (actualValue, forecastValue) => actualValue - forecastValue);
    
  6. Mesurez la performance en calculant les valeurs de Erreur absolue moyenne et Erreur quadratique moyenne.

    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
    

    Pour évaluer la performance, les métriques suivantes sont utilisées :

    • Erreur absolue moyenne : mesure la proximité des prédictions par rapport à la valeur réelle. Cette valeur est comprise entre 0 et l’infini. Plus la valeur est proche de 0, meilleure est la qualité du modèle.
    • Erreur quadratique moyenne : résume l’erreur dans le modèle. Cette valeur est comprise entre 0 et l’infini. Plus la valeur est proche de 0, meilleure est la qualité du modèle.
  7. Affichez les métriques sur la console.

    Console.WriteLine("Evaluation Metrics");
    Console.WriteLine("---------------------");
    Console.WriteLine($"Mean Absolute Error: {MAE:F3}");
    Console.WriteLine($"Root Mean Squared Error: {RMSE:F3}\n");
    
  8. Appelez la méthode Evaluate sous l’appel de la méthode Fit().

    Evaluate(secondYearData, forecaster, mlContext);
    

Enregistrer le modèle

Si vous êtes satisfait de votre modèle, enregistrez-le pour une utilisation ultérieure dans d’autres applications.

  1. Sous la méthode Evaluate(), créez un TimeSeriesPredictionEngine. TimeSeriesPredictionEngine est une méthode pratique pour faire des prédictions uniques.

    var forecastEngine = forecaster.CreateTimeSeriesEngine<ModelInput, ModelOutput>(mlContext);
    
  2. Enregistrez le modèle dans un fichier appelé MLModel.zip, comme spécifié par la variable modelPath précédemment définie. Utilisez la méthode Checkpoint pour enregistrer le modèle.

    forecastEngine.CheckPoint(mlContext, modelPath);
    

Utiliser le modèle pour prévoir la demande

  1. Sous la méthode Evaluate, créez une méthode utilitaire appelée Forecast.

    void Forecast(IDataView testData, int horizon, TimeSeriesPredictionEngine<ModelInput, ModelOutput> forecaster, MLContext mlContext)
    {
    
    }
    
  2. À l’intérieur de la méthode Forecast, utilisez la méthode Predict pour prévoir les locations pour les sept prochains jours.

    ModelOutput forecast = forecaster.Predict();
    
  3. Alignez les valeurs réelles et les valeurs prévues pour sept périodes.

    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. Effectuez une itération dans la sortie de la prévision et affichez-la sur la console.

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

Exécution de l'application

  1. Sous l’appel de la méthode Checkpoint(), appelez la méthode Forecast.

    Forecast(secondYearData, 7, forecastEngine, mlContext);
    
  2. Exécutez l’application. Une sortie similaire à celle ci-dessous doit apparaître sur la console. Par souci de concision, la sortie a été condensée.

    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
    

L’inspection des valeurs réelles et prévues montre les relations suivantes :

Comparaison des données réelles et des prévisions

Bien que les valeurs prévues ne prédisent pas le nombre exact de locations, elles fournissent une plage de valeurs plus étroite qui permet aux opérationnels d’optimiser leur utilisation des ressources.

Félicitations ! Vous avez maintenant créé avec succès un modèle Machine Learning de série chronologique pour prévoir la demande de location de vélos.

Le code source de ce tutoriel est disponible dans le dépôt dotnet/machinelearning-samples.

Étapes suivantes