张量流中名称范围和变量范围有什么区别?


276

这些功能之间有什么区别?

tf.variable_op_scope(values, name, default_name, initializer=None)

返回一个上下文管理器,用于定义创建变量的操作。该上下文管理器验证给定值是否来自同一图,确保该图是默认图,并推送名称范围和变量范围。


tf.op_scope(values, name, default_name=None)

返回一个上下文管理器,供定义Python op时使用。该上下文管理器验证给定的值是否来自同一图,确保该图是默认图,并推送名称范围。


tf.name_scope(name)

包装器,用于Graph.name_scope()使用默认图形。请参阅Graph.name_scope()以获取更多详细信息。


tf.variable_scope(name_or_scope, reuse=None, initializer=None)

返回变量作用域的上下文。变量作用域允许创建新变量并共享已创建的变量,同时提供检查以防止意外创建或共享。有关详细信息,请参见“可变作用域”,此处仅介绍一些基本示例。


Answers:


377

让我们从变量共享的简短介绍开始。它是一种机制,TensorFlow它允许共享在代码的不同部分中访问的变量,而无需传递对变量的引用。

该方法tf.get_variable可以将变量名用作参数,以使用该名称创建新变量或检索以前创建的变量。这不同于使用tf.Variable构造函数,该构造函数在每次调用它时都会创建一个新变量(如果已经存在具有此类名称的变量,则可能在变量名称后添加一个后缀)。

出于变量共享机制的目的,引入了一种单独的范围类型(​​变量范围)。

结果,我们最终得到了两种不同类型的范围:

这两个作用域对所有操作以及使用创建的变量都具有相同的作用tf.Variable,即,作用域将作为前缀添加到操作或变量名中。

但是,名称范围被忽略tf.get_variable。在下面的示例中我们可以看到:

with tf.name_scope("my_scope"):
    v1 = tf.get_variable("var1", [1], dtype=tf.float32)
    v2 = tf.Variable(1, name="var2", dtype=tf.float32)
    a = tf.add(v1, v2)

print(v1.name)  # var1:0
print(v2.name)  # my_scope/var2:0
print(a.name)   # my_scope/Add:0

tf.get_variable在范围内放置使用变量访问的唯一方法是使用变量范围,如以下示例所示:

with tf.variable_scope("my_scope"):
    v1 = tf.get_variable("var1", [1], dtype=tf.float32)
    v2 = tf.Variable(1, name="var2", dtype=tf.float32)
    a = tf.add(v1, v2)

print(v1.name)  # my_scope/var1:0
print(v2.name)  # my_scope/var2:0
print(a.name)   # my_scope/Add:0

这使我们可以轻松地在程序的不同部分之间共享变量,即使在不同的名称范围内也是如此:

with tf.name_scope("foo"):
    with tf.variable_scope("var_scope"):
        v = tf.get_variable("var", [1])
with tf.name_scope("bar"):
    with tf.variable_scope("var_scope", reuse=True):
        v1 = tf.get_variable("var", [1])
assert v1 == v
print(v.name)   # var_scope/var:0
print(v1.name)  # var_scope/var:0

更新

从r0.11版开始,op_scopevariable_op_scope均已弃用,并由name_scope和代替variable_scope


41
感谢您的明确解释。自然,后续问题将是:“ 为什么 Tensorflow具有这两种令人困惑的相似机制?为什么不只用scope一种有效地做到这一点的方法来代替它们variable_scope?”
约翰·

8
我不认为我从概念上理解为什么甚至需要区分variable_scopevs。name_scope如果创建了一个变量(使用tf.Variable或进行任何方式tf.get_variable),对我来说似乎更自然的是,如果我们指定范围或其全名,我们应该总是能够得到它。我不明白为什么一个人忽略作用域名称,而另一个人却不。您了解这种怪异行为的合理性吗?
查理·帕克

23
原因是使用变量作用域,可以为可重用变量定义单独的作用域,而不受可用于定义操作的当前名称作用域的影响。
安杰Pronobis

6
您好,您能解释一下为什么variable_scope中的变量名总是以0结尾吗?这是否意味着可能会有变量名称以:1,,2等结尾,所以这怎么发生?
詹姆士·范

