Mímisbrunnr知恵の泉

← 時系列分析 一覧

🎓 レベル:発展 | 重要度:A(必須)

📎 前提:カルマンフィルタベイズ構造時系列 | 数理:メトロポリスヘイスティングス(ベイズ・MCMC)・ベイズ更新と逐次推論(ベイズ・逐次更新)

要点(BLUF)

1. カルマンは状態を、MCMC はパラメータを

カルマンフィルタ で見たとおり、状態空間が線形ガウスで Q,HQ,H既知なら、状態の事後 p(αty1:t)p(\alpha_t\mid y_{1:t})正規分布のまま閉じて、漸化式で逐次に出ます。サンプリングは要りません。

ところが実務では Q,HQ,H未知です。ローカルレベルとローカルトレンドモデル はこれを最尤で点推定しました。その尤度こそ、カルマンフィルタが副産物として返す周辺尤度(予測誤差分解)

p(y1:nQ,H)=t=1nN ⁣(vt;0,Ft)p(y_{1:n}\mid Q,H) = \prod_{t=1}^{n} N\!\big(v_t;\, 0,\, F_t\big)

ここで vt=ytZatt1v_t=y_t - Z a_{t\mid t-1} はイノベーション、FtF_t はその分散——どちらも Q,HQ,H を与えればカルマン漸化式から出ます(カルマンフィルタ)。状態 α1:n\alpha_{1:n} はカルマンが解析的に積分消去してくれるので、尤度は Q,HQ,H だけの関数になります。statsmodels.fit() はこれを最大化して Q^,H^\hat Q,\hat H を1点で返す。

ベイズはここに事前を掛けます:

p(Q,Hy1:n)    p(y1:nQ,H)カルマンの周辺尤度  ×  p(Q)p(H)事前p(Q,H\mid y_{1:n}) \;\propto\; \underbrace{p(y_{1:n}\mid Q,H)}_{\text{カルマンの周辺尤度}} \;\times\; \underbrace{p(Q)\,p(H)}_{\text{事前}}

右辺は Q,HQ,H の2次元(解析的に解けない)なので、MCMC でサンプリングします(メトロポリスヘイスティングスハミルトニアンモンテカルロとNUTS)。役割分担が明快です。

flowchart TB
    MC["MCMC:パラメータ Q,H の事後を探索(外側)"] -->|"Q,H を提案"| KF["カルマン:状態を積分消去し周辺尤度 p(y|Q,H) を返す(内側・解析的)"]
    KF -->|"尤度 × 事前 = 事後の値"| MC
    PRI["事前 p(Q), p(H)"] --> MC
    MC --> POST["事後 p(Q,H|y):平均・中央値・モード・信用区間"]

実装上の補足:本コードは PyMC で状態 μt\mu_t とパラメータ Q,HQ,H を同時にサンプルします(データ拡大法)。線形ガウスなら状態をカルマンで積分消去しても、同時サンプルから Q,HQ,H の周辺だけ取り出しても、Q,HQ,H の事後は一致します。概念は「カルマンが尤度、MCMC がパラメータ」と捉えるのが見通し良いです。

2. 最尤の点 vs ベイズの事後分布

コード①:Q,H をベイズ推定し、最尤の点と重ねる

ローカルレベルとローカルトレンドモデル同じローカルレベル系列(真値 Q=0.5, H=4.0Q=0.5,\ H=4.0)を作り、(A) statsmodels最尤の点推定、(B) pymcQ,HQ,H の事後分布を出します。事後の平均・中央値・最頻(KDE)・95%区間を計算し、最尤の点が事後のどこに落ちるかを確かめます。水準のランダムウォークは非中心化(ベイズ構造時系列)。Windows は cores=1、数値は idata.posterior から取得(PyMC 6.0.1・ArviZ 1.2.0)。

import warnings
warnings.simplefilter("ignore")
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib  # 日本語ラベル用
import pymc as pm
import pytensor.tensor as pt
import arviz as az
from scipy.stats import gaussian_kde
from statsmodels.tsa.statespace.structural import UnobservedComponents

# 04-03 と同じローカルレベル系列(水準=RW, 観測=水準+ノイズ)
np.random.seed(2)
n = 120
Q_true, H_true = 0.5, 4.0
alpha = np.zeros(n); alpha[0] = 30.0
for t in range(1, n):
    alpha[t] = alpha[t-1] + np.random.normal(0, np.sqrt(Q_true))
y = alpha + np.random.normal(0, np.sqrt(H_true), n)

# (A) statsmodels の最尤推定(カルマン尤度を最大化=点推定)
res = UnobservedComponents(y, level="local level").fit(disp=False)
p = dict(zip(res.param_names, res.params))
Q_mle, H_mle = p["sigma2.level"], p["sigma2.irregular"]
print(f"最尤(MLE)  Q = {Q_mle:.3f}   H = {H_mle:.3f}")

# (B) ベイズ:Q,H に事前を置き MCMC で事後分布(状態も同時にサンプル=状態空間MCMC)
with pm.Model() as model:
    sigma_level = pm.HalfNormal("sigma_level", sigma=2.0)   # sqrt(Q) の事前
    sigma_obs = pm.HalfNormal("sigma_obs", sigma=5.0)       # sqrt(H) の事前
    mu0 = pm.Normal("mu0", mu=30.0, sigma=10.0)
    z = pm.Normal("z", 0.0, 1.0, shape=n-1)                 # 水準RWの非中心化
    mu = pm.Deterministic("mu", mu0 + pt.concatenate([[0.0], pt.cumsum(sigma_level*z)]))
    pm.Normal("y_obs", mu=mu, sigma=sigma_obs, observed=y)
    Q = pm.Deterministic("Q", sigma_level**2)
    H = pm.Deterministic("H", sigma_obs**2)
    idata = pm.sample(draws=1000, tune=1000, chains=2, cores=1,
                      random_seed=42, target_accept=0.97, progressbar=False)

