Sonnet : モジュール指向 TensorFlow 高位ライブラリ
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 04/13/2017
* 本ページは、github 上の sonnet の README.md の前半を動作確認・翻訳した上で適宜、補足説明したものです:
https://github.com/deepmind/sonnet/blob/master/README.md
* DeepMind 社の Sonnet ページは :
Open sourcing Sonnet – a new library for constructing neural networks
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
Sonnet は TensorFlow 上に構築された、複雑なニューラルネットワークを構成するための ライブラリです。
インストール説明
Sonnet をインストールするためには、TensorFlow ヘッダファイルに対して bazel を使用してライブラリをコンパイルする必要があります。TensorFlow installation instructions に従って TensorFlow はインストールされているものとします。
このインストールは Linux/Mac OS X と Python 2.7 と互換性があります。インストールされた TensorFlow のバージョンは少なくとも 1.0.1 でなければなりません。Sonnet のインストールは TensorFlow の native pip install に加えて、virtualenv installation mode もサポートします。
bazel をインストールする
bazel の最近のバージョン (>= 0.4.5 ) を持っていることを確かめてください。そうでないならば、これらの指示 に従ってください。
(virtualenv TensorFlow インストール) virtualenv を activate する
virtualenv を使用しているならば、残りのインストールのために virtualenv を activate してください、そうでないならばこのステップはスキップしてください :
$ source $VIRTUALENV_PATH/bin/activate # bash, sh, ksh, or zsh $ source $VIRTUALENV_PATH/bin/activate.csh # csh or tcsh
TensorFlow ヘッダを cofigure する
最初に Sonnet ソースコードを TensorFlow をサブモジュールとして clone します :
$ git clone --recursive https://github.com/deepmind/sonnet
そして configure を呼び出します :
$ cd sonnet/tensorflow $ ./configure $ cd ../
TensorFlow configuration の間は提示されたデフォルトを選択できます。
【注意】これは TensorFlow の既存のインストールを変更するものではありません。このステップは Sonnet が TensorFlow ヘッダに対してビルドできるようにするために必要です。
インストーラをビルドして実行する
一時ディレクトリに wheel ファイルを作成するためにインストール・スクリプトを実行します :
$ mkdir /tmp/sonnet $ bazel build --config=opt :install $ ./bazel-bin/install /tmp/sonnet
生成された wheel ファイルを pip install します :
$ pip install /tmp/sonnet/*.whl
Sonnet が既にインストールされているならば、wheel ファイルで pip install を呼び出す前に uninstall します :
$ pip uninstall sonnet
例えば、 resampler op を試してみることによって Sonnet が正しくインストールされたか検証できます :
$ cd ~/ $ python >>> import sonnet as snt >>> import tensorflow as tf >>> snt.resampler(tf.constant([0.]), tf.constant([0.]))
期待される出力は以下のようなものです :
けれども、もし ImportError が上がるならば C++ コンポーネントが見つかっていません。clone されたソースコードをインポートしていないこと (i.e. clone されたレポジトリの外で python を呼び出すこと) そして wheel ファイルをインストールする前に Sonnet を uninstall したことを確認してください。
使用例
次のコードは線形モデルを構築してそれを複数の入力に接続します。変数 (i..e 線形変換の重みとバイアス) は自動的に共有されます。
import sonnet as snt train_data = get_training_data() test_data = get_test_data() # モジュールを構築して、必要な構成を提供します。 linear_regression_module = snt.Linear(output_size=FLAGS.output_size) # モジュールを幾つかの入力に接続します、何回でも。 train_predictions = linear_regression_module(train_data) test_predictions = linear_regression_module(test_data)
更なる使用例は ここ で見つかります。
一般的な原理
Sonnet の主要な原理は最初にニューラルネットワークのある部分を表す Python オブジェクトを構築してこれらのオブジェクトを TensorFlow 計算グラフに別々に接続することです。オブジェクトは sonnet.AbstractModule のサブクラスでそれらはそういうものとしてモジュールとして参照されます。
モジュールはグラフに複数回接続されるかもしれません、そしてそのモジュールで宣言された任意の変数は続く接続呼び出しで自動的に共有されます。変数スコープ名を指定したり reuse= フラグを使用したりすることを含む、変数共有を制御する TensorFlow の低位な局面はユーザからは抽象化され離されます。
構成と接続の分離は高水準なモジュール、i.e. 他のモジュールをラップするモジュール 、の簡単な構築を可能にします。例えば、BatchApply モジュールはテンソルの leading dimension の数を single dimension にマージし、提供されたモジュールを接続し、そして入力に適合するように結果の leading dimension を分割します。コンストラクト時には、内部モジュールは BatchApply コンストラクタに引数として渡されます。実行時には、モジュールは最初に入力上の reshape 操作を実行して、コンストラクタに渡されたモジュールを適用し、そして reshape 操作を逆に実行します。
Python オブジェクトでモジュールを表す更なる優位点はそれは必要なところで定義される追加の方法を可能にすることです。これの例は、重み共有を保持する一方で、構築後に様々な方法で接続されるかもしれないモジュールです。例えば、生成モデルのケースにおいて、モデルからサンプリングしたり与えられた観測の log 確率を計算することを望むかもしれません。両者の接続を同時に持つことは重み共有を要求し、 そしてこれらの方法は同じ変数に依存します。変数はオブジェクトに概念的に所有され、モジュールの異なるメソッドで使用されます。
Sonnet を import する
Sonnet を import するために推奨される方法は snt と命名した変数にエイリアスすることです :
import sonnet as snt
すると全てのモジュールは名前空間 snt の下にアクセス可能となり、この文書の残りでは簡潔さのために snt を使用します。
次のコードは他のモジュールから成るモジュールを構築します :
import sonnet as snt # データは複数の入力経由で由来し、それぞれに同じモデルを適用するためには # 変数共有を使用することが必要です。 train_data = get_training_data() test_data = get_test_data() # 多層パーセプトロン (Multi Layer Perceptron) を形成するために、2つの線形モデルを作成します。 # TensorBoard / 他のツールで解釈可能な変数名を提供するために、 # デフォルト名をオーバーライドします。 (それは 'linear', 'linear_1' という結果になります) lin_to_hidden = snt.Linear(output_size=FLAGS.hidden_size, name='inp_to_hidden') hidden_to_out = snt.Linear(output_size=FLAGS.output_size, name='hidden_to_out') # Sequential は提供されるデータに対して連続的に多くの内部モジュールや ops を適用する # モジュールです。tanh のような生の TF ops は構築されたモジュールと互換的に利用可能です、 # それらは変数を含みませんので。 mlp = snt.Sequential([lin_to_hidden, tf.sigmoid, hidden_to_out]) # sequential をグラフに接続します、何回でも。 train_predictions = mlp(train_data) test_predictions = mlp(test_data)
次のコードは Linear モジュールに初期化と正則化を追加しています :
import sonnet as snt train_data = get_training_data() test_data = get_test_data() # 重みとバイアスへの初期化と正則化。 initializers={"w": tf.truncated_normal_initializer(stddev=1.0), "b": tf.truncated_normal_initializer(stddev=1.0)} regularizers = {"w": tf.contrib.layers.l1_regularizer(scale=0.1), "b": tf.contrib.layers.l2_regularizer(scale=0.1)} linear_regression_module = snt.Linear(output_size=FLAGS.output_size, initializers=initializers, regularizers=regularizers) # Connect the module to some inputs, any number of times. train_predictions = linear_regression_module(train_data) test_predictions = linear_regression_module(test_data) # ... # 正則化ロスを取得してそれらを一緒に追加します。 graph_regularizers = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES) total_regularization_loss = tf.reduce_sum(graph_regularizers) # ... # 損失を最小化する時、正則化損失もまた最小化します。 train_op = optimizer.minimize(loss + total_regularizer_loss)
貴方自身のモジュールを定義する
snt.AbstractModule から継承する
モジュールを定義するためには、snt.AbstractModule から継承した新しいクラスを作成します。貴方のクラスのコンストラクタは、そのモジュールの演算を定義する任意の構成を受け取って、private であることを示す、アンダースコアで prefix されたメンバー変数で保持します。
スーパークラス・コンストラクタを呼び出す
コンストラクタが行なう最初のことはスーパークラス・コンストラクタを呼び出し、そのモジュールの名前を渡すことです。
- これを行なうことを忘れた場合、変数共有は壊れます。name kwarg はリストの最後の一つとして、クラス名のスネークケース版であるデフォルト値とともに常に提供されるべきです。
class MyMLP(snt.AbstractModule): """Docstring for MyMLP.""" def __init__(self, hidden_size, output_size, nonlinearity=tf.tanh, name="my_mlp"): """Docstring explaining __init__ args, including types and defaults.""" super(MyMLP, self).__init__(name) self._hidden_size = hidden_size self._output_size = output_size self._nonlinearity = nonlinearity
_build() メソッドを実装する
提供されなければならない唯一の他のメソッド実装は _build() です。これはモジュールが tf.Graph に接続される時にいつでも呼び出されます。それは幾つかの入力を受け取り、それは空か、単一のテンソルか、あるいは複数のテンソルを含む幾つかの任意の構造です。複数のテンソルはタプルか namedtuple で提供され、それの要素は順番にテンソルか更なるタプル / namedtuple かです。多くの入力テンソルはバッチ次元を必要とし、もしテンソルがカラー・チャネルを持つならばそれは最後の次元でなければなりません。多くの場合でライブラリは明示的に妨げはしませんが、リストと辞書の使用はサポートされません、何故ならばこれらの構造の mutability (変更可能な性質) は微妙なバグにつながるからです。
# Following on from code snippet above.. def _build(self, inputs): """Compute output Tensor from input Tensor.""" lin_x_to_h = snt.Linear(output_size=self._hidden_size, name="x_to_h") lin_h_to_o = snt.Linear(output_size=self._output_size, name="h_to_o") return lin_h_to_o(self._nonlinearity(lin_x_to_h(inputs)))
_build メソッドは次のプロセスの任意の一つあるいは全てを含むでしょう :
- 内部モジュールの構築と利用
- 既に存在し、コンストラクタに渡されたモジュールの利用
- 変数を直接作成する。
変数を貴方自身で作成する場合、tf.get_variable でそれらを作成することは重要です。tf.Variable コンストラクタを直接呼び出すことは最初にモジュールが接続された時だけは動作しますが、2回目の呼び出しではエラーメッセージ “Trainable variable created when calling a template after the first time” を受け取るでしょう。
上のサンプルのモジュールは別々に作成され、様々な構成を渡して、最後の行がそれら全てをグラフに接続します。return 行は右から左へ読まれるべきです – 入力テンソルは最初の Linear, lin_x_to_h,に渡され、その出力はコンストラクタで保持される非線形が何であれ、その出力はその結果を生成するために他の Linear を通り抜けます。内部 Linear インスタンスに短い意味のある名前を与えていることに注意してください。
上の非線形は生の TF op、eg tf.tanh あるいは tf.sigmoid、あるいは Sonnet モジュールのインスタンスでもかまいません。Python 標準との調和を保つために、これを明示的にはチェックしないかもしれません、そしてそのために _build が呼ばれた時にエラーを受け取るかもしれません。また __init__ 内で制約とサニティーチェックを追加することも許容します。
上のコードでは、_build() が呼び出されるたびに snt.Linear の新しいインスタンスが生成されることに注意してください、そしてこれは異なる、非共有な変数を作成すると考えるかもしれません。そのようなことはありません – MLP インスタンスがグラフに何度接続されようとも、4 変数 (各 Linear に 2) だけが作成されます。これがどのように動作するかは低位 TF 詳細で、変更に従います – 詳細は [tf.variable_op_scope] を見てください。
サブモジュールはどこで宣言されるべきか?
モジュール eg Sequential etc. は、外部的に既にコンストラクトされた他のモジュールを受け取り使用するかもしれないことに注意してください。このセクションで議論するサブ・モジュールは他のモジュールのコード内部でコンストラクトされる任意のモジュールで、これは Parent Module として参照しましょう。例として LSTM があり、そこでは多くの実装は、重みを含む、内部的に1つまたはそれ以上の Linear モジュールをコンストラクトします。
サブモジュールは _build() で作成されることが奨励されます。このようにそれを行なうことは変数スコープの正しいネストを得ることを意味します、例えば :
class ParentModule(snt.AbstractModule): def __init__(self, hidden_size, name="parent_module"): super(ParentModule, self).__init__(name=name) self._hidden_size = hidden_size def _build(self, inputs): lin_mod = snt.Linear(self._hidden_size) # Construct submodule... return tf.relu(lin_mod(inputs)) # then connect it.
前半の翻訳はここまです。Recurrent Modules 以後の後半の翻訳は次回。
以上