ホーム » LSTM

LSTM」カテゴリーアーカイブ

TensorFlow : TensorLayer : チュートリアル (5) 言語モデル (LSTM)

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. センチメント解析、そこでは与えられたセンテンスが正あるいは負のセンチメントを表わすとして分類されます)。"

 

以上






TensorFlow : Tutorials : Sequences : ニューラル機械翻訳 (seq2seq) チュートリアル

TensorFlow : Tutorials : Sequences : ニューラル機械翻訳 (seq2seq) チュートリアル (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
更新日時 : 09/02, 07/16/2018
作成日時 : 06/07/2018

* TensorFlow 1.9 でドキュメント構成が変わりましたので調整しました。
* 本ページは、TensorFlow 本家サイトの Tutorials – Sequences – Neural Machine Translation (seq2seq) Tutorial を
翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、適宜、追加改変している場合もあります。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 


 

1 章. イントロダクション

 
Sequence-to-sequence (seq2seq) モデル (Sutskever et al., 2014, Cho et al., 2014) は機械翻訳、音声認識、そしてテキスト要約のような様々なタスクで大きな成功を楽しんでいます。このチュートリアルは読者に seq2seq モデルの完全な理解を与えて競争力のある seq2seq モデルをスクラッチからどのように構築するかを示します。ニューラル機械翻訳 (NMT) のタスクにフォーカスします、これは大成功 (= wild success) した seq2seq モデルのための本当に最初のテストベッドでした。含まれるコードは軽量で、高品質で、プロダクション・レディで、そして最新の研究アイデアが組み込まれています。この目標を以下により獲得します :

  1. 最近のデコーダ / attention ラッパー API、TensorFlow 1.2 データ iterator を使用する。
  2. 私達の強い専門技術をリカレントと seq2seq モデルの構築に組み入れる。
  3. 非常にベストな NMT モデルの構築と Google の NMT (GNMT) システム のレプリカのためのティップスとトリックを提供する。

私達は簡単にレプリカが作成できるベンチマークを提供することが重要であると信じます。その結果、完全な実験結果を提供して次の公に利用可能なデータセット上でモデルを事前訓練しました :

  1. Small-スケール: IWSLT Evaluation Campaign により提供される、英越 (= English-Vietnamese) パラレルコーパス of TED talks (133K センテンス・ペア)
  2. Large-スケール: WMT Evaluation Campaign により提供される、独英パラレルコーパス (4.5M センテンス・ペア)

最初に NMT のための seq2seq モデルについての何某かの基本知識を築き上げて、vanilla NMT モデルをどのように構築して訓練するかを説明します。2 番目のパートは attention メカニズムを持つ競争力のある NMT モデルの構築の詳細に入ります。それから、TensorFlow ベストプラクティス、bidirectional (双方向) RNN、ビームサーチ、更に GNMT attention を使用してマルチ GPU へスケールアップするというような、(速度と翻訳品質の両者で) ベストな可能な NMT モデルを構築するためにティップスとトリックを議論します。

 

2 章. 基本

ニューラル機械翻訳の背景

昔に戻れば、伝統的なフレーズ・ベースの翻訳システムはソース・センテンスを複数のチャンクに分解することによりタスクを遂行して、それからそれらをフレーズ・バイ・フレーズに翻訳しました。これは翻訳出力において流暢ではなく私達、人間が、翻訳するようなものではありませんでした。私達はソース・センテンス全体を読み、その意味を理解し、そしてそれから翻訳を生成します。ニューラル機械翻訳 (NMT) はそれを模倣します!

Figure 1. エンコーダ-デコーダ・アーキテクチャ – NMT のための一般的なアプローチの例です。エンコーダはソース・センテンスを “意味” ベクトルに変換します、これは翻訳を生成するためにデコーダを通して渡されます。

 
具体的には、NMT システムは最初に “thought” ベクトル を構築するためにエンコーダを使用して、ソース・センテンスを読みます、それは数字のシークエンスでセンテンスの意味を表します ; それからデコーダは、Figure 1 で示されるように翻訳を吐くためにセンテンス・ベクトルを処理します。これはしばしばエンコーダ-デコーダ・アーキテクチャとして参照されます。この方法で、NMT は伝統的なフレーズ・ベースのアプローチの局所翻訳問題に対処します : それは言語の long-range dependencies, e.g., gender agreements; シンタクス構造; etc., を捕捉することができて、そして Google Neural Machine Translation systems でデモされるような遥かにより流暢な翻訳を生成します。

NMT モデルはそれらの正確なアーキテクチャの観点から様々です。シーケンシャル・データのための自然な選択はリカレント・ニューラルネットワーク (RNN) で、殆どの NMT モデルで使用されます。通常は RNN はエンコーダとデコーダの両者のために使用されます。RNN モデルは、けれども、以下の観点から異なります : (a) 方向性 – 単方向 or 双方向 ; (b) 深さ – シングル- or マルチ層 ; そして (c) タイプ – しばしば vanilla RNN、Long Short-term Memory (LSTM)、または gated recurrent unit (GRU)。興味ある読者は RNN と LSTM についての更なる情報はこの ブログ投稿 で見つけることができます。

このチュートリアルでは、サンプルとして深層マルチ層 RNN を考えます、これは単方向でリカレント・ユニットとして LSTM を使用します。そのようなモデルのサンプルを Figure 2 で示します。このサンプルでは、ソース・センテンス “I am a student” をターゲット・センテンス “Je suis étudiant” に翻訳するモデルを構築します。高いレベルでは、NMT モデルは 2 つのリカレント・ニューラルネットワークから成ります : エンコーダ RNN はどのような予測をすることもなく単純に入力ソース単語を消費します ; デコーダは、一方で、次の単語を予測しながらターゲット・センテンスを処理します。

更なる情報のためには、読者は Luong (2016) を参照してください、これはこのチュートリアルがベースとしているものです。

Figure 2. ニューラル機械翻訳 – ソース・センテンス “I am a student” をターゲット・センテンス “Je suis étudiant” に翻訳するために提案された深層リカレント・アーキテクチャのサンプルです。ここで、”<s>” はデコーディング・プロセスの開始をマークしてその一方で “</s>” はデコーダに停止を伝えます。

 

チュートリアルをインストールする

このチュートリアルをインストールするためには、TensorFlow を貴方のシステム上にインストールする必要があります。このチュートリアルは TensorFlow Nightly を必要とします。TensorFlow をインストールするためには、ここのインストール手順 に従ってください。

ひとたび TensorFlow がインストールされれば、次を実行することでこのチュートリアルのソースコードをダウンロードできます :

git clone https://github.com/tensorflow/nmt/

 

訓練 – 最初の NMT システムをどのようにビルドするか

最初に具体的なコード・スニペットで NMT モデルの構築の中心へと飛び込みましょう、それを通して Figure 2 をより詳細に説明します。データ準備とフルコードは後に回します。このパートはファイル model.py を参照します。

ボトム層では、エンコーダとデコーダ RNN は入力として次を受け取ります : 最初に、ソース・センテンス、それから境界マーカー “\<s>”、これはエンコーディングからデコーディング・モードへの移行を示します、そしてターゲット・センテンスです。訓練のためには、次の tensor をシステムに供給します、これらは time-major 形式で単語インデックスを含みます :

  • encoder_inputs [max_encoder_time, batch_size]: ソース入力単語。
  • decoder_inputs [max_decoder_time, batch_size]: ターゲット入力単語。.
  • decoder_outputs [max_decoder_time, batch_size]: ターゲット出力単語、これらは右に追加された end-of-sentence タグを伴う左に 1 時間ステップシフトされた decoder_inputs です。

ここでは効率のために、複数のセンテンス (batch_size) で一度に訓練します。テスティングは少し異なりまるので、それは後で議論しましょう。

 

埋め込み

単語のカテゴリー的な性質が与えられたとき、モデルは対応する単語表現を取得するために最初にソースとターゲット埋め込みを調べなければなりません。この埋め込み層を動作させるために、最初に各言語のために語彙が選択されます。通常は、語彙サイズ V が選択され、そして最も頻度の高い V 単語だけが一意に扱われます。総ての他の単語は “unknown (未知)” トークンに変換されて総て同じ埋め込みを得ます。埋め込み重み、言語毎に 1 セット、は通常は訓練の間に学習されます。

# Embedding
embedding_encoder = variable_scope.get_variable(
    "embedding_encoder", [src_vocab_size, embedding_size], ...)
# Look up embedding:
#   encoder_inputs: [max_time, batch_size]
#   encoder_emb_inp: [max_time, batch_size, embedding_size]
encoder_emb_inp = embedding_ops.embedding_lookup(
    embedding_encoder, encoder_inputs)

同様に、embedding_decoder と decoder_emb_inp を構築することができます。word2vec や Glove ベクトルのような事前訓練された単語表現で埋め込み重みを初期化することを選択できることに注意してください。一般に、訓練データの巨大な総量が与えられた場合にはスクラッチからこれらの埋め込みを学習できます。

 

エンコーダ

ひとたび取得されれば、単語埋め込みはそれから入力としてメイン・ネットワークに供給されます、これは 2 つのマルチ層 RNN から成ります – ソース言語のためのエンコーダとターゲット言語のためのデコーダです。これらの 2 つの RNN は、原理的には、同じ重みを共有できます ; けれども、実際には、2 つの異なる RNN パラメータをしばしば使用します (そのようなモデルは巨大な訓練データセットにフィットするときより良いジョブを行ないます)。エンコーダ RNN はその開始状態としてゼロ・ベクトルを使用して次のように構築されます :

# Build RNN cell
encoder_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)

# Run Dynamic RNN
#   encoder_outputs: [max_time, batch_size, num_units]
#   encoder_state: [batch_size, num_units]
encoder_outputs, encoder_state = tf.nn.dynamic_rnn(
    encoder_cell, encoder_emb_inp,
    sequence_length=source_sequence_length, time_major=True)

センテンスは異なる長さを持つことに注意してください、無駄な計算を回避するために、dynamic_rnn に正確なソース・センテンスの長さを source_sequence_length を通して伝えます。私達の入力は time major ですので、time_major=True を設定します。ここでは、シングル層 LSTM, encoder_cell だけを構築します。どのようにマルチ層 LSTM を構築し、dropout を追加し、そして attention を使用するかについては後のセクションで説明します。

 

デコーダ

デコーダもまたソース情報へのアクセスを持つ必要があり、そしてそれを達成する一つの単純な方法はエンコーダの最後の隠れ状態, encoder_state でそれを初期化することです。Figure 2 で、ソース単語 “student” における隠れ状態をデコーダ側に渡します。

