TensorFlowの変数の扱い方は、一般的なプログラミング言語の変数とは随分異なります。TensorFlowや計算グラフを使用したディープラーニングライブラリを使い始めの方は、慣れるのに時間がかかるかもしれません。

本記事では、TensorFlowの変数の使い方に焦点を当てて、

  • 計算グラフのメリット
  • 変数の初期化方法
  • 変数の使い回し方
  • スコープの切り方
  • 変数の種類

について詳しく見ていきます。TensorFlowの変数を使いこなせるようになると、独自のロジックも組むことが出来るようになります。

TensorFlowの計算グラフのメリット

TensorFlowは計算グラフという表現を使って計算を実行します。計算グラフは、ノードとエッジで複雑な計算を表現する手法で、以下のようなメリットがあります。

  • 事前に計算処理が最適化されて、高速に計算できるようになる
  • 複数のデバイス間で並列計算することができる
  • 遅延評価されるので、不要な計算が減る

計算グラフとTensorFlowの仕組みの詳細は以下の記事で解説しています。

ビッグデータを分散学習するDeep LearningライブラリTensorFlowとは /tensorflow/2016/12/30/what-is-tensorflow.html

tf.Variableの作成方法

tf.Variableは、計算グラフ上の変数のシンボルになります。一般的なプログラミング言語の変数とは少し使い方が異なるので注意が必要になります。

一般的なPythonコードであれば、以下のように変数に代入して演算することが出来ます。

In [1]: x = 3

In [2]: y = 3 * 5

In [3]: y
Out[3]: 15

一見すると、上記のコードをTensorFlowで書くと次のようになりそうだと考えられます。

In [1]: import tensorflow as tf

In [2]: x = tf.Variable(3, name='x')

In [3]: y = x * 5

In [4]: print(y)
Tensor("mul:0", shape=(), dtype=int32)

上記のコードでは、tf.Variableを使用して初期値が3の変数を宣言しています。次に宣言したxの変数と5を掛けた結果保持する変数yを定義します。この変数を出力すれば15が返ってきそうな気がしますが、yをそのまま出力すると上記のように、<tf.Tensor 'mul:0' shape=() dtype=int32>のようにTensorが出力されるはずです。

実際には、15を出力するためには次のようになります。

In [1]: import tensorflow as tf

In [2]: x = tf.Variable(3, name='x')

In [3]: y = x * 5

In [4]: sess = tf.InteractiveSession()

In [5]: sess.run(tf.global_variables_initializer())

In [6]: sess.run(y)
Out[6]: 15

TensorFlowでは、tf.Sessionを通して計算グラフを構築して実行しなければなりません。また、tf.global_variables_initializerを使用して、計算グラフ内の変数を初期化する必要があります。

少々面倒のようにも感じますが、変数の作成方法を学んでいきましょう。

tf.Variable

基本的な変数作成方法は、tf.Variableです。APIドキュメントは以下のようになっています。

tf.Variable(initial_value=None, trainable=True,
collections=None, catching_device=None, name=None,
variable_def=None, dtype=None, expected_shape=None,
import_scope=None)

params:

パラメータ名 概要
initial_value Tensorに変換可能な型 変数の初期値を指定します。
trainable bool Trueであれば、Optimizerで使用されるGraphKeys.TRAINABLE_VARIABLESに追加されます。
collections list グラフのCollection Keyリスト、デフォルトは[GraphKeys.GLOBAL_VARIABLES]。
validate_shape bool Falseにすると、型や形状チェックしなくなります。
caching_device string 変数を読み込む際に、キャッシュするデバイスを指定します。
name string 変数の名前。デフォルトでは自動でユニークな名前を割り当てます。
validable_def string VariableDefプロトコルバッファ。Noneでなければ、validable_defに合わせて再作成します。
dtype type 指定されていれば、dtypeに合わせて初期値が変換されます。
expected_shape TensorShape 指定されていれば、expected_shapeに合わせて形状変換されます。
import_scope string 追加する名前空間。

returns:

変数のTensorを返します。

