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

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