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.Variable
とtf.get_variable
をどのように使い分ければいいのでしょうか。この違いを知るためには、TensorFlowの計算グラフ内部の名前空間による階層構造を理解する必要があります。
スコープ
TensorFlowの名前空間は2種類あります。tf.variable_scope
とtf.name_scope
です。これまで、計算グラフに変数を追加する方法を見てきましたが、大量の変数が作成されていると、計算グラフが汚くなってしまい、構造が把握しづらくなってしまいます。
例として、4個のグループに分けた変数を各25個ずつ作成してみましょう。
for i in range(4):
for j in range(25):
v = tf.Variable(1, name='{}-{}'.format(i, j))
こちらのコードで生成した計算グラフを出力すると、以下のようになります。
変数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_scope
とtf.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_variables
とglobal_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のローレベルなコードによく出てくるようなコードの意味が分かるようになったと思います。
新しい計算モデルを実装する手助けになると嬉しいですね!