間欠需要とは何か
需要予測と聞くと、売上の波を捉えてトレンドや季節性を分解して…みたいなイメージがあると思います。で、実際それでうまくいくケースは多いんですが、世の中には「ほとんどの期間で需要がゼロ」という厄介なデータが存在します。
これが間欠需要(intermittent demand)です。
たとえば航空機のスペアパーツとか、特殊な医療機器の交換部品とか。月次で見ても 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0 みたいなデータになります。需要が発生すること自体がレアイベントで、しかも発生したときの量もバラバラ。
在庫管理の文脈ではこういうアイテムが全体の50%以上を占めるケースもあって、地味に重要な問題なんですよね。SyntetosとBoylanの2005年の分類だと、ADI(Average Demand Interval:平均需要間隔)が1.32以上のものを間欠需要と呼んでいます。CV²(需要サイズの変動係数の二乗)も合わせて見ると、smooth / erratic / intermittent / lumpy の4象限に分かれるんですが、この辺の分類の話はまた別の機会に。
ARIMAやETSが間欠需要に向かない理由
最初に試したのがARIMAとETSでした。まあ時系列予測の定番ですし。
結果、予測値がほぼゼロ付近に張り付くか、逆にゼロの期間を全然再現できないかのどちらかでした。考えてみると当たり前で、ARIMAは「直近の値が次の値に影響する」という前提で動いています。先月の売上が高ければ今月もそこそこ高いだろう、みたいな連続的なつながりを捉えるモデルなんですよね。でも間欠需要の「ゼロが続いて突然ポンと需要が出る」パターンには、そんなつながりはほとんどありません。ETSも同様で、レベル・トレンド・季節性の分解が効くのは、ある程度連続的に値が観測される場合です。
もう少し具体的に言うと:
- ゼロ膨張(zero-inflation): 観測値の大半がゼロなので、モデルが「ゼロに近い値」を出力するのが最適解になってしまう。平均を取るとそうなる
- 需要発生タイミングの不規則性: いつ需要が発生するかのパターンが掴めない。季節性があるケースもあるけど、ノイズに埋もれがち
- 需要サイズの分散: 発生したときの量のばらつきが大きい
で、こういう問題に対して「需要の発生」と「需要のサイズ」を分離してモデリングしようという発想が生まれます。ここからが本題です。
Croston法:需要サイズと需要間隔の分離モデル
1972年にJ.D. Crostonが提案した方法で、間欠需要予測のベースラインと言っていいモデルです。発想はシンプルで、時系列を2つの系列に分解します。
- \( z_t \):需要が発生したときの需要サイズ(非ゼロの値だけ取り出す)
- \( p_t \):需要が発生するまでの需要間隔(前回の非ゼロからの期間数)
この2つをそれぞれ独立に単純指数平滑法(SES)で推定します。
需要が発生した時点( ( y_t > 0 ) )でのみ更新:
需要が発生しなかった時点( ( y_t = 0 ) )では更新しません:
最終的な1期あたりの需要予測は:
需要サイズの期待値を需要間隔の期待値で割る、というのは直感的にも理解しやすいですね。たとえば平均して3期に1回、平均サイズ5の需要があるなら、1期あたりの期待需要は 5/3 ≈ 1.67 という計算です。
ただ、このモデルには問題があって、\( \hat{z}_t / \hat{p}_t \) の期待値は \( E[z] / E[p] \) と一致しません。比率の期待値と期待値の比率は違うので(Jensenの不等式的な話)、Crostonの推定量には上方バイアスがあることがSyntetosとBoylanによって示されています。
CrostonSBA:Croston法のバイアスを補正するSyntetos-Boylan近似
2001年にSyntetosとBoylanが提案したのがCrostonSBA(Syntetos-Boylan Approximation)です。名前の通り、Crostonの方法のバイアスを補正します。
更新式自体はCrostonと同じです。違いは最終的な予測値の計算:
補正係数 (1 – α/2)を掛けるだけ。たったこれだけなんですが、理論的にはこの補正がCrostonの上方バイアスをかなり軽減します。
α= 0.1なら補正係数は0.95、α= 0.3なら0.85。平滑化パラメータが大きいほど補正が強くなるのは、αが大きいほどバイアスが大きくなることに対応しています。
正直、実装上はほぼCrostonと変わらないのに精度が改善するので、わざわざオリジナルのCrostonを使う理由はあまりないですね。
TSB法:需要発生確率を直接モデリングする手法
CrostonやCrostonSBAの問題点として、需要間隔 \( p_t \) をモデリングしている点が挙げられます。需要が長期間発生しないと、\( \hat{p}_t \) は更新されないまま固定されます。つまり、あるアイテムが徐々に陳腐化(obsolescence)して需要がなくなっていくような状況を反映できません。
2011年にTeunter、Syntetos、Babaiが提案したTSBモデルは、需要間隔の代わりに需要発生確率 \( d_t \) を直接モデリングします。ここが根本的に違う。
需要が発生した時点\( ( y_t > 0 ) \):
需要が発生しなかった時点( ( y_t = 0 ) ):
最終的な予測:
ここで \( \alpha \) と \( \beta \) はそれぞれ需要サイズと需要確率の平滑化パラメータです。Crostonと違って2つの平滑化パラメータを持つのも特徴ですね。
で、TSBの何が嬉しいかというと、需要が発生しない期間が続くと \( \hat{d}_t \) が自然に減衰する点です。需要が来ないたびに \( \hat{d}_t = (1-\beta) \cdot \hat{d}_{t-1} \) と更新されるので、指数的にゼロに近づいていく。これは陳腐化が進むアイテムの挙動として直感的に正しい。
Crostonだと需要間隔の推定値は最後に需要が発生した時点で固定されたままなので、もう二度と売れないアイテムに対しても「いつか需要が来る」という予測を出し続けてしまいます。在庫管理の観点からすると、これは結構致命的です。
ただ、パラメータが2つになる分、推定が不安定になるケースもあります。データが少ないときは特に。この辺のトレードオフは実データでの検証が必要ですね。
ADIDA:時間的集約による間欠需要予測
ここまでのモデルは「需要サイズ」と「需要間隔(or 需要確率)」に分解するアプローチでしたが、ADIDAはまったく違う発想です。
ADIDA(Aggregate-Disaggregate Intermittent Demand Approach)は2011年にNikolopoulosらが提案した方法で、時間的集約(temporal aggregation)を使います。
手順はこうです:
1. 集約レベルの決定
集約レベル (k) を平均需要間隔から決めます:
2. 時系列の集約
元の時系列を (k) 期間ごとにまとめて(合算して)、集約時系列を作ります。たとえば元データが 0, 0, 3, 0, 0, 0, 1, 0, 0 で (k=3) なら、3, 0, 1 になるイメージです。…いや、正確にはバケットに分割して各バケットの合計を取るので 3, 1, 0 ですね。
集約するとゼロの比率が下がるので、通常の時系列手法が適用しやすくなります。
3. 集約レベルでの予測
集約された時系列に対してSES(単純指数平滑法)を適用して予測値 (\hat{Y}) を得ます。
4. 分割(Disaggregate)
集約レベルでの予測値を元のレベルに戻します:
均等に分割するだけ。拍子抜けするほどシンプルですが、これが意外と効くんですよね。
ADIDAの面白いところは、間欠需要の問題を「別のモデルに置き換える」のではなく、「データ側を変換して既存手法が使えるようにする」という発想な点です。集約レベルの決め方にも工夫の余地があって、ADIの代わりに最適な集約レベルを探索するアプローチ(IMAPAとか)も提案されています。
statsforecast・dartsでの実装方法
理論はわかった、じゃあ実際にどう使うのか。PythonだとstatsForecastとdartsの2つが主な選択肢です。
statsforecast(Nixtla)は間欠需要モデルが充実していて、ADIDA、CrostonClassic、CrostonOptimized、CrostonSBA、TSB、IMAPAが揃っています。使い方もシンプルで:
from statsforecast import StatsForecast
from statsforecast.models import (
ADIDA,
CrostonClassic,
CrostonSBA,
TSB,
)
models = [
ADIDA(),
CrostonClassic(),
CrostonSBA(),
TSB(alpha_d=0.2, alpha_p=0.2),
]
sf = StatsForecast(models=models, freq='M')
sf.fit(df)
forecast = sf.predict(h=12)dartsはより汎用的な時系列ライブラリですが、Crostonクラスで間欠需要にも対応しています:
from darts.models import Croston
model = Croston(version="sba") # "classic", "optimized", "sba", "tsb"
model.fit(series)
forecast = model.predict(n=12)dartsの場合はversionパラメータでCrostonのバリエーションを切り替えられます。statsforecastのほうがモデルの種類は多いですが、dartsは他の深層学習モデルとかと統一的なインターフェースで扱えるのが利点ですね。
あと、statsforecastにはCrostonOptimizedというのもあって、これは平滑化パラメータ \( \alpha \) を最適化するバージョンです。オリジナルのCrostonやSBAでは \( \alpha \) を固定値(0.1とか)にすることが多いんですが、データに合わせて最適化したほうが当然良いケースもあります。ただ、データが少ない間欠需要だと過学習のリスクもあるので、この辺は注意が必要です。
まとめ
結局、間欠需要予測のモデルは「需要が来るかどうか」と「来たらどれくらいか」をどう扱うかの設計思想の違いです。Crostonが分離の元祖、SBAがバイアス補正、TSBが確率モデリングで陳腐化対応、ADIDAがそもそも集約して普通のSESに持ち込む。
どれが最強かは…正直データ次第としか言えません。陳腐化が問題になるならTSB、シンプルに済ませたいならCrostonSBA、データの前処理で勝負したいならADIDA。実務だと複数のモデルを走らせてクロスバリデーションで選ぶのが無難ですかね。
あと触れてないですが、近年はLightGBMとかの機械学習モデルで間欠需要を扱う話もあって、需要発生の二値分類と需要サイズの回帰を組み合わせるtwo-partモデル(ハードルモデル)なんかも面白いです。Crostonの発想を機械学習で拡張した感じ。この辺はいつか試してみたい。