StatModeling Memorandum

StatModeling Memorandum

StanとRとPythonでベイズ統計モデリングします. たまに書評.

GPy(Pythonのガウス過程用ライブラリ)の使い方

概要

GPyを用いて、サンプルパスの生成、ガウス過程回帰、クラス分類、ポアソン回帰、Bayesian GPLVMを実行しました。自分用のメモです。

参考資料

理論的背景は上記の[3]を参考にしてください。日本語でもガウス過程の解説がMLPシリーズから豪華著者陣で出るようです。超期待しています。

以下のサンプルプログラムは基本的に[2]を元にしています。しかし、古くてそのままでは動かないプログラムや分かりにくいプログラムを少し加工修正しています。なお、環境は以下の通りです。

サンプルパスの生成

RBFカーネルで適当に定めたパラメータの値でサンプルパスを生成するプログラムです。カーネルそのものやカーネルのパラメータを変えることでどのようなサンプルパスを生成するのかシミュレーションしたい場合によく使います。

import GPy
import numpy as np
import matplotlib.pyplot as plt

kernel = GPy.kern.RBF(input_dim=1, variance=1, lengthscale=0.2)

np.random.seed(seed=123)
N_sim = 100
x_sim = np.linspace(-1, 1, N_sim)
x_sim = x_sim[:, None]
mu = np.zeros(N_sim)
cov = kernel.K(x_sim, x_sim)
y_sim = np.random.multivariate_normal(mu, cov, size=20)

fig = plt.figure()
ax = fig.add_subplot(1,1,1)
for i in range(20):
    ax.plot(x_sim[:], y_sim[i,:])
fig.savefig('output/fig1.png')
  • 5行目: これでカーネルを定義します。入力の次元(input_dim)は必須です。
  • 10行目: GPyの関数の多くは、引数のshape(データ点の数, 1)である必要があります。そこで[:, None]を加えてその形にしています。
  • 12行目: kernelオブジェクトに対しK関数を使うと分散共分散行列を作成できます。
  • 13行目: numpyの関数で多変量正規分布からサンプルを生成しています。

ガウス過程回帰(入力1次元・出力1次元)

手順は カーネルを定める→モデル作成→最適化 だけです。

import GPy
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

kernel = GPy.kern.RBF(1)
# kernel = GPy.kern.RBF(1) + GPy.kern.Bias(1) + GPy.kern.Linear(1)

d = pd.read_csv('data-GPbook-Fig2_05.txt')
model = GPy.models.GPRegression(d.X[:, None], d.Y[:, None], kernel=kernel)
model.optimize()
model.plot()
plt.savefig('output/fig2.png')

## prediction
x_pred = np.linspace(-10, 10, 100)
x_pred = x_pred[:, None]
y_qua_pred = model.predict_quantiles(x_pred, quantiles=(2.5, 50, 97.5))[0]
  • 6行目: RBFカーネルinput_dim = 1で作成しています。7行目はRBF+Bias+Linearのカーネルを使う場合です。足したり掛けたりするだけで複雑なカーネルを作ることができるインターフェースが素敵です。
  • 10行目: GPy.models.GPRegression関数でモデルを作成しています。print(model)m['']とするとモデルに含まれるパラメータを見ることができます。特に指定しなければ、すべてのパラメータが最適化の対象となります。ちなみに各パラメータに固定値を与えることや制限をかけることができます(詳しくはこれこれを参照)。
  • 11行目: 最適化をしています。オプションでiterationの数などを指定できます。
  • 12~13行目: 最適化後のモデルをプロットしています。
  • 16~18行目: 最適化後のモデルを使って予測を行っています。

ガウス過程回帰(入力2次元・出力1次元)

import GPy
import numpy as np
import matplotlib.pyplot as plt

kernel = GPy.kern.Matern52(2, ARD=True)

np.random.seed(seed=123)
N = 50
X = np.random.uniform(-3.,3.,(N, 2))
Y = np.sin(X[:,0:1]) * np.sin(X[:,1:2]) + np.random.randn(N,1)*0.05

model = GPy.models.GPRegression(X, Y, kernel)
model.optimize(messages=True, max_iters=1e5)
model.plot()
plt.savefig('output/fig3.png')

model.plot(fixed_inputs=[(0, -1.0)], plot_data=False)
plt.savefig('output/fig3-slice.png')

