機械学習によるショールーミングが発生しやすい商品の需要予測モデル

ショールーミングを収益に変える需要予測:LightGBMとカスタム損失関数による在庫最適化実装ガイド

約13分で読めます
文字サイズ:
ショールーミングを収益に変える需要予測:LightGBMとカスタム損失関数による在庫最適化実装ガイド
目次

この記事の要点

  • ショールーミング行動を考慮した需要予測
  • LightGBMを用いた高精度なモデル構築
  • 在庫最適化のためのカスタム損失関数

「店舗では試着ばかりで売上が立たず、逆にECサイトでは在庫切れで機会損失が発生している」。小売やEC事業のデータ分析に関わる方であれば、現場からのこのような切実な声を耳にしたことがあるのではないでしょうか。

いわゆる「ショールーミング(Showrooming)」——実店舗で現物を確認し、購入はオンラインで行う行動——は、長らく実店舗の売上を奪う要因と見なされてきました。しかし、データを分析する視点から見れば、これは単なる脅威ではなく、強力な購買先行指標(Leading Indicator)として活用できる可能性を秘めています。

需要予測において陥りやすい課題は、店舗とECのデータを分断してモデルを構築してしまうことです。店舗の売上が下がったからといって、必ずしもそのエリアの需要が減ったわけではありません。需要が発生する場所(店舗)と、実際に購入される場所(EC)が分かれているだけなのです。

本記事では、このショールーミング行動をデータとしてモデルに組み込み、LightGBMを用いた需要予測モデルを構築する具体的なステップを解説します。単なる過去の売上推移からの予測にとどまらず、在庫の偏りによるリスクを考慮したカスタム損失関数の実装まで、実務に即した形で掘り下げます。

これは、実務の現場で検証され、在庫回転率の改善と機会損失の削減を両立させたアプローチに基づいています。Python環境があれば、すぐに手元で試せるコードも用意しました。専門用語をできるだけ避け、既存の業務フローにAIをどう組み込むかという視点を大切にしながら進めていきます。

店舗とECが融合する現代のデータ分析は、複雑であると同時に、ビジネス価値を大きく引き出す可能性を持っています。データの裏側に隠れた「真の需要」をどのように解き明かすか、順を追って見ていきましょう。


1. 技術的背景:なぜ従来の需要予測ではショールーミングに対応できないのか

まず、システムの仕組みという観点から、なぜ既存の予測モデルがうまく機能しないのか、その根本的な原因を明確にしておきましょう。

店舗POSデータとECログの乖離問題

従来の需要予測モデル(ARIMAなどの時系列モデルや単純な回帰モデル)は、過去の売上実績を最も重要なデータとして扱います。

$$ y_{t} = f(y_{t-1}, y_{t-2}, ..., y_{t-n}) $$

しかし、ショールーミングが発生する環境下では、以下のような現象が起きます。

  1. 店舗: 来店客数は多いが、購入数($y_{store}$)は減少する。
  2. EC: 特定地域からのアクセスが増え、購入数($y_{ec}$)が急増する。

もし店舗のデータのみで学習したAIモデルを使用していると、AIは「売上が落ちているため、需要が減っている」と誤って判断し、次回の在庫発注を減らすよう提案してしまいます。結果として、店舗には商品がなくなり、お客様が実物を確認することすらできない状態(完全な機会損失)に陥ります。

一方でEC側のモデルは、これを突発的な需要の増加として処理してしまい、その背景にある「店舗での体験」という重要な要因を見落としてしまいます。

ショールーミング行動のデータ痕跡を特定する

ここで解決すべき課題は、「店舗での体験」と「ECでの購入」の間のつながりを、AIが理解できる形にすることです。

ショールーミング行動は、データ上では以下のような痕跡として現れる傾向があります。

  • 位置情報とアプリログ: 店舗エリア内での自社ECアプリアクセス、または商品バーコードスキャン。
  • 会員ID統合: 店舗来店ポイント付与から$N$時間以内のEC購入。
  • 非会員の推論: 特定店舗の在庫検索回数の増加と、当該エリアへのEC配送の相関。

