Tutorial: prever a demanda de um serviço de aluguel de bicicletas com análises de série temporal e o ML.NET
Saiba como prever a demanda por um serviço de aluguel de bicicletas usando análises univariadas de série temporal nos dados armazenados em um banco de dados SQL Server com o ML.NET.
Neste tutorial, você aprenderá a:
- Compreender o problema
- Carregar dados de um banco de dados
- Criar um modelo de previsão
- Avaliar um modelo de previsão
- Salvar um modelo de previsão
- Usar um modelo de previsão
Pré-requisitos
- Visual Studio 2022 com a carga de trabalho "Desenvolvimento de área de trabalho do .NET" instalada.
Visão geral do exemplo de previsão de série temporal
Esse exemplo é um aplicativo de console do .NET Core em C# que prevê a demanda do aluguel de bicicletas usando um algoritmo de análise univariada de série temporal conhecido como análise de espectro singular. 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, o gerenciamento de estoque desempenha um papel fundamental. Ter muito de um produto em estoque significa que os produtos não vendidos nas prateleiras não geram receita. Ter pouco de um produto em estoque resulta em vendas perdidas e clientes que compram em concorrentes. Portanto, a pergunta constante é: qual é a quantidade ideal de estoque para manter disponível? A análise de série temporal ajuda a fornecer uma resposta para essas perguntas examinando dados históricos, identificando padrões e usando essas informações para prever valores em algum momento no futuro.
A técnica de análise de dados usada neste tutorial é a análise univariada de série temporal. A análise univariada de série temporal examina uma única observação numérica durante um período de tempo em intervalos específicos, como vendas mensais.
O algoritmo usado neste tutorial é SSA (análise de espectro singular). O algoritmo SSA funciona decompondo uma série temporal em um conjunto de componentes principais. Esses componentes podem ser interpretados como as partes de um sinal que correspondem a tendências, ruído, sazonalidade e muitos outros fatores. Em seguida, esses componentes são reconstruídos e usados para prever valores em algum momento no futuro.
Criar um aplicativo de console
Crie um aplicativo de console em C# chamado "BikeDemandForecasting". Clique no botão Avançar.
Escolha o .NET 6 como a estrutura a ser usada. Selecione o botão Criar.
Instale o pacote NuGet da versão Microsoft.ML
Observação
Este exemplo usa a versão estável mais recente dos pacotes NuGet mencionados, salvo indicação em contrário.
- No Gerenciador de Soluções, clique com o botão direito do mouse no seu projeto e selecione Gerenciar Pacotes NuGet.
- Escolha "nuget.org" como a origem do pacote, clique na guia Procurar e pesquise Microsoft.ML.
- Marque a caixa de seleção Incluir pré-lançamento.
- Selecione o botão Instalar.
- Selecione o botão OK na caixa de diálogo Visualizar Alterações e selecione o botão Aceito na caixa de diálogo Aceitação da Licença, se concordar com o termos de licença para os pacotes listados.
- Repita as etapas para System.Data.SqlClient e Microsoft.ML.TimeSeries.
Preparar e compreender os dados
- Crie um diretório chamado Data.
- Baixe o arquivo de banco de dados DailyDemand.mdf e salve-o no diretório Data.
Observação
Os dados usados neste tutorial são provenientes do conjunto de dados UCI Bike Sharing. Link da Web para "Event labeling combining ensemble detectors and background knowledge", de "Progress in Artificial Intelligence" (2013): páginas 1 a 15, Springer Berlin Heidelberg, por Hadi Fanaee-T e João Gama.
O conjunto de dados original contém diversas colunas correspondentes à sazonalidade e ao clima. Por questões de brevidade e porque o algoritmo usado neste tutorial requer somente os valores de uma única coluna numérica, o conjunto de dados original foi condensado para incluir somente as seguintes colunas:
- dteday: a data da observação.
- year: o ano codificado da observação (0=2011, 1=2012).
- cnt: o número total de aluguéis de bicicletas no dia.
O conjunto de dados original é mapeado para uma tabela de banco de dados com o esquema a seguir em um banco de dados SQL Server.
CREATE TABLE [Rentals] (
[RentalDate] DATE NOT NULL,
[Year] INT NOT NULL,
[TotalRentals] INT NOT NULL
);
Veja o seguinte exemplo dos dados:
RentalDate | Year | TotalRentals |
---|---|---|
1/1/2011 | 0 | 985 |
2/1/2011 | 0 | 801 |
3/1/2011 | 0 | 1349 |
Criar classes de entrada e saída
Abra o arquivo Program.cs e substitua as instruções
using
existentes pelo seguinte:using Microsoft.ML; using Microsoft.ML.Data; using Microsoft.ML.Transforms.TimeSeries; using System.Data.SqlClient;
Crie a classe
ModelInput
. Abaixo da classeProgram
, adicione o código a seguir.public class ModelInput { public DateTime RentalDate { get; set; } public float Year { get; set; } public float TotalRentals { get; set; } }
A classe
ModelInput
contém as seguintes colunas:- RentalDate: a data da observação.
- Year: o ano codificado da observação (0=2011, 1=2012).
- TotalRentals: o número total de aluguéis de bicicletas no dia.
Crie uma classe
ModelOutput
abaixo da classeModelInput
recém-criada.public class ModelOutput { public float[] ForecastedRentals { get; set; } public float[] LowerBoundRentals { get; set; } public float[] UpperBoundRentals { get; set; } }
A classe
ModelOutput
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
Abaixo das instruções de uso, defina variáveis para armazenar a localização de seus dados, a cadeia de conexão e onde salvar 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;";
Inicialize a variável
mlContext
com uma nova instância deMLContext
adicionando a linha a seguir depois de definir os caminhos.MLContext mlContext = new MLContext();
A classe
MLContext
é um ponto de partida para todas as operações do ML.NET, e a inicialização de mlContext cria um novo ambiente do ML.NET que pode ser compartilhado entre os objetos do fluxo de trabalho de criação de modelo. Ele é semelhante, conceitualmente, aDBContext
no Entity Framework.
Carregar os dados
Crie um
DatabaseLoader
que carregue os registros do tipoModelInput
.DatabaseLoader loader = mlContext.Data.CreateDatabaseLoader<ModelInput>();
Defina a consulta para carregar os dados do banco de dados.
string query = "SELECT RentalDate, CAST(Year as REAL) as Year, CAST(TotalRentals as REAL) as TotalRentals FROM Rentals";
Os algoritmos do ML.NET esperam que os dados sejam do tipo
Single
. Portanto, valores numéricos provenientes do banco de dados que não são do tipoReal
, um valor de ponto flutuante de precisão simples, devem ser convertidos emReal
.As colunas
Year
eTotalRental
são tipos inteiros no banco de dados. Usando a função internaCAST
, ambas são convertidas emReal
.Crie um
DatabaseSource
para se conectar ao banco de dados e executar a consulta.DatabaseSource dbSource = new DatabaseSource(SqlClientFactory.Instance, connectionString, query);
Carregar os dados em um
IDataView
.IDataView dataView = loader.Load(dbSource);
O conjunto de dados contém dois anos de dados. Somente os dados do primeiro ano são usados para o treinamento. O segundo ano é usado para comparar os valores reais com a previsão produzida pelo modelo. Filtre os dados usando a transformação
FilterRowsByColumn
.IDataView firstYearData = mlContext.Data.FilterRowsByColumn(dataView, "Year", upperBound: 1); IDataView secondYearData = mlContext.Data.FilterRowsByColumn(dataView, "Year", lowerBound: 1);
Para o primeiro ano, somente os valores na coluna
Year
menores que 1 são selecionados definindo o parâmetroupperBound
como 1. Por outro lado, para o segundo ano, os valores maiores ou iguais a 1 são selecionados definindo o parâmetrolowerBound
como 1.
Definir o pipeline da análise de série temporal
Defina um pipeline que use o SsaForecastingEstimator para prever valores em um 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 faz a amostragem ou divisão do conjunto de dados de série temporal em intervalos de 30 dias (mensais), conforme especificado pelo parâmetroseriesLength
. Cada uma dessas amostras é analisada em uma janela semanal ou de sete dias. Ao determinar qual é o valor previsto para os próximos períodos, os valores dos sete dias anteriores são usados para uma previsão. O modelo é definido para prever sete períodos no futuro, conforme definido pelo parâmetrohorizon
. Como uma previsão é um palpite informado, nem sempre ela é 100% precisa. Portanto, é bom conhecer o intervalo de valores nos cenários de melhor e pior caso, conforme definido pelos limites superior e inferior. Nesse caso, o nível de confiança para os limites inferior e superior é definido como 95%. O nível de confiança pode ser aumentado ou diminuído de acordo com o que é mais adequado. Quanto maior o valor, mais amplo é o intervalo entre os limites superior e inferior para atingir o nível de confiança desejado.Use o método
Fit
para treinar o modelo e ajustar os dados aoforecastingPipeline
previamente definido.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.
Crie um novo método de utilitário chamado
Evaluate
na parte inferior do arquivo Program.cs.Evaluate(IDataView testData, ITransformer model, MLContext mlContext) { }
Dentro do método
Evaluate
, preveja os dados do segundo ano usando o métodoTransform
com o modelo treinado.IDataView predictions = model.Transform(testData);
Obtenha os valores reais dos dados usando o método
CreateEnumerable
.IEnumerable<float> actual = mlContext.Data.CreateEnumerable<ModelInput>(testData, true) .Select(observed => observed.TotalRentals);
Obtenha os valores de previsão usando o método
CreateEnumerable
.IEnumerable<float> forecast = mlContext.Data.CreateEnumerable<ModelOutput>(predictions, true) .Select(prediction => prediction.ForecastedRentals[0]);
Calcule a diferença entre os valores reais e os previstos, comumente referida como o erro.
var metrics = actual.Zip(forecast, (actualValue, forecastValue) => actualValue - forecastValue);
Meça o desempenho calculando os valores de erro médio absoluto e raiz do erro quadrático médio.
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, as seguintes métricas são usadas:
- Erro médio absoluto: mede o quanto as previsões estão próximas do valor real. Este valor varia de 0 a infinito. Quanto mais próximo de 0, melhor a qualidade do modelo.
- Raiz do erro quadrático médio: resume o erro no modelo. Este valor varia de 0 a infinito. Quanto mais próximo de 0, melhor a qualidade do modelo.
Gere as métricas no console.
Console.WriteLine("Evaluation Metrics"); Console.WriteLine("---------------------"); Console.WriteLine($"Mean Absolute Error: {MAE:F3}"); Console.WriteLine($"Root Mean Squared Error: {RMSE:F3}\n");
Chame o método
Evaluate
abaixo chamando o métodoFit()
.Evaluate(secondYearData, forecaster, mlContext);
Salvar o modelo
Se estiver satisfeito com o modelo, salve-o para uso posterior em outros aplicativos.
Abaixo do método
Evaluate()
, crie umTimeSeriesPredictionEngine
. OTimeSeriesPredictionEngine
é um método de conveniência para fazer previsões individuais.var forecastEngine = forecaster.CreateTimeSeriesEngine<ModelInput, ModelOutput>(mlContext);
Salve o modelo em um arquivo chamado
MLModel.zip
, conforme especificado pela variável definida anteriormente,modelPath
. Use o métodoCheckpoint
para salvar o modelo.forecastEngine.CheckPoint(mlContext, modelPath);
Usar o modelo para prever a demanda
Abaixo do método
Evaluate
, crie um método de utilitário chamadoForecast
.void Forecast(IDataView testData, int horizon, TimeSeriesPredictionEngine<ModelInput, ModelOutput> forecaster, MLContext mlContext) { }
Dentro do método
Forecast
, use o métodoPredict
para prever aluguéis para os próximos sete dias.ModelOutput forecast = forecaster.Predict();
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"; });
Faça a iteração na saída da previsão para exibi-la no console.
Console.WriteLine("Rental Forecast"); Console.WriteLine("---------------------"); foreach (var prediction in forecastOutput) { Console.WriteLine(prediction); }
Executar o aplicativo
Depois de chamar o método
Checkpoint()
, chame o métodoForecast
.Forecast(secondYearData, 7, forecastEngine, mlContext);
Executar o aplicativo. Uma saída semelhante à abaixo deve aparecer no console. Para fins 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:
Embora os valores previstos não prevejam o número exato de aluguéis, eles fornecem uma faixa de valores mais estreita que permite que uma operação otimize o uso de recursos.
Parabéns! Você criou com sucesso um modelo de machine learning de série temporal para prever a demanda de aluguéis de bicicletas.
É possível encontrar o código-fonte deste tutorial no repositório dotnet/machinelearning-samples.