pyMC ベイズ・確率プログラミング

多変量ガウシアン・ランダム・ウォーク

import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pymc as pm
import pytensor

from scipy.linalg import cholesky

%matplotlib inline
RANDOM_SEED = 8927
rng = np.random.default_rng(RANDOM_SEED)
az.style.use("arviz-darkgrid")

 このノートブックは、多変量ガウシアン・ランダム・ウォーク(GRWs)を使った相関時系列適合の方法を示します。特に、私たちは、GRWsに依存したモデルに対して時系列データのベイジアン回帰を実行します。

 私たちは、3次元時系列としてデータを生成します。

  • i -> αi そして i -> βi , i ⊆ {0, 1, 2, 3, 4 }, は、二つの相関行列Σα Σβ のための 二つの3次元ガウシアンランダムウォークです。
  • 私たちは、インデックス$ を定義します。

 i[t] = j for t= 60j , 60j+1,...,60j + 59, and j=0,1,2,3,4,$

  • * は私たちが3 x 300 行列のjー番目のカラムとj-番目のベクターj =0,1,2,...299 のエントリーを乗算することを意味します。
  • εtは iid 正規エントリーN(0,σ2 ) の 3x 300行列

 系列yは、ステップ0,60,120,180,240の5度、GRW αのために変化します。その間、yは、GRW βのインクリメントのために ステップ1,60,120,180,240 で変化します。そして各ステップのβの重みは t/300です。直感的に、ノイズのあるシステム、それは300ステップの終端を5回経過し時に衝撃を受けます。しかし、βの衝撃の大きさは、段階的に各ステップで大きくなります。

データ生成

データを生成して図示しましょう。

D = 3  # Dimension of random walks
N = 300  # Number of steps
sections = 5  # Number of sections
period = N / sections  # Number steps in each section

Sigma_alpha = rng.standard_normal((D, D))
Sigma_alpha = Sigma_alpha.T.dot(Sigma_alpha)  # Construct covariance matrix for alpha
L_alpha = cholesky(Sigma_alpha, lower=True)  # Obtain its Cholesky decomposition

Sigma_beta = rng.standard_normal((D, D))
Sigma_beta = Sigma_beta.T.dot(Sigma_beta)  # Construct covariance matrix for beta
L_beta = cholesky(Sigma_beta, lower=True)  # Obtain its Cholesky decomposition

# Gaussian random walks:
alpha = np.cumsum(L_alpha.dot(rng.standard_normal((D, sections))), axis=1).T
beta = np.cumsum(L_beta.dot(rng.standard_normal((D, sections))), axis=1).T
t = np.arange(N)[:, None] / N
alpha = np.repeat(alpha, period, axis=0)
beta = np.repeat(beta, period, axis=0)
# Correlated series
sigma = 0.1
y = alpha + beta * t + sigma * rng.standard_normal((N, 1))

# Plot the correlated series
plt.figure(figsize=(12, 5))
plt.plot(t, y, ".", markersize=2, label=("y_0 data", "y_1 data", "y_2 data"))
plt.title("Three Correlated Series")
plt.xlabel("Time")
plt.legend()
plt.show();

モデル

 最初に私たちは、サンプリングの前に私たちのデータと時間パラメータを、再スケール化するためにスケーリングクラスを紹介します。その後、スケールされていないデータにマッチするために予測を再スケールします。

class Scaler:
    def __init__(self):
        mean_ = None
        std_ = None

    def transform(self, x):
        return (x - self.mean_) / self.std_

    def fit_transform(self, x):
        self.mean_ = x.mean(axis=0)
        self.std_ = x.std(axis=0)
        return self.transform(x)

    def inverse_transform(self, x):
        return x * self.std_ + self.mean_

 私たちは、ここでGRWs のαとβ、標準偏差σ、コレスキー行列のハイパーパラメータの 事前分布を果たす回帰モデルを構築します。私たちは、コレスキー行列のためにLKJ事前分布[Lewandowski et al., 2009 参考文献]を使います。(以下のコレスキー共分散の記事を参照してください。)