これらを単なる「ノイズ(不要なデータ)」として処理するのではなく、需要を予測するための「シグナル(重要なサイン)」として捉えるデータ加工(特徴量エンジニアリング)が必要です。

本ガイドで構築するモデルの全体アーキテクチャ

今回構築するのは、単に売上だけを予測するモデルではなく、店舗とECという異なるチャネル間の相互作用を考慮した統合予測モデルです。

  • モデルアルゴリズム: LightGBM(勾配ブースティング決定木)
    • 理由: 欠損値の扱いに強く、カテゴリカル変数(店舗ID、商品カテゴリ)を効率的に処理でき、非線形な相互作用(店舗で見ることがEC売上に与える影響)を捉えられるため。
  • 目的関数: カスタム損失関数(Asymmetric Loss)
    • 理由: 在庫切れによる機会損失コストと、売れ残りによる廃棄コストは等価ではないため。

では、具体的な実装手順を見ていきましょう。


2. 前提条件とデータパイプラインの準備

AIプロジェクトの成否はデータ準備に大きく依存しますが、店舗とECを横断する分析では特に「データの同期」が重要になります。

必要なPythonライブラリと環境構築

まずは必要なライブラリを読み込みます。今回はLightGBMをメインに使用し、データ操作にはpandas、数値計算にはnumpyを使用します。

import pandas as pd
import numpy as np
import lightgbm as lgb
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import mean_squared_error
import matplotlib.pyplot as plt
import seaborn as sns

# バージョン確認(再現性のため)
# pandas >= 1.3.0
# lightgbm >= 3.2.1

入力データの要件定義

シミュレーション用に、以下の3つのデータセットを結合した表形式のデータ(df_main)を想定します。

  1. POSデータ (df_pos): 店舗ID、商品ID、日時、販売数量。
  2. ECログ (df_ec): ユーザーID、商品ID、閲覧日時、購入日時、配送先エリア。
  3. 店舗行動ログ (df_action): 店舗内でのアプリアクセス、Wi-Fi接続ログ、RFID検知数(試着数など)。

ここで重要なのが、データの粒度(細かさ)です。需要予測では「日次×店舗×商品」または「週次×店舗×商品」で集計するのが一般的ですが、ショールーミングの影響を正確に捉えるには「日次」での集計が推奨されます。

データクレンジングと欠損値処理の戦略

店舗とECのデータを統合する際の前処理として注意すべきなのが、「在庫切れ」の扱いです。

店舗で在庫が切れていた場合、売上がゼロになるのは「需要がないから」ではなく「商品がないから」です。これをそのままAIに学習させると、本来の需要を少なく見積もってしまいます。

def preprocess_stockout(df):
    """
    在庫切れ期間の売上データを補正またはフラグ立てする処理
    """
    # 在庫数が0だった日のフラグを作成
    df['is_stockout'] = df['stock_quantity'].apply(lambda x: 1 if x == 0 else 0)
    
    # 在庫切れの日は、予測モデルの学習対象から除外するか、
    # あるいは「潜在需要」を推定して補完する必要があります。
    # ここではシンプルに特徴量として利用する方針をとります。
    return df

# データのマージ例(概念コード)
# df_main = pd.merge(df_pos, df_ec_agg, on=['date', 'item_id'], how='left')

また、タイムゾーンの違いにも注意が必要です。ECのサーバーログ(世界標準時など)と店舗のレジデータ(日本標準時)がずれていると、日次の集計で「当日のショールーミングが翌日のデータとして扱われる」といった不整合が生じます。必ず現地のビジネスタイムに統一して処理を行います。


3. 特徴量エンジニアリング:ショールーミング指数の実装

前提条件とデータパイプラインの準備 - Section Image

