Variables

TensorFlow里的variable是表示你程序操作的持续共享的状态的最好的方式。

变量是通过tf.Variable类操作的。一个tf.Variable表示一个tensor的值是可以通过操作来改变的。和tf.Tensor不同,tf.Variable是存在于单次session.run调用的context之外的。

在内部,一个tf.Variable存储着一个持久的tensor,特定的操作允许你读取或者修改这个tensor的值。而且这些修改是跨session的。所以多个worker可以看到同样值的tf.Variable.

创建一个Variable


最好的创建一个variable的办法是通过调用tf.get_variable方法。这个方法需要你设定变量的名字。别人可以通过这个名字来访问这个variable。同时这个名字还会在checkpointing和exporting model的时候给变量的值命名。tf.get_variable同时也允许你重用之前定义的同名变量。这样让你在定义模型时重用一些layer。

通过tf.get_variable创建一个变量的时候,只要简单的提供名字和shape


my_variable = tf.get_variable("my_variable",[1,2,3])

上边的代码创建了一个名叫my_variable的变量,它有3个维度,shape是[1,2,3].这个值默认是tf.float32类型的. 它的初始值会被tf.glorot_uniform_initializer随机初始化。

你也可以选在调用tf.get_variable的时候指定类型和初始化函数,比如:


my_variable = tf.get_variable("my_variable",[1,2,3],dtype=tf.int32,initializer=tf.zeros_initializer)

TensorFlow提供了很多好用的initializers。另外你可以用一个tensor来出初始化一个tf.Variable.比如:


other_variable = tf.get_variable("other_variable",dtype=tf.int32,initializer=tf.contant([23,42])

如果是用tensor来初始化variable,你就不用在指定shape了,会用初始化的tensor的shape来初始化variable的shape。

变量集合

因为tensorflow程序的不同部分都会创建variable,有时候你想用单一的方式访问所有的变量。因此tensorflow提供了collection,它是一个命名的tensor或者其他对象的list,比如tf.Variable实例.

默认情况下,一个variable被放在了两个集合里:
– tf.GraphKeys.GLOBAL_VARIABLES 这里的变量会在多个设备间共享。
– tf.GraphKeys.TRAINABLE_VARIABLES 这里的变量tensorflow会对它们计算梯度。
如果你又一个variable,你不想训练它,你就把他加到tf.GraphKeys.LOCAL_VARIABLES列表里。


my_local = tf.get_variable("my_local",shape=(),collections=[tf.GraphKeys.LOCAL_VARIABLES])

或者,你也可以通过指定tf.get_variable的参数trainable=False来达到同样的目的:


my_local = tf.get_variable("my_local",shape=(),trainable=False)

你也可以用你自己的集合,任意字符串都是有效的集合名。你不需要自己创建一个集合,只需要直接把一个变量(或者其他对象)加入到一个集合,调用时你只需要给出这个集合的名字:


tf.add_to_collection("my_coolection_name",my_local)

获取你之前放入collection的变量或者其他对象的方法是:


tf.get_collection("my_collection_name")

指定设备

就像其他的tensorflow的操作,你可以把变量放置到特定的设备上。比如下边的代码段创建了一个变量v,并把它放到第二个GPU上。


with tf.device("/device:GPU:1"):
    v = tf.get_variable("v",[1])

在分布式的情况下,把variable放在合适的设备上是非常重要的。如果不小心把变量放在了worker上,而不是parameter机器上。会非常明显的降低训练速度。最差的情况,让每个worker都没有意识到自己在自己独立的一份variable上训练。因此,我们提供了tf.train.replica_device_setter,它可以自动的把variable放到parameter server上。比如:


cluster_spec = {
    "ps":["ps0:2222","ps1:2222"],
    "worker":["work0:2222","worker1:2222","work2:2222"]
}
with tf.device(tf.train.replica_device_setter(cluster=cluster_spec)):
    v = tf.get_variable("v",shape=[20,20]) # 通过指定replica_device_setter,这个变量会放在
                                                                                 # parameter server上。

初始化变量

在你使用一个变量前,你需要初始化。如果你用一些底层的tensorflow API编程(也就是你自己创建graph和session),你必须自己初始化这些变量。高层的API,比如tf.contrib.slim,tf.estimator.Estimator和Keras会在你训练模型前自动的初始化变量。

明确的初始化变量值非常有用,当你load一个训练了一半的模型时,可以设置之前训练了一半的变量值。当你在分布式环境下可以给各个机器设置同样的随机初始值。

一次设置所有的可以训练的变量,在训练开始前,你可以调用tf.gloable_variables_initializer().这个方法返回一个operation,它负责初始化所有的在tf.GraphKeys.GLOBAL_VARIABLES集合里的变量。运行这个operation,会初始化所有的变量。比如:


sess.run(tf.global_variables_initializer())
#现在所有的变量都初始化了。

如果你需要自己初始化变量,你可以运行变量自己的initializer。比如:


sess.run(my_variable.initializer)

你也可以通过下边的代码查看哪些变量还没有被初始化:


print(sess.run(tf.report_uninitialized_variables()))

默认情况下,tf.global_variables_initializer是不指定变量初始化顺序的。因此如果一个变量的初始化值依赖于另一个变量的值,你可能会得到一个error。所以你最好在代码里,在所有的变量没有被初始化的时候,不要直接用一个variable的值,而使用它的initialized_value.比如:


v = tf.get_variable("v",shape=(),initialize=tf.zeros_initializer())
w = tf.get_variable("v",initializer=v.initialized_value()+1)

使用变量


在tensorflow的graph里,使用variable就像使用tensor一样。


v = tf.get_variable("v",shape=(),initalizer=tf.zeros_initializer())
w = v+1 # w是一个基于变量v计算而来的tensor。只要变量出现在一个表达式里,
                #它会自动转化成一个tensor来计算。这个tensor有着变量的值。

给变量赋值一个值,可以使用方法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()
sess.run(assignment) # 或者assignment.op.run(),或者assignment.eval()

绝大多数的tensorflow optimizer有专用的操作来高效的更新变量的值,依据像梯度下降这样的算法。tf.train.Optimizer的文档详细说了如何使用optimizer。

因为变量是可以更改的。所以有时知道变量的版本是很有用的。在某些操作后强制去重新读一个变量的值,你可以用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就可以保证反映了v在assign_add操作执行之后的值。

共享变量


TensorFlow有两种共享变量的方式:
– 直接传递variable对象。
– 把variable对象封装到tf.variable_scope对象里。

通过代码传递variable对象非常清晰。但是有时候我们在写tensorflow的function的时候,用隐式变量比较方便。大多数tf.layer里的layers和所有的tf.metrics以及一些工具库都是用这种方式。

比如我们这里创建一个激活函数为relu的卷积层:


def conv_relu(input,kernel_shape,bias_shape):
    # 创建名字为weights的变量
    weights = tf.get_variable("weights",kernel_shape,initializer=tf.random_normal_initializer())
    # 创建bias变量
    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)

