tensorflow中的共享变量及变量命名空间name_scope与variable_scope

在构建模型时,我们需要用到变量,在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.Variabletf.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()会自动检测有没有变量重名,如果有则会自行处理。

小结

  1. tf.Variable()所创建的变量受name_scope的层级控制,而tf.get_variable()则不受name_scope的层级控制。

  2. 同一name_scope下的tf.Variable()同名变量会被自动进行设置别名,而tf.get_variable()同名变量则报错。不同name_scope下的tf.Variable()同名变量,其完整变量名不同(因为name_scope不同),故它们不是同一变量。

  3. 要设置tf.get_variable()同名变量,需要在variable_scope下声明共享。variable_scope下声明共享后,tf.Variable()同名变量指向两个不同变量实体,而tf.get_variable ()同名变量则指向同一个变量实体。

  4. tf.Variable()的变量名称是可选参数,而tf.get_variable()的变量名称是必填参数。

参考:
tensorflow里面name_scope, variable_scope等如何理解?

你可能感兴趣的:(tensorflow中的共享变量及变量命名空间name_scope与variable_scope)