Tutorial: Previsão da procura do serviço de aluguer de bicicletas com análise de série temporal e ML.NET

Saiba como prever a procura de um serviço de aluguer de bicicletas com uma análise de série temporal nãoiva em dados armazenados numa base de dados SQL Server com ML.NET.

Neste tutorial, ficará a saber como:

  • Compreender o problema
  • Carregar dados a partir de uma base de dados
  • Criar um modelo de previsão
  • Avaliar modelo de previsão
  • Guardar um modelo de previsão
  • Utilizar um modelo de previsão

Pré-requisitos

Descrição geral do exemplo de previsão de série temporal

Este exemplo é uma aplicação de consola .NET Core C# que prevê a procura de alugueres de bicicletas através de um algoritmo de análise de série temporal indisiva conhecido como Singular Spectrum Analysis. O código para este exemplo pode ser encontrado no repositório dotnet/machinelearning-samples no GitHub.

Compreender o problema

Para executar uma operação eficiente, a gestão de inventário desempenha uma função fundamental. Ter demasiado produto em stock significa que os produtos não vendidos nas prateleiras não geram receitas. Ter pouco produto leva à perda de vendas e aos clientes que compram a concorrentes. Portanto, a pergunta constante é: qual é a quantidade ideal de inventário a manter disponível? A análise de séries temporizadas ajuda a fornecer uma resposta a estas perguntas ao analisar dados históricos, identificar padrões e utilizar estas informações para prever valores no futuro.

A técnica para analisar os dados utilizados neste tutorial é a análise de série temporal inivariada. A análise de série temporal nãoivada analisa uma única observação numérica ao longo de um período de tempo em intervalos específicos, como vendas mensais.

O algoritmo utilizado neste tutorial é Singular Spectrum Analysis(SSA). O SSA funciona ao decompor uma série temporal num conjunto de componentes principais. Estes componentes podem ser interpretados como as partes de um sinal que correspondem a tendências, ruído, sazonalidade e muitos outros fatores. Em seguida, estes componentes são reconstruídos e utilizados para prever valores no futuro.

Criar aplicação da consola

  1. Crie uma Aplicação de Consola C# denominada "BikeDemandForecasting". Clique no botão Seguinte .

  2. Selecione .NET 6 como a arquitetura a utilizar. Clique no botão Criar.

  3. Instalar Microsoft.ML versão do pacote NuGet

    Nota

    Este exemplo utiliza a versão estável mais recente dos pacotes NuGet mencionados, salvo indicação em contrário.

    1. No Explorador de Soluções, clique com o botão direito do rato no projeto e selecione Gerir Pacotes NuGet.
    2. Selecione "nuget.org" como origem do pacote, selecione o separador Procurar, procure Microsoft.ML.
    3. Selecione a caixa de verificação Incluir pré-lançamento .
    4. Selecione o botão Instalar .
    5. Selecione o botão OK na caixa de diálogo Pré-visualizar Alterações e, em seguida, selecione o botão Aceito na caixa de diálogo Aceitação da Licença se concordar com os termos de licença dos pacotes listados.
    6. Repita estes passos para System.Data.SqlClient e Microsoft.ML.TimeSeries.

Preparar e compreender os dados

  1. Crie um diretório denominado Dados.
  2. Transfira o ficheiro de base de dados DailyDemand.mdf e guarde-o no diretório Dados.

Nota

Os dados utilizados neste tutorial são provenientes do Conjunto de Dados de Partilha de Bicicletas UCI. Fanaee-T, Hadi e Gama, João, "Etiquetagem de eventos que combina detetores de conjuntos e conhecimentos de fundo", Progresso na Inteligência Artificial (2013): pp. 1-15, Springer Berlin Heidelberg, Web Link.

O conjunto de dados original contém várias colunas correspondentes à sazonalidade e às condições meteorológicas. Para a brevidade e porque o algoritmo utilizado neste tutorial apenas requer os valores de uma única coluna numérica, o conjunto de dados original foi condensado para incluir apenas as seguintes colunas:

  • dteday: a data da observação.
  • ano: o ano codificado da observação (0=2011, 1=2012).
  • cnt: O número total de alugueres de bicicletas para esse dia.

O conjunto de dados original é mapeado para uma tabela de base de dados com o seguinte esquema numa base de dados SQL Server.

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

Segue-se um exemplo dos dados:

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

