RStan を利用して MCMCにおけるパラメーター非中心化の効果 を確認します。
ステップ1:サンプルデータの作成
まず、Rでシミュレーションデータを作成します。
- 意図的に グループ間のばらつきが小さく、各グループのデータが少ない 階層モデルをサンプルとします。
- 20個のグループ (
J=20
) - 各グループに3つのデータ点 (
N_per_group=3
) - グループ間のばらつき (
sigma_theta
) を意図的に小さく設定 (0.5
)
この設定により、非中心化しないモデルでは「Divergent Transitions」が発生しやすくなると考えられます。
# RStanの読み込み
library(rstan)
<- "D:/stan_output"
stan_output
# Stanの並列計算設定
rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())
# rstan のバージョン確認
cat("--- パッケージ rstan のバージョン確認 ---\n")
sapply(X = c("rstan"), packageVersion)
--- パッケージ rstan のバージョン確認 ---
$rstan
[1] 2 32 7
# 1. サンプルデータの生成
# ------------------------------------
<- 20250629
seed set.seed(seed)
<- 20 # グループ数
J <- 3 # 各グループのデータ数
N_per_group <- J * N_per_group # 全データ数
N
<- 5 # グループ平均の全体平均
mu_theta_true <- 0.5 # グループ平均のばらつき(小さく設定)
sigma_theta_true <- 2 # 観測誤差の標準偏差
sigma_y_true
# グループごとの真の平均 theta を生成
<- rnorm(J, mean = mu_theta_true, sd = sigma_theta_true)
theta_true
# 観測データ y を生成
<- c()
y <- c()
group_id for (j in 1:J) {
<- c(y, rnorm(N_per_group, mean = theta_true[j], sd = sigma_y_true))
y <- c(group_id, rep(j, N_per_group))
group_id
}
# Stanに渡すためのデータリスト
<- list(
stan_data N = N,
J = J,
y = y,
group_id = group_id
)
cat("--- Stanに渡すデータの確認 ---\n")
stan_data
--- Stanに渡すデータの確認 ---
$N
[1] 60
$J
[1] 20
$y
[1] 5.3924234 4.4354016 3.9094285 8.1098320 5.1711431 3.1792661
[7] 2.0543359 4.0288667 7.4404240 2.9698163 5.7729112 5.2764629
[13] 3.9012393 3.2080973 5.6239613 3.1773727 5.0124879 4.6049246
[19] 8.4291529 6.8349197 3.4123091 7.4894056 4.0500269 1.1571312
[25] 5.6930797 4.0882532 5.5924185 4.3354823 6.0164813 5.9066664
[31] 5.1354331 5.7345194 5.4324633 7.9379117 4.7057127 2.7067251
[37] -0.0947001 1.5147723 5.3050856 6.1310973 6.1882418 4.1333137
[43] 5.4043420 2.3422923 4.6041155 2.7290008 4.7206715 8.7479012
[49] 5.2370215 3.1579747 4.1220437 9.3209726 4.1703008 5.5390429
[55] 6.3114662 5.2919338 6.0616842 4.2912314 8.1858573 7.4744017
$group_id
[1] 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9
[26] 9 9 10 10 10 11 11 11 12 12 12 13 13 13 14 14 14 15 15 15 16 16 16 17 17
[51] 17 18 18 18 19 19 19 20 20 20
ステップ2:Stanコードの作成
次に、2種類のStanコードを作成します。
a. 非中心化しないモデル
パラメータtheta
がmu_theta
とsigma_theta
に直接依存する、直感的な書き方です。
<- "
model_code data {
int<lower=1> N;
int<lower=1> J;
vector[N] y;
int<lower=1, upper=J> group_id[N];
}
parameters {
real mu_theta; // グループ平均の全体平均
real<lower=0> sigma_theta; // グループ平均の標準偏差
vector[J] theta; // 各グループの平均
real<lower=0> sigma_y; // 観測誤差の標準偏差
}
model {
// 事前分布
mu_theta ~ normal(0, 10);
sigma_theta ~ cauchy(0, 2.5); // 弱情報事前分布
sigma_y ~ cauchy(0, 2.5);
// 階層構造
theta ~ normal(mu_theta, sigma_theta);
// 尤度
y ~ normal(theta[group_id], sigma_y);
}
"
b. 非中心化モデル
標準正規分布に従うtheta_raw
を導入し、そこからtheta
を生成します。
<- "
model_code_ncp data {
int<lower=1> N;
int<lower=1> J;
vector[N] y;
int<lower=1, upper=J> group_id[N];
}
parameters {
real mu_theta;
real<lower=0> sigma_theta;
vector[J] theta_raw; // 非中心化された「生の」パラメータ
real<lower=0> sigma_y;
}
transformed parameters {
vector[J] theta;
// thetaをtheta_rawから変換して生成
theta = mu_theta + theta_raw * sigma_theta;
}
model {
// 事前分布
mu_theta ~ normal(0, 10);
sigma_theta ~ cauchy(0, 2.5);
sigma_y ~ cauchy(0, 2.5);
// 階層構造 (非中心化)
theta_raw ~ normal(0, 1); // theta_rawが標準正規分布に従う
// 尤度
y ~ normal(theta[group_id], sigma_y);
}
"
ステップ3:RでStanを実行
作成した2つのモデルをRで実行します。
# 2. Stanモデルの実行
# ------------------------------------
# 非中心化しないモデルの実行
<- stan(
fit model_code = model_code,
data = stan_data,
chains = 4,
iter = 2000,
warmup = 1000,
seed = seed,
control = list(adapt_delta = 0.8) # デフォルト設定
)
# 非中心化モデルの実行
<- stan(
fit_ncp model_code = model_code_ncp,
data = stan_data,
chains = 4,
iter = 2000,
warmup = 1000,
seed = seed,
control = list(adapt_delta = 0.8)
)
# stanfit オブジェクトの保存
setwd(stan_output)
saveRDS(object = fit, file = "stan_fit.rds")
saveRDS(object = fit_ncp, file = "stan_fit_ncp.rds")
非中心化しないモデルでは以下の警告メッセージが出ますが、非中心化モデルでは警告メッセージは出ませんでした。
警告メッセージ:
1: There were 303 divergent transitions after warmup. See

