ホーム » OkumuraMasashi の投稿

作者アーカイブ: OkumuraMasashi

Keras : バックエンド

Keras : バックエンド(翻訳/要約)

* 本ページは、Keras 本家サイトの Keras : Backends の簡単な要約です。

 

“バックエンド” とは何か?

Keras はモデル-レベルのライブラリで、深層学習モデルの開発のための高レベルなビルディング・ブロックを提供します。それ自体はテンソル積、畳み込み等々の低レベルな操作は処理しません。代わりに、それを行なう、Keras の “バックエンド・エンジン” としてサービスを提供する、特化され良く最適化されたテンソル操作ライブラリに頼ります。一つの単一のテンソルライブラリを選択して Keras の実装をそのライブラリに結びつける代わりに、Keras は問題をモジュール化の方法で扱い、幾つかの異なるバックエンド・エンジンが Keras にシームレスにプラグインできます。

現時点では、Keras は利用可能な2つのバックエンド実装を持ちます: Theano バックエンドと TensorFlow バックエンドです。

  • Theano は Université de Montréal の LISA/MILA Lab により開発された、オープンソースのシンボリック・テンソル操作フレームワークです。
  • TensorFlow は Google, Inc により開発された、オープンソースのシンボリック・テンソル操作フレームワークです。
 

一つのバックエンドから他の一つに切り替える

Keras を少なくとも一度実行したのであれば、Keras configuration ファイルを以下で見つけるでしょう :

~/.keras/keras.json

もしないのであれば、それを作成できます。

それはおそらくはこのようなものです :

{"epsilon": 1e-07, "floatx": "float32", "backend": "theano"}

単純に backend フィールドを “theano” か “tensorflow” に変更すると、次に任意の Keras コードを実行する時に Keras は新しい configurationi を使用します。

環境変数 KERAS_BACKEND を定義することも可能で、これは config ファイルで定義されたものをオーバーライドします :

KERAS_BACKEND=tensorflow python -c "from keras import backend; print backend._BACKEND"
Using TensorFlow backend.
tensorflow
 

抽象 Keras バックエンドを使用して新しいコードを書く

もし貴方が書く Keras モジュールを Theano と TensorFlow の両者に互換であるようにしないのであれば、抽象 Keras バックエンド API を通してそれらを書かなければなりません。ここでそのイントロを示します。

バックエンド・モジュールを次で import できます:

from keras import backend as K

下のコードは入力プレースホルダーをインスタンス化します。これは tf.placeholder() または T.matrix(), T.tensor3(), 等と等価です。(訳注: 前者は tensorflow プレースホルダー、後者は Theano のシンボリック変数宣言。)

input = K.placeholder(shape=(2, 4, 5))
# also works:
input = K.placeholder(shape=(None, 4, 5))
# also works:
input = K.placeholder(ndim=3)

下のコードは共有変数をインスタンス化しています。tf.variable() または theano.shared() と等価です。

val = np.random.random((3, 4, 5))
var = K.variable(value=val)

# all-zeros variable:
var = K.zeros(shape=(3, 4, 5))
# all-ones:
var = K.ones(shape=(3, 4, 5))

必要な大部分のテンソル演算は TensorFlow か Theano の中にいるように行なわれます :

a = b + c * K.abs(d)
c = K.dot(a, K.transpose(b))
a = K.sum(b, axis=2)
a = K.softmax(b)
a = concatenate([b, c], axis=-1)
# etc...

より詳細は、keras/backend/theano_backend.py と keras/backend/tensorflow_backend.py のコード参照。

 

以上

Keras : FAQ

Keras : FAQ(翻訳/要約)

* 本ページは、Keras 本家サイトの Keras : FAQ の簡単な要約です。

 

Keras FAQ: Frequently Asked Keras Questions

GPU 上で Keras をどのように実行しますか?

Tensorflow バックエンド上で実行しているのであれば、利用可能な GPU が検知されれば貴方のコードは自動的に GPU で動作します。Theano バックエンド上で実行しているのであれば、次のメソッドの一つが使用できます :

メソッド 1: Theano フラグを使用する。

THEANO_FLAGS=device=gpu,floatX=float32 python my_keras_script.py

名前 ‘gpu’ はデバイスの識別子に依存して変更する必要があるかもしれません (e.g. gpu0, gpu1, etc)。

メソッド 2: .theanorc を設定する: Instructions

メソッド 3: コードの冒頭で theano.config.device, theano.config.floatX を手動設定します :

import theano
theano.config.device = 'gpu'
theano.config.floatX = 'float32'

Keras モデルをどのように保存できますか?

Keras モデルを保存するために pickle または cPickle を使うことは推奨されません。

モデルのアーキテクチャのみを、その重みではなく、保存する必要がある場合は、次のようにできます :

# JSON として保存
json_string = model.to_json()

# YAML として保存
yaml_string = model.to_yaml()

そしてこのデータからフレッシュなモデルを構築できます :

# JSON からモデル再構築:
from keras.models import model_from_json
model = model_from_json(json_string)

# YAML からモデルを再構築
model = model_from_yaml(yaml_string)

モデルの重みを保存する必要がある場合は、下のコードで HDF5 でそのようにできます。

HDF5 と Python ライブラリ h5py を最初にインストールする必要があることに注意してください、これらは Keras にバンドルされて入ってはきません。

model.save_weights('my_model_weights.h5')

モデルをインスタンス化するためのコードがあるとすれば、保存した重みを同じアーキテクチャのモデルにロードすることができます :

model.load_weights('my_model_weights.h5')

これはモデルを保存してシリアライズされたデータのみから再構築する方法へとつながります :

json_string = model.to_json()
open('my_model_architecture.json', 'w').write(json_string)
model.save_weights('my_model_weights.h5')

# elsewhere...
model = model_from_json(open('my_model_architecture.json').read())
model.load_weights('my_model_weights.h5')

何故、訓練中の損失はテスト中の損失よりもかなり高いのでしょうか?

Keras モデルは2つのモードがあります: 訓練 (training) とテスト (testing) です。Dropout と L1/L2 重み正則化のような、正則化メカニズムはテスト時には無効にされます。更に、訓練中の損失は訓練データの各バッチ上の損失の平均です。貴方のモデルは時間をかえて変わっていきますから、エポックの最初のバッチ群上の損失は一般に最後のバッチ群のものよりも高くなります。その一方で、エポックに対するテスト中のロスはエポックの最後におけるモデルを使用して計算されますので、より低い損失になります。

どのようにして中間層 (intermediate layer) の出力を視覚化できますか?

ある入力が与えられた時のある層の出力を返す Keras 関数を構築できます、例えば :

from keras import backend as K

# Sequential モデルで
get_3rd_layer_output = K.function([model.layers[0].input],
                                  [model.layers[3].get_output(train=False)])
layer_output = get_3rd_layer_output([X])[0]

# Graph モデルで
get_conv_layer_output = K.function([model.inputs[i].input for i in model.input_order],
                                   [model.nodes['conv'].get_output(train=False)])
conv_output = get_conv_layer_output([input_data_dict[i] for i in model.input_order])[0]

同様に、Theano と TensorFlow 関数を直接構築することもできます。

メモリに fit in しないデータセットで Keras がどのように使えますか?

model.train_on_batch(X, y) そして model.test_on_batch(X, y) を使用してバッチ訓練を行なうことができます。モデル 文書参照。
See the models documentation.

代わりに、訓練データのバッチを生成する generator を書いてメソッド model.fit_generator(data_generator, samples_per_epoch, nb_epoch) を使うこともできます。

実際に CIFAR 10 サンプル でバッチ訓練を見ることができます。

検証ロスがそれ以上減少しない時、どのように訓練を中断できますか?

EarlyStopping callback が使用できます :

from keras.callbacks import EarlyStopping
early_stopping = EarlyStopping(monitor='val_loss', patience=2)
model.fit(X, y, validation_split=0.2, callbacks=[early_stopping])

callbacks 文書 で詳細が見つかります。

どのように検証分割が計算されるのでしょう?

model.fit の validation_split 引数を例えば 0.1 に設定すれば、使用される検証データはデータの最後の 10% になります。それを 0.25 に設定すれば、データの最後の 25 % になります等々。

データは訓練中にシャッフルされますか?

はい、model.fit の shuffle 引数が True (これはデフォルト)に設定されていれば、訓練データは各エポックでランダムにシャッフルされます。

検証データはシャッフルされません。

各エポックでどのように訓練 / 検証損失 / 精度を記録できますか?

model.fit メソッドは History callback を返し、これは連続的な損失 / 精度のリストを含む history 属性を持ちます。

hist = model.fit(X, y, validation_split=0.2)
print(hist.history)

ステートフル RNN をどのように使用できますか?

RNN をステートフルにするということは、各バッチのサンプルのための状態が次のバッチのサンプルのための初期状態として再利用されることを意味しています。

ステートフル RNN を使用する時は、それゆえに次を仮定します :

  • 全てのバッチは同じ数のサンプルを持ちます。
  • もし X1 と X2 がサンプルの連続するバッチであるならば、全ての i について、X2[i] は X1[i] に対する follow-up シーケンスです。

RNN でステートフルネスを使用するためには、次が必要です :

  • batch_input_shape 引数をモデルの最初の層に渡すことによって、明示的に使用するバッチサイズを指定します、それは整数のタプルで、例えば、時間ステップ毎に 16 の特徴で 10 時間ステップのシーケンスの 32-サンプル バッチのためには、(32, 10, 16) 。
  • RNN 層(群)で stateful=True を設定します。

