Tutorial: Previsión de la demanda de servicio de alquiler de bicicletas con análisis de serie temporal y ML.NET
Obtenga información sobre cómo prever la demanda de un servicio de alquiler de bicicletas mediante el análisis de serie temporal de variable única en los datos almacenados en una base de datos SQL Server con ML.NET.
En este tutorial aprenderá a:
- Entender el problema
- Cargar datos desde una base de datos
- Crear un modelo de previsión
- Evaluar un modelo de previsión
- Guardar un modelo de previsión
- Usar un modelo de previsión
Requisitos previos
- Visual Studio 2022 con la carga de trabajo "Desarrollo de escritorio de .NET" instalada.
Información general de un ejemplo de previsión de serie temporal
Este ejemplo es una aplicación de consola de .NET Core de C# que prevé la demanda de alquileres de bicicletas con un algoritmo de análisis de serie temporal de variable única conocido como "análisis de un solo espectro". El código de este ejemplo se puede encontrar en el repositorio dotnet/machinelearning-samples en GitHub.
Entender el problema
Para ejecutar una operación eficaz, la administración de inventario desempeña un rol clave. Tener en existencia una cantidad excesiva de un producto significa que los productos no vendidos que se encuentran en las estanterías no generan ingresos. Tener una cantidad reducida de un producto hace que se pierdan ventas y que los clientes compren a la competencia. Por lo tanto, la pregunta constante es ¿cuál es la cantidad óptima de inventario que debe mantenerse a mano? El análisis de serie temporal lo ayuda a proporcionar una respuesta a estas preguntas examinando los datos históricos, identificando patrones y usando esta información para prever valores en el futuro.
La técnica para analizar los datos que usa este tutorial es el análisis de serie temporal de variable única. El análisis de serie temporal de variante única examina una única observación numérica durante un período de tiempo a intervalos específicos, como las ventas mensuales.
El algoritmo que se usa en este tutorial es el análisis de un solo espectro (SSA). SSA sirve para descomponer una serie temporal en un conjunto de componentes principales. Estos componentes se pueden interpretar como partes de una señal que corresponde a tendencias, ruido, estacionalidad y muchos otros factores. Después, estos componentes se reconstruyen y se usan para pronosticar valores en el futuro.
Creación de una aplicación de consola
Cree una aplicación de consola en C# llamada "BikeDemandForecasting". Haga clic en el botón Next (Siguiente).
Seleccione .NET 6 como marco de trabajo que va a usarse. Haga clic en el botón Crear.
Instale el paquete NuGet versión Microsoft.ML.
Nota
En este ejemplo se usa la versión estable más reciente de los paquetes NuGet mencionados, a menos que se indique lo contrario.
- En el Explorador de soluciones, haga clic con el botón derecho en Administrar paquetes NuGet.
- Elija "nuget.org" como origen del paquete, seleccione la pestaña Examinar y busque Microsoft.ML.
- Active la casilla Incluir versión preliminar.
- Seleccione el botón Instalar.
- Seleccione el botón Aceptar en el cuadro de diálogo Vista previa de cambios y, a continuación, seleccione el botón Acepto del cuadro de diálogo Aceptación de la licencia en caso de que esté de acuerdo con los términos de licencia de los paquetes mostrados.
- Repita estos pasos para System.Data.SqlClient y Microsoft.ML.TimeSeries.
Preparar y entender los datos
- Cree un directorio denominado Data.
- Descargue el archivo de base de datos DailyDemand.mdf y guárdelo en el directorio Data.
Nota
Los datos que se usan en este tutorial provienen del conjunto de datos UCI Bike Sharing Dataset. 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, vínculo web.
El conjunto de datos original contiene varias columnas correspondientes a la estacionalidad y al clima. Con el fin de ser breves y como el algoritmo que se usa en este tutorial solo requiere los valores de una columna numérica única, el conjunto de datos original se ha condensado para incluir solo las columnas siguientes:
- dteday: la fecha de la observación.
- year: el año codificado de la observación (0=2011, 1=2012).
- cnt: el número total de alquileres de bicicletas para ese día.
El conjunto de datos original se asigna a una tabla de base de datos con el esquema siguiente en una base de datos de SQL Server.
CREATE TABLE [Rentals] (
[RentalDate] DATE NOT NULL,
[Year] INT NOT NULL,
[TotalRentals] INT NOT NULL
);
A continuación se muestra un ejemplo de los datos:
RentalDate | Año | TotalRentals |
---|---|---|
1/1/2011 | 0 | 985 |
1/2/2011 | 0 | 801 |
1/3/2011 | 0 | 1349 |
Creación de las clases de entrada y salida
Abra el archivo Program.cs y reemplace las instrucciones
using
existentes por las siguientes:using Microsoft.ML; using Microsoft.ML.Data; using Microsoft.ML.Transforms.TimeSeries; using System.Data.SqlClient;
Cree la clase
ModelInput
. Debajo de la claseProgram
, agregue el código siguiente.public class ModelInput { public DateTime RentalDate { get; set; } public float Year { get; set; } public float TotalRentals { get; set; } }
La clase
ModelInput
contiene las columnas siguientes:- RentalDate: la fecha de la observación.
- Year: el año codificado de la observación (0=2011, 1=2012).
- TotalRentals: el número total de alquileres de bicicletas para ese día.
Cree la clase
ModelOutput
debajo de la claseModelInput
recién creada.public class ModelOutput { public float[] ForecastedRentals { get; set; } public float[] LowerBoundRentals { get; set; } public float[] UpperBoundRentals { get; set; } }
La clase
ModelOutput
contiene las columnas siguientes:- ForecastedRentals: los valores de predicción del período previsto.
- LowerBoundRentals: los valores mínimos de predicción del período previsto.
- UpperBoundRentals: los valores máximos de predicción del período previsto.
Definición de rutas de acceso e inicialización de variables
Debajo de las instrucciones using, defina variables para almacenar la ubicación de los datos, la cadena de conexión y dónde guardar el modelo entrenado.
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;";
Inicialice la variable
mlContext
con una instancia nueva deMLContext
mediante la incorporación de la línea siguiente después de definir las rutas de acceso.MLContext mlContext = new MLContext();
La clase
MLContext
es un punto inicial para todas las operaciones de ML.NET. Al inicializar mlContext, se crea un entorno de ML.NET que se puede compartir entre los objetos del flujo de trabajo de creación de modelos. Como concepto, se parece aDBContext
en Entity Framework.
Carga de los datos
Cree
DatabaseLoader
que cargue los registros de tipoModelInput
.DatabaseLoader loader = mlContext.Data.CreateDatabaseLoader<ModelInput>();
Defina la consulta para cargar los datos de la base de datos.
string query = "SELECT RentalDate, CAST(Year as REAL) as Year, CAST(TotalRentals as REAL) as TotalRentals FROM Rentals";
Los algoritmos de ML.NET esperan que los datos sean de tipo
Single
. Por lo tanto, los valores numéricos que provienen de la base de datos que no son de tipoReal
, un valor de punto flotante de precisión sencilla, se deben convertir enReal
.Las columnas
Year
yTotalRental
son tipos enteros en la base de datos. Con la función integradaCAST
, ambos se convierten enReal
.Cree un
DatabaseSource
para conectarse a la base de datos y ejecutar la consulta.DatabaseSource dbSource = new DatabaseSource(SqlClientFactory.Instance, connectionString, query);
Cargue los datos en un
IDataView
.IDataView dataView = loader.Load(dbSource);
El conjunto de datos contiene datos de dos años. Solo se usan los datos del primer año para el entrenamiento, el segundo año se mantiene para comparar los valores reales con el pronóstico generado por el modelo. Filtre los datos con la transformación
FilterRowsByColumn
.IDataView firstYearData = mlContext.Data.FilterRowsByColumn(dataView, "Year", upperBound: 1); IDataView secondYearData = mlContext.Data.FilterRowsByColumn(dataView, "Year", lowerBound: 1);
Para el primer año, se seleccionan solo los valores de la columna
Year
menores que 1 al establecer el parámetroupperBound
en 1. A la inversa, para el segundo año, se seleccionan los valores mayores o iguales que 1 al establecer el parámetrolowerBound
en 1.
Definición de la canalización de análisis de serie temporal
Defina una canalización que use SsaForecastingEstimator para pronosticar valores en un conjunto de datos de serie temporal.
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
toma 365 puntos de datos para el primer año y muestrea o divide el conjunto de datos de serie temporal en intervalos de 30 días (mensualmente) según lo especificado en el parámetroseriesLength
. Cada uno de estos ejemplos se analiza durante una ventana semanal o de 7 días. A la hora de determinar cuál es el valor de previsión para el período siguiente, se usan los valores de los siete días anteriores para realizar una predicción. El modelo se establece para pronosticar siete períodos en el futuro, tal como se define en el parámetrohorizon
. Como una previsión es una estimación informada, no siempre es 100 precisa. Por lo tanto, es conveniente conocer el intervalo de valores en los mejores y peores escenarios, tal como se define en los límites superior e inferior. En este caso, el nivel de confianza de los límites inferior y superior se establece en 95 %. El nivel de confianza se puede aumentar o reducir en consecuencia. Cuanto mayor sea el valor, más amplio será el intervalo entre los límites superior e inferior para lograr el nivel de confianza deseado.Use el método
Fit
para entrenar el modelo y ajustar los datos a laforecastingPipeline
definida previamente.SsaForecastingTransformer forecaster = forecastingPipeline.Fit(firstYearData);
Evaluar el modelo
Evalúe el rendimiento del modelo al prever los datos del año siguiente y comparándolos con los valores reales.
Cree un método de utilidad llamado
Evaluate
al final del archivo Program.cs.Evaluate(IDataView testData, ITransformer model, MLContext mlContext) { }
Dentro del método
Evaluate
, prevea los datos del segundo año a través del métodoTransform
con el modelo entrenado.IDataView predictions = model.Transform(testData);
Obtenga los valores reales de los datos a través del método
CreateEnumerable
.IEnumerable<float> actual = mlContext.Data.CreateEnumerable<ModelInput>(testData, true) .Select(observed => observed.TotalRentals);
Use el método
CreateEnumerable
para obtener los valores de previsión.IEnumerable<float> forecast = mlContext.Data.CreateEnumerable<ModelOutput>(predictions, true) .Select(prediction => prediction.ForecastedRentals[0]);
Calcule la diferencia entre los valores reales y los de previsión, lo que se conoce a menudo como "error".
var metrics = actual.Zip(forecast, (actualValue, forecastValue) => actualValue - forecastValue);
Para medir el rendimiento, calcule los valores de error medio absoluto y medio y error cuadrático medio.
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
Para evaluar el rendimiento, se usan las métricas siguientes:
- Error medio absoluto: mide la cercanía de las predicciones respecto del valor real. Este valor va de 0 a infinito. Cuanto más se acerque a 0, mejor es la calidad del modelo.
- Error cuadrático medio: resume el error del modelo. Este valor va de 0 a infinito. Cuanto más se acerque a 0, mejor es la calidad del modelo.
Genere las métricas en la consola.
Console.WriteLine("Evaluation Metrics"); Console.WriteLine("---------------------"); Console.WriteLine($"Mean Absolute Error: {MAE:F3}"); Console.WriteLine($"Root Mean Squared Error: {RMSE:F3}\n");
Llame al método
Evaluate
debajo de la llamada al métodoFit()
.Evaluate(secondYearData, forecaster, mlContext);
Guardado del modelo
Si está satisfecho con el modelo, guárdelo para usarlo más adelante en otras aplicaciones.
Debajo del método
Evaluate()
, cree un elementoTimeSeriesPredictionEngine
.TimeSeriesPredictionEngine
es un método útil para hacer predicciones únicas.var forecastEngine = forecaster.CreateTimeSeriesEngine<ModelInput, ModelOutput>(mlContext);
Guarde el modelo en un archivo denominado
MLModel.zip
, según lo especificado en la variablemodelPath
definida anteriormente. Use el métodoCheckpoint
para guardar el modelo.forecastEngine.CheckPoint(mlContext, modelPath);
Uso del modelo para pronosticar la demanda
Debajo del método
Evaluate
, cree un método de utilidad denominadoForecast
.void Forecast(IDataView testData, int horizon, TimeSeriesPredictionEngine<ModelInput, ModelOutput> forecaster, MLContext mlContext) { }
En el método
Forecast
, use el métodoPredict
para pronosticar los alquileres durante los próximos siete días.ModelOutput forecast = forecaster.Predict();
Alinee los valores reales y los valores de previsión durante siete períodos.
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"; });
Recorra en iteración la salida del pronóstico y muéstrela en la consola.
Console.WriteLine("Rental Forecast"); Console.WriteLine("---------------------"); foreach (var prediction in forecastOutput) { Console.WriteLine(prediction); }
Ejecutar la aplicación
Debajo de la llamada al método
Checkpoint()
, llame al métodoForecast
.Forecast(secondYearData, 7, forecastEngine, mlContext);
Ejecute la aplicación. En la consola debería aparecer una salida similar a la siguiente. Por motivos de brevedad, la salida se ha resumido.
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
La inspección de los valores reales y previstos muestra las relaciones siguientes:
Aunque los valores pronosticados no predicen el número exacto de alquileres, sí proporcionan un intervalo más acotado de valores que permite que una operación optimice el uso de los recursos.
¡Enhorabuena! Creó correctamente un modelo de Machine Learning de serie temporal para predecir la demanda de alquileres de bicicletas.
Puede encontrar el código fuente para este tutorial en el repositorio dotnet/machinelearning-samples.