Mímisbrunnr知恵の泉

← ベイズ統計 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:なぜサンプリングか | 数理:MCMC(マルコフ連鎖モンテカルロ)(統計・詳細釣り合いの証明)

要点(BLUF)

1. アルゴリズム

現在地 θ\theta から次の点を決める手順です(詳細釣り合い→定常分布の証明は MCMC(マルコフ連鎖モンテカルロ))。

  1. 提案:対称な提案分布から候補を出す。例:ランダムウォーク θ=θ+ε, εN(0,step2)\theta'=\theta+\varepsilon,\ \varepsilon\sim\mathcal N(0,\text{step}^2)
  2. 受容確率:対称提案なら α=min ⁣(1, π(θ)π(θ))\displaystyle\alpha=\min\!\left(1,\ \frac{\pi(\theta')}{\pi(\theta)}\right)
  3. 採否uUniform(0,1)u\sim\mathrm{Uniform}(0,1) を引き、uαu\le\alpha なら受容して移動、そうでなければ棄却して現在地にとどまる(その点も標本に記録)。

π\pi は事後 p(θD)=p(Dθ)p(θ)/Zp(\theta\mid D)=p(D\mid\theta)p(\theta)/Z ですが、受容確率では比なので

π(θ)π(θ)=p(Dθ)p(θ)/Zp(Dθ)p(θ)/Z=p(Dθ)p(θ)p(Dθ)p(θ)\frac{\pi(\theta')}{\pi(\theta)}=\frac{p(D\mid\theta')p(\theta')/Z}{p(D\mid\theta)p(\theta)/Z}=\frac{p(D\mid\theta')p(\theta')}{p(D\mid\theta)p(\theta)}

ZZ が消えます。計算できる分子(尤度×事前)だけで回せる——これが なぜサンプリングか の壁を越える仕掛けです。

2. numpy でフル実装し、Beta(16,8) を再現する

正規化していない事後 π(θ)θ15(1θ)7\pi(\theta)\propto\theta^{15}(1-\theta)^{7}Beta(2,2)\mathrm{Beta}(2,2) 事前 × 二項 n=20,k=14n{=}20,k{=}14)を目標に、MH で標本を取り、真の事後 Beta(16,8)\mathrm{Beta}(16,8) と一致するか見ます。対数で計算して桁あふれを防ぎます。

import numpy as np
from scipy import stats

# 目標:正規化なしの対数事後 log π(θ) = 15 log θ + 7 log(1-θ)  ([0,1] 外は -inf)
a0, b0, n, k = 2, 2, 20, 14
def log_target(t):
    if t <= 0 or t >= 1:
        return -np.inf
    return (a0 - 1 + k)*np.log(t) + (b0 - 1 + n - k)*np.log(1 - t)

def metropolis(step, N=40000, burn=2000, seed=0):
    rng = np.random.default_rng(seed)
    th = 0.5; lt = log_target(th)
    out = np.empty(N); acc = 0
    for i in range(N):
        cand = th + rng.normal(0, step)            # 対称ランダムウォーク提案
        lc = log_target(cand)
        if np.log(rng.uniform()) < lc - lt:        # u<exp(Δlog) ⇔ 受容
            th, lt = cand, lc; acc += 1
        out[i] = th                                # 棄却時は現在地を記録
    return out[burn:], acc/N

true = stats.beta(16, 8)
print(f"真の事後 Beta(16,8): 平均={true.mean():.4f}, 95%=[{true.interval(0.95)[0]:.3f}, {true.interval(0.95)[1]:.3f}]")
for step in [0.02, 0.1, 0.4]:
    s, ar = metropolis(step)
    lo, hi = np.quantile(s, [0.025, 0.975])
    print(f"  step={step:<4}: 受容率={ar:.2f}  標本平均={s.mean():.4f}  95%=[{lo:.3f}, {hi:.3f}]")

出力:

真の事後 Beta(16,8): 平均=0.6667, 95%=[0.471, 0.836]
  step=0.02: 受容率=0.93  標本平均=0.6699  95%=[0.463, 0.835]
  step=0.1 : 受容率=0.69  標本平均=0.6683  95%=[0.478, 0.837]
  step=0.4 : 受容率=0.28  標本平均=0.6663  95%=[0.469, 0.836]

出力の意味:どの step でも標本平均 0.667\approx0.667・95%区間 [0.47,0.84]\approx[0.47,0.84] と、真の事後 Beta(16,8)\mathrm{Beta}(16,8) を正しく再現しています。正規化定数を一度も計算せず、θ15(1θ)7\theta^{15}(1-\theta)^7 の値だけでこれができたのが MH の威力です。一方で受容率は step とともに 0.930.280.93\to0.28 と下がります。次節でこの調整を見ます。

3. 提案幅のトレードオフと、トレースで見る歩き方

サンプルの時間変化(トレース)とヒストグラムを描くと、連鎖が事後を埋めていく様子が見えます。

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
import japanize_matplotlib  # 日本語ラベル用

a0, b0, n, k = 2, 2, 20, 14
def log_target(t):
    if t <= 0 or t >= 1: return -np.inf
    return (a0-1+k)*np.log(t) + (b0-1+n-k)*np.log(1-t)

rng = np.random.default_rng(0)
th, lt, step = 0.5, log_target(0.5), 0.4
chain = np.empty(40000)
for i in range(40000):
    cand = th + rng.normal(0, step); lc = log_target(cand)
    if np.log(rng.uniform()) < lc - lt: th, lt = cand, lc
    chain[i] = th
chain = chain[2000:]                                  # バーンイン除去

fig, ax = plt.subplots(1, 2, figsize=(11, 4))
ax[0].plot(chain[:1500], lw=0.6); ax[0].set_title("トレース(最初の1500ステップ)")
ax[0].set_xlabel("ステップ"); ax[0].set_ylabel("θ")
ax[1].hist(chain, bins=60, density=True, alpha=0.6, label="MH標本")
xx = np.linspace(0, 1, 300)
ax[1].plot(xx, stats.beta(16, 8).pdf(xx), lw=2, label="真の事後 Beta(16,8)")
ax[1].set_title("標本ヒストグラム vs 真の事後"); ax[1].set_xlabel("θ"); ax[1].legend()
plt.tight_layout(); plt.show()

グラフの意味:左のトレースは水平な帯状にばらつき(定常に達したサイン)、上下に揺れながら事後の台 [0.45,0.85][0.45,0.85] を往復します。右のヒストグラムは真の事後 Beta(16,8)\mathrm{Beta}(16,8)(実線)にぴたりと重なります。トレースがドリフトしたり一点に張り付いたりせず帯状なら「収束していそう」と読めます(厳密な判定は 収束診断)。

⚠️ よくある誤解

関連ノート