页面树结构

2017-11-09 ApacheCN 开源组织,第二期邀请成员活动,一起走的更远 : http://www.apachecn.org/member/209.html


MachineLearning 优酷地址 : http://i.youku.com/apachecn

转至元数据结尾
转至元数据起始

您可以按照变量HowTo中描述的方式创建,初始化,保存和加载单个变量。但是,当构建复杂的模型时,您经常需要共享大量变量,您可能希望在一个位置初始化所有这些变量。本教程将介绍如何使用tf.variable_scope()tf.get_variable()

问题

想象一下,您创建一个简单的图像过滤器模型,类似于我们的 卷积神经网络教程 模型,但只有2个卷积(为了简化本例)。如果您只是使用tf.Variable,如变量HowTo所述,您的模型可能看起来像这样。

def my_image_filter(input_images):
    conv1_weights = tf.Variable(tf.random_normal([5, 5, 32, 32]),
        name="conv1_weights")
    conv1_biases = tf.Variable(tf.zeros([32]), name="conv1_biases")
    conv1 = tf.nn.conv2d(input_images, conv1_weights,
        strides=[1, 1, 1, 1], padding='SAME')
    relu1 = tf.nn.relu(conv1 + conv1_biases)

    conv2_weights = tf.Variable(tf.random_normal([5, 5, 32, 32]),
        name="conv2_weights")
    conv2_biases = tf.Variable(tf.zeros([32]), name="conv2_biases")
    conv2 = tf.nn.conv2d(relu1, conv2_weights,
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv2 + conv2_biases)
正如你可以很容易想象,车型迅速得到多少比这更复杂,而即使在这里,我们已经有4个不同的变量:conv1_weightsconv1_biasesconv2_weights,和conv2_biases

当您想要重用此模型时,会出现问题。假设您要将图像过滤器应用于2个不同的图像,image1以及image2。您希望使用相同参数处理相同过滤器的两个图像。您可以调用my_image_filter()两次,但这将创建两组变量,每个变量中有4个变量,共有8个变量。

# First call creates one set of 4 variables.
result1 = my_image_filter(image1)
# Another set of 4 variables is created in the second call.
result2 = my_image_filter(image2)
共享变量的一个常见方法是在单独的代码段中创建它们,并将它们传递给使用它们的函数。例如通过使用字典:
variables_dict = {
    "conv1_weights": tf.Variable(tf.random_normal([5, 5, 32, 32]),
        name="conv1_weights")
    "conv1_biases": tf.Variable(tf.zeros([32]), name="conv1_biases")
    ... etc. ...
}

def my_image_filter(input_images, variables_dict):
    conv1 = tf.nn.conv2d(input_images, variables_dict["conv1_weights"],
        strides=[1, 1, 1, 1], padding='SAME')
    relu1 = tf.nn.relu(conv1 + variables_dict["conv1_biases"])

    conv2 = tf.nn.conv2d(relu1, variables_dict["conv2_weights"],
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv2 + variables_dict["conv2_biases"])

# Both calls to my_image_filter() now use the same variables
result1 = my_image_filter(image1, variables_dict)
result2 = my_image_filter(image2, variables_dict)
虽然方便,但在代码之外创建上述变量会破坏封装:
  • 构建图形的代码必须记录要创建的变量的名称,类型和形状。
  • 代码更改时,调用者可能必须创建更多或更少或不同的变量。

解决问题的一种方法是使用类创建一个模型,其中类负责管理他们需要的变量。对于较轻的解决方案,不涉及类,TensorFlow提供了一个可变范围机制,允许在构建图形时轻松共享命名变量。

 

可变范围示例

TensorFlow中的可变范围机制包括两个主要功能:

  • tf.get_variable(<name>, <shape>, <initializer>):创建或返回具有给定名称的变量。
  • tf.variable_scope(<scope_name>):管理传递给的名称的名称空间tf.get_variable()

