开发、评估超市销量预测模型并为其评分

本教程介绍了 Microsoft Fabric 中 Synapse 数据科学工作流的端到端示例。 此应用场景构建了一个预测模型,使用历史销售数据来预测超市中产品各类别的销量。

预测是销售中的一项关键资产, 结合了历史数据和预测方法,提供对未来趋势的洞察。 预测可以分析以往的销量以识别模式,并了解消费者的行为以优化库存、生产和营销策略。 这种主动方法增强了企业在动态市场中的适应能力、响应能力和整体业绩。

本教程涵盖以下步骤:

  • 加载数据
  • 使用探索性数据分析了解和处理数据
  • 使用开源软件包训练机器学习模型,并使用 MLflow 和 Fabric 自动日志记录功能跟踪试验。
  • 保存最终的机器学习模型并进行预测。
  • 使用 Power BI 可视化效果显示模型性能

先决条件

请按照笔记本进行操作

可以选择下面其中一个选项,以在笔记本中进行操作:

  • 在 Synapse 数据科学体验中打开并运行内置笔记本
  • 将笔记本从 GitHub 上传到 Synapse 数据科学体验

打开内置笔记本

本教程随附示例销量预测笔记本。

要在 Synapse 数据科学体验中打开教程的内置示例笔记本,请执行以下操作:

  1. 转到 Synapse 数据科学主页。

  2. 选择“使用示例”。

  3. 选择相应的示例:

    • 来自默认的“端到端工作流 (Python)”选项卡(如果示例适用于 Python 教程)。
    • 来自“端到端工作流 (R)“选项卡(如果示例适用于 R 教程)。
    • 来自“快速教程”选项卡(如果示例适用于快速教程)。
  4. 在开始运行代码之前,将湖屋连接到笔记本

从 GitHub 导入笔记本

AIsample - Superstore Forecast.ipynb 是本教程随附的笔记本。

若要打开本教程随附的笔记本,请按照让系统为数据科学做好准备教程中的说明操作,将该笔记本导入到工作区。

或者,如果要从此页面复制并粘贴代码,则可以创建新的笔记本

在开始运行代码之前,请务必将湖屋连接到笔记本

步骤 1:加载数据

该数据集包含 9,995 个各种产品的销售实例。 此外,还包括 21 个属性。 本表摘自此笔记本中使用的 Superstore.xlsx 文件:

行 ID 订单 ID 订单日期 Ship Date Ship Mode 客户 ID 客户名称 Segment 国家/地区 城市 状态 邮政编码 区域 产品 ID 类别 子类别 产品名称 Sales 数量 折扣 Profit
4 US-2015-108966 2015-10-11 2015-10-18 标准类 SO-20335 Sean O'Donnell 消费者 美国 Fort Lauderdale Florida 33311 南部 FUR-TA-10000577 家具 Bretford CR4500 Series Slim Rectangular Table 957.5775 5 0.45 -383.0310
11 CA-2014-115812 2014-06-09 2014-06-09 标准类 标准类 Brosina Hoffman 消费者 美国 Los Angeles California 90032 West FUR-TA-10001539 家具 Chromcraft Rectangular Conference Tables 1706.184 9 0.2 85.3092
31 US-2015-150630 2015-09-17 2015-09-21 标准类 TB-21520 Tracy Blumstein 消费者 美国 费城 Pennsylvania 19140 东部 OFF-EN-10001509 Office Supplies 包络线 Poly String Tie Envelopes 3.264 2 0.2 1.1016

定义这些参数,以便可以将此笔记本与不同的数据集一起使用:

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

下载数据集并上传到湖屋

以下代码将下载数据集的公开可用版本,然后将其存储在 Fabric 湖屋中:

重要

在运行笔记本之前,请务必向笔记本添加湖屋。 否则会出错。

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.")

设置 MLflow 试验跟踪

在训练机器学习模型时,Microsoft Fabric 会自动捕获其输入参数和输出指标的值。 这扩展了 MLflow 的自动记录功能。 然后,该信息会记录到工作区,可以在其中使用 MLflow API 或工作区中的相应试验对其进行访问并进行可视化。 若要详细了解自动日志记录,请参阅 Microsoft Fabric 中的自动日志记录

