TVP-VAR、MCMC、およびスパースシミュレーション平滑化

[ ]:
%matplotlib inline

from importlib import reload
import numpy as np
import pandas as pd
import statsmodels.api as sm
import matplotlib.pyplot as plt

from scipy.stats import invwishart, invgamma

# Get the macro dataset
dta = sm.datasets.macrodata.load_pandas().data
dta.index = pd.date_range('1959Q1', '2009Q3', freq='QS')

背景

線形ガウス状態空間モデルのベイズ解析は、特にデータとモデルパラメータに条件付けられた未観測の状態ベクトルの共通事後分布からのサンプリングの進展により、近年では一般的かつ比較的簡単に行えるようになっています(特にCarter and Kohn (1994)、de Jong and Shephard (1995)、Durbin and Koopman (2002)を参照)。これは、Gibbsサンプリングを用いたMCMCアプローチに特に有用です。

これらの手法は、再帰的なカルマンフィルタと平滑化の前進/後進処理を利用しますが、最近の別の研究では異なるアプローチを取り、状態ベクトル全体の事後結合分布を一度に構築しています。特に、経済学的時系列の処理についてはChan and Jeliazkov (2009)を、より一般的な調査についてはMcCausland et al. (2011)を参照してください。特に、事後平均と精度行列が明示的に構築され、後者は疎なバンド行列となります。その後、疎なバンド行列のCholesky分解の効率的なアルゴリズムが活用され、メモリコストが削減され、性能が向上する可能性があります。McCausland et al. (2011)に従い、この方法を「Choleskyファクターアルゴリズム(CFA)」アプローチと呼びます。

CFAベースのシミュレーション平滑化は、より典型的なカルマンフィルタおよび平滑化(KFS)に基づくものと比較して、いくつかの利点と欠点があります。

CFAの利点

  • 事後結合分布の導出は比較的簡単で理解しやすい。

  • 一部のケースでは、KFSアプローチよりも高速でメモリ効率が良い場合がある

    • このノートブックの最後の付録で、TVP-VARモデルの2つのシミュレーション平滑化の性能について簡単に議論します。まとめると、単一のマシンでの簡単なテストでは、TVP-VARモデルに対するCFAとKFSの実装はほぼ同じ実行時間であり、どちらの実装もChan and Jeliazkov (2009)によって提供されたMatlabで書かれた複製コードよりも約2倍速いことが示されています。

CFAの欠点

主な欠点は、この方法がKFSアプローチの一般性に達していないことです(少なくとも現時点では)。例えば:

  • 観測方程式や状態方程式において、低ランクの誤差項が含まれるモデルには使用できません。

    • これの意味するところは、自己回帰モデルでの高次ラグを処理するために状態方程式に恒等行列を含めるという、典型的な状態空間モデルの手法が適用できないことです。これらのモデルはCFAアプローチで処理できますが、各ラグごとにわずかに異なる実装を必要とします。

    • 例えば、ARMAやVARMAプロセスを状態空間形式で表現する標準的な方法では、観測方程式や状態方程式に恒等行列が含まれており、したがってChan and Jeliazkov (2009)で示されている基本的な公式はこれらのモデルには直ちには適用できません。

  • 状態の初期化や事前分布において、柔軟性が少ない。

Statsmodelsでの実装

ChanとJeliazkov(2009)で示された基本的な式に基づいたCFAシミュレーションスムーザーがStatsmodelsに実装されています。

注意点:

  • したがって、現時点でStatsmodelsのCFAシミュレーションスムーザーは、状態遷移が真の一次マルコフ過程である場合のみサポートしています(すなわち、一次マルコフ過程に積み重ねられたp階マルコフ過程はサポートしていません)。

  • これに対して、StatsmodelsのKFSスムーザーは完全に一般的であり、p階マルコフ過程を積み重ねたものや、観測方程式および状態方程式に他の恒等式が含まれるモデルを含む任意の状態空間モデルに使用できます。

KFSまたはCFAシミュレーションスムーザーは、simulation_smootherメソッドを使用して状態空間モデルから構築できます。基本的なアイデアを示すために、まずシンプルな例を考えます。

ローカルレベルモデル

ローカルレベルモデルは、観測された系列\(y_t\)を持続的なトレンド\(\mu_t\)と過渡的な誤差成分\(\varepsilon_t\)に分解します。

