Compartilhar via


Desenvolver, avaliar e pontuar um modelo de previsão para as vendas de uma superloja

Este tutorial apresenta um exemplo de ponta a ponta de um fluxo de trabalho da Ciência de Dados do Synapse no Microsoft Fabric. O cenário cria um modelo de previsão que usa dados históricos de vendas para prever as vendas de diferentes categorias de produtos em uma superloja.

A previsão é um ativo crucial nas vendas. Ele combina dados históricos e métodos preditivos para fornecer insights sobre tendências futuras. A previsão pode analisar as vendas anteriores para identificar padrões e aprender com o comportamento do consumidor, a fim de otimizar as estratégias de estoque, produção e marketing. Essa abordagem proativa aprimora a adaptabilidade, a capacidade de resposta e o desempenho geral das empresas em um marketplace dinâmico.

Este tutorial cobre estas etapas:

  • Carregar os dados
  • Usar análise exploratória de dados para compreender e processar os dados
  • Treinar um modelo de machine learning usando um pacote de software de código aberto e acompanhar os experimentos usando o MLflow e o recurso Log automático do Fabric
  • Salvar o modelo final de machine learning e fazer previsões
  • Mostrar o desempenho do modelo com visualizações do Power BI

Pré-requisitos

Acompanhar em um notebook

Você pode escolher uma dessas opções para acompanhar em um notebook:

  • Abrir e executar o Notebook interno na experiência de Ciência de Dados do Synapse
  • Carregar seu Notebook do GitHub para a experiência de Ciência de Dados do Synapse

Abrir o Notebook interno

O notebook de exemplo Previsão de vendas acompanha este tutorial.

Para abrir o Notebook de amostra interno do tutorial na experiência de Ciência de Dados do Synapse:

  1. Vá para a página inicial da Ciência de Dados do Synapse.

  2. Selecione Usar um exemplo.

  3. Selecione o exemplo correspondente:

    • Na guia padrão fluxos de trabalho de ponta a ponta (Python), se o exemplo for para um tutorial do Python.
    • Na guia fluxos de trabalho de ponta a ponta (R), se o exemplo for para um tutorial do R.
    • Na guia Tutoriais rápidos, se o exemplo for para um tutorial rápido.
  4. Anexar um lakehouse ao notebook antes de começar a executar o código.

Importar o Notebook do GitHub

O notebook AIsample – Superstore Forecast.ipynb acompanha este tutorial.

Para abrir o notebook que acompanha este tutorial, siga as instruções em Preparar seu sistema para ciência de dados para importar os notebooks do tutorial para seu espaço de trabalho.

Se preferir copiar e colar o código nesta página, você poderá criar um novo notebook.

Certifique-se de anexar um lakehouse ao notebook antes de começar a executar o código.

Etapa 1: carregar os dados

O conjunto de dados contém 9.995 instâncias de vendas de vários produtos. Também inclui 21 atributos. Essa tabela é proveniente do arquivo Superstore.xlsx usado neste notebook:

ID de linha ID da Ordem Data da Ordem Ship Date Ship Mode ID do Cliente Nome do cliente Segment País/região City State Código postal Region ID do Produto Categoria Subcategoria Nome do Produto Vendas Quantidade Desconto Lucro
4 US-2015-108966 2015-10-11 2015-10-18 Classe Standard SO-20335 Mateus Rodrigues Consumidor Estados Unidos Fort Lauderdale Florida 33311 Sul FUR-TA-10000577 Móveis Tabelas Bretford CR4500 Series Slim Rectangular Table 957,5775 5 0,45 -383,0310
11 CA-2014-115812 2014-06-09 2014-06-09 Classe Standard Classe Standard Mila Moraes Consumidor Estados Unidos Los Angeles Califórnia 90032 Oeste FUR-TA-10001539 Móveis Tabelas Chromcraft Rectangular Conference Tables 1.706,184 9 0,2 85,3092
31 US-2015-150630 2015-09-17 2015-09-21 Classe Standard TB-21520 Brenda Fernandes Consumidor Estados Unidos Filadélfia Pensilvânia 19140 Leste OFF-EN-10001509 Office Supplies Envelopes Poly String Tie Envelopes 3,264 2 0,2 1,1016

