Partilhar via


Tutorial: Previsão da procura por serviços de aluguer de bicicletas com análise de séries temporais e ML.NET

Aprenda como prever a procura por um serviço de aluguer de bicicletas usando análise univariada de séries temporais em dados armazenados numa base de dados SQL Server com ML.NET.

Neste tutorial, aprenderás como:

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

Pré-requisitos

Visão geral do exemplo de previsão de séries temporais

Este exemplo é uma aplicação de consola C# que prevê a procura por aluguer de bicicletas usando um algoritmo de análise de séries temporais univariadas conhecido como Análise de Espectro Singular. O código deste exemplo pode ser encontrado no repositório dotnet/machinelearning-samples no GitHub.

Entenda o problema

Para gerir uma operação eficiente, a gestão de inventário desempenha um papel fundamental. Ter um produto a mais em stock significa que os produtos não vendidos ficam nas prateleiras sem gerar receita. Ter produto insuficiente leva à perda de vendas e à compra de clientes a concorrentes. Portanto, a questão constante é: qual é a quantidade ótima de inventário para manter disponível? A análise de séries temporais ajuda a fornecer uma resposta a estas questões, analisando dados históricos, identificando padrões e utilizando essa informação para prever valores para algum momento futuro.

A técnica para analisar dados utilizada neste tutorial é a análise univariada de séries temporais. A análise de séries temporais univariadas 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 é a Análise do Espectro Singular (SSA). A SSA funciona decompondo 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. Depois, estes componentes são reconstruídos e usados para prever valores em algum momento futuro.

Criar aplicação de consola

  1. Crie uma aplicação de consola C# chamada "BikeDemandForecasting". Clique no botão Seguinte.

  2. Escolhe o .NET 8 como framework a usar. Clique no botão Criar .

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

    Observação

    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 no seu projeto e selecione Gerir Pacotes NuGet.
    2. Escolhe "nuget.org" como fonte do Pacote, seleciona o separador Explorar , pesquisa por Microsoft.ML.
    3. Assinala a opção Incluir pré-lançamento .
    4. Selecione o botão Instalar.
    5. Selecione o botão OK na janela de Pré-visualização de Alterações e depois selecione o botão Aceitar na janela de Aceitação de Licença se concordar com os termos da licença dos pacotes listados.
    6. Repita estes passos para System.Data.SqlClient e Microsoft.ML.TimeSeries.

Prepare e compreenda os dados

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

Observação

Os dados usados neste tutorial provêm do Conjunto de Dados de Partilha de Bicicletas da UCI. Hadi Fanaee-T e João Gama, 'Rotulagem de eventos que combina detetores de conjunto e conhecimento de fundo', Progress in Artificial Intelligence (2013): pp. 1-15, Springer Berlin Heidelberg, Web Link.

O conjunto de dados original contém várias colunas correspondentes à sazonalidade e ao clima. Para brevidade e porque o algoritmo utilizado neste tutorial requer apenas 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 bicicletas alugadas nesse 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 uma amostra dos dados:

Data de Aluguer Ano 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 Program.cs ficheiro e substitua as diretivas existentes using pelas seguintes:

    using Microsoft.ML;
    using Microsoft.ML.Data;
    using Microsoft.ML.Transforms.TimeSeries;
    using System.Data.SqlClient;
    
  2. Cria ModelInput classe. Por baixo da Program turma, 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:

    • Data de Observação: 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 nesse dia.
  3. Crie ModelOutput uma classe abaixo da nova classe 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:

    • Previsões de Alugueres: Os valores previstos para o período de previsão.
    • LowerLimitsRentals: 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 using diretivas definem variáveis para armazenar a localização dos seus dados, cadeia de ligação e onde guardar o modelo treinado.

    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 adicionando a linha seguinte após definir os caminhos.

    MLContext mlContext = new MLContext();
    

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

