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=<VariableSynchronization.AUTO: 0>, 
                aggregation=<VariableAggregation.NONE: 0>)

使用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等如何理解?

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容