## prediction
x_pred = np.array([np.linspace(-3, 3, 100), np.linspace(3, -3, 100)]).T
y_qua_pred = model.predict_quantiles(x_pred, quantiles=(2.5, 50, 97.5))[0]
  • 5行目: 今回はMatern5/2カーネルを使っています。オプションのARD=Trueは入力の次元1つに対し、1つのlengthscaleパラメータを割り振ること(すなわちGPは等方でないことを表します)。
  • 17~18行目: 2次元の入力のうち一部を固定した図(スライスした図;2枚目の図)を描いています。ここでは、fixed_inputs=[(0, -1.0)]でインデックス0の入力を-1.0に固定しています。
  • 21~22行目: 入力1次元のガウス過程回帰と同様に予測をしています。入力の次元に注意です。

スパースなガウス過程回帰

補助変数法やコンパクトなガウス過程回帰とも呼ばれます。ガウス過程はデータ点の数N逆行列を求める必要があり、その部分にN^3のオーダーの時間がかかります。そのため、データ点が増えると次第に遅くなります。そこで、一部の補助変数(inducing inputs)を入力次元の代表点として扱い、対数尤度を近似することで計算を高速化させる方法があります。それがこの節の方法になります。

import GPy
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

kernel = GPy.kern.RBF(1)

d = pd.read_csv('data-GPbook-Fig2_05.txt')
m_full = GPy.models.GPRegression(d.X[:, None], d.Y[:, None], kernel=kernel)
m_full.optimize()

Z = np.hstack((np.linspace(-6, -3, 3), np.linspace(3, 6, 3)))[:,None]
# Z = np.linspace(-6, 6, 12)[:, None]
m_sparse = GPy.models.SparseGPRegression(d.X[:, None], d.Y[:, None], Z=Z)
m_sparse.optimize()
m_sparse.plot()
plt.savefig('output/fig4.png')
print(m_sparse.log_likelihood(), m_full.log_likelihood())
  • 12行目: 補助変数の初期値です。6個をテキトーに定めました。結果は1枚目の図です。
  • 13行目: こちらは12個の場合です。結果は2枚目の図です。
  • 14行目: GPy.models.SparseGPRegression関数を使います。補助変数はZで指定します。
  • 15行目: 補助変数の位置も最適化の対象となります。
  • 18行目: modelオブジェクトに対しlog_likelihood関数を使うと対数尤度を取得できます。最適化の後の対数尤度を見ると、補助変数6個の場合が-28.85、補助変数12個の場合が-18.02、補助変数を使わないフルモデルの場合が-17.92となりました。12個の補助変数で十分よく近似できていることが分かります。

クラス分類

ここではPRML下のFig.6.12相当の図を再現してみます。

import GPy
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

kernel = GPy.kern.RBF(2, ARD=True)

d = pd.read_csv('data-classification.txt')
model = GPy.models.GPClassification(d[['X1', 'X2']].values, d.Y[:, None])
model.optimize()

ax = model.plot(plot_data=False)
d0 = d[d.Y == 0]
d1 = d[d.Y == 1]
ax.plot(d0.X1, d0.X2, 'ro')
ax.plot(d1.X1, d1.X2, 'bo')
plt.savefig('output/fig5.png')
  • 9行目: GPy.models.GPClassification関数でクラス分類のモデルを組み立てることができます。

ポアソン回帰

久保緑本の11章の欠測値なしのモデルを実行します。

import GPy
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

kernel = GPy.kern.RBF(1)

d = pd.read_csv('data-kubo11a.txt')

model = GPy.core.GP(X=np.linspace(1, 50, 50)[:,None], Y=d.Y[:,None], kernel=kernel,
    inference_method=GPy.inference.latent_function_inference.Laplace(),
    likelihood=GPy.likelihoods.Poisson())
model.optimize()
model.plot()
plt.savefig('output/fig6.png')

x_pred = np.linspace(1, 50, 50)[:, None]
f_mean, f_var = model._raw_predict(x_pred) # Predictive GP for log intensity mean and variance
f_upper, f_lower = f_mean + 2*np.sqrt(f_var), f_mean - 2.*np.sqrt(f_var)
plt.plot(x_pred, np.exp(f_mean), color='blue', lw=2)
plt.fill_between(x_pred[:,0], np.exp(f_lower[:,0]), np.exp(f_upper[:,0]), color='blue', alpha=.1)
plt.savefig('output/fig6-mean.png')
  • 10~12行目: 少し凝ったモデルを使用したい場合には、GPy.core.GP関数を使って尤度を自分で設定する必要があります。推定方法もあわせて指定します。ここでは単純なポアソン回帰なので、用意されているGPy.likelihoods.Poisson関数を使えば完了です。
  • 17~21行目: 真の平均の推定値と±2SDのグラフ(2枚目の図)を描いています。