# Build RNN cell
decoder_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)
# Helper
helper = tf.contrib.seq2seq.TrainingHelper(
    decoder_emb_inp, decoder_lengths, time_major=True)
# Decoder
decoder = tf.contrib.seq2seq.BasicDecoder(
    decoder_cell, helper, encoder_state,
    output_layer=projection_layer)
# Dynamic decoding
outputs, _ = tf.contrib.seq2seq.dynamic_decode(decoder, ...)
logits = outputs.rnn_output

ここで、このコードの中心パートは BasicDecoder, decoder, で、これは入力として (encoder_cell と同様の) decoder_cell、ヘルパー、そして前の encoder_state を受け取ります。デコーダとヘルパーを分離することにより、異なるコードベースを再利用することができます、e.g., TrainingHelper は greedy デコーディングを行なうために GreedyEmbeddingHelper で置き換えられます。それ以上は helper.py を見てださい。

最後に、projection_layer に言及していませんでしたが、これはトップの隠れ状態を次元 V のロジット・ベクトルに変える dense 行列です。このプロセスを Figure 2 のトップに示します。

projection_layer = layers_core.Dense(
    tgt_vocab_size, use_bias=False)

 

損失

上でロジットが与えられ、訓練損失を計算する用意ができました :

crossent = tf.nn.sparse_softmax_cross_entropy_with_logits(
    labels=decoder_outputs, logits=logits)
train_loss = (tf.reduce_sum(crossent * target_weights) /
    batch_size)

ここで、target_weights は decoder_outputs と同じサイズの 0-1 (= zero-one) 行列です。それはターゲット・シークエンス長の外側の位置のパディングを値 0 でマスクします。

重要なノート: 損失を batch_size で除算するので、ハイパーパラメータは batch_size に対して “不変 (= invariant)” であることは指摘する価値があります。人によっては損失を (batch_size * num_time_steps) で分割しますが、これは短いセンテンス上のエラーを軽視します。より微妙なことに、(前者に適用された) 私達のハイパーパラメータは後者の方法のためには使用できません。例えば、両者のアプローチが 1.0 の学習率で SGD を使用する場合、後者のアプローチは事実上 1 / num_time_steps の遥かに小さい学習率を使用します。

 

勾配計算 & 最適化

私達は今 NMT モデルの forward パスを定義しました。backpropagation パスを計算することは単に数行のコードの事柄です :

# Calculate and clip gradients
params = tf.trainable_variables()
gradients = tf.gradients(train_loss, params)
clipped_gradients, _ = tf.clip_by_global_norm(
    gradients, max_gradient_norm)

RNN の訓練における重要なステップの一つは勾配クリッピングです。ここで、global norm でクリップします。max value, max_gradient_norm はしばしば 5 or 1 のような値に設定されます。最後のステップは optimizer の選択です。Adam optimizer は一般的な選択です。学習率もまた選択します。learning_rate の値は通常は 0.0001 から 0.001 の範囲にあります ; そして訓練が進むにつれて減少するように設定できます。

# Optimization
optimizer = tf.train.AdamOptimizer(learning_rate)
update_step = optimizer.apply_gradients(
    zip(clipped_gradients, params))

私達自身の実験では、標準的な SGD (tf.train.GradientDescentOptimizer) を低下する学習率スケジュールで使用し、これはより良いパフォーマンスを生成します。ベンチマーク を見てください。

 

ハンズオン – NMT モデルを訓練しましょう

ベトナム語から英語に翻訳する、私達の本当に最初の NMT モデルを訓練しましょう、コードのエントリ・ポイントは nmt.py です。