2
@JamesFan每个“声明”都是一个操作,因此当您说a = tf.Variable(.. name)时,您将返回一个张量,但实际上也创建了一个操作。如果打印a,您将得到带有0的张量。如果打印a.op,您将获得将计算该张量值的运算。
罗伯特·拉格

84

无论variable_op_scopeop_scope现在已过时,不应该在所有使用。

关于其他两个,在尝试通过创建一个简单的示例来可视化所有内容之前,我还难以理解variable_scopename_scope(它们看起来几乎相同)之间的区别:

import tensorflow as tf


def scoping(fn, scope1, scope2, vals):
    with fn(scope1):
        a = tf.Variable(vals[0], name='a')
        b = tf.get_variable('b', initializer=vals[1])
        c = tf.constant(vals[2], name='c')

        with fn(scope2):
            d = tf.add(a * b, c, name='res')

        print '\n  '.join([scope1, a.name, b.name, c.name, d.name]), '\n'
    return d

d1 = scoping(tf.variable_scope, 'scope_vars', 'res', [1, 2, 3])
d2 = scoping(tf.name_scope,     'scope_name', 'res', [1, 2, 3])

with tf.Session() as sess:
    writer = tf.summary.FileWriter('logs', sess.graph)
    sess.run(tf.global_variables_initializer())
    print sess.run([d1, d2])
    writer.close()

在这里,我创建了一个函数,该函数创建一些变量和常量,并将它们分组在范围内(取决于我提供的类型)。在此功能中,我还将打印所有变量的名称。之后,我执行该图以获取结果值的值,并保存事件文件以在TensorBoard中对其进行调查。如果运行此命令,则会得到以下信息:

scope_vars
  scope_vars/a:0
  scope_vars/b:0
  scope_vars/c:0
  scope_vars/res/res:0 

scope_name
  scope_name/a:0
  b:0
  scope_name/c:0
  scope_name/res/res:0 

如果打开TensorBoard,您会看到类似的图案(如您所见,b它位于scope_name矩形外部):


这给你答案

现在您将看到tf.variable_scope()在所有变量(无论如何创建它们),ops和常量的名称之前添加一个前缀。另一方面,tf.name_scope()忽略使用创建的变量,tf.get_variable()因为它假定您知道要使用的变量和作用域。

关于共享变量的好的文档告诉您

tf.variable_scope():管理名称空间,以传递给的名称tf.get_variable()

同一文档提供了更多详细信息,“可变作用域”如何工作以及何时有用。


2
通过示例和视觉效果获得神话般的答案,让我们将此答案推高了!
大卫·帕克斯

43

命名空间是一种以分层方式组织变量和运算符名称的方法(例如“ scopeA / scopeB / scopeC / op1”)

  • tf.name_scope 在默认图中为运算符创建名称空间。
  • tf.variable_scope 在默认图中为变量和运算符创建名称空间。

  • tf.op_scope与相同tf.name_scope,但对于其中创建了指定变量的图。

  • tf.variable_op_scope与相同tf.variable_scope,但对于其中创建了指定变量的图。

指向上述资源的链接有助于消除此文档问题的歧义。

此示例说明,所有类型的作用域都为变量和运算符定义了名称空间,但存在以下差异:

  1. 定义的范围tf.variable_op_scopetf.variable_scope与之兼容tf.get_variable(忽略其他两个范围)
  2. tf.op_scopetf.variable_op_scope从指定变量列表中选择一个图形即可为其创建范围。除了他们的行为等于tf.name_scopetf.variable_scope相应地
  3. tf.variable_scopevariable_op_scope添加指定的或默认的初始化程序。

对于在其中创建指定变量的图形?这是否意味着像fabrizioM的上述示例那样,以tf.variable_op_scope([a,b],name,“ mysum2”)作为范围,此处参数a和b不受此函数的影响,并且此范围中定义的变量也受到影响?
杨秀仪

这两个问题的答案都是肯定的:在其中创建了指定变量且未对其进行修改的图形。
亚历山大·高尔班

这是否意味着tf.name_scope和tf.variable_scope仅在默认图形中使用,但是当您显然使用tf.Graph()定义和构造图形时,其他两个函数tf.op_scope和tf.variable_op_scope不能用于这个图!
杨秀仪