ガウス過程回帰(入力2次元・出力2次元)

出力が2次元となると、モデルの選択肢が増えます。その前に「どうして複数次元の出力が必要なのか?各出力は相関しているのか(一方の出力が他方の出力を予測するヒントになるのか)?」といった問いが重要だとNeil Lawrenceは述べています(メーリングリストより)。もしそれらの問いの回答がNoならば、出力1次元のモデルを複数組み合わせたモデルの方がよいかもしれません。

ここでは、「出力間に相関がある簡単なモデル」「出力間に相関がないモデル」「出力間に相関がある凝ったモデル」の順にすすめます。

import GPy
import numpy as np
import matplotlib.pyplot as plt

f_output1 = lambda x: 4*np.cos(x/5) - 0.4*x - 35 + np.random.rand(x.size) * 2
f_output2 = lambda x: 6*np.cos(x/5) + 0.2*x + 35 + np.random.rand(x.size) * 8

np.random.seed(seed=123)
X1 = np.random.rand(100)
X2 = np.random.rand(100)
X1 = X1*75
X2 = X2*70 + 30
Y1 = f_output1(X1)
Y2 = f_output2(X2)

x_pred1 = np.random.rand(100)*100
x_pred2 = np.random.rand(100)*100
y_pred1 = f_output1(x_pred1)
y_pred2 = f_output2(x_pred2)

def plot_2outputs(m):
    fig = plt.figure(figsize=(12, 8))
    ax1 = fig.add_subplot(211)
    ax1.set_ylim([-120, -20])
    ax1.set_title('Output 1')
    m.plot(plot_limits=[0, 100], fixed_inputs=[(1, 0)], which_data_rows=slice(0, 100), ax=ax1)
    ax1.plot(x_pred1, y_pred1, 'rx', mew=1.5)
    ax2 = fig.add_subplot(212)
    ax2.set_ylim([-20, 100])
    ax2.set_title('Output 2')
    m.plot(plot_limits=[0, 100], fixed_inputs=[(1, 1)], which_data_rows=slice(100, 200), ax=ax2)
    ax2.plot(x_pred2, y_pred2, 'rx', mew=1.5)


K = GPy.kern.Matern32(1)
B = GPy.kern.Coregionalize(input_dim=1, output_dim=2)
kernel = K**B
model = GPy.models.GPCoregionalizedRegression(X_list=[X1[:, None],X2[:, None]], Y_list=[Y1[:, None],Y2[:, None]], kernel=kernel)

model['.*Mat32.var'].constrain_fixed(1)
model.optimize()
plot_2outputs(model)
plt.savefig('output/fig7a.png')

model['.*coregion.W'].constrain_fixed(0)
model.randomize()
model.optimize()
plot_2outputs(model)
plt.savefig('output/fig7b.png')


K1 = GPy.kern.RBF(1)
K2 = GPy.kern.Bias(1) + GPy.kern.Linear(1)
B1 = GPy.kern.Coregionalize(1, output_dim=2)
B2 = GPy.kern.Coregionalize(1, output_dim=2)
kernel = K1**B1 + K2**B2

X = np.vstack((np.concatenate([X1, X2]), np.hstack((np.zeros(100), np.ones(100))))).T
Y = np.hstack((Y1, Y2))[:, None]
model = GPy.models.GPRegression(X, Y, kernel)
model.optimize()
plot_2outputs(model)
plt.savefig('output/fig7c.png')