この課題のために TED talks の small-スケールのパラレルコーパス ((133K 訓練サンプル) を使用します。ここで使用されるデータの総ては https://nlp.stanford.edu/projects/nmt/ で見つかります。tst2012 を dev データセットとして、そして tst2013 をテスト・データセットとして使用します。

NMT を訓練するためのデータをダンロードする次のコマンドを実行します :\
nmt/scripts/download_iwslt15.sh /tmp/nmt_data

訓練を開始するためには次のコマンドを実行します :

mkdir /tmp/nmt_model
python -m nmt.nmt \
    --src=vi --tgt=en \
    --vocab_prefix=/tmp/nmt_data/vocab  \
    --train_prefix=/tmp/nmt_data/train \
    --dev_prefix=/tmp/nmt_data/tst2012  \
    --test_prefix=/tmp/nmt_data/tst2013 \
    --out_dir=/tmp/nmt_model \
    --num_train_steps=12000 \
    --steps_per_stats=100 \
    --num_layers=2 \
    --num_units=128 \
    --dropout=0.2 \
    --metrics=bleu

上のコマンドは 128-dim 隠れユニットと埋め込みを持つ 2-層 LSTM seq2seq モデルを 12 エポック訓練します。0.2 の dropout 値 (確率 0.8 を保つ) を使用します。もしエラーがないのであれば、訓練につれての perplexity の減少とともに 下に似たログを見るはずです。

# First evaluation, global step 0
  eval dev: perplexity 17193.66
  eval test: perplexity 17193.27
# Start epoch 0, step 0, lr 1, Tue Apr 25 23:17:41 2017
  sample train data:
    src_reverse:   Điều đó , dĩ nhiên , là câu chuyện trích ra từ học thuyết của Karl Marx .
    ref: That , of course , was the  distilled from the theories of Karl Marx .   
  epoch 0 step 100 lr 1 step-time 0.89s wps 5.78K ppl 1568.62 bleu 0.00
  epoch 0 step 200 lr 1 step-time 0.94s wps 5.91K ppl 524.11 bleu 0.00
  epoch 0 step 300 lr 1 step-time 0.96s wps 5.80K ppl 340.05 bleu 0.00
  epoch 0 step 400 lr 1 step-time 1.02s wps 6.06K ppl 277.61 bleu 0.00
  epoch 0 step 500 lr 1 step-time 0.95s wps 5.89K ppl 205.85 bleu 0.00

より詳細は train.py を見てください。

訓練の間にモデルの要約を見るために TensorBoard を開始することもできます :

tensorboard --port 22222 --logdir /tmp/nmt_model/

英語からベトナム語への反対の方向の訓練は単純に次の変更で成されます:\ –src=en –tgt=vi

 

推論 – 翻訳をどのように生成するか

NMT モデルを訓練している一方で (そしてひとたびモデルを訓練したのであれば)、以前に見ていないソース・センテンスが与えられたときに翻訳を得ることができます。このプロセスは推論と呼ばれます。訓練と推論 (テスティング) の間には明確な区別があります : 推論時には、ソース・センテンス, i.e., encoder_inputsへのアクセスを持つのみです。デコーディングを遂行するためには多くの方法があります。デコーディング・メソッドは greedy、サンプリングそしてビームサーチ・デコーディングを含みます。ここでは、greedy デコーディング・ストラテジーを議論します。

アイデアは単純でそれを Figure 3 で示します :

  1. encoder_state を得るために訓練の間と同じ方法でソース・センテンスを依然としてエンコードします、そしてこの encoder_state はデコーダを初期化するために使用されます。
  2. デコーディング (翻訳) プロセスはデコーダが開始シンボル “\<s>” (コードでは tgt_sos_id として参照) を受け取るとすぐに開始されます;
  3. デコーダ側の各 time ステップに対して、RNN 出力をロジットのセットとして扱います。最も尤もらしい単語、最大ロジット値に関連する id、を出力された単語として選択します (これは “greedy” の挙動です)。Figure 3 のサンプルでは、最初のデコーディング・ステップで単語 “moi” が最も高い翻訳確率を持ちます。それからこの単語を次の time ステップへの入力として供給します。
  4. このプロセスが出力シンボルとして文末マーカー “\</s>” (コードでは tgt_eos_id として参照) が生成されるまで続きます。

Figure 3. Greedy デコーディング – 訓練された NMT モデルが greedy サーチを使用してソース・センテンス “je suis étudiant” のためにどのように翻訳を生成するかのサンプルです。

ステップ 3 は推論を訓練とは異なるものにするものです。入力として常に正しいターゲット単語を供給する代わりに、推論はモデルにより推測される単語を使用します。ここに greedy デコーディングを成し遂げるコードがあります。それは訓練デコーダに非常に似ています。

# Helper
helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(
    embedding_decoder,
    tf.fill([batch_size], tgt_sos_id), tgt_eos_id)

# Decoder
decoder = tf.contrib.seq2seq.BasicDecoder(
    decoder_cell, helper, encoder_state,
    output_layer=projection_layer)
# Dynamic decoding
outputs, _ = tf.contrib.seq2seq.dynamic_decode(
    decoder, maximum_iterations=maximum_iterations)
translations = outputs.sample_id

ここで、TrainingHelper の代わりに GreedyEmbeddingHelper を使用します。ターゲット・シークエンス長を前もって知らないので、翻訳の長さを制限するために maximum_iterations を使用します。一つの経験則はソース・センテンス長の 2 倍までデコードすることです。

maximum_iterations = tf.round(tf.reduce_max(source_sequence_length) * 2)

モデルを訓練したら、今では推論ファイルを作成して幾つかのセンテンスを翻訳することができます :

cat > /tmp/my_infer_file.vi
# (copy and paste some sentences from /tmp/nmt_data/tst2013.vi)

python -m nmt.nmt \
    --out_dir=/tmp/nmt_model \
    --inference_input_file=/tmp/my_infer_file.vi \
    --inference_output_file=/tmp/nmt_model/output_infer

cat /tmp/nmt_model/output_infer # To view the inference as output

訓練チェックポイントが存在する限りはモデルがまだ訓練中でも上のコマンドはまた実行できることに注意してください。より詳細は inference.py を見てください。

 

3 章. Intermediate

 
最も基本的な seq2seq モデルを通り抜けました、更に進みましょう!先端技術のニューラル翻訳システムを構築するためには、更なる “秘密のソース” が必要です : attention メカニズム、これは最初に Bahdanau et al., 2015 により導入され、後に Luong et al., 2015 他により洗練されました。attention メカニズムの鍵となるアイデアは、翻訳するときに関係するソースコンテンツに “注意 (attention)” を払うことによって、ターゲットとソース間の直接的なショートカット接続を確立することです。attention メカニズムの素晴らしい副産物は (Figure 4 で示されるような) ソースとターゲット・センテンス間の可視化しやすいアラインメント行列です。

Figure 4. Attention 可視化 – ソースとターゲット・センテンス間のアラインメントの例です。画像は (Bahdanau et al., 2015) から取られています。

vanilla seq2seq モデルでは、デコーディング・プロセスが始まるときエンコードからの最後のソース状態をデコーダに渡すことを思い出してください。これは短いあるいは中ぐらいの長さのセンテンスについては上手く動作します ; けれども、長いセンテンスに対しては、単一の固定長の隠れ状態は情報ボトルネックになります。ソース RNN で計算された隠れ状態の総てを捨てる代わりに、attention メカニズムはデコーダにそれらを覗き見ることを可能にします (それらをソース情報の動的メモリとして扱います)。
そのようにすることで、attention メカニズムはより長いセンテンスの翻訳を改善します。最近では、attention メカニズムはデファクト・スタンダードで多くの他のタスクに成功的に適用されてきています (画像キャプション生成、音声認識、そしてテキスト要約)。

 

Attention メカニズムの背景

(Luong et al., 2015) で提案された attention メカニズムの実例を今記述します、これはオープンソースのツールキット含む幾つかの先端技術システムとこのチュートリアルの TF seq2seq API で使用されているものです。attention メカニズムの他の変形へのコネクションもまた提供します。

Figure 5. Attention メカニズム – (Luong et al., 2015) で説明されている attention ベース NMT システムのサンプルです。attention 計算の最初のステップに詳細にハイライトしています。明確化のため、埋め込みと投射層 (= projection layer) は Figure (2) で示していません。

Figure 5 で図示されるように、attention 計算は総てのデコーダ time ステップで発生します。それは次の段階から成ります :

  1. (Figure 4 内で可視化される) attention 重みを導出するために現在のターゲット隠れ状態を総てのソース状態と比較します。
  2. attention 重みに基づいてソース状態の重み付けられた平均としてコンテキスト・ベクトルを計算します。
  3. 最終的な attention ベクトルを生成するためにコンテキスト・ベクトルを現在のターゲット隠れ状態と結合します。
  4. attention ベクトルは次の time ステップへの入力として供給されます (input feeding)。最初の 3 つのステップは下の等式によって要約できます :

 
ここで、関数 score はターゲット隠れ状態 $h_t$ をソース隠れ状態 $\overline{h}_s$ の各々と比較するために使用されて、結果は attention 重み (ソース位置に渡る分布) を生成するために正規化されます。scoring 関数の様々な選択があります ; ポピュラーな scoreing 関数は Eq. (4) で与えられる multiplicative and additive 形式を含みます。ひとたび計算されれば、attention ベクトルは softmax ロジットと損失を導出するために使用されます。これは vanilla seq2seq モデルのトップ層におけるターゲット隠れ状態に類似しています。関数 f はまた他の形式を取ることもできます。

attention メカニズムの様々な実装は attention_wrapper.py で見つけられます。

 

attention メカニズムでは何が重要でしょう?

上の等式でヒントが与えられているように、多くの異なる attention 変種があります。これらの変種は scoring 関数と attention 関数の形式、そして (Bahdanau et al., 2015) で元々提案されているように scoring 関数内で代わりに前の状態が使用されるか否かに依拠します。実証的に、私達は特定の選択だけが重要であることを見出しました。1 番目に、attention の基本的な形式、i.e. ターゲットとソースの直接コネクションが存在している必要があります。2 番目に、(Luong et al., 2015) で説明されているように過去の attention 決定についてネットワークに伝えるために attention ベクトルを次の timestep に供給することは重要です。最後に、scoring 関数の選択はしばしば異なるパフォーマンスの結果になります。詳細は (後述の) ベンチマーク結果のセクションを見てください。

 

Attention ラッパー API

AttentionWrapper の私達の実装では、(Weston et al., 2015) からメモリ・ネットワークのワークの幾つかの用語を借りています。読み書き可能なメモリを持つ代わりに、このチュートリアルで提案される attention メカニズムは read-only メモリです。特に、ソース隠れ状態 (あるいはそれらの変換されたバージョン、i.e. $W\overline{h}_s$ in Luong’s scoring スタイルまたは $W_2\overline{h}_s$ in Bahdanau’s scoring スタイル) のセットは “メモリ” として参照されます。各 time ステップで、メモリのどの部分を読むかを決定するために現在のターゲット隠れ状態を “query (問合せ)” として使用します。通常は、query は個々のメモリスロットに対応するキーと比較される必要があります。attention メカニズムの上の提示では、ソース隠れ状態 (あるいはそれらの変換されたバージョン、$W_1h_t$ i.e. in Bahdanau’s scoring スタイル) のセットを “キー” として偶々使用しています。他の attention 形式を導出するためにこのメモリ・ネットワーク用語によりインスパイアされるかもしれません!

attention ラッパーのおかげで、vanilla seq2seq コードを attention で拡張することは自明です。この部分はファイル attention_model.py に該当します。

最初に、attention メカニズム, e.g., from (Luong et al., 2015) を定義する必要があります :

# attention_states: [batch_size, max_time, num_units]
attention_states = tf.transpose(encoder_outputs, [1, 0, 2])

# Create an attention mechanism
attention_mechanism = tf.contrib.seq2seq.LuongAttention(
    num_units, attention_states,
    memory_sequence_length=source_sequence_length)

前のエンコーダのセクションでは、encoder_outputs はトップ層における総てのソース隠れ状態のセットで [max_time, batch_size, num_units] の shape を持ちます (何故ならば効率のために time_major を True に設定して dynamic_rnn を使用するからです)。attention メカニズムのためには、渡される “メモリ” が batch major であることを確かなものにする必要があります、そして attention_states を transpose する必要があります。 (non-padding 位置だけに渡り) attention 重みが適切に正規化されることを確かなものにするために source_sequence_length を attention メカニズムに渡します。attention メカニズムを定義しましたので、デコーディング・セルをラップするために AttentionWrapper を使用します :

decoder_cell = tf.contrib.seq2seq.AttentionWrapper(
    decoder_cell, attention_mechanism,
    attention_layer_size=num_units)

コードの残りはデコーダのセクション内のものと殆ど同じです!

 

ハンズオン – attention ベースの NMT モデルを構築する

attention を有効にするには、訓練の間 attention フラグとして luong, scaled_luong, bahdanau または normed_bahdanau の一つを使用する必要があります。フラグはどの attention メカニズムを使用するかを指定します。更に、attention モデルのための新しい辞書を作成する必要がありますので、前に訓練した基本 NMT モデルは再利用しません。

訓練を開始するためには次のコマンドを実行します :

mkdir /tmp/nmt_attention_model

python -m nmt.nmt \
    --attention=scaled_luong \
    --src=vi --tgt=en \
    --vocab_prefix=/tmp/nmt_data/vocab  \
    --train_prefix=/tmp/nmt_data/train \
    --dev_prefix=/tmp/nmt_data/tst2012  \
    --test_prefix=/tmp/nmt_data/tst2013 \
    --out_dir=/tmp/nmt_attention_model \
    --num_train_steps=12000 \
    --steps_per_stats=100 \
    --num_layers=2 \
    --num_units=128 \
    --dropout=0.2 \
    --metrics=bleu

訓練後、推論のための新しい out_dir を伴う同じ推論コマンドを利用できます :

python -m nmt.nmt \
    --out_dir=/tmp/nmt_attention_model \
    --inference_input_file=/tmp/my_infer_file.vi \
    --inference_output_file=/tmp/nmt_attention_model/output_infer

 

4 章. ティップス & トリック

 

訓練、評価、そして推論グラフを構築する

TensorFlow で機械学習モデルを構築するとき、3 つの分離したグラフを構築することがしばしば最善です :

  • 訓練グラフ、これは :
    • ファイル/外部入力のセットから入力データをバッチ処理し、バケットに入れ、そしておそらくはサブサンプリングします。
    • forward と backprop ops を含みます。
    • optimizer を構築して、訓練 op を追加します。
  • 評価グラフ、これは :
    • ファイル/外部入力のセットから入力データをバッチ処理し、バケットに入れます。
    • 訓練 forward ops と訓練では使用されない追加の評価 ops を含みます。
  • 推論グラフ、これは :
    • 入力データのバッチ処理はしないかもしれません。
    • 入力データのサブサンプリングやバケット処理は行ないません。
    • プレースホルダーから入力データを読みます (データは feed_dict を通して、あるいは C++ TensorFlow serving バイナリからデータはグラフに直接供給できます)。
    • モデル forward ops のサブセットと、おそらくは session.run 呼び出し間の状態をストアするため追加の特別な入出力を含みます。

分離したグラフの構築は幾つかの利点があります :

  • 推論グラフは通常は他の 2 つと非常に異なりますので、それを別に構築するのは意味があります。
  • 評価グラフはより単純になります、何故ならばそれはもはや追加の逆伝播 ops の総てを持たないからです。
  • データ供給は各グラフに対して別々に実装可能です。
  • 変数 reuse は遥かにより単純です。例えば、評価グラフにおいて reuse=True を持つ変数スコープを再オープンする必要はありません、それは単に訓練モデルはこれらの変数を既に作成しているからです。従って reuse= 引数を至るところに撒き散らすことなしに同じコードが再利用できます。
  • 分散訓練では、別々のワーカーに訓練、評価、そして推論を遂行させることはよくあります。これらはそれら自身のグラフを構築する必要がいずれにせよあります。従ってシステムをこの方法で構築することは分散訓練のために貴方を準備させることになります。

複雑さの主な源は単一のマシン設定で 3 つのグラフに渡り変数をどのように共有するかとなるでしょう。これは各グラフのために別々のセッションを使用することで解決されます。訓練セッションは定期的にチェックポイントをセーブし、そして評価セッションと推論セッションはチェックポイントからパラメータをレストアします。下のサンプルは 2 つのアプローチ間の主な違いを示します。

Before: 単一グラフ内の 3 つのモデルと単一セッションの共有

with tf.variable_scope('root'):
  train_inputs = tf.placeholder()
  train_op, loss = BuildTrainModel(train_inputs)
  initializer = tf.global_variables_initializer()

with tf.variable_scope('root', reuse=True):
  eval_inputs = tf.placeholder()
  eval_loss = BuildEvalModel(eval_inputs)

with tf.variable_scope('root', reuse=True):
  infer_inputs = tf.placeholder()
  inference_output = BuildInferenceModel(infer_inputs)

sess = tf.Session()

sess.run(initializer)

for i in itertools.count():
  train_input_data = ...
  sess.run([loss, train_op], feed_dict={train_inputs: train_input_data})

  if i % EVAL_STEPS == 0:
    while data_to_eval:
      eval_input_data = ...
      sess.run([eval_loss], feed_dict={eval_inputs: eval_input_data})

  if i % INFER_STEPS == 0:
    sess.run(inference_output, feed_dict={infer_inputs: infer_input_data})

After: 3 つのグラフ内の 3 つのモデル、3 つのセッションが同じ変数を共有する

train_graph = tf.Graph()
eval_graph = tf.Graph()
infer_graph = tf.Graph()

with train_graph.as_default():
  train_iterator = ...
  train_model = BuildTrainModel(train_iterator)
  initializer = tf.global_variables_initializer()

with eval_graph.as_default():
  eval_iterator = ...
  eval_model = BuildEvalModel(eval_iterator)

with infer_graph.as_default():
  infer_iterator, infer_inputs = ...
  infer_model = BuildInferenceModel(infer_iterator)

checkpoints_path = "/tmp/model/checkpoints"

train_sess = tf.Session(graph=train_graph)
eval_sess = tf.Session(graph=eval_graph)
infer_sess = tf.Session(graph=infer_graph)

train_sess.run(initializer)
train_sess.run(train_iterator.initializer)

for i in itertools.count():

  train_model.train(train_sess)

  if i % EVAL_STEPS == 0:
    checkpoint_path = train_model.saver.save(train_sess, checkpoints_path, global_step=i)
    eval_model.saver.restore(eval_sess, checkpoint_path)
    eval_sess.run(eval_iterator.initializer)
    while data_to_eval:
      eval_model.eval(eval_sess)

  if i % INFER_STEPS == 0:
    checkpoint_path = train_model.saver.save(train_sess, checkpoints_path, global_step=i)
    infer_model.saver.restore(infer_sess, checkpoint_path)
    infer_sess.run(infer_iterator.initializer, feed_dict={infer_inputs: infer_input_data})
    while data_to_infer:
      infer_model.infer(infer_sess)

後者のアプローチは分散バージョンに変換されるための「準備」ができていることに注意してください。

新しいアプローチの一つの他の違いは各 session.run 呼び出しでデータを供給するために feed_dicts を使用する (そして関連してバッチ処理、バケット処理、そしてデータの操作を遂行する) 代わりに、stateful iterator オブジェクトを使用します。これらの iterator は入力パイプラインを単一マシンと分散設定の両者において遥かにより簡単にします。次のセクションで (TensorFlow 1.2 で導入された) 新しい入力データパイプラインをカバーします。

 

データ入力パイプライン

TensorFlow 1.2 の前には、ユーザは TensorFlow 訓練と評価パイプラインにデータを供給するために 2 つのオプションを持ちました :

  1. 各訓練 session.run 呼び出しで feed_dict を通してデータを直接供給する。
  2. tf.train (e.g. tf.train.batch) と tf.contrib.train の queueing メカニズムを使用する。
  3. tf.contrib.learn or tf.contrib.slim のような高位フレームワークからのヘルパーを使用する (これは事実上 #2 を使用しています)。

最初のアプローチは TensorFlow に精通していないか、Python でのみ行える変わった入力変更を行なう (i.e. 彼ら自身のミニバッチ queueing) 必要があるユーザのためにより容易です。2 番目と 3 番目のアプローチはより標準的ですが少し柔軟性に欠けます : それらは複数の python スレッドを開始することも要求します (queue runners)。更に、誤って使用される場合、キューはデッドロックか不明瞭なエラーメッセージに繋がります。それにもかかわらず、キューは feed_dict を使用するよりも著しくより効率的で単一マシンと分散訓練の両者に対して標準的です。

TensorFlow 1.2 からは、データを TensorFlow モデルに読み込むために利用可能な新しいシステムがあります : tf.data モジュールで見つかる、データセット iterator です。データ iterator は柔軟で、それについて考えて操作することが容易で、TensorFlow C++ ランタイムを利用して効率性とマルチスレッドを提供します。

dataset はバッチデータ Tensor、ファイル名、または複数のファイル名を含む Tensor から作成できます。幾つかのサンプルです :

# Training dataset consists of multiple files.
train_dataset = tf.data.TextLineDataset(train_files)

# Evaluation dataset uses a single file, but we may
# point to a different file for each evaluation round.
eval_file = tf.placeholder(tf.string, shape=())
eval_dataset = tf.data.TextLineDataset(eval_file)

# For inference, feed input data to the dataset directly via feed_dict.
infer_batch = tf.placeholder(tf.string, shape=(num_infer_examples,))
infer_dataset = tf.data.Dataset.from_tensor_slices(infer_batch)

総ての dataset は入力処理を通して同様に扱えます。これはデータの読み込みとクリーンアップ、(訓練と評価の場合) バケット処理、フィルタリング、そしてバッチ処理を含みます。

各センテンスを単語文字列のベクトルに変換するため、例えばですが、dataset map 変換を使用します :

dataset = dataset.map(lambda string: tf.string_split([string]).values)

それから各センテンス・ベクトルをベクトルとその動的長さを含むタプルに切り替えることができます :

dataset = dataset.map(lambda words: (words, tf.size(words))

最後に、各センテンス上で語彙検索を遂行することができます。検索テーブル・オブジェクト・テーブルが与えられる場合、この map は最初のタプル要素を文字列ベクトルから整数ベクトルに変換します。

dataset = dataset.map(lambda words, size: (table.lookup(words), size))

2 つのデータセットの結合もまた簡単です。もし 2 つのファイルが互いの line-by-line 変換を含みそして各々一つがそれ自身のデータセットに読み込まれる場合、zipped lines のタプルを含む新しい dataset が以下を通して作成されます :

source_target_dataset = tf.data.Dataset.zip((source_dataset, target_dataset))

可変長のセンテンスのバッチ処理もストレートです。次の変換は source_target_dataset から batch_size 要素をバッチ処理し、各バッチでソースとターゲット・ベクトルをそれぞれ最も長いソースとターゲット・ベクトルの長さにパディングします。

batched_dataset = source_target_dataset.padded_batch(
        batch_size,
        padded_shapes=((tf.TensorShape([None]),  # source vectors of unknown size
                        tf.TensorShape([])),     # size(source)
                       (tf.TensorShape([None]),  # target vectors of unknown size
                        tf.TensorShape([]))),    # size(target)
        padding_values=((src_eos_id,  # source vectors padded on the right with src_eos_id
                         0),          # size(source) -- unused
                        (tgt_eos_id,  # target vectors padded on the right with tgt_eos_id
                         0)))         # size(target) -- unused

この dataset から吐かれた値はネストされたタプルでその tensor はサイズ batch_size の一番左の次元を持ちます。その構造は :

  • iterator[0][0] はバッチ化されてパディングされたソース・センテンス行列を持ちます。
  • iterator[0][1] はバッチ化されたソース・サイズ・ベクトルを持ちます。
  • iterator[1][0] はバッチ化されてパディングされたターゲット・センテンス行列を持ちます。
  • iterator[1][1] はターゲット・サイズ・ベクトルを持ちます。

最後に、同じようなサイズのソース・センテンスを一緒にバッチ化するバケット処理もまた可能です。
より詳細と完全な実装のためにはファイル utils/iterator_utils.py を見てください。

Dataset からデータを読むにはコードの 3 行が必要です : iterator を作成し、その値を取得して、そしてそれを初期化します。

batched_iterator = batched_dataset.make_initializable_iterator()

((source, source_lengths), (target, target_lengths)) = batched_iterator.get_next()

# At initialization time.
session.run(batched_iterator.initializer, feed_dict={...})

iterator がひとたび初期化されれば、ソースまたはターゲット・テンソルにアクセスする総ての session.run 呼び出しは基礎的な dataset から次のミニバッチを要求するでしょう。

 

より良い NMT モデルのための他の詳細

Bidirectional RNN

エンコーダ側の双方向性は一般的に (より多くの層が使用されるので何某かの速度の低下を伴い) 良いパフォーマンスを与えます。ここでは単一の bidirectional 層を持つエンコーダをどのように構築するかの単純化されたサンプルを与えます :

# Construct forward and backward cells
forward_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)
backward_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)

bi_outputs, encoder_state = tf.nn.bidirectional_dynamic_rnn(
    forward_cell, backward_cell, encoder_emb_inp,
    sequence_length=source_sequence_length, time_major=True)
encoder_outputs = tf.concat(bi_outputs, -1)

変数 encoder_outputs と encoder_state はエンコーダのセクションと同じ方法で使用できます。マルチ bidirectional 層のためには、encoder_state を少し操作する必要があることに注意してください、より詳細のためには model.py, メソッド _build_bidirectional_rnn() を見てください。

 

ビーム・サーチ

greedy デコーディングが非常に合理的な翻訳品質を与える一方で、ビーム・サーチ・デコーダはパフォーマンスを更にブーストします。ビーム・サーチの考えは、翻訳時にトップ候補の小さなセット回りを保持することにより総ての可能な翻訳の検索空間をより良く探検することです。ビームのサイズはビーム幅 (= beam width) と呼ばれます : 例えばサイズ 10 の最小のビーム幅で一般的には十分です。更なる情報のためには、読者は Neubig, (2017) のセクション 7.2.3 を参照してください。ここにはビーム・サーチがどのように成されるかのサンプルがあります :

# Replicate encoder infos beam_width times
decoder_initial_state = tf.contrib.seq2seq.tile_batch(
    encoder_state, multiplier=hparams.beam_width)

# Define a beam-search decoder
decoder = tf.contrib.seq2seq.BeamSearchDecoder(
        cell=decoder_cell,
        embedding=embedding_decoder,
        start_tokens=start_tokens,
        end_token=end_token,
        initial_state=decoder_initial_state,
        beam_width=beam_width,
        output_layer=projection_layer,
        length_penalty_weight=0.0)

# Dynamic decoding
outputs, _ = tf.contrib.seq2seq.dynamic_decode(decoder, ...)

デコーダのセクションと同様に、同じ dynamic_decode() API 呼び出しが使用されることに注意してください。ひとたびデコードされれば、次のように翻訳にアクセスできます :

translations = outputs.predicted_ids
# Make sure translations shape is [batch_size, beam_width, time]
if self.time_major:
   translations = tf.transpose(translations, perm=[1, 2, 0])

更なる詳細のためには model.py, メソッド _build_decoder() を見てください。

 

ハイパーパラメータ

追加のパフォーマンスに繋がることができる幾つかのハイパーパラメータがあります。ここでは、私達獅子にの経験に基づいて幾つかをリストします [ Disclaimers: 他の人は私達が書いたことに同意しないかもしれません! ]。

Optimizer: Adam が “unfamiliar” アーキテクチャに対して合理的な結果に繋がることができる一方で、もし SGD で訓練可能であればスケジューリングを伴う SGD は一般的により良いパフォーマンスに繋がるでしょう。

Attention: Bahdanau-スタイル attention は、上手く動作するためにはしばしばエンコーダ側で双方向性を要求します ; その一方で Luong-スタイル attention は異なる設定のために上手く動作する傾向があります。このチュートリアル・コードのためには、Luong & Bahdanau スタイル attention の 2 つの改良種を使用することを推奨します : scaled_luong & normed bahdanau です。

 

マルチ-GPU 訓練

NMT モデルの訓練は数日間かかるかもしれません。異なる RNN 層を異なる GPU 上に置けば訓練スピードを改善できます。マルチ GPU 上に RNN 層を作成するサンプルがここにあります。

cells = []
for i in range(num_layers):
  cells.append(tf.contrib.rnn.DeviceWrapper(
      tf.contrib.rnn.LSTMCell(num_units),
      "/gpu:%d" % (num_layers % num_gpus)))
cell = tf.contrib.rnn.MultiRNNCell(cells)

更に、勾配計算を並列化するためには tf.gradients で colocate_gradients_with_ops オプションを有効にする必要があります。attention ベースの NMT モデルのスピード改良は GPU 数の増加につれて非常に小さいことに気がつくかもしれません。標準的な attention アーキテクチャの一つの大きな欠点は各 time ステップで attention に query するためにトップ (最終) 層の出力を使用することです。これは各デコーディング・ステップはその前のステップが完全に終了するのを待たなければならないことを意味します : それ故に、RNN 層をマルチ GPU 上に単純に置くことではデコーディング・プロセスを並列化できません。

GNMT attention アーキテクチャ は attention に query するためにボトム (最初の) 層の出力を使用してデコーダの計算を並列化します。従って、各デコーディング・ステップはその前の最初の層と attention 計算が終了すればすぐに開始できます。そのアーキテクチャを tf.contrib.rnn.MultiRNNCell のサブクラス、GNMTAttentionMultiCell で実装しました。GNMTAttentionMultiCell でどのようにデコーダ・セルを作成するかのサンプルがここにあります。

cells = []
for i in range(num_layers):
  cells.append(tf.contrib.rnn.DeviceWrapper(
      tf.contrib.rnn.LSTMCell(num_units),
      "/gpu:%d" % (num_layers % num_gpus)))
attention_cell = cells.pop(0)
attention_cell = tf.contrib.seq2seq.AttentionWrapper(
    attention_cell,
    attention_mechanism,
    attention_layer_size=None,  # don't add an additional dense layer.
    output_attention=False,)
cell = GNMTAttentionMultiCell(attention_cell, cells)

 

5 章. ベンチマーク

 
(訳注: ベンチマークについては原文の該当セクション: Benchmarks を参照してください。)

 
以上






TensorFlow 画像キャプション・モデル – コンピュータ・ビジョンと自然言語処理の融合

TensorFlow 画像キャプション・モデル – コンピュータ・ビジョンと自然言語処理の融合

今年 (2016) の 9 月に Google 社により、Microsoft COCO 2015 画像キャプショニング・チャレンジで優秀な成績をおさめた、画像キャプション・モデルが TensorFlow 実装のオープンソースとして公開されました。(キャプションは画像の簡単な説明文です。)

動作検証してみましたので、簡単にご報告します。自前の画像でも試してみましたが、確率つきで3つのキャプション候補が表示され、(人間が見ても)十分に理解可能な結果が得られました。

該当する Google Research Blog 記事は以下です :

Show and Tell: image captioning open sourced in TensorFlow | Google Research Blog

このブログによれば、2014 年に Google Brain チームの研究員が画像を正確に記述するキャプションの自動生成のための機械学習システムのトレーニングを実行し、システムの更なる開発によって Microsoft COCO 2015 画像キャプショニング・チャレンジ(= 正確な画像キャプション計算のベストなアルゴリズムを競うコンペティション)においてトップタイの成績をおさめるという成功に導かれたとのことです。

そして更に、その画像キャプション・モデル最新版を TensorFlow 実装のオープンソースモデルとして利用可能にするとのことで、このリリースはキャプショニング・システムのコンピュータ・ビジョン・コンポーネントへの本質的な改善を含み、オリジナル・システムに比較してより詳細で正確な記述を生成できるようです。

該当ペーパーは以下 :

Show and Tell: Lessons learned from the 2015 MSCOCO Image Captioning Challenge

abstact を簡単にまとめておくと以下のような内容で、最後にコンペと TensorFlow への言及もあります :

自動的に画像の内容を説明することは、コンピュータ・ビジョンと自然言語処理を結びつける、人口知能における基本的な問題です。このペーパーでは、深層 recurrent アーキテクチャをベースとする生成モデルを提示しますが、これはコンピュータ・ビジョンと機械翻訳における最近の進歩を結合し、画像を説明する自然言語の文群を生成するために利用可能なものです。モデルは与えられた訓練画像のターゲット説明文の尤度を最大化するようにトレーニングされます。幾つかのデータセット上での実験はモデルの正確さと画像説明から単独で学習した言語の流暢さを示します。モデルは質的にも量的にも非常に正確なことが多々あります。

それから少し違う角度からの関連記事と Google / University of Edinburgh 共著のペーパーです :

Google researchers teach AIs to see the important parts of images — and tell you about them

Discovering the physical parts of an articulated object class from multiple videos

TensorFlow “Show and Tell ” モデル

今回はオープンソース・モデルの単なる動作検証ですので特に難しい話しはありません。
例によって TensorBoard でグラフを確認しておきますと、Inception モデルと LSTM を組み合わせていますね :

im2txt_graph_all2_with_logo

トレーニング

以下は損失と Perplexity(= 言語モデルの評価尺度、複雑さを表します) のグラフです。
もう少しトレーニングした方が良さそうです :
im3txt_total_loss2-50k

im2txt_perplexity2-50k

検証結果

Microsoft COCO 2015 画像で試すのが一番精度が高いだろうと考えて、ランダムに試してみました。
最初はお皿に盛られた肉、ブロッコリそしてポテトです :

coco_val2014_000000224012

キャプション候補は以下の3つ。合っていますが、1つ目と3つ目の違いはピリオドの有無でしょうか :

Captions for image COCO_val2014_000000224012.jpg:
  0) a plate of food with meat and vegetables . (p=0.000409)
  1) a plate of food with meat and broccoli . (p=0.000255)
  2) a plate of food with meat and vegetables (p=0.000025)