積算された状態をリセットするには :

  • モデルの全ての層の状態をリセットするために model.reset_states() を使用します。
  • 特定のステートフル RNN 層の状態をリセットするためには layer.reset_states() を使用します。

サンプル:

X  # これは shape (32, 21, 16) の入力データ
# 長さ 10 のシーケンスの中でそれをモデルに供給します。

model = Sequential()
model.add(LSTM(32, batch_input_shape=(32, 10, 16), stateful=True))
model.add(Dense(16, activation='softmax'))

model.compile(optimizer='rmsprop', loss='categorical_crossentropy')

# 最初の 10 が与えられた時の 11 番目の時間ステップを予測するネットワークを訓練します。
model.train_on_batch(X[:, :10, :], np.reshape(X[:, 10, :], (32, 16)))

# the state of the network has changed. We can feed the follow-up sequences:
model.train_on_batch(X[:, 10:20, :], np.reshape(X[:, 20, :], (32, 16)))

# LSTM 層の状態をリセットしましょう :
model.reset_states()

# この場合にそれを行なう他の方法
model.layers[0].reset_states()

メソッド predict, fit, train_on_batch, predict_classes, etc. は全てモデルのステートフル層の状態を更新することに注意してください。これはステートフル訓練だけでなく、ステートフル予測も行なうことを可能にします。

どのように Keras を cite(引用/参照) すべきでしょう?

Please cite Keras in your publications if it helps your research. Here is an example BibTeX entry:

