はじめに:遭遇したメモリエラー
時系列解析で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は、特にデータ量が小さいのにメモリエラーが出る場合、まず試すべき有効な解決策なように思いました。