読者です 読者をやめる 読者になる 読者になる

From Pure Math to Applied Math

Stat/ML from a pure mathematical perspective.

EdwardでBayesian DNN+Variational Inferenceをやってみた話

機械学習 データ解析 実験

こんにちは。


今(僕の中で)話題沸騰中のベイズ統計用Pythonライブラリ

Edward

GitHub - blei-lab/edward: A library for probabilistic modeling, inference, and criticism. Deep generative models, variational inference. Runs on TensorFlow.


を使って Bayesian DNN & Variational Inference

をやってみましたので、その報告&コードの簡単な解説&感想をこの記事では残しておこうと思います。


前回の記事

mathetake.hatenablog.com

で用意したHiggs粒子データセットを使って、分類器を作ろうと思います.


※Edwardってなんぞやって人は、公式Webまたは次の論文

[1701.03757] Deep Probabilistic Programming

[1610.09787] Edward: A library for probabilistic modeling, inference, and criticism

を読んで頂けたらと思います。
(僕自身Edwardについて全然把握しきれていないため、偉そうな顔してプロモーションすることはしません。)

§1 Edwardでのコーディング

Edwardは基本的にTensorflow(TF)をバックエンドに使って計算しているため、
モデル構築⇒推論の流れはTFにおけるグラフの構築⇒計算そのものです。

以下では4層のDNNをlogitに使った2値分類機を作ります。
まずTFでグラフを作ります。

import tensorflow as tf

# X_train:特徴量のデータセット
#Y_train: 説明変数のデータセット

N = X_train.shape[0]  # データ数
D = X_train.shape[1] # 特徴数
M = 50000 #バッチサイズ
n_epoch=30 #ポック数

def four_layer_nn(x,W_1,W_2,W_3,b_1,b_2):
    h = tf.tanh(tf.matmul(x, W_1) + b_1)
    h = tf.tanh(tf.matmul(h, W_2) + b_2)
    h = tf.matmul(h, W_3) 
    return tf.reshape(h, [-1])

次に各ウェイト(W_n & b_n)事前分布を定義します

from edward.models import Normal, Bernoulli, Uniform

#事前分布 on weights and biases
W_1 = Normal(mu=tf.zeros([D, 20]), sigma=tf.ones([D, 20])*100)
W_2 = Normal(mu=tf.zeros([20, 15]), sigma=tf.ones([20, 15])*100)
W_3 = Normal(mu=tf.zeros([15, 1]), sigma=tf.ones([15, 1])*100)
b_1 = Normal(mu=tf.zeros(20), sigma=tf.ones(20)*100)
b_2 = Normal(mu=tf.zeros(15), sigma=tf.ones(15)*100)

Edwardにおける確率分布は全てTFのそれです。(ただしMCMCをする際の経験分布/EmpiricalはEdward特有のもの。)
分かりやすい。

次にnaiveに変分ベイズで用いる各ウェイトに対する近似分布を定義します。
これはiteration毎に更新される分布なので,TFのVariableをEdwardの分布のクラスにぶち込みます.

#各ウェイトを近似する分布
qW_1 = Normal(mu=tf.Variable(tf.random_normal([D, 20])),
              sigma=tf.nn.softplus(tf.Variable(tf.random_normal([D, 20]))))
qW_2 = Normal(mu=tf.Variable(tf.random_normal([20, 15])),
              sigma=tf.nn.softplus(tf.Variable(tf.random_normal([20, 15]))))
qW_3 = Normal(mu=tf.Variable(tf.random_normal([15, 1])),
              sigma=tf.nn.softplus(tf.Variable(tf.random_normal([15, 1]))))
qb_1 = Normal(mu=tf.Variable(tf.random_normal([20])),
              sigma=tf.nn.softplus(tf.Variable(tf.random_normal([20]))))
qb_2 = Normal(mu=tf.Variable(tf.random_normal([15])),
              sigma=tf.nn.softplus(tf.Variable(tf.random_normal([15]))))


最後にEdward のInferenceクラスのオブジェクトを作ります.

import edward as ed
#Inferenceクラスの構築. X_train/Y_trainのBatchを入れるplace_holderを準備
x_ph  = tf.placeholder(tf.float32,[M,D])
y = Bernoulli(logits=four_layer_nn(x_ph, W_1, W_2, W_3, b_1, b_2))
y_ph = tf.placeholder(tf.float32,[M])
inference = ed.KLqp({W_1: qW_1, b_1: qb_1,W_2: qW_2, b_2: qb_2,W_3: qW_3}, data={y: y_ph})

