TensorFlow : Edward Tutorials : 混合密度ネットワーク (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 10/22/2018
* 本ページは、Edward サイトの Tutorials : Mixture density networks を翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
混合密度ネットワーク
混合密度ネットワーク (MDN, Mixture density networks) (Bishop, 1994) は従来のニューラルネットワークを混合密度モデルと結合して得られたモデルのクラスです。
Edward で例を示します。Jupyter notebook による対話的バージョンは こちら で利用可能です。
データ
私達は David Ha のブログ投稿 からの同じ toy データを使用します、そこでは彼は MDN を説明しています。それは逆問題 (= inverse problem) でそこでは総ての入力 $x_n$ に対して複数の出力 $y_n$ があります。
from sklearn.model_selection import train_test_split def build_toy_dataset(N): y_data = np.random.uniform(-10.5, 10.5, N) r_data = np.random.normal(size=N) # random noise x_data = np.sin(0.75 * y_data) * 7.0 + y_data * 0.5 + r_data * 1.0 x_data = x_data.reshape((N, 1)) return train_test_split(x_data, y_data, random_state=42) N = 5000 # number of data points D = 1 # number of features X_train, X_test, y_train, y_test = build_toy_dataset(N) print("Size of features in training data: {}".format(X_train.shape)) print("Size of output in training data: {}".format(y_train.shape)) print("Size of features in test data: {}".format(X_test.shape)) print("Size of output in test data: {}".format(y_test.shape)) sns.regplot(X_train, y_train, fit_reg=False)
## Size of features in training data: (3750, 1) ## Size of output in training data: (3750,) ## Size of features in test data: (1250, 1) ## Size of output in test data: (1250,)
TensorFlow プレースホルダを定義します、これは推論の間にデータのバッチを手動で供給するために使用されます。これは Edward においてデータでモデルを訓練するための 多くの方法の一つ です。
X_ph = tf.placeholder(tf.float32, [None, D]) y_ph = tf.placeholder(tf.float32, [None])
モデル
feedforward ネットワークによりパラメータ化された 20 正規分布の混合を使用します。すなわち、メンバーシップ確率と構成要素毎 mean と標準偏差は feedforward ネットワークの出力により与えられます。
ニューラルネットワークを構築するために tf.layers を使用します。各隠れ層に対して 15 隠れユニットを持つ 3-層ネットワークを指定します。
from edward.models import Categorical, Mixture, Normal def neural_network(X): """loc, scale, logits = NN(x; theta)""" # 2 hidden layers with 15 hidden units net = tf.layers.dense(X, 15, activation=tf.nn.relu) net = tf.layers.dense(net, 15, activation=tf.nn.relu) locs = tf.layers.dense(net, K, activation=None) scales = tf.layers.dense(net, K, activation=tf.exp) logits = tf.layers.dense(net, K, activation=None) return locs, scales, logits K = 20 # number of mixture components locs, scales, logits = neural_network(X_ph) cat = Categorical(logits=logits) components = [Normal(loc=loc, scale=scale) for loc, scale in zip(tf.unstack(tf.transpose(locs)), tf.unstack(tf.transpose(scales)))] y = Mixture(cat=cat, components=components, value=tf.zeros_like(y_ph))
Mixture 確率変数を使用していることに注意してください。それは各データポイントに対するメンバーシップ割り当てを崩壊させて (= collapse) モデルをその総てのパラメータについて微分可能にします。それは入力として (混合する個々の分布のリストである) 構成要素だけでなく Categorical 確率変数 – 各クラスタ割り当てに対する確率を表わします – も取ります。
MDN の更なる背景については、Christopher Bonnett のブログ投稿 か Bishop (1994) を見てください。
推論
モデルとデータセットを渡して、MAP 推定を使用します、Edward の MAP 推定 についてのこの拡張チュートリアルを見てください。
inference = ed.MAP(data={y: y_ph})
ここで、推論と各ステップでそれにどのようにデータを渡すかを手動で制御します。アルゴリズムと TensorFlow 変数を初期化します。
optimizer = tf.train.AdamOptimizer(5e-3) inference.initialize(optimizer=optimizer, var_list=tf.trainable_variables()) sess = ed.get_session() tf.global_variables_initializer().run()
さてデータを渡して、inference.update() を呼び出すことにより MDN を訓練します。数量 inference.loss は推論のステップにおける損失関数 (負の対数尤度) です。inference.loss を呼び出すことによりテストデータ上でも損失関数をレポートします、そこでは訓練データの代わりにテストデータを TensorFlow プレースホルダに供給します。train_loss and test_loss のもとで損失を追跡します。
n_epoch = 1000 train_loss = np.zeros(n_epoch) test_loss = np.zeros(n_epoch) for i in range(n_epoch): info_dict = inference.update(feed_dict={X_ph: X_train, y_ph: y_train}) train_loss[i] = info_dict['loss'] test_loss[i] = sess.run(inference.loss, feed_dict={X_ph: X_test, y_ph: y_test}) inference.print_progress(info_dict)
MDN を訓練するときの一般的な失敗モードは個々の混合分布が点に崩壊することであることに注意してください。これは正規分布の標準偏差を 0 に近くあることを強制して NaN 値を生成します。望むのであれば標準偏差に閾値を設定することでこれを回避できます。
数多くの反復の間の訓練の後、モデルから興味のある予測を取り出します: 予測された混合重み、クラスタ mean、そしてクラスタ標準偏差です。これを行なうため、テストデータ X_test をプレースホルダ X_ph に供給して、セッションからそれらの値を取得します。
pred_weights, pred_means, pred_std = sess.run( [tf.nn.softmax(logits), locs, scales], feed_dict={X_ph: X_test})
訓練とテストデータの対数尤度を訓練エポックの関数としてプロットしましょう。数量 inference.loss は総計の対数尤度で、データポイント毎の損失ではありません。下で訓練とテストデータそれぞれをサイズで割ることによりデータポイント毎の対数尤度をプロットします。
fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(16, 3.5)) plt.plot(np.arange(n_epoch), -test_loss / len(X_test), label='Test') plt.plot(np.arange(n_epoch), -train_loss / len(X_train), label='Train') plt.legend(fontsize=20) plt.xlabel('Epoch', fontsize=15) plt.ylabel('Log-likelihood', fontsize=15) plt.show()
それがおよそ 400 反復後に収束することを見ます。
批評
個々の例がどのように遂行するか見てみましょう。これは逆問題ですので正しい答えは得られないことに注意してください、しかし正解がモデルが高い確率を持つ領域にあることを望むことはできます。
このプロットでは正解は垂直のグレーの線で、一方で青色の線は混合密度ネットワークの予測です。見て取れるように、ある程度上手くやりました。
obj = [0, 4, 6] fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(16, 6)) plot_normal_mix(pred_weights[obj][0], pred_means[obj][0], pred_std[obj][0], axes[0], comp=False) axes[0].axvline(x=y_test[obj][0], color='black', alpha=0.5) plot_normal_mix(pred_weights[obj][2], pred_means[obj][2], pred_std[obj][2], axes[1], comp=False) axes[1].axvline(x=y_test[obj][2], color='black', alpha=0.5) plot_normal_mix(pred_weights[obj][1], pred_means[obj][1], pred_std[obj][1], axes[2], comp=False) axes[2].axvline(x=y_test[obj][1], color='black', alpha=0.5)
予測のサンプルをドローしてそれらの密度をプロットすることによりアンサンブルをチェックできます。MDN はそれに学習させたいことを学習しました。
a = sample_from_mixture(X_test, pred_weights, pred_means, pred_std, amount=len(X_test)) sns.jointplot(a[:,0], a[:,1], kind="hex", color="#4CB391", ylim=(-10,10), xlim=(-14,14))
References
- Bishop, C. M. (1994). Mixture density networks.
以上