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

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

  1. Cree una aplicación de consola en C# llamada "BikeDemandForecasting". Haga clic en el botón Next (Siguiente).

  2. Seleccione .NET 6 como marco de trabajo que va a usarse. Haga clic en el botón Crear.

  3. 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.

    1. En el Explorador de soluciones, haga clic con el botón derecho en Administrar paquetes NuGet.
    2. Elija "nuget.org" como origen del paquete, seleccione la pestaña Examinar y busque Microsoft.ML.
    3. Active la casilla Incluir versión preliminar.
    4. Seleccione el botón Instalar.
    5. 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.
    6. Repita estos pasos para System.Data.SqlClient y Microsoft.ML.TimeSeries.

Preparar y entender los datos

  1. Cree un directorio denominado Data.
  2. 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

  1. 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;
    
  2. Cree la clase ModelInput. Debajo de la clase Program, 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.
  3. Cree la clase ModelOutput debajo de la clase ModelInput 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

  1. 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;";
    
  2. Inicialice la variable mlContext con una instancia nueva de MLContext 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 a DBContext en Entity Framework.

Carga de los datos

  1. Cree DatabaseLoader que cargue los registros de tipo ModelInput.

    DatabaseLoader loader = mlContext.Data.CreateDatabaseLoader<ModelInput>();
    
  2. 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 tipo Real, un valor de punto flotante de precisión sencilla, se deben convertir en Real.

    Las columnas Year y TotalRental son tipos enteros en la base de datos. Con la función integrada CAST, ambos se convierten en Real.

  3. Cree un DatabaseSource para conectarse a la base de datos y ejecutar la consulta.

    DatabaseSource dbSource = new DatabaseSource(SqlClientFactory.Instance,
                                    connectionString,
                                    query);
    
  4. Cargue los datos en un IDataView.

    IDataView dataView = loader.Load(dbSource);
    
  5. 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ámetro upperBound en 1. A la inversa, para el segundo año, se seleccionan los valores mayores o iguales que 1 al establecer el parámetro lowerBound en 1.

Definición de la canalización de análisis de serie temporal

  1. 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ámetro seriesLength. 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ámetro horizon. 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.

  2. Use el método Fit para entrenar el modelo y ajustar los datos a la forecastingPipeline 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.

  1. Cree un método de utilidad llamado Evaluate al final del archivo Program.cs.

    Evaluate(IDataView testData, ITransformer model, MLContext mlContext)
    {
    
    }
    
  2. Dentro del método Evaluate, prevea los datos del segundo año a través del método Transform con el modelo entrenado.

    IDataView predictions = model.Transform(testData);
    
  3. 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);
    
  4. 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]);
    
  5. 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);
    
  6. 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.
  7. 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");
    
  8. Llame al método Evaluate debajo de la llamada al método Fit().

    Evaluate(secondYearData, forecaster, mlContext);
    

Guardado del modelo

Si está satisfecho con el modelo, guárdelo para usarlo más adelante en otras aplicaciones.

  1. Debajo del método Evaluate(), cree un elemento TimeSeriesPredictionEngine. TimeSeriesPredictionEngine es un método útil para hacer predicciones únicas.

    var forecastEngine = forecaster.CreateTimeSeriesEngine<ModelInput, ModelOutput>(mlContext);
    
  2. Guarde el modelo en un archivo denominado MLModel.zip, según lo especificado en la variable modelPath definida anteriormente. Use el método Checkpoint para guardar el modelo.

    forecastEngine.CheckPoint(mlContext, modelPath);
    

Uso del modelo para pronosticar la demanda

  1. Debajo del método Evaluate, cree un método de utilidad denominado Forecast.

    void Forecast(IDataView testData, int horizon, TimeSeriesPredictionEngine<ModelInput, ModelOutput> forecaster, MLContext mlContext)
    {
    
    }
    
  2. En el método Forecast, use el método Predict para pronosticar los alquileres durante los próximos siete días.

    ModelOutput forecast = forecaster.Predict();
    
  3. 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";
            });
    
  4. 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

  1. Debajo de la llamada al método Checkpoint(), llame al método Forecast.

    Forecast(secondYearData, 7, forecastEngine, mlContext);
    
  2. 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:

Comparación de valores reales y valores previstos

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.

Pasos siguientes