Python
機械学習
TensorFlow

TensorFlowチュートリアル - シーケンス変換モデル(翻訳)

More than 1 year has passed since last update.

TensorFlow のチュートリアル(Sequence-to-Sequence Models)
https://www.tensorflow.org/versions/master/tutorials/seq2seq/index.html#sequence-to-sequence-models
の翻訳です。
翻訳の誤りなどあればご指摘お待ちしております。


リカレント・ニューラルネットワークは、RNN のチュートリアルで説明したように(読んでいない場合は、進む前に読んでください)、言語のモデル化を学習することができます。これは興味深い問題を提起します:何らかの入力で発生する言葉を条件に、意味のある応答を生成することができるか?たとえば、英語からフランス語に翻訳するためにニューラルネットワークを訓練することができるか?答えはイエスであることが判明しました。

このチュートリアルでは、このようなシステムを構築し、訓練する方法を端から端まで説明します。pip パッケージによりインストールされ、tensorflow の git リポジトリをクローンし、git ツリーのルートにいるとします。

翻訳プログラムを実行することから始めます:

cd tensorflow/models/rnn/translate
python translate.py --data_dir [your_data_directory]

このプログラムは、WMT'15 ウェブサイトから英仏翻訳データをダウンロードし、訓練のための準備をし、訓練します。約20GBのディスク容量を必要とします。しばらくはダウンロードして準備しますので(詳細は後述)、開始して、このチュートリアルを読んでいる間、実行したままにしておくと良いでしょう。

このチュートリアルでは、models/rnn の、以下のファイルを参照します。

ファイル 内容
seq2seq.py シーケンス変換モデルを構築するためのライブラリ
translate/seq2seq_model.py ニューラル翻訳シーケンス変換モデル
translate/data_utils.py 翻訳データを準備するためのヘルパー関数
translate/translate.py 翻訳モデルを訓練し、実行するバイナリ

シーケンス変換の基本

基本シーケンス変換モデルは、Cho et al., 2014 で紹介されたように、2つのリカレント・ニューラルネットワーク(RNN)で構成されます:入力を処理するエンコーダと、出力を生成するデコーダです。この基本的アーキテクチャを、以下に示します。

図

上記の図の各ボックスは、RNN のセル、最も一般的には GRU セルや LSTM セル(それらの説明については、RNN のチュートリアルを参照)を表現します。エンコーダとデコーダは、重みを共有することもでき、より一般的には、パラメータの別個の集合を使用することもできます。たとえば、翻訳に関する Sutskever et al., 2014 のように、多層セルはシーケンス変換モデルでもうまく利用されています。

上に示した基本モデルでは、デコーダに渡される唯一のものとして、すべての入力は固定サイズの状態ベクトルに符号化されなければなりません。Bahdanu et al., 2014 では、デコーダがより直接的に入力にアクセスすることを許可する、注目(attention)メカニズムが紹介されています。注目メカニズムの詳細には触れません(論文を参照してください)。それはデコーダがすべてのデコード・ステップで入力をこっそりみることを許す、とだけ述べておきます。LSTM セルおよびデコーダの注目メカニズムを備えた多層シーケンス変換ネットワークは、次のようになります。

図

TensorFlow の seq2seq ライブラリ

上で見られるように、多様なシーケンス変換モデルがあります。これらのモデルには、それぞれ異なる RNN セルが使用されますが、それらすべては、エンコーダ入力およびデコーダ入力を受け付けます。このことは TensorFlow の seq2seq ライブラリ(models/rnn/seq2seq.py)内のインタフェースを促します。基本 RNN エンコーダ・デコーダ・シーケンス変換モデルは次のように動作します。

outputs, states = basic_rnn_seq2seq(encoder_inputs, decoder_inputs, cell)

上記の呼び出しで、encoder_inputs はエンコーダへの入力を表すテンソルのリスト、すなわち上述した最初の図内の文字 A、B、C に対応するものです。同様に、decoder_inputs はデコーダへの入力を表すテンソルであり、最初の図では、GO、W、X、Y、Z です。