print(f"発散数 = {int(idata.sample_stats['diverging'].values.sum())}")
print(f"R-hat  Q = {float(az.rhat(idata)['Q'].values):.3f}  H = {float(az.rhat(idata)['H'].values):.3f}")

Qs = idata.posterior["Q"].values.ravel()
Hs = idata.posterior["H"].values.ravel()
def mode_kde(s):
    g = np.linspace(s.min(), s.max(), 500)
    return g[gaussian_kde(s)(g).argmax()]
print("ベイズ事後(真値 Q=0.5, H=4.0):")
print(f"  Q  平均={Qs.mean():.3f}  中央値={np.median(Qs):.3f}  最頻(KDE)={mode_kde(Qs):.3f}  "
      f"95%区間=[{np.percentile(Qs,2.5):.3f}, {np.percentile(Qs,97.5):.3f}]")
print(f"  H  平均={Hs.mean():.3f}  中央値={np.median(Hs):.3f}  最頻(KDE)={mode_kde(Hs):.3f}  "
      f"95%区間=[{np.percentile(Hs,2.5):.3f}, {np.percentile(Hs,97.5):.3f}]")
print(f"  MLE は事後のどこか: Q_mle={Q_mle:.3f}(最頻 {mode_kde(Qs):.3f} の近く)  "
      f"H_mle={H_mle:.3f}(最頻 {mode_kde(Hs):.3f} の近く)")

# 図:Q,H の事後分布に MLE(点推定)と真値を重ねる
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
for a, s, mle, tv, name in [(ax[0], Qs, Q_mle, Q_true, "Q(状態ノイズ分散)"),
                            (ax[1], Hs, H_mle, H_true, "H(観測ノイズ分散)")]:
    a.hist(s, bins=40, density=True, color="C0", alpha=0.5, label="ベイズ事後")
    g = np.linspace(s.min(), s.max(), 300)
    a.plot(g, gaussian_kde(s)(g), color="C0", lw=1.5)
    a.axvline(mle, color="C3", lw=2, ls="-", label=f"MLE 点推定={mle:.2f}")
    a.axvline(tv, color="k", lw=1.5, ls="--", label=f"真値={tv:.2f}")
    a.axvline(np.median(s), color="C1", lw=1.5, ls=":", label=f"事後中央値={np.median(s):.2f}")
    a.set_xlabel(name); a.set_ylabel("事後密度"); a.legend(fontsize=8)
ax[0].set_title("分散パラメータ Q の事後分布")
ax[1].set_title("分散パラメータ H の事後分布")
plt.tight_layout(); plt.show()

出力:

最尤(MLE)  Q = 0.438   H = 5.215
発散数 = 0
R-hat  Q = 1.003  H = 1.003
ベイズ事後(真値 Q=0.5, H=4.0):
  Q  平均=0.580  中央値=0.501  最頻(KDE)=0.381  95%区間=[0.161, 1.472]
  H  平均=5.288  中央値=5.225  最頻(KDE)=5.197  95%区間=[3.711, 7.188]
  MLE は事後のどこか: Q_mle=0.438(最頻 0.381 の近く)  H_mle=5.215(最頻 5.197 の近く)

出力の意味:核心は最後の3行です。最尤の点推定 Qmle=0.438Q_{\text{mle}}=0.438 は、ベイズ事後の最頻(モード)0.3810.381 の近くに落ち、Hmle=5.215H_{\text{mle}}=5.215 も事後モード 5.1975.197 にほぼ重なります。これは偶然でなく必然——弱情報事前のもとで事後 \propto 尤度 ×\times 事前 \approx 尤度なので、事後を最大化する点(モード=MAP)が尤度を最大化する点(MLE)にほぼ一致するからです。「最尤はベイズ事後のモードの近似」が数値で見えました。

それ以上に重要なのは、ベイズが点でなく分布を返すこと。分散の事後は右に歪みます(QQ で平均 0.5800.580 > 中央値 0.5010.501 > 最頻 0.3810.381)。最尤の1点だけ見ると「左の裾の方の値」を握っていることになり、QQ の不確実性(95%区間 [0.161,1.472][0.161,\,1.472] は7倍以上の幅)を見落とします。真値はどうか——Q=0.5Q=0.5 は事後中央値 0.5010.501 にほぼ命中H=4.0H=4.0 は最尤・ベイズとも 5.25.2 付近と高めに出ましたが(このデータ実現が偶然 HH を高く支持した)、それでも真値 4.04.0 は95%区間 [3.711,7.188][3.711,\,7.188] の内側に収まっています。収束も良好(発散 0、R-hat 1.0031.003)。

3. なぜ事後分布を持つと嬉しいか

最尤の点推定 Q^,H^\hat Q,\hat H で予測区間を作ると、「Q,HQ,H がぴったり Q^,H^\hat Q,\hat H だ」と決めつけた区間になります。だが上で見たとおり QQ の事後は7倍幅の不確実性を持つ。ベイズはこのパラメータ不確実性を予測に伝播させます(ベイズ構造時系列 の事後予測)。短い系列・分散が識別しにくいモデルほど、この差は無視できません。最尤区間は楽観的に、ベイズ区間は正直に広がります。

また、Q,HQ,H事前を入れられるのも実利です。「観測ノイズはこの程度のはず」という装置仕様の知識を HH の事前に入れれば、データが少なくても破綻しにくい。最尤はこの注入がやりにくい。

4. 数式の直観

⚠️ よくある誤解・落とし穴

関連ノート