@misc{chollet2015keras,
author = {Chollet, François},
title = {Keras},
year = {2015},
publisher = {GitHub},
journal = {GitHub repository},
howpublished = {\url{https://github.com/fchollet/keras}}
}

 

以上

Keras : オプティマイザ

Keras : オプティマイザ(翻訳/要約)

* 本ページは、Keras 本家サイトの Keras : Optimizers の簡単な要約です。

オプティマイザの使用方法

optimizer は Keras モデルのコンパイリングのために必要な2つの引数の一つです :

model = Sequential()
model.add(Dense(64, init='uniform', input_dim=10))
model.add(Activation('tanh'))
model.add(Activation('softmax'))

sgd = SGD(lr=0.1, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='mean_squared_error', optimizer=sgd)

model.compile() に渡す前に optimizer をインスタンス化しても良いし、上の例のように、その名前で呼び出すこともできます。後者の場合には、optimizer のためのデフォルト・パラメータが使用されます。

# optimizer を名前で渡す: デフォルト・パラメータが使用されます。
model.compile(loss='mean_squared_error', optimizer='sgd')
 

Optimizer

keras.optimizers.Optimizer()

抽象 optimizer 基底クラス。

  • Note: これは全ての optimizer の親クラスです、訓練モデルのために使用可能な実際の optimizer ではありません。

全ての Keras optimizer は次のキーワード引数をサポートします :

  • clipnorm: float >= 0. (勾配の) L2 norm がこの値を超えた時、勾配がクリップされます。
  • clipvalue: float >= 0.
    (勾配の)絶対値がこの値を超えた時、勾配がクリップされます。
 

SGD

keras.optimizers.SGD(lr=0.01, momentum=0.0, decay=0.0, nesterov=False)

確率的勾配降下 (Stochastic gradient descent)。
momentum, decay, そして Nesterov momentum のサポートつき。

引数

  • lr: float >= 0. 学習率。
  • momentum: float >= 0. Parameter updates momentum.
  • decay: float >= 0. 各更新上の学習率減衰。
  • nesterov: boolean. Nesterov momentum を適用するか否か。
 

RMSprop

keras.optimizers.RMSprop(lr=0.001, rho=0.9, epsilon=1e-06)

RMSProp optimizer.

このオプティマイザのパラメータはデフォルト値のままにすることが推奨されます。

このオプティマイザは RNN のためには通常は良い選択です。

引数

  • lr: float >= 0. 学習率。
  • rho: float >= 0.
  • epsilon: float >= 0. Fuzz factor.
 

Adagrad

keras.optimizers.Adagrad(lr=0.01, epsilon=1e-06)

Adagrad optimizer.

このオプティマイザのパラメータはデフォルト値のままにすることが推奨されます。

引数

  • lr: float >= 0. 学習率。
  • epsilon: float >= 0.
 

Adadelta

keras.optimizers.Adadelta(lr=1.0, rho=0.95, epsilon=1e-06)

Adadelta optimizer.

このオプティマイザのパラメータはデフォルト値のままにすることが推奨されます。

引数

    lr: float >= 0. 学習率。デフォルト値のままにすることが推奨されます。
    rho: float >= 0.
    epsilon: float >= 0. Fuzz factor.

References

 

Adam

keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08)

Adam optimizer.

デフォルト・パラメータは元の論文で提供されているものに従っています。

引数

  • lr: float >= 0. 学習率。
  • beta_1/beta_2: floats, 0 < beta < 1. Generally close to 1.
  • epsilon: float >= 0. Fuzz factor.

References

 

Adamax

keras.optimizers.Adamax(lr=0.002, beta_1=0.9, beta_2=0.999, epsilon=1e-08)

Adamax optimizer from Adam paper’s Section 7.
infinity norm に基づく Adam の亜種です。

デフォルト・パラメータは論文で提供されているものに従っています。

引数

  • lr: float >= 0. 学習率。
  • beta_1/beta_2: floats, 0 < beta < 1. Generally close to 1.
  • epsilon: float >= 0. Fuzz factor.

References

以上

Keras : 目的関数

Keras : 目的関数(翻訳/要約)

* 本ページは、Keras 本家サイトの Keras : Objectives の簡単な要約です。

 

目的関数の使用方法

目的関数(あるいは損失関数、あるいは最適化スコア (optimization score) 関数)はモデルを compile するに必要な2つのパラメータの一つです :

model.compile(loss='mean_squared_error', optimizer='sgd')

既存の目的関数の名前を渡すこともできるし、あるいは、次の2つの引数を取り各データポイントのためのスカラを返す、 Theano/TensorFlow シンボリック関数を渡すこともできます :

  • y_true: True ラベル。Theano/TensorFlow テンソル。
  • y_pred: 予測。y_true と同じ shape の Theano/TensorFlow テンソル。

実際の最適化された目的関数 (optimized objective) は全てのデータポイントに渡る出力配列の平均です。

そのような関数の2、3の例は、objectives source を check out してください。

 

利用可能な目的関数 (objectives)

  • mean_squared_error / mse
  • mean_absolute_error / mae
  • mean_absolute_percentage_error / mape
  • mean_squared_logarithmic_error / msle
  • squared_hinge
  • hinge
  • binary_crossentropy: logloss としても知られます。
  • categorical_crossentropy: Also known as multiclass logloss としても知られます。Note: この目的関数を使用するためには、ラベルが arrays of shape (nb_samples, nb_classes) であることが必要です。
  • poisson: (予測 – targets * log(予測)) の平均。
  • cosine_proximity: the opposite (negative) of the mean cosine proximity between predictions and targets.

以上

Keras : データセット

Keras : Datasets(翻訳/要約)

* 本ページは、Keras 本家サイトの Keras : Datasets の簡単な要約です。

 

CIFAR10 小画像分類

keras.datasets.cifar10

10 カテゴリーにラベルづけられた、50,000 32×32 カラー訓練画像のデータセット、
そして 10,000 テスト画像。

使用方法:

(X_train, y_train), (X_test, y_test) = cifar10.load_data()

戻り値:

  • 2 タブル:
    • X_train, X_test: shape (nb_samples, 3, 32, 32) の RGB 画像データの uint8 配列。
    • y_train, y_test: shape (nb_samples,) のカテゴリー・ラベル(range 0-9 の整数値)の uint8 配列。
 

CIFAR100 小画像分類

keras.datasets.cifar100

100 カテゴリーにラベルづけられた、50,000 32×32 カラー訓練画像のデータセット、
そして 10,000 テスト画像。

使用方法:

(X_train, y_train), (X_test, y_test) = cifar100.load_data(label_mode=’fine’)

戻り値:

  • 2 タブル:
    • X_train, X_test: shape (nb_samples, 3, 32, 32) の RGB 画像データの uint8 配列。
    • y_train, y_test: shape (nb_samples,) のカテゴリー・ラベルの uint8 配列。

引数:

  • label_mode: “fine” または “coarse”.
 

MNIST 手書き数字のデータセット

keras.datasets.mnist

10 数字の 60,000 28×28 グレースケール画像のデータセット、10,000 画像のテストセット添付。

使用方法:

(X_train, y_train), (X_test, y_test) = mnist.load_data()

戻り値:

  • 2 タブル:
    • X_train, X_test: shape (nb_samples, 28, 28) のグレースケール画像データの uint8 配列。
    • y_train, y_test: shape (nb_samples,) の数字ラベル(range 0-9 の整数値)の uint8 配列。

引数:

  • path: if you do have the index file locally (at ‘~/.keras/datasets/’ + path),
    if will be downloaded to this location (in cPickle format).

以上

Recurrent Neural Networks (2) – Python, Numpy と Theano による RNN の実装 (翻訳/要約)

Recurrent Neural Networks (2) – Python, Numpy と Theano による RNN の実装(翻訳/要約)

* Recurrent Neural Networks Tutorial, Part 2 – Implementing a RNN with Python, Numpy and Theano の簡単な要約です。
* 画像図の引用はしませんので、原文を参照しながらお読みください。

 
本文

Code to follow along is on Github.

RNN 全部をスクラッチから実装します。Python を使用して(GPU 上で演算を実行できるライブラリ)Theano で実装を最適化します。

 

言語モデル

目標は RNN を使用して言語モデルを構築することです。これはこういう意味です。m 単語の文があるとしましょう。言語モデルは文を観測する確率を次のように予測することを可能にします :

\begin{aligned}  P(w_1,...,w_m) = \prod_{i=1}^{m} P(w_i \mid w_1,..., w_{i-1})  \end{aligned}

言葉で言うならば、文の確率はそれより前に来る単語群が与えられた時の各単語の確率の生産物です。従って、文 “He went to buy some chocolate” の確率は “He went to buy some” が与えられた時の “chocolate” の確率になり、(“He went to buy some” は)“He went to buy” が与えられた時の “some” の確率を乗算したもので…等々。

何故それが有用なのでしょうか?文の観測に確率を割り当てることを何故望むのでしょうか?

まず、そのようなモデルはスコアリング・メカニズムとして使用できます。例えば、機械翻訳システムは典型的には入力文に対して複数の候補を生成します。 最もありそうな文を選択するために言語モデルを使用できるでしょう。直感的には、(確率的に)最もありそうな文は文法的にも正しいことがありがちです。同様なスコアリングは音声認識システムでも起きるでしょう。

しかし言語モデル問題の解決はまた cool な副次効果もあります。先行する単語群が与えられた時の単語の確率を予測できるのですから、新しいテキストを生成することも可能です。それは 生成モデル です。
単語群の既存の文が与えられた時、予測された確率から次の単語をサンプリングしてこのプロセスを完全な文を得るまで繰り返します。Andrej Karparthy による RNN の有効性 は言語モデルができることを示します。彼のモデルは full の単語群に対して単一の文字上で訓練され、Shakespeare から Linux コードまで何でも生成できます。

上の等式において、各単語の確率は全ての前の単語上と条件付けられている点に注意してください。実践的には、多くのモデルは計算上のあるいはメモリ制約のためにそのような long-term 依存を表現することに苦戦しています。それらは典型的には2、3の前の単語のみを参照することに制限されています。RNN は、理論的には、そのような long-term 依存を捕捉できますが、実践的にはもう少し複雑です。

 

データの訓練と前処理

私たちの言語モデルを訓練するためにはそこから学習するためのテキストが必要です。幸いなことに言語モデルを訓練するためのラベルは不要で、生テキストのみが必要です。15,000 の長めの reddit コメントを dataset available on Google’s BigQuery からダウンロードしました。私たちのモデルにより生成されたテキストは reddit コメンテーターっぽいでしょう(願わくば)!しかし多くの機械学習プロジェクトのようにデータを適正なフォーマットにするための幾つかの前処理が最初に必要です。

1. テキストをトークン化する

生テキストがありますが、単語毎ベースで予測することを望みます。これはコメントを文に、文を単語にトークン化しなければならないことを意味しています。スペース(空白)で各コメントを分割することもできますが、それは句読点を正しく扱えないでしょう。文 “He left!” は 3 トークンであるべきです: “He”, “left”, “!” 。NLTK の word_tokenize と sent_tokenize メソッドを使用します。

2. 稀な単語を取り除く

テキストの殆どの単語は1回か2回現れるだけです。これらの稀な単語を取り除くことは良い考えです。大規模な語彙を持つことはモデルの訓練を遅くし、そしてそのような単語のための多くの文脈上のサンプルを持たないために、それらを正しく使うことを学習することができないでしょう。これは人間の学習方法と良く似ています。単語を適正に使用する方法を実際に理解するためには異なるコンテキストでそれを見る必要があります。

私たちのコードでは語彙を vocabulary_size 最も一般的な単語に制限します。(8000 に設定しましたが、自由に変更してください。)語彙に含まれていない全ての単語は UNKNOWN_TOKEN で置き換えます。例えば、単語 “nonlinearities” が語彙に含まれていないならば、文 “nonlineraties are important in neural networks” は “UNKNOWN_TOKEN are important in Neural Networks” となります。単語 UNKNOWN_TOKEN は語彙の一部となり他の単語と同様に予測します。新しいテキストを生成する時に UNKNOWN_TOKEN を再度置換できます、例えばランダムにサンプリングされた語彙にない単語を選ぶか、あるいは unknown token を含まない文を得るまで文を生成することもできるでしょう。

3. Prepend special start and end tokens

またどの単語が文の start と end になる傾向があるかも学習したいです。これをするために各文に特殊な SENTENCE_START トークンを先頭に追加して、特殊な SENTENCE_END トークンを追加します。最初のトークンが SENTENCE_START であるとして、次の単語は何になりがちでしょう(文の実際の最初の単語)?

4. 訓練データ行列の構築

RNN への入力はベクトルであり、文字列ではありません。そのため、単語とインデックス間のマッピングを作成します、index_to_word と word_to_index です。例えば、単語 “friendly” は index 2001 かもしれません。訓練サンプル x は [0, 179, 341, 416] のように見えるかもしれません、ここで 0 は SENTENCE_START に相当します。該当ラベル y は [179, 341, 416, 1] のようなものです。私たちの目標は次の単語を予測することを思い出してください、従って y は、最後の要素が SENTENCE_END トークンである、一つ位置をシフトしたベクトル x です。換言すれば、上の単語 179 への正しい予測は 341 になります、実際の次の単語です。

vocabulary_size = 8000
unknown_token = "UNKNOWN_TOKEN"
sentence_start_token = "SENTENCE_START"
sentence_end_token = "SENTENCE_END"
 
# データを読み、SENTENCE_START と SENTENCE_END を追加します。
print "Reading CSV file..."
with open('data/reddit-comments-2015-08.csv', 'rb') as f:
    reader = csv.reader(f, skipinitialspace=True)
    reader.next()
    # full コメントを文群に分割します。
    sentences = itertools.chain(*[nltk.sent_tokenize(x[0].decode('utf-8').lower()) for x in reader])
    # SENTENCE_START と SENTENCE_END を追加します。
    sentences = ["%s %s %s" % (sentence_start_token, x, sentence_end_token) for x in sentences]
print "Parsed %d sentences." % (len(sentences))
     
# 文を単語にトークン化します。
tokenized_sentences = [nltk.word_tokenize(sent) for sent in sentences]
 
# 単語の頻度をカウントします。
word_freq = nltk.FreqDist(itertools.chain(*tokenized_sentences))
print "Found %d unique words tokens." % len(word_freq.items())
 
# 最も一般的な単語群を得て、index_to_word と word_to_index ベクトルを構築します。
vocab = word_freq.most_common(vocabulary_size-1)
index_to_word = [x[0] for x in vocab]
index_to_word.append(unknown_token)
word_to_index = dict([(w,i) for i,w in enumerate(index_to_word)])
 
print "Using vocabulary size %d." % vocabulary_size
print "The least frequent word in our vocabulary is '%s' and appeared %d times." % (vocab[-1][0], vocab[-1][1])
 
# 語彙になり全ての単語を unknown token で置換します。
for i, sent in enumerate(tokenized_sentences):
    tokenized_sentences[i] = [w if w in word_to_index else unknown_token for w in sent]
 
print "\nExample sentence: '%s'" % sentences[0]
print "\nExample sentence after Pre-processing: '%s'" % tokenized_sentences[0]
 
# 訓練データを作成します。
X_train = np.asarray([[word_to_index[w] for w in sent[:-1]] for sent in tokenized_sentences])
y_train = np.asarray([[word_to_index[w] for w in sent[1:]] for sent in tokenized_sentences])

ここにテキストからの実際の訓練サンプルを示します :

x:
SENTENCE_START what are n't you understanding about this ? !
[0, 51, 27, 16, 10, 856, 53, 25, 34, 69]

y:
what are n't you understanding about this ? ! SENTENCE_END
[51, 27, 16, 10, 856, 53, 25, 34, 69, 1]
 

RNN を構築する

言語モデルのための RNN がどのようなものか見てみましょう。
入力 x は単語のシーケンスで各 x_t は単一の単語です。しかしもう一つあります: 行列乗算であるため、入力として単語 index (like 36) を単純には使用できません。代わりに、各単語をサイズ vocabulary_size の one-hot ベクトルで表します。例えば、index 36 の単語は全て 0 で位置 36 が 1 のベクトルになるでしょう。従って、各 x_t はベクトルとなり、x は各行が単語を表す行列です。この変換を前処理で行う代わりに、NN コードで遂行します。ネットワークの出力 o も同様のフォーマットです。各 o_t は vocabulary_size 要素のベクトルで各要素はその単語が文の次の単語となる確率を表します。

RNN のための等式です :

\begin{aligned}  s_t &= \tanh(Ux_t + Ws_{t-1}) \\  o_t &= \mathrm{softmax}(Vs_t)  \end{aligned}

行列とベクトルの次元を書き出すことは有用です。語彙サイズ C = 8000 と隠れ層サイズ H = 100 をすることを仮定しましょう。隠れ層サイズをネットワークの “メモリ” と考えて良いです。それを大きくすることはより複雑なパターンの学習を可能にしますが、追加の計算もまた引き起こします。従って :

\begin{aligned}  x_t & \in \mathbb{R}^{8000} \\  o_t & \in \mathbb{R}^{8000} \\  s_t & \in \mathbb{R}^{100} \\  U & \in \mathbb{R}^{100 \times 8000} \\  V & \in \mathbb{R}^{8000 \times 100} \\  W & \in \mathbb{R}^{100 \times 100} \\  \end{aligned}

これは価値ある情報です。U,VW はデータから学習したい、ネットワークのパラメータであることを思い出してください。従って、総計 2HC + H^2 パラメータを学習する必要があります。
C=8000H=100 の場合にはそれは 1,610,000 になります。

次元はまたモデルのボトルネックも教えてくれます。x_t は one-hot ベクトルなので、それを U で乗算することは U のカラムを選択することと本質的に同じなので、フルに乗算を遂行する必要はありません。そしてネットワークの最大の行列乗算は Vs_t になります。それが語彙サイズをできれば小さくした理由です。

初期化

RNN クラスを宣言してパラメータの初期化をすることから始めます。このクラスを RNNNumpy と呼びます、それは後で Theano 版を実装するからです。パラメータ U,VW の初期化は少しばかり技巧的です。0 で初期化することはできません、何故ならそれは層全部で対称計算 (symmetric calculations) を引き起こす結果になるからです。(訳注: i.e. 上手く学習できない。)それらをランダムに初期化しなければなりません。適正な初期化は訓練結果にインパクトがあるようですのでこの分野では沢山の研究があります。最良の初期化は活性化関数(私たちの場合は \tanh)に依存することは判明していて、一つの 推奨される アプローチは重みを \left[-\frac{1}{\sqrt{n}}, \frac{1}{\sqrt{n}}\right] からの間隔においてランダムに初期化することです。ここで n は前の層からの入ってくる接続 (incoming connections) の数です。これは非常に複雑に見えるかもしれませんが、必要以上にそれを心配しないでください。パラメータを小さなランダム値で初期化する限りは、通常は上手く動作します。

class RNNNumpy:
    def __init__(self, word_dim, hidden_dim=100, bptt_truncate=4):
        # インスタンス値を割り当てます
        self.word_dim = word_dim
        self.hidden_dim = hidden_dim
        self.bptt_truncate = bptt_truncate
        # ネットワーク・パラメータをランダムに初期化
        self.U = np.random.uniform(-np.sqrt(1./word_dim), np.sqrt(1./word_dim), (hidden_dim, word_dim))
        self.V = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (word_dim, hidden_dim))
        self.W = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (hidden_dim, hidden_dim))