cell 引数は models.rnn.rnn_cell.RNNCell クラスのインスタンスで、モデル内部で使用されるセルを定めます。GRUCell や LSTMCell などの既存のセルを使用することもでき、独自のものを書くこともできます。また、rnn_cell は、セル入力や出力にドロップアウトを追加したり他の変換を行う目的で、多層セルを構成するための、ラッパーを提供します。例としては、RNN のチュートリアルを参照してください。

basic_rnn_seq2seq の呼び出しは2つの引数を返します:出力と状態です。どちらも decoder_inputs と同じ長さのテンソルのリストです。当然のことながら、出力は各時間ステップにおけるデコーダの出力に対応し、上述した最初の図では W、X、Y、Z、EOS です。返された状態は、すべての時間ステップでのデコーダの内部状態を表します。

多くの場合、シーケンス変換モデルでは時刻 t におけるデコーダの出力がフィードバックされ、時刻 t+1 でのデコーダの入力になります。テスト時にシーケンスを復号する際、このようにシーケンスが構築されます。一方、訓練中は一般に、デコーダが前のステップで間違えた場合でも、すべての時間ステップでデコーダに正解を入力します。seq2seq.py の関数は feed_previous 引数を使用して、両方のモードをサポートしています。例として、埋め込み RNN モデルの次の使い方を分析してみましょう。

outputs, states = embedding_rnn_seq2seq(
    encoder_inputs, decoder_inputs, cell,
    num_encoder_symbols, num_decoder_symbols,
    output_projection=None, feed_previous=False)

embedding_rnn_seq2seq モデルでは、すべての入力(encoder_inputs 、 decoder_inputs の双方)は離散値を表す整数テンソルです。これらは密な表現に埋め込まれますが(埋め込みの詳細については、ベクトル表現のチュートリアルを参照してください)、これらの埋め込みを構築するためには、出現する離散シンボルの最大数を指定する必要があります:エンコーダ側の num_encoder_symbols と、デコーダ側の num_decoder_symbols です。

上記の呼び出しでは、feed_previous を False に設定しました。これは、デコーダが、提供された decoder_inputs テンソルを使用することを意味します。 feed_previous を True に設定した場合、デコーダは decoder_inputs の最初の要素のみを使用します。このリストの他のすべてのテンソルは無視され、代わりにエンコーダの直前の出力が使用されます。これは我々の翻訳モデルでは復号変換に使用されます。モデルを自身の誤りに対してよりロバストにするために、Bengio et al., 2015 のように、訓練中に使用することもできます。

上記で使用されているもう一つの重要な引数が output_projection です。これを指定しない場合、埋め込みモデルの出力は、生成された各シンボルのロジットを表す、バッチサイズ × num_decoder_symbols の形状のテンソルになります。大規模出力語彙でモデルを訓練する場合、すなわち num_decoder_symbols が大きい場合、これらの大きなテンソルを保存することは現実的ではありません。代わりに、小さな出力テンソルを返すことをお勧めします。それらは、後で、output_projection を用いて大規模出力テンソルに射影されます。これにより、 Jean et. al., 2015 に記載されているように、我々の seq2seq モデルをサンプリング・ソフトマックス損失と共に使用することができます。

seq2seq.py には basic_rnn_seq2seq と embedding_rnn_seq2seq 以外にも、いくつかのシーケンス変換モデルがあるので、それらを見てください。それらはすべて同様のインターフェイスを持っているので、詳細には説明しません。下記の翻訳モデルでは embedding_attention_seq2seq を使用します。

ニューラル翻訳モデル

シーケンス変換モデルの中心部は models/rnn/seq2seq.py の関数で構成されていますが、models/rnn/translate/seq2seq_model.py の翻訳モデルで使用されている、言及する価値があるトリックがいくつかあります。

サンプリング・ソフトマックスと出力射影

前述した理由のために大規模出力語彙を処理するには、サンプリング・ソフトマックスを使用します。出力射影からデコードするため、それを追い続ける必要があります。サンプリング・ソフトマックス損失と出力予測は共に seq2seq_model.py の次のコードで構築されます。

  if num_samples > 0 and num_samples < self.target_vocab_size:
    w = tf.get_variable("proj_w", [size, self.target_vocab_size])
    w_t = tf.transpose(w)
    b = tf.get_variable("proj_b", [self.target_vocab_size])
    output_projection = (w, b)

    def sampled_loss(inputs, labels):
      labels = tf.reshape(labels, [-1, 1])
      return tf.nn.sampled_softmax_loss(w_t, b, inputs, labels, num_samples,
                                        self.target_vocab_size)