该函数tf.get_variable()用于获取或创建一个变量,而不是直接调用tf.Variable。它使用初始化程序,而不是直接传递值,如同tf.Variable。一个初始化器是一个函数,它采取形状并提供一个具有该形状的张量。以下是TensorFlow中的一些初始化器:

  • tf.constant_initializer(value) 将所有内容初始化为提供的值,
  • tf.random_uniform_initializer(a, b) 从[a,b],
  • tf.random_normal_initializer(mean, stddev) 从给定的平均值和标准偏差的正态分布初始化。

要了解如何tf.get_variable()解决前面讨论过的问题,我们将创建一个卷积的代码重构为一个单独的函数,命名为conv_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"。我们想用它来既conv1conv2,但变量需要有不同的名称。这是tf.variable_scope()发挥作用的地方:它推送变量的命名空间。
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])
现在,我们来看看当我们打电话my_image_filter()两次时会发生什么。
result1 = my_image_filter(image1)
result2 = my_image_filter(image2)
# Raises ValueError(... conv1/weights already exists ...)
您可以看到,tf.get_variable()检查已经存在的变量不是意外共享的。如果要共享它们,则需要通过reuse_variables()如下设置来指定它。
with tf.variable_scope("image_filters") as scope:
    result1 = my_image_filter(image1)
    scope.reuse_variables()
    result2 = my_image_filter(image2)
这是分享变量,轻量级和安全的好方法。

 

可变范围如何工作?

理解 tf.get_variable()

要了解变量范围,有必要首先充分了解tf.get_variable()工作原理。这是tf.get_variable通常所说的方式。

v = tf.get_variable(name, shape, dtype, initializer)
这个调用有两件事情之一,取决于它所调用的范围。这是两个选项。
  • 情况1:范围设置用于创建新变量,如下所示 tf.get_variable_scope().reuse == False

在这种情况下,vtf.Variable使用提供的形状和数据类型新创建。创建的变量的全名将被设置为当前变量范围名称+提供的name,并且将执行检查以确保没有存在具有该全名的变量。如果具有该全名的变量已经存在,则该函数将引发一个ValueError。如果创建了一个新变量,它将被初始化为该值initializer(shape)。例如:

with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1])
assert v.name == "foo/v:0"

  • 情况2:范围设置为重用变量,如下所示 tf.get_variable_scope().reuse == True

在这种情况下,调用将搜索已经存在的变量,其名称等于当前可变范围名称+所提供的变量name。如果不存在这样的变量,ValueError则会产生一个。如果找到该变量,它将被返回。例如:

with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1])
with tf.variable_scope("foo", reuse=True):
    v1 = tf.get_variable("v", [1])
assert v1 is v

tf.variable_scope()基础

知道如何tf.get_variable()工作使得容易理解变量范围。可变范围的主要功能是携带将用作变量名称的前缀的名称和重用标志来区分上述两种情况。嵌套变量作用域以类似于目录的工作方式追加其名称:

with tf.variable_scope("foo"):
    with tf.variable_scope("bar"):
        v = tf.get_variable("v", [1])
assert v.name == "foo/bar/v:0"
可以使用当前变量范围来检索当前变量范围,并且可以通过调用以下方法将当前变量范围tf.get_variable_scope() 的reuse标志设置为:True tf.get_variable_scope().reuse_variables() 
with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1])
    tf.get_variable_scope().reuse_variables()
    v1 = tf.get_variable("v", [1])
assert v1 is v
请注意,您不能设置reuse标志False。原因在于允许组合创建模型的功能。想象你my_image_filter(inputs)以前写的功能。有人在一个变量范围内调用该函数reuse=True可能会期待所有的内部变量也被重用。允许强制reuse=False执行功能将破坏此合同,并使其难以以这种方式共享参数。

即使你不能设置reuseFalse明确,就可以进入重用变量范围,然后退出它,回到非重用之一。reuse=True打开变量范围时可以使用参数来完成。还要注意,由于与上述相同的原因,reuse参数是继承的。所以当你打开一个重用变量范围时,所有子范围也将被重用。

with tf.variable_scope("root"):
    # At start, the scope is not reusing.
    assert tf.get_variable_scope().reuse == False
    with tf.variable_scope("foo"):
        # Opened a sub-scope, still not reusing.
        assert tf.get_variable_scope().reuse == False
    with tf.variable_scope("foo", reuse=True):
        # Explicitly opened a reusing scope.
        assert tf.get_variable_scope().reuse == True
        with tf.variable_scope("bar"):
            # Now sub-scope inherits the reuse flag.
            assert tf.get_variable_scope().reuse == True
    # Exited the reusing scope, back to a non-reusing one.
    assert tf.get_variable_scope().reuse == False