我们给变量起了weight和bias这样通用的名字。但是在一个深度卷积神经网络里,我们会创建很多卷积层。我们如果重复的调用这个funciton就会有问题。


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_shap=[5,5,32,32],bias_shape=[32]) #这个调用会失败

因为期望的行为并不清晰,你到底是想创建新的变量还是重用老的变量。所以执行会失败。在不同变量域里就不会有这个歧义。下边的代码就会创建新的变量:


def my_image_filter(input_images):
    with tf.variable_scope("cov1"):
        # 在这里创建的变量名是 conv1/weights 和conv1/biases
        relu1=conv_relu(input_iamges,[5,5,32,32],[32])
    with tf.variable_scope("conv2"):
        # 在这里创建的变量名是 conv2/weights 和conv2/biases
        relu2=conv_relu(relu1,[5,5,32,32],[32])

如果你想变量被共享。你有两种选择,你可以定义一个和之前同名的变量scope,并且把reuse参数设置为True:


with tf.variable_scope("model")
    output1 = my_image_filter(input1)
with tf.variable_scope("model",reuse=True):
    output2 = my_iamge_filter(input2)

你也可以用scope.reuse_variables()方法来触发一个重用:


with tf.variable_scope("model") as scope:
    output1 = my_image_filter(input1)
    scope.reuse_variables()
    output2 = my_iamge_filter(input2)

因为用string来定义scope可能你觉得不安全,你也可以用已有的scope对象来初始化另一个scope:


with tf.variable_scope("model") as scope:
    output1 = my_image_filter(input1)
with tf.variable_scope(scope,reuse=True):
    output2 = my_image_filter(input2)

发表评论

电子邮件地址不会被公开。 必填项已用*标注

%d 博主赞过: