前言:
TensorFlow提供了通过变量名称来创建或者获取一个变量的机制。通过这个机制,在不同的函数中可以直接通过变量的名字来使用变量,而不需要将变量通过参数的形式到处传递。TensorFlow 中通过变量名称获取变量的机制主要是通过 tf.get_variable 和tf.variable_scope函数实现的。下面将分别介绍如何使用这两个函数。
一、变量的创建方式—— tf.Variable和 tf.get_variable函数
通过 tf.Variable函数来创建一个变量。除了 tf.Variable函数,TensorFlow 还提供了tf.get_variable函数来创建或者获取变量。当 tf.get_variable用于创建变量时,它和tf.Variable的功能是基本等价的。以下代码给出了通过这两个函数创建同一个变量的样例:
#coding=gbk
import tensorflow as tf
#下面这两个定义是等价的
v = tf.get_variable("v", shape=[1],initializer=tf.constant_initializer(1.0))
v1 = tf.Variable(tf.constant(1.0, shape=[1]), name="v1")
#打印变量的值
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
print("v:",sess.run(v))
print("v的类型:", type(sess.run(v)))
print("v1:",sess.run(v1))
print("v1的类型:", type(sess.run(v1)))
运行程序,输出:
从上面的代码中可以看出,通过 tf.Variable和 tf.get_variable函数创建变量的过程基本上是一样的,同时变量的结果也是一样的。tf.get_variable函数调用时提供的维度(shape)信息以及初始化方法(initializer) 的参数和tf.Variable函数调用时提供的初始化过程中的参数也类似。在上面的样例程序中使用到的常数初始化函数 tf.constant_initializer和常数生成函数tf.constant功能上就是一致的。
tf.get_variable函数与tf.Variable函数最大的区别在于指定变量名称的参数。对于tf.Variable函数,变量名称是一个可选的参数,通过name=”v“的形式给出。但是对于tf.get_variable函数,变量名称是一个必填的参数。tf.get_variable会根据这个名字去创建或者获取变量。
二、变量的获取方式—— tf.variable_scope函数
在上面的样例程序中,tf.get_variable首先会试图去创建一个名字为v的参数,如果创建失败(比如已经有同名的参数),那么这个程序就会报错。这是为了避免无意识的变量复用造成的错误。比如在定义神经网络参数时,第一层网络的权重已经叫 weights了,那么在创建第二层神经网络时,如果参数名仍然叫 weights,就会触发变量重用的错误。否则两层神经网络共用一个权重会出现一些比较难以发现的错误。如果需要通过tf.get_variable获取一个已经创建的变量,需要通过tf.variable_scope函数来生成一个上下文管理器,并明确指定在这个上下文管理器中,tf.get_variable将直接获取已经生成的变量。
下面给出了一段代码说明如何通过tf.variable_scope函数来控制tf.get_variable函数获取已经创建过的变量:
#coding=gbk
import tensorflow as tf
#在名字为namespace_1的命名空间内创建名字为v的变量
with tf.variable_scope("namespace_1"):
v = tf.get_variable("v", [1], initializer = tf.constant_initializer(1.0))
#因为在命名空间namespace_1中已经存在名字为v的变量,所有下面的代码将会报错:
with tf.variable_scope("namespace_1"):
v = tf.get_variable("v", [1])
运行程序,输出报错:
显然,在同一个命名空间内创建同名的变量是不允许的。再看看下面这段程序:
#coding=gbk
import tensorflow as tf
#在名字为namespace_1的命名空间内创建名字为v的变量
with tf.variable_scope("namespace_1",reuse=False):
v = tf.get_variable("v", [1], initializer = tf.constant_initializer(1.0))
# 在生成上下文管理器时,将参数reuse设置为True。这样tf.get_variable函数将直接获取已经声明的变量
with tf.variable_scope("namespace_1", reuse=True) :
v1 = tf.get_variable("v",[1])
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
print(v1 == v)
print("v1:", sess.run(v1))
print(v1)
运行程序,输出:
上面的程序简单地说明了通过 tf.variable_scope 函数可以控制tf.get_variable 函数的语义。当 tf.variable_scope函数使用参数 reuse=True生成上下文管理器时,这个上下文管理器内所有的 tf.get_variable函数会直接获取已经创建的变量。如果变量不存在,则 tf.get_variable 函数将报错;相反,如果 tf.variable_scope函数使用参数 reuse=None或者 reuse=False创建上下文管理器,tf.get_variable 操作将创建新的变量。如果同名的变量已经存在,则tf.get_variable函数将报错。
三、通过tf.variable_scope管理变量的名称
tf.variable_scope函数生成的上下文管理器也会创建一个 TensorFlow中的命名空间,在命名空间内创建的变量名称都会带上这个命名空间名作为前缀。所以,tfvariable_scope函数除了可以控制 tf.get_variable执行的功能之外,这个函数也提供了一个管理变量命名空间的方式。以下代码显示了如何通过 tf.variable_scope来管理变量的名称:
#coding=gbk
import tensorflow as tf
v1 = tf.get_variable("v",[1])
print(v1.name )
# 输出v:0。“v”为变量的名称,其中“ :0”表示这个变量是生成变量这个运算的第一个结果
with tf.variable_scope("namespace_1"):
v2 = tf.get_variable("v", [1])
print(v2.name )
# 输出namespace_1/v:0。在tf.variable_scope中创建的变量,名称前面会加入命名空间的名称,
# 并通过/来分隔命名空间的名称和变量的名称
with tf.variable_scope("namespace_1") :
with tf.variable_scope("namespace_2"):
v3 = tf.get_variable("v",[1])
print(v3.name )
# 输出 namespace_1/namespace_2/v;0。命名空间可以嵌套,同时变量的名称也会加
# 入所有命名空间的名称作为前缀
v4 = tf.get_variable("v1", [1])
print(v4.name)
#输出 namespace_1/v1:0。当命名空间退出之后,变量名称也就不会再被加入其前缀了。
# 创建一个名称为空的命名空间,并设置 reuse=True。
# 可以直接通过带命名空间名称的变量名来获取其他命名空间下的变量。
# 比如这里通过指定名称namespace_1/namespace_2/v来获取在命名空间namespace_1/namespace_2中创建的变量。
with tf.variable_scope("", reuse=True):
v5 = tf.get_variable("namespace_1/namespace_2/v",[1])
print(v5 == v3) #输出True
v6 = tf.get_variable("namespace_1/v1",[1])
print(v6 == v4) #输出True
运行程序,输出:
参考文献:
[1] 郑泽宇著 《TensorFlow实战Google深度学习框架》
[2] 黄文坚著 《TensorFlow实战》