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
Créez une application console C# appelée « BikeDemandForecasting ». Cliquez sur le bouton Suivant.
Choisissez .NET 6 comme framework à utiliser. Cliquez sur le bouton Créer.
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.
- Dans l'Explorateur de solutions, cliquez avec le bouton droit sur votre projet, puis sélectionnez Gérer les packages NuGet.
- Choisissez « nuget.org » comme source du package, sélectionnez l’onglet Parcourir et recherchez Microsoft.ML.
- Cochez la case Inclure la préversion.
- Sélectionnez le bouton Installer.
- 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.
- Répétez ces étapes pour System.Data.SqlClient et Microsoft.ML.TimeSeries.
Préparer et comprendre les données
- Créez un répertoire appelé Data.
- 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
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;
Créez la classe
ModelInput
. Sous la classeProgram
, 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.
Créez la classe
ModelOutput
sous la classeModelInput
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
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;";
Initialisez la variable
mlContext
avec une nouvelle instance deMLContext
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
Créez
DatabaseLoader
, qui charge des enregistrements de typeModelInput
.DatabaseLoader loader = mlContext.Data.CreateDatabaseLoader<ModelInput>();
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 typeReal
, une valeur à virgule flottante simple précision, doivent être converties enReal
.Les colonnes
Year
etTotalRental
sont toutes deux de type entier dans la base de données. En utilisant la fonction intégréeCAST
, elles sont toutes deux castées enReal
.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);
Chargez les données dans un
IDataView
.IDataView dataView = loader.Load(dbSource);
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ètreupperBound
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ètrelowerBound
sur 1.
Définir le pipeline d’analyse de la série chronologique
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ètreseriesLength
. 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ètrehorizon
. 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é.Utilisez la méthode
Fit
pour entraîner le modèle et ajuster les données auforecastingPipeline
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.
Créez une méthode utilitaire appelée
Evaluate
dans le bas du fichier Program.cs.Evaluate(IDataView testData, ITransformer model, MLContext mlContext) { }
À l’intérieur de la méthode
Evaluate
, prévoyez les données de la deuxième année en utilisant la méthodeTransform
avec le modèle entraîné.IDataView predictions = model.Transform(testData);
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);
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]);
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);
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.
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");
Appelez la méthode
Evaluate
sous l’appel de la méthodeFit()
.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.
Sous la méthode
Evaluate()
, créez unTimeSeriesPredictionEngine
.TimeSeriesPredictionEngine
est une méthode pratique pour faire des prédictions uniques.var forecastEngine = forecaster.CreateTimeSeriesEngine<ModelInput, ModelOutput>(mlContext);
Enregistrez le modèle dans un fichier appelé
MLModel.zip
, comme spécifié par la variablemodelPath
précédemment définie. Utilisez la méthodeCheckpoint
pour enregistrer le modèle.forecastEngine.CheckPoint(mlContext, modelPath);
Utiliser le modèle pour prévoir la demande
Sous la méthode
Evaluate
, créez une méthode utilitaire appeléeForecast
.void Forecast(IDataView testData, int horizon, TimeSeriesPredictionEngine<ModelInput, ModelOutput> forecaster, MLContext mlContext) { }
À l’intérieur de la méthode
Forecast
, utilisez la méthodePredict
pour prévoir les locations pour les sept prochains jours.ModelOutput forecast = forecaster.Predict();
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"; });
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
Sous l’appel de la méthode
Checkpoint()
, appelez la méthodeForecast
.Forecast(secondYearData, 7, forecastEngine, mlContext);
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 :
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.