## prediction
x_pred = np.arange(100, 110)[:, None]
x_pred = np.hstack([x_pred, np.ones_like(x_pred)])
output_index_pred = {'output_index':x_pred[:,1:].astype(int)}
y_pred = model.predict(x_pred, Y_metadata=output_index_pred)
  • 5~19行目: デモデータ作成部分です。
  • 21~32行目: プロットする関数を定義しています。
  • 36行目: 出力間の関係を定める行列B(coregionalization matrix)を作成しています。詳しくはここを参照。
  • 37行目: kernelオブジェクトに対する**クロネッカー積となります。
  • 38行目: GPy.models.GPCoregionalizedRegression関数を使うことで、複数の出力の次元が相関を持ち、出力の各次元でノイズの大きさが異なるモデルを簡単に作成することができます。なお、入力と出力はlistで渡します。
  • 40行目: 38行目でもモデルにノイズが入るので、Matern3/2カーネルに含まれるノイズを1に固定しています。
  • 41~43行目: このモデルの結果は1枚目の図です。
  • 45行目: Bを対角行列に固定しています。出力次元ごとにガウス過程回帰を行うのと同じになります。
  • 46行目: 41行目で最適化された値になっているので、いったん初期値をぐちゃぐちゃにする意味です。
  • 47~49行目: このモデルの結果は2枚目の図です。1枚目の図との違いに注目してください。
  • 52~56行目: coregionalization matrixをカーネルの種類ごとに用意して組み合わせることもできます。
  • 60行目: GPy.models.GPRegression関数を使うと、すべての出力次元の共通の大きさのノイズとなります。なお、GPy.models.GPRegression関数を用いる場合にはXは「1列目に値・2列目に出力次元のインデックス」となっているndarrayを渡す必要があります。YXに対応するndarrayです。
  • 61~63行目: このモデルの結果は3枚目の図です。予測区間が狭いです。モデルが複雑で過学習の恐れがあるかもしれません。
  • 67~70行目: 予測の例です。出力が複数ある場合にはdictionaryを作って渡します。ここではoutput_index1(すなわち2番目の出力)が100110でどのような出力になるか予測しています。

Bayesian GPLVM

前の記事と同じようにPRMLでおなじみのOil Flowのデータに対してBayesian GPLVMを実行します。

from scipy.io import loadmat
import scipy.io as spio
import GPy
import matplotlib.pyplot as plt

d = spio.loadmat('input/3Class.mat')
X = d['DataTrn']
X -= X.mean(0)
L = d['DataTrnLbls'].nonzero()[1]
input_dim = 2 # How many latent dimensions to use

kernel = GPy.kern.RBF(input_dim, ARD=True) + GPy.kern.Bias(input_dim) + GPy.kern.Linear(input_dim) + GPy.kern.White(input_dim)
model = GPy.models.BayesianGPLVM(X, input_dim, kernel=kernel, num_inducing=30)
model.optimize(messages=True, max_iters=5e3)
model.plot_latent(labels=L)
plt.savefig('output/fig8.png')
  • 10行目: 潜在変数の次元。ここではチュートリアルと同じように2にしています。
  • 13行目: GPy.models.BayesianGPLVM関数の一発で補助変数込みでモデルが作成できます。

不明点

GPy.models.GPRegression関数を使うと、パラメータにノイズの大きさが加わります。このノイズと、カーネル側でGPy.kern.White関数で設定したノイズの違いがよく分かりません。なお、簡単なモデルで両方とも設定すると数式の上では識別できなくなると思うのですが、最適化の結果は分散をちょうど半分に分ける形で推定されて全体的な予測分布は変わりません。

Bayesian GPLVMをStanで実装してみた

この記事の続きです。PRML下の12章に出てくるOil Flowのデータ(データ点1000個×特徴量12個)に対してBayesian GPLVMで2次元(または3次元)の潜在変数空間にマッピングして綺麗に分離されるか見てみます。

まずはPRMLにもあるように普通の主成分分析でやると以下になります。綺麗には分離されません。

f:id:StatModeling:20201106181957p:plain