Redirect
to find out why this is a problem and how to eliminate them.
2: There were 4 chains where the estimated Bayesian Fraction of Missing Information was low. See

Redirect
3: Examine the pairs() plot to diagnose sampling problems
4: The largest R-hat is 1.1, indicating chains have not mixed.
Running the chains for more iterations may help. See

Redirect
5: Bulk Effective Samples Size (ESS) is too low, indicating posterior means and medians may be unreliable.
Running the chains for more iterations may help. See

Redirect
6: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable.
Running the chains for more iterations may help. See

Redirect
ステップ4:結果の比較と可視化
実行結果を比較し、非中心化の効果を確かめます。
a. 診断情報の比較
まず、発散的移行(Divergent Transitions)の数と、有効サンプルサイズ(n_eff)を比較します。
# stanfit オブジェクトの読み込み
setwd(stan_output)
<- readRDS("stan_fit.rds")
fit <- readRDS("stan_fit_ncp.rds")
fit_ncp
# 3. 診断情報の比較
# ------------------------------------
# 発散的移行の数を取得
<- get_sampler_params(fit, inc_warmup = FALSE)
sampler_params <- sum(sapply(sampler_params, function(x) sum(x[, "divergent__"])))
divergences cat("非中心化しないモデルの発散的移行の数:", divergences, "\n")
<- get_sampler_params(fit_ncp, inc_warmup = FALSE)
sampler_params_ncp <- sum(sapply(sampler_params_ncp, function(x) sum(x[, "divergent__"])))
divergences_ncp cat("非中心化モデルの発散的移行の数:", divergences_ncp, "\n")
# パラメータサマリーの比較(特にn_effとRhat)
<- summary(fit, pars = c("mu_theta", "sigma_theta", "sigma_y"))$summary
summary <- summary(fit_ncp, pars = c("mu_theta", "sigma_theta", "sigma_y"))$summary
summary_ncp
cat("\n--- 非中心化しないモデルのサマリー ---\n")
print(summary)
cat("\n--- 非中心化モデルのサマリー ---\n")
print(summary_ncp)
非中心化しないモデルの発散的移行の数: 303
非中心化モデルの発散的移行の数: 0
--- 非中心化しないモデルのサマリー ---
mean se_mean sd 2.5% 25% 50%
mu_theta 4.9740548 0.024221281 0.2632660 4.47548205 4.7901500 4.9654186
sigma_theta 0.3713703 0.033129236 0.2652357 0.06019965 0.1441772 0.3151995
sigma_y 1.9100031 0.004518739 0.1662546 1.61252174 1.7956502 1.9046722
75% 97.5% n_eff Rhat
mu_theta 5.1626708 5.486415 118.13954 1.043297
sigma_theta 0.5250711 1.013060 64.09749 1.069294
sigma_y 2.0049586 2.273945 1353.66977 1.001293
--- 非中心化モデルのサマリー ---
mean se_mean sd 2.5% 25% 50%
mu_theta 4.9706889 0.003725087 0.2640666 4.44894312 4.7982835 4.9718528
sigma_theta 0.3785844 0.005678245 0.2800542 0.01736643 0.1562877 0.3211806
sigma_y 1.9095934 0.002714660 0.1858965 1.58428991 1.7821383 1.8945712
75% 97.5% n_eff Rhat
mu_theta 5.1392033 5.498809 5025.209 0.9994479
sigma_theta 0.5484783 1.062503 2432.518 1.0010267
sigma_y 2.0218879 2.325418 4689.338 1.0004101
非中心化しないモデルでは 303
の発散が発生し、sigma_theta
の n_eff
が 64
と小さいのに対し、非中心化モデルでは発散がゼロになり、n_eff
も 2432
へと改善していることがわかります。
b. トレースプロットによる確認
次に、この違いを視覚的に確認します。
# 4. ggplotによる可視化
# ------------------------------------
library(ggplot2)
<- stan_trace(fit, pars = "sigma_theta", inc_warmup = FALSE) +
p_trace labs(title = "非中心化しないモデルのトレースプロット") +
theme_bw() + theme(legend.position = "none")
<- stan_trace(fit_ncp, pars = "sigma_theta", inc_warmup = FALSE) +
p_trace_ncp labs(title = "非中心化モデルのトレースプロット") +
theme_bw() + theme(legend.position = "none")
# プロットを結合して表示
library(patchwork)
+ p_trace_ncp p_trace
非中心化モデルも決してきれいなトレースプロットではありませんが、非中心化しないモデルよりは改善されていることが確認できます。
結論
本シミュレーション、本サンプルデータにおいては、パラメータを非中心化することで発散的移行をなくし、収束が改善される ことが確認されました。
以上です。