\[\begin{split}\begin{aligned} y_t & = \mu_t + \varepsilon_t, \qquad \varepsilon_t \sim N(0, \sigma_\text{irregular}^2) \\ \mu_t & = \mu_{t-1} + \eta_t, \quad ~ \eta_t \sim N(0, \sigma_\text{level}^2) \end{aligned}\end{split}\]

このモデルは、観測誤差項\(\varepsilon_t\)と状態革新項\(\eta_t\)がどちらも退化していないため、CFAシミュレーションスムーサーの要件を満たします。すなわち、これらの共分散行列はフルランクです。

このモデルをインフレに適用し、共状態ベクトルの事後分布からのサンプリングをシミュレーションすることを考えます。すなわち、次の確率分布からサンプリングすることに関心があります。

\[p(\mu^t \mid y^t, \sigma_\text{irregular}^2, \sigma_\text{level}^2)\]

ここで、\(\mu^t \equiv (\mu_1, \dots, \mu_T)'\)および\(y^t \equiv (y_1, \dots, y_T)'\)と定義します。

Statsmodelsでは、ローカルレベルモデルはより一般的な「未観測成分」モデルのクラスに分類され、次のように構築できます。

[ ]:
# Construct a local level model for inflation
mod = sm.tsa.UnobservedComponents(dta.infl, 'llevel')

# Fit the model's parameters (sigma2_varepsilon and sigma2_eta)
# via maximum likelihood
res = mod.fit()
print(res.params)

# Create simulation smoother objects
sim_kfs = mod.simulation_smoother()              # default method is KFS
sim_cfa = mod.simulation_smoother(method='cfa')  # can specify CFA method

シミュレーションスムーザーオブジェクト sim_kfssim_cfa には、シミュレーションスムージングを実行する simulate メソッドがあります。simulate が呼び出されるたびに、simulated_state 属性は事後分布からの新しいシミュレーション結果で再構築されます。

以下では、KFSおよびCFAアプローチを使用して、最大尤度パラメータ推定に基づいて、トレンドのシミュレートされた20のパスを構築します。

[ ]:
nsimulations = 20
simulated_state_kfs = pd.DataFrame(
    np.zeros((mod.nobs, nsimulations)), index=dta.index)
simulated_state_cfa = pd.DataFrame(
    np.zeros((mod.nobs, nsimulations)), index=dta.index)

for i in range(nsimulations):
    # Apply KFS simulation smoothing
    sim_kfs.simulate()
    # Save the KFS simulated state
    simulated_state_kfs.iloc[:, i] = sim_kfs.simulated_state[0]

    # Apply CFA simulation smoothing
    sim_cfa.simulate()
    # Save the CFA simulated state
    simulated_state_cfa.iloc[:, i] = sim_cfa.simulated_state[0]

以下の各方法を使用して作成したシミュレーションと観測データをプロットすると、これらの2つの方法が同じことをしているのが簡単にわかります。

[ ]:
# Plot the inflation data along with simulated trends
fig, axes = plt.subplots(2, figsize=(15, 6))

# Plot data and KFS simulations
dta.infl.plot(ax=axes[0], color='k')
axes[0].set_title('Simulations based on KFS approach, MLE parameters')
simulated_state_kfs.plot(ax=axes[0], color='C0', alpha=0.25, legend=False)

# Plot data and CFA simulations
dta.infl.plot(ax=axes[1], color='k')
axes[1].set_title('Simulations based on CFA approach, MLE parameters')
simulated_state_cfa.plot(ax=axes[1], color='C0', alpha=0.25, legend=False)

# Add a legend, clean up layout
handles, labels = axes[0].get_legend_handles_labels()
axes[0].legend(handles[:2], ['Data', 'Simulated state'])
fig.tight_layout();

モデルのパラメータの更新

シミュレーションスムーザーはモデルインスタンス(ここでは変数mod)に関連付けられています。モデルインスタンスが新しいパラメータで更新されるたびに、シミュレーションスムーザーはその新しいパラメータを考慮して、今後のsimulateメソッドの呼び出しで使用します。

これは、MCMCアルゴリズムに便利です。MCMCアルゴリズムでは、(a)モデルのパラメータを繰り返し更新し、(b)状態ベクトルのサンプルを引き、(c)モデルの新しいパラメータの値を引きます。

ここでは、スムーズなトレンドを生み出す異なるパラメータ化にモデルを変更し、シミュレーションされた値がどのように変化するかを示します(簡潔さのために、KFSアプローチからのシミュレーションのみを示しますが、CFAアプローチからのシミュレーションも同じです)。

