张量流中名称范围和变量范围有啥区别?
Posted
技术标签:
【中文标题】张量流中名称范围和变量范围有啥区别?【英文标题】:What's the difference of name scope and a variable scope in tensorflow?张量流中名称范围和变量范围有什么区别? 【发布时间】:2016-06-25 10:43:30 【问题描述】:这些函数有什么区别?
tf.variable_op_scope(values, name, default_name, initializer=None)
返回一个上下文管理器,用于定义一个创建变量的操作。 此上下文管理器验证给定值是否来自同一个图,确保该图是默认图,并推送名称范围和变量范围。
tf.op_scope(values, name, default_name=None)
返回一个上下文管理器以在定义 Python 操作时使用。 此上下文管理器验证给定值是否来自同一个图,确保该图是默认图,并推送名称范围。
tf.name_scope(name)
Graph.name_scope()
的包装器使用默认图表。 详情请见Graph.name_scope()
。
tf.variable_scope(name_or_scope, reuse=None, initializer=None)
返回变量范围的上下文。 变量范围允许创建新变量并共享已创建的变量,同时提供检查以防止意外创建或共享。有关详细信息,请参阅如何使用变量作用域,这里我们仅提供几个基本示例。
【问题讨论】:
What is the difference between variable_scope and name_scope?的可能重复 【参考方案1】:让我们从变量共享的简短介绍开始。这是TensorFlow
中的一种机制,允许共享在代码的不同部分访问的变量,而无需传递对变量的引用。
tf.get_variable
方法可以与变量名称一起用作参数,以创建具有该名称的新变量或检索之前创建的变量。这与使用 tf.Variable
构造函数不同,后者在每次调用时都会创建一个新变量(如果已存在具有该名称的变量,则可能会在变量名称中添加一个后缀)。
为了实现变量共享机制,引入了一种单独的作用域(变量作用域)。
因此,我们最终得到了两种不同类型的作用域:
名称范围,使用tf.name_scope
创建
变量范围,使用tf.variable_scope
创建
两个作用域对所有操作以及使用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_scope
和 variable_op_scope
都是 deprecated 并被 name_scope
和 variable_scope
替换。
【讨论】:
感谢您的明确解释。自然地,后续问题会是“为什么Tensorflow 有这两种令人困惑的相似机制?为什么不只用一个scope
方法替换它们,它可以有效地执行variable_scope
?”
我不认为我从概念上理解为什么 variable_scope
与 name_scope
之间的区别甚至是必要的。如果创建一个变量(以任何方式使用tf.Variable
或tf.get_variable
),对我来说,如果我们指定范围或其全名,我们应该总是能够得到它似乎更自然。我不明白为什么一个忽略范围名称,而另一个忽略。你了解这种奇怪行为的原因吗?
原因是使用变量作用域,可以为不受当前用于定义操作的名称作用域影响的可重用变量定义单独的作用域。
你好,你能解释一下为什么variable_scope中的变量名总是以:0结尾吗?这是否意味着变量名可能以 :1、:2 等结尾,那怎么会发生呢?
@JamesFan 每个“声明”都是一个操作,所以当你说 a = tf.Variable(..name) 你会得到一个张量,但它实际上也创建了一个操作。如果你打印 a,你会得到一个 :0 的张量。如果您打印 a.op,您将获得计算该张量值的操作。【参考方案2】:
variable_op_scope 和 op_scope 现在都已弃用,根本不应使用。
关于其他两个,在我尝试通过创建一个简单的示例来可视化所有内容之前,我也无法理解 variable_scope 和 name_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()
为所有变量(无论您如何创建它们)、操作、常量的名称添加了前缀。另一方面,tf.name_scope()
会忽略使用tf.get_variable()
创建的变量,因为它假定您知道要使用哪个变量以及在哪个范围内。
Sharing variables 上的一个很好的文档告诉你
tf.variable_scope()
:管理传递给tf.get_variable()
的名称的命名空间。
相同的文档提供了变量范围如何工作以及何时有用的更多详细信息。
【讨论】:
用示例和视觉效果给出了绝妙的答案,让我们得到这个答案的人!【参考方案3】:命名空间是一种以分层方式组织变量和运算符名称的方法(例如“scopeA/scopeB/scopeC/op1”)
tf.name_scope
为默认图中的运算符创建命名空间。
tf.variable_scope
在默认图中为变量和运算符创建命名空间。
tf.op_scope
与 tf.name_scope
相同,但用于创建指定变量的图形。
tf.variable_op_scope
与 tf.variable_scope
相同,但用于创建指定变量的图形。
上述来源的链接有助于消除此文档问题的歧义。
This example 表明所有类型的作用域都为变量和运算符定义了命名空间,但有以下区别:
tf.variable_op_scope
或tf.variable_scope
定义的范围与tf.get_variable
兼容(它忽略了另外两个范围)
tf.op_scope
和 tf.variable_op_scope
只需从指定变量列表中选择一个图表即可为其创建范围。除了他们的行为等于tf.name_scope
和tf.variable_scope
相应
tf.variable_scope
和 variable_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 不能在这个图中使用!【参考方案4】:让我们简单点:只需使用tf.variable_scope
。 Quoting a TF developer,:
目前,我们建议大家使用
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
可以避免你因此而感到头疼。
【讨论】:
【参考方案5】:
至于 API r0.11,op_scope
和 variable_op_scope
都是 deprecated。
name_scope
和 variable_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'
【讨论】:
【参考方案6】:您可以将它们视为两组:variable_op_scope
和op_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
您可以在tutorial 中阅读有关变量范围的更多信息。 类似的问题是 Stack Overflow 上的 asked before。
【讨论】:
【参考方案7】:来自 tensorflow 文档本页的最后一部分:Names of ops in 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"
当使用捕获的对象而不是字符串打开变量范围时,我们不会更改操作的当前名称范围。
【讨论】:
【参考方案8】:Tensorflow 2.0 Compatible Answer:Andrzej Pronobis
和Salvador Dali
的解释非常详细,关于Scope
相关的函数。
截至目前(2020 年 2 月 17 日)有效的上述范围功能包括 variable_scope
和 name_scope
。
为我们上面讨论过的那些函数指定 2.0 兼容调用,以造福社区。p>
1.x 中的功能:
tf.variable_scope
tf.name_scope
2.x 中的相应功能:
tf.compat.v1.variable_scope
tf.name_scope
(tf.compat.v2.name_scope
如果从1.x to 2.x
迁移)
有关从 1.x 迁移到 2.x 的更多信息,请参阅Migration Guide。
【讨论】:
以上是关于张量流中名称范围和变量范围有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章