次に海とサーファー。このキャプション・モデルの紹介記事では、(異なる画像ですが)良く利用される題材です :
Oregon Surfers

Captions for image COCO_val2014_000000224477.jpg:
  0) a man riding a wave on top of a surfboard . (p=0.034516)
  1) a person riding a wave on a surfboard . (p=0.007086)
  2) a man riding a wave on a surfboard in the ocean . (p=0.004863)

coco_val2014_000000224000

Captions for image COCO_val2014_000000224000.jpg:
  0) a herd of sheep grazing in a field . (p=0.000379)
  1) a herd of sheep walking down a road . (p=0.000219)
  2) a herd of sheep walking down a street . (p=0.000148)

coco_val2014_000000224051

  0) a man riding a bike down a street . (p=0.000337)
  1) a bicycle is parked on the side of the road . (p=0.000283)
  2) a bicycle is parked on the side of a road . (p=0.000143)

coco_val2014_000000224037

Captions for image COCO_val2014_000000224037.jpg:
  0) a group of boats docked in the water . (p=0.000443)
  1) a group of boats floating in the water . (p=0.000402)
  2) a group of boats docked in a harbor . (p=0.000140)

Imagenet の画像も幾つか試してみましたが、結果はあまり芳しくありませんでした。
おそらく、Imagenet の画像は粗すぎるのだと思います。
honda_jazz_7

  0) a white car parked in front of a car . (p=0.000060)
  1) a white car parked in front of a parking meter . (p=0.000031)
  2) a white car parked in front of a car (p=0.000019)

