开发、评估超市销量预测模型并为其评分
本教程介绍了 Microsoft Fabric 中 Synapse 数据科学工作流的端到端示例。 此应用场景构建了一个预测模型,使用历史销售数据来预测超市中产品各类别的销量。
预测是销售中的一项关键资产, 结合了历史数据和预测方法,提供对未来趋势的洞察。 预测可以分析以往的销量以识别模式,并了解消费者的行为以优化库存、生产和营销策略。 这种主动方法增强了企业在动态市场中的适应能力、响应能力和整体业绩。
本教程涵盖以下步骤:
- 加载数据
- 使用探索性数据分析了解和处理数据
- 使用开源软件包训练机器学习模型,并使用 MLflow 和 Fabric 自动日志记录功能跟踪试验。
- 保存最终的机器学习模型并进行预测。
- 使用 Power BI 可视化效果显示模型性能
先决条件
获取 Microsoft Fabric 订阅。 或者注册免费的 Microsoft Fabric 试用版。
登录 Microsoft Fabric。
使用主页左侧的体验切换器切换到 Synapse 数据科学体验。
- 如有必要,请如在 Microsoft Fabric 中创建湖屋中所述创建 Microsoft Fabric 湖屋。
请按照笔记本进行操作
可以选择下面其中一个选项,以在笔记本中进行操作:
- 在 Synapse 数据科学体验中打开并运行内置笔记本
- 将笔记本从 GitHub 上传到 Synapse 数据科学体验
打开内置笔记本
本教程随附示例销量预测笔记本。
要在 Synapse 数据科学体验中打开教程的内置示例笔记本,请执行以下操作:
转到 Synapse 数据科学主页。
选择“使用示例”。
选择相应的示例:
- 来自默认的“端到端工作流 (Python)”选项卡(如果示例适用于 Python 教程)。
- 来自“端到端工作流 (R)“选项卡(如果示例适用于 R 教程)。
- 来自“快速教程”选项卡(如果示例适用于快速教程)。
从 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 ID
、Order ID
、Customer ID
和 Customer Name
)是不必要的,因为其不会产生影响。 我们要预测整个州和区域特定产品类别 (Furniture
) 的总销量,因此可以删除State
、Region
、Country
、City
和 Postal 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
重新取样。
首先,按 Furniture
对 Order 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 Date
对 Sales
类别 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) 模式中涉及的参数(p
、d
、q
),并添加了季节性参数(P
、D
、Q
、s
)。 这些 SARIMAX 模型参数分别称为“阶数”(p
、d
、q
)和“季节性阶数”(P
、D
、Q
、s
)。 因此,要训练模型,须先优化七个参数。
阶数参数:
p
:AR 分量的阶数,表示时序中用于预测当前值的过去观测值数量。通常,此参数应为非负整数。 常用值在
0
到3
的范围内,但根据具体的数据特征,也可以使用更高的值。p
值越高,表示模型中对过去值的记忆越久。d
:差分阶数,表示为了实现稳定而需要差分时序的次数。此参数应为非负整数。 常用值通常在
0
到2
的范围内。d
值为0
表示时序已平稳。 更高的值表示使其平稳所需的差分操作次数。q
:MA 分量的阶数,表示用于预测当前值的过去白噪声误差项数量。此参数应为非负整数。 常用值通常在
0
到3
的范围内,但某些时序可能需要更高的值。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 模型将在拟合模型之前自动对数据应用差分,以按照d
和D
阶数的指定使模型保持平稳。 这是一种常见做法,因为许多时序模型(包括 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
类别的销量。
根据观测到的这种情况,应对该模型对 2023 年下半年乃至 2024 年总体销量的预测能力充满信心。 凭借这种自信,可以在库存管理、原材料采购和其他业务相关考量方面做出明智的战略性决策。