次の方法で共有


スーパーストアの売上予測モデルを開発、評価、スコア付けする

このチュートリアルでは、Microsoft Fabric の Synapse Data Science ワークフローのエンド ツー エンドの例について説明します。 このシナリオでは、過去の売上データを使用して、スーパーストアでのさまざまなカテゴリの製品の売上を予測する予測モデルを構築します。

予測は売上における重要な資産です。 履歴データと予測方法を組み合わせて、将来のトレンドに関する分析情報を提供します。 予測により、過去の売上を分析してパターンを特定し、消費者の行動から学習し、在庫、生産、マーケティングの戦略を最適化できます。 このプロアクティブなアプローチにより、動的な市場でのビジネスの適応性、応答性、全体的なパフォーマンスが向上します。

このチュートリアルに含まれる手順は次のとおりです:

  • データの読み込み
  • 探索的データ分析を通じてデータを理解して処理します
  • オープンソース ソフトウェア パッケージを使用して機械学習モデルをトレーニングし、MLflow と Fabric の自動ログ機能を使用して実験を追跡します
  • 最終的な機械学習モデルを保存し、予測を行います
  • Power BI の視覚化を使用してモデルのパフォーマンスを表示します

前提条件

ノートブックで作業を進めます

ノートブックでこれらの後続のオプションのうちのいずれかを選択できます

  • Synapse Data Science 環境のビルトイン ノートブックを開いて実行します
  • GitHub から Synapse Data Science 環境にノートブックをアップロードします

ビルトインのノートブックを開きます

[売上予測] のサンプル ノートブックは、このチュートリアルに付属しています。

チュートリアルのビルトインのサンプル ノートブックを Synapse Data Science 環境で開くには:

  1. Synapse Data Science のホーム ページに移動します。

  2. [サンプルの使用] を選択してください。

  3. 対応するサンプルを選択してください。

    • サンプルが Python チュートリアル用の場合は、既定の [エンド ツー エンド ワークフロー (Python)] タブから。
    • サンプルが R チュートリアル用の場合は、[エンド ツー エンド ワークフロー (R)] タブから。
    • サンプルがクイック チュートリアル用の場合は、[クイック チュートリアル] タブから。
  4. コードの実行を開始する前に、[レイクハウスをノートブックにアタッチします]

GitHub からノートブックをインポートします

[AIsample - Superstore Forecast.ipynb] ノートブックは、このチュートリアルに付属しています。

このチュートリアルが付属するノートブックを開く場合は、「データ サイエンス チュートリアル用にシステムを準備する」内の指示に従い、ノートブックを、お使いのワークスペースにインポートします。

このページからコードをコピーして貼り付ける場合は、[新しいノートブックを作成する] ことができます。

コードの実行を開始する前に、必ずレイクハウスをノートブックにアタッチしてください。

ステップ 1: データをロードする

データセットには、さまざまな製品の売上の 9,995 インスタンスが含まれています。 また、21 個の属性も含まれています。 このテーブルは、このノートブックで使用される Superstore.xlsx ファイルの内容です:

行 ID 注文 ID 注文日 出荷日 出荷モード 顧客 ID 顧客名 Segment 市町村 状態 郵便番号 リージョン 製品 ID カテゴリ サブカテゴリ 製品名 Sales 件数 値引き Profit
4 US-2015-108966 2015-10-11 2015-10-18 Standard クラス SO-20335 Sean O'Donnell コンシューマー アメリカ合衆国 Fort Lauderdale フロリダ 33311 South FUR-TA-10000577 家具 Tables Bretford CR4500 シリーズ スリム型長方形テーブル 957.5775 5 0.45 -383.0310
11 CA-2014-115812 2014-06-09 2014-06-09 Standard クラス Standard クラス Brosina Hoffman コンシューマー アメリカ合衆国 Los Angeles カリフォルニア 90032 West FUR-TA-10001539 家具 Tables Chromcraft 長方形会議テーブル 1706.184 9 0.2 85.3092
31 US-2015-150630 2015-09-17 2015-09-21 Standard クラス TB-21520 Tracy Blumstein コンシューマー アメリカ合衆国 フィラデルフィア ペンシルベニア 19140 East OFF-EN-10001509 オフィス用品 エンベロープ ポリエステル製ストリング タイ付き封筒 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())

データの前処理

実際のビジネス シナリオでは、多くの場合、3 つの異なるカテゴリで売り上げを予測する必要があります:

  • 特定の製品カテゴリ
  • 特定の顧客カテゴリ
  • 特定の製品カテゴリと顧客の組み合わせ