Defina estes parâmetros, para que você possa usar este Notebook com diferentes conjuntos de dados:

IS_CUSTOM_DATA = False  # If TRUE, the dataset has to be uploaded manually

IS_SAMPLE = False  # If TRUE, use only rows of data for training; otherwise, use all data
SAMPLE_ROWS = 5000  # If IS_SAMPLE is True, use only this number of rows for training

DATA_ROOT = "/lakehouse/default"
DATA_FOLDER = "Files/salesforecast"  # Folder with data files
DATA_FILE = "Superstore.xlsx"  # Data file name

EXPERIMENT_NAME = "aisample-superstore-forecast"  # MLflow experiment name

Baixar o conjunto de dados e carregar no lakehouse

Esse código faz o download de uma versão disponível publicamente do conjunto de dados e, em seguida, o armazena em um lakehouse do Fabric:

Importante

Certifique-se deAdicionar um lakehouse ao notebook antes de executá-lo. Caso contrário, você terá um erro.

import os, requests
if not IS_CUSTOM_DATA:
    # Download data files into the lakehouse if they're not already there
    remote_url = "https://synapseaisolutionsa.blob.core.windows.net/public/Forecast_Superstore_Sales"
    file_list = ["Superstore.xlsx"]
    download_path = "/lakehouse/default/Files/salesforecast/raw"

    if not os.path.exists("/lakehouse/default"):
        raise FileNotFoundError(
            "Default lakehouse not found, please add a lakehouse and restart the session."
        )
    os.makedirs(download_path, exist_ok=True)
    for fname in file_list:
        if not os.path.exists(f"{download_path}/{fname}"):
            r = requests.get(f"{remote_url}/{fname}", timeout=30)
            with open(f"{download_path}/{fname}", "wb") as f:
                f.write(r.content)
    print("Downloaded demo data files into lakehouse.")

Configurar o acompanhamento de experimentos do MLflow

O Microsoft Fabric captura automaticamente os valores de parâmetros de entrada e métricas de saída de um modelo de machine learning enquanto o treina. Isso amplia as capacidades de registro de log automático do MLflow. Essa informação é então registradas no log do espaço de trabalho, onde podem ser acessadas e visualizadas usando as APIs do MLflow ou o experimento correspondente no espaço de trabalho. Para saber mais sobre o log automático, confira Log Automático no Microsoft Fabric.

Para desabilitar o log automático do Microsoft Fabric em uma sessão do notebook, chame mlflow.autolog() e configure como disable=True:

# Set up MLflow for experiment tracking
import mlflow

mlflow.set_experiment(EXPERIMENT_NAME)
mlflow.autolog(disable=True)  # Turn off MLflow autologging

Ler dados brutos do lakehouse

Leia dados brutos da seção Arquivos da lakehouse. Adicione mais colunas para diferentes partes de data. As mesmas informações são usadas para criar uma tabela delta particionada. Como os dados brutos são armazenados como um arquivo do Excel, você deve usar pandas para lê-los:

import pandas as pd
df = pd.read_excel("/lakehouse/default/Files/salesforecast/raw/Superstore.xlsx")

Etapa 2: Executar a análise exploratória de dados

Importar bibliotecas

Antes de qualquer análise, importe as bibliotecas necessárias:

# Importing required libraries
import warnings
import itertools
import numpy as np
import matplotlib.pyplot as plt
warnings.filterwarnings("ignore")
plt.style.use('fivethirtyeight')
import pandas as pd
import statsmodels.api as sm
import matplotlib
matplotlib.rcParams['axes.labelsize'] = 14
matplotlib.rcParams['xtick.labelsize'] = 12
matplotlib.rcParams['ytick.labelsize'] = 12
matplotlib.rcParams['text.color'] = 'k'
from sklearn.metrics import mean_squared_error,mean_absolute_percentage_error

Exibir os dados brutos

Revise manualmente um subconjunto dos dados, para compreender melhor o próprio conjunto de dados e use a função display para imprimir o DataFrame. Além disso, as exibições Chart para visualizar com facilidade os subconjuntos do conjunto de dados.

display(df)

