Bayesian Analysis with PyMC 勉強ノート 7 混合モデル
Osvaldo Martin 著, 金子武久 訳
Pythonによるベイズ統計モデリング (Bayesian Analysis with Python)
2018-06-26
https://www.kyoritsu-pub.co.jp/book/b10003944.html
Christopher M. Bishop 著, 元田浩 栗田多喜夫 樋口知之 松本裕治 村田昇 監訳
パターン認識と機械学習 ベイズ理論による統計的予測 (Pattern Recognition and Machine Learning)
2012-01-20
https://www.maruzen-publishing.co.jp/item/?book_no=294524
Pythonによるベイズ統計モデリングを読んだので学習メモを整理します。学習中のノートなので正確ではないかもしれません。使われているコードをPyMC3からPyMC5に書き直しました。
また、せっかくなのでPRMLの本を参照しながら読むことにしました。式の文字の扱いなどはPRML風になるよう頭に収めようと思っています。
混合モデル
単純な分布では表現できない複雑な問題をモデリングすることができる。
- 有限混合モデル
- ゼロ過剰ポアソン分布・ゼロ過剰ポアソン回帰
- ロバストロジスティック回帰
- モデルベースクラスタリング
- 連続型混合モデル
次のライブラリを使用する。
import matplotlib.pyplot as plt
import math
import numpy as np
from scipy import stats
import pandas as pd
import seaborn as sns
import pymc as pm
import arviz as az
az.style.use("arviz-darkgrid")
1. ガウス混合モデル
過程や現象を正規分布など知れた正準分布(canonical distribution)で表わせない場合、分布の組み合わせを仮定するといい感じなことがある。
部分母集団がいくつかの組み合わせ(例えばデータ生成元の正規分布がいくつか存在する場合など)になっている場合はこれを直接的に表現できる。また、多峰性の分布を扱いやすく表現するにはガウス混合モデルGMM を使うといい。
頻度論的な使い方としてはカーネル密度推定KDE は馴染み深い。KDEではヒストグラムのかわりに、ヒストグラムが生成されていると仮定する連続的な分布を考えることで、ノンパラメトリックに推定する。具体的には各点に正規分布を割り当てて、全体で合計することで求められる。
混合モデルは、ある個数の部分母集団がある時に、どの分布かわからないけどそれに従うデータがある状況で、各データをfitする母集団のもとに割り当てるような時に使う。これは階層モデルで実現できる。
潜在変数(latent variable): 実際には観測できない確率変数。どの分布がどの観測データに対応するかを規定する。
clusters = 3
n_cluster = [90, 50, 75]
n_total = sum(n_cluster)
mu_real, sig_real = [9, 21, 35], [2, 2, 2]
y = np.random.normal(np.repeat(mu_real, n_cluster), np.repeat(sig_real, n_cluster))
fig, axes = plt.subplots(1, 2, figsize=(10,3))
sns.kdeplot(np.array(y), ax=axes[0])
sns.histplot(np.array(y), ax=axes[1])
plt.show()
このデータから要素の正規分布の数(k=3)を既知として、平均(mu)、分散(sig)とそれぞれのデータ点がどの正規分布に割り当てられるかを推定する。
with pm.Model() as model:
p = pm.Dirichlet("p", a=np.ones(clusters))
category = pm.Categorical("category", p=p, shape=n_total)
mu = pm.Normal("mu", mu=[10, 20, 30], sigma=10)
sig = pm.HalfCauchy("sig", 5)
y_pred = pm.Normal("y_pred", mu=mu[category], sigma=sig, observed=y)
trace = pm.sample(2000, chains=2, cores=1)
az.plot_trace(trace, var_names=["p", "mu", "sig"])
y_hat = pm.sample_posterior_predictive(trace, model=model)
fig, axes = plt.subplots(2, 1, figsize=(8,5))
az.plot_ppc(y_hat, num_pp_samples=20, ax=axes[0])
sns.kdeplot(y, c="b", ax=axes[1])
axes[1].set_xlim(0,45)
Dirichlet分布
β分布の一般化。3次元の場合、ベクトル
[1] Bela A. Frigyik, Amol Kapila, and Maya R. Gupta
Introduction to the Dirichlet Distribution and Related Processes
UWEE Technical Report, UWEETR-2010-0006
http://mayagupta.org/publications/FrigyikKapilaGuptaIntroToDirichlet.pdf
[1]より、ディリクレ分布の例
PRML 2.2.1 より、関数は以下。カテゴリカル分布(多項分布)の共役事前分布であることが示されているが、確率密度関連に関してはどこかでまとめて復習しておきたい...
周辺化されたGMM
潜在変数、上記の例ではcategory
のサンプリングは分布の無駄な探索を起こしてしまい遅くなる原因になる。潜在変数を迷惑変数として周辺化して消すことで、これを解消できる。
with pm.Model() as model:
p = pm.Dirichlet("p", a=np.ones(clusters))
mu = pm.Normal("mu", mu=[10, 20, 30], sigma=10, shape=clusters)
sig = pm.HalfCauchy("sig", 5)
y_pred = pm.NormalMixture("y_pred", w=p, mu=mu, sigma=sig, observed=y)
trace = pm.sample(2000, chains=2, cores=1)
az.plot_trace(trace, var_names=["p", "mu", "sig"])
2. ポアソンモデルとカウントデータ
カウントデータ(count data): 離散的な非負数のデータを扱うにはポアソン分布を使う。
ポアソン分布
ある時間で発生する、独立事象の回数をの確率を記述するのにポアソン分布が使われる。確率質量関数(probability mass function; pmf)により、以下のように表せる。
lambda_list = [0.5, 1.5, 3, 8]
k = np.arange(0, max(lambda_list)*3)
for l in lambda_list:
y = stats.poisson(l).pmf(k)
plt.plot(k, y, "-o", label=f"λ = {l:3.1f}")
plt.legend()
2項分布
ポアソン分布は、
ゼロ過剰ポアソンモデル
ゼロ過剰ポアソンモデル(zero-inflated Poisson; ZIP)は、事後予測チェックなどを行う時に、データがポアソン分布に従っていると期待より過剰に0を観測してしまい、うまくデータにfitしないことがある問題を解決する。
ポアソン分布
この記事ではあまり深入りせず使ってみる。より専門的な、わかりやすい記事としては以下のNospareのものが良かった。
# ゼロが多いデータに対するZIPのfitting
n = 100
theta = 2.5
pi = 0.5
y = np.array([(np.random.random() > pi)*np.random.poisson(theta) for _ in range(n)])
with pm.Model() as model:
psi = pm.Beta("psi", alpha=1, beta=1)
lam = pm.Gamma("lambda", alpha=2, beta=0.1)
y_pred = pm.ZeroInflatedPoisson("y_pred", psi=psi, mu=lam, observed=y)
trace = pm.sample(2000, chains=2, cores=1)
az.plot_trace(trace, var_names=["psi", "lambda"])
fig, axes = plt.subplots(1, 2, figsize=(10,5))
y_sampled = pm.sample_posterior_predictive(trace, model=model)
az.plot_ppc(y_sampled, num_pp_samples=20, ax=axes[0])
sns.histplot(y, color="b", ax=axes[1])
ポアソン回帰・ゼロ過剰ポアソン回帰
線形モデルの一部としてポアソンやZIPを使うこともある。例えば、通常の実数範囲における一般化線形回帰モデルは活性化関数と線形モデルや誤差分布(たとえばガウスやT分布)を使って構築できた。分類のためのロジスティック回帰なども一般化線形モデルの枠組みで作れる。このアイディアと同じく、ポアソン分布を使って離散的な目標変数を回帰することができる。
# 訪問者の属性から、釣った魚の数を予測する
data = pd.read_csv("https://stats.oarc.ucla.edu/stat/data/fish.csv")
print(data)
x1, x2 = data["child"].values, data["camper"].values
y = data["count"].values
with pm.Model() as model:
psi = pm.Beta("psi", alpha=1, beta=1)
w = pm.Normal("w", mu=0, sigma=10, shape=3)
lam = pm.math.exp(w[0] + x1*w[1] + x2*w[2])
y_pred = pm.ZeroInflatedPoisson("y_pred", psi=psi, mu=lam, observed=y)
trace = pm.sample(2000, chains=2, cores=1)
az.plot_trace(trace, var_names=["psi", "w"])
az.plot_autocorr(trace, var_names=["psi", "w"], combined=True)
fig, axes = plt.subplots(1, 3, figsize=(10,4))
for i, y_i in enumerate(y):
if x2[i] == 0:
axes[0].scatter(x1[i], y_i, color="c")
else:
axes[1].scatter(x1[i], y_i, color="m") # キャンプ者
axes[0].set_ylim(0,40)
axes[1].set_ylim(0,40)
w_post_mean = trace.posterior["w"].mean(dim=["chain","draw"]).values
c = np.array([0,1,2,3])
axes[2].plot(np.exp(w_post_mean[0] + w_post_mean[1]*c + w_post_mean[2]*0), "-o", color="c")
axes[2].plot(np.exp(w_post_mean[0] + w_post_mean[1]*c + w_post_mean[2]*1), "-o", color="m")
plt.show()
nofish livebait camper persons child xb zg count
0 1 0 0 1 0 -0.896315 3.050405 0
1 0 1 1 1 0 -0.558345 1.746149 0
2 0 1 0 1 0 -0.401731 0.279939 0
3 0 1 1 2 1 -0.956298 -0.601526 0
4 0 1 0 1 0 0.436891 0.527709 1
.. ... ... ... ... ... ... ... ...
248 1 1 1 3 2 1.374641 -2.595630 0
249 1 1 1 2 1 0.828834 -1.457115 0
ロバストロジスティック
目的変数に異常に大きな、あるいは小さな値がある場合でもそれに引っ張られずに分類するため、混合モデルを使える。
目的変数が
iris = sns.load_dataset("iris")
dataset = pd.concat([iris[iris["species"] == "versicolor"], iris[iris["species"] == "setosa"]])
y = np.concatenate([pd.Categorical(dataset["species"]).codes, np.ones(8)])
x = np.concatenate([dataset["sepal_length"].values, np.array([4.0,4.1,4.2,4.3,4.0,4.1,4.2,4.3])])
fig, axes = plt.subplots(1, 2, figsize=(10,4))
# 通常のロジスティック回帰
with pm.Model() as model:
w = pm.Normal("w", mu=0, sigma=10, shape=2)
mu = pm.Deterministic("mu", w[0] + pm.math.dot(x, w[1]))
theta = pm.Deterministic("theta", 1/(1 + pm.math.exp(-mu)))
bd = pm.Deterministic("bd", -w[0]/w[1])
y_pred = pm.Bernoulli("y_pred", p=theta, observed=y)
trace = pm.sample(2000, chains=2, cores=1)
idx = np.argsort(x)
post_theta = trace.posterior["theta"].values
post_theta_hdi = az.hdi(trace.posterior, hdi_prob=0.90)["theta"].values
post_bd = trace.posterior["bd"].values
post_bd_hdi = az.hdi(trace.posterior, hdi_prob=0.90)["bd"].values
axes[0].scatter(x, y, color="b")
axes[0].plot(x[idx], post_theta.mean(axis=(0,1))[idx], color="r")
axes[0].fill_between(x[idx], post_theta_hdi[idx][:,0], post_theta_hdi[idx][:,1], color="r", alpha=0.4)
axes[0].axvline(post_bd.mean(), ymax=1, color="r")
axes[0].fill_betweenx(y=[0,1], x1=post_bd_hdi[0], x2=post_bd_hdi[1], color="r", alpha=0.4)
# ロバストロジスティック
with pm.Model() as model:
w = pm.Normal("w", mu=0, sigma=10, shape=2)
mu = pm.Deterministic("mu", w[0] + pm.math.dot(x, w[1]))
theta = pm.Deterministic("theta", 1/(1 + pm.math.exp(-mu)))
bd = pm.Deterministic("bd", -w[0]/w[1])
pi = pm.Beta("pi", alpha=1, beta=1)
p = pi * 0.5 + (1 -pi) * theta
y_pred = pm.Bernoulli("y_pred", p=p, observed=y)
trace = pm.sample(2000, chains=2, cores=1)
idx = np.argsort(x)
post_theta = trace.posterior["theta"].values
post_theta_hdi = az.hdi(trace.posterior, hdi_prob=0.90)["theta"].values
post_bd = trace.posterior["bd"].values
post_bd_hdi = az.hdi(trace.posterior, hdi_prob=0.90)["bd"].values
axes[1].scatter(x, y, color="b")
axes[1].plot(x[idx], post_theta.mean(axis=(0,1))[idx], color="r")
axes[1].fill_between(x[idx], post_theta_hdi[idx][:,0], post_theta_hdi[idx][:,1], color="r", alpha=0.4)
axes[1].axvline(post_bd.mean(), ymax=1, color="r")
axes[1].fill_betweenx(y=[0,1], x1=post_bd_hdi[0], x2=post_bd_hdi[1], color="r", alpha=0.4)
print(trace.posterior["pi"].mean())
0.29522286 ← 混合確率 π
3. 連続型混合モデル
ロバストロジスティクス回帰は、ロジスティック回帰とランダム性の推測による連続型混合モデルの一種であることはわかりやすい。
また、階層モデルも連続型混合モデルの一種で、各グループのパラメータが上位の連続分布から発生していると考えられる。
β2項分布
これは
すなわち、
モデル内で期待値を取るパラメータは潜在変数のことを表している。
負の2項分布
連続型ガンマ-ポアソン混合(gamma-Poisson mixture)モデルの一つで、ガンマ分布による連続値を使ってポアソン確率
これにより、カウントデータによくある過剰分散 (データの分散がポアソン分布モデルの分散を超える)問題を解決できる。ポアソン分布の平均と分散はパラメータ1つを共有しているので、この部分をガンマ分布の情報を使って扱うことが良いらしい(?)
T分布
感想
これまで具体的な混合モデルはGMMしか知らなかった(使っていたと思っていなかった)が、実際に式で見てみると階層モデルなどを始め知らないところで使っていた。というより現象のモデル(仮説)を考えるときに混合モデルになるのは自然なので、GMM以外を使うときも意識してモデリングに組み込めれば、説明性の観点からよりよい推論ができるようになるだろう。
最後の連続型混合モデルについては詳しく触れなかったのでまだ理解していない。PRMLだと下巻の14.5あたりに書かれている気がするので、読み追いついたらその時に詳しく見る予定。(下巻の難易度高い)
Discussion