メインコンテンツへスキップ
見出し画像

書記が数学やるだけ#939 非ガウス型状態空間モデル

    非線形の状態空間モデルについて。


    問題

    画像


    説明

    非ガウス型状態空間モデルにおける予測・フィルター・平滑化は以下のように一般化される。

    画像


    粒子フィルター(逐次モンテカルロ法)は、ベイズ統計学における事後分布の再帰的推定を、数式による厳密解から数値シミュレーションによる近似解で表す手法である。

    画像


    解答

    1. 一期先予測 (One-step Ahead Prediction)

    定義: 時刻 t−1 までの観測値に基づき、時刻 t の状態を推定するプロセス。
    導出: チャップマン・コルモゴロフ方程式(Chapman-Kolmogorov equation)を用いる。
    意味: システムモデル p(xt ∣xt−1 ) を介して、前時刻の事後分布を現時刻へ遷移(伝播)させる。

    2. フィルタリング (Filtering)

    定義: 最新の観測値 yt  を取り込み、一期先予測分布を修正して現在の事後分布を得るプロセス。
    導出: ベイズの定理(Bayes' theorem)に基づく。
    意味: 観測モデル(尤度) p(yt ∣xt ) によって予測の不確実性を縮小させ、現在の状態に関する最新の知見を構成する。

    画像


    3. 平滑化 (Smoothing)

    定義: 全期間(1 から T まで)の観測値 y1:T  を用いて、過去の任意の時刻 t (t<T) の状態を再推定するプロセス。
    導出: 固定区間平滑化(Fixed-interval smoothing)では、逆向きの再帰計算を行う。
    意味: 未来の観測情報 yt+1:T  を過去へ逆伝播させることで、フィルタリング時に生じていた推定のラグや不確実性を低減し、より精緻な状態軌跡を復元する。

    画像


    非ガウス型状態空間モデルの事例について,まずはコーシー分布ノイズを含むデータについて「線形ガウスを仮定するKF」と「非ガウスを許容するPF」の両方で推定する。

    import numpy as np
    import matplotlib.pyplot as plt
    from scipy.stats import cauchy, norm
    
    # --- 1. 地震波風データの生成 (Non-Gaussian Data) ---
    np.random.seed(7)
    T = 300
    x_true = np.zeros(T)
    y = np.zeros(T)
    
    for t in range(1, T):
        # システムノイズ: 95%は微小変動、5%でコーシー分布によるジャンプ
        if np.random.rand() > 0.95:
            v = cauchy.rvs(loc=0, scale=2.0)
        else:
            v = np.random.normal(0, 0.02)
        x_true[t] = x_true[t-1] + v
        y[t] = x_true[t] + np.random.normal(0, 0.5)
    
    # --- 2. 粒子フィルタ (PF) の推定 ---
    N = 2000
    particles = np.zeros(N)
    weights = np.ones(N) / N
    x_est_pf = np.zeros(T)
    
    for t in range(T):
        # 予測: システムモデルにCauchy分布を仮定
        particles += cauchy.rvs(loc=0, scale=0.1, size=N)
        # 更新: 観測尤度 (Gaussian)
        likelihood = norm.pdf(y[t], loc=particles, scale=0.5)
        weights *= likelihood
        weights += 1e-300
        weights /= np.sum(weights)
        # 推定
        x_est_pf[t] = np.sum(particles * weights)
        # リサンプリング
        if 1.0 / np.sum(weights**2) < N/2:
            indices = np.random.choice(range(N), size=N, p=weights)
            particles = particles[indices]
            weights = np.ones(N) / N
    
    # --- 3. 線形カルマンフィルタ (KF) の推定 ---
    x_kf = 0.0
    P_kf = 1.0
    Q_kf = 0.1  # ガウス分布を仮定したプロセスノイズ
    R_kf = 0.25 # 観測ノイズ
    x_est_kf = np.zeros(T)
    
    for t in range(T):
        # 予測
        x_pred = x_kf
        P_pred = P_kf + Q_kf
        # 更新
        K = P_pred / (P_pred + R_kf)
        x_kf = x_pred + K * (y[t] - x_pred)
        P_kf = (1 - K) * P_pred
        x_est_kf[t] = x_kf
    
    # --- 4. 可視化 (2つのパネルに分割) ---
    fig, axes = plt.subplots(2, 1, figsize=(15, 10), sharex=True)
    
    # 上段: 線形カルマンフィルタの限界
    axes[0].plot(x_true, color='black', lw=2, label='True Signal')
    axes[0].plot(x_est_kf, color='green', lw=1.5, label='Linear KF Estimate')
    axes[0].scatter(range(T), y, color='gray', s=5, alpha=0.3, label='Observations')
    axes[0].set_title("Failure of Linear Model (Kalman Filter) on Abrupt Changes")
    axes[0].legend()
    axes[0].grid(True, alpha=0.3)
    
    # 下段: 粒子フィルタによる高精度推定
    axes[1].plot(x_true, color='black', lw=2, label='True Signal')
    axes[1].plot(x_est_pf, color='red', lw=1.5, label='Particle Filter Estimate')
    axes[1].scatter(range(T), y, color='gray', s=5, alpha=0.3, label='Observations')
    axes[1].set_title("Success of Non-Gaussian Model (Particle Filter)")
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()


    線形モデル(カルマンフィルタ)では,地震波のような急激なステップ状の変化(ジャンプ)に対し、推定線がなだらかに追従してしまい、大きなラグが生じる。一方で非ガウス型状態空間モデル(粒子フィルタ)では,コーシー分布をシステムモデルに組み込んでいるため,大きな変化を「あり得る事象」として許容し、垂直に近い形でジャンプに即応する。

    画像


    次に,観測方程式が2乗であるために正負の判断が困難になる状況で,両者の挙動がいかに異なるかを示す。

    import numpy as np
    import matplotlib.pyplot as plt
    from scipy.stats import norm
    
    # 再現性のためのシード設定
    np.random.seed(42)
    
    # --- 1. データの生成 (Highly Non-linear & Periodic Model) ---
    T = 100
    sigma_v = 1.0
    sigma_w = 1.0
    
    x_true = np.zeros(T)
    y = np.zeros(T)
    x_now = 0.1
    
    for t in range(T):
        v = np.random.normal(0, sigma_v)
        w = np.random.normal(0, sigma_w)
        # 非線形な状態遷移
        x_now = x_now/2 + 25*x_now/(1 + x_now**2) + 8*np.cos(1.2*t) + v
        x_true[t] = x_now
        # 二乗を含む観測方程式
        y[t] = (x_now**2)/20 + w
    
    # --- 2. 粒子フィルター (PF) の推定 ---
    N = 1000
    particles = np.random.normal(0, 5, N)
    weights = np.ones(N) / N
    x_est_pf = np.zeros(T)
    
    for t in range(T):
        # 予測
        v = np.random.normal(0, sigma_v, N)
        particles = particles/2 + 25*particles/(1 + particles**2) + 8*np.cos(1.2*t) + v
        # 更新 (尤度計算)
        likelihood = norm.pdf(y[t], loc=(particles**2)/20, scale=sigma_w)
        weights *= likelihood
        weights += 1e-300
        weights /= np.sum(weights)
        # 推定 (加重平均)
        x_est_pf[t] = np.sum(particles * weights)
        # リサンプリング
        if 1.0 / np.sum(weights**2) < N/2:
            indices = np.random.choice(N, size=N, p=weights)
            particles = particles[indices]
            weights = np.ones(N) / N
    
    # --- 3. 線形カルマンフィルター (KF) の推定 ---
    x_kf = 0.0
    P_kf = 10.0
    Q_kf = 5.0 # 無理やり追従させるための大きめのシステムノイズ
    R_kf = 1.0
    x_est_kf = np.zeros(T)
    
    for t in range(T):
        # 予測 (線形近似 F=1)
        x_pred = x_kf
        P_pred = P_kf + Q_kf
        # 更新 (線形近似 H=1, y=x+w と仮定)
        K = P_pred / (P_pred + R_kf)
        x_kf = x_pred + K * (y[t] - x_pred)
        P_kf = (1 - K) * P_pred
        x_est_kf[t] = x_kf
    
    # --- 4. 結果の可視化 (2つのパネル) ---
    fig, axes = plt.subplots(2, 1, figsize=(15, 10), sharex=True)
    
    # 上段: 線形KF (失敗例)
    axes[0].plot(x_true, color='black', lw=2, linestyle='--', label='True State (Non-linear)')
    axes[0].plot(x_est_kf, color='green', lw=1.5, label='Linear KF Estimate (Misfit)')
    axes[0].scatter(range(T), y, color='blue', s=10, alpha=0.3, label='Observed (y)')
    axes[0].set_title("Failure of Linear Model (Kalman Filter)")
    axes[0].legend()
    axes[0].grid(True, alpha=0.3)
    
    # 下段: 粒子フィルター (成功例)
    axes[1].plot(x_true, color='black', lw=2, linestyle='--', label='True State (Non-linear)')
    axes[1].plot(x_est_pf, color='red', lw=1.5, label='Particle Filter Estimate')
    axes[1].scatter(range(T), y, color='blue', s=10, alpha=0.3, label='Observed (y)')
    axes[1].set_title("Success of Particle Filter on Highly Non-linear System")
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()


    線形カルマンフィルター (KF) は、事後分布を常に単峰性のガウス分布 N(x^t ,Pt ) と仮定する。観測モデルがのような非単調関数である場合、尤度関数は x の正負にピークを持つ多峰性を示す。しかしKFは分布を一つの平均値に集約しようとするため、多峰的な尤度を適切に処理できず、平均値が一方の局所解(プラス側)にトラップされてしまう。

    一方で粒子フィルター (PF) は、事後分布を経験分布として表現する。尤度 p(yt ∣xt ) が正負の領域で等しく高い値を持つとき、粒子群は自然に二峰性の分布を形成する。この「解の分岐」を保持したまま次時刻の遷移モデル p(xt+1 ∣xt ) を適用することで、時間発展の過程でシステムモデルと矛盾する側のピークが自然に淘汰され、非線形性が強い状況下でも正しい状態へと収束が可能となる。

    画像



    本記事のもくじはこちら:


    あなたへのおすすめ