自前の画像でも幾つか試してみました :

classcat_cat

  0) a cat is sitting on the floor next to a laptop . (p=0.000014)
  1) a cat is sitting on the floor next to a computer . (p=0.000008)
  2) a cat is sitting on the floor next to a cat . (p=0.000007)

apple_persimmon

  0) a bunch of apples are sitting on a table (p=0.000065)
  1) a bunch of apples are sitting on a plate (p=0.000026)
  2) a bunch of apples are sitting on a table . (p=0.000024)
 

以上

TensorFlow RNN ( LSTM / GRU ) で NY ダウ株価予測

TensorFlow RNN ( LSTM / GRU ) で NY ダウ株価予測

基本モデルと実装

RNN (Recurrent Neural Network) は自然言語処理分野で最も成果をあげていますが、得意分野としては時系列解析もあげられます。定番ですが Yahoo Finance が Web で提供している株価情報をベースに RNN で株価予測をしてみました。

使用した RNN ネットワークのアーキテクチャは今回はシンプルなものとして深く積層せずに LSTM と GRU で個別に試しています。window サイズは取り敢えず 10 としましたが再考は必要です。実装はもちろん TensorFlow ですが、MAE (Mean Absolute Error, 平均絶対誤差) の算出には sklearn を使用しています。

提供される情報カラムは Daily で Open, High, Low, Close, Volume, Adj Close ですが、今回は Adj Close prices を使用しました。2000 年 1 月 1 日から 2016 年 10 月 31 日までのデータを取得しています。データの割り振りはトレーニングに 90 %、verification に 5 %、そして予測に 5 % を使用しました。つまり最後の約 10 ヶ月分を予測に回していることになります。(トレーニングの収束は高速です。)

予測誤差が小さい例

(1) アップル社

最初に予測が比較的きれいに当てはまった例を幾つかあげておきます。
まず、以下はアップル社の Adj Close の実値をそのままグラフにしたものです :
apple

そして以下は予測曲線を重ね合わせたものですが、誤差によるズレが殆どないのでこれだけでは分かりにくいので :
lstm_predict_apple

予測期間を zoom in してみた画像が以下です。
LSTM における MAE は 1.042638 ; GRU では 1.060532 でした :
lstm_predict_apple2

 
(2) SONY 社
lstm_predict_sony

MAE: 0.428332 :
lstm_predict_sony2

 
(3) Yahoo 社
lstm_predict_yahoo
MAE: 0.080035 :
lstm_predict_yahoo2

予測誤差が大きい例