Este notebook tem como foco principal a previsão das vendas referente à categoria Furniture. Isso acelera a computação e ajuda a mostrar o desempenho do modelo. No entanto, este notebook usa técnicas adaptáveis. Você pode estender essas técnicas para prever as vendas de outras categorias de produtos.

# Select "Furniture" as the product category
furniture = df.loc[df['Category'] == 'Furniture']
print(furniture['Order Date'].min(), furniture['Order Date'].max())

Pré-processar os dados

Cenários de negócios do mundo real, em geral, necessitam prever as vendas em três categorias distintas:

  • Uma categoria de produto específica
  • Uma categoria de cliente específica
  • Uma combinação específica de categoria de produto e categoria de cliente

Primeiro, descarte colunas desnecessárias para pré-processar os dados. Algumas das colunas (Row ID, Order ID, Customer ID, e Customer Name) são desnecessárias porque não têm impacto. Queremos prever as vendas gerais, em todo o estado e região, para uma categoria de produto específica (Furniture), para que possamos descartar as colunas State, Region, Country, City, e Postal Code. Para prever as vendas de uma localização ou de uma categoria específica, talvez seja necessário ajustar a etapa de pré-processamento de acordo.

# Data preprocessing
cols = ['Row ID', 'Order ID', 'Ship Date', 'Ship Mode', 'Customer ID', 'Customer Name', 
'Segment', 'Country', 'City', 'State', 'Postal Code', 'Region', 'Product ID', 'Category', 
'Sub-Category', 'Product Name', 'Quantity', 'Discount', 'Profit']
# Drop unnecessary columns
furniture.drop(cols, axis=1, inplace=True)
furniture = furniture.sort_values('Order Date')
furniture.isnull().sum()

O conjunto de dados é estruturado diariamente. Devemos colocar uma nova amostra na coluna Order Date, porque queremos desenvolver um modelo para prever as vendas mensalmente.

Primeiro, agrupe a categoria Furniture por Order Date. Em seguida, calcule a soma da coluna Sales para cada grupo, a fim de determinar o total de vendas de cada valor Order Date exclusivo. Faça uma nova amostra da coluna Sales com a frequência MS para agregar os dados por mês. Por fim, calcule o valor médio de vendas para cada mês.

# Data preparation
furniture = furniture.groupby('Order Date')['Sales'].sum().reset_index()
furniture = furniture.set_index('Order Date')
furniture.index
y = furniture['Sales'].resample('MS').mean()
y = y.reset_index()
y['Order Date'] = pd.to_datetime(y['Order Date'])
y['Order Date'] = [i+pd.DateOffset(months=67) for i in y['Order Date']]
y = y.set_index(['Order Date'])
maximim_date = y.reset_index()['Order Date'].max()

Demonstre o impacto de Order Date sobre Sales na categoria Furniture:

# Impact of order date on the sales
y.plot(figsize=(12, 3))
plt.show()

Antes de qualquer análise estatística, você deve importar o módulo statsmodels do Python. Ele fornece classes e funções para a estimativa de muitos modelos estatísticos. Ele também fornece classes e funções para realizar testes estatísticos e exploração de dados estatísticos.

import statsmodels.api as sm

Executar análise estatística

Uma série temporal acompanha esses elementos de dados em intervalos definidos para determinar a variação desses elementos no padrão de série temporal:

  • Nível: o componente fundamental que representando o valor médio de um período específico

  • Tendência: descreve se a série temporal diminui, se permanece constante ou se aumenta ao longo do tempo

  • Sazonalidade: descreve o sinal periódico na série temporal e procura ocorrências cíclicas que impactam os padrões crescentes ou decrescentes da série temporal

  • Ruído/Residual: refere-se às flutuações aleatórias e à variabilidade nos dados de série temporal que o modelo não consegue explicar.

Neste código, você observa esses elementos para seu conjunto de dados após o pré-processamento:

# Decompose the time series into its components by using statsmodels
result = sm.tsa.seasonal_decompose(y, model='additive')

# Labels and corresponding data for plotting
components = [('Seasonality', result.seasonal),
              ('Trend', result.trend),
              ('Residual', result.resid),
              ('Observed Data', y)]

# Create subplots in a grid
fig, axes = plt.subplots(nrows=4, ncols=1, figsize=(12, 7))
plt.subplots_adjust(hspace=0.8)  # Adjust vertical space
axes = axes.ravel()