Carregar os dados

  1. Cria 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. Portanto, valores numéricos provenientes da base de dados que não são do tipo Real, um valor de ponto flutuante de precisão simples, têm de ser convertidos para Real.

    As Year colunas e TotalRental são ambos tipos inteiros na base de dados. Usando a função incorporada CAST, ambos são convertidos para Real.

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

    DatabaseSource dbSource = new DatabaseSource(SqlClientFactory.Instance,
                                    connectionString,
                                    query);
    
  4. Carregue os dados num IDataViewarquivo .

    IDataView dataView = loader.Load(dbSource);
    
  5. O conjunto de dados contém dois anos de dados. Apenas os dados do primeiro ano são usados para o treino, o segundo ano é reservado para comparar os valores reais com a previsão produzida pelo modelo. Filtra os dados usando a FilterRowsByColumn transformada.

    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 Year coluna inferior a 1 são selecionados definindo o upperBound parâmetro para 1. Por outro lado, para o segundo ano, valores maiores ou iguais a 1 são selecionados definindo o lowerBound parâmetro como 1.

Definir fluxo de análise de séries temporais

  1. Defina um pipeline que utiliza o SsaForecastingEstimator para prever valores num conjunto de dados de séries temporais.

    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 pontos de dados para o primeiro ano e amostra ou divide o conjunto de dados de séries temporais em intervalos mensais de 30 dias, conforme especificado pelo parâmetro seriesLength. Cada uma destas amostras é analisada numa janela semanal ou de 7 dias. Ao determinar qual é o valor previsto para o(s) próximo(s) período(s), os valores dos sete dias anteriores são usados para fazer uma previsão. O modelo está configurado para prever sete períodos no futuro, conforme definido pelo horizon parâmetro. Como uma previsão é um palpite informado, nem sempre é 100% precisa. Por isso, é 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 é definido para 95%. O nível de confiança pode ser aumentado ou diminuído em conformidade. Quanto maior for o valor, maior é o intervalo entre os limites superior e inferior para atingir o nível desejado de confiança.

  2. Use o Fit método para treinar o modelo e ajuste os dados ao .forecastingPipeline

    SsaForecastingTransformer forecaster = forecastingPipeline.Fit(firstYearData);
    

Avaliar o modelo

Avalie o desempenho do modelo, prevendo os dados do próximo ano e comparando-os com os valores reais.

  1. Crie um novo método utilitário chamado Evaluate no final do ficheiro Program.cs .

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

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

    IEnumerable<float> actual =
        mlContext.Data.CreateEnumerable<ModelInput>(testData, true)
            .Select(observed => observed.TotalRentals);
    
  4. Obtenha os valores das previsões usando 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, comumente referidos como o erro.

    var metrics = actual.Zip(forecast, (actualValue, forecastValue) => actualValue - forecastValue);
    
  6. Mede o desempenho calculando os valores do Erro Absoluto Médio e do Erro Quadrático Médio 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 usadas as seguintes métricas:

    • Erro Absoluto Médio: Mede quão próximas estão as previsões do valor real. Este valor varia entre 0 e infinito. Quanto mais próximo de 0, melhor a qualidade do modelo.
    • Erro Quadrático Médio Raiz: Resume o erro no modelo. Este valor varia entre 0 e infinito. Quanto mais próximo de 0, melhor a qualidade do modelo.
  7. Envia 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. Chama o Evaluate método abaixo a chamar o Fit() método.

    Evaluate(secondYearData, forecaster, mlContext);
    

Guardar o modelo

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

  1. Abaixo do Evaluate() método, crie um TimeSeriesPredictionEngine. TimeSeriesPredictionEngine é um método de conveniência para fazer previsões únicas.

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

    forecastEngine.CheckPoint(mlContext, modelPath);
    

Use 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. No Forecast processo, utilize-o Predict para prever as rendas para os próximos sete dias.

    ModelOutput forecast = forecaster.Predict();
    
  3. Alinhe os valores reais e previstos para 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. Percorrer o resultado da previsão e mostrá-lo na consola.

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

Execute o aplicativo

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

    Forecast(secondYearData, 7, forecastEngine, mlContext);
    
  2. Execute o aplicativo. Uma saída semelhante à abaixo deverá aparecer na consola. Para ser breve, o resultado foi condensado.

    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 prevejam o número exato de arrendamentos, fornecem uma gama mais restrita de valores que permite a uma operação otimizar a utilização dos recursos.

Parabéns! Agora construiu com sucesso um modelo de aprendizagem automática em séries temporais para prever a procura de aluguer de bicicletas.

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

Próximos passos