TensorFlow : Guide : 低位 API : 変数 (Variables) (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
更新日時 : 07/14/2018
作成日時 : 09/12/2017
* TensorFlow 1.9 でドキュメント構成が変わりましたので調整しました。
* 本ページは、TensorFlow 本家サイトの Guide – Low Level APIs – Variables を翻訳した上で
適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、適宜、追加改変している場合もあります。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
TensorFlow 変数 (= variable) は貴方のプログラムで操作される共有され、永続的な状態を表わすための最良の方法です。
変数は tf.Variable クラスを通して操作されます。tf.Variable はテンソルを表わしその値はその上で ops (演算子) を実行することで変更されます。tf.Tensor オブジェクトとは異なり、tf.Variable は単一の session.run 呼び出しのコンテキストの外側に存在します。
内部的には、tf.Variable は永続的なテンソルをストアします。特定の ops はこのテンソルの値を読んで変更することを可能にします。これらの変更は複数の tf.Session に渡って見ることができ (= visible)、従って複数のワーカーが (一つの) tf.Variable に対して同じ値を見ることができます。
変数を作成する
変数を作成する最良の方法は tf.get_variable 関数を呼び出すことです。この関数は変数名を指定することを要求します。この名前は、モデルをチェックポイントしてエクスポートするとき他のレプリカによって同じ変数にアクセスするため、更にこの変数の値に名前をつけるために使用されます。tf.get_variable はまた同じ名前の以前に作成された変数の再利用を可能にし、層を再利用するモデルを定義することを容易にします。
tf.get_variable で変数を作成するためには、単に名前と shape を提供します :
my_variable = tf.get_variable("my_variable", [1, 2, 3])
これは “my_variable” という名前の変数を作成します、これは shape [1, 2, 3] を持つ 3-次元テンソルです。この変数は、デフォルトでは、dtype tf.float32 を持ちそしてその初期値は tf.glorot_uniform_initializer を通してランダム化されます。
tf.get_variable へのオプションで dtype と initializer を指定することもできます。例えば :
my_int_variable = tf.get_variable("my_int_variable", [1, 2, 3], dtype=tf.int32, initializer=tf.zeros_initializer)
TensorFlow は多くの便利な intializer を提供しています。或いは、tf.Tensor の値を持つように tf.Variable を初期化しても良いです。例えば :
other_variable = tf.get_variable("other_variable", dtype=tf.int32, initializer=tf.constant([23, 42]))
initializer が tf.Tensor であるとき変数の shape は指定するべきではないことに注意してください、initializer テンソルの shape が使用されますので。
変数コレクション
TensorFlow プログラムの離れた (= disconnected) 部分が変数を作成することを望むかもしれませんので、それら総てにアクセスする単一の方法を持つことは時々有用です。この理由のために TensorFlow はコレクション (= collections) を提供します、これは tf.Variable インスタンスのような、テンソルまたは他のオブジェクトの名前付きリストです。
デフォルトでは総ての tf.Variable は次の2つのコレクションに置かれます : * tf.GraphKeys.GLOBAL_VARIABLES — 複数のデバイスに渡って共有される変数、* tf.GraphKeys.TRAINABLE_VARIABLES — TensorFlow がそのために勾配を計算する変数。
変数を trainable にすることを望まないのであれば、代わりにそれを tf.GraphKeys.LOCAL_VARIABLES コレクションに追加してください。例えば、次のスニペットは my_local という名前の変数をこのコレクションにどのように追加するかを示します :
my_local = tf.get_variable("my_local", shape=(), collections=[tf.GraphKeys.LOCAL_VARIABLES])
或いは、tf.get_variable への引数として trainable=False を指定することもできます :
my_non_trainable = tf.get_variable("my_non_trainable", shape=(), trainable=False)
貴方自身のコレクションを使用することもできます。任意の文字列が正当なコレクション名で、明示的にコレクションを作成する必要はありません。変数を作成後に変数 (または任意の他のオブジェクト) をコレクションに追加するには、tf.add_to_collection を呼び出します。例えば、次のコードは my_local という名前の既存の変数を my_collection_name という名前のコレクションに追加します :
tf.add_to_collection("my_collection_name", my_local)
そしてコレクション内に置いた総ての変数 (または他のオブジェクト) のリストを取得するためには、以下を使用できます :
tf.get_collection("my_collection_name")
デバイス配置
ちょうど任意の他の TensorFlow 演算のように、変数を特定のデバイス上に置くことができます。例えば、次のスニペットは v という名前の変数を作成してそれを2番目の GPU デバイスに置きます :
with tf.device("/gpu:1"): v = tf.get_variable("v", [1])
変数について正しいデバイスにあることは分散セッティングでは特に重要です。たまたま変数をパラメータ・サーバの代わりにワーカーに置くことは、例えばですが、訓練を酷くスローダウンさせるかまたは、最悪の場合には、各ワーカーを軽率にも各変数のそれ自身の独立したコピーで推し進めます。このため tf.train.replica_device_setter を提供します、これは変数を自動的にパラメータ・サーバに置くことができます。例えば :
cluster_spec = { "ps": ["ps0:2222", "ps1:2222"], "worker": ["worker0:2222", "worker1:2222", "worker2:2222"]} with tf.device(tf.train.replica_device_setter(cluster=cluster_spec)): v = tf.get_variable("v", shape=[20, 20]) # this variable is placed # in the parameter server # by the replica_device_setter
変数を初期化する
変数が使用できる前に、それは初期化されなければなりません。もし貴方が低位 TensorFlow API (つまり、貴方自身のグラフとセッションを明示的に作成しているとき) 、変数を明示的に初期化しなければなりません。tf.contrib.slim, tf.estimator.Estimator そして Keras のような多くの高位フレームワークはモデルを訓練する前に貴方のために変数を自動的に初期化します。
明示的な初期化は別の点で有用です、何故ならばそれはチェックポイントからモデルをリロードするときに潜在的に高価な initializer を再実行しないことを可能にして、そして分散セッティングでランダムに初期化された変数が共有されるときに決定論を可能にします。
訓練が始まる前に総ての trainable 変数をまとめて初期化するためには tf.global_variables_initializer() を呼び出します。この関数は tf.GraphKeys.GLOBAL_VARIABLES コレクションの総ての変数を初期化する責任のある単一の演算を返します。この演算の実行は総ての変数を初期化します。例えば :
session.run(tf.global_variables_initializer()) # Now all variables are initialized.
貴方自身で変数を初期化する必要がある場合は、変数の initializer 演算を実行することができます。例えば :
session.run(my_variable.initializer)
どの変数がまだ初期化されていないかを尋ねることもできます。例えば、次のコードはまだ初期化されていない総ての変数の名前を出力表示します :
print(session.run(tf.report_uninitialized_variables()))
デフォルトでは tf.global_variables_initializer は変数が初期化される順序を指定しないことに注意してください。それゆえに、変数の初期値が他の変数の値に依存する場合、エラーを得ることがあるかもしれません。
総ての変数が初期化されていないコンテクストで変数の値を使用するときはいつでも (say, もし他の変数を初期化する間に変数の値を使用する場合)、variable の代わりに variable.initialized_value() を使用することが最善です :
v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer()) w = tf.get_variable("w", initializer=v.initialized_value() + 1)
変数を使用する
TensorFlow グラフで tf.Variable の値を使用するためには、単に標準の tf.Tensor のようにそれを取り扱います :
v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer()) w = v + 1 # w is a tf.Tensor which is computed based on the value of v. # Any time a variable is used in an expression it gets automatically # converted to a tf.Tensor representing its value.
変数に値を割り当てるためには、tf.Variable クラスのメソッド assign, assign_add, そして フレンドを使用します。例えば、これらのメソッドをどのように呼び出すかをここに示します :
v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer()) assignment = v.assign_add(1) tf.global_variables_initializer().run() assignment.run()
たいていの TensorFlow optimizer は勾配降下的なアルゴリズムを効率的に更新する特別な ops を持ちます。どのように optimizer を使用するかの説明については tf.train.Optimizer を見てください。
変数は変更可能 (= mutable) ですから時間内の任意のポイントで変数の値のどのバージョンが使用されているかを知ることは時に有用です。何かが起きた後に変数の値の再読み込み (= re-read) を強制的に行なうためには、tf.Variable.read_value が使用できます。例えば :
v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer()) assignment = v.assign_add(1) with tf.control_dependencies([assignment]): w = v.read_value() # w is guaranteed to reflect v's value after the # assign_add operation.
変数を共有する
TensorFlow は変数を共有する2つの方法をサポートします :
- 明示的に tf.Variable オブジェクトを回りに渡す。
- 暗黙的に tf.Variable オブジェクトを tf.variable_scope 内にラップする。
変数を明示的に渡し回るコードが非常に明白である間、その実装において変数を暗黙的に使用する TensorFlow 関数を書くことは時々便利です。tf.layer からの関数層の殆どはこのアプローチを使用しています、tf.metrics, そして2、3の他のライブラリ・ユテリティもそうです。
変数スコープは、変数を暗黙的に作成して使用する関数を呼び出す時に、変数の再利用を制御することを可能にします。 それらはまた変数に階層的で (人間に) 理解できる方法で名前を付けることも可能にします。
例えば、畳み込み / relu 層を作成する関数を書いていると仮定しましょう :
def conv_relu(input, kernel_shape, bias_shape): # Create variable named "weights". weights = tf.get_variable("weights", kernel_shape, initializer=tf.random_normal_initializer()) # Create variable named "biases". biases = tf.get_variable("biases", bias_shape, initializer=tf.constant_initializer(0.0)) conv = tf.nn.conv2d(input, weights, strides=[1, 1, 1, 1], padding='SAME') return tf.nn.relu(conv + biases)
この関数は短い名前 weights と biases を使用していて、これは明確にするには良いです。実際のモデルでは、けれども、多くのそのような畳み込み層を望みます、そしてこの関数を繰り返し呼び出すと動作しません :
input1 = tf.random_normal([1,10,10,32]) input2 = tf.random_normal([1,20,20,32]) x = conv_relu(input1, kernel_shape=[5, 5, 1, 32], bias_shape=[32]) x = conv_relu(x, kernel_shape=[5, 5, 32, 32], bias_shape = [32]) # This fails.
望まれる挙動が不明瞭なので (新しい変数を作成するか既存のものを再利用するか?) TensorFlow は失敗します。異なるスコープで conv_relu を呼び出すと、けれども、新しい変数を作成することを望むことを明らかにします :
def my_image_filter(input_images): with tf.variable_scope("conv1"): # Variables created here will be named "conv1/weights", "conv1/biases". relu1 = conv_relu(input_images, [5, 5, 1, 32], [32]) with tf.variable_scope("conv2"): # Variables created here will be named "conv2/weights", "conv2/biases". return conv_relu(relu1, [5, 5, 32, 32], [32])
変数が共有されることを望む場合、2つのオプションがあります。最初に、reuse=True を使用して同じ名前でスコープを作成します :
with tf.variable_scope("model"): output1 = my_image_filter(input1) with tf.variable_scope("model", reuse=True): output2 = my_image_filter(input2)
再利用を発生するために scope.reuse_variables() を呼び出すことも可能です :
with tf.variable_scope("model") as scope: output1 = my_image_filter(input1) scope.reuse_variables() output2 = my_image_filter(input2)
スコープの正確な文字列名に依存することは危険な感じがしますので、他の一つに基づき変数スコープを初期化することもまた可能です。
with tf.variable_scope("model") as scope: output1 = my_image_filter(input1) with tf.variable_scope(scope, reuse=True): output2 = my_image_filter(input2)
以上