ここが本記事の重要なポイントです。ショールーミング行動をどのように数値化し、AIモデルに理解させるか。実務の現場において効果的とされるデータ加工(特徴量設計)の方法を紹介します。

クロスチャネル相関特徴量の作成

店舗での「商品を見ているだけ」の行動を数値化するために、Showrooming Index(ショールーミング指数)を定義します。

$$ \text{Showrooming Index} = \frac{\text{店舗での商品接触数(試着・スキャン)}}{\text{店舗での購入数} + \epsilon} $$

この数値が高いほど、「関心はあるが店舗では買わない」傾向が強いことを示します。この指標をEC売上の予測モデルに組み込みます。

def create_showrooming_features(df):
    # 店舗での接触数(RFID検知やアプリ商品スキャン数)
    # 店舗での購入数
    
    # ゼロ除算回避のために小さな値(epsilon)を加える
    epsilon = 1e-5
    
    df['showrooming_index'] = df['store_interaction_count'] / (df['store_sales_count'] + epsilon)
    
    # さらに、エリアごとのショールーミング傾向を計算
    # 特定の店舗(例:旗艦店)はショールーミング指数が高くなる傾向がある
    df['store_showrooming_rate'] = df.groupby('store_id')['showrooming_index'].transform('mean')
    
    return df

ラグ特徴量と移動平均の適用範囲

ショールーミングには時間差(タイムラグ)が発生します。「週末に店舗で実物を見て、月曜日の通勤中にスマートフォンで購入する」といったパターンです。この時間差を捉えるために、過去の行動履歴を反映したデータ(ラグ特徴量)を作成します。

def create_lag_features(df, lags=[1, 2, 3, 7]):
    """
    店舗のアクションデータがECの売上に与えるラグ効果を作成
    """
    for lag in lags:
        # 店舗での接触数がN日後のEC売上にどう響くか
        df[f'lag_{lag}_store_interaction'] = df.groupby('item_id')['store_interaction_count'].shift(lag)
        
        # 同一商圏内でのラグ特徴量(より精緻なモデルの場合)
        # df[f'lag_{lag}_area_interaction'] = ...
        
    return df

「店舗での試着・確認」を代替指標で数値化する

もしRFID(電子タグ)や詳細なアプリのログが存在しない場合はどうすればよいでしょうか。
その場合は、「店舗在庫検索ログ」「店舗詳細ページの閲覧数」を代替指標として活用します。

「店舗に在庫があるか」をECサイトやアプリで確認する行動は、来店意欲の強い表れです。この検索数が増加した後に、店舗の売上が伸び悩み、逆にECの売上が伸びているのであれば、それは典型的なショールーミング行動と推測できます。


4. LightGBMによる需要予測モデルの実装手順

データの準備ができたら、AIモデルの構築に移ります。ここでは、ビジネス上の損失を最小限に抑えるためのカスタム目的関数の実装に焦点を当てます。

カスタム目的関数の設計(在庫切れリスクの重み付け)

一般的な予測精度の評価指標(RMSEなど)は、多めに予測してしまった場合(売れ残り)と、少なめに予測してしまった場合(在庫切れ)を同じ重みで評価します。しかし、実際のビジネスではこの2つのコストは異なります。

  • 在庫切れ(Under-prediction): 機会損失、顧客ロイヤルティの低下。
  • 過剰在庫(Over-prediction): 保管コスト、廃棄コスト、値引きロス。

トレンド商品などの場合、在庫切れによる機会損失のリスクをより重く見積もりたいケースが多いでしょう。そこで、在庫切れに対してより厳しい評価を下す「非対称な損失関数」を定義します。