パラメータがたくさんありますが、指定する必要があるのは、第一引数の初期値initial_valueのみです。以下のように使用することができ、assignメソッドで代入することもできます。

In [1]: import tensorflow as tf

In [2]: v = tf.Variable(3, name='v')

In [3]: v2 = v.assign(5)

In [4]: sess = tf.InteractiveSession()

In [5]: sess.run(v.initializer)

In [6]: sess.run(v)
Out[6]: 3

In [7]: sess.run(v2)
Out[7]: 5

tf.get_variable

tf.get_variableは、既に存在すれば取得し、なければ変数を作成する関数です。tf.Variableとは違い、変数値ではなく、第一引数に変数の名前を指定することが必須となっています。

In [1]: import tensorflow as tf

In [2]: init = tf.constant_initializer([5])

In [3]: x = tf.get_variable('x', shape=[1], initializer=init)

In [4]: sess = tf.InteractiveSession()

In [5]: sess.run(x.initializer)

In [6]: sess.run(x)
Out[6]: array([ 5.], dtype=float32)

変数の使い回し方

では、tf.Variabletf.get_variableをどのように使い分ければいいのでしょうか。この違いを知るためには、TensorFlowの計算グラフ内部の名前空間による階層構造を理解する必要があります。

スコープ

TensorFlowの名前空間は2種類あります。tf.variable_scopetf.name_scopeです。これまで、計算グラフに変数を追加する方法を見てきましたが、大量の変数が作成されていると、計算グラフが汚くなってしまい、構造が把握しづらくなってしまいます。

例として、4個のグループに分けた変数を各25個ずつ作成してみましょう。

for i in range(4):
    for j in range(25):
         v = tf.Variable(1, name='{}-{}'.format(i, j))

こちらのコードで生成した計算グラフを出力すると、以下のようになります。

変数100個の計算グラフ

変数100個が並列に並び、どこでグループ化されているのかが分かりません。ニューラルネットワークの変数は、莫大な数になることもあります。計算グラフを綺麗に分かりやすくするためには、スコープを区切って階層構造をつくります。スコープを使って大きく4個にグルーピングするために、次のコードで試してみます。

for i in range(4):
    with tf.variable_scope('scope-{}'.format(i)):
        for j in range(25):
             v = tf.Variable(1, name=str(j))

実行して可視化すると、以下のようになります。

グループ化した計算グラフ

TensorBoardでは、ダブルクリックすると、スコープの中身を展開することができるので、細かい変数まで確認したいときには便利です。TensorBoardについての詳細は、こちらの記事を参考にしてください。

あらゆるデータを可視化するTensorBoard徹底入門 /tensorflow/2017/04/25/tensorboard.html

階層構造は変数のプロパティnameで確認することができます。

In [1]: import tensorflow as tf

In [2]: with tf.variable_scope('scope1'):
   ...:     v = tf.get_variable('var1', [1])
   ...:

In [3]: v.name
Out[3]: 'scope1/var1:0'

各スコープはUNIXのディレクトリ構造のように/で区切られて表示されます。

tf.variable_scopetf.name_scopeの違いを確認してみましょう。

tf.variable_scope

一般的に、tf.get_variableで変数を作成する際には、同様の名前を付けることはできません。

In [1]: import tensorflow as tf

In [2]: with tf.variable_scope('scope'):
   ...:     v1 = tf.get_variable('var', [1])
   ...:     v2 = tf.get_variable('var', [1])
ValueError: Variable scope/var already exists, disallowed. Did you mean to set reuse=True in VarScope? Originally defined at:

一方で、tf.Variableであれば、同じ名前を付けることができます。

In [1]: import tensorflow as tf

In [2]: with tf.variable_scope('scope'):
   ...:     v1 = tf.Variable(1, name='var')
   ...:     v2 = tf.Variable(2, name='var')
   ...:

In [3]: v1.name, v2.name
Out[3]: ('scope/var:0', 'scope/var_1:0')

tf.Variableで作成した変数は、スコープ内で名前が被ると、名前に数字を足して新しいユニークな名前を改めて割り当てます。