予測誤差が小さい例だけを見ていると上手くいっているのか判断しづらいので、誤差が大きい例も見てみます。
直感的には stationarity の考慮が必要なものと思われます。

(1) Amazon 社

以下は、実数曲線に予測曲線を重ね合わせたものです :
lstm_predict_amazon

そして zoom in した画像です。MAE は 198.287123 です :
lstm_predict_amazon2

 

以上

TensorFlow の RNN/LSTM/GRU で消費者感情分析

TensorFlow の LSTM / GRU / bidirectional RNN で IMDb 消費者感情分析

IMDb & LSTM

消費者感情分析という用語が定着しているか分かりませんが、sentiment analysis を意訳したつもりです。IMDb は Internet Movie Database の略です。

LSTM による IMDb を利用した sentiment 分析は元々は Theano のチュートリアルで扱われたものですが :

 

TensorFlow で実装してみました。やることは単純で movie review の評価を positive か negative か二値分類するだけなのですが、LSTM だけでなく GRU と bidirectional RNN でも試してみました。

TensorFlow で実装/訓練

モデルは試行錯誤しましたが、最終的に次のようなモデルを利用しました。
以下は GRU の図ですが LSTM でも同様です :

imdb_model_gru2

それから bidirectional RNN :

imdb_model_bidirectional2

 

訓練は 40 epochs 行ないました。GRU の収束が速いですが LSTM の曲線の方が滑らかですね :

imdb_all_loss3

 

validation 精度は 85 % 前後になりましたが、bidirectional-RNN については 80% 強が上限でした :

imdb_all_acc_validation2

bidirectional-RNN の結果については optimizer やモデルをいじってもあまり変わりませんでしたので、より適合するテーマを見つけた方が良さそうです。

 

以上

正弦波 (sine wave) の RNN (LSTM) による予測の TensorFlow による実装

正弦波 (sine wave) の RNN (LSTM) による予測の TensorFlow による実装

定番ですが、sine wave の RNN による予測をやっていなかったので、例によって TensorFlow で簡単に実装してみました。普通の regression と異なるのは、履歴 sequence から next value を予測することです。

訓練サンプルデータは以下のように安直に生成。取りあえずノイズなしです :

x = np.sin(np.arange(0, 10*math.pi, 0.01))

seq = []
next_val = []

steps_of_history = 100
for i in range(0, len(x) - steps_of_history, 1):
    seq.append(x[i: i + steps_of_history])
    next_val.append(x[i + steps_of_history])

seq = np.reshape(seq, [-1, steps_of_history, 1])
next_val = np.reshape(next_val, [-1, 1])

trainX = np.array(seq)
trainY = np.array(next_val)

モデルは下左図の tensorboard の出力のように LSTM 層を3つ並べて FC (全結合)層を追加してみましたが、実際には simple な RNN 層 + FC 層でも十分だったようです。
下右図は損失グラフで、20 epochs も必要なし。

sine_lstm_graph

sine_lstm_loss

問題が単純過ぎたようですが、一応 matplotlib で描画してみました。
履歴 sequence の長さは 100 です :

sine_lstm2

 

以上

TensorFlow による RNN : Character-Level 言語モデルの実装

TensorFlow による Character-Level 言語モデル

RNN & Character-Level 言語モデル

RNN による言語モデルについても少し試しておきます。
以下は TensorFlow のチュートリアルでも参照されている、Stanford の Andrej Karpathy(現在は OpenAI らしい)による RNN の紹介記事ですが :

 
この中で Character-Level 言語モデルが紹介されています。

We’ll train RNN character-level language models. That is, we’ll give the RNN a huge chunk of text and ask it to model the probability distribution of the next character in the sequence given a sequence of previous characters. This will then allow us to generate new text one character at a time.

ついでに python による簡単な実装例も示されています :

TensorFlow による Character-Level 言語モデルの実装

そして TensorFlow で実装するのですが、実際には A. Karpathy の Torch による実装があるのでそちらを参考にしています。character-level 言語モデルで訓練/サンプリングするためのマルチ層の Recurrent Neural Network (RNN, LSTM そして GRU) を実装していて、具体的にはモデルは一つのテキストファイルを入力として、sequence の次の文字を predict することを学習する RNN を訓練します。
(試した限りでは LSTM よりも GRU の方が良い感じでした。)

題材としては取りあえず、Gutenberg から子供向けの古代エジプトについての歴史書を選んでみました。

で、訓練開始。最初のサンプリングはもちろん意味不明 :

z5L8JqA7ôPPêN:t,yUXaw!7.6àÔâ"ç:Uï7Büâœæàbôæ8XZ46:ï6Z*L MX1ïrâ4JÔôü5.GU8œ8Wê[5çtz.yuMfAt7GToüâPbL"9TzZEUXNmMzzqüæq4UzK*ïG7Dïb4ZZ]ôÔXŒïUâMïæfXWPÔ4'ês4êj6êbGU8MA°]ôêœWZUXWïôZFôJà4ôGYOüPA4âNA4ôPGçT*NZê(übob_KnUtw!Uz8üvfUG9 PT_âwé")kb:0"ôMôG:ÔUDâ.zKdTZoP(AXr?,ôUZ4SiXïU*f"s4WÔqü"564](_4àbUÏZzb4ôXp7_LW",HLkT(,8rxübïôUWbUæZpZrX7.TMWê_X,K(DASELGTü_JPTi5âzGb3PzUHvë44ôzfrMô8ræPtXÔUôbM,UjXê"êi4J3Xdüzlé4,P.WÔP-ôL"ÏU*ZêufrZut'z]"4ék?7âRMï6ô_D*oE4M5ï"o1wUHïP,tê:qqâ7

200 epochs。単語としては成立していますが、英語としてはいまいち…

"Herow? We may be proved the Greek lines for Mamessioh, the hands fall into complete determining
roubt what it was slow thee, under Tigolathy, engradus the month, 12, the trade had
regulication with a more inclier feet; which is consuit and involved,
   Whit heart vine upon the Hittite people of the god and in
mystery, and a pezit of a sixteen three more enjoyovies.

The east the government of "Harehæ). The march. The face
of the monarch remained below the two favous. He hadbendy that h

250 epochs。少し英語らしくなってきた?

In whohe,

           And two Nile Egypt, Psamatik had
the Levinites from their monument to land in different broadling the
blossom of the Egyptian conqueror.

   I came which determined, and had carried had
probably beauty and remove mid. The production of Assour, a
general towns reducted the King"--campte a point of each of his wife and brought
between the circumstances of his others, and to have been the dimensions
war, with difficues of Lakes, Naphuled, and hasing his bounth fine

Ablimn the bas a long god-gual-cylem in expedition, the missult
fronty of kings received afterwards amperiescent feet, and we must, must expenditues
that revolt at Karnak was barbarly one of Caired and Greek might be respected to
very remaining a period may be more him and to cut confine it, in his
worst, a deities of the western city of men cometthing of enarxer till shall be calamities
that here indeed in it thought taken pillarners, between their pas

500 epochs。このモデルではこのあたりが限界でしょうか :

 Egypt
possistration obtaining his limbs, lief force. The same monarch
were told as much invade brich emblec, and to make all the purpose of all period. The like took off the
artigious smaller commonly to ultime a hast to the good delights.
  Let stand who were modern Asiatic doing countries will but a
fewer hundred _shupps and Shumad Bœlusaa, Skirwaan; no one-who
had ever pieces is cut and left between the
Greek nine sides traverse, a certain pure, you adm; but
man had been remotle. Pent

The two hordes the first chariots landed history Esya-space of the
Nile in the crown of Egyptian terrible hand, in the jude timed Horuss to south herself. xpresidrages of
successure strengthen after a live death, and all their cities labour is a considerable
establishment of the podtis foreshonous, the Arab to Tash and in the imagine
food; and a residence Ramesses, came long, and forty numerous in 56°z,
in his kindlim, and after ordered the piece of the Nile, much to the sea, 

 which
make it scarcely skin a bettive grew lakes of the other." It is designated
his general. Antefaadich cemertions must advantage of the number, there were
models, or with the sovereignty of Sheshom and Shabak. The head
of considerable quiltly ashess fell into the other wind submitted by
maintain matiries in each year and Cyrus in the old temple battle--Egypt. Only further
recounces who had like agels out at an indecener and accomplishez, and
the first limbuted had yet reigned and carri

 
以上

LSTM ネットワークの理解 (翻訳/要約)

LSTM ネットワークの理解 (翻訳/要約)

* TensorFlow : コード解説 : RNN – リカレント・ニューラルネットワーク & LSTM で参照されている Understanding LSTM Networks の要約です。原文の図を参照しながらお読みください。

 

RNNリカレント・ニューラルネットワーク

人間は刻々とゼロから思考を始めるわけではありません。この文書を読む時でも、前の単語の理解を元に各単語を理解しており、全てを捨ててゼロから再度考え始めるわけではありません。人間の思考には持続性があります。伝統的な NN はこれができません。大きな欠点です。例えば、映画の全てのポイントでどのような種類のイベントが起きているか分類することを望むとして、伝統的な NN が映画の前のイベントについての情報を後のイベントに伝えるための方法が判然としません。

RNNリカレント・ニューラルネットワーク はこの問題を処理します。RNN はそれらの中にループを持つことで情報を持続することを可能にします。NN の塊 A がある入力 xt を見て値 ht を出力するとします。ループは情報がネットワークの一つのステップから次へ渡されることを可能にします。

これらのループは RNN を幾分神秘的にしていますが、普通の NN とそれほど違うわけではありません。RNN は個々が継承者にメッセージを渡す同じネットワークの複数のコピーとして考えられます。ループを展開して得られる、鎖状 (chain-like) の性質は RNN がシークエンスとリストに親密に関係することを示しています。これらはそのようなデータを使用するために自然なアーキテクチャです。

ここ2、3年で RNN を各種の問題に適用することについて大きな成功があります : 音声認識、言語モデリング、翻訳、画像キャプション …。RNN の成果についての議論は Andrej Karpathy のブログポスト – The Unreasonable Effectiveness of Recurrent Neural Networks に委ねますが、驚異的です。

これらの成功の本質は LSTM の使用にあります。これは RNN の特殊な種類ですが、多くのタスクに対して標準判よりも非常に上手く動作します。RNN 上の殆どの素晴らしい結果はこれによって達成されています。

 

Long-Term 依存の問題

RNN の主張の一つは、以前の情報を現在のタスクに結合できるということです。例えば、以前のビデオフレームの利用が現在のフレームの理解に情報を与えるというようにです。もし RNN がこれを行なうのであれば非常に有用ですが、本当にできるのでしょうか?それは場合によります。