[ ]:
fig, ax = plt.subplots(figsize=(15, 3))

# Update the model's parameterization to one that attributes more
# variation in inflation to the observation error and so has less
# variation in the trend component
mod.update([4, 0.05])

# Plot simulations
for i in range(nsimulations):
    sim_kfs.simulate()
    ax.plot(dta.index, sim_kfs.simulated_state[0],
            color='C0', alpha=0.25, label='Simulated state')

# Plot data
dta.infl.plot(ax=ax, color='k', label='Data', zorder=-1)

# Add title, legend, clean up layout
ax.set_title('Simulations with alternative parameterization yielding a smoother trend')
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles[-2:], labels[-2:])
fig.tight_layout();

応用:MCMCによるTVP-VARモデルのベイズ分析

ChanとJeliazkov(2009)が考慮した応用の一つは、ベイズ的ギブスサンプリング(MCMC)法を用いて推定された時間変動パラメータベクトル自己回帰(TVP-VAR)モデルです。彼らはこれを、以下の4つのマクロ経済時系列の共動をモデル化するために適用しています:

  • 実質GDP成長

  • インフレ

  • 失業率

  • 短期金利

我々は、Statsmodelsに含まれている非常に似たデータセットを使用して、彼らの例を再現します。

[ ]:
# Subset to the four variables of interest
y = dta[['realgdp', 'cpi', 'unemp', 'tbilrate']].copy()
y.columns = ['gdp', 'inf', 'unemp', 'int']

# Convert to real GDP growth and CPI inflation rates
y[['gdp', 'inf']] = np.log(y[['gdp', 'inf']]).diff() * 100
y = y.iloc[1:]

fig, ax = plt.subplots(figsize=(15, 5))
y.plot(ax=ax)
ax.set_title('Evolution of macroeconomic variables included in TVP-VAR exercise');

TVP-VARモデル

注意: このセクションはChan and Jeliazkov (2009) のセクション3.1に基づいており、詳細についてはその部分を参照できます。

通常の(時間不変型)VAR(1)モデルは次のように表されます:

\[\begin{aligned} y_t & = \mu + \Phi y_{t-1} + \varepsilon_t, \qquad \varepsilon_t \sim N(0, H) \end{aligned}\]

ここで、\(y_t\)は時点\(t\)で観測された\(p \times 1\)の変数ベクトルであり、\(H\)は共分散行列です。

TVP-VAR(1)モデルは、これを一般化して、係数が時間とともに変動することを許可します。すべてのパラメータをベクトル\(\alpha_t = \text{vec}([\mu_t : \Phi_t])\)として積み重ね、ここで\(\text{vec}\)は行列の列をベクトルに積み重ねる操作を示します。これらのパラメータの時間にわたる進化を次のようにモデル化します:

\[\alpha_{i,t+1} = \alpha_{i, t} + \eta_{i,t}, \qquad \eta_{i, t} \sim N(0, \sigma_i^2)\]

言い換えれば、各パラメータはランダムウォークに従って独立に進化します。

なお、\(\mu_t\)には\(p\)個の係数があり、\(\Phi_t\)には\(p^2\)個の係数があるため、完全な状態ベクトル\(\alpha\)\(p * (p + 1) \times 1\)の形状を持ちます。

TVP-VAR(1)モデルを状態空間形式に変換するのは比較的簡単で、実際には観測方程式をSUR形式に書き換えるだけです:

\[\begin{split}\begin{aligned} y_t & = Z_t \alpha_t + \varepsilon_t, \qquad \varepsilon_t \sim N(0, H) \\ \alpha_{t+1} & = \alpha_t + \eta_t, \qquad \eta_t \sim N(0, \text{diag}(\{\sigma_i^2\})) \end{aligned}\end{split}\]

ここで