次にBayesian GPLVMでやってみます。Stanコードは以下になります。

  • 2~4行目: NKDはそれぞれ、データ点の数・特徴量の数・最終的に落とし込む潜在空間の次元です。
  • 14行目: 潜在変数です。
  • 15行目: カーネルに含まれるパラメータです。僕が理解したところだと特徴量ごとにガウス過程が存在するのでKごとに異なる値を持つようにしています。→ 2017.07.02追記 Kごとに異なる値にするのではなく、1つだけにし、スケーリングしてから適用することで情報を圧縮させる方がふつうのようです。詳しくはMLPシリーズ『ガウス過程と機械学習』参照。
  • 19行目: 同様に特徴量ごとに分散共分散行列があります。
  • 20・22~23行目: カーネルの定義で効率的な行列演算をするため、matrix型をvector型の配列に持ち替えます。
  • 24~28行目: カーネルの定義です。ここではGaussian+bias+linear+white noiseにしました。カーネルについてはGP summser school 2015のKernel Designの講義資料 (pdf)The Kernel Cookbookなどを参照してください。
  • 29行目: 潜在空間に対する縛りです。代わりにparametersブロックでlowerupperを定めてもOKです。
  • 30~31行目: カーネルに含まれるパラメータの事前分布です。軽くしばっています。ある範囲の一様分布にするなど他にも色々考えられると思います。
  • 32~33行目: ガウス過程の部分です。この書き方だと各特徴量は独立になっていますが、さらに特徴量間の相関を考慮したモデル(例えばCoregionalized Regression Model)もあります。ここではすでに計算量が膨大なので独立としました。

Rからの実行方法は以下です。計算が重いのでADVIを使いました。

  • 5行目: データがmatlabのファイルで与えられていたのでそれを読み込んでいます。結果はリストになります。
  • 8行目: 潜在変数の次元Dを与えています。最終的に2次元にプロットしたいです。余裕をもって次元を設定し、主成分分析のように寄与の大きい次元トップ2だけを抽出することで2次元に射影する方法もあるようです。ここでははじめから2次元の空間とします。また3次元の空間も試して3Dプロットしてみます。
  • 10~16行目: まずはふつうの主成分分析しています。前述の図はここで出力しているresult-pca.pngになります。またGPLVMを実行する際の初期値にする意味もあります。
  • 18~28行目: Bayesian GPLVMを実行しています。21行目ではinitオプションでPCAの結果を初期値として設定しています。22行目では時間短縮のためetaを求めないで1で与えています(手元のモデルとデータではチューニングの結果がいつもeta = 1だったという理由があります)。

計算時間はAWS EC2のc4.xlargeを使っても3時間半ぐらいかかりました。かなり遅いです。ADVIの代わりにStanのoptimizing関数を使って推定した方がよいかもしれません。また汎用的な確率的プログラミング言語ではガウス過程に特化した専用ライブラリにはかないません。Stanのモデルはユーザの問題にあわせた拡張が簡単なので、その点で使う価値はあると思います。特にモデルを拡張する予定がないならば、もしくはデータが巨大ならば、Pythonガウス過程に特化したライブラリであるGPyなどの使い方を学ぶべきと思います。

結果

D = 2の場合

潜在変数xの乱数サンプルの中央値を使って2次元の散布図を描くと以下になりました(result-bgplvm.png)。それなりに綺麗に分離していると思います。乱数の種の影響も見ましたが、おおよそ似たような結果になりました。

f:id:StatModeling:20201106181953p:plain

D = 3の場合

同様に3次元の散布図を描きました。2次元の場合より若干綺麗に分離していそうです。

f:id:StatModeling:20201106181948p:plain

条件付き独立と有向分離を用いた統計モデルの妥当性チェック

この記事では条件付き独立と有向分離を使い、作成したモデルが背景知識と齟齬がないかチェックする方法を紹介します。以下の本の4章と5.3.1項を参考にしています。

Modeling and Reasoning with Bayesian Networks

僕が持っているのは上の一版なのですが、新しい二版が出ているみたいです。

Modeling and Reasoning with Bayesian Networks

Modeling and Reasoning with Bayesian Networks

  • 作者:Darwiche, Adnan
  • 発売日: 2014/08/07
  • メディア: ペーパーバック
以降では単にグラフと言えばDAG(閉路を持たない有向グラフ)を指すことにします。

条件付き独立とは

以降では確率変数の集合を太文字で \textbf{X},\textbf{Y},\textbf{Z}のように太文字で表します。確率変数が1つの場合は X,Y,Zのように表します。

条件付き独立とは「 \textbf{Z}の値が与えられると、 \textbf{X} \textbf{Y}と独立になる」ことを指します。 I(\textbf{X},\textbf{Z},\textbf{Y})で表現します。「\textbf{Z}が与えられると、\textbf{Y}の情報は\textbf{X}を推測する上でヒントにならない。」と表現することもできます。

「確率変数 \textbf{X} \textbf{Y}が独立」とは P(\textbf{x},\textbf{y})=P(\textbf{x}) P(\textbf{y})を意味するのでした。条件付き独立は以下を意味します。

  P(\textbf{x},\textbf{y}|\textbf{z}) = P(\textbf{x}|\textbf{z}) P(\textbf{y}|\textbf{z})