要在笔记本会话中禁用 Microsoft Fabric 自动记录,请调用 mlflow.autolog() 并设置 disable=True

# Set up MLflow for experiment tracking
import mlflow

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

从湖屋中读取原始数据

从湖屋的文件部分读取原始数据。 为不同的日期部分添加更多列。 使用相同的信息创建分区增量表。 由于原始数据存储为 Excel 文件,因此须使用 Pandas 读取:

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

步骤 2:执行探索性数据分析

导入库

在进行任何分析之前,导入所需的库:

# 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

显示原始数据

手动查看数据的子集,以便更好地了解数据集本身,并使用 display 函数打印 DataFrame。 此外,Chart 视图可轻松可视化数据集的子集。

display(df)

此笔记本主要侧重于预测 Furniture 类别销售。 这会加快计算速度,并有助于显示模型的性能。 但是,此笔记本使用技术适应性强的技术。 可以扩展这些技术来预测其他产品类别的销量。

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

预处理数据

现实业务场景通常需要预测三个不同类别的销量:

  • 特定的产品类别
  • 特定的客户类别
  • 产品类别和客户类别的特定组合

首先,删除不必要的列来预处理数据。 某些列(如 Row IDOrder IDCustomer IDCustomer Name)是不必要的,因为其不会产生影响。 我们要预测整个州和区域特定产品类别 (Furniture) 的总销量,因此可以删除StateRegionCountryCityPostal Code 列。 要预测特定地理位置或类别的销量,可能需要相应地调整预处理步骤。

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

数据集每天执行结构化。 因为我们想要开发一个模型来每月预测销售额,因此必须对列 Order Date 重新取样。

首先,按 FurnitureOrder Date 类别进行分组。 然后计算每个组 Sales 列的总和,以确定每个唯一 Order Date 值的总销量。 使用 Sales 频率对 MS 列进行重新取样,以按月汇总数据。 最后,计算每个月的平均销售额。

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

展示 Order DateSales 类别 Furniture 的影响:

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

进行任何统计分析之前,须导入 statsmodels Python 模块。 Python 模块提供用于估计多种统计模型的类和函数。 还提供用于执行统计测试和统计数据探索的类和函数。

import statsmodels.api as sm

执行统计分析

时序按设置的间隔跟踪以下数据元素,以确定这些元素在时序模式中的变化:

  • Level:代表特定时段的平均值的基本组成部分。

  • Trend:描述时序是随时间递减、保持恒定还是递增。

  • Seasonality:描述时序中的周期性信号,并查找影响时序模式递增或递减的循环事件。

  • Noise/Residual:指时序数据中模型无法解释的随机波动和变化。

在此代码中,可以在预处理后观察到数据集的这些元素:

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

绘图描述了预测数据的季节性、趋势和噪声。 可捕获基础模式,并开发能够做出准确预测且能够灵活应对随机波动的模型。

步骤 3:训练和跟踪模型

有了可用的数据后,接下来定义预测模型。 在此笔记本中,应用名为“具有外生因素的季节性自动回归集成移动平均”(SARIMAX) 的预测模型。 SARIMAX 结合了自动回归 (AR) 和移动平均 (MA) 分量、季节性差分和外部预测因子,可以对时序数据做出准确灵活的预测。

还可以使用 MLfLow 和 Fabric 自动记录功能来跟踪试验。 在此处,将从湖屋加载增量表。 可以使用将湖屋视为源的其他增量表。

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

优化超参数

SARIMAX 会考虑常规自动回归集成移动平均 (ARIMA) 模式中涉及的参数(pdq),并添加了季节性参数(PDQs)。 这些 SARIMAX 模型参数分别称为“阶数”pdq)和“季节性阶数”PDQs)。 因此,要训练模型,须先优化七个参数。

阶数参数:

  • p:AR 分量的阶数,表示时序中用于预测当前值的过去观测值数量。

    通常,此参数应为非负整数。 常用值在 03 的范围内,但根据具体的数据特征,也可以使用更高的值。 p 值越高,表示模型中对过去值的记忆越久。

  • d:差分阶数,表示为了实现稳定而需要差分时序的次数。

    此参数应为非负整数。 常用值通常在 02 的范围内。 d 值为 0 表示时序已平稳。 更高的值表示使其平稳所需的差分操作次数。

  • q:MA 分量的阶数,表示用于预测当前值的过去白噪声误差项数量。

    此参数应为非负整数。 常用值通常在 03 的范围内,但某些时序可能需要更高的值。 q 值越高,表示越依赖于过去的误差项来做出预测。

