理系学生日記

おまえはいつまで学生気分なのか

SARIMAXのメモリ消費問題とsimple_differencingによる解決

はじめに:遭遇したメモリエラー

時系列解析でSARIMAモデルのハイパーパラメータ探索をAICで行う際、statsmodels.tsa.SARIMAXを呼び出していると、以下のようなエラーに頻繁に遭遇しました。

numpy._core_.exceptions._ArrayMemoryError: Unable to allocate 4.27 GiB for an array with shape (n, n, d) and data type float64 ...

私の場合、

  • データは2,000行程度、float64型、外生変数なし
  • パラメータ1セットのfitでもメモリエラーでプロセスが落ちる
  • 発生したのはWindows環境(Macで試したわけではない)

という状況でした。
「データが小さいのに、なぜ?」と困惑した方も多いのではないでしょうか。 実際、2,000行程度のデータでメモリエラーが出るのは本来“ありえない”現象なのでは、、と思ってます。

なぜSARIMAXでメモリエラーが起きるのか?

SARIMAXは、時系列データの変動を「状態空間モデル」という枠組みで捉えています。状態空間モデルとは、観測できない“隠れた状態”の推移と、それに基づく観測値の生成過程を数式で表現する手法です。ARIMAやSARIMAもこの枠組みで一般化でき、複雑な時系列の特徴を柔軟に扱えるのが特徴です。まぁ僕はまだちゃんと理解できてなくて、時系列解析を学んでいるときにいきなりARIMAやSARIMAは状態空間モデルとして表現できるよと言われて「はぁ?」となったクチです。

で、SARIMAXは、状態空間モデルの内部で「差分系列」を逐次的に計算・保持します。このとき、BLAS/LAPACKの実装やPython環境によっては、不要に巨大な行列(例:shapeが(n, n, d)の三次元配列)を一時的に確保しようとし、データ量が小さくてもメモリ不足に陥ることがあるんじゃないかと。

simple_differencing=True でなぜ解決するのか?

sarimax(..., simple_differencing=True)を指定すると、モデル内部での差分計算をやめ、事前に差分済みデータを使って推定を行います。これにより、冗長なデータコピーや巨大な行列演算が不要となり、メモリ消費が劇的に減少しました。 実際にこのオプションで_ArrayMemoryErrorが解消し、処理も高速化されました。

実装例

import numpy as np
from statsmodels.tsa.statespace.sarimax import SARIMAX

y = np.random.randn(2000).astype(np.float64)
model = SARIMAX(y, order=(1,1,1), seasonal_order=(1,1,1,12),
                enforce_stationarity=False, enforce_invertibility=False,
                simple_differencing=True)
results = model.fit(disp=False)

メリット

simple_differencing=Trueを使うと、まず何より「メモリ消費が劇的に減る」ことを実感しました。これまでfitのたびにプロセスが落ちていたのが嘘のように、あっさり計算が終わります。計算時間も短くなり、パラメータ探索のストレスが大幅に減りました。statsmodelsの内部実装に起因する謎のエラーとも無縁になり、「これでようやく本来の時系列解析に集中できる」と感じました。

デメリット・注意点

実際にはまだデメリットを感じてないのですが、仕組み上「推定結果が微妙に変わる」ことには注意を払う必要がありそうです。特に欠損値や系列長の扱いが変わるため、理論的な厳密さや再現性を重視する場合は注意が必要です。

まとめ

SARIMAXのメモリ消費問題として、simple_differencing=Trueは、特にデータ量が小さいのにメモリエラーが出る場合、まず試すべき有効な解決策なように思いました。