ここでx_ph, y_ph はTFのplacefolderで定義されており、あとでBatch毎にiterationを回すための仮変数で、X_train,Y_trainを分割したBatchが挿入される部分になっています。

以上で準備ができたのでパラメータ(今の場合qb__k,qW_kの)推定をします。

#推論開始(subsamplingについては:http://edwardlib.org/api/inference-data-subsampling)
# n_iterはこのセッションにおけるiteration回数.
# n_printはinfo_dictに出力する回数.
inference.initialize(n_iter=n_epoch*int(float(N)/M), n_print=100)
tf.initialize_all_variables().run()
X_batch_list, Y_batch_list = make_batch_list(X_train,Y_train,M)
for _ in xrange(n_epoch):
  for i in xrange(len(X_batch_list)):
    info_dict=inference.update(feed_dict={x_ph: X_batch_list[i], y_ph: Y_batch_list[i]})
    inference.print_progress(info_dict)
inference.finalize() #session終了

まずinitializeでTFのセッションが始まります。
その後各variableを初期化します。(tf.initialize_all_variables().run())
そして全バッチに対するパラメータ更新をエポック数だけ回します.

これだけです。簡単です。


このあと推論の結果を確認するには以下のようにすればOKです。

#評価(http://edwardlib.org/tutorials/point-evaluation)
#X_testを挿入するためのTFのplaceholderを準備.
x_test_ph  = tf.placeholder(tf.float32,[X_test.shape[0],D])
#次にy_postに推定された事後分布をコピー
y = Bernoulli(logits=four_layer_nn(x_test_ph, W_1, W_2, W_3, b_1, b_2))
y_post = ed.copy(y, {W_1: qW_1.mean(), b_1: qb_1.mean(),W_2: qW_2.mean(), \
 b_2: qb_2.mean(),W_3: qW_3.mean()})

#BinaryAccuacyで評価
print 'Binary accuracy on test data:'
print ed.evaluate('binary_accuracy', data={x_test_ph: X_test, y_post: Y_test})


以上がEdwardで行う

Bayesian DNNを構成 -> 変分ベイズ -> 結果の確認

のステップです。

※ソース全文はこの記事の末尾につけておきます。

§2 推論の結果と所感

テキトーにネットワークの構成(7-20-15-1)は変えずに色々試したのですが、

Binary Accuracyは60~63%ほどしかでませんでした。

素直に勾配法で最適化した時は70%ほどは出ていたので、うーーーん。。。と言う感じです。
(2次の多項式特徴+ロジスティック回帰と同等の性能です.)


ただ、収束は素直な最適化に比べてかなり高速で安定してます。

(正確な比較データがあれば良いですが、やりなおすのも面倒なので許してください...)



正直DNNで構成されるような複雑な事後分布を上のようなNaiveな分布で近似するのは相当無理があるのでは?とちょっと思いました。


ただ、上のような収束が早くて安定している推論をしたあとに、それを初期値として利用して通常の勾配法などで最適化をinitializeするのはアリなのかな、とか思ったり。(しかもその操作はベースがTFで構成されているのでseamlessに出来るかも?)

§3 MCMCはまだ不安定なのか?と言う話

変分ベイズだけじゃなくてMCMCはどうなんよ???

と言う質問ですね。分かります。

この論文[1701.03757] Deep Probabilistic Programming によれば

54次元の特徴量 & 581012個のデータセット

を使ったロジスティック回帰で、Stanの35倍の速さがでたらしいです。


そんなこと言われたらベイジアンDNNで試したくなります。


が、色々試していたのですがバグが見つかってしまいました。

gitのissueに投げたら、すぐ回答を頂いたのですが...

github.com

近いうちに修正するらしいです。(ソースを自分で書き換えれば簡単に修正できるっぽいですがそこまでモチベーションがない。。。)


他にもHMCが簡単なモデルに対して収束しない問題も報告されています。
(投稿者がPyMC3のdeveloperなのがちょっとオモシロイ。)

github.com

そんなこんなで未だにMCMCは試せていません。無念。
まだまだ発展途上と言うことで、これからに期待したいと思います。



以上、少し短かったですが

EdwardでBayesian DNN+Variational Inferenceをやってみた話

でした。


dnn + Variational inference by Edward