時として現在のタスクを遂行するために最近の情報を見ることだけが必要となります。以前の単語をベースに次の単語を予測を試みる言語モデルを考えましょう。“the clouds are in the sky.” における最後の単語の予測を試みる場合、それ以上のコンテキスト情報は必要ありません – 次の単語が sky になることは明白です。

しかし更なるコンテキストが必要な場合もあります。テキスト “I grew up in France… I speak fluent French.” の最後の単語を予測する場合、 最近の情報が次の単語は言語名と提案します、しかしどの言語か狭めようとするならば、更に戻ったところから、フランス語のコンテキストが必要となります。関係情報とそれが必要なポイントのギャップが非常に大きくなる可能性があります。

残念ながらギャップが大きくなれば、RNN は情報を結合して学習することができなくなります。理論的には RNN はそのような “long-term 依存” を処理できますが、実践では RNN はそれらを学習できるようには見えません。

LSTM はこの問題がありません。

 

LSTM ネットワーク

Long Short Term Memory ネットワーク – 通常は LSTM と呼称 – は RNN の特別な種類で long-term 依存を学習することができます。LSTM は Hochreiter & Schmidhuber (1997) で導入されました。

LSTM は long-term 依存問題を回避するためにデザインされ、情報を長時間記憶しておくことがデフォルトの挙動です。

全ての RNN は NN の反復モジュールのチェイン形式を持ちます。標準的な RNN では、この反復モジュールは tanh 単層のような非常に単純な構造を持ちます。LSTM はまたチェイン状の構造を持ちますが、反復モジュールは異なる構造を持ちます。単一のニューラルネットワーク層を持つ代わりに、4つの NN 層があり、特殊な方法で相互作用します。

 

LSTM の背後の核心の考え

LSTM への鍵はセル状態 (cell state) で、セル状態はベルトコンベアのようなものです。幾つかの小さな線形作用だけを伴い鎖全体をストレートに走り抜けます。情報は変更されないままそれに沿って流れることは簡単です。

LSTM は情報を削除したりセル状態に追加したりする能力を持ち、ゲート と呼ばれる構造により注意深く統制されます。ゲートは選択的に情報を通り抜けさせる手段です。これらはシグモイド NN 層と単点の乗算演算から成ります。シグモイド層は 0 と1の間の数を出力し各コンポーネントのどれだけを通り抜けさせるかを記述します。0 の値は「何も通さない」を、1 の値は「全てを通す」を意味しています。

LSTM はセル状態を保護して制御するためにこれらゲートを3つ持ちます、

 

Step-by-Step LSTM ウォークスルー

LSTM の最初のステップはセル状態からどの情報を捨てるかを決めることです。この決定は「忘却ゲート層」と呼ばれるシグモイド層により行なわれます。それは \(h_{t-1}\) と \(x_t\) を見て、セル状態 \(C_{t-1}\) の各数値のために 0 と 1 の間を数値を出力します。1 は「完全に保持する」を表す一方で、0 は「完全にこれを除去する」を表します。

全ての前の単語をもとに次の単語の予測を試みる言語モデルの例に戻りましょう。そのような問題では、セル状態が現在の主語の性別を含むことにより正しい代名詞が使用できることになります。新しい主語を見る時には、古い主語の性別は忘れたいです。

次のステップはどのような新しい情報をセル状態にストアするかを決定します。これは2つの部分を持ちます。まず、「入力ゲート層」と呼ばれるシグモイド層がどの値を更新するか決めます。次に、状態に追加される可能性のある、新しい候補値のベクタ \(\tilde{C}_t\) を tanh 層が作成します。次のステップは、これら2つを結合して状態への更新を作成します。

言語モデルの例では、忘れようとしている古いものを置き換えるために、新しい主語の性別をセル状態に追加することを望んでいます。

そして今、古いセル状態 \(C_{t-1}\) を新しいセル状態 \(C_t\) に更新する時です。前のステップが既に何をするか決めているで、実際にそれを行なうだけです。

古い状態に \(f_t\) を乗算して、前に忘れると決めたことを忘れます。それから \(i_t*\tilde{C}_t\) を加算します。これは各状態値を更新すると決めた数によりスケールされた、新しい候補値です。

言語モデルの場合には、ここでは前のステップで決定したように、実際に古い主語の性別についての情報をドロップして新しい情報を追加します。

最後に、何を出力するかを決める必要があります。この出力はセル状態の基づきますが、フィルターされたバージョンになります。まず、シグモイド層を実行します、これはセル状態のどのパートを出力するかを決めます。それから、セル状態に(-1 と 1 の間に値を収めるために) \(\tanh\) を通してからシグモイド・ゲートの出力を乗算します、結果として決めたパートだけを出力します。

言語モデルの例で言えば、単に主語を見てからは、(次に来るのが動詞である場合には)動詞に関連した情報を出力したいかもしれません。例えば、主語が単数か複数かを出力するかもしれません、その結果、(次に来るとして)動詞がどのような形に活用すべきかを知ります。

 

Long Short Term Memory の変形 (Variants)

ここまで説明してきたものは通常の LSTM です。しかし全ての LSTM が上と同じというわけではありません。事実、LSTM を含む殆ど全ての論文は少し違ったバージョンを使用しています。違いは大きなものではありませんが、幾つかについては言及する価値があります。

Gers & Schmidhuber (2000) で紹介された、一つの有名な LSTM の変形は 「のぞき穴接続 (peephole connections)」を追加します。これはゲート層にセル状態を見させることを意味しています。

のぞき穴を全てのゲートに追加する場合もありますが、多くの論文では幾つかにのぞき穴を与え、他には与えません。

他のバリエーションはカップリングされた忘却と入力ゲートを使用します。何を忘れて新しい情報を何に追加すべきかを別々に決定する代わりに、それらの決定を一緒に行ないます。何かをその場所に入力する時にだけ忘れます。古い何かを忘れる時にだけ新しい値を状態に入力します。

少しばかりよりドラマチックな LSTM バリエーションは、Cho, et al. (2014) で紹介された GRU – Gated Recurrent Unit です。これは忘却と入力ゲートを単一の「更新ゲート (update gate)」に結合します。それはまたセル状態と隠れ状態をマージして、幾つかの他の変更を行ないます。結果としてのモデルは標準的な LSTM モデルよりも単純で、段々と一般的なものになってきています。

これらは最も有名な LSTM バリエーションの2、3に過ぎません。他にも Yao, et al. (2015) による Depth Gated RNNs のようなものがたくさんあります。また Koutnik, et al. (2014) による Clockwork RNN のような、long-term 依存にタックルするための完全に異なる幾つかのアプローチもあります。

これらのバリエーションのどれがベストでしょうか?違いは重要でしょうか?Greff, et al. (2015) は人気のあるバリエーションの良い比較を行ない、すべて大体同じあることを見出しています。 Jozefowicz, et al. (2015) は 10,000 以上の RNN アーキテクチャをテストし、あるタスクでは幾つかは LSTM よりも良く動作したとしています。

 

結論

LSTM は RNN で達成できることの中で大きなステップでした。他の大きなステップはあるのか? と疑問を抱くのは自然なことです。研究者の共通の意見は : 「はい!次のステップがありそれは『attention』です!」アイデアは、RNN の全てのステップに、情報のある大きなコレクションから、情報を見るために pick させることです。例えば、画像を説明するキャプションを作成するために RNN を使用している場合、それは出力する全ての単語のために、画像の一部を見るために pick するかもしれません。実際に、Xu, et al. (2015) は正確にこれを行なっています – attention を探検することを望むならば楽しい開始点かもしれません。attention を使用した本当にエキサイティングな結果が幾つかあり、角を曲がればより沢山ありそうです…

attention だけが RNN 研究における唯一のエキサイティングな話題ではありません。
例えば、Grid LSTMs by Kalchbrenner, et al. (2015) による Grid LSTM は特に有望に思われます。生成モデルにおいて RNN を使用したワーク – 例えば Gregor, et al. (2015), Chung, et al. (2015), あるいは Bayer & Osendorfer (2015) – はまた非常に興味深く見えます。

ここ2、3年は RNN にとってエキサイティングな時間でしたが、これからの2、3年もそうあるでしょう。

 

以上

TensorFlow : RNN – リカレント・ニューラルネットワーク & LSTM (コード解説)

TensorFlow : コード解説 : RNNリカレント・ニューラルネットワーク & LSTM

* TensorFlow : Tutorials : リカレント・ニューラルネットワーク に、数式排除/コード重視の方針で詳細な解説を加筆したものです。

 

LSTM ネットワーク

RNNリカレント・ニューラルネットワーク、特に LSTM への入門としては Understanding LSTM Networks を参照のこと。要点は LSTM ネットワークの理解 (翻訳/要約) に簡単にまとめておきました。

 

言語モデリング

このチュートリアルではリカレント・ニューラルネットワークをどのように訓練するかを示します。言語モデリングの挑戦的なタスクです。ゴールは文に確率を割り当てる確率モデルを最適化することです。前の単語の履歴を与えられたテキストで次の単語を予測することで達成します。

言語モデリングは、音声認識、機械翻訳あるいは画像キャプションのような多くの興味ある問題へのキーで将来有望です – こちら をご覧ください。

このチュートリアルでは、PTB データセット上で非常に良い結果を達成してる、Zaremba et al., 2014 (pdf) からの結果を再現します。

Penn Tree Bank (PTB) を使用します。これは訓練するに小さくて比較的速く、これらのモデルのクオリティを計測するための人気のあるベンチマークです。

 

チュートリアル・ファイル

このチュートリアルではコード: models/rnn/ptb から次のファイルを参照します :

ファイル

目的
ptb_word_lm.py

PTB データセット上で言語モデルを訓練するコード。
reader.py

データセットを読むコード。
 

データのダウンロードと準備

このチュートリアルに必要なデータは Tomas Mikolov の web ページからの
PTB データセットの data/ ディレクトリにあります :
    http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz

データセットは既に前処理されていて、文の最後のマーカーと珍しい単語のための特殊なシンボル (<unk>) を含む、全体で 10000 の異なる単語を含みます。

ニューラルネットワークが処理しやすいようにこれら全部を reader.py で一意の整数識別子に変換します。

ptb_word_lm.py#main は、最初に reader.py#ptb_raw_data を呼び出してデータセットを取得します。

def main(unused_args):
  if not FLAGS.data_path:
    raise ValueError("Must set --data_path to PTB data directory")
  raw_data = reader.ptb_raw_data(FLAGS.data_path)
  train_data, valid_data, test_data, _ = raw_data
