PyStanで混合ベルヌーイ分布を実装
目的
0と1を要素とする長さNのベクトルが作るn個のデータがあるとき、その集合をクラスタリングするモデルとして混合ベルヌーイ分布がある。ここではそのベイズ推測をPythonによるStanのインターフェースであるPyStanで行う方法をまとめる。
混合ベルヌーイ分布とは, とをパラメータとするの分布であり, 次式で定義されるものである.
今回は, PyStanで混合ベルヌーイ分布のパラメータのサンプリングを行ってみた.
サンプリングを行うにあたって, パラメータ, の事前分布を
とし, 入力するデータを, 既知の混合ベルヌーイ分布から生成された120個のサンプル
(各は次元のベクトル)とする.
(ただし, ,
各, についてのとき, それ以外はとする)
また, サンプリングされたとを用いて, 各サンプルがに所属する事後確率を計算し, どのクラスタに所属しているかを推定する.
は次のように表される.(ここで, はの所属するクラスタを表す )
データの生成と入力, 結果の出力を行うPythonのコードは次のように実装する.
import pystan import numpy as np import matplotlib.pyplot as plt import collections def make_cluster(result, n): cluster = collections.defaultdict(list) for i in range(n): cluster[np.argmax(result[i])].append(i) return cluster def draw_matrix(X, n, cluster, k, title): plt.figure(figsize=(7,7)) divide_pos = -0.5 min_lim = -0.5 max_lim = n - 0.5 X_new = [X[i] for r in range(k) for i in cluster[r]] for r in range(k): if r == k - 1: break divide_pos += len(cluster[r]) plt.hlines([divide_pos], min_lim, max_lim, linestyles="dashed") plt.imshow(X_new, cmap="Greys", interpolation="none") plt.tick_params(labelbottom='off') plt.tick_params(labelleft='off') plt.savefig(title) plt.show() if __name__ == '__main__': n = 120 # サンプル数 N = 80 # サンプル一つあたりの次元 k = 4 # クラスタ数 # テストデータのthetaの値 real_theta = [1. / k for i in range(k)] # テストデータのmuの値 real_mu = [[0.9 if (N / k) * i <= j < (N / k) * (i + 1) else 0.1 for j in range(N)] for i in range(k)] # テストデータの生成 X = np.zeros((n, N)) for i in range(n): c = int(np.random.choice(k, 1, p=real_theta)) for j in range(N): mu = real_mu[c][j] X[i][j] = np.random.choice(2, 1, p=[1.0 - mu, mu]) X = X.astype(int) # Stanのコードに渡すデータ stan_data = {'n': n, 'N': N, 'k': k, 'X': X} # Stanのコードで定義されたモデルを生成 sm = pystan.StanModel(file='mixture_bernoulli.stan') # サンプリング fit = sm.sampling(data=stan_data, chains=4) fit.plot() samples = fit.extract() # 集めたサンプルからクラスタ構造を推定 cluster = make_cluster(samples['y'].mean(axis=0), n) # 結果の表示 draw_matrix(X, n, cluster, k, 'y_sample')
Pythonのコードに読み込ませるStanのコードは次の通り.
事前分布のハイパーパラメータは次のように設定した.
data { int<lower=0> n; int<lower=0> N; int<lower=0> k; int<lower=0, upper=1> X[n, N]; } parameters { simplex[k] theta; real<lower=0, upper=1> mu[k, N]; } transformed parameters { vector[k] ps[n]; for(i in 1:n){ for(r in 1:k){ ps[i, r] = log(theta[r]); for(j in 1:N){ ps[i, r] += bernoulli_lpmf(X[i, j] | mu[r, j]); } } } } model { theta ~ dirichlet(rep_vector(1.0, k)); for(r in 1:k){ for(j in 1:N){ mu[r, j] ~ beta(1.0, 3.0); } } for(i in 1:n){ target += log_sum_exp(ps[i]); } } generated quantities { simplex[k] y[n]; for(i in 1:n){ y[i] = softmax(ps[i]); } }
推測されたパラメータは次のようになった.
パラメータから推測されたクラスタ分割から, サンプルの各ベクトル(0=白, 1=黒)を縦に並べなおすと次のようになる. おおむねあらかじめ設定したサンプル生成元の分布と一致することがわかる.
今回は成功した例だが, Stanでこのモデルのパラメータを推測すると失敗する場合もあった.
まとめ
混合ベルヌーイ分布を用いてクラスタリングを行うコードを紹介した. 実際に混合数kやハイパーパラメータを定めるには, 交差検証や情報量規準などが必要になる。