# Plot the components
for ax, (label, data) in zip(axes, components):
    ax.plot(data, label=label, color='blue' if label != 'Observed Data' else 'purple')
    ax.set_xlabel('Time')
    ax.set_ylabel(label)
    ax.set_xlabel('Time', fontsize=10)
    ax.set_ylabel(label, fontsize=10)
    ax.legend(fontsize=10)

plt.show()

Os gráficos descrevem a sazonalidade, a tendência e o ruído nos dados de previsão. Você pode capturar os padrões subjacentes e desenvolver modelos que fazem previsões precisas que são resilientes a flutuações aleatórias.

Etapa 3: Treinar e acompanhar o modelo

Agora que você tem os dados disponíveis, defina o modelo de previsão. Neste notebook, aplique o modelo de previsão denominado média de movimentação integrada de regressão automática sazonal com fatores exógenos (SARIMAX). O SARIMAX combina os componentes AR (regressão automática) e MA (média móvel), diferenciação sazonal e previsores externos para fazer previsões precisas e flexíveis para dados de série temporal.

Você também usará o MLflow e o Log automático do Fabric para acompanhar os experimentos. Aqui, carregue a tabela delta do lakehouse. Você pode usar outras tabelas delta que consideram o lakehouse como a origem.

# Import required libraries for model evaluation
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error

Ajustar hiperparâmetros

O SARIMAX leva em conta os parâmetros envolvidos no modo ARIMA regular (média de movimentação integrada de regressão automática) (p, d, q) e adiciona os parâmetros de sazonalidade (P, D, Q, s). Esses argumentos do modelo SARIMAX são chamados de ordem (p, d, q) e ordem sazonal (P, D, Q, s), respectivamente. Portanto, para treinar o modelo, devemos primeiro ajustar sete parâmetros.

A ordem dos parâmetros:

  • p: a ordem do componente AR, representando o número de observações anteriores na série temporal que são usadas para prever o valor atual.

    Normalmente, este parâmetro deve ser um inteiro não negativo. Em geral, os valores comuns estão no intervalo de 0 a 3, embora valores mais altos sejam possíveis, dependendo das características específicas dos dados. Um valor de p mais alto indica uma memória mais longa de valores anteriores no modelo.

  • d: a ordem de diferenciação, representando o número de vezes que a série temporal precisa ser diferenciada, para alcançar estacionaridade.

    Esse parâmetro deve ser um inteiro não negativo. Os valores comuns estão no intervalo de 0 a 2. Um valor d de 0 significa que a série temporal já está estacionária. Valores mais altos indicam o número de operações diferenciais necessárias para torná-lo estacionário.

  • q: a ordem do componente MA, representando o número de termos de erro de ruído branco anteriores usados para prever o valor atual.

    Esse parâmetro deve ser um inteiro não negativo. Os valores comuns estão no intervalo de 0 a 3, mas valores mais altos podem ser necessários para algumas séries temporais. Um valor de q mais alto indica uma dependência mais forte em termos de erro anteriores para fazer previsões.

Os parâmetros de ordem sazonal:

  • P: a ordem sazonal do componente AR, semelhante a p, mas referente à parte sazonal
  • D: a ordem sazonal de diferenciação, semelhante a d, mas referente à parte sazonal
  • Q: a ordem sazonal do componente MA, semelhante a q, mas referente à parte sazonal
  • s: o número de etapas temporais por ciclo sazonal (por exemplo, 12 para dados mensais com sazonalidade anual)
# Hyperparameter tuning
p = d = q = range(0, 2)
pdq = list(itertools.product(p, d, q))
seasonal_pdq = [(x[0], x[1], x[2], 12) for x in list(itertools.product(p, d, q))]
print('Examples of parameter combinations for Seasonal ARIMA...')
print('SARIMAX: {} x {}'.format(pdq[1], seasonal_pdq[1]))
print('SARIMAX: {} x {}'.format(pdq[1], seasonal_pdq[2]))
print('SARIMAX: {} x {}'.format(pdq[2], seasonal_pdq[3]))
print('SARIMAX: {} x {}'.format(pdq[2], seasonal_pdq[4]))