tf.get_variableで同じ変数名を使用したい場合は、スコープを区切れば、変数の作成が可能になります。

In [1]: import tensorflow as tf

In [2]: with tf.variable_scope('scope1'):
   ...:     v1 = tf.get_variable('var', shape=[1])
   ...:     with tf.variable_scope('scope2'):
   ...:         v2 = tf.get_variable('var', shape=[1])
   ...:

In [3]: v1.name, v2.name
Out[3]: ('scope1/var:0', 'scope1/scope2/var:0')

このようにすることで、スコープの階層が変わるので、同じ名前を引数にしても作成することができます。

tf.name_scope

tf.name_scopeは、tf.get_variableと一緒に使用した場合、変数空間ではないと判断し、名前空間を無視します。次の例では、tf.get_variableで作成した変数の名前だけtf.name_scopeの変数空間が抜けていることが分かります。

In [1]: import tensorflow as tf

In [2]: with tf.variable_scope('v_scope'):
   ...:     with tf.name_scope('n_scope'):
   ...:         x = tf.Variable([1], name='x')
   ...:         y = tf.get_variable('x', shape=[1], dtype=tf.int32)
   ...:         z = x + y
   ...:

In [3]: x.name, y.name, z.name
Out[3]: ('v_scope/n_scope/x:0', 'v_scope/x:0', 'v_scope/n_scope/add:0')

変数の再利用

事前に作成した変数を再利用するにはどうすればいいのでしょうか。Recurrent Neural Networksのモデルを定義する場合には、前の層のパラメータ変数を再利用した方がパフォーマンスが向上しそうです。

先程tf.variable_scopeの節で見てきたとおり、同じ変数名でtf.get_variableをしようとするとValueErrorが発生しました。

In [1]: import tensorflow as tf

In [2]: with tf.variable_scope('scope'):
   ...:     v1 = tf.get_variable('var', [1])
   ...:     v2 = tf.get_variable('var', [1])
ValueError: Variable scope/var already exists, disallowed. Did you mean to set reuse=True in VarScope? Originally defined at:

解決策としては、tf.get_variable_scope().reuse_variables()を使用することで、変数を再作成しようとせずに再利用できるようになります。

In [1]: import tensorflow as tf

In [2]: with tf.variable_scope('scope'):
   ...:     v1 = tf.get_variable('var', [1])
   ...:     tf.get_variable_scope().reuse_variables()
   ...:     v2 = tf.get_variable('var', [1])
   ...:

In [3]: v1.name, v2.name
Out[3]: ('scope/var:0', 'scope/var:0')

また、tf.variable_scopeを再度定義して、reuseフラグをTrueにすることで、再利用できるようになります。

In [1]: import tensorflow as tf

In [2]: with tf.variable_scope('scope'):
   ...:     v1 = tf.get_variable('x', [1])
   ...:

In [3]: with tf.variable_scope('scope', reuse=True):
   ...:     v2 = tf.get_variable('x', [1])
   ...:

In [4]: v1.name, v2.name
Out[4]: ('scope/x:0', 'scope/x:0')

変数の種類

TensorFlowの変数には、local_variablesglobal_variablesの二種類あります。変数を作成する際に、collections=[tf.GraphKeys.LOCAL_VARIABLES]とすることでlocal_variablesを作成することができます。

つまり、保存する必要のない一時的な変数に向いています。以下のように使用します。

with tf.name_scope("increment"):
    zero64 = tf.constant(0, dtype=tf.int64)
    current = tf.Variable(
        zero64, name="incr", trainable=False,
        collections=[ops.GraphKeys.LOCAL_VARIABLES])

まとめ

本記事では、一般的なプログラミング言語とTensorFlowの変数の扱い方の違いを比較しながら解説することで、コンセプトを理解する手助けになるように説明してきました。

また、名前空間や変数の種類に言及することで、TensorFlowのローレベルなコードによく出てくるようなコードの意味が分かるようになったと思います。

新しい計算モデルを実装する手助けになると嬉しいですね!