まず、サンプル数(デフォルトでは512)がターゲット語彙サイズよりも小さい場合にのみサンプリング・ソフトマックスを構築するということに注意してください。語彙が512よりも小さければ、単に標準のソフトマックス損失を使用するのが良いでしょう。

続いて、コードに見られるように、出力射影を構築します。これは、重み行列とバイアス・ベクトルのペアです。これを使用した場合、RNN セルは、バッチサイズ × target_vocab_size ではなく、バッチサイズ × size の形状のベクトルを返します。seq2seq_model.py の 124-126 行目で行われるように、ロジットを取り出すために、重み行列を乗算し、バイアスを加える必要があります。

if output_projection is not None:
  self.outputs[b] = [tf.matmul(output, output_projection[0]) +
                     output_projection[1] for ...]

バケツ化(bucketing)とパディング

サンプリング・ソフトマックスに加えて、我々の翻訳モデルは、異なる長さの文を効率的に処理する方法であるバケツ化を利用します。最初に問題を明確にしましょう。英語をフランス語に翻訳するとき、可変長 L1 の英語文の入力と、可変長L2のフランス語文の出力があります。英語文が encoder_inputs として渡され、(GO 記号で始まる)decoder_inputs としてフランス語文になるので、原理上、英語とフランス語の文の長さのすべてのペア (L1, L2+1) の seq2seq モデルを作成する必要があります。これは、多くの非常に類似したサブグラフからなる巨大なグラフになります。一方、すべての文を特別な PAD 記号によりパディングすることもできます。その場合、パディングされた唯一つの長さについての seq2seq モデルのみ必要になります。しかし、文が短い場合、不必要な多数の PAD シンボルのエンコードとデコードが必要になるため、このモデルは非効率になります。

すべての長さのペアのためのグラフを構築する方法と、単一長さにパディングする方法との折衷案として、いくつかのバケツを使用し、各文をそれを上回るバケツの長さにするためのパディングを行います。translate.py では、次のデフォルトのバケツを使用します。

buckets = [(5, 10), (10, 15), (20, 25), (40, 50)]

これは、入力が3トークンの英語文であり、対応する出力が6トークンのフランス語文である場合、最初のバケツに入れられ、エンコーダの入力は長さ5に、デコーダの入力は長さ10にパディングされることを意味します。8トークンの英語文があり、対応するフランス語文が18トークンの場合は、(10, 15) バケツに収まらないので、(20, 25) バケツが使用されます、すなわち英語文は20に、そしてフランス語文は25にパディングされます。

デコーダ入力を構築する際に、入力データに特別な GO 記号を付加することを思い出してください。これは、seq2seq_model.py の get_batch() 関数で行います。この関数はまた、入力英文を反転させます。入力の反転は、Sutskever et al., 2014 で、ニューラル翻訳モデルの結果を改善することが示されました。すべてをまとめるために、入力として「I go.」という文があり、["I", "go", "."] とトークン化され、出力として「Je vais.」という文があり、["Je", "vais", "."] とトークン化されているとしましょう。この場合、[PAD PAD "." "go" "I"] で表現されるエンコーダ入力と、[GO "Je" "vais" "." EOS PAD PAD PAD PAD PAD] で表現されるデコーダ入力として、(5, 10) バケツに入れられます。

実行してみましょう

上記のモデルを訓練するために、大規模な英仏コーパスが必要になります。WMT'15 ウェブサイトから、訓練のために 10^9 語の仏英コーパスを使用し、同じサイトから開発セットとして2013年のニュース・テストを使用します。コマンドを実行すると、これらのデータセットが data_dir にダウンロードされ、訓練が開始され、train_dir にチェックポイントが保存されます。

python translate.py
  --data_dir [your_data_directory] --train_dir [checkpoints_directory]
  --en_vocab_size=40000 --fr_vocab_size=40000