上で、word_dim は語彙のサイズで、hidden_dim は隠れ層のサイズです。(選択可能です。)bptt_truncate パラメータについては今のところは心配しないでください、それが何かは後で説明します。

フォワード・プロパゲーション (Forward Propagation)

次に、上述の等式で定義される(単語の確率を予測する)forward propagation を実装しましょう。

def forward_propagation(self, x):
    # 時間ステップの総数
    T = len(x)
    # forward propagation の間、全ての隠れ状態は s に保存します、何故なら後で必要になるので。
    # 初期隠れ (initial hidden) のための追加要素を追加します、これは 0 に設定します。
    s = np.zeros((T + 1, self.hidden_dim))
    s[-1] = np.zeros(self.hidden_dim)
    # 各時間ステップにおける出力。再度、後のために保存します。
    o = np.zeros((T, self.word_dim))
    # 各時間ステップのために…
    for t in np.arange(T):
        # U を x[t] でインデックスしていることに注意してください。
        # これは U に one-hot ベクトルを乗算することと同じです。
        s[t] = np.tanh(self.U[:,x[t]] + self.W.dot(s[t-1]))
        o[t] = softmax(self.V.dot(s[t]))
    return [o, s]
 
RNNNumpy.forward_propagation = forward_propagation

計算された出力だけでなく、隠れ状態も返します。それらは勾配を計算するために後で使います、そしてそれらをここで返すことにより計算の重複を回避します。各 o_t は語彙の単語を表す確率のベクトルです。しかし時に、例えばモデルを評価する時に、望むことは最も高い確率の次の単語です。関数 predict を呼び出します :

def predict(self, x):
    # forward propagation を遂行して最も高いスコアの index を返します。
    o, s = self.forward_propagation(x)
    return np.argmax(o, axis=1)
 
RNNNumpy.predict = predict

新たに実装したメソッドを試してサンプル出力を見てみましょう :

np.random.seed(10)
model = RNNNumpy(vocabulary_size)
o, s = model.forward_propagation(X_train[10])
print o.shape
print o
(45, 8000)
[[ 0.00012408  0.0001244   0.00012603 ...,  0.00012515  0.00012488
   0.00012508]
 [ 0.00012536  0.00012582  0.00012436 ...,  0.00012482  0.00012456
   0.00012451]
 [ 0.00012387  0.0001252   0.00012474 ...,  0.00012559  0.00012588
   0.00012551]
 ..., 
 [ 0.00012414  0.00012455  0.0001252  ...,  0.00012487  0.00012494
   0.0001263 ]
 [ 0.0001252   0.00012393  0.00012509 ...,  0.00012407  0.00012578
   0.00012502]
 [ 0.00012472  0.0001253   0.00012487 ...,  0.00012463  0.00012536
   0.00012665]]

(訳注: 動作確認済み、コンソール出力一致。)

文の各単語のために(上では 45)、モデルは次の単語の確率を表す 8000 の予測を作成しました。U,V,W をランダム値に初期化したので、現時点でこれらの予測は完全にランダムであることに注意してください。次は、各単語のための最も高い確率予測の index を当てます :

predictions = model.predict(X_train[10])
print predictions.shape
print predictions
(45,)
[1284 5221 7653 7430 1013 3562 7366 4860 2212 6601 7299 4556 2481 238 2539
 21 6548 261 1780 2005 1810 5376 4146 477 7051 4832 4991 897 3485 21
 7291 2007 6006 760 4864 2182 6569 2800 2752 6821 4437 7021 7875 6912 3575]

(訳注: 動作確認済み、コンソール出力一致。)

損失を計算する

ネットワークを訓練するためにはそれが起こすエラーを計測する方法が必要です。これを損失関数 L と呼び、目標は、訓練データのための損失関数を最小化するパラメータ U,VW を見つけることです。損失関数に対する一般的な選択は 交差エントロピー損失 です。もし N 訓練サンプル(テキストの単語)と C クラス(語彙のサイズ)を持つ場合、予測 o と真のラベル y に関連する損失は次で与えられます :

\begin{aligned}  L(y,o) = - \frac{1}{N} \sum_{n \in N} y_{n} \log o_{n}  \end{aligned}

式は少し複雑に見えますが、実際にそれが行っていることは訓練サンプルに渡って合計して予測がどれだけ間違っているかをベースにして損失に加算しているだけです。y(正しい単語)と o(予測)が離れれば離れるほど、損失が大きくなっていきます。関数 calculate_loss を実装します :

def calculate_total_loss(self, x, y):
    L = 0
    # 各文に対して…
    for i in np.arange(len(y)):
        o, s = self.forward_propagation(x[i])
        # 「正しい」単語の予測だけに注意を払います。
        correct_word_predictions = o[np.arange(len(y[i])), y[i]]
        # どれだけ間違えたかをベースに損失に加算します。
        L += -1 * np.sum(np.log(correct_word_predictions))
    return L

def calculate_loss(self, x, y):
    # 訓練サンプルの数で損失合計を除算する
    N = np.sum((len(y_i) for y_i in y))
    return self.calculate_total_loss(x,y)/N

RNNNumpy.calculate_total_loss = calculate_total_loss
RNNNumpy.calculate_loss = calculate_loss

ステップバックしてランダムな予測のための損失とは何かを考えてみましょう。それはベースラインを与えてくれて実装が正しいことを確かにしてくれます。語彙に C 単語を持ちますから、各単語は(平均して)確率 1/C で予測されます、これは L = -\frac{1}{N} N \log\frac{1}{C} = \log C の損失を生むでしょう :

# 時間の節約のために 1000 サンプルに制限します。
print "Expected Loss for random predictions: %f" % np.log(vocabulary_size)
print "Actual loss: %f" % model.calculate_loss(X_train[:1000], y_train[:1000])
Expected Loss for random predictions: 8.987197
Actual loss: 8.987440

(訳注: 動作確認済み、コンソール出力一致。)

非常に近いです! フル・データセット上の損失を評価することは高コストな演算で、沢山のデータを持つならば数時間かかるかもしれません。

SGD と Backpropagation Through Time (BPTT) により RNN を訓練する