まず、不要な列を削除してデータを前処理します。 一部の列 (Row IDOrder IDCustomer ID、およびCustomer 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 に再サンプリングする必要があります。

まず、Furniture カテゴリを Order Date 別にグループ化します。 次に、カテゴリごとに Sales 列の合計を計算し、一意の Order Date 値ごとに合計売上を決定します。 月単位でデータを集計するために、MS 頻度で Sales 列を再サンプリングします。 最後に、各月の平均売上値を計算します。

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

Furniture カテゴリの Sales に対する Order Date の影響を確認します:

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

統計分析を行う前に、statsmodels Python モジュールをインポートする必要があります。 多くの統計モデルを推定するためのクラスと関数を提供します。 また、統計テストと統計データ探索を実行するためのクラスと関数も提供します。

import statsmodels.api as sm

統計分析を実行する

時系列では、時系列パターン内のそのデータ要素の変動を判断するために、設定された間隔でこちらの要素を追跡します:

  • レベル: 特定の期間の平均値を表す基本成分

  • 傾向: 時系列が時間の経過と共に減少している、一定である、または増加しているかを表す

  • 季節性: 時系列内の周期信号を言い表すもので、時系列の増加または減少パターンに影響を与える周期的な発生を検索する

  • ノイズまたは残差: モデルでは説明できない時系列データのランダムな変動やばらつきを指す

このコードでは、前処理後にデータセットのこれらの要素を観察します:

# 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) と呼ばれます。 そのため、モデルをトレーニングするには、最初に 7 つのパラメーターを調整する必要があります。

次数パラメーター:

  • p: AR コンポーネントの次数は、現在の値の予測に使用される、時系列内の過去の観測値の数を表します。

    通常、このパラメーターは、負ではない整数にする必要があります。 一般的な値は、通常 0 から 3 までの範囲ですが、データ特有の特性に応じて、これよりも大きい値を使用することもできます。 p が大きいほど、モデル内の過去の値がより長く記憶されていることを示します。

  • d: 階差次数は、時系列が定常に達するまでに差分する必要がある回数を表します。

    このパラメーターは、負ではない整数にする必要があります。 一般的な値は、通常 0 から 2 までの範囲です。 0d 値は、時系列が既に固定されていることを意味します。 値が大きいほど、固定するために必要な差分操作の数を示します。

  • q: MA コンポーネントの次数は、現在の値の予測に使用される、ホワイト ノイズ エラー用語の数を表します。

    このパラメーターは、負ではない整数にする必要があります。 一般的な値は、通常 0 から 3 までの範囲ですが、特定の時系列では、これよりも大きい値が必要になる場合があります。 q が大きいほど、予測を行うために過去のエラー用語に対する依存が強いことを示します。

季節次数パラメータ:

  • P: AR コンポーネントの季節次数は、p と似ていますが、季節部分の次数です
  • D: 階差の季節次数は、d と似ていますが、季節部分の次数です
  • Q: MA コンポーネントの季節次数は、q と似ていますが、季節部分の次数です
  • s: 季節周期あたりの時間ステップ数 (たとえば、1 年の季節性がある月単位のデータの場合は 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_stationarityTrue (既定値) に設定した場合、これは、SARIMAX モデルが時系列データに対して定常性を強制する必要があることを示します。 データを定常にするために、モデルを適合する前に SARIMAX モデルによって、次数 d および D で指定されているとおりにデータに差分が自動的に適用されます。 SARIMAX を含め、多くの時系列モデルでは、データが定常であることを前提としているため、これは一般的な方法です。

    時系列が非定常である場合 (たとえば、傾向や季節性を示す場合など)、定常性を達成するために、enforce_stationarityTrue に設定し、差分を SARIMAX モデルに処理させることをお勧めします。 時系列が既に定常である場合 (たとえば、傾向や季節性がない場合など)、enforce_stationarityFalse に設定して、不要な差分を回避します。

  • enforce_invertibility: 最適化プロセス中に、モデルが、推定されたパラメータに対して可逆性を強制する必要があるかどうか。

    enforce_invertibilityTrue (既定値) に設定した場合、これは、SARIMAX モデルが推定されたパラメータに対して可逆性を強制する必要があることを示します。 可逆性により、モデルが明確に定義され、推定されたAR 係数と MA 係数が定常性の範囲内にあることが保証されます。

    可逆性の強制は、SARIMAX モデルが安定した時系列モデルの理論的要件を確実に遵守することを保証するのに役立ちます。 これは、モデルの推定と安定性に関する問題を防ぐためにも役立ちます。

既定は AR(1) モデルです。 これは (1, 0, 0) を指します。 ただし、次数パラメータと季節次数パラメータのさまざまな組み合わせを試し、特定のデータセットに対するモデルのパフォーマンスを評価するのが一般的です。 適切な値は、時系列によって異なる場合があります。

最適な値の決定には、多くの場合、時系列データの自己相関関数 (ACF) と部分自己相関関数 (PACF) の分析が含まれます。 また、多くの場合、モデルの選択基準 (例: Akaike 情報基準 (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 は単純なメトリックです。 10% MAPE は、偏差が正か負かに関係なく、予測値と実際の値の間の平均偏差が 10% であることを表します。 望ましい MAPE 値の標準は、業界によって異なります。

このグラフの水色の線は、実際の売上値を表します。 濃い青色の線は、予測売上値を表します。 実際の売上と予測された売上を比較すると、このモデルが 2023 年の最初の 6 か月間での Furniture カテゴリの売上を効果的に予測していることがわかります。

Screenshot of a Power BI report.

この観察に基づくと、2023 年の最後の 6 か月から 2024 年までの全体的な売上についてモデルの予測機能に信頼を持つことができます。 この信頼は、在庫管理、原材料調達、その他のビジネス関連の考慮事項に関する戦略的決定を知らせることできます。