両辺を P(\textbf{y}|\textbf{z})で割ることで、以下の形で表現することもできます( P(\textbf{y},\textbf{z}) \neq 0とします)。

  P(\textbf{x}|\textbf{y},\textbf{z}) = P(\textbf{x}|\textbf{z})

あるグラフィカルモデル Gが与えられると、たちどころに各ノード Xについて  I(X, Parents(X), Non\_Descendants(X))が成り立ちます。ここで Parents(X) Xの親で、 Xへ直接矢印が向いているノードの集合です。Descendants(X)Xの子孫、すなわち Xから矢印の向きに沿ってたどりつけるノードの集合です。したがって Non\_Descendants(X)は子孫以外の集合です(ただし Parents(X)は除く)。

それでは上記以外の条件付き独立はグラフィカルモデル Gに含まれていないのでしょうか。いいえ、次に挙げる性質で他の条件付き独立を導くことができます。

条件付き独立の諸性質

ここでは4つだけ取り上げます。

性質の名前 解釈
対称
symmetry
 I(\textbf{X},\textbf{Z},\textbf{Y}) = I(\textbf{Y},\textbf{Z},\textbf{X})  \textbf{z}が与えられた場合、\textbf{y}の情報が\textbf{x}のヒントにならなければ、\textbf{x}の情報もまた\textbf{y}のヒントにならない。その逆も然り。
分解
decomposition
 I(\textbf{X},\textbf{Z},\textbf{Y} \cup \textbf{W})ならば
  I(\textbf{X},\textbf{Z},\textbf{Y})かつ
  I(\textbf{X},\textbf{Z},\textbf{W})
\{\textbf{y},\textbf{w}\}の情報が\textbf{x}のヒントにならなければ、その一部である\textbf{y}\textbf{w}\textbf{x}のヒントにならない。
弱結合
weak union
 I(\textbf{X},\textbf{Z},\textbf{Y} \cup \textbf{W})ならば
  I(\textbf{X},\textbf{Z} \cup \textbf{W},\textbf{Y})かつ
  I(\textbf{X},\textbf{Z} \cup \textbf{Y},\textbf{W})
\{\textbf{y},\textbf{w}\}の情報が\textbf{x}のヒントにならなければ、その一部である\textbf{w}が与えられても残りの\textbf{y}の情報はヒントにならない。\textbf{y}を与えた場合も同様。
縮約
contraction
 I(\textbf{X},\textbf{Z},\textbf{Y})かつ
 I(\textbf{X},\textbf{Z} \cup \textbf{Y},\textbf{W})ならば
  I(\textbf{X},\textbf{Z},\textbf{Y} \cup \textbf{W})
関連のない \textbf{y}が与えられたあとで\textbf{w}の情報が\textbf{x}のヒントにならなければ、はじめからそれらを合わせた\{\textbf{y},\textbf{w}\}の情報もヒントにならない。

備考

  • 合成(composition)

以下の「合成(composition)」は一般に成り立たない。縮約(contraction)との違いに注目。

  I(\textbf{X},\textbf{Z},\textbf{Y})かつ I(\textbf{X},\textbf{Z},\textbf{W})ならば I(\textbf{X},\textbf{Z},\textbf{Y} \cup \textbf{W})

  • 分解(decomposition)から導かれること

XをあるグラフGのノードとすると、すべての \textbf{W} \subseteq Non\_Descendants(X)に対し、 I(X,Parents(X),\textbf{W})が成り立つ。

  • 弱結合(weak union)から導かれること

XをあるグラフGのノードとすると、任意の \textbf{W} \subseteq Non\_Descendants(X)に対し、 I(X,Parents(X) \cup \textbf{W}, Non\_Descendants(X) \setminus \textbf{W})が成り立つ。

証明