O SARIMAX tem outros parâmetros:

  • enforce_stationarity: caso o modelo deve ou não impor a estacionaridade nos dados de série temporal antes de ajustar o modelo SARIMAX.

    Se enforce_stationarity é definido como True (o padrão), indica que o modelo SARIMAX deve impor a estacionaridade nos dados de série temporal. O modelo SARIMAX então aplica automaticamente a diferenciação aos dados para torná-los estacionários, conforme especificado pelas ordens d e D, antes de ajustar o modelo. Essa é uma prática comum porque muitos modelos de série temporal, incluindo o SARIMAX, pressupõem que os dados são estacionários.

    Para uma série temporal não estacionária (por exemplo, ela exibe tendências ou sazonalidade), é uma boa prática definir enforce_stationarity como True e permitir que o modelo SARIMAX cuide da diferenciação para alcançar a estacionaridade. Para uma série temporal estacionária (por exemplo, ela não tem tendências nem sazonalidade), defina enforce_stationarity como False para evitar diferenciações desnecessárias.

  • enforce_invertibility: controla se o modelo deve ou não impor a invertibilidade nos parâmetros estimados durante o processo de otimização.

    Se enforce_invertibility for definido como True (o padrão), indica que o modelo SARIMAX deve impor a invertibilidade nos parâmetros estimados. A invertibilidade garante que o modelo esteja bem definido e que os coeficientes estimados de AR e MA estejam dentro do intervalo de estacionaridade.

    Impor a invertibilidade ajuda a garantir que o modelo SARIMAX atenda aos requisitos teóricos de um modelo de série temporal estável. Isso também ajuda a evitar problemas com a estimativa e estabilidade do modelo.

O padrão é um modelo AR(1). Isso se refere a (1, 0, 0). No entanto, é uma prática comum experimentar diferentes combinações de parâmetros de ordem e sazonais e avaliar o desempenho do modelo para determinado conjunto de dados. Os valores apropriados podem variar de uma série temporal para outra.

A determinação dos valores ideais geralmente envolve a análise da função de auto correlação (ACF) e da função de auto correlação parcial (PACF) dos dados de séries temporais. Também envolve frequentemente o uso de critérios de seleção de modelos, por exemplo, o critério de informação de Akaike (AIC) ou o critério de informação bayesiano (BIC).

Ajuste os hiperparâmetros:

# Tune the hyperparameters to determine the best model
for param in pdq:
    for param_seasonal in seasonal_pdq:
        try:
            mod = sm.tsa.statespace.SARIMAX(y,
                                            order=param,
                                            seasonal_order=param_seasonal,
                                            enforce_stationarity=False,
                                            enforce_invertibility=False)
            results = mod.fit(disp=False)
            print('ARIMA{}x{}12 - AIC:{}'.format(param, param_seasonal, results.aic))
        except:
            continue

Após a avaliação dos resultados a seguir, você pode determinar os valores dos parâmetros de ordem e dos parâmetros de ordem sazonal. A escolha é order=(0, 1, 1) e seasonal_order=(0, 1, 1, 12), que oferecem o menor AIC (por exemplo, 279,58). Use esses valores para treinar o modelo.

Treinar o modelo

# Model training 
mod = sm.tsa.statespace.SARIMAX(y,
                                order=(0, 1, 1),
                                seasonal_order=(0, 1, 1, 12),
                                enforce_stationarity=False,
                                enforce_invertibility=False)
results = mod.fit(disp=False)
print(results.summary().tables[1])

Esse código visualiza uma previsão de série temporal para dados de vendas de móveis. Os resultados plotados mostram tanto os dados observados quanto a previsão de um passo à frente, com uma região sombreada para o intervalo de confiança.

# Plot the forecasting results
pred = results.get_prediction(start=maximim_date, end=maximim_date+pd.DateOffset(months=6), dynamic=False) # Forecast for the next 6 months (months=6)
pred_ci = pred.conf_int() # Extract the confidence intervals for the predictions
ax = y['2019':].plot(label='observed')
pred.predicted_mean.plot(ax=ax, label='One-step ahead forecast', alpha=.7, figsize=(12, 7))
ax.fill_between(pred_ci.index,
                pred_ci.iloc[:, 0],
                pred_ci.iloc[:, 1], color='k', alpha=.2)