12

让我们简单点:只使用tf.variable_scope引用TF开发人员

当前,除了内部代码和库,我们建议每个人都使用variable_scope而不是使用name_scope

除了variable_scope的功能基本上扩展了的功能之外name_scope,请考虑一下它们如何不能很好地配合使用:

with tf.name_scope('foo'):
  with tf.variable_scope('bar'):
    x = tf.get_variable('x', shape=())
    x2 = tf.square(x**2, name='x2')
print(x.name)
# bar/x:0
print(x2.name)
# foo/bar/x2:0

通过坚持variable_scope只可以避免由于这种不兼容而引起的一些麻烦。


9

至于API r0.11,op_scope并且variable_op_scope不赞成name_scopevariable_scope可以嵌套:

with tf.name_scope('ns'):
    with tf.variable_scope('vs'): #scope creation
        v1 = tf.get_variable("v1",[1.0])   #v1.name = 'vs/v1:0'
        v2 = tf.Variable([2.0],name = 'v2')  #v2.name= 'ns/vs/v2:0'
        v3 = v1 + v2       #v3.name = 'ns/vs/add:0'

8

您可以将它们分为两组:variable_op_scopeop_scope接受一组变量作为输入并设计为创建操作。区别在于它们如何影响变量的创建tf.get_variable

def mysum(a,b,name=None):
    with tf.op_scope([a,b],name,"mysum") as scope:
        v = tf.get_variable("v", 1)
        v2 = tf.Variable([0], name="v2")
        assert v.name == "v:0", v.name
        assert v2.name == "mysum/v2:0", v2.name
        return tf.add(a,b)

def mysum2(a,b,name=None):
    with tf.variable_op_scope([a,b],name,"mysum2") as scope:
        v = tf.get_variable("v", 1)
        v2 = tf.Variable([0], name="v2")
        assert v.name == "mysum2/v:0", v.name
        assert v2.name == "mysum2/v2:0", v2.name
        return tf.add(a,b)

with tf.Graph().as_default():
    op = mysum(tf.Variable(1), tf.Variable(2))
    op2 = mysum2(tf.Variable(1), tf.Variable(2))
    assert op.name == 'mysum/Add:0', op.name
    assert op2.name == 'mysum2/Add:0', op2.name

注意v两个示例中的变量名称。

tf.name_scope和相同tf.variable_scope

with tf.Graph().as_default():
    with tf.name_scope("name_scope") as scope:
        v = tf.get_variable("v", [1])
        op = tf.add(v, v)
        v2 = tf.Variable([0], name="v2")
        assert v.name == "v:0", v.name
        assert op.name == "name_scope/Add:0", op.name
        assert v2.name == "name_scope/v2:0", v2.name

with tf.Graph().as_default():
    with tf.variable_scope("name_scope") as scope:
        v = tf.get_variable("v", [1])
        op = tf.add(v, v)
        v2 = tf.Variable([0], name="v2")
        assert v.name == "name_scope/v:0", v.name
        assert op.name == "name_scope/Add:0", op.name
        assert v2.name == "name_scope/v2:0", v2.name

您可以在本教程中阅读有关变量作用域的更多信息。在堆栈溢出之前也曾提出过类似的问题。


2

从tensorflow文档的该页面的最后一节:在OPS的名称tf.variable_scope()

[...]执行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"

除了变量作用域之外,还可以打开名称作用域,然后它们将仅影响操作的名称,而不影响变量的名称。

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"

当使用捕获的对象而不是字符串打开变量范围时,我们不会更改ops的当前名称范围。


2

Tensorflow 2.0兼容的答案Andrzej PronobisSalvador Dali相关的功能的解释非常详细Scope

上面讨论的范围函数中,截至(2020年2月17日)有效的是variable_scopename_scope

为社区的利益,我们在上面讨论了为这些功能指定2.0兼容的调用。

1.x中的功能

tf.variable_scope

tf.name_scope

2.x中的相应功能

tf.compat.v1.variable_scope

tf.name_scopetf.compat.v2.name_scope如果从迁移1.x to 2.x

有关从1.x到2.x迁移的更多信息,请参阅此迁移指南

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.