Partilhar via


Desenvolver, avaliar e pontuar um modelo de previsão para vendas em supermercados

Este tutorial apresenta um exemplo completo de um fluxo de trabalho Synapse Data Science no Microsoft Fabric. O cenário cria um modelo de previsão que usa dados históricos de vendas para prever as vendas da categoria de produtos em uma superloja.

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

Este tutorial aborda estas etapas:

  • Carregue os dados
  • Use a análise exploratória de dados para entender e processar os dados
  • Treine um modelo de aprendizado de máquina com um pacote de software de código aberto
  • Rastreie experimentos com MLflow e o recurso de registro automático de malha
  • Salve o modelo final de aprendizado de máquina e faça previsões
  • Mostrar o desempenho do modelo com visualizações do Power BI

Pré-requisitos

  • Obtenha uma assinatura do Microsoft Fabric . Ou inscreva-se para obter uma avaliação gratuita do Microsoft Fabric.

  • Inicie sessão no Microsoft Fabric.

  • Altera para o Fabric usando o alternador de experiência no canto inferior esquerdo da sua página inicial.

    Captura de ecrã que mostra a seleção de Fabric no menu do comutador de experiências.

Acompanhe num caderno

Para acompanhar em um bloco de anotações, você tem estas opções:

  • Abra e execute o bloco de anotações integrado na experiência Synapse Data Science
  • Carregue seu notebook do GitHub para a experiência Synapse Data Science

Abra o bloco de notas incorporado

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

  1. Para abrir o bloco de anotações de exemplo para este tutorial, siga as instruções em Preparar seu sistema para tutoriais de ciência de dados.

  2. Certifique-se de 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.

Passo 1: Carregue os dados

O conjunto de dados tem 9.995 ocorrências de vendas de vários produtos. Inclui também 21 atributos. O bloco de notas utiliza um ficheiro denominado Superstore.xlsx. Esse arquivo tem esta estrutura de tabela:

ID da linha ID da encomenda Data da encomenda Data de Envio Modo de Navio ID do Cliente Nome do Cliente Segmento País Cidade Estado Código Postal Região ID do produto Categoria Sub-Category Nome do Produto Sales Quantidade Desconto Lucro
4 EUA-2015-108966 2015-10-11 2015-10-18 Classe Standard SO-20335 Sean O'Donnell Consumidor Estados Unidos Forte Lauderdale Flórida 33311 Sul FUR-TA-10000577 Mobiliário Tabelas Bretford CR4500 Series Mesa retangular fina 957,5775 5 0.45 -383.0310
11 CA-2014-115812 2014-06-09 2014-06-09 Classe Standard Classe Standard Brosina Hoffman Consumidor Estados Unidos Los Angeles Califórnia 90032 Oeste FUR-TA-10001539 Mobiliário Tabelas Mesas de conferência retangulares Chromcraft 1706.184 9 0.2 85.3092
31 EUA-2015-150630 2015-09-17 2015-09-21 Classe Standard TB-21520 Tracy Blumstein Consumidor Estados Unidos Filadélfia Pensilvânia 19140 Leste DESLIGADO-EN-10001509 Material de Escritório Envelopes Envelopes de fecho com cordão de polipropileno 3.264 2 0.2 1.1016

O trecho de código a seguir define parâmetros específicos, para que você possa usar este bloco de anotações 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

Faça o download do conjunto de dados e carregue para o lakehouse

O trecho de código a seguir baixa uma versão disponível publicamente do conjunto de dados e, em seguida, armazena esse conjunto de dados em um lago de malha:

Importante

Você deve adicionar uma casa de lago ao bloco de anotações antes de executá-lo. Caso contrário, você receberá 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.z13.web.core.windows.net/data/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 rastreamento de experimentos MLflow

O Microsoft Fabric captura automaticamente os valores dos parâmetros de entrada e as métricas de saída de um modelo de aprendizado de máquina, à medida que você o treina. Isso amplia os recursos de registro automático do MLflow. As informações são então registradas no espaço de trabalho, onde você pode acessá-las e visualizá-las com as APIs MLflow ou o experimento correspondente no espaço de trabalho. Para obter mais informações sobre registro automático, visite o recurso Registro automático no Microsoft Fabric .

Para desativar o registro automático do Microsoft Fabric em uma sessão de bloco de anotações, chame mlflow.autolog() e defina disable=True, conforme mostrado no trecho de código a seguir:

