TensorFlow : Edward Tutorials : バッチ訓練 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 10/16/2018
* 本ページは、Edward サイトの Tutorials : Batch Training を翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
バッチ訓練
各更新に完全なデータセットを必要とするアルゴリズムを実行することはデータが巨大であるとき高価であるかもしれません。推論をスケールするためには、バッチ訓練を行なうことができます。これは一度にデータのサブサンプルだけを使用してモデルを訓練します。
このチュートリアルでは、教師あり学習チュートリアル を拡張します、そこではタスクはラベル付けされたサンプル $\{(x_n, y_n)\}$ から隠れ構造を推論することです。
Jupyter notebook による対話的バージョンは こちら で利用可能です。
データ
$N$ 訓練サンプルと固定数のテストサンプルをシミュレートします。各サンプルは入力 $\mathbf{x}_n\in\mathbb{R}^{10}$ と出力 $y_n\in\mathbb{R}$ のペアです。それらは正規分布に従う線形従属を持ちます。
def build_toy_dataset(N, w): D = len(w) x = np.random.normal(0.0, 2.0, size=(N, D)) y = np.dot(x, w) + np.random.normal(0.0, 0.05, size=N) return x, y N = 10000 # size of training data M = 128 # batch size during training D = 10 # number of features w_true = np.ones(D) * 5 X_train, y_train = build_toy_dataset(N, w_true) X_test, y_test = build_toy_dataset(235, w_true)
サンプルのフルセットからデータポイントの次のバッチを選択するヘルパー関数も定義します。それは現在のバッチ・インデックスを追跡して関数 next() を使用して次のバッチを返します。
def generator(arrays, batch_size): """Generate batches, one with respect to each array's first axis.""" starts = [0] * len(arrays) # pointers to where we are in iteration while True: batches = [] for i, array in enumerate(arrays): start = starts[i] stop = start + batch_size diff = stop - array.shape[0] if diff <= 0: batch = array[start:stop] starts[i] += batch_size else: batch = np.concatenate((array[start:], array[:diff])) starts[i] = diff batches.append(batch) yield batches data = generator([X_train, y_train], M)
推論の間にデータからバッチを生成します。
モデル
モデルをベイジアン線形回帰として仮定します (Murphy, 2012)。$N$ データポイント $(\mathbf{X},\mathbf{y})=\{(\mathbf{x}_n, y_n)\}$ のセットのために、モデルは次の分布を仮定します :
\[
\begin{aligned}
p(\mathbf{w})
&=
\text{Normal}(\mathbf{w} \mid \mathbf{0}, \sigma_w^2\mathbf{I}),
\\[1.5ex]
p(b)
&=
\text{Normal}(b \mid 0, \sigma_b^2),
\\
p(\mathbf{y} \mid \mathbf{w}, b, \mathbf{X})
&=
\prod_{n=1}^N
\text{Normal}(y_n \mid \mathbf{x}_n^\top\mathbf{w} + b, \sigma_y^2).\end{aligned}
\]
潜在変数は線形モデルの重み $\mathbf{w}$ とバイアスとしても知られる切片 $b$ です。$\sigma_w^2,\sigma_b^2$ は既知の事前分散で $\sigma_y^2$ は既知の尤度分散であると仮定します。尤度の平均は入力 $\mathbf{x}_n$ 線形変換で与えられます。
$\sigma_w,\sigma_b,\sigma_y=1$ を固定して、Edward でモデルを構築しましょう。
X = tf.placeholder(tf.float32, [None, D]) y_ph = tf.placeholder(tf.float32, [None]) w = Normal(loc=tf.zeros(D), scale=tf.ones(D)) b = Normal(loc=tf.zeros(1), scale=tf.ones(1)) y = Normal(loc=ed.dot(X, w) + b, scale=1.0)
ここで、プレースホルダ X を定義します。推論の間、データのバッチに従ってこのプレースホルダに値を渡します。様々なサイズのバッチでの訓練を可能にするため、X と y のための行数は固定しません (代わりに、固定サイズで訓練してテストをするのであれば、それを batch size に固定することもできるでしょう)。
推論
さて変分推論を使用して事後分布を推論することに取り掛かります。変分モデルを重みに渡る fully factorized normal として定義します。
qw = Normal(loc=tf.get_variable("qw/loc", [D]), scale=tf.nn.softplus(tf.get_variable("qw/scale", [D]))) qb = Normal(loc=tf.get_variable("qb/loc", [1]), scale=tf.nn.softplus(tf.get_variable("qb/scale", [1])))
Kullback-Leibler ダイバージェンスで変分推論を実行します。アルゴリズムのブラックボックス確率的勾配を計算するために 55 の潜在変数サンプルを使用します (より詳細については、$\text{KL}(q\|p)$ チュートリアル を見てください)。
バッチ訓練については、バッチ数に渡り反復してそれらをそれぞれのプレースホルダに供給します。反復数を 5 エポック (データセットに渡るフルパス) に渡るバッチの総数に設定します。
n_batch = int(N / M) n_epoch = 5 inference = ed.KLqp({w: qw, b: qb}, data={y: y_ph}) inference.initialize( n_iter=n_batch * n_epoch, n_samples=5, scale={y: N / M}) tf.global_variables_initializer().run() for _ in range(inference.n_iter): X_batch, y_batch = next(data) info_dict = inference.update({X: X_batch, y_ph: y_batch}) inference.print_progress(info_dict)
390/390 [100%] ██████████████████████████████ Elapsed: 4s | Loss: 10481.556
推論を初期化するとき、$y$ を $N/M$ でスケールすることに注意してください、so it is as if the algorithm had seen $N/M$ as many data points per iteration. アルゴリズム的には、変分法の目的 (= objective) の対数尤度をスケールするように、$y$ に関する総ての計算を $N/M$ でスケールします (統計的には、これは推論が事前分布に支配されることを回避しています)。
ループ構成は訓練を非常に柔軟にします。例えば、各バッチに対して多くの更新を実行しようとすることもまた可能です。
n_batch = int(N / M) n_epoch = 1 inference = ed.KLqp({w: qw, b: qb}, data={y: y_ph}) inference.initialize(n_iter=n_batch * n_epoch * 10, n_samples=5, scale={y: N / M}) tf.global_variables_initializer().run() for _ in range(inference.n_iter // 10): X_batch, y_batch = next(data) for _ in range(10): info_dict = inference.update({X: X_batch, y_ph: y_batch}) inference.print_progress(info_dict)
770/780 [ 98%] █████████████████████████████ ETA: 0s | Loss: 9760.541
一般に、推論を初期化するとき訓練反復の総数が正しく指定されていることを確実にしてください。そうでないと訓練反復の不正な数が意図しない結果を持つ可能性があります ; 例えば、ed.KLqp はその optimizer の学習率のステップサイズを適切に減衰するために内部カウンターを使用します。
アルゴリズムを実行して報告された損失値は (トータルのデータセットではなく) 現在のバッチが与えられたときに計算された目的関数に対応していることにも注意してください。代わりに各エポックに対する info_dict[’loss’] を合計することによりトータルのデータセットに渡る損失をそれに報告させることができます。
批評
回帰のための標準的な評価は取り置きをしておいた「テスティング」データ上で予測精度を比較することです。最初に事後予測分布を形成することでこれを行ないます。
y_post = ed.copy(y, {w: qw, b: qb}) # This is equivalent to # y_post = Normal(loc=ed.dot(X, qw) + qb, scale=tf.ones(N))
これによってモデルからの予測 (事後予測) を使用して様々な数量を評価できます。
print("Mean squared error on test data:") print(ed.evaluate('mean_squared_error', data={X: X_test, y_post: y_test})) print("Mean absolute error on test data:") print(ed.evaluate('mean_absolute_error', data={X: X_test, y_post: y_test}))
## Mean squared error on test data: ## 0.00659598 ## Mean absolute error on test data: ## 0.0705906
訓練されたモデルは (出力の大きさに比して) 低い損失で予測を行ないます。
Footnotes
MAP, KLqp と SGLD のような特定のアルゴリズムだけがバッチ訓練をサポートします。また、上では (総てのデータポイントに渡り共有される変数である) グローバル潜在変数だけを持つモデルのためのバッチ訓練を示しました。より複雑なストラテジーについては、inference data subsampling API を見てください。
References
- Murphy, K. P. (2012). Machine learning: A probabilistic perspective. MIT Press.
以上