訓練データ上の合計損失を最小化するパラメータ U,VW を見つけることを望んでいることを思い出してください。これを行なう最も一般的な方法は SGD (Stochastic Gradient Descent) – 確率的勾配降下法です。SGD の裏にある考えは非常に簡単です。訓練サンプルに渡って反復して、そして各反復においてエラーを減少させる方向にパラメータを少し押してやります。これらの方向は損失上の勾配で与えられます : \frac{\partial L}{\partial U}, \frac{\partial L}{\partial V}, \frac{\partial L}{\partial W}. SGD はまた学習率を必要とします、これは各反復においてどの位の大きさのステップをするかを定義します。SGD は、NN のためだけではなく、多くの他の機械学習アルゴリズムのためにも最も人気のある最適化手法です。それ自体、バッチ、並列性そして適応可能な学習率を使用して SGD をどのように最適化するかについては多くの研究があります。基本的な考え方は単純ですが、実際に効率的な方法で SGD を実装することは非常に複雑です。SGD についてもっと学びたければ これ が始めるに良い地点です。最適化のバックグラウンドさえなくても理解できるはずの SGD の単純版を実装します。

しかし上述したそれらの勾配をどのように計算するのでしょうか?伝統的な NN ではこれを backpropagation アルゴリズムを通して行ないます。RNN では、Backpropagation Through Time (BPTT) と呼ばれる、このアルゴリズムを少し修正した版を使用します。パラメータはネットワークの全ての時間ステップで共有されますので、各出力における勾配は現在の時間ステップの計算上だけではなく、前の時間ステップ群にも依存します。微積分を知っていれば、それは実際に chain rule (合成関数の微分の連鎖律 )を適用するだけです。チュートリアルの次のパートはすべて BPTT についてですので、導関数についてはここでは深入りしません。backpropagation への一般的なイントロは これこの投稿 をチェックしてください。今のところは BPTT をブラックボックスとして扱ってかまいません。それは訓練サンプル (x,y) を入力として取り、勾配 \frac{\partial L}{\partial U}, \frac{\partial L}{\partial V}, \frac{\partial L}{\partial W} を返します。

def bptt(self, x, y):
    T = len(y)
    # forward propagation を遂行する
    o, s = self.forward_propagation(x)
    # これらの変数の勾配を accumulate する
    dLdU = np.zeros(self.U.shape)
    dLdV = np.zeros(self.V.shape)
    dLdW = np.zeros(self.W.shape)
    delta_o = o
    delta_o[np.arange(len(y)), y] -= 1.
    # For each output backwards...
    for t in np.arange(T)[::-1]:
        dLdV += np.outer(delta_o[t], s[t].T)
        # Initial delta 計算
        delta_t = self.V.T.dot(delta_o[t]) * (1 - (s[t] ** 2))
        # Backpropagation through time (for at most self.bptt_truncate steps)
        for bptt_step in np.arange(max(0, t-self.bptt_truncate), t+1)[::-1]:
            # print "Backpropagation step t=%d bptt step=%d " % (t, bptt_step)
            dLdW += np.outer(delta_t, s[bptt_step-1])              
            dLdU[:,x[bptt_step]] += delta_t
            # 次のステップのための delta を更新する
            delta_t = self.W.T.dot(delta_t) * (1 - s[bptt_step-1] ** 2)
    return [dLdU, dLdV, dLdW]

RNNNumpy.bptt = bptt

勾配チェック

backpropagation を実装する時はいつでも勾配チェックも実装することは良い考えです、これは貴方の実装が正しいことを検証する方法です。勾配チェックの背後の考えはパラメータの導関数はその点での傾きに等しいことです、これはパラメータを少し変更して変更で除算することにより見積もることができます。

\begin{aligned}  \frac{\partial L}{\partial \theta} \approx \lim_{h \to 0} \frac{J(\theta + h) - J(\theta -h)}{2h}  \end{aligned}

それから backpropagation を使用して計算した勾配と上の方法で見積もった勾配を比較します。大きな違いがなければ良いでしょう。近似は全てのパラメータに対しての合計損失を計算する必要があるので、勾配チェックは非常に高コストです。(忘れないでください、上のサンプルで 100 万以上のパラメータを持ちました。)
そこで小さい語彙のモデルで遂行するのが良いアイデアです。

def gradient_check(self, x, y, h=0.001, error_threshold=0.01):
    # backpropagation を使用して勾配を計算します。これらが正しいかチェッカーが欲しいです。
    bptt_gradients = self.bptt(x, y)
    # チェックしたいパラメータのリスト。
    model_parameters = ['U', 'V', 'W']
    # 各パラメータのための勾配チェック
    for pidx, pname in enumerate(model_parameters):
        # Get the actual parameter value from the mode, e.g. model.W
        parameter = operator.attrgetter(pname)(self)
        print "Performing gradient check for parameter %s with size %d." % (pname, np.prod(parameter.shape))
        # Iterate over each element of the parameter matrix, e.g. (0,0), (0,1), ...
        it = np.nditer(parameter, flags=['multi_index'], op_flags=['readwrite'])
        while not it.finished:
            ix = it.multi_index
            # Save the original value so we can reset it later
            original_value = parameter[ix]
            # Estimate the gradient using (f(x+h) - f(x-h))/(2*h)
            parameter[ix] = original_value + h
            gradplus = self.calculate_total_loss([x],[y])
            parameter[ix] = original_value - h
            gradminus = self.calculate_total_loss([x],[y])
            estimated_gradient = (gradplus - gradminus)/(2*h)
            # Reset parameter to original value
            parameter[ix] = original_value
            # The gradient for this parameter calculated using backpropagation
            backprop_gradient = bptt_gradients[pidx][ix]
            # calculate The relative error: (|x - y|/(|x| + |y|))
            relative_error = np.abs(backprop_gradient - estimated_gradient)/(np.abs(backprop_gradient) + np.abs(estimated_gradient))
            # If the error is to large fail the gradient check
            if relative_error > error_threshold:
                print "Gradient Check ERROR: parameter=%s ix=%s" % (pname, ix)
                print "+h Loss: %f" % gradplus
                print "-h Loss: %f" % gradminus
                print "Estimated_gradient: %f" % estimated_gradient
                print "Backpropagation gradient: %f" % backprop_gradient
                print "Relative Error: %f" % relative_error
                return 
            it.iternext()
        print "Gradient check for parameter %s passed." % (pname)

RNNNumpy.gradient_check = gradient_check

# To avoid performing millions of expensive calculations we use a smaller vocabulary size for checking.
grad_check_vocab_size = 100
np.random.seed(10)
model = RNNNumpy(grad_check_vocab_size, 10, bptt_truncate=1000)
model.gradient_check([0,1,2,3], [1,2,3,4])

訳注: 実行結果 :

Performing gradient check for parameter U with size 1000.
rnn_numpy.py:115: RuntimeWarning: invalid value encountered in double_scalars
  relative_error = np.abs(backprop_gradient - estimated_gradient)/(np.abs(backprop_gradient) + np.abs(estimated_gradient))
Gradient check for parameter U passed.
Performing gradient check for parameter V with size 1000.
Gradient check for parameter V passed.
Performing gradient check for parameter W with size 100.
Gradient check for parameter W passed.

SGD 実装

パラメータに対する勾配を計算できるようになったので
SGD を実装できます :

1. 関数 sdg_step は勾配を計算して一つのバッチに対して更新を遂行します。
2. 外部ループ (outer loop) は訓練セットを通して反復して学習率を調整します。

# SGD の 1 ステップを実行します。
def numpy_sdg_step(self, x, y, learning_rate):
    # 勾配を計算します。
    dLdU, dLdV, dLdW = self.bptt(x, y)
    # 勾配と学習率に従ってパラメータを変更します。
    self.U -= learning_rate * dLdU
    self.V -= learning_rate * dLdV
    self.W -= learning_rate * dLdW

RNNNumpy.sgd_step = numpy_sdg_step
# Outer SGD ループ
# - model: RNN モデル・インスタンス
# - X_train: 訓練データ・セット
# - y_train: 訓練データ・ラベル
# - learning_rate: SGD のための初期学習率
# - nepoch: 完全なデータセットを通して反復するための回数
# - evaluate_loss_after: この多くの epoch 後の損失を評価する
def train_with_sgd(model, X_train, y_train, learning_rate=0.005, nepoch=100, evaluate_loss_after=5):
    # We keep track of the losses so we can plot them later
    losses = []
    num_examples_seen = 0
    for epoch in range(nepoch):
        # Optionally evaluate the loss
        if (epoch % evaluate_loss_after == 0):
            loss = model.calculate_loss(X_train, y_train)
            losses.append((num_examples_seen, loss))
            time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            print "%s: Loss after num_examples_seen=%d epoch=%d: %f" % (time, num_examples_seen, epoch, loss)
            # Adjust the learning rate if loss increases
            if (len(losses) > 1 and losses[-1][1] > losses[-2][1]):
                learning_rate = learning_rate * 0.5  
                print "Setting learning rate to %f" % learning_rate
            sys.stdout.flush()
        # For each training example...
        for i in range(len(y_train)):
            # One SGD step
            model.sgd_step(X_train[i], y_train[i], learning_rate)
            num_examples_seen += 1
            

Done ! ネットワークを訓練するのにどの程度長くかかるか感じ取ってみましょう :

np.random.seed(10)
model = RNNNumpy(vocabulary_size)
%timeit model.sgd_step(X_train[10], y_train[10], 0.005)

Uh-oh, bad news. 私のラップトップで SGD の 1 ステップは約 350 ミリ秒かかりました。
訓練データには約 80,000 サンプルがありますから、1 epoch(全てのデータセットに渡る反復)は数時間かかります。複数の epoch は数日あるいは数週間さえかかるでしょう!そして私たちは依然として、多くの企業や研究者がそこで使用しているものと比較して、小さなデータセットで作業しています。What now?

