MCMCサンプリングにおける不定性とスィッチング
ベイズ分析における対応の試み
モデルのパラメーターの不定性の問題は、最小2乗法による重回帰分析の場合はコントラストの推定可能性の問題として扱われている(岡本、2014、pp. 39-42)。ベイズ分析におけるモデルは重回帰モデル以外においても不定性の問題が起こりうるが、簡単な2項分布モデルの場合においてパラメータの不定性の問題を見てみる。取り上げた2項分布モデルの場合、パラメータ推定におけるMCMCサンプリングのスィッチングの問題も観察されたので、その対応も試みた。スクリプトファイルは、まとめて圧縮ファイルindefswfiles.zipとした。
次のモデルを設定する。
物体AとBがあり、それぞれ+とーの2つの内部状態をとるものとする。
観測されるのは、AとBをまとめた状態ABで、
Aの状態が+でBの状態が+のとき、あるいはAの状態がーでBの状態がーのときに、ABの状態は+と観測さる。
Aの状態が+でBの状態がーのとき、あるいはAの事態がーでBの状態が+のときは、ABの状態はーと観測される。
Aの状態とBの状態は独立であるとし、それぞれの確率は以下のように表されるものとする。
このとき、
となる。
今、n回の観測においてABが+であった回数がk回であったとする。2項分布が適用できるとすると、
と置くことができる。
パラメータpは、データに対してモデル(3)により求めることができるが、pを与えるパラメータpaとpbは式(2)からは一意には決まらない。独立変数としてpaとpbをとったときのpの値の等高線図を描くと図1のようになっている。
図1
図1のグラフは、リスト1のスクリプトで描かれたものである。
また、式(2)において、paおよびpbを(1-pa)および(1-pb)と交換しても式(2)の値は変わらない。このため、MCMCサンプリングにおいてスィッチングが起こる。スィッチング対策例も示した。
モデル(3)によるベイズ分析を行うためにリスト2およびリスト3に示すスクリプトを用意した。リスト2では、Stanスクリプトのビルドと実行を行っている。Pystan3は、Windows上では直接実行できないので、WSLによりインストールしたUbuntu上で実行した。WSLではグラフィック描画ができないので、Stanスクリプトの実行により生成したMCMCサンプルの描画は、リスト2の実行で生成したMCMCサンプルをファイルに保存して、Windows上で実行したリスト3のスクリプトにより保存したMCMCサンプルを読み込んで分析・描画した。リスト2のスクリプトファイルとリスト3のスクリプトファイルを同じフォルダ内に置いておけば、リスト2の実行に続けてリスト3の実行を行うことは容易である。
リスト2の実行後、リスト3を実行すると、図2のグラフが表示される。
図2
パラメータpaとpbの事後分布は、0から1まで一様に広がっているが、これら2つのパラメータから算出されるpの分布は0.7付近に集まっている。式(3)によりパラメータpは、データに対応するモデルのパラメータとして定まるが、式(2)で定まるパラメータpaとpbは不定であることに対応している。個々のパラメータが不定のとき、パラメータの組み合わせによっては推定可能であることは、重回帰分析では推定可能性の問題として論じられている(岡本、2014、pp.39-42)。この意味で、パラメータpは推定可能であるといえる。
パラメータpa、pbの事後分布の裾が、0より左側、および1より右側に広がっているのは、KDEにより密度関数が推定されているためである。図2のWindowを閉じると、図3のヒストグラムが表示される。
図3
ヒストグラムでは、確率の事後分布は0から1の範囲に収まっている。
図3のWindowを閉じると、図4の散布図が表示される。
図4
点(pa, pb)の分布が、右上の領域と左下の領域に分かれているのは、paおよびpbと(1-pa)および(1-pb)の間のスィッチングであると考えられる。スィッチングを避ける方法として、ベイズ分析の場合は事前分布に制約を置くことが考えられるが、例をこの後で示した。
MCMCサンプリングのトレースプロット
パラメータpaとpbのMCMCサンプリングのトレース図を描くために、リスト4のスクリプトを作成して、Ubuntu 22において実行した。
図5に示すトレースプロットが得られた。
図5
パラメータpaとpbのスィッチングは、図4を見ると、右上のグループと左下のグループの間で起こっていると思われるので、これを防ぐために事前分布を次のように設定して、paとpbの組を左下のグループに制限してみた。
pa ~
uniform(0, 0.5);
pb ~
uniform(0, 0.5);
リスト5のスクリプトを、Python3.12/miniconda/Ubuntu24/WSL/Windows11において実行後、リスト6のスクリプトをPython3.13/Windows11において実行すると、図6のグラフが表示された。
図6
パラメータpaとpbの事後分布が左側に制限されていることがわかる。
図6のWindowを閉じると、図7のグラフが表示される。
図7
パラメータpaとpbの事後分布は、事前分布で制限された範囲でほぼ一様に分布しているが、これは不定性の反映であると思われる。
図7のWindowを閉じると、図8の散布図が表示される。
図8
散布図からは、スィッチングが起こっているようには見えない。
リスト1 等高線描画スクリプト(draw_contour.py)
import matplotlib.pyplot
as plt
import numpy
as np
import matplotlib.cm as cm
x = np.linspace(0.0,
1.0, 100)
y = np.linspace(0.0,
1.0, 100)
X, Y = np.meshgrid(x,
y)
Z = X*Y + (1-X)*(1-Y)
# p = pa * pb + (1 -
pa) * (1 - pb)
fig, ax = plt.subplots()
CS = ax.contour(X,
Y, Z)
plt.xlabel('pa', fontsize=14)
plt.ylabel('pb', fontsize=14)
ax.clabel(CS, fontsize=10)
ax.set_title('p=pa*pa+(1-pa)*(1-pb)', fontsize=16)
plt.show()
リスト2 MCMCサンプリングスクリプト(indef_switch_ba.py)
import stan
import arviz
as az
import pickle
import pandas as pd
StanCode = """
data {
int
k;
int
n;
}
parameters {
real<lower=0, upper=1> pa;
real<lower=0, upper=1> pb;
}
transformed parameters {
real<lower=0, upper=1> p;
p =
pa*pb + (1-pa)*(1-pb);
}
model {
pa ~
uniform(0, 1);
pb ~
uniform(0, 1);
k ~
binomial(n, p);
}
"""
sm = stan.build(StanCode, data = {'n':10, 'k':7})
fit = sm.sample()
infer_data = az.from_pystan(posterior=fit,
posterior_model=sm)
smry = az.summary(infer_data)
print(smry)
df = fit.to_frame()
print(df.describe().T)
with open('df.pkl',
'wb') as f:
pickle.dump(df, f)
リスト3 MCMCサンプルの分析(indef_switch_anal.py)
import pickle
import pandas as pd
import matplotlib.pyplot
as plt
import seaborn as sb
with open('df.pkl',
'rb') as f:
df
= pickle.load(f)
sb.kdeplot(df["pa"],
c='r', label='pa')
sb.kdeplot(df['pb'],
c='g', label='pb')
sb.kdeplot(df['p'],
c='b', label='p')
plt.xlabel('')
plt.legend()
plt.show()
plt.figure(figsize=(5,6))
plt.subplot(211)
plt.hist(df['pa'])
plt.xlabel('pa', fontsize=14)
plt.subplot(212)
plt.hist(df['pb'])
plt.xlabel('pb', fontsize=14)
plt.tight_layout()
plt.show()
plt.figure(figsize=(7,7))
plt.plot(df['pa'], df['pb'], 'o', markerfacecolor='none')
plt.xlabel('pa', fontsize=14)
plt.ylabel('pb', fontsize=14)
plt.show()
リスト4 Linux用スクリプト(indef_switch_linux.py)
import stan
import arviz
as az
import pandas as pd
import matplotlib.pyplot
as plt
import seaborn as sb
StanCode = """
data {
int
k;
int
n;
}
parameters {
real<lower=0, upper=1> pa;
real<lower=0, upper=1> pb;
}
transformed parameters {
real<lower=0, upper=1> p;
p =
pa*pb + (1-pa)*(1-pb);
}
model {
pa ~
uniform(0, 1);
pb ~
uniform(0, 1);
k ~
binomial(n, p);
}
"""
sm = stan.build(StanCode, data = {'n':10, 'k':7})
fit = sm.sample()
infer_data = az.from_pystan(posterior=fit,
posterior_model=sm)
smry = az.summary(infer_data)
print(smry)
az.plot_trace(infer_data,
var_names=['pa', 'pb'])
plt.show()
df = fit.to_frame()
print(df.describe().T)
sb.kdeplot(df["pa"],
c='r', label='pa')
sb.kdeplot(df['pb'],
c='g', label='pb')
sb.kdeplot(df['p'],
c='b', label='p')
plt.xlabel('')
plt.legend()
plt.show()
plt.figure(figsize=(5,6))
plt.subplot(211)
plt.hist(df['pa'])
plt.xlabel('pa', fontsize=14)
plt.subplot(212)
plt.hist(df['pb'])
plt.xlabel('pb', fontsize=14)
plt.tight_layout()
plt.show()
plt.figure(figsize=(7,7))
plt.plot(df['pa'], df['pb'], 'o', markerfacecolor='none')
plt.xlabel('pa', fontsize=14)
plt.ylabel('pb', fontsize=14)
plt.show()
リスト5 スィッチングを避ける事前分布の設定(indef_nswitch_ba.py)
import stan
import arviz
as az
import pickle
import pandas as pd
StanCode = """
data {
int
k;
int
n;
}
parameters {
real<lower=0, upper=1> pa;
real<lower=0, upper=1> pb;
}
transformed parameters {
real<lower=0, upper=1> p;
p =
pa*pb + (1-pa)*(1-pb);
}
model {
pa ~
uniform(0, 0.5);
pb ~
uniform(0, 0.5);
k ~
binomial(n, p);
}
"""
sm = stan.build(StanCode, data = {'n':10, 'k':7})
fit = sm.sample()
infer_data = az.from_pystan(posterior=fit,
posterior_model=sm)
smry = az.summary(infer_data)
print(smry)
df = fit.to_frame()
print(df.describe().T)
with open('df_nsw.pkl',
'wb') as f:
pickle.dump(df, f)
リスト6 リスト5のスクリプトと組み合わせたMCMCサンプル表示スクリプト(indef_nswitch_alan.py)
import pickle
import numpy
as np
import pandas as pd
import matplotlib.pyplot
as plt
import seaborn as sb
with open('df_nsw.pkl',
'rb') as f:
df
= pickle.load(f)
sb.kdeplot(df["pa"],
c='r', label='pa')
sb.kdeplot(df['pb'],
c='g', label='pb')
sb.kdeplot(df['p'],
c='b', label='p')
plt.xlabel('')
plt.legend()
plt.show()
plt.figure(figsize=(5,6))
plt.subplot(211)
plt.hist(df['pa'])
plt.xlabel('pa', fontsize=14)
plt.subplot(212)
plt.hist(df['pb'])
plt.xlabel('pb', fontsize=14)
plt.tight_layout()
plt.show()
plt.figure(figsize=(7,7))
plt.plot(df['pa'], df['pb'], 'o', markerfacecolor='none')
plt.xticks(np.arange(0,
1.001, 0.1))
plt.yticks(np.arange(0,
1.001, 0.1))
plt.xlabel('pa', fontsize=14)
plt.ylabel('pb', fontsize=14)
plt.show()