# Set up MLflow for experiment tracking
import mlflow

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

Leia dados brutos da casa do lago

O trecho de código a seguir lê dados brutos da seção Arquivos da casa do lago. Ele também adiciona mais colunas para diferentes partes de data. As mesmas informações criam 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

Importe as bibliotecas necessárias antes de iniciar a análise:

# 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

Para entender melhor o conjunto de dados em si, revise manualmente um subconjunto dos dados. Use a display função para imprimir o DataFrame. As Chart visualizações podem visualizar facilmente subconjuntos do conjunto de dados:

display(df)

Este tutorial aborda um notebook que se concentra principalmente nas previsões de venda por categoria Furniture. Essa abordagem acelera a computação e ajuda a mostrar o desempenho do modelo. No entanto, este notebook utiliza técnicas adaptáveis. Você pode estender essas técnicas para prever as vendas de outras categorias de produtos. O trecho de código a seguir seleciona Furniture como categoria de produto.

# 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

Os cenários de negócios do mundo real geralmente precisam prever as vendas em três categorias distintas:

  • Categoria específica do produto
  • Categoria específica do cliente
  • Combinação específica de categoria de produto e categoria de cliente

O trecho de código a seguir descarta colunas desnecessárias para pré-processar os dados. Não precisamos de algumas das colunas (Row ID, Order ID,Customer ID, e Customer Name) porque elas não têm relevância. Queremos prever as vendas gerais, em todo o estado e região, para uma categoria específica de produtos (Furniture). Portanto, podemos eliminar as colunas State, Region, Country, City e Postal Code. Para prever as vendas para um local ou 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 fazer uma nova amostragem na Order Date coluna, 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, para determinar o total de vendas para cada valor único Order Date. Faça uma nova amostragem 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. O trecho de código a seguir mostra essas etapas:

# 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()

No trecho de código a seguir, mostre o impacto de Order Date on Sales para a Furniture categoria:

# 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 Python. Este módulo fornece classes e funções para estimar muitos modelos estatísticos. Ele também fornece classes e funções para conduzir testes estatísticos e exploração de dados estatísticos. O trecho de código a seguir mostra esta etapa:

import statsmodels.api as sm

Realizar análises estatísticas

Uma série temporal rastreia 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 representa o valor médio para um período de tempo específico

  • Trend: Descreve se a série temporal diminui, permanece constante ou aumenta ao longo do tempo

  • Sazonalidade: Descreve o sinal periódico na série temporal e procura ocorrências cíclicas que afetam os padrões de séries temporais crescentes ou decrescentes

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

O trecho de código a seguir mostra 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, tendências e ruído nos dados de previsão. Você pode capturar os padrões subjacentes e desenvolver modelos que façam previsões precisas que tenham resiliência contra flutuações aleatórias.

Passo 3: Treinar e acompanhar o modelo

Agora que você tem os dados disponíveis, defina o modelo de previsão. Neste caderno, aplique o modelo de previsão de média móvel integrada autoregressiva sazonal com fatores exógenos (SARIMAX). O SARIMAX combina componentes autorregressivos (RA) e de média móvel (MA), diferenças sazonais e preditores externos para fazer previsões precisas e flexíveis para dados de séries temporais.

Você também usa o registro automático MLflow e Fabric para acompanhar os experimentos. Aqui, carregue a tabela delta do Lakehouse. Você pode usar outras tabelas delta que consideram a casa do lago como a fonte. O trecho de código a seguir importa as bibliotecas necessárias:

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

Ajustar hiperparâmetros

O SARIMAX contabiliza os parâmetros envolvidos no modo de média móvel integrada autoregressiva regular (ARIMA) (p, d, q), e adiciona os parâmetros de sazonalidade (P, D, Qs, ). Esses argumentos do modelo SARIMAX são denominados ordem (p, d, q) e ordem sazonal (P, D, Qs, ), respectivamente. Portanto, para treinar o modelo, devemos primeiro ajustar sete parâmetros.