季节性阶数参数:

  • P:AR 分量的季节性阶数,与 p 类似,但用于季节性部分
  • D:季节性差分阶数,与 d 类似,但用于季节性部分
  • Q:MA 分量的季节性阶数,与 q 类似,但用于季节性部分
  • s:每个季节性周期的时间步数(例如,对于具有年度季节性的月度数据,时间步数为 12)
# 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 具有其他参数:

  • enforce_stationarity:控制该模型在拟合 SARIMAX 模型之前是否应强制要求时序数据保持平稳。

    如果 enforce_stationarity 设置为 True(默认值),表示 SARIMAX 模型应强制要求时序数据保持稳定。 然后 SARIMAX 模型将在拟合模型之前自动对数据应用差分,以按照 dD 阶数的指定使模型保持平稳。 这是一种常见做法,因为许多时序模型(包括 SARIMAX)都假设数据是稳定的。

    对于不稳定时序(例如,其展示了趋势或季节性),则通常最好将 enforce_stationarity 设置为 True,并让 SARIMAX 模型处理差分以实现平稳。 对于稳定时序(例如,没有趋势或季节性),则可以将 enforce_stationarity 设置为 False 以避免不必要的差分。

  • enforce_invertibility:控制该模型在优化过程中是否应强制要求被估算参数的可逆性。

    如果 enforce_invertibility 设置为 True(默认值),表示 SARIMAX 模型应强制要求被估算参数的可逆性。 可逆性确保模型经过妥善的定义,并且估算的自动回归 (AR) 和移动平均 (MA) 系数位于平稳范围内。

    强制要求可逆性可确保 SARIMAX 模型符合稳定时序模型的理论要求。 这还有助于防止出现模型估算和稳定性问题。

默认为 AR(1) 模型。 这指的是 (1, 0, 0)。 但是,常见的做法是尝试阶数参数和季节性阶数参数的不同组合,并评估数据集的模型性能。 适当值可能因时序而异。

确定最优值通常需要分析时序数据的自相关函数 (ACF) 和偏自相关函数 (PACF)。 此外,还经常涉及使用模型选择标准,例如赤池信息量准则 (AIC) 或贝叶斯信息量准则 (BIC)。

优化超参数:

# 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

评估上述结果后,可以确定阶数参数和季节性阶数参数的值。 选择是 order=(0, 1, 1)seasonal_order=(0, 1, 1, 12),其能提供最低的 AIC(例如 279.58)。 请使用这些值来训练模型。

模型

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

此代码用于可视化家具销售数据的时序预测。 绘制的结果同时显示了观察到的数据和提前预测的数据,阴影区域表示置信区间。

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

通过将其与实际值对比,使用 predictions 来评估模型的性能。 predictions_future 值表示未来的预测。

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

步骤 4:为模型评分并保存预测

将实际值与预测值集成,以创建 Power BI 报表。 将这些结果存储在湖屋中的某个表中。

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

步骤 5:在 Power BI 中可视化

Power BI 报表显示平均绝对百分比误差 (MAPE) 为 16.58。 MAPE 指标用于定义预测方法的准确性。 表示预测数量与实际数量相比的准确程度。

MAPE 是一个简单的指标。 MAPE 为 10% 表示预测值与实际值之间的平均偏差为 10%,无论偏差是正还是负。 所需 MAPE 值的标准因行业而异。

此图中的浅蓝色线条表示实际销售额。 深蓝色线条表示预测销售额。 对实际销量和预测销量的比较分析表明,该模型有效预测了 2023 年前六个月 Furniture 类别的销量。

Screenshot of a Power BI report.

根据观测到的这种情况,应对该模型对 2023 年下半年乃至 2024 年总体销量的预测能力充满信心。 凭借这种自信,可以在库存管理、原材料采购和其他业务相关考量方面做出明智的战略性决策。