在构建模型时,我们需要用到变量,在tensorflow中有两种方式来定义一个变量:tf.Variable()
和tf.get_variable()
。一般情况下,使用tf.Variable()
可以很简单的定义一个变量,但有时候我们需要复用或者共享一些变量,这时候就需要用到tf.get_variable()
了。
1、使用tf.get_variable
来获取变量
tf.get_variable()
一般会配合variable_scope
一起使用,以实现变量共享。variable_scope
就是变量作用域。在某一作用域中的变量可以被设置成共享的方式,被其他网络模型使用。
tf.get_variable(name,
shape=None,
dtype=None,
initializer=None,
regularizer=None,
trainable=None,
collections=None,
caching_device=None,
partitioner=None,
validate_shape=True,
use_resource=None,
custom_getter=None,
constraint=None,
synchronization=,
aggregation=)
使用tf.get_variable()
生成的变量是以指定的name
属性为唯一标识,并不是定义的变量名称,使用时一般通过name
属性定位到具体变量,并进行共享。
栗子1:
分别使用tf.Variable
和tf.get_variable
来定义变量。
tf.reset_default_graph()
v1 = tf.Variable(1.0, name='firstvar')
print('v1', v1.name)
v1 = tf.Variable(2.0, name='firstvar')
print('v1', v1.name)
v2 = tf.Variable(3.0)
print('v2', v2.name)
v2 = tf.Variable(4.0)
print('v2', v2.name)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print('v1 value',v1.eval())
print('v2 value', v2.eval())
# 输出:
v1 firstvar:0
v1 firstvar_1:0
v2 Variable:0
v2 Variable_1:0
v1 value 2.0
v2 value 4.0
在上面我们分别定义了两次v1和两次v2,可以看到在内存中的确是生成了两个不同名字的v1和v2,但是在图会话中最后只会用到后面的那个。
而当你没有指定变量名称时,系统会自动给你指定变量名称。如v2的Variable:0和v2 Variable_1:0。
下面我们在使用tf.get_variable()
来定义变量:
get_v1 = tf.get_variable(name='firstvar',shape=[1],initializer=tf.constant_initializer(0.3))
print('get_v1', get_v1.name)
get_v1 = tf.get_variable(name='firstvar',shape=[1],initializer=tf.constant_initializer(0.4))
print('get_v1',get_v1.name)
# 输出:
get_v1 firstvar_2:0
ValueError: Variable firstvar already exists, disallowed.
Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope? Originally defined at: ...
可以看到,在使用tf.get_variable
定义变量时,定义的第二个变量由于与第一个变量重名发生错误。同时由于我们在前面使用tf.Variable()
定义了两个firstvar变量,因此这里的firstvar的后缀变为了2.
而如果我们对第二个get_v1变量的name稍作更改,便能正常运行了。
get_v1 = tf.get_variable(name='firstvar',shape=[1],initializer=tf.constant_initializer(0.3))
print('get_v1', get_v1.name)
get_v1 = tf.get_variable(name='firstvar1',shape=[1],initializer=tf.constant_initializer(0.4))
print('get_v1',get_v1.name)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(get_v1.eval())
# 输出:
get_v1 firstvar_2:0
get_v1 firstvar1:0
[0.4]
从上面还可以看到,系统依然只识别最后一个定义的get_v1变量。
2、在特定作用域下获取变量
在前面我们知道了使用tf.get_variable
连续定义两个相同名称的变量是行不通的,但是我们可以使用变量作用域variable_scope
来实现这个目的。
tf.reset_default_graph()
with tf.variable_scope(name_or_scope='test1') as scope:
get_v1 = tf.get_variable(name='firstvar',shape=[1],initializer=tf.constant_initializer(0.3))
print('get_v1', get_v1.name)
with tf.variable_scope(name_or_scope='test2') as scope:
get_v1 = tf.get_variable(name='firstvar',shape=[1],initializer=tf.constant_initializer(0.4))
print('get_v1', get_v1.name)
with tf.variable_scope(name_or_scope='test3') as scope:
get_v1 = tf.get_variable(name='firstvar',shape=[1],initializer=tf.constant_initializer(0.5))
print('get_v1',get_v1.name)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(get_v1.eval())
#输出:
get_v1 test1/firstvar:0
get_v1 test2/firstvar:0
get_v1 test2/test3/firstvar:0
[0.5]
从上面可以看到,即便是同名,不同variable_scope
下面使用tf.get_variable
定义变量也是可以行得通的。而且从变量名可以看出他们是属于不同的变量作用域的。
3、共享变量功能的实现
使用变量作用域中的reuse
参数可以实现变量共享功能。当设置variable_scope
中的reuse=True
时,表示这个变量已经存在,我们不是去定义而是去获取这个同名变量。
with tf.variable_scope(name_or_scope='test1', reuse=True) as scope:
get_v1 = tf.get_variable(name='firstvar', shape=[1])
print('get_v1', get_v1.name)
with tf.variable_scope(name_or_scope='test2', reuse=True) as scope:
get_v1 = tf.get_variable(name='firstvar', shape=[1])
print('get_v1', get_v1.name)
with tf.variable_scope(name_or_scope='test3', reuse=True) as scope:
get_v1 = tf.get_variable(name='firstvar', shape=[1])
print('get_v1', get_v1.name)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(get_v1.eval())
# 输出:
get_v1 test1/firstvar:0
get_v1 test2/firstvar:0
get_v1 test2/test3/firstvar:0
[0.5]
在上面的代码中我们没有给变量赋值而是获取已有变量,结果与第二节中的一致,这时就已经实现了变量共享。
在模型载入时,我们可以使用variable_scope
配合tf.get_variable
来载入已经训练好的变量参数。
初始化变量共享作用域内变量的默认值。除了指定作用域共享变量外,variable_scope
还提供了一个默认初始化参数可以给其作用域下面定义的参数给定默认值,但是当在get_variable
中给定变量值时,会覆盖variable_scope
给定的默认值。
tf.reset_default_graph()
with tf.variable_scope(name_or_scope='test1', initializer=tf.constant_initializer(0.1)) as scope:
get_v1 = tf.get_variable(name='firstvar', shape=[1])
print('get_v1', get_v1.name)
with tf.variable_scope(name_or_scope='test2', initializer=tf.constant_initializer(0.2)) as scope:
get_v2 = tf.get_variable(name='firstvar', shape=[1])
print('get_v2', get_v2.name)
with tf.variable_scope(name_or_scope='test3', initializer=tf.constant_initializer(0.3)) as scope:
get_v3 = tf.get_variable(name='firstvar', shape=[1], initializer=tf.constant_initializer(0.8))
print('get_v3', get_v3.name)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(get_v1.eval())
print(get_v2.eval())
print(get_v3.eval())
# 输出:
get_v1 test1/firstvar:0
get_v2 test2/firstvar:0
get_v3 test2/test3/firstvar:0
[0.1]
[0.2]
[0.8]
现在,我们再来看一个有意思的地方,当我们外层不使用with tf.variable_scope() as scope
而内层使用时,会发现外层的作用域限制不在起作用:
tf.reset_default_graph()
with tf.variable_scope(name_or_scope='test1', initializer=tf.constant_initializer(0.1)) as scope:
get_v1 = tf.get_variable(name='firstvar', shape=[1])
print('get_v1', get_v1.name)
print('scope name', scope.name)
with tf.variable_scope(name_or_scope='test2', initializer=tf.constant_initializer(0.2)):
get_v2 = tf.get_variable(name='firstvar', shape=[1])
print('get_v2', get_v2.name)
with tf.variable_scope(name_or_scope=scope, initializer=tf.constant_initializer(0.3)) as scope1:
get_v3 = tf.get_variable(name='firstvar', shape=[1], initializer=tf.constant_initializer(0.8))
print('get_v3', get_v3.name)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(get_v1.eval())
print(get_v2.eval())
print(get_v3.eval())
# 输出:
get_v1 test1/firstvar:0
scope name test1
get_v2 test2/firstvar:0
ValueError: Variable test1/firstvar already exists, disallowed.
Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope? Originally defined at ...
可以看到,我们在内部使用with ... as ...
而外部不使用时,外层的作用域是不起作用的。下面的get_v3其实就是上面test1中的firstvar,而由于firstvar已经定义,因此此处报错了,我们如果设置reuse=True
则会正常运行。
4、操作符(operation)作用域name_scope
name_scope
能对op起作用域限制作用但无法对变量起作用域限制。而op则是不仅受到name_scope
的限制还受到variable_scope
的限制。
tf.reset_default_graph()
with tf.variable_scope('all_scope'):
with tf.name_scope('op_scope'):
v = tf.get_variable('v1', shape=[1], initializer=tf.constant_initializer(1))
op = 1+v
print('v', v.name)
print('op', op.name)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(op.eval())
#输出
v all_scope/v1:0
op all_scope/op_scope/add:0
[2.]
此外,name_scope
还可以通过空字符串将作用域返回到顶层。而variable_scope
则是无此作用。
tf.reset_default_graph()
with tf.variable_scope('top'):
v_top = tf.get_variable(
'v_top', shape=[1], initializer=tf.constant_initializer(1))
print('v_top', v_top.name)
with tf.variable_scope('second'):
v_down = tf.get_variable(
'v_second', shape=[1], initializer=tf.constant_initializer(2))
print('v_down', v_down.name)
with tf.variable_scope(''):
v_null = tf.get_variable(
'v_null', shape=[1], initializer=tf.constant_initializer(3))
print('v_null', v_null.name)
with tf.variable_scope('last') as sp:
v_last = tf.get_variable(
'v_last',
shape=[1],
initializer=tf.constant_initializer(4))
print('v_last', v_last.name)
with tf.name_scope(''):
v_op = tf.get_variable(
'v_op',
shape=[1],
initializer=tf.constant_initializer(5))
print('v_op', v_op.name)
op = v_top + v_op
print('op', op.name)
# 输出:
v_top top/v_top:0
v_down top/second/v_second:0
v_null top/second//v_null:0
v_last top/second//last/v_last:0
v_op top/second//last/v_op:0
op add:0
最后,虽然tf.name_scope
无法对tf.get_variable
起限制作用,但是它可以对tf.Variable()
起限制作用。
tf.reset_default_graph()
with tf.name_scope('a'):
a = tf.Variable('va',1)
ga = tf.get_variable('gva',shape=[1],initializer=tf.constant_initializer(2))
print('a', a.name)
print('ga',ga.name)
#输出:
a a/Variable:0
ga gva:0
在 tf.name_scope
下时,tf.get_variable()
创建的变量名不受 name_scope
的影响,而且在未指定共享变量时,如果重名会报错,tf.Variable()
会自动检测有没有变量重名,如果有则会自行处理。
小结
tf.Variable()
所创建的变量受name_scope
的层级控制,而tf.get_variable()
则不受name_scope
的层级控制。同一
name_scope
下的tf.Variable()
同名变量会被自动进行设置别名,而tf.get_variable()
同名变量则报错。不同name_scope
下的tf.Variable()
同名变量,其完整变量名不同(因为name_scope
不同),故它们不是同一变量。要设置
tf.get_variable()
同名变量,需要在variable_scope
下声明共享。variable_scope
下声明共享后,tf.Variable()
同名变量指向两个不同变量实体,而tf.get_variable ()
同名变量则指向同一个变量实体。tf.Variable()
的变量名称是可选参数,而tf.get_variable()
的变量名称是必填参数。
参考:
tensorflow里面name_scope, variable_scope等如何理解?