捕获可变范围

在上面提到的所有例子中,我们共享参数,只因为它们的名称是一致的,也就是因为我们打开一个重复使用的变量范围,具有完全相同的字符串。在更复杂的情况下,传递VariableScope对象可能非常有用,而不是依靠获取正确的名称。为此,在打开新的变量范围时,可以捕获并使用变量作用域而不是名称。

with tf.variable_scope("foo") as foo_scope:
    v = tf.get_variable("v", [1])
with tf.variable_scope(foo_scope):
    w = tf.get_variable("w", [1])
with tf.variable_scope(foo_scope, reuse=True):
    v1 = tf.get_variable("v", [1])
    w1 = tf.get_variable("w", [1])
assert v1 is v
assert w1 is w
当使用先前存在的范围打开变量范围时,我们跳出当前的变量范围前缀到完全不同的范围。这完全独立于我们做什么。
with tf.variable_scope("foo") as foo_scope:
    assert foo_scope.name == "foo"
with tf.variable_scope("bar"):
    with tf.variable_scope("baz") as other_scope:
        assert other_scope.name == "bar/baz"
        with tf.variable_scope(foo_scope) as foo_scope2:
            assert foo_scope2.name == "foo"  # Not changed.

可变范围的初始化器

使用tf.get_variable()允许编写创建或重用变量的函数,并可以从外部透明地调用。但是如果我们想要更改创建的变量的初始化器呢?我们需要为每个创建变量的函数传递一个额外的参数吗?最常见的情况是,当我们想在一个地方为所有功能的所有变量设置默认的初始化程序?为了帮助这些情况,可变范围可以携带默认的初始化程序。它由子范围继承并传递给每个tf.get_variable()调用。但是如果另一个初始化程序被明确指定,它将被覆盖。

with tf.variable_scope("foo", initializer=tf.constant_initializer(0.4)):
    v = tf.get_variable("v", [1])
    assert v.eval() == 0.4  # Default initializer as set above.
    w = tf.get_variable("w", [1], initializer=tf.constant_initializer(0.3)):
    assert w.eval() == 0.3  # Specific initializer overrides the default.
    with tf.variable_scope("bar"):
        v = tf.get_variable("v", [1])
        assert v.eval() == 0.4  # Inherited default initializer.
    with tf.variable_scope("baz", initializer=tf.constant_initializer(0.2)):
        v = tf.get_variable("v", [1])
        assert v.eval() == 0.2  # Changed default initializer.

操作名称 tf.variable_scope()

我们讨论了如何tf.variable_scope控制变量的名称。但是如何影响范围内其他操作的名称?在变量范围内创建的ops很自然也应该共享该名称。因此,当我们这样做with tf.variable_scope("name")时,这隐含地打开了tf.name_scope("name")。例如:

with tf.variable_scope("foo"):
    x = 1.0 + tf.get_variable("v", [1])
assert x.op.name == "foo/add"
除了可变范围之外,还可以打开名称范围,然后它们只会影响ops的名称,而不影响变量的名称。
with tf.variable_scope("foo"):
    with tf.name_scope("bar"):
        v = tf.get_variable("v", [1])
        x = 1.0 + v
assert v.name == "foo/v:0"
assert x.op.name == "foo/bar/add"
当使用捕获的对象而不是字符串打开变量范围时,我们不会更改op的当前名称范围。

 

使用示例

这里是指向使用可变范围的几个文件的指针。他们都可以在TensorFlow模型回购中找到。特别地,可变范围被大量用于循环神经网络和序列到序列模型。

文件这里面是什么?
models/tutorials/image/cifar10/cifar10.py用于检测图像中物体的模型。
models/tutorials/rnn/rnn_cell.py循环神经网络的细胞功能。
models/tutorials/rnn/seq2seq.py构建序列到序列模型的功能。
  • 无标签