ax.set_xlabel('Date')
ax.set_ylabel('Furniture Sales')
plt.legend()
plt.show()
# Validate the forecasted result
predictions = results.get_prediction(start=maximim_date-pd.DateOffset(months=6-1), dynamic=False)
# Forecast on the unseen future data
predictions_future = results.get_prediction(start=maximim_date+ pd.DateOffset(months=1),end=maximim_date+ pd.DateOffset(months=6),dynamic=False)

Use predictions para avaliar o desempenho do modelo, contrastando ele com os valores reais. O valor predictions_future indica previsões futuras.

# Log the model and parameters
model_name = f"{EXPERIMENT_NAME}-Sarimax"
with mlflow.start_run(run_name="Sarimax") as run:
    mlflow.statsmodels.log_model(results,model_name,registered_model_name=model_name)
    mlflow.log_params({"order":(0,1,1),"seasonal_order":(0, 1, 1, 12),'enforce_stationarity':False,'enforce_invertibility':False})
    model_uri = f"runs:/{run.info.run_id}/{model_name}"
    print("Model saved in run %s" % run.info.run_id)
    print(f"Model URI: {model_uri}")
mlflow.end_run()
# Load the saved model
loaded_model = mlflow.statsmodels.load_model(model_uri)

Etapa 4: Pontuar o modelo e salvar as previsões

Integre os valores reais com os valores previstos para criar um relatório do Power BI. Armazene esses resultados em uma tabela do lakehouse.

# Data preparation for Power BI visualization
Future = pd.DataFrame(predictions_future.predicted_mean).reset_index()
Future.columns = ['Date','Forecasted_Sales']
Future['Actual_Sales'] = np.NAN
Actual = pd.DataFrame(predictions.predicted_mean).reset_index()
Actual.columns = ['Date','Forecasted_Sales']
y_truth = y['2023-02-01':]
Actual['Actual_Sales'] = y_truth.values
final_data = pd.concat([Actual,Future])
# Calculate the mean absolute percentage error (MAPE) between 'Actual_Sales' and 'Forecasted_Sales' 
final_data['MAPE'] = mean_absolute_percentage_error(Actual['Actual_Sales'], Actual['Forecasted_Sales']) * 100
final_data['Category'] = "Furniture"
final_data[final_data['Actual_Sales'].isnull()]
input_df = y.reset_index()
input_df.rename(columns = {'Order Date':'Date','Sales':'Actual_Sales'}, inplace=True)
input_df['Category'] = 'Furniture'
input_df['MAPE'] = np.NAN
input_df['Forecasted_Sales'] = np.NAN
# Write back the results into the lakehouse
final_data_2 = pd.concat([input_df,final_data[final_data['Actual_Sales'].isnull()]])
table_name = "Demand_Forecast_New_1"
spark.createDataFrame(final_data_2).write.mode("overwrite").format("delta").save(f"Tables/{table_name}")
print(f"Spark DataFrame saved to delta table: {table_name}")

Etapa 5: Visualizá-lo no Power BI

O relatório do Power BI mostra um MAPE (percentual de erro médio absoluto) de 16,58. A métrica MAPE define a precisão de um método de previsão. Ele representa a precisão das quantidades previstas, em comparação com as quantidades reais.

Ele é uma métrica simples. Um MAPE igual a 10% representa que o desvio médio entre os valores previstos e os valores reais foi de 10%, independentemente de o desvio ser positivo ou negativo. Os padrões de valores MAPE desejáveis variam entre os setores.

A linha azul clara neste gráfico representa os valores reais de vendas. A linha azul escura representa os valores de vendas previstos. A comparação entre as vendas reais e previstas revela que o modelo prevê efetivamente as vendas para a categoria Furniture nos primeiros seis meses de 2023.

Screenshot of a Power BI report.

Com base nessa observação, podemos ter confiança nas funcionalidades de previsão do modelo em relação às vendas globais nos últimos seis meses de 2023 e que vão até 2024. Essa confiança pode informar decisões estratégicas sobre gerenciamento de estoque, aquisição de matérias-primas e outras considerações relacionadas aos negócios.