def custom_asymmetric_objective(y_true, y_pred):
    """
    過小予測(在庫切れ)に対してより大きなペナルティを与えるカスタム目的関数
    grad: 勾配 (1次導関数)
    hess: ヘッセ行列 (2次導関数)
    """
    residual = (y_true - y_pred).astype("float")
    
    # パラメータ設定
    # alpha: 過小予測(実際の需要 > 予測)に対するペナルティの重み
    # ここでは在庫切れを避けたいため、alphaを大きく設定(例: 0.7〜0.9)
    alpha = 0.7 
    
    grad = np.where(residual > 0, -2 * alpha * residual, -2 * (1 - alpha) * residual)
    hess = np.where(residual > 0, 2 * alpha, 2 * (1 - alpha))
    
    return grad, hess

def custom_asymmetric_eval(y_true, y_pred):
    """
    評価関数(メトリクス用)
    """
    residual = (y_true - y_pred).astype("float")
    alpha = 0.7
    loss = np.where(residual > 0, alpha * residual**2, (1 - alpha) * residual2)
    return 'asymmetric_loss', np.mean(loss), False

この関数をLightGBMの学習時に指定することで、AIモデルは「多少在庫が余るリスクを取ってでも、欠品による機会損失を防ぐ」という方針に沿って学習を進めます。

モデルパラメータの基本設定と最適化

LightGBMの設定(ハイパーパラメータ)を行います。時系列データであることを考慮し、過去のデータに過剰に適合してしまう(過学習)のを防ぐ設定にします。

params = {
    'objective': custom_asymmetric_objective, # カスタム目的関数を指定
    'metric': 'None', # カスタム評価関数を使用するためNone
    'boosting_type': 'gbdt',
    'num_leaves': 31,
    'learning_rate': 0.05,
    'feature_fraction': 0.9,
    'bagging_fraction': 0.8,
    'bagging_freq': 5,
    'verbose': -1,
    'seed': 42
}

# データセットの作成
train_set = lgb.Dataset(X_train, y_train)
val_set = lgb.Dataset(X_val, y_val, reference=train_set)

# 学習実行
model = lgb.train(
    params,
    train_set,
    num_boost_round=1000,
    valid_sets=[train_set, val_set],
    feval=custom_asymmetric_eval, # カスタム評価関数
    early_stopping_rounds=50,
    verbose_eval=100
)

学習プロセスとバリデーション戦略

時系列データの検証において、データをランダムに分割する手法は、未来の情報が過去の学習に混ざってしまうため避けるべきです。必ず時間の流れに沿って分割するTimeSeriesSplitを使用します。

学習データ期間: 2022-01-01 〜 2022-12-31
検証データ期間: 2023-01-01 〜 2023-01-31

このように時間を区切って検証することで、実際の運用環境に近い形での精度評価が可能になります。


5. 予測結果の検証と在庫配分シミュレーション

LightGBMによる需要予測モデルの実装手順 - Section Image

モデルが完成したら、その予測結果が実際のビジネスにどう貢献するかを検証します。技術的な精度指標だけでなく、経営層や現場の担当者に対しては「金額」や「具体的な業務への影響」に換算して説明することが重要です。

精度評価指標(RMSE, MAE)とビジネスKPIの対比

技術的な指標とビジネス上の指標を紐づける考え方を整理します。

技術指標 ビジネス指標への変換ロジック 意味
RMSE 在庫分散リスク 予測が外れた場合の在庫の偏り具合
Bias 在庫過不足傾向 常に多めに発注しているか、少なめか
Asymmetric Loss 機会損失額 + 在庫保有コスト 今回のモデルが最小化しようとしている真のコスト

特に重要なのは、今回導入したカスタム損失関数によって、「機会損失額」がどれだけ削減される見込みかをシミュレーションすることです。

ショールーミング発生時の在庫移動シミュレーション

予測結果に基づき、既存の業務フローの中で在庫配分をどう最適化できるかシミュレーションを行います。

シナリオ: 商品Aについて、全体で100個の在庫がある。

  • 従来モデル: 店舗に80個、ECに20個配分(店舗売上実績に基づく)。
    • 結果: 店舗で売れ残り30個、ECで即完売(機会損失発生)。
  • 新モデル(ショールーミング考慮): 店舗に40個、ECに60個配分。
    • 予測根拠: 「店舗での試着数は多いが、購入確率は低い。しかしECでの購入確率は高い」という予測。
    • 結果: 店舗在庫は試着需要を満たすのに十分で、ECの需要もカバー。全体の消化率が向上。

