name_scope与variable_scope 详解
[参考文献]:
3.tf.variable_scope和tf.name_scope的用法
1.scope是干什么的
顾名思义“scope”的意思是“范围”,那么name_scope和variable_scope就是针对name所做的范围定义。典型的 TensorFlow 可以有数以千计的节点,在构建各op的过程中,命名要做到不重复,那么在编写程序的时候就要特别注意,例如“x”、“y”、”W”、”B”甚至“weight”等经常使用的变量/常量命名就很可能会重复,否则就要增加一个字符串来表示不同的op所属,例如在x前加train成为train_x,在x前加scan成为scan_x等等,一方面表明此变量、常量所属“范围”的标识,这个name_scope和variable_scope就是干这个事情的,另一方面是从name上保证此变量命名的唯一性。有了这个scope的存在,其中描述的变量和常量就会在机器当中自动给添加前缀描述,作为对各个变量、常量的区分。在这里的编程和以前使用C、C++不同之处就是,C/C++当中的变量名实际相当于我们这里的XXX.name()而不是XXX,计算机区分的是XXX.name()。简单说,有了scope,那么在tensorflow当中就是用op/tensor名来划定范围,实现对变量或者常量名的唯一性定义,避免冲突。
2.scope是怎么干的
接下来我们要分析的问题是:究竟name_scope()和variable_scope()对可以产生变量的tf.get_variable()和tf.Variable()会有什么不同?为了一探究竟,接下来做一些测试:
2.1 tf.name_scope对tf.get_variable()的影响
import tensorflow as tf
with tf.name_scope("a_name_scope") as myscope:
initializer = tf.constant_initializer(value=1)
var1 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # var1:0
print(sess.run(var1)) # [ 1.]
var1:0
[ 1.]
[结论]:
name_scope()没有对 tf.get_variable()产生的变量增加“范围”。于是,如果再有相同的变量名生成,即便name_scope使用了新的名字“a_name_scope_new”,仍旧会破坏“唯一性”,从而导致出错“该变量已经定义过了!”,试验一下:
with tf.name_scope("a_name_scope_new") as myscope:
initializer = tf.constant_initializer(value=1)
var1 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)
---------------------------------------------------------------------------
ValueError: Variable var1 already exists, disallowed. Did you mean to set reuse=True in VarScope?
【结论】:
果然出错了!“Variable var1 already exists, disallowed.”,说明name_scope没有对get_variable()增加“范围”。那我现在还想生成一个新的变量var2而且要让其内容与var1相同怎么办?
with tf.name_scope("a_name_scope") as myscope:
#initializer = tf.constant_initializer(value=1)
var2 = tf.get_variable(name='var2', dtype=tf.float32, initializer=var1)
#var2=var1
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # var1:0
print(sess.run(var1)) # [ 1.]
print(var2.name) # var2:0
print(sess.run(var2)) # [ 1.]
var1:0
[ 1.]
var2:0
[ 1.]
【结论】:
看结果,的确生成了新的变量var2。如果我不想生成新的变量var2,而只想做个变量的名称更改怎么办?注意,这里就和C、C++有不同了。
- 1
with tf.name_scope("a_name_scope") as myscope:
var3=var1
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # var1:0
print(sess.run(var1)) # [ 1.]
print(var3.name) # var1:0
print(sess.run(var3)) # [ 1.]
var1:0
[ 1.]
var1:0
[ 1.]
【结论】:
虽然有一个变量var3,但是其name和值都是与var1相同的,计算机认的是name,认为这两个是完全一样的,并不是新建,只是引用,我们可以理解为是C语言中的指针,指针的名字可以千奇百怪,但是其指向的地址内容才是其真正的归属。所以,这个变量var3你可以随时设置,不会引起重复性、唯一性的错误——“Variable var1 already exists, disallowed.”。
- 1
变量的名称只是指针名,变量的name是地址
with tf.name_scope("a_name_scope") as myscope:
var3=var1
【结论】:
的确没有引起错误提示。
2.2 variable_scope对get_variable的影响
那么接着分析,既然name_scope对get_variable没有增加“范围”,那variable_scope呢?
with tf.variable_scope("a_variable_scope") as myscope:
initializer = tf.constant_initializer(value=1)
var1 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # a_variable_scope/var1:0
print(sess.run(var1)) # [ 1.]
a_variable_scope/var1:0
[ 1.]
【结论】:variable_scope对get_variable有命名的影响
看到var1指向的内容被新建了,其name变成了“a_variable_scope/var1:0”,也就是说对计算机而言这已经是一个新的内容了,需要一个新的内存来存储,这个和之前的var1.name = var1:0已经不是一个东西了.同时,我们想要找到之前的var1的name已经是不能够了,这个完全就是C/C++指针的意思,指针指向了一个新的内存,如果不使用一个新的指针指向旧的地址,那么就会导致旧地址的内容无法检索了。验证一下:
- 1
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # a_variable_scope/var1:0
print(sess.run(var1)) # [ 1.]
print(var3.name) # var1:0
print(sess.run(var3)) # [ 1.]
a_variable_scope/var1:0
[ 1.]
var1:0
[ 1.]
【结论】: 变量名就是指针,变量的name属性是地址
var3是前面对旧的var1的内容的指针,虽然在前面var1指向了新建的内容(a_variable_scope/var1:0),但是原有内容的指针给了var3,所以现在仍旧可以通过指针找到旧地址内容。前面看到了,使用variable_scope能够产生新的变量,即增加了“范围”标识的变量,那么这个能否再次执行产生新的变量呢?也就是说,新产生的name为(a_variable_scope/var1:0)的地址能否再次被新建?
with tf.variable_scope("a_variable_scope") as myscope:
initializer = tf.constant_initializer(value=1)
var1 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
ValueError: Variable a_variable_scope/var1 already exists, disallowed. Did you mean to set reuse=True in VarScope?
【结论】:get_variable取值的唯一性仍旧需要保证
可见虽然variable_scope可以增加name“范围”,但是get_variable取值的唯一性仍旧需要维护,否则就会出错,你是不可以任性新建的。那我现在就是想重用此变量,我就是想更改一个变量名而已,行吗?当然行,之前我们就采用了直接变量名赋值就行了“var3=var1”,这样变量名改为了var3,但是其name仍旧是var1的name,从c理解就是,指针随便建,地址唯一不变,街道名你随便改,可是街道你不能随便拆改。那还有一种材料中介绍的方法,就是使用reuse_variables(),但是一旦重用的对象本身不存在就会报错。这里说的对象不是“指针”,而是指针地址,而且是带有variable_scope指定的“范围”的指针地址。
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # a_variable_scope/var1:0
print(sess.run(var1)) # [ 1.]
print(var2.name) # var2:0
print(sess.run(var2)) # [ 1.]
print(var3.name) # var1:0
print(sess.run(var3)) # [ 1.]
a_variable_scope/var1:0
[ 1.]
var2:0
[ 1.]
var1:0
[ 1.]
【分析】:
看看上面三个变量,如果在 tf.variable_scope(“a_variable_scope”)下则会增加“范围”——(a_variable_scope),显然,var1是已存在的对象,而var2虽然也是有已有的地址,但是如果在 tf.variable_scope当中使用get_variable来获取则会新建一个name为:(a_variable_scope/var2:0),所以var2不能当做是已有地址的变量来被重用,否则会出错。var1是可以被重用的,因为他的确是一个已有的范围内的地址。
with tf.variable_scope("a_variable_scope") as myscope:
initializer = tf.constant_initializer(value=1)
myscope.reuse_variables()
var2 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # a_variable_scope/var1:0
print(sess.run(var1)) # [ 1.]
print(var2.name) # a_variable_scope/var1:0
print(sess.run(var2)) # [ 1.]
a_variable_scope/var1:0
[ 1.]
a_variable_scope/var1:0
[ 1.]
**【结论】:**var1被var2重用了
with tf.variable_scope("a_variable_scope") as myscope:
initializer = tf.constant_initializer(value=1)
myscope.reuse_variables()
var2 = tf.get_variable(name='var2', shape=[1], dtype=tf.float32, initializer=initializer)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # a_variable_scope/var1:0
print(sess.run(var1)) # [ 1.]
print(var2.name) # a_variable_scope/var1:0
print(sess.run(var2)) # [ 1.]
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
ValueError: Variable a_variable_scope/var7 does not exist, or was not created with tf.get_variable(). Did you mean to set reuse=None in VarScope?
【结论】:
在上面var1被var2重用,这个是正确的,但是紧接着想重新新建一个就会立马报错的,说明这个重用操作“myscope.reuse_variables()”在with范围内都是生效的。如果name_scope下使用reuse_variables()则会报错。
with tf.name_scope("a_name_scope") as myscope:
initializer = tf.constant_initializer(value=1)
myscope.reuse_variables()
var1 = tf.get_variable(name='var1', shape=[1], dtype=tf.float32, initializer=initializer)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # var1:0
print(sess.run(var1)) # [ 1.]
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
AttributeError: 'str' object has no attribute 'reuse_variables'
2.3 name_scope()对variable的影响
with tf.name_scope("a_name_scope") as myscope:
var2 = tf.Variable(name='var2', initial_value=[2], dtype=tf.float32)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # var1:0
print(sess.run(var1)) # [ 1.]
print(var2.name) # a_name_scope/var2:0
print(sess.run(var2)) # [ 2.]
a_variable_scope/var1:0
[ 1.]
a_name_scope_5/var2:0
[ 2.]
【结论】:
很棒!name_scope对variable有效,新增了“范围”名。那这个范围名是否与get_variable一样有唯一性?
with tf.name_scope("a_name_scope") as myscope:
var2 = tf.Variable(name='var2', initial_value=[2], dtype=tf.float32)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # var1:0
print(sess.run(var1)) # [ 1.]
print(var2.name) # a_name_scope/var2:0
print(sess.run(var2)) # [ 2.]
a_variable_scope/var1:0
[ 1.]
a_name_scope_6/var2:0
[ 2.]
【结论】:
从上面结果看,唯一性仍旧保证了,原因是自动的将var2.name从(a_name_scope_1/var2:0)变为了(a_name_scope_2/var2:0).这里使用了“变为”这个词实际是错误的,因为根本不是变为,而是新建了一个name 为(a_name_scope_2/var2:0)的变量,这个变量和前面的那个变量的名都为var2但是name属性却是不同的。
2.4 variable_scope对variable的影响
with tf.variable_scope("my_variable_scope") as myscope:
var2 = tf.Variable(name='var2', initial_value=[initializer.value], dtype=tf.float32)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # var1:0
print(sess.run(var1)) # [ 1.]
print(var2.name) # a_name_scope/var2:0
print(sess.run(var2)) # [ 2.]
a_variable_scope/var1:0
[ 1.]
my_variable_scope/var2:0
[ 1.]
【结论】:
variable_scope对variable有效,而且从下面的运行结果看,唯一性可以得到保证,新建了一个新的变量,这点和name_scope一致。
- 1
with tf.variable_scope("my_variable_scope") as myscope:
var2 = tf.Variable(name='var2', initial_value=[initializer.value], dtype=tf.float32)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1.name) # var1:0
print(sess.run(var1)) # [ 1.]
print(var2.name) # a_name_scope/var2:0
print(sess.run(var2)) # [ 2.]
a_variable_scope/var1:0
[ 1.]
my_variable_scope_1/var2:0
[ 1.]
总结:
[1]. name_scope 对 get_variable新建变量的name属性无影响;对variable新建变量的name属性增加了“范围”标识。
[2]. variable_scope对get_variable新建变量的name属性和variable新建变量的name属性都增加了“范围”标识。
[3]. get_variable新建变量如果遇见重复的name则会因为重复而报错。
[4]. variable新建的变量如果遇见重复的name则会自动修改前缀,以避免重复出现。