TensorFlow : TensorLayer : チュートリアル (5) (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 12/06/2018
* 本ページは、TensorLayer の以下のドキュメントの一部を翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
チュートリアル (5)
PTB サンプルを実行する
Penn TreeBank (PTB) データセットは “Empirical Evaluation and Combination of Advanced Language Modeling Techniques”, “Recurrent Neural Network Regularization” を含む、多くの 言語モデリング ペーパーで使用されています。それは 929k 訓練単語、73k 検証単語そして 82k テスト単語から成ります。それはその語彙に 10k 単語を持ちます。
PTB サンプルは言語モデリングの挑戦タスク上でリカレント・ニューラルネットワークをどのように訓練するかを示すことを試行します。
センテンス “I am from Imperial College London” が与えられたとき、モデルは “from Imperial College” から “Imperial College London” を予測することを学習できます。換言すれば、それは前の単語の履歴が与えられたときテキストの次の単語を予測します。前の例では、num_steps (シークエンス長) は 3 です。
python tutorial_ptb_lstm.py
スクリプトは 3 つの設定 (small, medium, large) を提供します、そこではより大きなモデルはより良いパフォーマンスを持ちます。次で異なる設定を選択できます :
flags.DEFINE_string( "model", "small", "A type of model. Possible options are: small, medium, large.")
small 設定を選択する場合、以下を見ることができます :
Epoch: 1 Learning rate: 1.000 0.004 perplexity: 5220.213 speed: 7635 wps 0.104 perplexity: 828.871 speed: 8469 wps 0.204 perplexity: 614.071 speed: 8839 wps 0.304 perplexity: 495.485 speed: 8889 wps 0.404 perplexity: 427.381 speed: 8940 wps 0.504 perplexity: 383.063 speed: 8920 wps 0.604 perplexity: 345.135 speed: 8920 wps 0.703 perplexity: 319.263 speed: 8949 wps 0.803 perplexity: 298.774 speed: 8975 wps 0.903 perplexity: 279.817 speed: 8986 wps Epoch: 1 Train Perplexity: 265.558 Epoch: 1 Valid Perplexity: 178.436 ... Epoch: 13 Learning rate: 0.004 0.004 perplexity: 56.122 speed: 8594 wps 0.104 perplexity: 40.793 speed: 9186 wps 0.204 perplexity: 44.527 speed: 9117 wps 0.304 perplexity: 42.668 speed: 9214 wps 0.404 perplexity: 41.943 speed: 9269 wps 0.504 perplexity: 41.286 speed: 9271 wps 0.604 perplexity: 39.989 speed: 9244 wps 0.703 perplexity: 39.403 speed: 9236 wps 0.803 perplexity: 38.742 speed: 9229 wps 0.903 perplexity: 37.430 speed: 9240 wps Epoch: 13 Train Perplexity: 36.643 Epoch: 13 Valid Perplexity: 121.475 Test Perplexity: 116.716
PTB サンプルは RNN が言語をモデル化できることを示しますが、このサンプルは何か実践的に興味深いことは行ないませんでした。けれども貴方は RNN の基本を理解するためにこの例と “Understand LSTM” を読み通すべきです。その後で、RNN を使用することによってテキストをどのように生成するか、言語翻訳をどのように達成するか、そして質問応答システムをどのように構築するかを学習するでしょう。
LSTM を理解する
リカレント・ニューラルネットワーク
個人的にはリカレント・ニューラルネットワークを理解するためには Andrej Karpathy のブログ が最善の資料と考えます、それを読んだ後、Colah のブログ が (長期依存性の問題を解くことができる) LSTM ネットワークを理解する助けとなれます。RNN の理論についてはこれ以上記述しませんので、進む前にこれらのブログを通して読んでください。
Image by Andrej Karpathy
同期したシークエンス入力と出力
PTB サンプルのモデルは同期したシークエンス入力と出力の典型的なタイプです、これは Karpathy により次のように記述されていました: “(5) Synced sequence input and output (e.g. ビデオの各フレームにラベル付けることを望むビデオ分類)。総てのケースでシークエンスの長さについて事前指定された制約がないことに注意してください、何故ならばリカレント変換 (green) は好きなだけの回数適用可能であるからです。”
モデルは次のように構築されます。最初に、埋め込み行列を検索することにより単語を単語ベクトルに移します。このチュートリアルでは、埋め込み行列上での事前訓練はありません。2 番目に、埋め込み層、LSTM 層(s) そして正則化のための出力層の間で dropout を使用して、2 つの LSTM を一緒にスタックします。最後の層では、モデルは softmax 出力のシークエンスを提供します。
最初の LSTM 層はその後でもう一つの LSTM をスタックするために [batch_size, num_steps, hidden_size] を出力します。2 番目の LSTM 層はその後で DenseLayer をスタックするために [batch_size*num_steps, hidden_size] を出力します。それから DenseLayer は各サンプル (n_examples = batch_size*num_steps) の softmax 出力を計算します。
PTB チュートリアルを理解するためには、TensorFlow PTB チュートリアル も読むことができます。
(TensorLayer は v1.1の後 DynamicRNNLayer をサポートしますので、一つの単一層で入力/出力 dropout、幾つかの RNN 層を設定できます。)
network = tl.layers.EmbeddingInputlayer( inputs = x, vocabulary_size = vocab_size, embedding_size = hidden_size, E_init = tf.random_uniform_initializer(-init_scale, init_scale), name ='embedding_layer') if is_training: network = tl.layers.DropoutLayer(network, keep=keep_prob, name='drop1') network = tl.layers.RNNLayer(network, cell_fn=tf.contrib.rnn.BasicLSTMCell, cell_init_args={'forget_bias': 0.0}, n_hidden=hidden_size, initializer=tf.random_uniform_initializer(-init_scale, init_scale), n_steps=num_steps, return_last=False, name='basic_lstm_layer1') lstm1 = network if is_training: network = tl.layers.DropoutLayer(network, keep=keep_prob, name='drop2') network = tl.layers.RNNLayer(network, cell_fn=tf.contrib.rnn.BasicLSTMCell, cell_init_args={'forget_bias': 0.0}, n_hidden=hidden_size, initializer=tf.random_uniform_initializer(-init_scale, init_scale), n_steps=num_steps, return_last=False, return_seq_2d=True, name='basic_lstm_layer2') lstm2 = network if is_training: network = tl.layers.DropoutLayer(network, keep=keep_prob, name='drop3') network = tl.layers.DenseLayer(network, n_units=vocab_size, W_init=tf.random_uniform_initializer(-init_scale, init_scale), b_init=tf.random_uniform_initializer(-init_scale, init_scale), act = tf.identity, name='output_layer')
データセット反復
batch_size は実行している同時の計算の数として見ることができます。次のサンプルが示すように、最初のバッチは項目 0 から 9 使用してシークエンス情報を学習します。2 番目のバッチは項目 10 から 19 を使用してシークエンス情報を学習します。従ってそれは項目 9 から 10 への情報は無視します。batch_size = 1 を設定する場合に限り、それは項目 0 から 20 への総ての情報を考慮します。
ここでは batch_size の意味は MNIST サンプルにおける batch_size と同じではありません。MNIST サンプルでは、batch_size は各反復で幾つのサンプルを考慮するかを反映しますが、一方で PTB サンプルでは、batch_size は計算を加速するための同時プロセス (セグメント) の数です。batch_size > 1 であればある情報は無視されます、けれども貴方のデータセットが十分に「長い」(テキスト・コーパスは通常は数十億の単語を持ちます) のであれば、無視された情報は最終結果に影響しないでしょう。
PTB チュートリアルでは、batch_size = 20 を設定しますので、データセットを 20 セグメントに分割します。各エポックの最初に、20 セグメントのための 20 RNN 状態をゼロに初期化 (リセット) し、それから 20 セグメントを別々に通り抜けます。
訓練データを生成する例は次のようなものです :
train_data = [i for i in range(20)] for batch in tl.iterate.ptb_iterator(train_data, batch_size=2, num_steps=3): x, y = batch print(x, '\n',y)
... [[ 0 1 2] <---x 1st subset/ iteration ... [10 11 12]] ... [[ 1 2 3] <---y ... [11 12 13]] ... ... [[ 3 4 5] <--- 1st batch input 2nd subset/ iteration ... [13 14 15]] <--- 2nd batch input ... [[ 4 5 6] <--- 1st batch target ... [14 15 16]] <--- 2nd batch target ... ... [[ 6 7 8] 3rd subset/ iteration ... [16 17 18]] ... [[ 7 8 9] ... [17 18 19]]
Note: このサンプルは単語埋め込み行列の事前訓練としても考えることができます。
損失と更新式
コスト関数は各ミニバッチの平均コストです :
nsorlayer.cost.cross_entropy_seq() for more details def loss_fn(outputs, targets, batch_size, num_steps): # Returns the cost function of Cross-entropy of two sequences, implement # softmax internally. # outputs : 2D tensor [batch_size*num_steps, n_units of output layer] # targets : 2D tensor [batch_size, num_steps], need to be reshaped. # n_examples = batch_size * num_steps # so # cost is the average cost of each mini-batch (concurrent process). loss = tf.nn.seq2seq.sequence_loss_by_example( [outputs], [tf.reshape(targets, [-1])], [tf.ones([batch_size * num_steps])]) cost = tf.reduce_sum(loss) / batch_size return cost # Cost for Training cost = loss_fn(network.outputs, targets, batch_size, num_steps)
更新については、学習プロセスを扱いやすくするために、truncated backpropagation が勾配の値をそれらのノルムの総計の比率によりクリップします。
# Truncated Backpropagation for training with tf.variable_scope('learning_rate'): lr = tf.Variable(0.0, trainable=False) tvars = tf.trainable_variables() grads, _ = tf.clip_by_global_norm(tf.gradients(cost, tvars), max_grad_norm) optimizer = tf.train.GradientDescentOptimizer(lr) train_op = optimizer.apply_gradients(zip(grads, tvars))
加えて、エポックインデックスが max_epoch よりも大きい場合には、学習率を lr_decay を乗算することにより減少させます。
new_lr_decay = lr_decay ** max(i - max_epoch, 0.0) sess.run(tf.assign(lr, learning_rate * new_lr_decay))
各エポックの最初に、LSTM の総ての状態はゼロ状態にリセット (初期化) される必要があります。それから各反復後、LSTM の状態は更新され、そこで新しい LSTM 状態 (最終状態) は次の反復の初期状態として割り当てられる必要があります :
# set all states to zero states at the beginning of each epoch state1 = tl.layers.initialize_rnn_state(lstm1.initial_state) state2 = tl.layers.initialize_rnn_state(lstm2.initial_state) for step, (x, y) in enumerate(tl.iterate.ptb_iterator(train_data, batch_size, num_steps)): feed_dict = {input_data: x, targets: y, lstm1.initial_state: state1, lstm2.initial_state: state2, } # For training, enable dropout feed_dict.update( network.all_drop ) # use the new states as the initial state of next iteration _cost, state1, state2, _ = sess.run([cost, lstm1.final_state, lstm2.final_state, train_op], feed_dict=feed_dict ) costs += _cost; iters += num_steps
予測する
モデルを訓練した後、次の出力を予測するとき、ステップ数 (シークエンス長) はもはや考慮しません、i.e. batch_size, num_steps は 1 に設定されます。それで単語のシークエンスから単語のシークエンスを予測する代わりに、一つずつ次の単語を出力できます。
input_data_test = tf.placeholder(tf.int32, [1, 1]) targets_test = tf.placeholder(tf.int32, [1, 1]) ... network_test, lstm1_test, lstm2_test = inference(input_data_test, is_training=False, num_steps=1, reuse=True) ... cost_test = loss_fn(network_test.outputs, targets_test, 1, 1) ... print("Evaluation") # Testing # go through the test set step by step, it will take a while. start_time = time.time() costs = 0.0; iters = 0 # reset all states at the beginning state1 = tl.layers.initialize_rnn_state(lstm1_test.initial_state) state2 = tl.layers.initialize_rnn_state(lstm2_test.initial_state) for step, (x, y) in enumerate(tl.iterate.ptb_iterator(test_data, batch_size=1, num_steps=1)): feed_dict = {input_data_test: x, targets_test: y, lstm1_test.initial_state: state1, lstm2_test.initial_state: state2, } _cost, state1, state2 = sess.run([cost_test, lstm1_test.final_state, lstm2_test.final_state], feed_dict=feed_dict ) costs += _cost; iters += 1 test_perplexity = np.exp(costs / iters) print("Test Perplexity: %.3f took %.2fs" % (test_perplexity, time.time() - start_time))
What Next?
今では、貴方は同期した入力と出力を理解しました。"Many to one (シークエンス入力と一つの出力)" について考えましょう、その結果 LSTM は “I am from London, I speak ..” から次の単語 "English" を予測することができます。
tutorial_generate_text.py のコードを読んで理解してください。それは事前訓練された埋め込み行列をどのように restore するか、そして与えられたコンテキストからどのようにテキスト生成を学習するかを示します。
Karpathy のブログ : “(3) センテンス入力 (e.g. センチメント解析、そこでは与えられたセンテンスが正あるいは負のセンチメントを表わすとして分類されます)。"
以上