訓練コーパスを準備するには、約18GBのディスク容量と数時間程度がかかります。準備では、データセットを解凍し、語彙ファイルを data_dir に作成し、その後、コーパスをトークン化して、整数のIDに変換します。語彙サイズを決定するパラメータに注意してください。上記の例では、4万語の最も一般的なもの以外のすべての単語を、未知語を表す UNK トークンに変換します。語彙サイズを変更した場合、バイナリは再びコーパスをトークンとIDに再変換します。

データが準備された後、訓練が開始されます。translate のデフォルト・パラメータは大きな値を使用するように設定されています。長い時間をかけて訓練した大規模なモデルでは、良好な結果が得られます。しかし、それには時間がかかりすぎたり、GPU がメモリ不足になる場合がありますので、次の例のように、小さいモデルを訓練するように要求することもできます。

python translate.py
  --data_dir [your_data_directory] --train_dir [checkpoints_directory]
  --size=256 --num_layers=2 --steps_per_checkpoint=50

上記のコマンドは、2層(デフォルトは3)、各層ごとに256ユニット(デフォルトは1024)のモデルを訓練し、50ステップ(デフォルトは200)ごとに、チェックポイントを保存します。モデルが GPU のメモリに収まるようにすることができます。モデルを GPU のメモリに収まるような大きさにするために、これらのパラメータを変更することができます。

訓練中、各 steps_per_checkpoint ステップにおいて、バイナリは、最近のステップからの統計情報を出力します。デフォルト・パラメータ(3層でサイズ1024)では、最初のメッセージは、以下のようになります。

global step 200 learning rate 0.5000 step-time 1.39 perplexity 1720.62
  eval: bucket 0 perplexity 184.97
  eval: bucket 1 perplexity 248.81
  eval: bucket 2 perplexity 341.64
  eval: bucket 3 perplexity 469.04
global step 400 learning rate 0.5000 step-time 1.38 perplexity 379.89
  eval: bucket 0 perplexity 151.32
  eval: bucket 1 perplexity 190.36
  eval: bucket 2 perplexity 227.46
  eval: bucket 3 perplexity 238.66

各ステップに1.4秒以下かかること、および、各バケットの訓練セット上のパープレキシティと開発セット上のパープレキシティがわかります。約3万ステップの後、短い文(バケツ0および1)のパープレキシティが一桁台に入ることを確認してください。訓練コーパスには約22Mの文が含まれているため、1エポック(訓練データを一巡する期間)は64のバッチサイズで約34万ステップです。この時点で、--decode オプションを使用することにより、モデルを使用して英語文をフランス語に翻訳することができます。

python translate.py --decode
  --data_dir [your_data_directory] --train_dir [checkpoints_directory]

Reading model parameters from /tmp/translate.ckpt-340000
>  Who is the president of the United States?
 Qui est le président des États-Unis ?

次は?

上記の例では、独自の英仏翻訳機を構築する方法を、端から端まで示しました。それを走らせて、モデルがどのように作動するかについて見てください。それは合理的な品質ですが、デフォルトのパラメータは、最良の翻訳モデルを与えることはありません。ここでは、いくつか改善することができる点があります。

まず第一に、我々は data_utils で非常に原始的なトークナイザ、basic_tokenizer 関数を使用しました。より良いトークナイザを WMT'15 ウェブサイトで見つけることができます。そのトークナイザおよびより大規模な語彙を使用すれば、翻訳が改善されるはずです。

また、翻訳モデルのデフォルトのパラメータをチューニングしていません。学習率や減衰を変更したり、モデルの重みを別の方法で初期化することを試みることができます。また、seq2seq_model.py で、デフォルトの GradientDescentOptimizer を AdagradOptimizer などのより高度なものに変更することができます。これらのことを試してみて、結果がどのように改善されるかを確認してください!

最後に、上記のモデルは、翻訳だけでなく、任意のシーケンス変換タスクに使用することができます。たとえば構文解析ツリーを生成するために、シーケンスをツリーに変換したい場合でも、Vinyals & Kaiser et al., 2015 で示されたように、同様のモデルにより最先端の結果が得られます。独自の翻訳機だけでなく、また、パーサ、チャットボット、または思いつく任意のプログラムを構築することができます。 実験してください!