幸いなことにコードをスピードアップする多くの方法があります。同じモデルにこだわってコードをより速く動作することもできますし、あるいは計算上のコストを下げるようにモデルを修正しても良いですし、あるいは両方でも良いです。

研究者はモデルを計算上のコストを下げるような沢山の方法を同定してきました、例えば大規模な行列乗算を回避するために階層 softmax を使用したり射影 (projection) 層を追加することによってです。(これ または これ を参照). しかしモデルを単純なままに保持して最初のルートを進みます: GPU を使用して実装を速くします。けれどもそれを行なう前に、小さなデータセットで SGD を実行して損失が実際に減少することをチェックしてみましょう:

np.random.seed(10)
# Train on a small subset of the data to see what happens
model = RNNNumpy(vocabulary_size)
losses = train_with_sgd(model, X_train[:100], y_train[:100], nepoch=10, evaluate_loss_after=1)
2015-09-30 10:08:19: Loss after num_examples_seen=0 epoch=0: 8.987425
2015-09-30 10:08:35: Loss after num_examples_seen=100 epoch=1: 8.976270
2015-09-30 10:08:50: Loss after num_examples_seen=200 epoch=2: 8.960212
2015-09-30 10:09:06: Loss after num_examples_seen=300 epoch=3: 8.930430
2015-09-30 10:09:22: Loss after num_examples_seen=400 epoch=4: 8.862264
2015-09-30 10:09:38: Loss after num_examples_seen=500 epoch=5: 6.913570
2015-09-30 10:09:53: Loss after num_examples_seen=600 epoch=6: 6.302493
2015-09-30 10:10:07: Loss after num_examples_seen=700 epoch=7: 6.014995
2015-09-30 10:10:24: Loss after num_examples_seen=800 epoch=8: 5.833877
2015-09-30 10:10:39: Loss after num_examples_seen=900 epoch=9: 5.710718

(訳注: 以下は検証結果)

2016-03-20 20:41:27: Loss after num_examples_seen=0 epoch=0: 8.987425
2016-03-20 20:41:49: Loss after num_examples_seen=100 epoch=1: 8.976270
2016-03-20 20:42:12: Loss after num_examples_seen=200 epoch=2: 8.960212
2016-03-20 20:42:33: Loss after num_examples_seen=300 epoch=3: 8.930430
2016-03-20 20:42:57: Loss after num_examples_seen=400 epoch=4: 8.862264
2016-03-20 20:43:24: Loss after num_examples_seen=500 epoch=5: 6.913570
2016-03-20 20:43:55: Loss after num_examples_seen=600 epoch=6: 6.302493
2016-03-20 20:44:25: Loss after num_examples_seen=700 epoch=7: 6.014995
2016-03-20 20:44:52: Loss after num_examples_seen=800 epoch=8: 5.833877

Good, 私たちの実装は少なくとも何か有用なことをして損失を減らしているようです、望んだように。

 

ネットワークを Theano と GPU で訓練する

以前に Theno について tutorial を書きました、そしてロジックは正確に同じままなのでここでは再度最適化されたコードを通り抜けはしません。numpy 計算を相当する Theano の計算に置き換える、RNNTheano クラスを定義しました。

np.random.seed(10)
model = RNNTheano(vocabulary_size)
%timeit model.sgd_step(X_train[10], y_train[10], 0.005)

今回は、一つの SGD ステップは私の Mac (without GPU) で 70ms、GPU 装備の Amazon EC2 インスタンス g2.2xlarge 上で 23 ms です。それは初期実装の 15x の改善でモデルを数週間の代わりに数時間/数日で訓練できることを意味しています。依然として数多くの可能な最適化がありますが、当面はこれで十分です。

モデルを訓練するのに数日間を費やすことを貴方が回避することを助けるため、Theano モデルを 50 次元の隠れ層と 8000 の語彙で事前訓練しました。50 epoch に対して約 20 時間の訓練をしました。損失は依然として減少していて、より長時間の訓練は間違いなくより良いモデルになるでしょう。貴方自身で自由に試して長時間訓練してみてください。モデル・パラメータは Github レポジトリの data/trained-model-theano.npz で見つかります。そして load_model_parameters_theano メソッドを使用してそれらをロードします。

from utils import load_model_parameters_theano, save_model_parameters_theano

model = RNNTheano(vocabulary_size, hidden_dim=50)
# losses = train_with_sgd(model, X_train, y_train, nepoch=50)
# save_model_parameters_theano('./data/trained-model-theano.npz', model)
load_model_parameters_theano('./data/trained-model-theano.npz', model)

テキストを生成する

モデルを持った今、それに私たちのために新しいテキストを生成することを頼むことができます。新しい文を生成するためのヘルパー関数を実装しましょう:

def generate_sentence(model):
    # We start the sentence with the start token
    new_sentence = [word_to_index[sentence_start_token]]
    # Repeat until we get an end token
    while not new_sentence[-1] == word_to_index[sentence_end_token]:
        next_word_probs = model.forward_propagation(new_sentence)
        sampled_word = word_to_index[unknown_token]
        # We don't want to sample unknown words
        while sampled_word == word_to_index[unknown_token]:
            samples = np.random.multinomial(1, next_word_probs[-1])
            sampled_word = np.argmax(samples)
        new_sentence.append(sampled_word)
    sentence_str = [index_to_word[x] for x in new_sentence[1:-1]]
    return sentence_str

num_sentences = 10
senten_min_length = 7

for i in range(num_sentences):
    sent = []
    # We want long sentences, not sentences with one or two words
    while len(sent) &lt; senten_min_length:
        sent = generate_sentence(model)
    print " ".join(sent)

2、3の選択された(検閲された)文です。大文字にしてあります。

  • Anyway, to the city scene you’re an idiot teenager.
  • What ? ! ! ! ! ignore!
  • Screw fitness, you’re saying: https
  • Thanks for the advice to keep my thoughts around girls.
  • Yep, please disappear with the terrible generation.

生成テキストを見ると注意すべき2、3の興味深いことがあります。モデルはシンタックスを成功的に学習しています。コンマを(通常は and や or の前に)正しく置いて文を句点で終了させます。時々それは複数の感嘆符やスマイリーのようなインターネット・スピーチを真似します。

けれども、生成テキストの大半は意味をなさないか、文法的なエラーがあります。(上では実際には最良なものを選んでいます。)一つの理由はネットワークを十分に訓練していないことです。(あるいは十分な訓練データを使用していないことです。)それは多分正しいです、しかし主要な理由ではおそらくないでしょう。私たちの vanilla RNN は意味のあるテキストを生成できません、何故なら幾つかのステップが離れた単語間の依存性を学習できないからです。それは RNN が最初に創られた時に人気の獲得に失敗した理由でもあります。それらは理論的には美しいですが実践的には上手く動作しませんでした、そして私たちは何故かを直ちに理解しませんでした。

幸いなことに、RNN を訓練する難しさは現在では 非常に良く理解されて います。このチュートリアルの次のパートでは Backpropagation Through Time (BPTT) アルゴリズムをより詳しく探求します。そして vanishing gradient problem(勾配消失問題)と呼ばれるものをデモします。これは、LSTM のようなより洗練された RNN モデルへと進む動機となります。これは NLP の多くのタスクに対して最新の技術です(そして非常に良い reddit コメントを生成できます!)。このチュートリアルで学んだこと全てはLSTM と他の RNN モデルにも当てはまりますので、vanilla RNN の結果が期待以下だとしてもがっかりしないでください。

 

以上

Recurrent Neural Networks (1) – RNN への序説 (翻訳/要約)

Recurrent Neural Networks (1) – RNN への序説 (翻訳/要約)

* Recurrent Neural Networks Tutorial, Part 1 – Introduction to RNNs の簡単な要約です。
* 原文の図を参照しながらお読みください。

 
本文

チュートリアルの一部として RNN ベースの言語モデルを実装します。言語モデルのアプリケーションは2つの fold を持ちます : 最初に、現実世界で起きることの尤もらしさをベースに任意の文に点数をつけることを可能にします。これは文法的、意味的な正確性の尺度を提供します。そのようなモデルは典型的には機械翻訳システムの一部として使用されます。2つ目に、言語モデルは新しいテキストの生成を可能にします。(よりクールなアプリケーションでしょう。)Shakespeare 上で言語モデルを訓練すれば Shakespeare ライクなテキストの生成を可能にします。Andrej Karpathy による 興味深い投稿要約)は RNN ベースの文字レベル言語モデルが何ができるかを示しています。

基本的な NN にある程度慣れていることを仮定しています。そうでないなら、Implementing A Neural Network From Scratch へ進むことを望むかもしれません、これは Non-RNN の背後のアイデアや実装を紹介ます。

 

RNN とは何か?

RNN の背後のアイデアはシーケンシャル情報を利用することです。伝統的な NN では全ての入力(そして出力)は互いに独立です。しかしそれは多くのタスクに対しては非常に悪い考えです。文で次の単語を予測したければその前にどの単語が来たか知る方が良いでしょう。RNN は “recurrent (再発的・周期的)” と呼ばれますが、これはシークエンスの全ての要素に対して(前の計算に依存する出力とともに)同じタスクを遂行するからです。RNN について考える他の方法はそれが “メモリ” を持つことです、これはそれまでに何が計算されたかについての情報を捕捉します。理論的には RNN は任意の長さのシーケンスの情報を利用できますが、しかし実践的には 2, 3 ステップ(後でそれ以上)を look back することに制限されます。典型的な RNN がどのように見えるかをここに示します : (図/省略)