Os parâmetros de ordem:

  • p: A ordem do componente RA, que representa o número de observações passadas na série temporal usada para prever o valor atual.

    Normalmente, esse parâmetro deve ter um valor inteiro não negativo. Os valores comuns estão no intervalo de 0 a 3. No entanto, são possíveis valores mais elevados, dependendo das características específicas dos dados. Um valor de p mais alto indica uma memória mais longa de valores passados no modelo.

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

    Este parâmetro deve ter um valor inteiro não negativo. Os valores comuns estão no intervalo de 0 a 2. Um valor d igual a 0 indica que a série temporal já está estacionária. Valores maiores indicam que o número de operações de diferenciação necessárias para torná-lo estacionário é maior.

  • q: A ordem do componente MA. Este parâmetro representa o número de termos de erro de ruído branco anteriores usados para prever o valor atual.

    Este parâmetro deve ter um valor inteiro não negativo. Os valores comuns estão no intervalo de 0 até 3, mas certas séries temporais podem exigir valores mais altos. Um valor de q mais alto indica uma maior dependência de termos de erro passados para fazer previsões.

Os parâmetros de ordem sazonal:

  • P: A ordem sazonal do componente RA, semelhante ao p parâmetro, mas abrangendo a parte sazonal
  • D: A ordem sazonal de diferenciação, semelhante ao d parâmetro, mas abrangendo a parte sazonal
  • Q: A ordem sazonal do componente MA, semelhante ao q parâmetro, mas abrangendo a parte sazonal
  • s: O número de etapas temporais por ciclo sazonal (por exemplo, 12 para dados mensais com uma 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]))

SARIMAX tem outros parâmetros:

  • enforce_stationarity: Se o modelo deve ou não impor estacionariedade nos dados das séries cronológicas, antes de ajustar o modelo SARIMAX.

    Um valor de enforce_stationarity valor de True (o valor padrão) indica que o modelo SARIMAX deve impor estacionariedade aos dados de séries temporais. Antes de ajustar o modelo, o modelo SARIMAX aplica automaticamente a diferença aos dados para torná-los estacionários, conforme especificado pelas ordens d e D. Esta é uma prática comum porque muitos modelos de séries temporais, incluindo SARIMAX, assumem que os dados são estacionários.

    Para uma série temporal não estacionária (por exemplo, uma série que exibe tendências ou sazonalidade), é uma boa prática definir enforce_stationarity como True, e deixar o modelo SARIMAX lidar com a diferença para alcançar a estacionariedade. Para uma série temporal estacionária (por exemplo, uma sem tendências ou sazonalidade), defina enforce_stationarity para False para evitar diferenças desnecessárias.

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

    Um enforce_invertibility valor de True (o padrão) indica que o modelo SARIMAX deve impor a invertibilidade nos parâmetros estimados. A invertibilidade garante que um modelo bem definido e que os coeficientes estimados de RA e MA fiquem dentro da faixa de estacionariedade.

    A imposição da invertibilidade ajuda a garantir que o modelo SARIMAX cumpra os requisitos teóricos para um modelo de série temporal estável. Também ajuda a evitar problemas com a estimativa e estabilidade do modelo.

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

A determinação dos valores ótimos geralmente envolve a análise da função de autocorrelação (ACF) e da função de autocorrelaçã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, conforme mostrado no seguinte trecho de código:

# 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 anteriores, você pode determinar os valores para os parâmetros de ordem e os 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. O trecho de código a seguir mostra esta etapa:

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])

Este código visualiza uma previsão de série temporal para dados de vendas de móveis. Os resultados representados graficamente mostram os dados observados e a previsão de um passo à frente, incluindo uma região sombreada para intervalo de confiança. Os seguintes trechos de código mostram a visualização:

# 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)

O trecho de código a seguir é usado predictions para avaliar o desempenho do modelo, contrastando-o 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: Marcar o modelo e salvar previsões

O trecho de código a seguir integra os valores reais com os valores previstos, para criar um relatório do Power BI. Além disso, armazena esses resultados em uma tabela dentro 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: Visualizar no Power BI

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

MAPE é uma métrica simples. A MAPE de 10% representa que o desvio médio entre os valores previstos e os valores reais é de 10%, independentemente de o desvio ter sido 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 das vendas reais e previstas revela que o modelo prevê efetivamente as vendas para a categoria Furniture durante os primeiros seis meses de 2023.

Captura de ecrã de um relatório do Power BI.

Com base nesta observação, podemos ter confiança nas capacidades de previsão do modelo para as vendas globais nos últimos seis meses de 2023, e estendendo-se 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 ao negócio.