TensorFlow变量(Variables)是在程序中表示共享、持久状态的最佳方式。
通过tf.Variable
类操作变量。tf.Variable
表示值可以被操作改变的张量。跟tf.Tensor
对象不一样,tf.Variable
存在于单个session.run
调用的上下文之外。
在内部,atf.Variable
存储一个持久张量,允许读取和修改张量的值。这些修改是跨tf.Session
可见的,所以不同的工作节点可以看到相同的值tf.Variable
。
创建一个变量
创建变量的最好方法是调用tf.get_variable
功能。此功能要求您指定变量的名称。此名称将被其他副本用于访问相同变量,以及在检查点和导出模型时命名此变量的值。tf.get_variable
还允许您重复使用先前创建的同名变量,从而可以轻松定义重复使用图层的模型。
用tf.get_variable
来创建一个变量,只需提供名称和形状
my_variable = tf.get_variable("my_variable", [1, 2, 3])
这将创建一个名为”my_variable”的变量,它是一个带有形状[1, 2, 3]
的3维张量。这个变量默认的dtype
是tf.float32
,其初始值将通过tf.glorot_uniform_initializer
随机化。
您可以选择指定dtype
和初始化程序tf.get_variable
。例如:
my_int_variable = tf.get_variable("my_int_variable", [1, 2, 3], dtype=tf.int32,
initializer=tf.zeros_initializer)
TensorFlow提供了许多方便的初始化程序。或者,您可以初始化一个tf.Variable
为具有tf.Tensor
的值。例如:
other_variable = tf.get_variable("other_variable", dtype=tf.int32,
initializer=tf.constant([23, 42]))
请注意,当初始化器是一个tf.Tensor
,你不应该指定变量的形状,因为用来初始化的张量的形状将被复用。
变量集合
因为TensorFlow程序中不连接的部分之间可能需要创建变量,所以用单一的方法来访问所有的变量是有用的。出于这个原因,TensorFlow提供 collections(集合),它们被命名为张量或其他对象的列表,例如tf.Variable
实例。
默认情况下每个tf.Variable
被放置在以下两个集合中:*tf.GraphKeys.GLOBAL_VARIABLES
—可以在多个设备上共享的变量,*tf.GraphKeys.TRAINABLE_VARIABLES
— TensorFlow将计算梯度的变量。
如果你不想要一个变量是可训练的,把它添加到tf.GraphKeys.LOCAL_VARIABLES
集合。例如,以下代码片段演示了如何添加一个名为my_local
的变量到这个集合:
my_local = tf.get_variable("my_local", shape=(),
collections=[tf.GraphKeys.LOCAL_VARIABLES])
或者,您可以指定trainable=False
作为tf.get_variable
的参数:
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
的变量并将其放置在第二个GPU设备上:
with tf.device("/device:GPU:1"):
v = tf.get_variable("v", [1])
变量在分布式设置中处于正确的设备中尤为重要。例如,不小心将变量放在worker而不是参数服务器上,可能会严重减慢训练速度,或者在最坏的情况下,让每个worker都进行各自独立的每个变量的复制。为此我们提供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
初始化变量
在你使用一个变量之前,它必须被初始化。如果您在low-level TensorFlow API中进行编程(即,您正在显式创建自己的图形和会话),则必须明确地初始化变量。大多数high-level框架如tf.contrib.slim
,tf.estimator.Estimator
和Keras
在训练模型之前自动初始化变量。
显式初始化在其他方面很有用,因为它允许您从检查点重新加载模型时不会重新运行潜在的昂贵初始化程序,并且在分布式设置中共享随机初始化变量时保证确定性。
要在训练开始之前一次初始化所有可训练变量,请调用tf.global_variables_initializer()
。这个函数返回一个负责初始化tf.GraphKeys.GLOBAL_VARIABLES
集合中所有变量的操作。运行此操作会初始化所有变量。例如:
session.run(tf.global_variables_initializer())
# Now all variables are initialized.
如果你确实需要自己初始化某个变量,你可以运行变量的初始化操作。例如:
session.run(my_variable.initializer)
你也可以问哪些变量还没有被初始化。例如,以下代码打印所有尚未初始化的变量的名称:
print(session.run(tf.report_uninitialized_variables()))
请注意,默认情况下tf.global_variables_initializer
没有指定变量初始化的顺序。因此,如果变量的初始值取决于另一个变量的值,那么很可能会出现错误。任何时候,如果在并非所有变量都被初始化的情况下使用变量的值(例如,如果在初始化另一个变量时使用变量的值),最好使用variable.initialized_value()
代替variable
:
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.
要为变量赋值,请使用这些方法assign
,assign_add
以及tf.Variable
类中的其他方法。方法使用示例:
v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
assignment = v.assign_add(1)
tf.global_variables_initializer().run()
assignment.run()
大多数TensorFlow优化器都有专门的操作,根据某种梯度下降类算法有效地更新变量的值。见tf.train.Optimizer
了解如何使用优化器。
因为变量是可变的,所以知道在任何时间点使用什么版本的变量值是有用的。在某个事件发生后强制变量值的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支持两种共享变量的方式:
- 明确传递
tf.Variable
对象。
- 在
tf.variable_scope
对象内隐式包装tf.Variable
对象。
尽管明确地传递变量的代码非常清晰,但有时编写TensorFlow函数在实现中隐式使用变量也很方便。大部分来自tf.layer
功能层使用这种方法,以及所有tf.metrics
和其他一些库工具。
变量作用域允许您在调用隐式创建和使用的变量的函数时能够控制变量重用。它们还允许您以分层和可理解的方式命名变量。
例如,假设我们编写一个函数来创建一个卷积/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, 32, 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, 32, 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])
如果你想要共享变量,你有两个选择。首先,您可以使用相同的名称创建一个范围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)