Criar classes de entrada e saída

  1. Abra o ficheiro Program.cs e substitua as instruções existentes using pelo seguinte:

    using Microsoft.ML;
    using Microsoft.ML.Data;
    using Microsoft.ML.Transforms.TimeSeries;
    using System.Data.SqlClient;
    
  2. Crie a classe ModelInput. Abaixo da Program classe, adicione o seguinte código.

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

    A ModelInput classe contém as seguintes colunas:

    • RentalDate: a data da observação.
    • Ano: o ano codificado da observação (0=2011, 1=2012).
    • TotalRentals: o número total de alugueres de bicicletas para esse dia.
  3. Crie ModelOutput a classe abaixo da classe recentemente criada ModelInput .

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

    A ModelOutput classe contém as seguintes colunas:

    • ForecastedRentals: os valores previstos para o período previsto.
    • LowerBoundRentals: os valores mínimos previstos para o período previsto.
    • UpperBoundRentals: os valores máximos previstos para o período previsto.

Definir caminhos e inicializar variáveis

  1. Abaixo das instruções de utilização, defina variáveis para armazenar a localização dos seus dados, cadeia de ligação e onde guardar o modelo preparado.

    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. Inicialize a mlContext variável com uma nova instância de MLContext ao adicionar a seguinte linha depois de definir os caminhos.

    MLContext mlContext = new MLContext();
    

    A MLContext classe é um ponto de partida para todas as operações de ML.NET e inicializar mlContext cria um novo ambiente de ML.NET que pode ser partilhado entre os objetos de fluxo de trabalho de criação de modelos. É semelhante, conceptualmente, a DBContext no Entity Framework.

Carregar os dados

  1. Criar DatabaseLoader que carrega registos do tipo ModelInput.

    DatabaseLoader loader = mlContext.Data.CreateDatabaseLoader<ModelInput>();
    
  2. Defina a consulta para carregar os dados da base de dados.

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

    ML.NET algoritmos esperam que os dados sejam do tipo Single. Por conseguinte, os valores numéricos provenientes da base de dados que não são do tipo Real, um valor de vírgula flutuante de precisão única, têm de ser convertidos em Real.

    As Year colunas e TotalRental são ambos tipos inteiros na base de dados. Com a CAST função incorporada, ambos são fundidos em Real.

  3. Crie um DatabaseSource para ligar à base de dados e executar a consulta.

    DatabaseSource dbSource = new DatabaseSource(SqlClientFactory.Instance,
                                    connectionString,
                                    query);
    
  4. Carregue os dados para um IDataView.

    IDataView dataView = loader.Load(dbSource);
    
  5. O conjunto de dados contém dois anos de dados. Apenas são utilizados dados do primeiro ano para preparação, sendo que o segundo ano é mantido para comparar os valores reais com a previsão produzida pelo modelo. Filtre os dados com a transformação FilterRowsByColumn .

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

    No primeiro ano, apenas os valores na coluna inferior a Year 1 são selecionados ao definir o upperBound parâmetro como 1. Por outro lado, para o segundo ano, os valores maiores ou iguais a 1 são selecionados ao definir o lowerBound parâmetro como 1.

Definir o pipeline de análise de série temporal

  1. Defina um pipeline que utiliza o SsaForecastingEstimator para prever valores num conjunto de dados de série 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");
    

    O forecastingPipeline utiliza 365 pontos de dados para o primeiro ano e exemplos ou divide o conjunto de dados da série temporal em intervalos de 30 dias (mensais), conforme especificado pelo seriesLength parâmetro. Cada um destes exemplos é analisado através de uma janela semanal ou de 7 dias. Ao determinar qual é o valor previsto para o(s) período(s) seguinte, os valores dos sete dias anteriores são utilizados para fazer uma predição. O modelo está definido para prever sete períodos no futuro, conforme definido pelo horizon parâmetro. Como uma previsão é uma estimativa informada, nem sempre é 100% precisa. Por conseguinte, é bom conhecer o intervalo de valores nos melhores e piores cenários, conforme definido pelos limites superior e inferior. Neste caso, o nível de confiança para os limites inferior e superior está definido como 95%. O nível de confiança pode ser aumentado ou diminuído em conformidade. Quanto maior for o valor, maior será o intervalo entre os limites superior e inferior para alcançar o nível de confiança pretendido.

  2. Utilize o Fit método para preparar o modelo e ajustar os dados ao definido anteriormente forecastingPipeline.

    SsaForecastingTransformer forecaster = forecastingPipeline.Fit(firstYearData);
    