上の図は RNN がフル・ネットワークに展開される (unrolled, unfolded) ことを示しています。この展開は、完全なシーケンスのためのネットワークを書き出していることを単に意味しています。例えば、気にかけるシーケンスが5つの単語の文の場合、ネットワークは 5-層 NN に展開されます、各単語のために 1 層です。RNN で起きる計算を統治する数式は以下のようなものです :

  • x_t はタイム・ステップ t における入力です。例えば、x_1 は文の2番目の単語に相当する one-hot ベクトルです。
  • s_t はタイム・ステップ t における隠れ状態 (hidden state) です。これはネットワークの「メモリ」です。
    s_t は前の隠れ状態と現在のステップの入力をベースに計算されます : s_t=f(Ux_t + Ws_{t-1})
    関数 f は通常は tanh あるいは ReLU のような非線形性です。
    s_{-1}、これは最初の隠れ状態の計算に必要ですが、典型的には全て 0 に初期化されます。
  • o_t はステップ t における出力です。例えば、文の次の単語を予測することを望む場合、それは確率のベクトルになります。 o_t = \mathrm{softmax}(Vs_t).

ここで2、3の注意すべき点があります :

  • 隠れ状態 s_t はネットワークのメモリとして考えて良いです。s_t は全ての前の時間ステップにおいて発生したことについての情報を捕捉します。ステップ o_t における出力は時間 t におけるメモリをベースに単独で計算されます。簡単に上述したように、実践ではもう少し複雑です、何故ならば s_t は通常は非常に多くの過去の時間ステップから情報を捕捉することはできないからです。
  • 各層で異なるパラメータを使用する伝統的な DNN と違って、RNN は同じパラメータ ((U, V, W) を全てのステップに渡り共有します。これは各ステップで同じタスクを、異なる入力だけで、遂行しているという事実を反映しています。これは学習しなければならないパラメータの総数を大きく減らします。
  • 上図は各時間ステップで出力を持ちますが、タスクによってはこれは必要ないかもしれません。例えば、文の感情を予測する時、各単語の後の感情ではなくて、最終的な出力のみに注意を払うかもしれません。同様に、各時間ステップで入力は必要ないかもしれません。RNN の主要な特徴はその隠れ状態にあります、これはシーケンスについての幾つかの情報を捕捉します。
 

RNN は何ができるか?

RNN は多くの NLP タスクで大きな成功を見せてきました。ここで RNN の最も共通的に使用されるタイプは LSTM であることに言及すべきでしょう。これは long-term 依存を捕捉する点で vanilla RNN よりも遥に優れています。しかし心配しないでください、LSTM は本質的にはこのチュートリアルで開発るる RNN と同じものです、それらは隠れ状態の異なる計算方法を持つだけです。

ここで NLP における RNN の幾つかの例となるアプリケーションを示します。

言語モデルとテキスト生成

単語のシーケンスが与えられた時、前の単語群が与えられた時の各単語の確率を予測したいです。言語モデルは文の尤もらしさを計測することを可能にし、これは機械翻訳のための重要な入力となります。(何故なら高い確率の文は典型的には正しいからです。)次の単語を予測することができる副次的効果は生成モデルを得られることです、これは出力確率からのサンプリングによる新しいテキストの生成を可能にします。そして訓練データに依存して あらゆるもの が生成できます。 言語モデルでは入力は典型的には(例えば one-hot ベクトルとしてエンコードされた)単語のシーケンスで、出力は予測された単語のシーケンスです。ネットワークを訓練する時には o_t = x_{t+1} とします、何故ならステップ t における出力を実際の次の単語として望むからです。

言語モデルとテキスト生成についての研究論文です :

機械翻訳

機械学習は、入力がソース言語(例えばドイツ語)における単語のシーケンスである、という言語モデルと同様です。ターゲット言語(例えば英語)における単語のシーケンスを出力することを望みます。鍵となる違いは、出力が完全な入力を見た後でのみ始まることです。何故なら訳文の最初の単語は完全な入力文から捕捉された情報を必要とするかもしれないからです。

機械翻訳についての研究論文です :

音声認識

音波からの音響信号の入力シーケンスが与えられた時、音声セグメント (phonetic segments) を確率と一緒に予測できます。

音声認識についての研究論文です :

画像説明(文)を生成する

畳み込み NN と一緒に、RNN は unlabeled 画像のための generate descriptions へのモデルの一部として使用されています。これがどれほど上手く動作しているか驚異的です。combined モデルは、画像で見られる特徴と生成された単語を連携させる (align) ことさえします。

 

RNN を訓練する

RNN の訓練は伝統的な NN の訓練と同様です。backpropagation アルゴリズムもまた使いますが、少し捻っています。パラメータがネットワークの全ての時間ステップで共有されますので、各出力の勾配は現在の時間ステップの計算上だけではなく、前のステップにも依存します。例えば、t=4 における勾配を計算するためには 3 ステップを backpropagate して勾配を加算する必要があるでしょう。これは BPTTBackpropagation Through Time と呼ばれます。今のところ、BPTT で訓練された vanilla RNN は long-term 依存(= 遠く離れたステップ間の依存)を学習するには、勾配消失/発散問題 (vanishing/exploding gradient problem) と呼ばれるもののために 困難を持つ という事実を知っておいてください。これらの問題を扱うために幾つかの機械が存在して、(LSTM のような)あるタイプの RNN がこれを回避するために特に設計されました。

 

RNN 拡張

何年もの間、vanilla RNN モデルの欠点の幾つかに対処するために、研究者は RNN のより洗練されたタイプを開発してきました。この章はモデルの分類に慣れるように簡単な概要を提供します。

Bidirectional RNN は時間 t における出力がシーケンスの前の要素だけではなく、未来の要素にも依存するというアイデアをベースにしています。例えば、文の欠けた単語を予測するためには左側と右側のコンテキストを見たいでしょう。Bidirectional RNN は非常に単純です。それらは互いのトップに積み重ねた、単に2つの RNN です。そして出力は両方の RNN の隠れ状態をベースにして計算されます。

Deep (Bidirectional) RNN は Bidirectional RNN と同様ですが、時間ステップ毎に多層を持ちます。実際にこれはより高い学習キャパシティを与えてくれます。(しかしまた沢山の訓練データが必要になりますが。)

LSTM ネットワーク は最近極めて人気があり上でも簡単に話題にしました。LSTM は基本的には RNN と違うアーキテクチャを持つわけではありませんが、隠れ状態を計算するために異なる関数を使用します。LSTM のメモリはセルと呼ばれ、それらを、入力として前の状態 h_{t-1} と現在の入力 x_t を取るブラックボックスと考えることができます。内部的にはこれらのセルは何をメモリに保持するか(そしてそこから何を消すか)を決めます。そしてそれらは前の状態、現在のメモリ、そして入力を結合します。これらのタイプのユニットは long-term 依存を捕捉するために非常に効率的であることが分かっています。

 

以上

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 : Sequence-to-Sequence モデル(コード解説)

TensorFlow : コード解説 : RNN : Sequence-to-Sequence モデル

* TensorFlow : Tutorials : Sequence-to-Sequence モデル (翻訳/解説) に、数式排除/コード重視の方針で詳細な解説を加筆したものです。

 
本文

既に RNN & LSTM 解説 で議論したように RNN は言語のモデル化を学習できます。これは興味深い質問を引き起こします: ある入力に対して生成された単語を条件として、意味のあるレスポンスを生成できるでしょうか? 例えば、ニューラルネットワークを英語からフランス語に翻訳するために訓練することができるでしょうか? 答えは yes であることが判明しています。

このチュートリアルはそのようなシステムをどのように構築して訓練するかを示します。pip パッケージを通してインストール済みで、tensorflow git レポジトリをクローンし、そして git ツリーのルートにいることを仮定しています。

そして翻訳プログラムの実行で開始することができます:

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

これは WMT’15 Web サイト から英語フランス語翻訳データをダウンロードして訓練のための準備をし、そして訓練します。約 20 GB のディスク容量を取り、そしてダウンロードと準備(詳細は後で)にしばらくの時間がかかりますので、貴方はこのチュートリアルを読みながら始めて実行したままにするのが良いでしょう。

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

ファイル

What’s in it? (内容)
seq2seq.py

sequence-to-sequence モデルを構築するライブラリ。
translate/seq2seq_model.py

ニューラル翻訳 sequence-to-sequence モデル。
translate/data_utils.py

翻訳データを準備するためのヘルパー関数。
translate/translate.py

翻訳モデルを訓練して実行するバイナリ。
 

Sequence-to-Sequence 基本

基本的な sequence-to-sequence モデルは、Cho et al., 2014 (pdf) で紹介されているように、2つの RNN から成ります: 入力を処理するエンコーダと出力を生成するデコーダです。この基本的なアーキテクチャは下のようなものです :

上の図の各ボックスは RNN のセルを表しています、より一般的には GRU セルまたは LSTM セルです (それらの説明については RNN チュートリアル を参照)。エンコーダとデコーダは重みを共有することができますがあるいは、より一般的に、異なるパラメータセットを使用することもできます。多層セルは sequence-to-sequence モデルでも成功的に使用されています。例えば翻訳のために Sutskever et al., 2014 (pdf)。

上に描かれた基本モデルでは、デコーダに渡される唯一のものとして、全ての入力は固定サイズの状態ベクタにエンコードされなければなりません、デコーダにより直接的な入力へのアクセスを可能にするため、attention メカニズムが Bahdanau et al., 2014 (pdf) で紹介されました。私たちは attention メカニズムの詳細には踏み込みませんが(論文参照)、それはデコーダに全てのデコーディング・ステップで入力をピークすることを許可するとだけ言っておけば十分でしょう。LSTM セルと attention メカニズムによる多層 sequence-to-sequence ネットワークはこのように見えます。

 

TensorFlow seq2seq ライブラリ

上で見たように、多くの異なる sequence-to-sequence があります。これらのモデルの各々は異なる RNN セルを使用することができますが、これら全てはエンコーダ入力とデコーダ入力を受け取り、これは TensorFlow seq2seq ライブラリ (models/rnn/seq2seq.py) の I/F への興味を起こさせます。
(訳注: 実装の実態は tensorflow/python/ops/seq2seq.py に移されています。)

基本的な RNN encoder-decoder sequence-to-sequence モデルは次のように動作します。

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 は多層セルを構築し、セル入力または出力にドロップアウトを追加し、あるいは他の変換を行なうためのラッパーを提供します。

basic_rnn_seq2seq の呼び出しの戻り値は2つです: outputs と states です。両方とも decoder_inputs と同じ長さのテンソルのリストです。もちろん、outputs は各時間ステップのデコーダの出力に相当します。上の最初の図ではそれは W, X, Y, Z, EOS でした。返された states は全ての時間ステップにおけるデコーダの内部状態を表します。

sequence-to-sequence モデルの多くのアプリケーションでは、時刻 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 では、全ての入力は(both encoder_inputs と decoder_inputs 両方について)離散した値を表す整数テンソルです。これらは密な表現に埋め込まれます(埋め込みについての詳細は ベクタ表現チュートリアル を参照)、しかしこれらの埋め込みを構築するには、次のように現れる離散シンボルの最大値を指定する必要があります: エンコーダ側の num_encoder_symbols とデコーダ側の num_decoder_symbols です。

上記の呼び出しでは、feed_previous を False に設定しました。これはデコーダが decoder_inputs テンソルを提供されたものとして使用することを意味しています。もし feed_previous を True に設定した場合には、デコーダは decoder_inputs の最初の要素だけを使います。このリストからの他の全てのテンソルは無視され、代わりにエンコードの前の出力が使用されます。これは、私たちの翻訳モデルで翻訳をデコードするために使われますが、モデルを自身の誤りに対して堅固にするために、訓練時にもまた使うことができます。Bengio et al., 2015 (pdf) と同様です。

上で使われているもう一つの重要な引数は output_projection です。指定されない場合は、埋め込みモデルの出力は、各々の生成されたシンボルのロジットを表す、バッチ・サイズ x num_decoder_symbols の shape のテンソルになります。大規模出力語彙でモデルを訓練する時、すなわち num_decoder_symbols が大きい時、これらの大規模なテンソルをストアするのは実践的ではありません。代わりに、より小さな出力テンソルを返すのが良いです。これらは output_projection を使って後で大規模出力テンソルに射影されます。これは Jean et. al., 2014 (pdf) に記述されているように、私たちの seq2seq モデルを sampled softmax 損失とともに使うことを可能にします。

basic_rnn_seq2seq と embedding_rnn_seq2seq に加えて、seq2seq.py には2、3の更なる sequence-to-sequence モデルがありますので、見てください。それらは全て同様のインターフェイスを持ちますので、詳細は説明しません。下の翻訳モデルのためには embedding_attention_seq2seq を使います。

 

ニューラル翻訳モデル

sequence-to-sequence モデルの核が models/rnn/seq2seq.py の関数で構築される一方で、言及するに値する2、3のトリックがまだあります。それは models/rnn/translate/seq2seq_model.py における翻訳モデルで使われます。

Sampled softmax と出力射影 (projection)

上で既に述べたような理由で、大規模出力語彙を処理するために sampled softmax を使いたいです。それからデコードするために、出力射影を追跡する必要があります。sampled softmax 損失と出力射影の両者は 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) がターゲット語彙サイズよりも小さい場合にのみ sampled softmax を構築することに注意してください。512 より小さい語彙のためには標準 softmax 損失だけを使うのはより良い考えでしょう。