このように、「店舗の在庫は『その場で売るため』だけでなく、『実物を見せるため』の役割も担っている」**という考え方を、データに基づいて論理的に裏付けることができます。

SHAP値を用いた寄与度分析

AIモデルがなぜその予測結果を出したのか、その根拠を分かりやすく説明するために、SHAP(SHapley Additive exPlanations)という手法を用います。

import shap

explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_val)

shap.summary_plot(shap_values, X_val)

もしショールーミング指数などのデータが予測の重要な要因として上位に来ていれば、ショールーミング行動が需要予測に大きく寄与していることが確認できます。これは、店舗スタッフに対して「店舗での丁寧な接客が、結果的にECの売上として会社全体に貢献している」と伝えるための、客観的な根拠にもなります。


6. 本番運用に向けたMLOpsとモニタリング

AIモデルは構築して終わりではありません。特に消費者の行動変化は早いため、実運用に乗せた後も継続的な監視と調整が必要です。

モデルの再学習サイクルとドリフト検知

ショールーミング行動の傾向は、季節やキャンペーンによって変化します。例えば、大型セールの時期は、店舗で実物を確認する余裕がなく、ECで即決する行動が増える可能性があります。

このような傾向の変化を検知するために、データの前提が変わっていないかを監視する仕組み(ドリフト検知)を導入します。

  • 入力データの分布監視: showrooming_indexの平均値が急激に変化していないか?
  • 予測精度の監視: 実績値との乖離(Residual)が許容範囲を超えていないか?

週次または月次でAIモデルを再学習させる仕組みを構築し、常に最新のトレンドを予測に反映させ続けることが、安定した運用の鍵となります。

API化と既存基幹システムへの連携パターン

予測結果を実際の業務で活用するためには、既存の基幹システム(ERPや在庫管理システム)とスムーズに連携させることが不可欠です。

一般的なアプローチとしては、予測を行うシステムを独立した環境(コンテナなど)で稼働させます。そして、夜間の自動処理で翌日以降の需要を予測し、その結果をデータベースやファイル経由で既存の在庫管理システムに連携する形式が、運用しやすく安全な方法として多く採用されています。

まとめ

データセットの作成 - Section Image 3

ショールーミングを「見えない脅威」から「予測可能な指標」に変えるためのアプローチについて解説しました。

重要なポイントを振り返ります。

  1. データ統合: 店舗とECを分断せず、相互作用(Interaction)をデータ化する。
  2. 特徴量設計: ショールーミング指数やラグ特徴量を用いて、因果関係をモデルに教える。
  3. カスタム損失関数: ビジネスゴール(機会損失の最小化)に合わせて、予測の「癖」を調整する。

このようなモデルを業務に組み込むことで、単に予測精度が向上するだけでなく、「店舗の役割」を再定義し、組織全体の評価指標を見直すきっかけにもなります。

しかし、実際のデータ環境は企業ごとに千差万別です。レジデータの仕様が古い、ECのログとの紐付けが不完全であるといった課題に直面することも少なくありません。

もし、「データの準備段階で課題を感じている」「カスタム損失関数の設計が自社のビジネスモデルに合っているか不安だ」といった状況であれば、専門家に相談し、ROI(投資対効果)最大化に向けたアーキテクチャ設計のサポートを受けることも一つの有効な手段です。
自社のデータ環境を客観的に診断し、既存の業務フローに最適な形でAIを導入することで、データに眠る「隠れた需要」を確実なビジネス価値へと変換していくことができるはずです。

ショールーミングを収益に変える需要予測:LightGBMとカスタム損失関数による在庫最適化実装ガイド - Conclusion Image

コメント

コメントは1週間で消えます
コメントを読み込み中...