Avaliar o modelo

Avalie o desempenho do modelo ao prever os dados do próximo ano e compará-lo com os valores reais.

  1. Crie um novo método utilitário chamado Evaluate na parte inferior do ficheiro Program.cs .

    Evaluate(IDataView testData, ITransformer model, MLContext mlContext)
    {
    
    }
    
  2. Dentro do Evaluate método, preveja os dados do segundo ano com o Transform método com o modelo preparado.

    IDataView predictions = model.Transform(testData);
    
  3. Obtenha os valores reais dos dados com o CreateEnumerable método .

    IEnumerable<float> actual =
        mlContext.Data.CreateEnumerable<ModelInput>(testData, true)
            .Select(observed => observed.TotalRentals);
    
  4. Obtenha os valores de previsão com o CreateEnumerable método .

    IEnumerable<float> forecast =
        mlContext.Data.CreateEnumerable<ModelOutput>(predictions, true)
            .Select(prediction => prediction.ForecastedRentals[0]);
    
  5. Calcule a diferença entre os valores reais e previstos, normalmente referidos como o erro.

    var metrics = actual.Zip(forecast, (actualValue, forecastValue) => actualValue - forecastValue);
    
  6. Medir o desempenho ao calcular os valores Erro Absoluto Médio Absoluto e Erro Quadrado Médio de Raiz.

    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 avaliar o desempenho, são utilizadas as seguintes métricas:

    • Erro Absoluto Médio: mede a proximidade das predições com o valor real. Este valor varia entre 0 e infinito. Quanto mais perto de 0, melhor será a qualidade do modelo.
    • Erro Quadrado Médio de Raiz: resume o erro no modelo. Este valor varia entre 0 e infinito. Quanto mais perto de 0, melhor será a qualidade do modelo.
  7. Exportar as métricas para a consola.

    Console.WriteLine("Evaluation Metrics");
    Console.WriteLine("---------------------");
    Console.WriteLine($"Mean Absolute Error: {MAE:F3}");
    Console.WriteLine($"Root Mean Squared Error: {RMSE:F3}\n");
    
  8. Chame o Evaluate método abaixo ao chamar o Fit() método.

    Evaluate(secondYearData, forecaster, mlContext);
    

Guardar o modelo

Se estiver satisfeito com o seu modelo, guarde-o para utilização posterior noutras aplicações.

  1. Por baixo do Evaluate() método, crie um TimeSeriesPredictionEngine. TimeSeriesPredictionEngine é um método de conveniência para fazer predições individuais.

    var forecastEngine = forecaster.CreateTimeSeriesEngine<ModelInput, ModelOutput>(mlContext);
    
  2. Guarde o modelo num ficheiro chamado MLModel.zip conforme especificado pela variável definida modelPath anteriormente. Utilize o Checkpoint método para guardar o modelo.

    forecastEngine.CheckPoint(mlContext, modelPath);
    

Utilizar o modelo para prever a procura

  1. Por baixo do Evaluate método, crie um novo método utilitário chamado Forecast.

    void Forecast(IDataView testData, int horizon, TimeSeriesPredictionEngine<ModelInput, ModelOutput> forecaster, MLContext mlContext)
    {
    
    }
    
  2. Dentro do Forecast método, utilize o Predict método para prever alugueres para os próximos sete dias.

    ModelOutput forecast = forecaster.Predict();
    
  3. Alinhe os valores reais e previstos durante sete 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. Itera através do resultado da previsão e apresente-o na consola do .

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

Executar a aplicação

  1. Abaixo, chamar o Checkpoint() método chama o Forecast método.

    Forecast(secondYearData, 7, forecastEngine, mlContext);
    
  2. Execute a aplicação. O resultado semelhante ao apresentado abaixo deverá aparecer na consola do . Por questões de brevidade, a saída foi condensada.

    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
    

A inspeção dos valores reais e previstos mostra as seguintes relações:

Comparação Real vs. Previsão

Embora os valores previstos não estejam a prever o número exato de alugueres, fornecem um intervalo de valores mais estreito que permite que uma operação otimize a utilização dos recursos.

Parabéns! Criou com êxito um modelo de machine learning de série temporal para prever a procura de aluguer de bicicletas.

Pode encontrar o código fonte deste tutorial no repositório dotnet/machinelearning-samples .

Passos seguintes