\[\begin{split}Z_t = \begin{bmatrix} 1 & y_{t-1}' & 0 & \dots & & 0 \\ 0 & 0 & 1 & y_{t-1}' & & 0 \\ \vdots & & & \ddots & \ddots & \vdots \\ 0 & 0 & 0 & 0 & 1 & y_{t-1}' \\ \end{bmatrix}\end{split}\]

\(H\)がフルランクであり、各分散\(\sigma_i^2\)がゼロでない限り、このモデルはCFAシミュレーションスムーザーの要件を満たします。

また、初期状態\(\alpha_1\)の初期化/事前分布を指定する必要があります。ここではChanとJeliazkov(2009)に従い、\(\alpha_1 \sim N(0, 5 I)\)を使用しますが、拡散的にモデル化することもできます。

時間変動係数\(\alpha_t\)を除けば、推定する必要がある他のパラメータは、共分散行列\(H\)の項とランダムウォークの分散\(\sigma_i^2\)です。

StatsmodelsにおけるTVP-VARモデル

このモデルをStatsmodelsでプログラム的に構築するのは比較的簡単で、基本的に次の4つのステップがあります:

  1. sm.tsa.statespace.MLEModelのサブクラスとして新しいTVPVARクラスを作成する

  2. 状態空間システム行列の固定値を入力する

  3. \(\alpha_1\)の初期化を指定する

  4. 共分散行列\(H\)とランダムウォークの分散\(\sigma_i^2\)の新しい値で状態空間システム行列を更新するメソッドを作成する

これを行うために、まず注意すべきことは、Statsmodelsで使用される一般的な状態空間表現は次のようになります:

\[\begin{split}\begin{aligned} y_t & = d_t + Z_t \alpha_t + \varepsilon_t, \qquad \varepsilon_t \sim N(0, H_t) \\ \alpha_{t+1} & = c_t + T_t \alpha_t + R_t \eta_t, \qquad \eta_t \sim N(0, Q_t) \\ \end{aligned}\end{split}\]

次に、TVP-VAR(1)モデルは以下の特別な場合を示唆します:

  • 切片項はゼロ、すなわち \(c_t = d_t = 0\)

  • 設計行列 \(Z_t\) は時間変動しますが、その値は上記のように固定されています(すなわち、\(y_t\)の遅れと1の値が含まれています)

  • 観測共分散行列は時間変動しません、すなわち \(H_t = H_{t+1} = H\)

  • 遷移行列は時間変動せず、単位行列に等しい、すなわち \(T_t = T_{t+1} = I\)

  • 選択行列 \(R_t\) は時間変動せず、単位行列に等しい、すなわち \(R_t = R_{t+1} = I\)

  • 状態共分散行列 \(Q_t\) は時間変動せず、対角行列である、すなわち \(Q_t = Q_{t+1} = \text{diag}(\{\sigma_i^2\})\)

[ ]:
# 1. Create a new TVPVAR class as a subclass of sm.tsa.statespace.MLEModel
class TVPVAR(sm.tsa.statespace.MLEModel):
    # Steps 2-3 are best done in the class "constructor", i.e. the __init__ method
    def __init__(self, y):
        # Create a matrix with [y_t' : y_{t-1}'] for t = 2, ..., T
        augmented = sm.tsa.lagmat(y, 1, trim='both', original='in', use_pandas=True)
        # Separate into y_t and z_t = [1 : y_{t-1}']
        p = y.shape[1]
        y_t = augmented.iloc[:, :p]
        z_t = sm.add_constant(augmented.iloc[:, p:])

        # Recall that the length of the state vector is p * (p + 1)
        k_states = p * (p + 1)
        super().__init__(y_t, exog=z_t, k_states=k_states)

        # Note that the state space system matrices default to contain zeros,
        # so we don't need to explicitly set c_t = d_t = 0.

        # Construct the design matrix Z_t
        # Notes:
        # -> self.k_endog = p is the dimension of the observed vector
        # -> self.k_states = p * (p + 1) is the dimension of the observed vector
        # -> self.nobs = T is the number of observations in y_t
        self['design'] = np.zeros((self.k_endog, self.k_states, self.nobs))
        for i in range(self.k_endog):
            start = i * (self.k_endog + 1)
            end = start + self.k_endog + 1
            self['design', i, start:end, :] = z_t.T

        # Construct the transition matrix T = I
        self['transition'] = np.eye(k_states)

        # Construct the selection matrix R = I
        self['selection'] = np.eye(k_states)

        # Step 3: Initialize the state vector as alpha_1 ~ N(0, 5I)
        self.ssm.initialize('known', stationary_cov=5 * np.eye(self.k_states))

    # Step 4. Create a method that we can call to update H and Q
    def update_variances(self, obs_cov, state_cov_diag):
        self['obs_cov'] = obs_cov
        self['state_cov'] = np.diag(state_cov_diag)

    # Finally, it can be convenient to define human-readable names for
    # each element of the state vector. These will be available in output
    @property
    def state_names(self):
        state_names = np.empty((self.k_endog, self.k_endog + 1), dtype=object)
        for i in range(self.k_endog):
            endog_name = self.endog_names[i]
            state_names[i] = (
                ['intercept.%s' % endog_name] +
                ['L1.%s->%s' % (other_name, endog_name) for other_name in self.endog_names])
        return state_names.ravel().tolist()

上記のクラスは、任意のデータセットに対して状態空間モデルを定義しました。次に、先に作成した実質GDP成長率、インフレ率、失業率、金利を含むデータセットを使って、その具体的なインスタンスを作成する必要があります。

[ ]:
# Create an instance of our TVPVAR class with our observed dataset y
mod = TVPVAR(y)

HとQのアドホックパラメータを用いた予備調査

以下の分析では、MCMCイテレーションを開始するためにいくつかの初期パラメータ設定が必要です。Chan and Jeliazkov(2009)に従い、\(H\)をデータセットのサンプル共分散行列に設定し、各\(i\)について\(\sigma_i^2 = 0.01\)に設定します。

モデルについての推論を行うMCMCスキームを議論する前に、まずはこれらの初期パラメータを単に代入した場合のモデルの出力を考えます。これらのパラメータを埋めるために、先に定義したupdate_variancesメソッドを使用し、その後、これらのパラメータに条件付けてカルマンフィルタリングとスムージングを行います。

警告:この演習はあくまで説明のためのものです - モデルの実際の影響を意味のある方法で研究するためには、MCMC演習の出力を待つ必要があります

[ ]:
initial_obs_cov = np.cov(y.T)
initial_state_cov_diag = [0.01] * mod.k_states

# Update H and Q
mod.update_variances(initial_obs_cov, initial_state_cov_diag)

# Perform Kalman filtering and smoothing
# (the [] is just an empty list that in some models might contain
# additional parameters. Here, we don't have any additional parameters
# so we just pass an empty list)
initial_res = mod.smooth([])

initial_res変数は、これらの初期パラメータに条件付けられたカルマンフィルタリングおよび平滑化の出力を含んでいます。特に、私たちは「平滑化された状態」、すなわち\(E[\alpha_t \mid y^t, H, \{\sigma_i^2\}]\)に関心があるかもしれません。

まず、観測変数の方程式に分けて、時間に沿った係数をグラフ化する関数を作成しましょう。

[ ]:
def plot_coefficients_by_equation(states):
    fig, axes = plt.subplots(2, 2, figsize=(15, 8))

    # The way we defined Z_t implies that the first 5 elements of the
    # state vector correspond to the first variable in y_t, which is GDP growth
    ax = axes[0, 0]
    states.iloc[:, :5].plot(ax=ax)
    ax.set_title('GDP growth')
    ax.legend()

    # The next 5 elements correspond to inflation
    ax = axes[0, 1]
    states.iloc[:, 5:10].plot(ax=ax)
    ax.set_title('Inflation rate')
    ax.legend();

    # The next 5 elements correspond to unemployment
    ax = axes[1, 0]
    states.iloc[:, 10:15].plot(ax=ax)
    ax.set_title('Unemployment equation')
    ax.legend()

    # The last 5 elements correspond to the interest rate
    ax = axes[1, 1]
    states.iloc[:, 15:20].plot(ax=ax)
    ax.set_title('Interest rate equation')
    ax.legend();

    return ax

現在、私たちはスムージングされた状態に興味があります。これは、結果オブジェクトinitial_resstates.smoothed属性で利用できます。

下のグラフが示すように、初期のパラメータ設定は、いくつかの係数に大きな時間的変動を含んでいることを意味しています。

[ ]:
# Here, for illustration purposes only, we plot the time-varying
# coefficients conditional on an ad-hoc parameterization

# Recall that `initial_res` contains the Kalman filtering and smoothing,
# and the `states.smoothed` attribute contains the smoothed states
plot_coefficients_by_equation(initial_res.states.smoothed);

MCMCによるベイズ推定

次に、ChanとJeliazkov(2009)のアルゴリズム2で説明されているギブスサンプラー方式を実装します。

以下の(条件付き共役)事前分布を使用します:

\[\begin{split}\begin{aligned} H & \sim \mathcal{IW}(\nu_1^0, S_1^0) \\ \sigma_i^2 & \sim \mathcal{IG} \left ( \frac{\nu_{i2}^0}{2}, \frac{S_{i2}^0}{2} \right ) \end{aligned}\end{split}\]

ここで、\(\mathcal{IW}\)は逆ウィシャート分布、\(\mathcal{IG}\)は逆ガンマ分布を示します。事前のハイパーパラメータは次のように設定します:

\[\begin{split}\begin{aligned} v_1^0 = T + 3, & \quad S_1^0 = I \\ v_{i2}^0 = 6, & \quad S_{i2}^0 = 0.01 \qquad \text{各} ~ iについて \end{aligned}\end{split}\]
[ ]:
# Prior hyperparameters

# Prior for obs. cov. is inverse-Wishart(v_1^0=k + 3, S10=I)
v10 = mod.k_endog + 3
S10 = np.eye(mod.k_endog)

# Prior for state cov. variances is inverse-Gamma(v_{i2}^0 / 2 = 3, S+{i2}^0 / 2 = 0.005)
vi20 = 6
Si20 = 0.01

MCMCの反復を実行する前に、いくつかの実務的な手順があります:

  1. 状態ベクトル、観測共分散行列、状態誤差分散のドローを格納するための配列を作成します。

  2. 上記で説明したHとQの初期値を格納ベクトルに入れます。

  3. TVPVARインスタンスに関連するシミュレーションスムーザーオブジェクトを構築し、状態ベクトルのドローを行います。

[ ]:
# Gibbs sampler setup
niter = 11000
nburn = 1000

# 1. Create storage arrays
store_states = np.zeros((niter + 1, mod.nobs, mod.k_states))
store_obs_cov = np.zeros((niter + 1, mod.k_endog, mod.k_endog))
store_state_cov = np.zeros((niter + 1, mod.k_states))

# 2. Put in the initial values
store_obs_cov[0] = initial_obs_cov
store_state_cov[0] = initial_state_cov_diag
mod.update_variances(store_obs_cov[0], store_state_cov[0])

# 3. Construct posterior samplers
sim = mod.simulation_smoother(method='cfa')

以前と同様に、カルマンフィルタとスムーザーに基づくシミュレーションスムーザー、またはコレスキー分解アルゴリズムに基づくものを使用することができました。

[ ]:
for i in range(niter):
    mod.update_variances(store_obs_cov[i], store_state_cov[i])
    sim.simulate()

    # 1. Sample states
    store_states[i + 1] = sim.simulated_state.T

    # 2. Simulate obs cov
    fitted = np.matmul(mod['design'].transpose(2, 0, 1), store_states[i + 1][..., None])[..., 0]
    resid = mod.endog - fitted
    store_obs_cov[i + 1] = invwishart.rvs(v10 + mod.nobs, S10 + resid.T @ resid)

    # 3. Simulate state cov variances
    resid = store_states[i + 1, 1:] - store_states[i + 1, :-1]
    sse = np.sum(resid**2, axis=0)

    for j in range(mod.k_states):
        rv = invgamma.rvs((vi20 + mod.nobs - 1) / 2, scale=(Si20 + sse[j]) / 2)
        store_state_cov[i + 1, j] = rv

いくつかの初期サンプルを除去した後、残りの事後サンプルを使用して推論を行います。以下に、時間変動する回帰係数の事後平均をプロットします。

: これらのプロットは、Chan and Jeliazkov (2009) の公開版の図1にあるものとは異なりますが、http://joshuachan.org/code/code_TVPVAR.html で入手可能なMatlabの再現コードによって生成されたものに非常に似ています。)

[ ]:
# Collect the posterior means of each time-varying coefficient
states_posterior_mean = pd.DataFrame(
    np.mean(store_states[nburn + 1:], axis=0),
    index=mod._index, columns=mod.state_names)

# Plot these means over time
plot_coefficients_by_equation(states_posterior_mean);

Pythonには、ベイズモデルの探索をサポートするための多くのライブラリもあります。ここでは、arvizパッケージを使用して、共分散および分散パラメータの信頼区間を探索しますが、このパッケージは分析のためのはるかに広範なツールセットも提供しています。

[ ]:
import arviz as az

# Collect the observation error covariance parameters
az_obs_cov = az.convert_to_inference_data({
    ('Var[%s]' % mod.endog_names[i] if i == j else
     'Cov[%s, %s]' % (mod.endog_names[i], mod.endog_names[j])):
    store_obs_cov[nburn + 1:, i, j]
    for i in range(mod.k_endog) for j in range(i, mod.k_endog)})

# Plot the credible intervals
az.plot_forest(az_obs_cov, figsize=(8, 7));
[ ]:
# Collect the state innovation variance parameters
az_state_cov = az.convert_to_inference_data({
    r'$\sigma^2$[%s]' % mod.state_names[i]: store_state_cov[nburn + 1:, i]
    for i in range(mod.k_states)})

# Plot the credible intervals
az.plot_forest(az_state_cov, figsize=(8, 7));

付録:パフォーマンス

最後に、Jupyterノートブックのマジックコマンド%timeitを使用して、KFSとCFAシミュレーションスムーザーのパフォーマンスを比較するためのいくつかの簡単なテストを実行します。

1つの注意点は、KFSシミュレーションスムーザーが後部状態ベクトルのシミュレーションだけでなく、さまざまな出力を生成する可能性があり、これらの追加計算が結果にバイアスを与える可能性があることです。結果を比較可能にするために、simulation_output引数を使用してKFSシミュレーションスムーザーに状態のシミュレーションのみを計算させることにします。

[ ]:
from statsmodels.tsa.statespace.simulation_smoother import SIMULATION_STATE

sim_cfa = mod.simulation_smoother(method='cfa')
sim_kfs = mod.simulation_smoother(simulation_output=SIMULATION_STATE)

Then we can use the following code to perform a basic timing exercise:

%timeit -n 10000 -r 3 sim_cfa.simulate()
%timeit -n 10000 -r 3 sim_kfs.simulate()

On the machine this was tested on, this resulted in the following:

2.06 ms ± 26.5 µs per loop (mean ± std. dev. of 3 runs, 10000 loops each)
2.02 ms ± 68.4 µs per loop (mean ± std. dev. of 3 runs, 10000 loops each)

これらの結果は、少なくともこのモデルに関しては、KFSアプローチに対してCFAアプローチからの計算上の顕著な利得はないことを示唆しています。しかし、以下の可能性を排除するわけではありません:

  1. CFAシミュレーションスムーザーのStatsmodels実装は、さらに最適化できるかもしれません。

  2. CFAアプローチは、特定のモデル(例えば、endog変数が多いモデル)に対してのみ改善を示すかもしれません。

最初の可能性を評価するための簡単な方法は、CFAシミュレーションスムーザーのStatsmodels実装の実行時間を、ChanとJeliazkov(2009)の再現コードに含まれるMatlab実装と比較することです。この再現コードは、http://joshuachan.org/code/code_TVPVAR.html で利用可能です。

Statsmodels版のCFAシミュレーションスムーザーはCythonで書かれ、Cコードにコンパイルされていますが、Matlab版はMatlabのスパース行列機能を活用しています。その結果、コンパイルされたコードではないにもかかわらず、比較的良好なパフォーマンスを発揮することが期待できます。

このテストが行われたマシンでは、Matlab版は通常11,000回のMCMCループを70~75秒で実行しましたが、このノートブックでStatsmodelsのCFAシミュレーションスムーザー(上記参照)を使用したMCMCループは、同じく11,000回のイテレーションで40~45秒で実行されました。これは、StatsmodelsのCFAスムーザー実装がすでに比較的良好なパフォーマンスを発揮しているという証拠の一つです(ただし、追加の利得が可能であることを排除するわけではありません)。

参考文献

Carter, Chris K., and Robert Kohn. 「On Gibbs sampling for state space models.」 Biometrika 81, no. 3 (1994): 541-553.

Chan, Joshua CC, and Ivan Jeliazkov. 「Efficient simulation and integrated likelihood estimation in state space models.」 International Journal of Mathematical Modelling and Numerical Optimisation 1, no. 1-2 (2009): 101-120.

De Jong, Piet, and Neil Shephard. 「The simulation smoother for time series models.」 Biometrika 82, no. 2 (1995): 339-350.

Durbin, James, and Siem Jan Koopman. 「A simple and efficient simulation smoother for state space time series analysis.」 Biometrika 89, no. 3 (2002): 603-616.

McCausland, William J., Shirley Miller, and Denis Pelletier. 「Simulation smoothing for state–space models: A computational efficiency analysis.」 Computational Statistics & Data Analysis 55, no. 1 (2011): 199-212.


最終更新日: 2025年01月28日