各性質の証明です。興味なければ飛ばしてください。本に載っていなかったので僕が足したものです。間違っていたらすみません。

  • 対称(symmetry)の証明

 P(\textbf{x},\textbf{y}|\textbf{z}) = P(\textbf{x}|\textbf{z}) P(\textbf{y}|\textbf{z}) = P(\textbf{y}|\textbf{z}) P(\textbf{x}|\textbf{z}) = P(\textbf{y},\textbf{x}|\textbf{z})

  • 分解(decomposition)の証明

 P(\textbf{x},\textbf{y}|\textbf{z}) = \sum_{\textbf{w}} P(\textbf{x},\textbf{y},\textbf{w}|\textbf{z}) = \sum_{\textbf{w}} P(\textbf{x}|\textbf{z}) P(\textbf{y},\textbf{w}|\textbf{z}) = P(\textbf{x}|\textbf{z}) P(\textbf{y}|\textbf{z})

2つめの等号で仮定を使っています。

  • 弱結合(weak union)の証明

 P(\textbf{x},\textbf{y}|\textbf{z},\textbf{w}) = \frac{P(\textbf{x},\textbf{y},\textbf{w}|\textbf{z})}{P(\textbf{w}|\textbf{z})} = \frac{P(\textbf{x}|\textbf{z}) P(\textbf{y},\textbf{w}|\textbf{z})}{P(\textbf{w}|\textbf{z})} = P(\textbf{x}|\textbf{z}) P(\textbf{y}|\textbf{z},\textbf{w}) = P(\textbf{x}|\textbf{z},\textbf{w}) P(\textbf{y}|\textbf{z},\textbf{w})

2つめの等号で仮定を使っています。また、仮定に分解(decomposition)を適用するとI(\textbf{X},\textbf{Z},\textbf{W})、すなわち P(\textbf{x}|\textbf{z})=P(\textbf{x}|\textbf{z},\textbf{w})が成り立つので、それを最後の等号で使っています。

  • 縮約(contraction)の証明

 P(\textbf{x},\textbf{y},\textbf{w}|\textbf{z}) = P(\textbf{x},\textbf{w}|\textbf{y},\textbf{z}) P(\textbf{y}|\textbf{z}) = P(\textbf{x}|\textbf{y},\textbf{z}) P(\textbf{w}|\textbf{y},\textbf{z}) P(\textbf{y}|\textbf{z})  \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ = P(\textbf{x}|\textbf{y},\textbf{z}) P(\textbf{y},\textbf{w}|\textbf{z}) = P(\textbf{x}|\textbf{z}) P(\textbf{y},\textbf{w}|\textbf{z})

2つめの等号で2番目の仮定を使っています。最後の等号で1番目の仮定を使っています。

有向分離とは

グラフィカルモデルが大きくなると、あるノードの集まり \textbf{X} \textbf{Y}の条件付き独立を調べるのは大変になります。もっと簡単かつ効率的に条件付き独立を判定するにはどうすればいいでしょうか。解決策の一つが有向分離(d-separation)です。

交わりを持たない \textbf{X},\textbf{Y},\textbf{Z}があって、あるグラフGにおいて\textbf{X}\textbf{Y}\textbf{Z}によって有向分離(d-separated)されているとは、 \textbf{X} \textbf{Y}をつなぐすべてのパス(矢印の向きは無視してよい)が \textbf{Z}によってブロックされていることを指します。dsep(\textbf{X},\textbf{Z},\textbf{Y})と表記します。

ブロックの前に「バルブが閉まっている」の説明をします。以下の3つの場合にA_1A_2が独立になり、A_1A_2の間のバルブが閉まっていると言います。

  • A_1\rightarrow W\rightarrow A_2というパスの形において、 Wが与えられている場合
  • A_1\leftarrow W\rightarrow A_2というパスの形において、 Wが与えられている場合
  • A_1\rightarrow W\leftarrow A_2というパスの形において、 Wとその子孫が与えられていない場合

「ブロックされている」とはパスのどこかで、少なくとも1つのバルブが閉まっていることです。この定義によると、バルブがそもそもない場合(すなわち直接矢印で X \rightarrow Yとなっている場合)にはブロックされようがありません。

この部分の説明と証明はPRML下の8.2節が分かりやすいです。

実際の dsep(\textbf{X},\textbf{Z},\textbf{Y})かどうかの判定方法は以下の手順で行うのが簡便です。

  1.  \textbf{X} \cup \textbf{Y} \cup \textbf{Z}に含まれない葉ノード(矢印が出ていないノード)を取り除く。葉ノードへ入る矢印も除く。グラフが変わらなくなるまでこのステップを繰り返し適用する。
  2.  \textbf{Z}から出ている矢印を取り除く。
  3.  \textbf{X} \textbf{Y}をつなぐパス(矢印の向きは無視してよい)が存在しなければ dsep(\textbf{X},\textbf{Z},\textbf{Y})