そして、見て取れるように、出力射影を構築します。それはペアで、重み行列とバイアスベクタから成ります。使用された場合、rnn セルは shape batch-size x size の shape のベクタを返します、batch-size x target_vocab_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 とパディング

sampled softmax に加えて、私たちの翻訳モデルはまた bucketing を使います、これは異なる長さの文を効率的に処理するための方法です。まず問題をクリアにしましょう。英語からフランス語に翻訳する時、入力として異なる長さ L1 の英語の文があるでしょう、そして出力として異なる長さの L2 のフランス語の文があります。英語の文は encoder_inputs として渡され、フランス語の文は(GO シンボルで prefix される)decoder_inputs として現れますから、原理上は英語とフランス語の文の長さの全てのペア (L1, L2+1) のために seq2seq モデルを作成すべきです。これは多くの非常に類似したサブグラフから成る巨大なグラフという結果になるでしょう。一方で、特殊な PAD シンボルで全ての文をパディングすることもできます。そうすればパディングされた長さのため、一つの seq2seq モデルだけが必要になります。しかしより短い文では私たちのモデルは非効率的となり、必要のない、多くの PAD シンボルをエンコードしてデコードすることになります。長さの全てのペアのためのグラフを構築することと単一の長さにパディングすることの間の妥協として、幾つかのバケツを使いそして個々の文をその長さを超えてバケツの長さにパディングします。

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 (pdf) で示されています。すべてを理解するために、"I go." という文があり、入力として ["I", "go", "."] としてトークン分割されそして出力は "Je vais." という文で、["Je", "vais", "."] とトークン分割されていることを想像してください。それは (5, 10) バケツに入れられます、[PAD PAD "." "go" "I"] を表すエンコーダ入力と デコーダ入力 [GO "Je" "vais" "." EOS PAD PAD PAD PAD PAD] と共に。

 

Let's Run It

上述のモデルを訓練するためには、大規模な英仏コーパスが必要です。私たちは訓練のために WMT'15 Website からの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

訓練コーパスを準備するためには約 18 GB のディスク空間を必要とし数時間かかります。それはアンパックされ、語彙ファイルは data_dir で作成され、そしてコーパスはトークン分割されて整数 id に変換されます。語彙サイズを決定するパラメータに注意してください。上の例では、40 K のもっとも一般的なもの以外の全ての単語は未知の単語を表す UNK トークンに変換されます。もし語彙サイズを変更するのであれば、バイナリはコーパスをトークン-id に再度マップし直します。

データが準備された後、訓練が始まります。translate(.py) のデフォルト・パラメータは非常に大きな値に設定されています。長時間かけて訓練されたラージ・モデルは良い結果を与えますが、時間がかかり過ぎたり貴方の 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 メモリに適合することができるか見つけられます。訓練の間、steps_per_checkpoint ステップ毎にバイナリは新しいステップから統計情報をプリントアウトします。デフォルト・パラメータ(サイズ 1024 の 3 層)では、最初のメッセージはこのようなものです。

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 秒以下かかること、各バケツのための訓練セット上の perplexity と開発セット上の perplexity が見て取れます。約 30K ステップ後、短い文(バケツ 0 と 1)の perplexity が一桁に入ることを見ます。訓練コーパスは ~ 22M 文を含みますので、一つの epoch(訓練データを一巡すること) はバッチサイズ 64 で約 340K ステップかかります。この時点でモデルは --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 ?
 

What Next?

上の例は貴方自身の英仏翻訳機をどのようにビルドするかを示しています、端から端まで。それを実行してモデルがどのように遂行するかを貴方自身で見てください。それは reasonable な品質を持つ一方で、デフォルト・パラメータはベストな翻訳モデルを提示してはいません。貴方が改良できる2、3のことがここにあります。

まず最初に、私たちは非常にプリミティブな tokenizer (トークン解析器) - data_utils の basic_tokenizer 関数を使用しています。より良い tokenizer は WMT'15 Web サイト で見つかります。その tokenizer、そしてより大規模な語彙を使用することは、翻訳を改良します。

また、翻訳モデルのデフォルト・パラメータはチューニングされていません。学習率、減衰を変更してみる、あるいはモデルの重みを違う方法で初期化してみることができます。また seq2seq_model.py のデフォルト GradientDescentOptimizer をより進んだもの、AdagradOptimizer のようなものに変更することもできます。これらのことにトライしてそれらがどのように結果を改善するか見てみましょう。

最後に、上で提示されたモデルは翻訳だけではなく、任意の sequence-to-sequence タスクに使用できます。例えば解析木を生成するためにシークエンスをツリーに変換することを望む場合、Vinyals & Kaiser et al., 2014 (pdf) で示されてるように、上と同じモデルが最高水準の結果を与えることができます。従って貴方自身の翻訳器をビルドできるだけでなく、解析器、チャットボット、あるいは思いつく任意のプログラムをビルドすることもできます。 Experiment!

 

以上

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