def ptb_raw_data(data_path=None):
  """
  PTB 生データをデータディレクトリ "data_path" からロードする。
  PTB テキストファイルを読み込み、文字列を整数値 id に変換、
  入力のミニ・バッチを遂行します。
  The PTB dataset comes from Tomas Mikolov's webpage:
  http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz
  Args:
    data_path: string path to the directory where simple-examples.tgz has
      been extracted.
  Returns:
    tuple (train_data, valid_data, test_data, vocabulary)
    where each of the data objects can be passed to PTBIterator.
  """
  train_path = os.path.join(data_path, "ptb.train.txt")
  valid_path = os.path.join(data_path, "ptb.valid.txt")
  test_path = os.path.join(data_path, "ptb.test.txt")
  word_to_id = _build_vocab(train_path)
  train_data = _file_to_word_ids(train_path, word_to_id)
  valid_data = _file_to_word_ids(valid_path, word_to_id)
  test_data = _file_to_word_ids(test_path, word_to_id)
  vocabulary = len(word_to_id)
  return train_data, valid_data, test_data, vocabulary
 

モデル

LSTM

モデルの核は LSTM セルから構成されます。このセルは、一度に一つの単語を処理し文の(可能性のある)継続性の確率を計算します。ネットワークのメモリ状態は 0 のベクタで初期化されて各単語を読んだ後で更新されます。また、計算上の理由で、サイズ batch_size のミニ・バッチでデータを処理します。

基本的な擬似コードは次のようなものです :

lstm = rnn_cell.BasicLSTMCell(lstm_size)
# LSTM メモリの初期状態。
state = tf.zeros([batch_size, lstm.state_size])

loss = 0.0
for current_batch_of_words in words_in_dataset:
    # 状態の値は単語の各バッチ処理の後で更新されます。
    output, state = lstm(current_batch_of_words, state)

    # LSTM 出力は次の単語予測をするために使用できます。
    logits = tf.matmul(output, softmax_w) + softmax_b
    probabilities = tf.nn.softmax(logits)
    loss += loss_function(probabilities, target_words)

PTBModel コンストラクタ

モデルの実装は PTBModel クラスで、コンストラクタで構築されます。

class PTBModel(object):

  def __init__(self, is_training, config):
    self.batch_size = batch_size = config.batch_size
    self.num_steps = num_steps = config.num_steps
    size = config.hidden_size
    vocab_size = config.vocab_size

    # プレースホルダー
    self._input_data = tf.placeholder(tf.int32, [batch_size, num_steps])
    self._targets = tf.placeholder(tf.int32, [batch_size, num_steps])

    #  1 で初期化された忘却ゲート・バイアスによれば幾分良い結果が得られますが、
    # モデルのハイパーパラメータは論文での報告とは異なるものである必要があります。
    lstm_cell = rnn_cell.BasicLSTMCell(size, forget_bias=0.0)
    if is_training and config.keep_prob < 1:
      lstm_cell = rnn_cell.DropoutWrapper(
          lstm_cell, output_keep_prob=config.keep_prob)
    cell = rnn_cell.MultiRNNCell([lstm_cell] * config.num_layers)

    self._initial_state = cell.zero_state(batch_size, tf.float32)

    with tf.device("/cpu:0"):
      embedding = tf.get_variable("embedding", [vocab_size, size])
      inputs = tf.nn.embedding_lookup(embedding, self._input_data)
    if is_training and config.keep_prob < 1:
      inputs = tf.nn.dropout(inputs, config.keep_prob)

    # tensorflow.models.rnn.rnn.py の rnn() の簡易版。
    # チュートリアル目的のみで unrolled LSTM を構築します。
    # 一般的には、rnn.py の rnn() か state_saving_rnn() を使用します。
    #
    # The alternative version of the code below is:
    #
    # from tensorflow.models.rnn import rnn
    # inputs = [tf.squeeze(input_, [1])
    #           for input_ in tf.split(1, num_steps, inputs)]
    # outputs, states = rnn.rnn(cell, inputs, initial_state=self._initial_state)
    outputs = []
    states = []
    state = self._initial_state
    with tf.variable_scope("RNN"):
      for time_step in range(num_steps):
        if time_step > 0: tf.get_variable_scope().reuse_variables()
        (cell_output, state) = cell(inputs[:, time_step, :], state)
        outputs.append(cell_output)
        states.append(state)

    output = tf.reshape(tf.concat(1, outputs), [-1, size])
    softmax_w = tf.get_variable("softmax_w", [size, vocab_size])
    softmax_b = tf.get_variable("softmax_b", [vocab_size])
    logits = tf.matmul(output, softmax_w) + softmax_b
    loss = seq2seq.sequence_loss_by_example([logits],
                                            [tf.reshape(self._targets, [-1])],
                                            [tf.ones([batch_size * num_steps])],
                                            vocab_size)
    self._cost = cost = tf.reduce_sum(loss) / batch_size
    self._final_state = states[-1]

    if not is_training:
      return

    self._lr = tf.Variable(0.0, trainable=False)
    tvars = tf.trainable_variables()
    grads, _ = tf.clip_by_global_norm(tf.gradients(cost, tvars),
                                      config.max_grad_norm)
    optimizer = tf.train.GradientDescentOptimizer(self.lr)
    self._train_op = optimizer.apply_gradients(zip(grads, tvars))

Truncated バックプロパゲーション

学習プロセスを扱いやすくするために、バックプロパゲーションのための勾配を展開されたステップの固定数 (num_steps) で打ち切る (truncate) ことは一般的に実践されます。これは、一度に num_steps の長さの入力を供給して各反復後に後方へパスを行なうことにより実装は容易です。

truncated バックプロパゲーションのためのグラフ作成のためのコードの簡易版です :

# 与えられた反復における入力のためのプレースホルダー。
words = tf.placeholder(tf.int32, [batch_size, num_steps])

lstm = rnn_cell.BasicLSTMCell(lstm_size)
# LSTM メモリの初期状態。
initial_state = state = tf.zeros([batch_size, lstm.state_size])

for i in range(len(num_steps)):
    # 状態値は単語のバッチ毎処理後に更新されます。
    output, state = lstm(words[:, i], state)

    # 残りのコード。
    # ...

final_state = state

そしてこれがデータセット全体に渡る反復の実装です。

# 単語の各バッチ後の LSTM の状態を保持する numpy 配列。
numpy_state = initial_state.eval()
total_loss = 0.0
for current_batch_of_words in words_in_dataset:
    numpy_state, current_loss = session.run([final_state, loss],
        # 以前の反復からの LSTM 状態の初期化。
        feed_dict={initial_state: numpy_state, words: current_batch_of_words})
    total_loss += current_loss

入力

単語 ID は LSTM に供給される前に密な表現(単語のベクタ表現チュートリアル 参照)に埋め込まれます。これはモデルに、特定の単語についての知識を効率的に表現することを可能にします。コードを書くのもまた簡単です:

# embedding_matrix は形状 [vocabulary_size, embedding size] のテンソルです。
word_embeddings = tf.nn.embedding_lookup(embedding_matrix, word_ids)

埋め込み行列はランダムに初期化されてモデルはデータを検索するだけで単語の意味を識別できるようにするために学習します。

損失関数

ターゲット単語の確率の対数の負の平均を最小化したいです :

\[ \text{loss} = -\frac{1}{N}\sum_{i=1}^{N} \ln p_{\text{target}_i} \]

関数 sequence_loss_by_example が利用可能です。

    output = tf.reshape(tf.concat(1, outputs), [-1, size])
    softmax_w = tf.get_variable("softmax_w", [size, vocab_size])
    softmax_b = tf.get_variable("softmax_b", [vocab_size])
    logits = tf.matmul(output, softmax_w) + softmax_b
    loss = seq2seq.sequence_loss_by_example([logits],
                                            [tf.reshape(self._targets, [-1])],
                                            [tf.ones([batch_size * num_steps])],
                                            vocab_size)
    self._cost = cost = tf.reduce_sum(loss) / batch_size

論文で報告されている典型的な尺度は平均単語毎 (per-word) perplexity (単に perplexity とも)で、これは以下に等しいです

\[e^{-\frac{1}{N}\sum_{i=1}^{N} \ln p_{\text{target}_i}} = e^{\text{loss}} \]

そして私たちは訓練プロセスを通じてこの値をモニタします。

複数 LSTM のスタック

モデルにより表現力を与えるために、LSTM の複数層を追加できます。
第1層の出力は第2層の入力、等々になります。

MultiRNNCell と呼ばれるクラスを持ち、これは実装をシームレスにします :

lstm = rnn_cell.BasicLSTMCell(lstm_size)
stacked_lstm = rnn_cell.MultiRNNCell([lstm] * number_of_layers)

initial_state = state = stacked_lstm.zero_state(batch_size, tf.float32)
for i in range(len(num_steps)):
    # 単語の各バッチの処理後、状態値は更新されます。
    output, state = stacked_lstm(words[:, i], state)

    # 残りのコード。
    # ...

final_state = state

実際のコード :

    lstm_cell = rnn_cell.BasicLSTMCell(size, forget_bias=0.0)
    if is_training and config.keep_prob < 1:
      lstm_cell = rnn_cell.DropoutWrapper(
          lstm_cell, output_keep_prob=config.keep_prob)
    cell = rnn_cell.MultiRNNCell([lstm_cell] * config.num_layers)
 

コードを実行する

pip パッケージのインストールを行ない、tensorflow git レポジトリをクローンし、そして git ツリーのルートにいることを仮定しています。(もしソースからビルドしているならば、bazel でターゲット tensorflow/models/rnn/ptb:ptb_word_lm をビルドします。)

次に: cd tensorflow/models/rnn/ptb python ptb_word_lm --data_path=/tmp/simple-examples/data/ --model small

チュートリアル・コードには3つのサポートされたモデル構成があります: "small", "medium" そして "large" です。これらの違いは LSTM のサイズと訓練に使われるハイパーパラメータのセットにあります。

より大きなモデルは、より良い結果を得られるでしょう。small モデルはテストセット上で 120 以下の perplexity に達することができ、large モデルは 80 以下です、訓練に数時間かかるかもしれませんが。

 

What Next?

モデルを良くするための私たちが言及していない幾つかのトリックあります、これは次を含みます:

  • 学習率を減少するスケジュール
  • LSTM 層間のドロップアウト

コードを研究してモデルを更に改善するように修正しましょう。

 

以上

AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com