そして有向分離と条件付き独立は以下の定理で結びつきます。

 dsep(\textbf{X},\textbf{Z},\textbf{Y}) ならば  I(\textbf{X},\textbf{Z},\textbf{Y})

なお、無向グラフではA_1 - W - A_2というパスの形において、 Wが与えられている場合がブロックとなります。つながっているかどうかだけ見ればよいので簡単です。

有向分離の練習

本のFig4.12から引用です。以下のDAGを考えます。

f:id:StatModeling:20201106182234p:plain

  • 例1

上のDAGにおいて、 \textbf{X}=\{A,S\} \textbf{Y}=\{X,D\} \textbf{Z}=\{B,P\}によって有向分離されているか考えます。

f:id:StatModeling:20201106182237p:plain

データが与えられた確率変数は黒丸に白文字で書くのが慣例なのでそうしています。それでは実際に手順を適用してみましょう。判定方法の手順1によりDを取り除きます。手順2によりP \rightarrow Xの矢印を除きます。すると、\textbf{X}\textbf{Y}をつなぐパスはありませんので dsep(\textbf{X},\textbf{Z},\textbf{Y})と判定できます。

  • 例2

上のDAGにおいて、 \textbf{X}=\{T,C\} \textbf{Y}=\{B\} \textbf{Z}=\{S,X\}によって有向分離されているか考えます。

f:id:StatModeling:20201106182241p:plain

同様に判定方法の手順1によりDを取り除きます。手順2によりS \rightarrow CS \rightarrow Bの矢印を除きます。すると、\textbf{X}\textbf{Y}をつなぐパスはありませんので dsep(\textbf{X},\textbf{Z},\textbf{Y})と判定できます。

作成したモデルが背景知識と齟齬がないかチェックする例

本のFig5.9からの引用です。病気の診断をするモデルを2つ考えます。

1つめは以下のようなDAGです。モデル1としましょう。

f:id:StatModeling:20201106182246p:plain

2つめは以下のようなDAGです。モデル2としましょう。

f:id:StatModeling:20201106182250p:plain

モデル1がよいのかモデル2がよいのか考えています。いろいろな判断基準があると思いますが、ここでは背景知識とも言える「条件付き独立」と照らし合わせて考えてみます。

  • 「風邪を引いている」と分かっているとします。モデル1では「熱があるか」と「喉が痛いか」は有向分離になりません。「熱があるか」分かれば(恐らく扁桃炎の可能性がアップして)「喉が痛いか」かどうかのヒントになり得ます。一方、モデル2では「コンディション」が分かっているとすると、「熱があるか」と「喉が痛いか」は有向分離となり、条件付き独立になります。
  • モデル1では何もわかっていない状態においても、「体が痛い」と「扁桃炎」、「体が痛い」と「風邪」はそれぞれ有向分離となっており、条件付き独立となります。従って「体が痛い」という情報は「扁桃炎か」どうか「風邪か」どうかのヒントになりません。モデル2では有向分離していないので、「体が痛い」という情報は(インフルエンザの可能性を高め)「扁桃炎」および「風邪」があるかどうかのヒントになり得ます(恐らくこれらの可能性を減らすでしょう)。
  • モデル1では何もわかっていない状態においても、「熱があるか」と「風邪か」は有向分離となっており、条件付き独立となります。従って「熱がない」という情報は「風邪か」どうかのヒントになりません。モデル2では有向分離していないので、「熱がない」という情報は恐らく「風邪である」可能性を高めるでしょう。

このように条件付き独立と有向分離の考え方は背景知識と照らし合わせて統計モデリングに活用することができます。

その他の参考文献

グラフィカルモデルには上記以外にも無向グラフ、I-MAP、Perfect MAP、ネットワーク構造推定などさまざまなトピックがあります。詳しく知りたい読者は上記で挙げた二冊の本を参照してください。

因果推論の身近な話やバックドア基準については上記で挙げた本には載ってませんので、岩波DS3を読むとよいと思います。他の話も分かりやすく、実験者や研究者にオススメです。

岩波データサイエンス Vol.3

岩波データサイエンス Vol.3

  • 発売日: 2016/06/10
  • メディア: 単行本(ソフトカバー)