def inference(t, y, sections, n_samples=100):
    N, D = y.shape

    # Standardize y and t
    y_scaler = Scaler()
    t_scaler = Scaler()
    y = y_scaler.fit_transform(y)
    t = t_scaler.fit_transform(t)
    # Create a section index
    t_section = np.repeat(np.arange(sections), N / sections)

    # Create PyTensor equivalent
    t_t = pytensor.shared(np.repeat(t, D, axis=1))
    y_t = pytensor.shared(y)
    t_section_t = pytensor.shared(t_section)

    coords = {"y_": ["y_0", "y_1", "y_2"], "steps": np.arange(N)}
    with pm.Model(coords=coords) as model:
        # Hyperpriors on Cholesky matrices
        chol_alpha, *_ = pm.LKJCholeskyCov(
            "chol_cov_alpha", n=D, eta=2, sd_dist=pm.HalfCauchy.dist(2.5), compute_corr=True
        )
        chol_beta, *_ = pm.LKJCholeskyCov(
            "chol_cov_beta", n=D, eta=2, sd_dist=pm.HalfCauchy.dist(2.5), compute_corr=True
        )

        # Priors on Gaussian random walks
        alpha = pm.MvGaussianRandomWalk(
            "alpha", mu=np.zeros(D), chol=chol_alpha, shape=(sections, D)
        )
        beta = pm.MvGaussianRandomWalk("beta", mu=np.zeros(D), chol=chol_beta, shape=(sections, D))

        # Deterministic construction of the correlated random walk
        alpha_r = alpha[t_section_t]
        beta_r = beta[t_section_t]
        regression = alpha_r + beta_r * t_t

        # Prior on noise ξ
        sigma = pm.HalfNormal("sigma", 1.0)

        # Likelihood
        likelihood = pm.Normal("y", mu=regression, sigma=sigma, observed=y_t, dims=("steps", "y_"))

        # MCMC sampling
        trace = pm.sample(n_samples, tune=1000, chains=4, target_accept=0.9)

        # Posterior predictive sampling
        pm.sample_posterior_predictive(trace, extend_inferencedata=True)

    return trace, y_scaler, t_scaler, t_section

推定

 私たちは、モデルからサンプルします。そしてトレース、時間と空間のためのスケーリング関数、スケールされた時間にインデックスをリターンします。

trace, y_scaler, t_scaler, t_section = inference(t, y, sections)

 私たちは、モデルの収束を視覚チェックのためにarviz.plot_energy()を使ってエナジープロットを表示します。その後、arviz.plot_ppc()を使い、私たちは、観測データyに対して、事後予測サンプルの分布を図示します。この図はモデル(yの値は実際に、yのスケールされたバージョンに一致することに注意してください)の正確さの一般的な考えを提供します。

az.plot_energy(trace)
az.plot_ppc(trace);

事後分布可視化

 上のグラフはよく見えます。ここで、私たちは予測した3次元系列の平均に対して、観測した3次元系列を図示します。言い換えると、私たちはモデルから予測した回帰線に対してデータを図示します。

# Compute the predicted mean of the multivariate GRWs
alpha_mean = trace.posterior["alpha"].mean(dim=("chain", "draw"))
beta_mean = trace.posterior["beta"].mean(dim=("chain", "draw"))

# Compute the predicted mean of the correlated series
y_pred = y_scaler.inverse_transform(
    alpha_mean[t_section].values + beta_mean[t_section].values * t_scaler.transform(t)
)

# Plot the predicted mean
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
ax.plot(t, y, ".", markersize=2, label=("y_0 data", "y_1 data", "y_2 data"))
plt.gca().set_prop_cycle(None)
ax.plot(t, y_pred, label=("y_0 pred", "y_1 pred", "y_2 pred"))
ax.set_xlabel("Time")
ax.legend()
ax.set_title("Predicted Mean of Three Correlated Series");

 最後に、私たちは事後予測サンプルに対してデータを図示します。

# Rescale the posterior predictive samples
ppc_y = y_scaler.inverse_transform(trace.posterior_predictive["y"].mean("chain"))

fig, ax = plt.subplots(1, 1, figsize=(12, 6))
# Plot the data
ax.plot(t, y, ".", markersize=3, label=("y_0 data", "y_1 data", "y_2 data"))
# Plot the posterior predictive samples
ax.plot(t, ppc_y.sel(y_="y_0").T, color="C0", alpha=0.003)
ax.plot(t, ppc_y.sel(y_="y_1").T, color="C1", alpha=0.003)
ax.plot(t, ppc_y.sel(y_="y_2").T, color="C2", alpha=0.003)
ax.set_xlabel("Time")
ax.legend()

製作者

著者:Lorenzon Itoniazzi 2022年 10月

更新:Chris Fonnesbeck 2023年 2月 PyMC v5

参考文献

  • Daniel Lewandowski, Dorota Kurowicka, and Harry Joe. Generating random correlation matrices based on vines and extended onion method. Journal of multivariate analysis, 100(9):1989–2001, 2009.

-pyMC, ベイズ・確率プログラミング
-